summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBaligh Uddin <baligh@google.com>2013-11-01 16:01:55 -0700
committerBaligh Uddin <baligh@google.com>2013-11-01 16:01:55 -0700
commitec502fb532582da0f3141560bc451df3902ce463 (patch)
treebfd8e692b73dee4749734ca317b4707988dfae3a
parent5588ded0ae11d6fa36e1771747b82b7831db906b (diff)
parent53a521c76400a3e6d64dc96396390b746ec1e48e (diff)
downloadsrc-idea133.tar.gz
Merge remote-tracking branch 'origin/kitkat-dev'chromium_org-pre-replicationidea133
-rw-r--r--animator/SkAnimate.h34
-rw-r--r--animator/SkAnimate3DSchema.xsd39
-rw-r--r--animator/SkAnimate3DSchema.xsx3
-rw-r--r--animator/SkAnimateActive.cpp504
-rw-r--r--animator/SkAnimateActive.h79
-rw-r--r--animator/SkAnimateBase.cpp233
-rw-r--r--animator/SkAnimateBase.h83
-rw-r--r--animator/SkAnimateField.cpp111
-rw-r--r--animator/SkAnimateMaker.cpp372
-rw-r--r--animator/SkAnimateMaker.h160
-rw-r--r--animator/SkAnimateProperties.h21
-rw-r--r--animator/SkAnimateSchema.xsd2787
-rw-r--r--animator/SkAnimateSchema.xsx3
-rw-r--r--animator/SkAnimateSet.cpp87
-rw-r--r--animator/SkAnimateSet.h27
-rw-r--r--animator/SkAnimator.cpp706
-rw-r--r--animator/SkAnimatorScript.cpp594
-rw-r--r--animator/SkAnimatorScript.h75
-rw-r--r--animator/SkAnimatorScript2.cpp622
-rw-r--r--animator/SkAnimatorScript2.h50
-rw-r--r--animator/SkBoundable.cpp55
-rw-r--r--animator/SkBoundable.h41
-rw-r--r--animator/SkBuildCondensedInfo.cpp282
-rw-r--r--animator/SkCondensedDebug.cpp1387
-rw-r--r--animator/SkCondensedRelease.cpp1365
-rw-r--r--animator/SkDisplayAdd.cpp245
-rw-r--r--animator/SkDisplayAdd.h71
-rw-r--r--animator/SkDisplayApply.cpp804
-rw-r--r--animator/SkDisplayApply.h106
-rw-r--r--animator/SkDisplayBounds.cpp43
-rw-r--r--animator/SkDisplayBounds.h24
-rw-r--r--animator/SkDisplayEvent.cpp252
-rw-r--r--animator/SkDisplayEvent.h66
-rw-r--r--animator/SkDisplayEvents.cpp113
-rw-r--r--animator/SkDisplayEvents.h42
-rw-r--r--animator/SkDisplayInclude.cpp59
-rw-r--r--animator/SkDisplayInclude.h25
-rw-r--r--animator/SkDisplayInput.cpp55
-rw-r--r--animator/SkDisplayInput.h33
-rw-r--r--animator/SkDisplayList.cpp158
-rw-r--r--animator/SkDisplayList.h70
-rw-r--r--animator/SkDisplayMath.cpp240
-rw-r--r--animator/SkDisplayMath.h31
-rw-r--r--animator/SkDisplayMovie.cpp128
-rw-r--r--animator/SkDisplayMovie.h51
-rw-r--r--animator/SkDisplayNumber.cpp70
-rw-r--r--animator/SkDisplayNumber.h22
-rw-r--r--animator/SkDisplayPost.cpp298
-rw-r--r--animator/SkDisplayPost.h59
-rw-r--r--animator/SkDisplayRandom.cpp65
-rw-r--r--animator/SkDisplayRandom.h40
-rw-r--r--animator/SkDisplayScreenplay.cpp20
-rw-r--r--animator/SkDisplayScreenplay.h21
-rw-r--r--animator/SkDisplayType.cpp766
-rw-r--r--animator/SkDisplayType.h206
-rw-r--r--animator/SkDisplayTypes.cpp214
-rw-r--r--animator/SkDisplayTypes.h106
-rw-r--r--animator/SkDisplayXMLParser.cpp316
-rw-r--r--animator/SkDisplayXMLParser.h91
-rw-r--r--animator/SkDisplayable.cpp540
-rw-r--r--animator/SkDisplayable.h112
-rw-r--r--animator/SkDraw3D.cpp106
-rw-r--r--animator/SkDraw3D.h50
-rw-r--r--animator/SkDrawBitmap.cpp197
-rw-r--r--animator/SkDrawBitmap.h74
-rw-r--r--animator/SkDrawBlur.cpp31
-rw-r--r--animator/SkDrawBlur.h25
-rw-r--r--animator/SkDrawClip.cpp39
-rw-r--r--animator/SkDrawClip.h29
-rw-r--r--animator/SkDrawColor.cpp269
-rw-r--r--animator/SkDrawColor.h42
-rw-r--r--animator/SkDrawDash.cpp35
-rw-r--r--animator/SkDrawDash.h26
-rw-r--r--animator/SkDrawDiscrete.cpp34
-rw-r--r--animator/SkDrawDiscrete.h24
-rw-r--r--animator/SkDrawEmboss.cpp33
-rw-r--r--animator/SkDrawEmboss.h24
-rw-r--r--animator/SkDrawExtraPathEffect.cpp515
-rw-r--r--animator/SkDrawFull.cpp18
-rw-r--r--animator/SkDrawFull.h22
-rw-r--r--animator/SkDrawGradient.cpp226
-rw-r--r--animator/SkDrawGradient.h67
-rw-r--r--animator/SkDrawGroup.cpp321
-rw-r--r--animator/SkDrawGroup.h72
-rw-r--r--animator/SkDrawLine.cpp35
-rw-r--r--animator/SkDrawLine.h28
-rw-r--r--animator/SkDrawMatrix.cpp268
-rw-r--r--animator/SkDrawMatrix.h74
-rw-r--r--animator/SkDrawOval.cpp28
-rw-r--r--animator/SkDrawOval.h22
-rw-r--r--animator/SkDrawPaint.cpp269
-rw-r--r--animator/SkDrawPaint.h79
-rw-r--r--animator/SkDrawPath.cpp220
-rw-r--r--animator/SkDrawPath.h69
-rw-r--r--animator/SkDrawPoint.cpp44
-rw-r--r--animator/SkDrawPoint.h33
-rw-r--r--animator/SkDrawRectangle.cpp142
-rw-r--r--animator/SkDrawRectangle.h55
-rw-r--r--animator/SkDrawSaveLayer.cpp76
-rw-r--r--animator/SkDrawSaveLayer.h36
-rw-r--r--animator/SkDrawShader.cpp83
-rw-r--r--animator/SkDrawShader.h30
-rw-r--r--animator/SkDrawText.cpp55
-rw-r--r--animator/SkDrawText.h36
-rw-r--r--animator/SkDrawTextBox.cpp80
-rw-r--r--animator/SkDrawTextBox.h38
-rw-r--r--animator/SkDrawTo.cpp55
-rw-r--r--animator/SkDrawTo.h34
-rw-r--r--animator/SkDrawTransparentShader.cpp15
-rw-r--r--animator/SkDrawTransparentShader.h20
-rw-r--r--animator/SkDrawable.cpp24
-rw-r--r--animator/SkDrawable.h28
-rw-r--r--animator/SkDump.cpp150
-rw-r--r--animator/SkDump.h42
-rw-r--r--animator/SkExtraPathEffects.xsd33
-rw-r--r--animator/SkExtras.h34
-rw-r--r--animator/SkGetCondensedInfo.cpp121
-rw-r--r--animator/SkHitClear.cpp32
-rw-r--r--animator/SkHitClear.h25
-rw-r--r--animator/SkHitTest.cpp74
-rw-r--r--animator/SkHitTest.h30
-rw-r--r--animator/SkIntArray.h55
-rw-r--r--animator/SkMatrixParts.cpp292
-rw-r--r--animator/SkMatrixParts.h119
-rw-r--r--animator/SkMemberInfo.cpp559
-rw-r--r--animator/SkMemberInfo.h270
-rw-r--r--animator/SkOpArray.cpp23
-rw-r--r--animator/SkOpArray.h29
-rw-r--r--animator/SkOperand.h46
-rw-r--r--animator/SkOperand2.h54
-rw-r--r--animator/SkOperandInterpolator.h47
-rw-r--r--animator/SkOperandIterpolator.cpp149
-rw-r--r--animator/SkPaintParts.cpp101
-rw-r--r--animator/SkPaintParts.h75
-rw-r--r--animator/SkParseSVGPath.cpp234
-rw-r--r--animator/SkPathParts.cpp318
-rw-r--r--animator/SkPathParts.h164
-rw-r--r--animator/SkPostParts.cpp56
-rw-r--r--animator/SkPostParts.h31
-rw-r--r--animator/SkScript.cpp1895
-rw-r--r--animator/SkScript.h266
-rw-r--r--animator/SkScript2.h291
-rw-r--r--animator/SkScriptCallBack.h65
-rw-r--r--animator/SkScriptDecompile.cpp211
-rw-r--r--animator/SkScriptRuntime.cpp351
-rw-r--r--animator/SkScriptRuntime.h50
-rw-r--r--animator/SkScriptTokenizer.cpp1511
-rw-r--r--animator/SkSnapshot.cpp67
-rw-r--r--animator/SkSnapshot.h30
-rw-r--r--animator/SkTDArray_Experimental.h142
-rw-r--r--animator/SkTextOnPath.cpp39
-rw-r--r--animator/SkTextOnPath.h30
-rw-r--r--animator/SkTextToPath.cpp47
-rw-r--r--animator/SkTextToPath.h31
-rw-r--r--animator/SkTime.cpp80
-rw-r--r--animator/SkTypedArray.cpp179
-rw-r--r--animator/SkTypedArray.h31
-rw-r--r--animator/SkXMLAnimatorWriter.cpp82
-rw-r--r--animator/SkXMLAnimatorWriter.h33
-rw-r--r--animator/thingstodo.txt21
-rw-r--r--core/ARGB32_Clamp_Bilinear_BitmapShader.h177
-rw-r--r--core/Sk64.cpp354
-rw-r--r--core/SkAAClip.cpp2178
-rw-r--r--core/SkAAClip.h132
-rw-r--r--core/SkAdvancedTypefaceMetrics.cpp317
-rw-r--r--core/SkAlphaRuns.cpp178
-rw-r--r--core/SkAnnotation.cpp92
-rw-r--r--core/SkAntiRun.h92
-rw-r--r--core/SkAutoKern.h53
-rw-r--r--core/SkBBoxHierarchy.cpp11
-rw-r--r--core/SkBBoxHierarchy.h88
-rw-r--r--core/SkBBoxHierarchyRecord.cpp115
-rw-r--r--core/SkBBoxHierarchyRecord.h58
-rw-r--r--core/SkBBoxRecord.cpp284
-rw-r--r--core/SkBBoxRecord.h78
-rw-r--r--core/SkBitmap.cpp1709
-rw-r--r--core/SkBitmapFilter.cpp131
-rw-r--r--core/SkBitmapFilter.h175
-rw-r--r--core/SkBitmapHeap.cpp402
-rw-r--r--core/SkBitmapHeap.h307
-rw-r--r--core/SkBitmapProcShader.cpp397
-rw-r--r--core/SkBitmapProcShader.h51
-rw-r--r--core/SkBitmapProcState.cpp958
-rw-r--r--core/SkBitmapProcState.h229
-rw-r--r--core/SkBitmapProcState_filter.h125
-rw-r--r--core/SkBitmapProcState_matrix.h303
-rw-r--r--core/SkBitmapProcState_matrixProcs.cpp501
-rw-r--r--core/SkBitmapProcState_procs.h343
-rw-r--r--core/SkBitmapProcState_sample.h248
-rw-r--r--core/SkBitmapProcState_shaderproc.h94
-rw-r--r--core/SkBitmapScaler.cpp318
-rw-r--r--core/SkBitmapScaler.h108
-rw-r--r--core/SkBitmapShader16BilerpTemplate.h245
-rw-r--r--core/SkBitmapShaderTemplate.h306
-rw-r--r--core/SkBitmap_scroll.cpp123
-rw-r--r--core/SkBlitBWMaskTemplate.h128
-rw-r--r--core/SkBlitMask.h90
-rw-r--r--core/SkBlitMask_D32.cpp592
-rw-r--r--core/SkBlitRow_D16.cpp245
-rw-r--r--core/SkBlitRow_D32.cpp249
-rw-r--r--core/SkBlitter.cpp1016
-rw-r--r--core/SkBlitter.h174
-rw-r--r--core/SkBlitter_A1.cpp50
-rw-r--r--core/SkBlitter_A8.cpp345
-rw-r--r--core/SkBlitter_ARGB32.cpp638
-rw-r--r--core/SkBlitter_RGB16.cpp1055
-rw-r--r--core/SkBlitter_Sprite.cpp87
-rw-r--r--core/SkBuffer.cpp129
-rw-r--r--core/SkBuffer.h138
-rw-r--r--core/SkCanvas.cpp2222
-rw-r--r--core/SkChunkAlloc.cpp142
-rw-r--r--core/SkClipStack.cpp784
-rw-r--r--core/SkColor.cpp113
-rw-r--r--core/SkColorFilter.cpp46
-rw-r--r--core/SkColorTable.cpp159
-rw-r--r--core/SkComposeShader.cpp192
-rw-r--r--core/SkConfig8888.cpp280
-rw-r--r--core/SkConfig8888.h76
-rw-r--r--core/SkConvolver.cpp461
-rw-r--r--core/SkConvolver.h203
-rw-r--r--core/SkCordic.cpp289
-rw-r--r--core/SkCordic.h28
-rw-r--r--core/SkCoreBlitters.h187
-rw-r--r--core/SkCubicClipper.cpp162
-rw-r--r--core/SkCubicClipper.h34
-rw-r--r--core/SkData.cpp338
-rw-r--r--core/SkDataTable.cpp250
-rw-r--r--core/SkDebug.cpp44
-rw-r--r--core/SkDeque.cpp308
-rw-r--r--core/SkDescriptor.h164
-rw-r--r--core/SkDevice.cpp532
-rw-r--r--core/SkDeviceImageFilterProxy.h34
-rw-r--r--core/SkDeviceLooper.cpp114
-rw-r--r--core/SkDeviceLooper.h96
-rw-r--r--core/SkDeviceProfile.cpp79
-rw-r--r--core/SkDeviceProfile.h98
-rw-r--r--core/SkDither.cpp55
-rw-r--r--core/SkDraw.cpp2836
-rw-r--r--core/SkDrawLooper.cpp58
-rw-r--r--core/SkDrawProcs.h70
-rw-r--r--core/SkEdge.cpp497
-rw-r--r--core/SkEdge.h137
-rw-r--r--core/SkEdgeBuilder.cpp280
-rw-r--r--core/SkEdgeBuilder.h52
-rw-r--r--core/SkEdgeClipper.cpp531
-rw-r--r--core/SkEdgeClipper.h51
-rw-r--r--core/SkError.cpp143
-rw-r--r--core/SkErrorInternals.h27
-rw-r--r--core/SkFDot6.h60
-rw-r--r--core/SkFP.h79
-rw-r--r--core/SkFilterProc.cpp294
-rw-r--r--core/SkFilterProc.h135
-rw-r--r--core/SkFilterShader.cpp102
-rw-r--r--core/SkFilterShader.h41
-rw-r--r--core/SkFlate.cpp140
-rw-r--r--core/SkFlattenable.cpp127
-rw-r--r--core/SkFlattenableBuffers.cpp56
-rw-r--r--core/SkFlattenableSerialization.cpp28
-rw-r--r--core/SkFloat.cpp393
-rw-r--r--core/SkFloat.h109
-rw-r--r--core/SkFloatBits.cpp206
-rw-r--r--core/SkFontDescriptor.cpp79
-rw-r--r--core/SkFontDescriptor.h46
-rw-r--r--core/SkFontHost.cpp246
-rw-r--r--core/SkFontStream.cpp210
-rw-r--r--core/SkFontStream.h49
-rw-r--r--core/SkGeometry.cpp1666
-rw-r--r--core/SkGlyph.h150
-rw-r--r--core/SkGlyphCache.cpp788
-rw-r--r--core/SkGlyphCache.h301
-rw-r--r--core/SkGraphics.cpp180
-rw-r--r--core/SkImageFilter.cpp171
-rw-r--r--core/SkImageFilterUtils.cpp46
-rw-r--r--core/SkInstCnt.cpp12
-rw-r--r--core/SkLineClipper.cpp297
-rw-r--r--core/SkMallocPixelRef.cpp66
-rw-r--r--core/SkMask.cpp76
-rw-r--r--core/SkMaskFilter.cpp356
-rw-r--r--core/SkMaskGamma.cpp124
-rw-r--r--core/SkMaskGamma.h221
-rw-r--r--core/SkMath.cpp517
-rw-r--r--core/SkMathPriv.h81
-rw-r--r--core/SkMatrix.cpp2057
-rw-r--r--core/SkMatrixUtils.h54
-rw-r--r--core/SkMemory_stdlib.cpp267
-rw-r--r--core/SkMetaData.cpp336
-rw-r--r--core/SkMipMap.cpp262
-rw-r--r--core/SkMipMap.h43
-rw-r--r--core/SkOrderedReadBuffer.cpp314
-rw-r--r--core/SkOrderedReadBuffer.h138
-rw-r--r--core/SkOrderedWriteBuffer.cpp314
-rw-r--r--core/SkOrderedWriteBuffer.h117
-rw-r--r--core/SkPackBits.cpp411
-rw-r--r--core/SkPaint.cpp2611
-rw-r--r--core/SkPaintDefaults.h35
-rw-r--r--core/SkPaintOptionsAndroid.cpp44
-rw-r--r--core/SkPaintPriv.cpp78
-rw-r--r--core/SkPaintPriv.h25
-rw-r--r--core/SkPath.cpp3013
-rw-r--r--core/SkPathEffect.cpp81
-rw-r--r--core/SkPathHeap.cpp63
-rw-r--r--core/SkPathHeap.h50
-rw-r--r--core/SkPathMeasure.cpp542
-rw-r--r--core/SkPathRef.h551
-rw-r--r--core/SkPerspIter.h48
-rw-r--r--core/SkPicture.cpp350
-rw-r--r--core/SkPictureFlat.cpp152
-rw-r--r--core/SkPictureFlat.h807
-rw-r--r--core/SkPicturePlayback.cpp1618
-rw-r--r--core/SkPicturePlayback.h228
-rw-r--r--core/SkPictureRecord.cpp1481
-rw-r--r--core/SkPictureRecord.h249
-rw-r--r--core/SkPictureStateTree.cpp191
-rw-r--r--core/SkPictureStateTree.h146
-rw-r--r--core/SkPixelRef.cpp213
-rw-r--r--core/SkPoint.cpp517
-rw-r--r--core/SkProcSpriteBlitter.cpp47
-rw-r--r--core/SkPtrRecorder.cpp77
-rw-r--r--core/SkPtrRecorder.h151
-rw-r--r--core/SkQuadClipper.cpp128
-rw-r--r--core/SkQuadClipper.h70
-rw-r--r--core/SkRRect.cpp361
-rw-r--r--core/SkRTree.cpp481
-rw-r--r--core/SkRTree.h193
-rw-r--r--core/SkRasterClip.cpp279
-rw-r--r--core/SkRasterClip.h162
-rw-r--r--core/SkRasterizer.cpp50
-rw-r--r--core/SkRect.cpp183
-rw-r--r--core/SkRefCnt.cpp13
-rw-r--r--core/SkRefDict.cpp89
-rw-r--r--core/SkRefDict.h54
-rw-r--r--core/SkRegion.cpp1481
-rw-r--r--core/SkRegionPriv.h233
-rw-r--r--core/SkRegion_path.cpp501
-rw-r--r--core/SkRegion_rects.cpp290
-rw-r--r--core/SkScalar.cpp36
-rw-r--r--core/SkScaledImageCache.cpp525
-rw-r--r--core/SkScaledImageCache.h119
-rw-r--r--core/SkScalerContext.cpp941
-rw-r--r--core/SkScalerContext.h308
-rw-r--r--core/SkScan.cpp117
-rw-r--r--core/SkScan.h138
-rw-r--r--core/SkScanPriv.h40
-rw-r--r--core/SkScan_AntiPath.cpp746
-rw-r--r--core/SkScan_Antihair.cpp1064
-rw-r--r--core/SkScan_Hairline.cpp419
-rw-r--r--core/SkScan_Path.cpp729
-rw-r--r--core/SkShader.cpp362
-rw-r--r--core/SkSinTable.h277
-rw-r--r--core/SkSpriteBlitter.h46
-rw-r--r--core/SkSpriteBlitterTemplate.h82
-rw-r--r--core/SkSpriteBlitter_ARGB32.cpp313
-rw-r--r--core/SkSpriteBlitter_RGB16.cpp377
-rw-r--r--core/SkStream.cpp846
-rw-r--r--core/SkString.cpp659
-rw-r--r--core/SkStringUtils.cpp19
-rw-r--r--core/SkStroke.cpp725
-rw-r--r--core/SkStroke.h56
-rw-r--r--core/SkStrokeRec.cpp105
-rw-r--r--core/SkStrokerPriv.cpp264
-rw-r--r--core/SkStrokerPriv.h41
-rw-r--r--core/SkTDynamicHash.h241
-rw-r--r--core/SkTLList.h385
-rwxr-xr-xcore/SkTLS.cpp123
-rw-r--r--core/SkTLS.h84
-rwxr-xr-xcore/SkTRefArray.h112
-rw-r--r--core/SkTSearch.cpp114
-rw-r--r--core/SkTSort.h210
-rw-r--r--core/SkTemplatesPriv.h76
-rw-r--r--core/SkTextFormatParams.h41
-rw-r--r--core/SkTextToPathIter.h49
-rw-r--r--core/SkTileGrid.cpp134
-rw-r--r--core/SkTileGrid.h130
-rw-r--r--core/SkTileGridPicture.cpp28
-rw-r--r--core/SkTypeface.cpp229
-rw-r--r--core/SkTypefaceCache.cpp145
-rw-r--r--core/SkTypefaceCache.h94
-rw-r--r--core/SkTypefacePriv.h38
-rw-r--r--core/SkUnPreMultiply.cpp80
-rw-r--r--core/SkUtils.cpp397
-rw-r--r--core/SkUtilsArm.cpp197
-rw-r--r--core/SkUtilsArm.h87
-rw-r--r--core/SkWriter32.cpp306
-rw-r--r--core/SkXfermode.cpp1980
-rw-r--r--device/xps/SkXPSDevice.cpp2444
-rw-r--r--doc/SkDocument.cpp84
-rw-r--r--doc/SkDocument_PDF.cpp91
-rw-r--r--effects/Sk1DPathEffect.cpp200
-rw-r--r--effects/Sk2DPathEffect.cpp132
-rw-r--r--effects/SkArithmeticMode.cpp455
-rw-r--r--effects/SkAvoidXfermode.cpp179
-rw-r--r--effects/SkBicubicImageFilter.cpp377
-rw-r--r--effects/SkBitmapSource.cpp29
-rw-r--r--effects/SkBlurDrawLooper.cpp151
-rw-r--r--effects/SkBlurImageFilter.cpp236
-rw-r--r--effects/SkBlurMask.cpp985
-rw-r--r--effects/SkBlurMask.h55
-rw-r--r--effects/SkBlurMaskFilter.cpp499
-rwxr-xr-xeffects/SkColorFilterImageFilter.cpp137
-rw-r--r--effects/SkColorFilters.cpp547
-rw-r--r--effects/SkColorMatrix.cpp161
-rw-r--r--effects/SkColorMatrixFilter.cpp495
-rw-r--r--effects/SkComposeImageFilter.cpp56
-rw-r--r--effects/SkCornerPathEffect.cpp137
-rw-r--r--effects/SkDashPathEffect.cpp559
-rw-r--r--effects/SkDiscretePathEffect.cpp82
-rw-r--r--effects/SkDisplacementMapEffect.cpp541
-rw-r--r--effects/SkDropShadowImageFilter.cpp62
-rw-r--r--effects/SkEmbossMask.cpp163
-rw-r--r--effects/SkEmbossMask.h20
-rw-r--r--effects/SkEmbossMaskFilter.cpp155
-rw-r--r--effects/SkEmbossMask_Table.h1038
-rw-r--r--effects/SkGpuBlurUtils.cpp265
-rw-r--r--effects/SkGpuBlurUtils.h46
-rw-r--r--effects/SkKernel33MaskFilter.cpp142
-rw-r--r--effects/SkLayerDrawLooper.cpp350
-rw-r--r--effects/SkLayerRasterizer.cpp171
-rw-r--r--effects/SkLerpXfermode.cpp110
-rw-r--r--effects/SkLightingImageFilter.cpp1553
-rw-r--r--effects/SkMagnifierImageFilter.cpp354
-rw-r--r--effects/SkMatrixConvolutionImageFilter.cpp587
-rwxr-xr-xeffects/SkMergeImageFilter.cpp165
-rw-r--r--effects/SkMorphologyImageFilter.cpp533
-rw-r--r--effects/SkOffsetImageFilter.cpp53
-rw-r--r--effects/SkPaintFlagsDrawFilter.cpp20
-rw-r--r--effects/SkPerlinNoiseShader.cpp1378
-rw-r--r--effects/SkPixelXorXfermode.cpp38
-rw-r--r--effects/SkPorterDuff.cpp87
-rw-r--r--effects/SkRectShaderImageFilter.cpp67
-rw-r--r--effects/SkStippleMaskFilter.cpp52
-rw-r--r--effects/SkTableColorFilter.cpp431
-rw-r--r--effects/SkTableMaskFilter.cpp143
-rwxr-xr-xeffects/SkTestImageFilters.cpp81
-rw-r--r--effects/SkTransparentShader.cpp122
-rw-r--r--effects/SkXfermodeImageFilter.cpp146
-rw-r--r--effects/gradients/SkBitmapCache.cpp153
-rw-r--r--effects/gradients/SkBitmapCache.h49
-rw-r--r--effects/gradients/SkClampRange.cpp165
-rw-r--r--effects/gradients/SkClampRange.h38
-rw-r--r--effects/gradients/SkGradientShader.cpp986
-rw-r--r--effects/gradients/SkGradientShaderPriv.h349
-rw-r--r--effects/gradients/SkLinearGradient.cpp568
-rw-r--r--effects/gradients/SkLinearGradient.h38
-rw-r--r--effects/gradients/SkRadialGradient.cpp608
-rw-r--r--effects/gradients/SkRadialGradient.h40
-rw-r--r--effects/gradients/SkRadialGradient_Table.h139
-rw-r--r--effects/gradients/SkSweepGradient.cpp517
-rw-r--r--effects/gradients/SkSweepGradient.h40
-rw-r--r--effects/gradients/SkTwoPointConicalGradient.cpp765
-rw-r--r--effects/gradients/SkTwoPointConicalGradient.h84
-rw-r--r--effects/gradients/SkTwoPointRadialGradient.cpp717
-rw-r--r--effects/gradients/SkTwoPointRadialGradient.h55
-rw-r--r--fonts/SkFontMgr_fontconfig.cpp296
-rw-r--r--fonts/SkGScalerContext.cpp267
-rw-r--r--fonts/SkGScalerContext.h47
-rw-r--r--gpu/FlingState.cpp125
-rw-r--r--gpu/GrAAConvexPathRenderer.cpp686
-rw-r--r--gpu/GrAAConvexPathRenderer.h26
-rw-r--r--gpu/GrAAHairLinePathRenderer.cpp1288
-rw-r--r--gpu/GrAAHairLinePathRenderer.h65
-rw-r--r--gpu/GrAARectRenderer.cpp812
-rw-r--r--gpu/GrAddPathRenderers_default.cpp34
-rw-r--r--gpu/GrAddPathRenderers_none.cpp15
-rw-r--r--gpu/GrAllocPool.cpp118
-rw-r--r--gpu/GrAllocPool.h63
-rwxr-xr-xgpu/GrAllocator.h250
-rw-r--r--gpu/GrAtlas.cpp252
-rw-r--r--gpu/GrAtlas.h94
-rw-r--r--gpu/GrBinHashKey.h110
-rw-r--r--gpu/GrBufferAllocPool.cpp488
-rw-r--r--gpu/GrBufferAllocPool.h352
-rw-r--r--gpu/GrCacheID.cpp34
-rw-r--r--gpu/GrClipData.cpp34
-rw-r--r--gpu/GrClipMaskCache.cpp21
-rw-r--r--gpu/GrClipMaskCache.h239
-rw-r--r--gpu/GrClipMaskManager.cpp1013
-rw-r--r--gpu/GrClipMaskManager.h170
-rw-r--r--gpu/GrContext.cpp1735
-rw-r--r--gpu/GrDefaultPathRenderer.cpp521
-rw-r--r--gpu/GrDefaultPathRenderer.h62
-rw-r--r--gpu/GrDrawState.cpp484
-rw-r--r--gpu/GrDrawState.h1100
-rw-r--r--gpu/GrDrawTarget.cpp999
-rw-r--r--gpu/GrDrawTarget.h855
-rw-r--r--gpu/GrDrawTargetCaps.h67
-rw-r--r--gpu/GrEffect.cpp104
-rw-r--r--gpu/GrGeometryBuffer.cpp10
-rw-r--r--gpu/GrGeometryBuffer.h103
-rw-r--r--gpu/GrGpu.cpp500
-rw-r--r--gpu/GrGpu.h539
-rw-r--r--gpu/GrGpuFactory.cpp43
-rw-r--r--gpu/GrInOrderDrawBuffer.cpp827
-rw-r--r--gpu/GrInOrderDrawBuffer.h230
-rw-r--r--gpu/GrIndexBuffer.h33
-rw-r--r--gpu/GrMemory.cpp26
-rw-r--r--gpu/GrMemoryPool.cpp161
-rw-r--r--gpu/GrMemoryPool.h80
-rw-r--r--gpu/GrOvalRenderer.cpp859
-rw-r--r--gpu/GrPaint.cpp35
-rw-r--r--gpu/GrPath.cpp10
-rw-r--r--gpu/GrPath.h29
-rw-r--r--gpu/GrPathRenderer.cpp26
-rw-r--r--gpu/GrPathRenderer.h199
-rw-r--r--gpu/GrPathRendererChain.cpp89
-rw-r--r--gpu/GrPathUtils.cpp478
-rw-r--r--gpu/GrPathUtils.h119
-rw-r--r--gpu/GrPlotMgr.h76
-rw-r--r--gpu/GrRectanizer.cpp121
-rw-r--r--gpu/GrRectanizer.h51
-rw-r--r--gpu/GrRectanizer_fifo.cpp121
-rw-r--r--gpu/GrRedBlackTree.h1118
-rw-r--r--gpu/GrReducedClip.cpp415
-rw-r--r--gpu/GrReducedClip.h40
-rw-r--r--gpu/GrRenderTarget.cpp111
-rw-r--r--gpu/GrResource.cpp63
-rw-r--r--gpu/GrResourceCache.cpp474
-rw-r--r--gpu/GrResourceCache.h426
-rw-r--r--gpu/GrSWMaskHelper.cpp204
-rw-r--r--gpu/GrSWMaskHelper.h107
-rw-r--r--gpu/GrSoftwarePathRenderer.cpp153
-rw-r--r--gpu/GrSoftwarePathRenderer.h47
-rw-r--r--gpu/GrStencil.cpp395
-rw-r--r--gpu/GrStencil.h395
-rw-r--r--gpu/GrStencilAndCoverPathRenderer.cpp123
-rw-r--r--gpu/GrStencilAndCoverPathRenderer.h55
-rw-r--r--gpu/GrStencilBuffer.cpp49
-rw-r--r--gpu/GrStencilBuffer.h82
-rw-r--r--gpu/GrSurface.cpp10
-rw-r--r--gpu/GrTBSearch.h45
-rw-r--r--gpu/GrTHashCache.h246
-rw-r--r--gpu/GrTemplates.h70
-rw-r--r--gpu/GrTextContext.cpp255
-rw-r--r--gpu/GrTextStrike.cpp261
-rw-r--r--gpu/GrTextStrike.h120
-rw-r--r--gpu/GrTextStrike_impl.h105
-rw-r--r--gpu/GrTexture.cpp196
-rw-r--r--gpu/GrTextureAccess.cpp107
-rw-r--r--gpu/GrVertexBuffer.h24
-rw-r--r--gpu/SkGpuDevice.cpp1797
-rw-r--r--gpu/SkGr.cpp216
-rw-r--r--gpu/SkGrFontScaler.cpp201
-rw-r--r--gpu/SkGrPixelRef.cpp179
-rw-r--r--gpu/SkGrTexturePixelRef.cpp11
-rw-r--r--gpu/effects/Gr1DKernelEffect.h54
-rw-r--r--gpu/effects/GrConfigConversionEffect.cpp296
-rw-r--r--gpu/effects/GrConfigConversionEffect.h78
-rw-r--r--gpu/effects/GrConvolutionEffect.cpp250
-rw-r--r--gpu/effects/GrConvolutionEffect.h113
-rw-r--r--gpu/effects/GrSimpleTextureEffect.cpp129
-rw-r--r--gpu/effects/GrSimpleTextureEffect.h104
-rw-r--r--gpu/effects/GrSingleTextureEffect.cpp40
-rw-r--r--gpu/effects/GrSingleTextureEffect.h74
-rw-r--r--gpu/effects/GrTextureDomainEffect.cpp244
-rw-r--r--gpu/effects/GrTextureDomainEffect.h89
-rw-r--r--gpu/effects/GrTextureStripAtlas.cpp348
-rw-r--r--gpu/effects/GrTextureStripAtlas.h181
-rw-r--r--gpu/gl/GrGLBufferImpl.cpp165
-rw-r--r--gpu/gl/GrGLBufferImpl.h60
-rw-r--r--gpu/gl/GrGLCaps.cpp599
-rw-r--r--gpu/gl/GrGLCaps.h368
-rw-r--r--gpu/gl/GrGLContext.cpp93
-rw-r--r--gpu/gl/GrGLContext.h129
-rw-r--r--gpu/gl/GrGLCreateNativeInterface_none.cpp12
-rw-r--r--gpu/gl/GrGLCreateNullInterface.cpp395
-rw-r--r--gpu/gl/GrGLDefaultInterface_native.cpp12
-rw-r--r--gpu/gl/GrGLDefaultInterface_none.cpp12
-rw-r--r--gpu/gl/GrGLDefines.h882
-rw-r--r--gpu/gl/GrGLEffect.cpp50
-rw-r--r--gpu/gl/GrGLEffect.h99
-rw-r--r--gpu/gl/GrGLEffectMatrix.cpp238
-rw-r--r--gpu/gl/GrGLEffectMatrix.h121
-rw-r--r--gpu/gl/GrGLExtensions.cpp96
-rw-r--r--gpu/gl/GrGLIRect.h79
-rw-r--r--gpu/gl/GrGLIndexBuffer.cpp57
-rw-r--r--gpu/gl/GrGLIndexBuffer.h57
-rw-r--r--gpu/gl/GrGLInterface.cpp391
-rw-r--r--gpu/gl/GrGLNoOpInterface.cpp628
-rw-r--r--gpu/gl/GrGLNoOpInterface.h360
-rw-r--r--gpu/gl/GrGLPath.cpp112
-rw-r--r--gpu/gl/GrGLPath.h43
-rw-r--r--gpu/gl/GrGLProgram.cpp1010
-rw-r--r--gpu/gl/GrGLProgram.h230
-rw-r--r--gpu/gl/GrGLProgramDesc.cpp261
-rw-r--r--gpu/gl/GrGLProgramDesc.h221
-rw-r--r--gpu/gl/GrGLRenderTarget.cpp110
-rw-r--r--gpu/gl/GrGLRenderTarget.h107
-rw-r--r--gpu/gl/GrGLSL.cpp147
-rw-r--r--gpu/gl/GrGLSL.h232
-rw-r--r--gpu/gl/GrGLSL_impl.h192
-rw-r--r--gpu/gl/GrGLShaderBuilder.cpp736
-rw-r--r--gpu/gl/GrGLShaderBuilder.h467
-rw-r--r--gpu/gl/GrGLShaderVar.h360
-rw-r--r--gpu/gl/GrGLStencilBuffer.cpp39
-rw-r--r--gpu/gl/GrGLStencilBuffer.h62
-rw-r--r--gpu/gl/GrGLTexture.cpp70
-rw-r--r--gpu/gl/GrGLTexture.h111
-rw-r--r--gpu/gl/GrGLUniformHandle.h16
-rw-r--r--gpu/gl/GrGLUniformManager.cpp269
-rw-r--r--gpu/gl/GrGLUniformManager.h84
-rw-r--r--gpu/gl/GrGLUtil.cpp235
-rw-r--r--gpu/gl/GrGLUtil.h162
-rw-r--r--gpu/gl/GrGLVertexArray.cpp123
-rw-r--r--gpu/gl/GrGLVertexArray.h174
-rw-r--r--gpu/gl/GrGLVertexBuffer.cpp58
-rw-r--r--gpu/gl/GrGLVertexBuffer.h57
-rw-r--r--gpu/gl/GrGpuGL.cpp2563
-rw-r--r--gpu/gl/GrGpuGL.h439
-rw-r--r--gpu/gl/GrGpuGL_program.cpp408
-rw-r--r--gpu/gl/SkGLContextHelper.cpp139
-rw-r--r--gpu/gl/SkNullGLContext.cpp13
-rw-r--r--gpu/gl/android/GrGLCreateNativeInterface_android.cpp169
-rw-r--r--gpu/gl/android/SkNativeGLContext_android.cpp104
-rw-r--r--gpu/gl/angle/GrGLCreateANGLEInterface.cpp159
-rw-r--r--gpu/gl/angle/SkANGLEGLContext.cpp107
-rw-r--r--gpu/gl/debug/GrBufferObj.cpp31
-rw-r--r--gpu/gl/debug/GrBufferObj.h68
-rw-r--r--gpu/gl/debug/GrDebugGL.cpp211
-rw-r--r--gpu/gl/debug/GrDebugGL.h160
-rw-r--r--gpu/gl/debug/GrFBBindableObj.h88
-rw-r--r--gpu/gl/debug/GrFakeRefObj.h95
-rw-r--r--gpu/gl/debug/GrFrameBufferObj.cpp67
-rw-r--r--gpu/gl/debug/GrFrameBufferObj.h68
-rw-r--r--gpu/gl/debug/GrGLCreateDebugInterface.cpp918
-rw-r--r--gpu/gl/debug/GrProgramObj.cpp27
-rw-r--r--gpu/gl/debug/GrProgramObj.h43
-rw-r--r--gpu/gl/debug/GrRenderBufferObj.h40
-rw-r--r--gpu/gl/debug/GrShaderObj.cpp14
-rw-r--r--gpu/gl/debug/GrShaderObj.h36
-rw-r--r--gpu/gl/debug/GrTextureObj.cpp14
-rw-r--r--gpu/gl/debug/GrTextureObj.h57
-rw-r--r--gpu/gl/debug/GrTextureUnitObj.cpp31
-rw-r--r--gpu/gl/debug/GrTextureUnitObj.h44
-rw-r--r--gpu/gl/debug/GrVertexArrayObj.h21
-rw-r--r--gpu/gl/debug/SkDebugGLContext.cpp13
-rw-r--r--gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp152
-rw-r--r--gpu/gl/iOS/SkNativeGLContext_iOS.mm62
-rw-r--r--gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp241
-rw-r--r--gpu/gl/mac/SkNativeGLContext_mac.cpp76
-rw-r--r--gpu/gl/mesa/GrGLCreateMesaInterface.cpp218
-rw-r--r--gpu/gl/mesa/SkMesaGLContext.cpp106
-rw-r--r--gpu/gl/mesa/osmesa_wrapper.h16
-rw-r--r--gpu/gl/nacl/SkNativeGLContext_nacl.cpp34
-rw-r--r--gpu/gl/unix/GrGLCreateNativeInterface_unix.cpp280
-rw-r--r--gpu/gl/unix/SkNativeGLContext_unix.cpp287
-rw-r--r--gpu/gl/win/GrGLCreateNativeInterface_win.cpp316
-rw-r--r--gpu/gl/win/SkNativeGLContext_win.cpp114
-rw-r--r--gpu/gr_unittests.cpp80
-rw-r--r--image/SkDataPixelRef.cpp39
-rw-r--r--image/SkDataPixelRef.h35
-rw-r--r--image/SkImage.cpp54
-rw-r--r--image/SkImagePriv.cpp173
-rw-r--r--image/SkImagePriv.h74
-rw-r--r--image/SkImage_Base.h30
-rw-r--r--image/SkImage_Codec.cpp78
-rw-r--r--image/SkImage_Gpu.cpp78
-rw-r--r--image/SkImage_Picture.cpp66
-rw-r--r--image/SkImage_Raster.cpp171
-rw-r--r--image/SkSurface.cpp111
-rw-r--r--image/SkSurface_Base.h96
-rw-r--r--image/SkSurface_Gpu.cpp138
-rw-r--r--image/SkSurface_Picture.cpp92
-rw-r--r--image/SkSurface_Raster.cpp180
-rw-r--r--images/SkForceLinking.cpp33
-rw-r--r--images/SkImageDecoder.cpp469
-rw-r--r--images/SkImageDecoder_FactoryDefault.cpp37
-rw-r--r--images/SkImageDecoder_FactoryRegistrar.cpp69
-rw-r--r--images/SkImageDecoder_libbmp.cpp160
-rw-r--r--images/SkImageDecoder_libgif.cpp389
-rw-r--r--images/SkImageDecoder_libico.cpp419
-rw-r--r--images/SkImageDecoder_libjpeg.cpp1162
-rw-r--r--images/SkImageDecoder_libpng.cpp1206
-rw-r--r--images/SkImageDecoder_libwebp.cpp597
-rw-r--r--images/SkImageDecoder_wbmp.cpp175
-rw-r--r--images/SkImageEncoder.cpp52
-rw-r--r--images/SkImageEncoder_Factory.cpp28
-rw-r--r--images/SkImageEncoder_argb.cpp119
-rw-r--r--images/SkImageRef.cpp189
-rw-r--r--images/SkImageRefPool.cpp192
-rw-r--r--images/SkImageRefPool.h49
-rw-r--r--images/SkImageRef_GlobalPool.cpp100
-rw-r--r--images/SkImageRef_ashmem.cpp230
-rw-r--r--images/SkImageRef_ashmem.h47
-rw-r--r--images/SkImages.cpp21
-rw-r--r--images/SkJpegUtility.cpp178
-rw-r--r--images/SkJpegUtility.h66
-rw-r--r--images/SkMovie.cpp97
-rw-r--r--images/SkMovie_gif.cpp449
-rw-r--r--images/SkPageFlipper.cpp76
-rw-r--r--images/SkScaledBitmapSampler.cpp503
-rw-r--r--images/SkScaledBitmapSampler.h69
-rw-r--r--images/bmpdecoderhelper.cpp369
-rw-r--r--images/bmpdecoderhelper.h116
-rw-r--r--images/transform_scanline.h140
-rw-r--r--lazy/SkBitmapFactory.cpp83
-rw-r--r--lazy/SkLazyPixelRef.cpp147
-rw-r--r--lazy/SkLazyPixelRef.h81
-rw-r--r--lazy/SkLruImageCache.cpp206
-rw-r--r--lazy/SkPurgeableImageCache.cpp160
-rw-r--r--lazy/SkPurgeableMemoryBlock.h94
-rw-r--r--lazy/SkPurgeableMemoryBlock_common.cpp16
-rw-r--r--opts/SkBitmapFilter_opts_SSE2.cpp636
-rw-r--r--opts/SkBitmapFilter_opts_SSE2.h37
-rw-r--r--opts/SkBitmapProcState_arm_neon.cpp92
-rw-r--r--opts/SkBitmapProcState_filter_neon.h88
-rw-r--r--opts/SkBitmapProcState_matrixProcs_neon.cpp144
-rw-r--r--opts/SkBitmapProcState_matrix_clamp_neon.h911
-rw-r--r--opts/SkBitmapProcState_matrix_repeat_neon.h542
-rw-r--r--opts/SkBitmapProcState_opts_SSE2.cpp766
-rw-r--r--opts/SkBitmapProcState_opts_SSE2.h30
-rw-r--r--opts/SkBitmapProcState_opts_SSSE3.cpp724
-rw-r--r--opts/SkBitmapProcState_opts_SSSE3.h21
-rw-r--r--opts/SkBitmapProcState_opts_arm.cpp422
-rw-r--r--opts/SkBitmapProcState_opts_none.cpp26
-rw-r--r--opts/SkBlitRect_opts_SSE2.cpp133
-rw-r--r--opts/SkBlitRect_opts_SSE2.h23
-rw-r--r--opts/SkBlitRow_opts_SSE2.cpp853
-rw-r--r--opts/SkBlitRow_opts_SSE2.h30
-rw-r--r--opts/SkBlitRow_opts_arm.cpp395
-rw-r--r--opts/SkBlitRow_opts_arm_neon.cpp1369
-rw-r--r--opts/SkBlitRow_opts_arm_neon.h18
-rw-r--r--opts/SkBlitRow_opts_none.cpp45
-rw-r--r--opts/SkCachePreload_arm.h34
-rw-r--r--opts/SkUtils_opts_SSE2.cpp71
-rw-r--r--opts/SkUtils_opts_SSE2.h13
-rw-r--r--opts/SkUtils_opts_none.cpp18
-rw-r--r--opts/memset.arm.S111
-rw-r--r--opts/memset16_neon.S143
-rw-r--r--opts/memset32_neon.S113
-rw-r--r--opts/opts_check_SSE2.cpp259
-rw-r--r--opts/opts_check_arm.cpp67
-rw-r--r--pathops/SkAddIntersections.cpp430
-rw-r--r--pathops/SkAddIntersections.h18
-rw-r--r--pathops/SkDCubicIntersection.cpp548
-rw-r--r--pathops/SkDCubicLineIntersection.cpp327
-rw-r--r--pathops/SkDCubicToQuads.cpp190
-rw-r--r--pathops/SkDLineIntersection.cpp315
-rw-r--r--pathops/SkDQuadImplicit.cpp117
-rw-r--r--pathops/SkDQuadImplicit.h39
-rw-r--r--pathops/SkDQuadIntersection.cpp551
-rw-r--r--pathops/SkDQuadLineIntersection.cpp402
-rw-r--r--pathops/SkIntersectionHelper.h135
-rw-r--r--pathops/SkIntersections.cpp189
-rw-r--r--pathops/SkIntersections.h261
-rw-r--r--pathops/SkLineParameters.h119
-rw-r--r--pathops/SkOpAngle.cpp430
-rw-r--r--pathops/SkOpAngle.h93
-rw-r--r--pathops/SkOpContour.cpp261
-rw-r--r--pathops/SkOpContour.h255
-rw-r--r--pathops/SkOpEdgeBuilder.cpp196
-rw-r--r--pathops/SkOpEdgeBuilder.h63
-rw-r--r--pathops/SkOpSegment.cpp3027
-rw-r--r--pathops/SkOpSegment.h415
-rw-r--r--pathops/SkOpSpan.h31
-rw-r--r--pathops/SkPathOpsBounds.cpp40
-rw-r--r--pathops/SkPathOpsBounds.h63
-rw-r--r--pathops/SkPathOpsCommon.cpp594
-rw-r--r--pathops/SkPathOpsCommon.h32
-rw-r--r--pathops/SkPathOpsCubic.cpp510
-rw-r--r--pathops/SkPathOpsCubic.h82
-rw-r--r--pathops/SkPathOpsCurve.h152
-rw-r--r--pathops/SkPathOpsDebug.cpp88
-rw-r--r--pathops/SkPathOpsDebug.h160
-rw-r--r--pathops/SkPathOpsLine.cpp144
-rw-r--r--pathops/SkPathOpsLine.h42
-rw-r--r--pathops/SkPathOpsOp.cpp319
-rw-r--r--pathops/SkPathOpsPoint.cpp17
-rw-r--r--pathops/SkPathOpsPoint.h164
-rw-r--r--pathops/SkPathOpsQuad.cpp342
-rw-r--r--pathops/SkPathOpsQuad.h68
-rw-r--r--pathops/SkPathOpsRect.cpp65
-rw-r--r--pathops/SkPathOpsRect.h63
-rw-r--r--pathops/SkPathOpsSimplify.cpp206
-rw-r--r--pathops/SkPathOpsSpan.h31
-rw-r--r--pathops/SkPathOpsTriangle.cpp31
-rw-r--r--pathops/SkPathOpsTriangle.h20
-rw-r--r--pathops/SkPathOpsTypes.cpp104
-rw-r--r--pathops/SkPathOpsTypes.h277
-rw-r--r--pathops/SkPathWriter.cpp166
-rw-r--r--pathops/SkPathWriter.h45
-rw-r--r--pathops/SkQuarticRoot.cpp165
-rw-r--r--pathops/SkQuarticRoot.h16
-rw-r--r--pathops/SkReduceOrder.cpp452
-rw-r--r--pathops/SkReduceOrder.h38
-rw-r--r--pathops/main.cpp16
-rw-r--r--pdf/SkPDFCatalog.cpp215
-rw-r--r--pdf/SkPDFCatalog.h137
-rw-r--r--pdf/SkPDFDevice.cpp1929
-rw-r--r--pdf/SkPDFDocument.cpp309
-rw-r--r--pdf/SkPDFFont.cpp1418
-rw-r--r--pdf/SkPDFFont.h203
-rwxr-xr-xpdf/SkPDFFontImpl.h83
-rw-r--r--pdf/SkPDFFormXObject.cpp95
-rw-r--r--pdf/SkPDFFormXObject.h61
-rw-r--r--pdf/SkPDFGraphicState.cpp289
-rw-r--r--pdf/SkPDFGraphicState.h109
-rw-r--r--pdf/SkPDFImage.cpp339
-rw-r--r--pdf/SkPDFImage.h71
-rw-r--r--pdf/SkPDFImageStream.cpp79
-rw-r--r--pdf/SkPDFImageStream.h53
-rw-r--r--pdf/SkPDFPage.cpp158
-rw-r--r--pdf/SkPDFPage.h107
-rw-r--r--pdf/SkPDFResourceDict.cpp126
-rw-r--r--pdf/SkPDFResourceDict.h100
-rw-r--r--pdf/SkPDFShader.cpp1192
-rw-r--r--pdf/SkPDFShader.h73
-rw-r--r--pdf/SkPDFStream.cpp125
-rw-r--r--pdf/SkPDFStream.h99
-rw-r--r--pdf/SkPDFTypes.cpp501
-rw-r--r--pdf/SkPDFTypes.h444
-rw-r--r--pdf/SkPDFUtils.cpp249
-rw-r--r--pdf/SkPDFUtils.h59
-rw-r--r--pdf/SkTSet.h356
-rw-r--r--pipe/SkGPipePriv.h282
-rw-r--r--pipe/SkGPipeRead.cpp857
-rw-r--r--pipe/SkGPipeWrite.cpp1195
-rw-r--r--pipe/utils/SamplePipeControllers.cpp118
-rw-r--r--pipe/utils/SamplePipeControllers.h87
-rw-r--r--ports/SkDebug_android.cpp35
-rw-r--r--ports/SkDebug_nacl.cpp38
-rw-r--r--ports/SkDebug_stdio.cpp20
-rw-r--r--ports/SkDebug_win.cpp32
-rw-r--r--ports/SkFontConfigInterface_android.cpp825
-rw-r--r--ports/SkFontConfigInterface_direct.cpp726
-rw-r--r--ports/SkFontConfigParser_android.cpp318
-rw-r--r--ports/SkFontConfigParser_android.h61
-rw-r--r--ports/SkFontConfigTypeface.h64
-rw-r--r--ports/SkFontHost_FreeType.cpp1535
-rw-r--r--ports/SkFontHost_FreeType_common.cpp327
-rw-r--r--ports/SkFontHost_FreeType_common.h79
-rw-r--r--ports/SkFontHost_fontconfig.cpp204
-rw-r--r--ports/SkFontHost_linux.cpp527
-rwxr-xr-xports/SkFontHost_mac.cpp2314
-rw-r--r--ports/SkFontHost_none.cpp37
-rw-r--r--ports/SkFontHost_sandbox_none.cpp0
-rwxr-xr-xports/SkFontHost_win.cpp2385
-rw-r--r--ports/SkFontHost_win_dw.cpp1869
-rw-r--r--ports/SkGlobalInitialization_chromium.cpp34
-rw-r--r--ports/SkGlobalInitialization_default.cpp116
-rw-r--r--ports/SkHarfBuzzFont.cpp187
-rw-r--r--ports/SkImageDecoder_CG.cpp304
-rw-r--r--ports/SkImageDecoder_WIC.cpp446
-rw-r--r--ports/SkImageDecoder_empty.cpp156
-rw-r--r--ports/SkMemory_malloc.cpp51
-rw-r--r--ports/SkMemory_mozalloc.cpp39
-rw-r--r--ports/SkOSFile_none.cpp26
-rw-r--r--ports/SkOSFile_posix.cpp80
-rw-r--r--ports/SkOSFile_stdio.cpp165
-rw-r--r--ports/SkOSFile_win.cpp113
-rw-r--r--ports/SkPurgeableMemoryBlock_android.cpp110
-rw-r--r--ports/SkPurgeableMemoryBlock_mac.cpp104
-rw-r--r--ports/SkPurgeableMemoryBlock_none.cpp40
-rw-r--r--ports/SkTLS_none.cpp18
-rw-r--r--ports/SkTLS_pthread.cpp30
-rw-r--r--ports/SkTLS_win.cpp75
-rw-r--r--ports/SkThread_none.cpp43
-rw-r--r--ports/SkThread_pthread.cpp197
-rw-r--r--ports/SkThread_win.cpp65
-rw-r--r--ports/SkTime_Unix.cpp39
-rw-r--r--ports/SkTime_win.cpp38
-rw-r--r--ports/SkXMLParser_empty.cpp23
-rw-r--r--ports/SkXMLParser_expat.cpp140
-rw-r--r--ports/SkXMLParser_tinyxml.cpp87
-rw-r--r--ports/SkXMLPullParser_expat.cpp213
-rw-r--r--sfnt/SkIBMFamilyClass.h174
-rw-r--r--sfnt/SkOTTableTypes.h48
-rw-r--r--sfnt/SkOTTable_OS_2.h52
-rw-r--r--sfnt/SkOTTable_OS_2_V0.h149
-rw-r--r--sfnt/SkOTTable_OS_2_V1.h518
-rw-r--r--sfnt/SkOTTable_OS_2_V2.h540
-rw-r--r--sfnt/SkOTTable_OS_2_V3.h550
-rw-r--r--sfnt/SkOTTable_OS_2_V4.h585
-rw-r--r--sfnt/SkOTTable_OS_2_VA.h142
-rw-r--r--sfnt/SkOTTable_glyf.h214
-rw-r--r--sfnt/SkOTTable_head.h150
-rw-r--r--sfnt/SkOTTable_hhea.h56
-rw-r--r--sfnt/SkOTTable_loca.h31
-rw-r--r--sfnt/SkOTTable_maxp.h34
-rw-r--r--sfnt/SkOTTable_maxp_CFF.h30
-rw-r--r--sfnt/SkOTTable_maxp_TT.h49
-rw-r--r--sfnt/SkOTTable_name.cpp534
-rw-r--r--sfnt/SkOTTable_name.h584
-rw-r--r--sfnt/SkOTTable_post.h52
-rw-r--r--sfnt/SkOTUtils.cpp203
-rw-r--r--sfnt/SkOTUtils.h89
-rw-r--r--sfnt/SkPanose.h639
-rw-r--r--sfnt/SkPreprocessorSeq.h826
-rw-r--r--sfnt/SkSFNTHeader.h70
-rw-r--r--sfnt/SkTTCFHeader.h56
-rw-r--r--sfnt/SkTypedEnum.h68
-rw-r--r--svg/SkSVG.cpp28
-rw-r--r--svg/SkSVGCircle.cpp45
-rw-r--r--svg/SkSVGCircle.h24
-rw-r--r--svg/SkSVGClipPath.cpp40
-rw-r--r--svg/SkSVGClipPath.h23
-rw-r--r--svg/SkSVGDefs.cpp24
-rw-r--r--svg/SkSVGDefs.h23
-rw-r--r--svg/SkSVGElements.cpp86
-rw-r--r--svg/SkSVGElements.h73
-rw-r--r--svg/SkSVGEllipse.cpp47
-rw-r--r--svg/SkSVGEllipse.h25
-rw-r--r--svg/SkSVGFeColorMatrix.cpp24
-rw-r--r--svg/SkSVGFeColorMatrix.h26
-rw-r--r--svg/SkSVGFilter.cpp25
-rw-r--r--svg/SkSVGFilter.h27
-rw-r--r--svg/SkSVGG.cpp16
-rw-r--r--svg/SkSVGG.h21
-rw-r--r--svg/SkSVGGradient.cpp114
-rw-r--r--svg/SkSVGGradient.h29
-rw-r--r--svg/SkSVGGroup.cpp45
-rw-r--r--svg/SkSVGGroup.h28
-rw-r--r--svg/SkSVGImage.cpp44
-rw-r--r--svg/SkSVGImage.h28
-rw-r--r--svg/SkSVGLine.cpp30
-rw-r--r--svg/SkSVGLine.h25
-rw-r--r--svg/SkSVGLinearGradient.cpp44
-rw-r--r--svg/SkSVGLinearGradient.h28
-rw-r--r--svg/SkSVGMask.cpp33
-rw-r--r--svg/SkSVGMask.h29
-rw-r--r--svg/SkSVGMetadata.cpp24
-rw-r--r--svg/SkSVGMetadata.h23
-rw-r--r--svg/SkSVGPaintState.cpp454
-rw-r--r--svg/SkSVGParser.cpp441
-rw-r--r--svg/SkSVGPath.cpp37
-rw-r--r--svg/SkSVGPath.h22
-rw-r--r--svg/SkSVGPolygon.cpp33
-rw-r--r--svg/SkSVGPolygon.h23
-rw-r--r--svg/SkSVGPolyline.cpp43
-rw-r--r--svg/SkSVGPolyline.h27
-rw-r--r--svg/SkSVGRadialGradient.cpp42
-rw-r--r--svg/SkSVGRadialGradient.h30
-rw-r--r--svg/SkSVGRect.cpp35
-rw-r--r--svg/SkSVGRect.h26
-rw-r--r--svg/SkSVGSVG.cpp76
-rw-r--r--svg/SkSVGSVG.h34
-rw-r--r--svg/SkSVGStop.cpp24
-rw-r--r--svg/SkSVGStop.h23
-rw-r--r--svg/SkSVGSymbol.cpp22
-rw-r--r--svg/SkSVGSymbol.h22
-rw-r--r--svg/SkSVGText.cpp39
-rw-r--r--svg/SkSVGText.h32
-rw-r--r--svg/SkSVGUse.cpp30
-rw-r--r--svg/SkSVGUse.h28
-rw-r--r--text/SkTextLayout.cpp80
-rw-r--r--utils/SkBase64.cpp185
-rw-r--r--utils/SkBase64.h44
-rwxr-xr-xutils/SkBitSet.cpp83
-rwxr-xr-xutils/SkBitSet.h78
-rw-r--r--utils/SkBitmapHasher.cpp64
-rw-r--r--utils/SkBitmapHasher.h35
-rw-r--r--utils/SkBoundaryPatch.cpp81
-rw-r--r--utils/SkCamera.cpp425
-rw-r--r--utils/SkCanvasStack.cpp108
-rw-r--r--utils/SkCanvasStack.h52
-rw-r--r--utils/SkCanvasStateUtils.cpp343
-rw-r--r--utils/SkCondVar.cpp68
-rw-r--r--utils/SkCountdown.cpp32
-rw-r--r--utils/SkCubicInterval.cpp67
-rw-r--r--utils/SkCullPoints.cpp219
-rw-r--r--utils/SkDebugTrace.h24
-rw-r--r--utils/SkDeferredCanvas.cpp1011
-rw-r--r--utils/SkDumpCanvas.cpp525
-rw-r--r--utils/SkFloatUtils.h173
-rw-r--r--utils/SkInterpolator.cpp331
-rw-r--r--utils/SkJSON.cpp634
-rw-r--r--utils/SkLayer.cpp232
-rw-r--r--utils/SkLua.cpp1129
-rw-r--r--utils/SkLuaCanvas.cpp288
-rw-r--r--utils/SkMD5.cpp252
-rw-r--r--utils/SkMD5.h53
-rw-r--r--utils/SkMatrix44.cpp854
-rw-r--r--utils/SkMeshUtils.cpp102
-rw-r--r--utils/SkNWayCanvas.cpp334
-rw-r--r--utils/SkNinePatch.cpp335
-rw-r--r--utils/SkNullCanvas.cpp18
-rw-r--r--utils/SkOSFile.cpp249
-rw-r--r--utils/SkParse.cpp338
-rw-r--r--utils/SkParseColor.cpp539
-rw-r--r--utils/SkParsePath.cpp248
-rw-r--r--utils/SkPathUtils.cpp152
-rw-r--r--utils/SkPictureUtils.cpp224
-rw-r--r--utils/SkProxyCanvas.cpp179
-rw-r--r--utils/SkRTConf.cpp296
-rw-r--r--utils/SkSHA1.cpp268
-rw-r--r--utils/SkSHA1.h53
-rw-r--r--utils/SkTFitsIn.h209
-rw-r--r--utils/SkTLogic.h61
-rw-r--r--utils/SkThreadPool.cpp107
-rw-r--r--utils/SkThreadUtils.h46
-rw-r--r--utils/SkThreadUtils_pthread.cpp117
-rw-r--r--utils/SkThreadUtils_pthread.h43
-rw-r--r--utils/SkThreadUtils_pthread_linux.cpp46
-rw-r--r--utils/SkThreadUtils_pthread_mach.cpp30
-rw-r--r--utils/SkThreadUtils_pthread_other.cpp12
-rw-r--r--utils/SkThreadUtils_win.cpp136
-rw-r--r--utils/SkThreadUtils_win.h28
-rw-r--r--utils/SkUnitMappers.cpp61
-rw-r--r--utils/android/ashmem.cpp87
-rw-r--r--utils/android/ashmem.h44
-rw-r--r--utils/debugger/SkDebugCanvas.cpp421
-rw-r--r--utils/debugger/SkDebugCanvas.h273
-rw-r--r--utils/debugger/SkDrawCommand.cpp878
-rw-r--r--utils/debugger/SkDrawCommand.h553
-rw-r--r--utils/debugger/SkObjectParser.cpp371
-rw-r--r--utils/debugger/SkObjectParser.h128
-rwxr-xr-xutils/ios/SkFontHost_iOS.mm262
-rwxr-xr-xutils/ios/SkImageDecoder_iOS.mm68
-rwxr-xr-xutils/ios/SkOSFile_iOS.mm98
-rwxr-xr-xutils/ios/SkStream_NSData.mm44
-rw-r--r--utils/mac/SkCreateCGImageRef.cpp232
-rw-r--r--utils/mac/SkStream_mac.cpp67
-rw-r--r--utils/win/SkAutoCoInitialize.cpp29
-rw-r--r--utils/win/SkDWriteFontFileStream.cpp232
-rw-r--r--utils/win/SkDWriteFontFileStream.h76
-rw-r--r--utils/win/SkDWriteGeometrySink.cpp146
-rw-r--r--utils/win/SkDWriteGeometrySink.h46
-rw-r--r--utils/win/SkHRESULT.cpp36
-rw-r--r--utils/win/SkIStream.cpp270
-rw-r--r--utils/win/SkWGL_win.cpp368
-rw-r--r--views/SkBGViewArtist.cpp30
-rw-r--r--views/SkEvent.cpp508
-rw-r--r--views/SkEventSink.cpp305
-rw-r--r--views/SkOSMenu.cpp263
-rw-r--r--views/SkParsePaint.cpp109
-rw-r--r--views/SkProgressView.cpp132
-rw-r--r--views/SkStackViewLayout.cpp273
-rw-r--r--views/SkTagList.cpp62
-rw-r--r--views/SkTagList.h43
-rw-r--r--views/SkTextBox.cpp259
-rw-r--r--views/SkTouchGesture.cpp327
-rw-r--r--views/SkView.cpp846
-rw-r--r--views/SkViewInflate.cpp145
-rw-r--r--views/SkViewPriv.cpp103
-rw-r--r--views/SkViewPriv.h44
-rw-r--r--views/SkWidgets.cpp560
-rw-r--r--views/SkWindow.cpp415
-rw-r--r--views/animated/SkBorderView.cpp96
-rw-r--r--views/animated/SkImageView.cpp302
-rw-r--r--views/animated/SkProgressBarView.cpp109
-rw-r--r--views/animated/SkScrollBarView.cpp145
-rw-r--r--views/animated/SkStaticTextView.cpp191
-rw-r--r--views/animated/SkWidgetViews.cpp400
-rwxr-xr-xviews/ios/SkOSWindow_iOS.mm64
-rw-r--r--views/mac/SampleApp-Info.plist34
-rw-r--r--views/mac/SampleApp.xib3962
-rw-r--r--views/mac/SampleAppDelegate.h24
-rw-r--r--views/mac/SampleAppDelegate.mm16
-rw-r--r--views/mac/SkEventNotifier.h13
-rw-r--r--views/mac/SkEventNotifier.mm68
-rw-r--r--views/mac/SkNSView.h52
-rw-r--r--views/mac/SkNSView.mm419
-rw-r--r--views/mac/SkOSWindow_Mac.cpp542
-rw-r--r--views/mac/SkOSWindow_Mac.mm80
-rw-r--r--views/mac/SkOptionsTableView.h40
-rw-r--r--views/mac/SkOptionsTableView.mm297
-rw-r--r--views/mac/SkSampleNSView.h12
-rw-r--r--views/mac/SkSampleNSView.mm36
-rw-r--r--views/mac/SkTextFieldCell.h15
-rw-r--r--views/mac/SkTextFieldCell.m56
-rw-r--r--views/mac/skia_mac.mm20
-rw-r--r--views/sdl/SkOSWindow_SDL.cpp226
-rw-r--r--views/unix/SkOSWindow_Unix.cpp422
-rw-r--r--views/unix/keysym2ucs.c848
-rw-r--r--views/unix/skia_unix.cpp37
-rw-r--r--views/win/SkOSWindow_win.cpp612
-rw-r--r--views/win/skia_win.cpp207
-rw-r--r--xml/SkBML_Verbs.h25
-rw-r--r--xml/SkBML_XMLParser.cpp181
-rw-r--r--xml/SkDOM.cpp503
-rw-r--r--xml/SkJS.cpp228
-rw-r--r--xml/SkJSDisplayable.cpp460
-rw-r--r--xml/SkXMLParser.cpp87
-rw-r--r--xml/SkXMLPullParser.cpp138
-rw-r--r--xml/SkXMLWriter.cpp333
1073 files changed, 267721 insertions, 0 deletions
diff --git a/animator/SkAnimate.h b/animator/SkAnimate.h
new file mode 100644
index 00000000..1bbecf90
--- /dev/null
+++ b/animator/SkAnimate.h
@@ -0,0 +1,34 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimate_DEFINED
+#define SkAnimate_DEFINED
+
+#include "SkAnimateBase.h"
+#include "SkDisplayType.h"
+#include "SkIntArray.h"
+#include "SkUtils.h"
+
+class SkAnimate : public SkAnimateBase {
+ DECLARE_MEMBER_INFO(Animate);
+ SkAnimate();
+ virtual ~SkAnimate();
+ virtual int components();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void onEndElement(SkAnimateMaker& maker);
+protected:
+ bool resolveCommon(SkAnimateMaker& );
+ int fComponents;
+private:
+ typedef SkAnimateBase INHERITED;
+};
+
+#endif // SkAnimateField_DEFINED
diff --git a/animator/SkAnimate3DSchema.xsd b/animator/SkAnimate3DSchema.xsd
new file mode 100644
index 00000000..5063b757
--- /dev/null
+++ b/animator/SkAnimate3DSchema.xsd
@@ -0,0 +1,39 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:Sk="http://www.skia.com/schema/SkAnimateSchema.xsd"
+ targetNamespace="urn:skia3D" xmlns:Sk3D="urn:skia3D">
+
+ <xs:simpleType name="Patch" >
+ <xs:restriction base="xs:string" >
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="Point" >
+ <xs:restriction base="xs:string" >
+ <xs:pattern value="[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)( *[ ,] *[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)){2}" />
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:element name="camera">
+ <xs:complexType >
+ <xs:attribute name="axis" type="Sk3D:Point" />
+ <xs:attribute name="hackHeight" type="Sk:Float" />
+ <xs:attribute name="hackWidth" type="Sk:Float" />
+ <xs:attribute name="location" type="Sk3D:Point" />
+ <xs:attribute name="observer" type="Sk3D:Point" />
+ <xs:attribute name="patch" type="Sk3D:Patch" />
+ <xs:attribute name="zenith" type="Sk3D:Point" />
+ <xs:attribute name="id" type="xs:ID" />
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="patch">
+ <xs:complexType >
+ <xs:attribute name="origin" type="Sk3D:Point" />
+ <xs:attribute name="rotateDegrees" type="Sk:MemberFunction" />
+ <xs:attribute name="u" type="Sk3D:Point" />
+ <xs:attribute name="v" type="Sk3D:Point" />
+ <xs:attribute name="id" type="xs:ID" />
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
diff --git a/animator/SkAnimate3DSchema.xsx b/animator/SkAnimate3DSchema.xsx
new file mode 100644
index 00000000..ceb7d890
--- /dev/null
+++ b/animator/SkAnimate3DSchema.xsx
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--This file is auto-generated by the XML Schema Designer. It holds layout information for components on the designer surface.-->
+<XSDDesignerLayout />
diff --git a/animator/SkAnimateActive.cpp b/animator/SkAnimateActive.cpp
new file mode 100644
index 00000000..46b849b5
--- /dev/null
+++ b/animator/SkAnimateActive.cpp
@@ -0,0 +1,504 @@
+
+/*
+ * 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 "SkAnimateActive.h"
+#include "SkAnimateBase.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateSet.h"
+#include "SkDrawGroup.h"
+#ifdef SK_DEBUG
+#include "SkTime.h"
+#endif
+
+// SkActive holds array of interpolators
+
+SkActive::SkActive(SkApply& apply, SkAnimateMaker& maker) : fApply(apply),
+ fMaxTime(0), fMaker(maker), fDrawIndex(0), fDrawMax(0) {
+}
+
+void SkActive::init()
+{
+ fAnimators = fApply.fAnimators;
+ int animators = fAnimators.count();
+ fInterpolators.setCount(animators);
+ memset(fInterpolators.begin(), 0, animators * sizeof(SkOperandInterpolator*));
+ fState.setCount(animators);
+ int index;
+ for (index = 0; index < animators; index++)
+ fInterpolators[index] = SkNEW(SkOperandInterpolator);
+ initState(&fApply, 0);
+// for (index = 0; index < animators; index++)
+// fState[index].bumpSave();
+ SkASSERT(fInterpolators.count() == fAnimators.count());
+}
+
+SkActive::~SkActive() {
+ int index;
+ for (index = 0; index < fSaveRestore.count(); index++)
+ delete[] fSaveRestore[index];
+ for (index = 0; index < fSaveInterpolators.count(); index++)
+ delete[] fSaveInterpolators[index];
+ for (index = 0; index < fInterpolators.count(); index++)
+ delete fInterpolators[index];
+}
+
+void SkActive::advance() {
+ if (fDrawMax < fDrawIndex)
+ fDrawMax = fDrawIndex;
+ fDrawIndex += fAnimators.count();
+}
+
+void SkActive::append(SkApply* apply) {
+ int oldCount = fAnimators.count();
+ SkTDAnimateArray& animates = apply->fAnimators;
+ int newCount = animates.count();
+ int index;
+ int total = oldCount + newCount;
+ if (total == 0)
+ return;
+ fInterpolators.setCount(total);
+ memset(&fInterpolators.begin()[oldCount], 0, newCount * sizeof(SkOperandInterpolator*));
+ for (index = oldCount; index < total; index++)
+ fInterpolators[index] = SkNEW(SkOperandInterpolator);
+ fAnimators.setCount(total);
+ memcpy(&fAnimators[oldCount], animates.begin(), sizeof(fAnimators[0]) *
+ newCount);
+ fState.setCount(total);
+ initState(apply, oldCount);
+ SkASSERT(fApply.scope == apply->scope);
+ for (index = 0; index < newCount; index++) {
+ SkAnimateBase* test = animates[index];
+// SkASSERT(fApply.scope == test->fTarget || fApply.scope->contains(test->fTarget));
+ SkActive::SkState& testState = fState[oldCount + index];
+ for (int inner = 0; inner < oldCount; inner++) {
+ SkAnimateBase* oldGuard = fAnimators[inner];
+ SkActive::SkState& oldState = fState[inner];
+ if (oldGuard->fTarget == test->fTarget && oldGuard->fFieldInfo == test->fFieldInfo &&
+ testState.fBegin == oldState.fBegin) {
+ delete fInterpolators[inner];
+ fInterpolators.remove(inner);
+ fAnimators.remove(inner);
+ testState.fSave = oldState.fSave;
+ if (oldState.fUnpostedEndEvent) {
+// SkDEBUGF(("%8x %8x active append: post on end\n", this, oldGuard));
+ fMaker.postOnEnd(oldGuard, oldState.fBegin + oldState.fDuration);
+ }
+ fState.remove(inner);
+ if (fApply.restore) {
+ int saveIndex = fSaveRestore.count();
+ SkASSERT(fSaveInterpolators.count() == saveIndex);
+ saveIndex += inner;
+ do {
+ saveIndex -= oldCount;
+ delete[] fSaveRestore[saveIndex];
+ fSaveRestore.remove(saveIndex);
+ delete[] fSaveInterpolators[saveIndex];
+ fSaveInterpolators.remove(saveIndex);
+ } while (saveIndex > 0);
+ }
+ oldCount--;
+ break;
+ }
+ }
+ }
+// total = oldCount + newCount;
+// for (index = oldCount; index < total; index++)
+// fState[index].bumpSave();
+ SkASSERT(fInterpolators.count() == fAnimators.count());
+}
+
+void SkActive::appendSave(int oldCount) {
+ SkASSERT(fDrawMax == 0); // if true, we can optimize below quite a bit
+ int newCount = fAnimators.count();
+ int saveIndex = fSaveRestore.count();
+ SkASSERT(fSaveInterpolators.count() == saveIndex);
+ int records = saveIndex / oldCount;
+ int newTotal = records * newCount;
+ fSaveRestore.setCount(newTotal);
+ do {
+ saveIndex -= oldCount;
+ newTotal -= newCount;
+ SkASSERT(saveIndex >= 0);
+ SkASSERT(newTotal >= 0);
+ memmove(&fSaveRestore[newTotal], &fSaveRestore[saveIndex], oldCount);
+ memset(&fSaveRestore[newTotal + oldCount], 0,
+ sizeof(fSaveRestore[0]) * (newCount - oldCount));
+ memmove(&fSaveInterpolators[newTotal],
+ &fSaveInterpolators[saveIndex], oldCount);
+ memset(&fSaveInterpolators[newTotal + oldCount], 0,
+ sizeof(fSaveRestore[0]) * (newCount - oldCount));
+ } while (saveIndex > 0);
+ SkASSERT(newTotal == 0);
+}
+
+void SkActive::calcDurations(int index)
+{
+ SkAnimateBase* animate = fAnimators[index];
+ SkMSec duration = animate->dur;
+ SkState& state = fState[index];
+ switch (state.fMode) {
+ case SkApply::kMode_immediate:
+ case SkApply::kMode_create:
+ duration = state.fSteps ? state.fSteps * SK_MSec1 : 1;
+ break;
+// case SkApply::kMode_hold: {
+// int entries = animate->entries();
+// SkScriptValue value;
+// value.fOperand = animate->getValues()[entries - 1];
+// value.fType = animate->getValuesType();
+// bool result = SkScriptEngine::ConvertTo(NULL, SkType_Int, &value);
+// SkASSERT(result);
+// duration = value.fOperand.fS32 * SK_MSec1;
+// break;
+// }
+ }
+ state.fDuration = duration;
+ SkMSec maxTime = state.fBegin + duration;
+ if (fMaxTime < maxTime)
+ fMaxTime = maxTime;
+}
+
+void SkActive::create(SkDrawable* drawable, SkMSec time) {
+ fApply.fLastTime = time;
+ fApply.refresh(fMaker);
+ for (int index = 0; index < fAnimators.count(); index++) {
+ SkAnimateBase* animate = fAnimators[index];
+ SkOperandInterpolator& interpolator = *fInterpolators[index];
+ int count = animate->components();
+ if (animate->formula.size() > 0) {
+ SkTDOperandArray values;
+ values.setCount(count);
+ SkDEBUGCODE(bool success = ) animate->fFieldInfo->setValue(fMaker, &values, 0, 0, NULL,
+ animate->getValuesType(), animate->formula);
+ SkASSERT(success);
+ fApply.applyValues(index, values.begin(), count, animate->getValuesType(), time);
+ } else {
+ SkAutoSTMalloc<16, SkOperand> values(count);
+ interpolator.timeToValues(time, values.get());
+ fApply.applyValues(index, values.get(), count, animate->getValuesType(), time);
+ }
+ }
+ drawable->enable(fMaker);
+ SkASSERT(fAnimators.count() == fInterpolators.count());
+}
+
+bool SkActive::immediate(bool enable) {
+ SkMSec time = 0;
+ bool result = false;
+ SkDrawable* drawable = fApply.scope;
+ SkMSec final = fMaxTime;
+ do {
+ bool applied = fAnimators.count() == 0;
+ fApply.fLastTime = time;
+ fApply.refresh(fMaker);
+ for (int index = 0; index < fAnimators.count(); index++) {
+ SkAnimateBase* animate = fAnimators[index];
+ SkState& state = fState[index];
+ if (state.fMode != SkApply::kMode_immediate)
+ continue;
+ if (state.fBegin > time)
+ continue;
+ if (time > state.fBegin + state.fDuration)
+ continue;
+ applied = true;
+ SkOperandInterpolator& interpolator = *fInterpolators[index];
+ int count = animate->components();
+ if (animate->formula.size() > 0) {
+ SkTDOperandArray values;
+ values.setCount(count);
+ SkDEBUGCODE(bool success = ) animate->fFieldInfo->setValue(fMaker, &values, 0, 0, NULL,
+ animate->getValuesType(), animate->formula);
+ SkASSERT(success);
+ fApply.applyValues(index, values.begin(), count, animate->getValuesType(), time);
+ } else {
+ SkAutoSTMalloc<16, SkOperand> values(count);
+ interpolator.timeToValues(time, values.get());
+ fApply.applyValues(index, values.get(), count, animate->getValuesType(), time);
+ }
+ }
+ if (enable)
+ drawable->enable(fMaker);
+ else if (applied)
+ result |= drawable->draw(fMaker);
+ time += SK_MSec1;
+ } while (time <= final);
+ return result;
+}
+
+void SkActive::fixInterpolator(SkBool save) {
+ int animators = fAnimators.count();
+ for (int index = 0; index < animators; index++) {
+ SkAnimateBase* animate = fAnimators[index];
+ if (save) { // saved slots increased
+ animate->refresh(fMaker);
+ SkOperand* values = animate->getValues();
+ setInterpolator(index, values);
+ saveInterpolatorValues(index);
+ } else
+ restoreInterpolatorValues(index);
+ }
+}
+
+SkMSec SkActive::getTime(SkMSec inTime, int animatorIndex) {
+ fState[animatorIndex].fTicks = inTime;
+ return inTime - fState[animatorIndex].fStartTime;
+}
+
+bool SkActive::initializeSave() {
+ int animators = fAnimators.count();
+ int activeTotal = fDrawIndex + animators;
+ int oldCount = fSaveRestore.count();
+ if (oldCount < activeTotal) {
+ fSaveRestore.setCount(activeTotal);
+ memset(&fSaveRestore[oldCount], 0, sizeof(fSaveRestore[0]) * (activeTotal - oldCount));
+ SkASSERT(fSaveInterpolators.count() == oldCount);
+ fSaveInterpolators.setCount(activeTotal);
+ memset(&fSaveInterpolators[oldCount], 0,
+ sizeof(fSaveInterpolators[0]) * (activeTotal - oldCount));
+ return true;
+ }
+ return false;
+}
+
+void SkActive::initState(SkApply* apply, int offset) {
+ int count = fState.count();
+ for (int index = offset; index < count; index++) {
+ SkState& state = fState[index];
+ SkAnimateBase* animate = fAnimators[index];
+#if 0 // def SK_DEBUG
+ if (animate->fHasEndEvent)
+ SkDebugf("%8x %8x active initState:\n", this, animate);
+#endif
+ SkOperand* from = animate->getValues();
+ state.fStartTime = state.fBegin = apply->begin + animate->begin;
+ state.fMode = apply->mode;
+ state.fTransition = apply->transition;
+#if 0
+ state.fPickup = (SkBool8) apply->pickup;
+#endif
+ state.fRestore = (SkBool8) apply->restore;
+ state.fSave = apply->begin;
+ state.fStarted = false;
+ state.fSteps = apply->steps;
+ state.fTicks = 0;
+ state.fUnpostedEndEvent = (SkBool8) animate->fHasEndEvent;
+ calcDurations(index);
+ setInterpolator(index, from);
+ }
+ if (count == 0 && (apply->mode == SkApply::kMode_immediate || apply->mode == SkApply::kMode_create))
+ fMaxTime = apply->begin + apply->steps * SK_MSec1;
+}
+
+void SkActive::pickUp(SkActive* existing) {
+ SkTDOperandArray existingValues;
+ for (int index = 0; index < fAnimators.count(); index++) {
+ SkAnimateBase* animate = fAnimators[index];
+ SkASSERT(animate->getValuesType() == SkType_Float);
+ int components = animate->components();
+ SkOperand* from = animate->getValues();
+ SkOperand* to = &from[animate->components()];
+ existingValues.setCount(components);
+ existing->fInterpolators[index]->timeToValues(
+ existing->fState[index].fTicks - existing->fState[index].fStartTime, existingValues.begin());
+ SkScalar originalSum = 0;
+ SkScalar workingSum = 0;
+ for (int cIndex = 0; cIndex < components; cIndex++) {
+ SkScalar delta = to[cIndex].fScalar - from[cIndex].fScalar;
+ originalSum += SkScalarMul(delta, delta);
+ delta = to[cIndex].fScalar - existingValues[cIndex].fScalar;
+ workingSum += SkScalarMul(delta, delta);
+ }
+ if (workingSum < originalSum) {
+ SkScalar originalDistance = SkScalarSqrt(originalSum);
+ SkScalar workingDistance = SkScalarSqrt(workingSum);
+ existing->fState[index].fDuration = (SkMSec) SkScalarMulDiv(fState[index].fDuration,
+ workingDistance, originalDistance);
+ }
+ fInterpolators[index]->reset(components, 2, SkType_Float);
+ fInterpolators[index]->setKeyFrame(0, 0, existingValues.begin(), animate->blend[0]);
+ fInterpolators[index]->setKeyFrame(1, fState[index].fDuration, to, animate->blend[0]);
+ }
+}
+
+void SkActive::resetInterpolators() {
+ int animators = fAnimators.count();
+ for (int index = 0; index < animators; index++) {
+ SkAnimateBase* animate = fAnimators[index];
+ SkOperand* values = animate->getValues();
+ setInterpolator(index, values);
+ }
+}
+
+void SkActive::resetState() {
+ fDrawIndex = 0;
+ int count = fState.count();
+ for (int index = 0; index < count; index++) {
+ SkState& state = fState[index];
+ SkAnimateBase* animate = fAnimators[index];
+#if 0 // def SK_DEBUG
+ if (animate->fHasEndEvent)
+ SkDebugf("%8x %8x active resetState: has end event\n", this, animate);
+#endif
+ state.fStartTime = state.fBegin = fApply.begin + animate->begin;
+ state.fStarted = false;
+ state.fTicks = 0;
+ }
+}
+
+void SkActive::restoreInterpolatorValues(int index) {
+ SkOperandInterpolator& interpolator = *fInterpolators[index];
+ index += fDrawIndex ;
+ int count = interpolator.getValuesCount();
+ memcpy(interpolator.getValues(), fSaveInterpolators[index], count * sizeof(SkOperand));
+}
+
+void SkActive::saveInterpolatorValues(int index) {
+ SkOperandInterpolator& interpolator = *fInterpolators[index];
+ index += fDrawIndex ;
+ int count = interpolator.getValuesCount();
+ SkOperand* cache = new SkOperand[count]; // this should use sk_malloc/sk_free since SkOperand does not have a constructor/destructor
+ fSaveInterpolators[index] = cache;
+ memcpy(cache, interpolator.getValues(), count * sizeof(SkOperand));
+}
+
+void SkActive::setInterpolator(int index, SkOperand* from) {
+ if (from == NULL) // legitimate for set string
+ return;
+ SkAnimateBase* animate = fAnimators[index];
+ int entries = animate->entries();
+ SkASSERT(entries > 0);
+ SkMSec duration = fState[index].fDuration;
+ int components = animate->components();
+ SkOperandInterpolator& interpolator = *fInterpolators[index];
+ interpolator.reset(components, entries == 1 ? 2 : entries, animate->getValuesType());
+ interpolator.setMirror(SkToBool(animate->fMirror));
+ interpolator.setReset(SkToBool(animate->fReset));
+ interpolator.setRepeatCount(animate->repeat);
+ if (entries == 1) {
+ interpolator.setKeyFrame(0, 0, from, animate->blend[0]);
+ interpolator.setKeyFrame(1, duration, from, animate->blend[0]);
+ return;
+ }
+ for (int entry = 0; entry < entries; entry++) {
+ int blendIndex = SkMin32(animate->blend.count() - 1, entry);
+ interpolator.setKeyFrame(entry, entry * duration / (entries - 1), from,
+ animate->blend[blendIndex]);
+ from += components;
+ }
+}
+
+void SkActive::setSteps(int steps) {
+ int count = fState.count();
+ fMaxTime = 0;
+ for (int index = 0; index < count; index++) {
+ SkState& state = fState[index];
+ state.fSteps = steps;
+ calcDurations(index);
+ }
+}
+
+void SkActive::start() {
+ int count = fState.count();
+ SkASSERT(count == fAnimators.count());
+ SkASSERT(count == fInterpolators.count());
+ for (int index = 0; index < count; index++) {
+ SkState& state = fState[index];
+ if (state.fStarted)
+ continue;
+ state.fStarted = true;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkString debugOut;
+ SkMSec time = fMaker.getAppTime();
+ debugOut.appendS32(time - fMaker.fDebugTimeBase);
+ debugOut.append(" active start adjust delay id=");
+ debugOut.append(fApply._id);
+ debugOut.append("; ");
+ debugOut.append(fAnimators[index]->_id);
+ debugOut.append("=");
+ debugOut.appendS32(fAnimators[index]->fStart - fMaker.fDebugTimeBase);
+ debugOut.append(":");
+ debugOut.appendS32(state.fStartTime);
+#endif
+ if (state.fStartTime > 0) {
+ SkMSec future = fAnimators[index]->fStart + state.fStartTime;
+ if (future > fMaker.fEnableTime)
+ fMaker.notifyInvalTime(future);
+ else
+ fMaker.notifyInval();
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ debugOut.append(":");
+ debugOut.appendS32(future - fMaker.fDebugTimeBase);
+#endif
+ }
+ if (state.fStartTime >= fMaker.fAdjustedStart) {
+ state.fStartTime -= fMaker.fAdjustedStart;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ debugOut.append(" (less adjust = ");
+ debugOut.appendS32(fMaker.fAdjustedStart);
+#endif
+ }
+ state.fStartTime += fAnimators[index]->fStart;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ debugOut.append(") new start = ");
+ debugOut.appendS32(state.fStartTime - fMaker.fDebugTimeBase);
+ SkDebugf("%s\n", debugOut.c_str());
+// SkASSERT((int) (state.fStartTime - fMaker.fDebugTimeBase) >= 0);
+#endif
+ }
+ SkASSERT(fAnimators.count() == fInterpolators.count());
+}
+
+#ifdef SK_DEBUG
+void SkActive::validate() {
+ int count = fState.count();
+ SkASSERT(count == fAnimators.count());
+ SkASSERT(count == fInterpolators.count());
+ for (int index = 0; index < count; index++) {
+ SkASSERT(fAnimators[index]);
+ SkASSERT(fInterpolators[index]);
+// SkAnimateBase* test = fAnimators[index];
+// SkASSERT(fApply.scope == test->fTarget || fApply.scope->contains(test->fTarget));
+ }
+}
+#endif
+
+// think about this
+// there should only be one animate object, not two, to go up and down
+// when the apply with reverse came into play, it needs to pick up the value
+// of the existing animate object then remove it from the list
+// the code below should only be bumping fSave, and there shouldn't be anything
+// it needs to be synchronized with
+
+// however, if there are two animates both operating on the same field, then
+// when one replaces the other, it may make sense to pick up the old value as a starting
+// value for the new one somehow.
+
+//void SkActive::SkState::bumpSave() {
+// if (fMode != SkApply::kMode_hold)
+// return;
+// if (fTransition == SkApply::kTransition_reverse) {
+// if (fSave > 0)
+// fSave -= SK_MSec1;
+// } else if (fSave < fDuration)
+// fSave += SK_MSec1;
+//}
+
+SkMSec SkActive::SkState::getRelativeTime(SkMSec time) {
+ SkMSec result = time;
+// if (fMode == SkApply::kMode_hold)
+// result = fSave;
+// else
+ if (fTransition == SkApply::kTransition_reverse) {
+ if (SkMSec_LT(fDuration, time))
+ result = 0;
+ else
+ result = fDuration - time;
+ }
+ return result;
+}
diff --git a/animator/SkAnimateActive.h b/animator/SkAnimateActive.h
new file mode 100644
index 00000000..33d0164c
--- /dev/null
+++ b/animator/SkAnimateActive.h
@@ -0,0 +1,79 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimateActive_DEFINED
+#define SkAnimateActive_DEFINED
+
+#include "SkDisplayApply.h"
+#include "SkOperandInterpolator.h"
+#include "SkIntArray.h"
+
+class SkAnimateMaker;
+
+class SkActive {
+public:
+ SkActive(SkApply& , SkAnimateMaker& );
+ ~SkActive();
+ void advance();
+ void append(SkApply* );
+ void calcDurations(int index);
+ void create(SkDrawable* scope, SkMSec time);
+ bool draw() { return immediate(false); }
+ bool enable() { return immediate(true); }
+ void init( );
+ SkMSec getTime(SkMSec inTime, int animatorIndex);
+ void pickUp(SkActive* existing);
+ void reset() { fDrawIndex = 0; }
+ void setInterpolator(int index, SkOperand* from);
+ void start();
+#ifdef SK_DEBUG
+ void validate();
+#endif
+private:
+ void appendSave(int oldCount);
+ void fixInterpolator(SkBool save);
+ bool immediate(bool enable);
+ bool initializeSave();
+ void initState(SkApply* , int offset);
+ void resetInterpolators();
+ void resetState();
+ void restoreInterpolatorValues(int index);
+ void saveInterpolatorValues(int index);
+ void setSteps(int steps);
+ struct SkState {
+// void bumpSave();
+ SkMSec getRelativeTime(SkMSec time);
+ SkApply::Mode fMode;
+ SkApply::Transition fTransition;
+ SkBool8 fPickup;
+ SkBool8 fRestore;
+ SkBool8 fStarted;
+ SkBool8 fUnpostedEndEvent;
+ int32_t fSteps;
+ SkMSec fBegin;
+ SkMSec fStartTime;
+ SkMSec fDuration;
+ SkMSec fSave;
+ SkMSec fTicks;
+ };
+ SkActive& operator= (const SkActive& );
+ SkTDArray<SkOperandInterpolator*> fInterpolators;
+ SkApply& fApply;
+ SkTDArray<SkState> fState; // one per animator
+ SkTDOperandPtrArray fSaveRestore; // if apply has restore="true"
+ SkTDOperandPtrArray fSaveInterpolators;
+ SkTDAnimateArray fAnimators;
+ SkMSec fMaxTime; // greatest of all animation durations; only used by immediate mode
+ SkAnimateMaker& fMaker;
+ int fDrawIndex;
+ int fDrawMax;
+ friend class SkApply;
+};
+
+#endif // SkAnimateActive_DEFINED
diff --git a/animator/SkAnimateBase.cpp b/animator/SkAnimateBase.cpp
new file mode 100644
index 00000000..3d50144a
--- /dev/null
+++ b/animator/SkAnimateBase.cpp
@@ -0,0 +1,233 @@
+
+/*
+ * 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 "SkAnimateBase.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateProperties.h"
+#include "SkAnimatorScript.h"
+#include "SkDisplayApply.h"
+#include "SkDrawable.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAnimateBase::fInfo[] = {
+ SK_MEMBER(begin, MSec),
+ SK_MEMBER_ARRAY(blend, Float),
+ SK_MEMBER(dur, MSec),
+ SK_MEMBER_PROPERTY(dynamic, Boolean),
+ SK_MEMBER(field, String), // name of member info in target
+ SK_MEMBER(formula, DynamicString),
+ SK_MEMBER(from, DynamicString),
+ SK_MEMBER(lval, DynamicString),
+ SK_MEMBER_PROPERTY(mirror, Boolean),
+ SK_MEMBER(repeat, Float),
+ SK_MEMBER_PROPERTY(reset, Boolean),
+ SK_MEMBER_PROPERTY(step, Int),
+ SK_MEMBER(target, DynamicString),
+ SK_MEMBER(to, DynamicString),
+ SK_MEMBER_PROPERTY(values, DynamicString)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAnimateBase);
+
+SkAnimateBase::SkAnimateBase() : begin(0), dur(1), repeat(SK_Scalar1),
+ fApply(NULL), fFieldInfo(NULL), fFieldOffset(0), fStart((SkMSec) -1), fTarget(NULL),
+ fChanged(0), fDelayed(0), fDynamic(0), fHasEndEvent(0), fHasValues(0),
+ fMirror(0), fReset(0), fResetPending(0), fTargetIsScope(0) {
+ blend.setCount(1);
+ blend[0] = SK_Scalar1;
+}
+
+SkAnimateBase::~SkAnimateBase() {
+ SkDisplayTypes type = fValues.getType();
+ if (type == SkType_String || type == SkType_DynamicString) {
+ SkASSERT(fValues.count() == 1);
+ delete fValues[0].fString;
+ }
+}
+
+int SkAnimateBase::components() {
+ return 1;
+}
+
+SkDisplayable* SkAnimateBase::deepCopy(SkAnimateMaker* maker) {
+ SkAnimateBase* result = (SkAnimateBase*) INHERITED::deepCopy(maker);
+ result->fApply = fApply;
+ result->fFieldInfo =fFieldInfo;
+ result->fHasValues = false;
+ return result;
+}
+
+void SkAnimateBase::dirty() {
+ fChanged = true;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkAnimateBase::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ if (target.size() > 0)
+ SkDebugf("target=\"%s\" ", target.c_str());
+ else if (fTarget && strcmp(fTarget->id, ""))
+ SkDebugf("target=\"%s\" ", fTarget->id);
+ if (lval.size() > 0)
+ SkDebugf("lval=\"%s\" ", lval.c_str());
+ if (field.size() > 0)
+ SkDebugf("field=\"%s\" ", field.c_str());
+ else if (fFieldInfo)
+ SkDebugf("field=\"%s\" ", fFieldInfo->fName);
+ if (formula.size() > 0)
+ SkDebugf("formula=\"%s\" ", formula.c_str());
+ else {
+ if (from.size() > 0)
+ SkDebugf("from=\"%s\" ", from.c_str());
+ SkDebugf("to=\"%s\" ", to.c_str());
+ }
+ if (begin != 0) {
+ SkDebugf("begin=\"%g\" ", SkScalarToFloat(SkScalarDiv(begin,1000)));
+ }
+}
+#endif
+
+SkDisplayable* SkAnimateBase::getParent() const {
+ return (SkDisplayable*) fApply;
+}
+
+bool SkAnimateBase::getProperty(int index, SkScriptValue* value) const {
+ int boolResult;
+ switch (index) {
+ case SK_PROPERTY(dynamic):
+ boolResult = fDynamic;
+ goto returnBool;
+ case SK_PROPERTY(mirror):
+ boolResult = fMirror;
+ goto returnBool;
+ case SK_PROPERTY(reset):
+ boolResult = fReset;
+returnBool:
+ value->fOperand.fS32 = SkToBool(boolResult);
+ value->fType = SkType_Boolean;
+ break;
+ case SK_PROPERTY(step):
+ if (fApply == NULL)
+ return false; // !!! notify there's an error?
+ fApply->getStep(value);
+ break;
+ case SK_PROPERTY(values):
+ value->fOperand.fString = (SkString*) &to;
+ value->fType = SkType_String;
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+bool SkAnimateBase::hasExecute() const
+{
+ return false;
+}
+
+void SkAnimateBase::onEndElement(SkAnimateMaker& maker) {
+ fChanged = false;
+ setTarget(maker);
+ if (field.size()) {
+ SkASSERT(fTarget);
+ fFieldInfo = fTarget->getMember(field.c_str());
+ field.reset();
+ }
+ if (lval.size()) {
+ // lval must be of the form x[y]
+ const char* lvalStr = lval.c_str();
+ const char* arrayEnd = strchr(lvalStr, '[');
+ if (arrayEnd == NULL)
+ return; //should this return an error?
+ size_t arrayNameLen = arrayEnd - lvalStr;
+ SkString arrayStr(lvalStr, arrayNameLen);
+ SkASSERT(fTarget); //this return an error?
+ fFieldInfo = fTarget->getMember(arrayStr.c_str());
+ SkString scriptStr(arrayEnd + 1, lval.size() - arrayNameLen - 2);
+ SkAnimatorScript::EvaluateInt(maker, this, scriptStr.c_str(), &fFieldOffset);
+ }
+}
+
+void SkAnimateBase::packARGB(SkScalar array[], int count, SkTDOperandArray* converted)
+{
+ SkASSERT(count == 4);
+ converted->setCount(1);
+ SkColor color = SkColorSetARGB(SkScalarRound(array[0]), SkScalarRound(array[1]),
+ SkScalarRound(array[2]), SkScalarRound(array[3]));
+ (*converted)[0].fS32 = color;
+}
+
+
+
+void SkAnimateBase::refresh(SkAnimateMaker& ) {
+}
+
+bool SkAnimateBase::setParent(SkDisplayable* apply) {
+ SkASSERT(apply->isApply());
+ fApply = (SkApply*) apply;
+ return false;
+}
+
+bool SkAnimateBase::setProperty(int index, SkScriptValue& value) {
+ bool boolValue = SkToBool(value.fOperand.fS32);
+ switch (index) {
+ case SK_PROPERTY(dynamic):
+ fDynamic = boolValue;
+ goto checkForBool;
+ case SK_PROPERTY(values):
+ fHasValues = true;
+ SkASSERT(value.fType == SkType_String);
+ to = *value.fOperand.fString;
+ break;
+ case SK_PROPERTY(mirror):
+ fMirror = boolValue;
+ goto checkForBool;
+ case SK_PROPERTY(reset):
+ fReset = boolValue;
+checkForBool:
+ SkASSERT(value.fType == SkType_Boolean);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void SkAnimateBase::setTarget(SkAnimateMaker& maker) {
+ if (target.size()) {
+ SkAnimatorScript engine(maker, this, SkType_Displayable);
+ const char* script = target.c_str();
+ SkScriptValue scriptValue;
+ bool success = engine.evaluateScript(&script, &scriptValue);
+ if (success && scriptValue.fType == SkType_Displayable)
+ fTarget = scriptValue.fOperand.fDrawable;
+ else if (maker.find(target.c_str(), (SkDisplayable**) &fTarget) == false) {
+ if (fApply->getMode() == SkApply::kMode_create)
+ return; // may not be an error
+ if (engine.getError() != SkScriptEngine::kNoError)
+ maker.setScriptError(engine);
+ else {
+ maker.setErrorNoun(target);
+ maker.setErrorCode(SkDisplayXMLParserError::kTargetIDNotFound);
+ }
+ return;
+ }
+ if (fApply && fApply->getMode() != SkApply::kMode_create)
+ target.reset();
+ }
+}
+
+bool SkAnimateBase::targetNeedsInitialization() const {
+ return false;
+}
diff --git a/animator/SkAnimateBase.h b/animator/SkAnimateBase.h
new file mode 100644
index 00000000..df8d38ac
--- /dev/null
+++ b/animator/SkAnimateBase.h
@@ -0,0 +1,83 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimateBase_DEFINED
+#define SkAnimateBase_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMath.h"
+#include "SkMemberInfo.h"
+#include "SkTypedArray.h"
+
+class SkApply;
+class SkDrawable;
+
+class SkAnimateBase : public SkDisplayable {
+public:
+ DECLARE_MEMBER_INFO(AnimateBase);
+ SkAnimateBase();
+ virtual ~SkAnimateBase();
+ virtual int components();
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual void dirty();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ int entries() { return fValues.count() / components(); }
+ virtual bool hasExecute() const;
+ bool isDynamic() const { return SkToBool(fDynamic); }
+ virtual SkDisplayable* getParent() const;
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ SkMSec getStart() const { return fStart; }
+ SkOperand* getValues() { return fValues.begin(); }
+ SkDisplayTypes getValuesType() { return fValues.getType(); }
+ virtual void onEndElement(SkAnimateMaker& );
+ void packARGB(SkScalar [], int count, SkTDOperandArray* );
+ virtual void refresh(SkAnimateMaker& );
+ void setChanged(bool changed) { fChanged = changed; }
+ void setHasEndEvent() { fHasEndEvent = true; }
+ virtual bool setParent(SkDisplayable* );
+ virtual bool setProperty(int index, SkScriptValue& value);
+ void setTarget(SkAnimateMaker& );
+ virtual bool targetNeedsInitialization() const;
+protected:
+ SkMSec begin;
+ SkTDScalarArray blend;
+ SkMSec dur;
+ // !!! make field part of a union with fFieldInfo, or fValues, something known later?
+ SkString field; // temporary; once target is known, this is reset
+ SkString formula;
+ SkString from;
+ SkString lval;
+ SkScalar repeat;
+ SkString target; // temporary; once target is known, this is reset
+ SkString to;
+ SkApply* fApply;
+ const SkMemberInfo* fFieldInfo;
+ int fFieldOffset;
+ SkMSec fStart; // corrected time when this apply was enabled
+ SkDrawable* fTarget;
+ SkTypedArray fValues;
+ unsigned fChanged : 1; // true when value referenced by script has changed
+ unsigned fDelayed : 1; // enabled, but undrawn pending delay
+ unsigned fDynamic : 1;
+ unsigned fHasEndEvent : 1;
+ unsigned fHasValues : 1; // set if 'values' passed instead of 'to'
+ unsigned fMirror : 1;
+ unsigned fReset : 1;
+ unsigned fResetPending : 1;
+ unsigned fTargetIsScope : 1;
+private:
+ typedef SkDisplayable INHERITED;
+ friend class SkActive;
+ friend class SkApply;
+ friend class SkDisplayList;
+};
+
+#endif // SkAnimateBase_DEFINED
diff --git a/animator/SkAnimateField.cpp b/animator/SkAnimateField.cpp
new file mode 100644
index 00000000..0f92989d
--- /dev/null
+++ b/animator/SkAnimateField.cpp
@@ -0,0 +1,111 @@
+
+/*
+ * 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 "SkAnimate.h"
+#include "SkAnimateMaker.h"
+#include "SkDrawable.h"
+#include "SkParse.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAnimate::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAnimate);
+
+SkAnimate::SkAnimate() : fComponents(0) {
+}
+
+SkAnimate::~SkAnimate() {
+}
+
+int SkAnimate::components() {
+ return fComponents;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkAnimate::dump(SkAnimateMaker* maker) {
+ INHERITED::dump(maker); //from animateBase
+ //SkSet inherits from this class
+ if (getType() != SkType_Set) {
+ if (fMirror)
+ SkDebugf("mirror=\"true\" ");
+ if (fReset)
+ SkDebugf("reset=\"true\" ");
+ SkDebugf("dur=\"%g\" ", SkScalarToFloat(SkScalarDiv(dur,1000)));
+ if (repeat != SK_Scalar1)
+ SkDebugf("repeat=\"%g\" ", SkScalarToFloat(repeat));
+ //if (fHasValues)
+ // SkDebugf("values=\"%s\" ", values);
+ if (blend.count() != 1 || blend[0] != SK_Scalar1) {
+ SkDebugf("blend=\"[");
+ bool firstElem = true;
+ for (int i = 0; i < blend.count(); i++) {
+ if (!firstElem)
+ SkDebugf(",");
+ firstElem = false;
+ SkDebugf("%g", SkScalarToFloat(blend[i]));
+ }
+ SkDebugf("]\" ");
+ }
+ SkDebugf("/>\n");//i assume that if it IS, we will do it separately
+ }
+}
+#endif
+
+bool SkAnimate::resolveCommon(SkAnimateMaker& maker) {
+ if (fTarget == NULL) // if NULL, recall onEndElement after apply closes and sets target to scope
+ return false;
+ INHERITED::onEndElement(maker);
+ return maker.hasError() == false;
+}
+
+void SkAnimate::onEndElement(SkAnimateMaker& maker) {
+ bool resolved = resolveCommon(maker);
+ if (resolved && fFieldInfo == NULL) {
+ maker.setErrorNoun(field);
+ maker.setErrorCode(SkDisplayXMLParserError::kFieldNotInTarget);
+ }
+ if (resolved == false || fFieldInfo == NULL)
+ return;
+ SkDisplayTypes outType = fFieldInfo->getType();
+ if (fHasValues) {
+ SkASSERT(to.size() > 0);
+ fFieldInfo->setValue(maker, &fValues, 0, 0, NULL, outType, to);
+ SkASSERT(0);
+ // !!! this needs to set fComponents
+ return;
+ }
+ fComponents = fFieldInfo->getCount();
+ if (fFieldInfo->fType == SkType_Array) {
+ SkTypedArray* array = (SkTypedArray*) fFieldInfo->memberData(fTarget);
+ int count = array->count();
+ if (count > 0)
+ fComponents = count;
+ }
+ if (outType == SkType_ARGB) {
+ fComponents <<= 2; // four color components
+ outType = SkType_Float;
+ }
+ fValues.setType(outType);
+ if (formula.size() > 0){
+ fComponents = 1;
+ from.set("0");
+ to.set("dur");
+ outType = SkType_MSec;
+ }
+ int max = fComponents * 2;
+ fValues.setCount(max);
+ memset(fValues.begin(), 0, max * sizeof(fValues.begin()[0]));
+ fFieldInfo->setValue(maker, &fValues, fFieldOffset, max, this, outType, from);
+ fFieldInfo->setValue(maker, &fValues, fComponents + fFieldOffset, max, this, outType, to);
+}
diff --git a/animator/SkAnimateMaker.cpp b/animator/SkAnimateMaker.cpp
new file mode 100644
index 00000000..a3ebb642
--- /dev/null
+++ b/animator/SkAnimateMaker.cpp
@@ -0,0 +1,372 @@
+
+/*
+ * 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 "SkAnimateMaker.h"
+#include "SkAnimator.h"
+#include "SkAnimatorScript.h"
+#include "SkDisplayable.h"
+#include "SkDisplayApply.h"
+#include "SkDisplayList.h"
+#include "SkDisplayMovie.h"
+#include "SkDisplayType.h"
+#include "SkExtras.h"
+#include "SkMemberInfo.h"
+#include "SkStream.h"
+#include "SkSystemEventTypes.h"
+#include "SkTime.h"
+
+class DefaultTimeline : public SkAnimator::Timeline {
+ virtual SkMSec getMSecs() const {
+ return SkTime::GetMSecs();
+ }
+} gDefaultTimeline;
+
+SkAnimateMaker::SkAnimateMaker(SkAnimator* animator, SkCanvas* canvas, SkPaint* paint)
+ : fActiveEvent(NULL), fAdjustedStart(0), fCanvas(canvas), fEnableTime(0),
+ fHostEventSinkID(0), fMinimumInterval((SkMSec) -1), fPaint(paint), fParentMaker(NULL),
+ fTimeline(&gDefaultTimeline), fInInclude(false), fInMovie(false),
+ fFirstScriptError(false), fLoaded(false), fIDs(256), fAnimator(animator)
+{
+ fScreenplay.time = 0;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ fDebugTimeBase = (SkMSec) -1;
+#endif
+#ifdef SK_DUMP_ENABLED
+ fDumpEvents = fDumpGConditions = fDumpPosts = false;
+#endif
+}
+
+SkAnimateMaker::~SkAnimateMaker() {
+ deleteMembers();
+}
+
+#if 0
+SkMSec SkAnimateMaker::adjustDelay(SkMSec expectedBase, SkMSec delay) {
+ SkMSec appTime = (*fTimeCallBack)();
+ if (appTime)
+ delay -= appTime - expectedBase;
+ if (delay < 0)
+ delay = 0;
+ return delay;
+}
+#endif
+
+void SkAnimateMaker::appendActive(SkActive* active) {
+ fDisplayList.append(active);
+}
+
+void SkAnimateMaker::clearExtraPropertyCallBack(SkDisplayTypes type) {
+ SkExtras** end = fExtras.end();
+ for (SkExtras** extraPtr = fExtras.begin(); extraPtr < end; extraPtr++) {
+ SkExtras* extra = *extraPtr;
+ if (extra->definesType(type)) {
+ extra->fExtraCallBack = NULL;
+ extra->fExtraStorage = NULL;
+ break;
+ }
+ }
+}
+
+bool SkAnimateMaker::computeID(SkDisplayable* displayable, SkDisplayable* parent, SkString* newID) {
+ const char* script;
+ if (findKey(displayable, &script) == false)
+ return true;
+ return SkAnimatorScript::EvaluateString(*this, displayable, parent, script, newID);
+}
+
+SkDisplayable* SkAnimateMaker::createInstance(const char name[], size_t len) {
+ SkDisplayTypes type = SkDisplayType::GetType(this, name, len );
+ if ((int)type >= 0)
+ return SkDisplayType::CreateInstance(this, type);
+ return NULL;
+}
+
+// differs from SkAnimator::decodeStream in that it does not reset error state
+bool SkAnimateMaker::decodeStream(SkStream* stream)
+{
+ SkDisplayXMLParser parser(*this);
+ return parser.parse(*stream);
+}
+
+// differs from SkAnimator::decodeURI in that it does not set URI base
+bool SkAnimateMaker::decodeURI(const char uri[]) {
+// SkDebugf("animator decode %s\n", uri);
+
+// SkStream* stream = SkStream::GetURIStream(fPrefix.c_str(), uri);
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(uri));
+ if (stream.get()) {
+ bool success = decodeStream(stream);
+ if (hasError() && fError.hasNoun() == false)
+ fError.setNoun(uri);
+ return success;
+ } else {
+ return false;
+ }
+}
+
+#if defined SK_DEBUG && 0
+//used for the if'd out section of deleteMembers
+#include "SkTSearch.h"
+
+extern "C" {
+ int compare_disp(const void* a, const void* b) {
+ return *(const SkDisplayable**)a - *(const SkDisplayable**)b;
+ }
+}
+#endif
+
+void SkAnimateMaker::delayEnable(SkApply* apply, SkMSec time) {
+ int index = fDelayed.find(apply);
+ if (index < 0) {
+ *fDelayed.append() = apply;
+ }
+
+ (new SkEvent(SK_EventType_Delay, fAnimator->getSinkID()))->postTime(time);
+}
+
+void SkAnimateMaker::deleteMembers() {
+ int index;
+#if defined SK_DEBUG && 0
+ //this code checks to see if helpers are among the children, but it is not complete -
+ //it should check the children of the children
+ int result;
+ SkTDArray<SkDisplayable*> children(fChildren.begin(), fChildren.count());
+ SkQSort(children.begin(), children.count(), sizeof(SkDisplayable*),compare_disp);
+ for (index = 0; index < fHelpers.count(); index++) {
+ SkDisplayable* helper = fHelpers[index];
+ result = SkTSearch(children.begin(), children.count(), helper, sizeof(SkDisplayable*));
+ SkASSERT(result < 0);
+ }
+#endif
+ for (index = 0; index < fChildren.count(); index++) {
+ SkDisplayable* child = fChildren[index];
+ delete child;
+ }
+ for (index = 0; index < fHelpers.count(); index++) {
+ SkDisplayable* helper = fHelpers[index];
+ delete helper;
+ }
+ for (index = 0; index < fExtras.count(); index++) {
+ SkExtras* extras = fExtras[index];
+ delete extras;
+ }
+}
+
+void SkAnimateMaker::doDelayedEvent() {
+ fEnableTime = getAppTime();
+ for (int index = 0; index < fDelayed.count(); ) {
+ SkDisplayable* child = fDelayed[index];
+ SkASSERT(child->isApply());
+ SkApply* apply = (SkApply*) child;
+ apply->interpolate(*this, fEnableTime);
+ if (apply->hasDelayedAnimator())
+ index++;
+ else
+ fDelayed.remove(index);
+ }
+}
+
+bool SkAnimateMaker::doEvent(const SkEvent& event) {
+ return (!fInMovie || fLoaded) && fAnimator->doEvent(event);
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkAnimateMaker::dump(const char* match) {
+ SkTDict<SkDisplayable*>::Iter iter(fIDs);
+ const char* name;
+ SkDisplayable* result;
+ while ((name = iter.next(&result)) != NULL) {
+ if (strcmp(match,name) == 0)
+ result->dump(this);
+ }
+}
+#endif
+
+int SkAnimateMaker::dynamicProperty(SkString& nameStr, SkDisplayable** displayablePtr ) {
+ const char* name = nameStr.c_str();
+ const char* dot = strchr(name, '.');
+ SkASSERT(dot);
+ SkDisplayable* displayable;
+ if (find(name, dot - name, &displayable) == false) {
+ SkASSERT(0);
+ return 0;
+ }
+ const char* fieldName = dot + 1;
+ const SkMemberInfo* memberInfo = displayable->getMember(fieldName);
+ *displayablePtr = displayable;
+ return (int) memberInfo->fOffset;
+}
+
+SkMSec SkAnimateMaker::getAppTime() const {
+ return fTimeline->getMSecs();
+}
+
+#ifdef SK_DEBUG
+SkAnimator* SkAnimateMaker::getRoot()
+{
+ SkAnimateMaker* maker = this;
+ while (maker->fParentMaker)
+ maker = maker->fParentMaker;
+ return maker == this ? NULL : maker->fAnimator;
+}
+#endif
+
+void SkAnimateMaker::helperAdd(SkDisplayable* trackMe) {
+ SkASSERT(fHelpers.find(trackMe) < 0);
+ *fHelpers.append() = trackMe;
+}
+
+void SkAnimateMaker::helperRemove(SkDisplayable* alreadyTracked) {
+ int helperIndex = fHelpers.find(alreadyTracked);
+ if (helperIndex >= 0)
+ fHelpers.remove(helperIndex);
+}
+
+#if 0
+void SkAnimateMaker::loadMovies() {
+ for (SkDisplayable** dispPtr = fMovies.begin(); dispPtr < fMovies.end(); dispPtr++) {
+ SkDisplayable* displayable = *dispPtr;
+ SkASSERT(displayable->getType() == SkType_Movie);
+ SkDisplayMovie* movie = (SkDisplayMovie*) displayable;
+ SkAnimateMaker* movieMaker = movie->fMovie.fMaker;
+ movieMaker->fEvents.doEvent(*movieMaker, SkDisplayEvent::kOnload, NULL);
+ movieMaker->fEvents.removeEvent(SkDisplayEvent::kOnload, NULL);
+ movieMaker->loadMovies();
+ }
+}
+#endif
+
+void SkAnimateMaker::notifyInval() {
+ if (fHostEventSinkID)
+ fAnimator->onEventPost(new SkEvent(SK_EventType_Inval), fHostEventSinkID);
+}
+
+void SkAnimateMaker::notifyInvalTime(SkMSec time) {
+ if (fHostEventSinkID)
+ fAnimator->onEventPostTime(new SkEvent(SK_EventType_Inval), fHostEventSinkID, time);
+}
+
+void SkAnimateMaker::postOnEnd(SkAnimateBase* animate, SkMSec end) {
+ SkEvent evt;
+ evt.setS32("time", animate->getStart() + end);
+ evt.setPtr("anim", animate);
+ evt.setType(SK_EventType_OnEnd);
+ SkEventSinkID sinkID = fAnimator->getSinkID();
+ fAnimator->onEventPost(new SkEvent(evt), sinkID);
+}
+
+void SkAnimateMaker::reset() {
+ deleteMembers();
+ fChildren.reset();
+ fHelpers.reset();
+ fIDs.reset();
+ fEvents.reset();
+ fDisplayList.hardReset();
+}
+
+void SkAnimateMaker::removeActive(SkActive* active) {
+ if (active == NULL)
+ return;
+ fDisplayList.remove(active);
+}
+
+bool SkAnimateMaker::resolveID(SkDisplayable* displayable, SkDisplayable* original) {
+ SkString newID;
+ bool success = computeID(original, NULL, &newID);
+ if (success)
+ setID(displayable, newID);
+ return success;
+}
+
+void SkAnimateMaker::setErrorString() {
+ fErrorString.reset();
+ if (fError.hasError()) {
+ SkString err;
+ if (fFileName.size() > 0)
+ fErrorString.set(fFileName.c_str());
+ else
+ fErrorString.set("screenplay error");
+ int line = fError.getLineNumber();
+ if (line >= 0) {
+ fErrorString.append(", ");
+ fErrorString.append("line ");
+ fErrorString.appendS32(line);
+ }
+ fErrorString.append(": ");
+ fError.getErrorString(&err);
+ fErrorString.append(err);
+#if defined SK_DEBUG
+ SkDebugf("%s\n", fErrorString.c_str());
+#endif
+ }
+}
+
+void SkAnimateMaker::setEnableTime(SkMSec appTime, SkMSec expectedTime) {
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkString debugOut;
+ SkMSec time = getAppTime();
+ debugOut.appendS32(time - fDebugTimeBase);
+ debugOut.append(" set enable old enable=");
+ debugOut.appendS32(fEnableTime - fDebugTimeBase);
+ debugOut.append(" old adjust=");
+ debugOut.appendS32(fAdjustedStart);
+ debugOut.append(" new enable=");
+ debugOut.appendS32(expectedTime - fDebugTimeBase);
+ debugOut.append(" new adjust=");
+ debugOut.appendS32(appTime - expectedTime);
+ SkDebugf("%s\n", debugOut.c_str());
+#endif
+ fAdjustedStart = appTime - expectedTime;
+ fEnableTime = expectedTime;
+ SkDisplayable** firstMovie = fMovies.begin();
+ SkDisplayable** endMovie = fMovies.end();
+ for (SkDisplayable** ptr = firstMovie; ptr < endMovie; ptr++) {
+ SkDisplayMovie* movie = (SkDisplayMovie*) *ptr;
+ movie->fMovie.fMaker->setEnableTime(appTime, expectedTime);
+ }
+}
+
+void SkAnimateMaker::setExtraPropertyCallBack(SkDisplayTypes type,
+ SkScriptEngine::_propertyCallBack callBack, void* userStorage) {
+ SkExtras** end = fExtras.end();
+ for (SkExtras** extraPtr = fExtras.begin(); extraPtr < end; extraPtr++) {
+ SkExtras* extra = *extraPtr;
+ if (extra->definesType(type)) {
+ extra->fExtraCallBack = callBack;
+ extra->fExtraStorage = userStorage;
+ break;
+ }
+ }
+}
+
+void SkAnimateMaker::setID(SkDisplayable* displayable, const SkString& newID) {
+ fIDs.set(newID.c_str(), displayable);
+#ifdef SK_DEBUG
+ displayable->_id.set(newID);
+ displayable->id = displayable->_id.c_str();
+#endif
+}
+
+void SkAnimateMaker::setScriptError(const SkScriptEngine& engine) {
+ SkString errorString;
+#ifdef SK_DEBUG
+ engine.getErrorString(&errorString);
+#endif
+ setErrorNoun(errorString);
+ setErrorCode(SkDisplayXMLParserError::kErrorInScript);
+}
+
+bool SkAnimateMaker::GetStep(const char* token, size_t len, void* stepPtr, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("step", token, len)) {
+ value->fOperand.fS32 = *(int32_t*) stepPtr;
+ value->fType = SkType_Int;
+ return true;
+ }
+ return false;
+}
diff --git a/animator/SkAnimateMaker.h b/animator/SkAnimateMaker.h
new file mode 100644
index 00000000..a5abff73
--- /dev/null
+++ b/animator/SkAnimateMaker.h
@@ -0,0 +1,160 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimateMaker_DEFINED
+#define SkAnimateMaker_DEFINED
+
+// #define SK_DEBUG_ANIMATION_TIMING
+
+#include "SkAnimator.h"
+#include "SkBitmap.h"
+#include "SkIntArray.h"
+#include "SkDisplayEvents.h"
+#include "SkDisplayList.h"
+#include "SkDisplayScreenplay.h"
+#include "SkDisplayXMLParser.h"
+#include "SkScript.h"
+#include "SkString.h"
+#include "SkTDict.h"
+
+// not sure where this little helper macro should go
+
+
+class SkActive;
+class SkAnimate;
+class SkCanvas;
+class SkDisplayable;
+class SkDrawable;
+class SkDump;
+class SkEvent;
+class SkEventSink;
+class SkExtras;
+class SkGroup;
+class SkPaint;
+class SkStream;
+
+class SkAnimateMaker {
+public:
+ SkAnimateMaker(SkAnimator* animator, SkCanvas* canvas, SkPaint* paint);
+ ~SkAnimateMaker();
+ void appendActive(SkActive* );
+ void childrenAdd(SkDisplayable* child) { *fChildren.append() = child; }
+ void clearExtraPropertyCallBack(SkDisplayTypes type);
+ bool computeID(SkDisplayable* displayable, SkDisplayable* parent, SkString* newID);
+ SkDisplayable* createInstance(const char name[], size_t len);
+ bool decodeStream(SkStream* stream);
+ bool decodeURI(const char uri[]);
+ void delayEnable(SkApply* apply, SkMSec time);
+ void doDelayedEvent();
+ bool doEvent(const SkEvent& event);
+#ifdef SK_DUMP_ENABLED
+ void dump(const char* match);
+#endif
+ int dynamicProperty(SkString& nameStr, SkDisplayable** );
+ bool find(const char* str, SkDisplayable** displayablePtr) const {
+ return fIDs.find(str, displayablePtr);
+ }
+ bool find(const char* str, size_t len, SkDisplayable** displayablePtr) const {
+ return fIDs.find(str, len, displayablePtr);
+ }
+ bool findKey(SkDisplayable* displayable, const char** string) const {
+ return fIDs.findKey(displayable, string);
+ }
+// bool find(SkString& string, SkDisplayable** displayablePtr) {
+// return fIDs.find(string.c_str(), displayablePtr);
+// }
+ SkAnimator* getAnimator() { return fAnimator; }
+ SkMSec getAppTime() const; // call caller to get current time
+#ifdef SK_DEBUG
+ SkAnimator* getRoot();
+#endif
+ SkXMLParserError::ErrorCode getErrorCode() const { return fError.getErrorCode(); }
+ SkMSec getInTime() { return fDisplayList.getTime(); }
+ int getNativeCode() const { return fError.getNativeCode(); }
+ bool hasError() { return fError.hasError(); }
+ void helperAdd(SkDisplayable* trackMe);
+ void helperRemove(SkDisplayable* alreadyTracked);
+ void idsSet(const char* attrValue, size_t len, SkDisplayable* displayable) {
+ fIDs.set(attrValue, len, displayable); }
+// void loadMovies();
+ void notifyInval();
+ void notifyInvalTime(SkMSec time);
+ void postOnEnd(SkAnimateBase* animate, SkMSec end);
+ void removeActive(SkActive* );
+ void reset();
+ bool resolveID(SkDisplayable* displayable, SkDisplayable* original);
+ void setEnableTime(SkMSec appTime, SkMSec expectedTime);
+ void setErrorCode(SkXMLParserError::ErrorCode err) { if (fError.hasError() == false) fError.INHERITED::setCode(err); }
+ void setErrorCode(SkDisplayXMLParserError::ErrorCode err) { if (fError.hasError() == false) fError.setCode(err); }
+ void setErrorNoun(const SkString& str) { if (fError.hasError() == false) fError.setNoun(str); }
+ void setErrorString();
+ void setExtraPropertyCallBack(SkDisplayTypes type, SkScriptEngine::_propertyCallBack , void* userStorage);
+ void setID(SkDisplayable* displayable, const SkString& newID);
+ void setInnerError(SkAnimateMaker* maker, const SkString& str) { fError.setInnerError(maker, str); }
+ void setScriptError(const SkScriptEngine& );
+#ifdef SK_DEBUG
+ void validate() { fDisplayList.validate(); }
+#else
+ void validate() {}
+#endif
+ SkDisplayEvent* fActiveEvent;
+ SkMSec fAdjustedStart;
+ SkCanvas* fCanvas;
+ SkMSec fEnableTime;
+ int fEndDepth; // passed parameter to onEndElement
+ SkEvents fEvents;
+ SkDisplayList fDisplayList;
+ SkEventSinkID fHostEventSinkID;
+ SkMSec fMinimumInterval;
+ SkPaint* fPaint;
+ SkAnimateMaker* fParentMaker;
+ SkString fPrefix;
+ SkDisplayScreenplay fScreenplay;
+ const SkAnimator::Timeline* fTimeline;
+ SkBool8 fInInclude;
+ SkBool8 fInMovie;
+ SkBool8 fFirstScriptError;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkMSec fDebugTimeBase;
+#endif
+#ifdef SK_DUMP_ENABLED
+ SkString fDumpAnimated;
+ SkBool8 fDumpEvents;
+ SkBool8 fDumpGConditions;
+ SkBool8 fDumpPosts;
+#endif
+private:
+ void deleteMembers();
+ static bool GetStep(const char* token, size_t len, void* stepPtr, SkScriptValue* );
+ SkAnimateMaker& operator=(SkAnimateMaker& );
+ SkTDDisplayableArray fChildren;
+ SkTDDisplayableArray fDelayed; // SkApply that contain delayed enable events
+ SkDisplayXMLParserError fError;
+ SkString fErrorString;
+ SkTDArray<SkExtras*> fExtras;
+ SkString fFileName;
+ SkTDDisplayableArray fHelpers; // helper displayables
+ SkBool8 fLoaded;
+ SkTDDisplayableArray fMovies;
+ SkTDict<SkDisplayable*> fIDs;
+ SkAnimator* fAnimator;
+ friend class SkAdd;
+ friend class SkAnimateBase;
+ friend class SkDisplayXMLParser;
+ friend class SkAnimator;
+ friend class SkAnimatorScript;
+ friend class SkApply;
+ friend class SkDisplayMovie;
+ friend class SkDisplayType;
+ friend class SkEvents;
+ friend class SkGroup;
+ friend struct SkMemberInfo;
+};
+
+#endif // SkAnimateMaker_DEFINED
diff --git a/animator/SkAnimateProperties.h b/animator/SkAnimateProperties.h
new file mode 100644
index 00000000..b0706405
--- /dev/null
+++ b/animator/SkAnimateProperties.h
@@ -0,0 +1,21 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimateProperties_DEFINED
+#define SkAnimateProperties_DEFINED
+
+enum SkAnimateBase_Properties {
+ SK_PROPERTY(dynamic),
+ SK_PROPERTY(mirror),
+ SK_PROPERTY(reset),
+ SK_PROPERTY(step),
+ SK_PROPERTY(values)
+};
+
+#endif // SkAnimateProperties_DEFINED
diff --git a/animator/SkAnimateSchema.xsd b/animator/SkAnimateSchema.xsd
new file mode 100644
index 00000000..f7af332c
--- /dev/null
+++ b/animator/SkAnimateSchema.xsd
@@ -0,0 +1,2787 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+xmlns:Sk="urn:screenplay" targetNamespace="urn:screenplay">
+
+ <!-- /** Animate
+ An ID of an element of type <animate> or <set>
+ */ -->
+ <xs:simpleType name="Animate">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** 3D_Point
+ An array of three floats in ECMAScript notation: [x, y, z].
+ */ -->
+ <xs:simpleType name="3D_Point">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)( *, *[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)){2}" />
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** ARGB
+ The red, green, blue, and optional alpha color components.
+ */ -->
+ <xs:simpleType name="ARGB">
+ <xs:restriction base="xs:string">
+ <!-- @pattern #[0-9a-fA-F]{3} #rgb contains three hexadecimal digits. #rgb is equivalent to 0xFFrrggbb. -->
+ <xs:pattern value="#[0-9a-fA-F]{3}"/>
+ <!-- @pattern #[0-9a-fA-F]{4} #argb contains four hexadecimal digits. #argb is equivalent to 0xaarrggbb. -->
+ <xs:pattern value="#[0-9a-fA-F]{4}"/>
+ <!-- @pattern #[0-9a-fA-F]{6} #rrggbb contains six hexadecimal digits. #rrggbb is equivalent to 0xFFrrggbb. -->
+ <xs:pattern value="#[0-9a-fA-F]{6}"/>
+ <!-- @pattern #[0-9a-fA-F]{8} #aarrggbb contains eight hexadecimal digits. #aarrggbb is equivalent to 0xaarrggbb. -->
+ <xs:pattern value="#[0-9a-fA-F]{8}"/>
+ <!-- @pattern 0[xX][0-9a-fA-F]{8} 0xaarrggbb describes the color as a packed hexadecimal; each pair of digits
+ corresponds to alpha, red, green, and blue respectively. -->
+ <xs:pattern value="0[xX][0-9a-fA-F]{8}"/>
+ <!-- @pattern rgb\(\d+{1,3},\d+{1,3},\d+{1,3}\) rgb(r, g, b) describes color with three integers ranging from 0 to 255,
+ corresponding to red, green, and blue respectively. -->
+ <xs:pattern value="rgb\(\d+{1,3},\d+{1,3},\d+{1,3}\)"/>
+ <!-- @patternList Color can be described by the following standard CSS color names. -->
+ <xs:pattern value="aliceblue"/>
+ <xs:pattern value="antiquewhite"/>
+ <xs:pattern value="aqua"/>
+ <xs:pattern value="aquamarine"/>
+ <xs:pattern value="azure"/>
+ <xs:pattern value="beige"/>
+ <xs:pattern value="bisque"/>
+ <xs:pattern value="black"/>
+ <xs:pattern value="blanchedalmond"/>
+ <xs:pattern value="blue"/>
+ <xs:pattern value="blueviolet"/>
+ <xs:pattern value="brown"/>
+ <xs:pattern value="burlywood"/>
+ <xs:pattern value="cadetblue"/>
+ <xs:pattern value="chartreuse"/>
+ <xs:pattern value="chocolate"/>
+ <xs:pattern value="coral"/>
+ <xs:pattern value="cornflowerblue"/>
+ <xs:pattern value="cornsilk"/>
+ <xs:pattern value="crimson"/>
+ <xs:pattern value="cyan"/>
+ <xs:pattern value="darkblue"/>
+ <xs:pattern value="darkcyan"/>
+ <xs:pattern value="darkgoldenrod"/>
+ <xs:pattern value="darkgray"/>
+ <xs:pattern value="darkgreen"/>
+ <xs:pattern value="darkkhaki"/>
+ <xs:pattern value="darkmagenta"/>
+ <xs:pattern value="darkolivegreen"/>
+ <xs:pattern value="darkorange"/>
+ <xs:pattern value="darkorchid"/>
+ <xs:pattern value="darkred"/>
+ <xs:pattern value="darksalmon"/>
+ <xs:pattern value="darkseagreen"/>
+ <xs:pattern value="darkslateblue"/>
+ <xs:pattern value="darkslategray"/>
+ <xs:pattern value="darkturquoise"/>
+ <xs:pattern value="darkviolet"/>
+ <xs:pattern value="deeppink"/>
+ <xs:pattern value="deepskyblue"/>
+ <xs:pattern value="dimgray"/>
+ <xs:pattern value="dodgerblue"/>
+ <xs:pattern value="firebrick"/>
+ <xs:pattern value="floralwhite"/>
+ <xs:pattern value="forestgreen"/>
+ <xs:pattern value="fuchsia"/>
+ <xs:pattern value="gainsboro"/>
+ <xs:pattern value="ghostwhite"/>
+ <xs:pattern value="gold"/>
+ <xs:pattern value="goldenrod"/>
+ <xs:pattern value="gray"/>
+ <xs:pattern value="green"/>
+ <xs:pattern value="greenyellow"/>
+ <xs:pattern value="honeydew"/>
+ <xs:pattern value="hotpink"/>
+ <xs:pattern value="indianred"/>
+ <xs:pattern value="indigo"/>
+ <xs:pattern value="ivory"/>
+ <xs:pattern value="khaki"/>
+ <xs:pattern value="lavender"/>
+ <xs:pattern value="lavenderblush"/>
+ <xs:pattern value="lawngreen"/>
+ <xs:pattern value="lemonchiffon"/>
+ <xs:pattern value="lightblue"/>
+ <xs:pattern value="lightcoral"/>
+ <xs:pattern value="lightcyan"/>
+ <xs:pattern value="lightgoldenrodyellow"/>
+ <xs:pattern value="lightgreen"/>
+ <xs:pattern value="lightgrey"/>
+ <xs:pattern value="lightpink"/>
+ <xs:pattern value="lightsalmon"/>
+ <xs:pattern value="lightseagreen"/>
+ <xs:pattern value="lightskyblue"/>
+ <xs:pattern value="lightslategray"/>
+ <xs:pattern value="lightsteelblue"/>
+ <xs:pattern value="lightyellow"/>
+ <xs:pattern value="lime"/>
+ <xs:pattern value="limegreen"/>
+ <xs:pattern value="linen"/>
+ <xs:pattern value="magenta"/>
+ <xs:pattern value="maroon"/>
+ <xs:pattern value="mediumaquamarine"/>
+ <xs:pattern value="mediumblue"/>
+ <xs:pattern value="mediumorchid"/>
+ <xs:pattern value="mediumpurple"/>
+ <xs:pattern value="mediumseagreen"/>
+ <xs:pattern value="mediumslateblue"/>
+ <xs:pattern value="mediumspringgreen"/>
+ <xs:pattern value="mediumturquoise"/>
+ <xs:pattern value="mediumvioletred"/>
+ <xs:pattern value="midnightblue"/>
+ <xs:pattern value="mintcream"/>
+ <xs:pattern value="mistyrose"/>
+ <xs:pattern value="moccasin"/>
+ <xs:pattern value="navajowhite"/>
+ <xs:pattern value="navy"/>
+ <xs:pattern value="oldlace"/>
+ <xs:pattern value="olive"/>
+ <xs:pattern value="olivedrab"/>
+ <xs:pattern value="orange"/>
+ <xs:pattern value="orangered"/>
+ <xs:pattern value="orchid"/>
+ <xs:pattern value="palegoldenrod"/>
+ <xs:pattern value="palegreen"/>
+ <xs:pattern value="paleturquoise"/>
+ <xs:pattern value="palevioletred"/>
+ <xs:pattern value="papayawhip"/>
+ <xs:pattern value="peachpuff"/>
+ <xs:pattern value="peru"/>
+ <xs:pattern value="pink"/>
+ <xs:pattern value="plum"/>
+ <xs:pattern value="powderblue"/>
+ <xs:pattern value="purple"/>
+ <xs:pattern value="red"/>
+ <xs:pattern value="rosybrown"/>
+ <xs:pattern value="royalblue"/>
+ <xs:pattern value="saddlebrown"/>
+ <xs:pattern value="salmon"/>
+ <xs:pattern value="sandybrown"/>
+ <xs:pattern value="seagreen"/>
+ <xs:pattern value="seashell"/>
+ <xs:pattern value="sienna"/>
+ <xs:pattern value="silver"/>
+ <xs:pattern value="skyblue"/>
+ <xs:pattern value="slateblue"/>
+ <xs:pattern value="slategray"/>
+ <xs:pattern value="snow"/>
+ <xs:pattern value="springgreen"/>
+ <xs:pattern value="steelblue"/>
+ <xs:pattern value="tan"/>
+ <xs:pattern value="teal"/>
+ <xs:pattern value="thistle"/>
+ <xs:pattern value="tomato"/>
+ <xs:pattern value="turquoise"/>
+ <xs:pattern value="violet"/>
+ <xs:pattern value="wheat"/>
+ <xs:pattern value="white"/>
+ <xs:pattern value="whitesmoke"/>
+ <xs:pattern value="yellow"/>
+ <!--@patternListLast -->
+ <xs:pattern value="yellowgreen"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** AddMode
+ AddMode controls how the add element adds its referenced element to the
+ display list. By default, the referenced element remains in the add element
+ so that the add element's use attribute may be animated to change the
+ element it refers to. Setting the mode attribute to "immediate" causes the
+ add element to put the referenced element in the display list directly.
+ The move and replace elements are not affected by the mode attribute;
+ they always move or replace the referenced element directly.
+ */ -->
+ <xs:simpleType name="AddMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern immediate Puts the referenced element in the display list. -->
+ <xs:pattern value="immediate"/>
+ <!-- @pattern indirect Puts the containing element in the display list. -->
+ <xs:pattern value="indirect"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Align
+ Align places text to the left, center, or right of the text position.
+ */ -->
+ <xs:simpleType name="Align">
+ <xs:restriction base="xs:string">
+ <!-- @pattern left The first character in the text string is drawn at the text position. -->
+ <xs:pattern value="left"/>
+ <!-- @pattern center The text string is measured and centered on the text position. -->
+ <xs:pattern value="center"/>
+ <!-- @pattern right The last character in the text string is drawn to the left of the text position. -->
+ <xs:pattern value="right"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** ApplyMode
+ ApplyMode affects how the apply element animates values.
+ */ -->
+ <xs:simpleType name="ApplyMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern immediate Iterates through all animation values immediately. -->
+ <xs:pattern value="immediate"/>
+ <!-- @pattern once Performs the animation at once without adding the scope to
+ the display list. -->
+ <xs:pattern value="once"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** ApplyTransition
+ ApplyTransition affects how the apply element sets the time of the animators.
+ */ -->
+ <xs:simpleType name="ApplyTransition">
+ <xs:restriction base="xs:string">
+ <!-- @pattern reverse Performs the animation in reverse. -->
+ <xs:pattern value="reverse"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Base64
+ Base64 describes 8 bit binary using 64 character values.
+ See http://rfc.net/rfc2045.html for the base64 format.
+ */ -->
+ <xs:simpleType name="Base64">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="[A-Za-z0-9+/ ]+"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** BaseBitmap
+ A reference to an image like a JPEG, GIF, or PNG; or a reference to a bitmap element
+ that has been drawn into with a drawTo element.
+ */ -->
+ <xs:simpleType name="BaseBitmap">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** BitmapEncoding
+ Used to specify the compression format for writing an image file with the snapshot element.
+ */ -->
+ <xs:simpleType name="BitmapEncoding">
+ <xs:restriction base="xs:string">
+ <!-- @pattern jpeg See http://www.jpeg.org/jpeg/ for more information about JPEG. -->
+ <xs:pattern value="jpeg"/>
+ <!-- @pattern png See http://www.libpng.org/pub/png/ for more information about PNG. -->
+ <xs:pattern value="png"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** BitmapFormat
+ Determines the number of bits per pixel in a bitmap.
+ */ -->
+ <xs:simpleType name="BitmapFormat">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="none"/>
+ <!-- @pattern A1 1-bit per pixel, (0 is transparent, 1 is opaque). -->
+ <xs:pattern value="A1"/>
+ <!-- @pattern A8 8-bits per pixel, with only alpha specified (0 is transparent, 0xFF is opaque). -->
+ <xs:pattern value="A8"/>
+ <!-- @pattern Index8 8-bits per pixel, using a ColorTable element to specify the colors. -->
+ <xs:pattern value="Index8"/>
+ <!-- @pattern RGB16 16-bits per pixel, compile-time configured to be either 555 or 565. -->
+ <xs:pattern value="RGB16"/>
+ <!-- @pattern RGB32 32-bits per pixel, plus alpha. -->
+ <xs:pattern value="RGB32"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Boolean
+ Either "true" (non-zero) or "false" (zero).
+ */ -->
+ <xs:simpleType name="Boolean">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="false"/>
+ <xs:pattern value="true"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Cap
+ The values for the strokeCap attribute.
+ */ -->
+ <xs:simpleType name="Cap">
+ <xs:restriction base="xs:string">
+ <!-- @pattern butt begin and end a contour with no extension -->
+ <xs:pattern value="butt"/>
+ <!-- @pattern round begin and end a contour with a semi-circle extension -->
+ <xs:pattern value="round"/>
+ <!-- @pattern square begin and end a contour with a half square extension -->
+ <xs:pattern value="square"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Color
+ A reference to a color element.
+ */ -->
+ <xs:simpleType name="Color">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Displayable
+ A reference to any element: @list(Displayable)
+ */ -->
+ <xs:simpleType name="Displayable">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** DisplayableArray
+ An array of one or more element IDs.
+ */ -->
+ <xs:simpleType name="DisplayableArray">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Drawable
+ A reference to an element that can be drawn: @list(Drawable)
+ */ -->
+ <xs:simpleType name="Drawable">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** DynamicString
+ Dynamic strings contain scripts that are re-evaluated each time the script is enabled.
+ */ -->
+ <xs:simpleType name="DynamicString">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** EventCode
+ Key codes that can trigger events, usually corresponding to physical buttons on the device.
+ */ -->
+ <xs:simpleType name="EventCode">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="none"/>
+ <!-- @pattern up The up arrow. -->
+ <xs:pattern value="up"/>
+ <!-- @pattern down The down arrow. -->
+ <xs:pattern value="down"/>
+ <!-- @pattern left The left arrow. -->
+ <xs:pattern value="left"/>
+ <!-- @pattern right The right arrow. -->
+ <xs:pattern value="right"/>
+ <!-- @pattern back The back button (may not be present; the Backspace key on a PC). -->
+ <xs:pattern value="back"/>
+ <!-- @pattern end The end button (may not be present; the Esc key on a PC). -->
+ <xs:pattern value="end"/>
+ <!-- @pattern OK The OK button (the Enter key on a PC). -->
+ <xs:pattern value="OK"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** EventKind
+ Specifies how an event is triggered; by a key, when an animation ends, when the
+ document is loaded, or when this event is triggered by the user's C++ or XML.
+ */ -->
+ <xs:simpleType name="EventKind">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="none"/>
+ <!-- @pattern keyChar A key corresponding to a Unichar value. -->
+ <xs:pattern value="keyChar"/>
+ <!-- @pattern keyPress A key with a particular function, such as an arrow key or the OK button. -->
+ <xs:pattern value="keyPress"/>
+ <!-- @pattern mouseDown Triggered when the primary mouse button is pressed. -->
+ <xs:pattern value="mouseDown"/>
+ <!-- @pattern mouseDrag Triggered when the primary mouse is moved while the button is pressed. -->
+ <xs:pattern value="mouseDrag"/>
+ <!-- @pattern mouseMove Triggered when the primary mouse is moved. -->
+ <xs:pattern value="mouseMove"/>
+ <!-- @pattern mouseUp Triggered when the primary mouse button is released. -->
+ <xs:pattern value="mouseUp"/>
+ <!-- @pattern onEnd Triggered when an event ends. -->
+ <xs:pattern value="onEnd"/>
+ <!-- @pattern onLoad Triggered when the document loads. -->
+ <xs:pattern value="onLoad"/>
+ <!-- @pattern user Triggered when a post element or C++ event is activated. -->
+ <xs:pattern value="user"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** EventMode
+ Specifies whether the event is delivered immediately to matching event element or deferred to
+ the application-wide event handler.
+ */ -->
+ <xs:simpleType name="EventMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern deferred Process the event using the host's event queue. -->
+ <xs:pattern value="deferred"/>
+ <!-- @pattern immediate Activate the event element immediately. -->
+ <xs:pattern value="immediate"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** FillType
+ Filled paths that self-intersect use the winding or evenOdd rule to determine whether the
+ overlaps are filled or are holes.
+ */ -->
+ <xs:simpleType name="FillType">
+ <xs:restriction base="xs:string">
+ <!-- @pattern winding Fill if the sum of edge directions is non-zero. -->
+ <xs:pattern value="winding"/>
+ <!-- @pattern evenOdd Fill if the sum of edges is an odd number. -->
+ <xs:pattern value="evenOdd"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** FilterType
+ Scaled bitmaps without a filter type set point-sample the source bitmap to determine the
+ destination pixels' colors. Bilinear and bicubic compute the values of intermediate pixels
+ by sampling the pixels around them.
+ */ -->
+ <xs:simpleType name="FilterType">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="none"/>
+ <!-- @pattern bilinear Compute the pixel value as the linear interpolation of adjacent pixels. -->
+ <xs:pattern value="bilinear"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Float
+ A signed fractional value.
+ */ -->
+ <xs:simpleType name="Float">
+ <xs:restriction base="xs:float">
+ <xs:pattern value="[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** FloatArray
+ An array of one or more signed fractional values.
+ */ -->
+ <xs:simpleType name="FloatArray">
+ <xs:restriction base="xs:float">
+ <xs:pattern value="\[[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)( *, *[+-]?([0-9]*\.[0-9]+|[0-9]+\.?))*\]"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** FromPathMode
+ A matrix computed from an offset along a path may include the point's position, the angle
+ tangent, or both.
+ .
+ */ -->
+ <xs:simpleType name="FromPathMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern normal Compute the matrix using the path's angle and position. -->
+ <xs:pattern value="normal"/>
+ <!-- @pattern angle Compute the matrix using only the path's angle. -->
+ <xs:pattern value="angle"/>
+ <!-- @pattern position Compute the matrix using only the path's position. -->
+ <xs:pattern value="position"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Int
+ A signed integer.
+ */ -->
+ <xs:simpleType name="Int">
+ <xs:restriction base="xs:integer"/>
+ </xs:simpleType>
+
+ <!-- /** IntArray
+ An array of one or more signed integer values.
+ */ -->
+ <xs:simpleType name="IntArray">
+ <xs:restriction base="xs:integer">
+ <xs:pattern value="\[[+-]?[0-9]+( *, *[+-]?[0-9]+)*\]"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Join
+ The edges of thick lines in a path are joined by extending the outer edges to form a miter,
+ or by adding a round circle at the intersection point, or by connecting the outer edges with a line
+ to form a blunt joint.
+ */ -->
+ <xs:simpleType name="Join">
+ <xs:restriction base="xs:string">
+ <!-- @pattern miter Extend the outer edges to form a miter. -->
+ <xs:pattern value="miter"/>
+ <!-- @pattern round Join the outer edges with a circular arc. -->
+ <xs:pattern value="round"/>
+ <!-- @pattern blunt Connect the outer edges with a line. -->
+ <xs:pattern value="blunt"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** MaskFilterBlurStyle
+ A blur can affect the inside or outside part of the shape, or it can affect both. The shape
+ itself can be drawn solid, or can be invisible.
+ */ -->
+ <xs:simpleType name="MaskFilterBlurStyle">
+ <xs:restriction base="xs:string">
+ <!-- @pattern normal Blur inside and outside. -->
+ <xs:pattern value="normal"/>
+ <!-- @pattern solid Solid inside, blur outside. -->
+ <xs:pattern value="solid"/>
+ <!-- @pattern outer Invisible inside, blur outside. -->
+ <xs:pattern value="outer"/>
+ <!-- @pattern inner Blur inside only.. -->
+ <xs:pattern value="inner"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** MaskFilter
+ The ID of a blur or emboss element.
+ */ -->
+ <xs:simpleType name="MaskFilter">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Matrix
+ The ID of a matrix element.
+ */ -->
+ <xs:simpleType name="Matrix">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** MSec
+ A fractional second with millisecond resolution.
+ */ -->
+ <xs:simpleType name="MSec">
+ <xs:restriction base="xs:float"/>
+ </xs:simpleType>
+
+ <!-- /** Paint
+ The ID of a paint element.
+ */ -->
+ <xs:simpleType name="Paint">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Path
+ The ID of a path element.
+ */ -->
+ <xs:simpleType name="Path">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** PathDirection
+ PathDirection determines if the path is traveled clockwise or counterclockwise.
+ */ -->
+ <xs:simpleType name="PathDirection">
+ <xs:restriction base="xs:string">
+ <!-- @pattern cw The path is traveled clockwise. -->
+ <xs:pattern value="cw"/>
+ <!-- @pattern ccw The path is traveled counterclockwise. -->
+ <xs:pattern value="ccw"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** PathEffect
+ The ID of a dash or discrete element.
+ */ -->
+ <xs:simpleType name="PathEffect">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Point
+ A pair of signed values representing the x and y coordinates of a point.
+ */ -->
+ <xs:simpleType name="Point">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="\[ *[+-]?([0-9]*\.[0-9]+|[0-9]+\.?) *[ ,] *[+-]?([0-9]*\.[0-9]+|[0-9]+\.?)\]"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Rect
+ The ID of a rectangle element.
+ */ -->
+ <xs:simpleType name="Rect">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Shader
+ The ID of a linear or radial gradient.
+ */ -->
+ <xs:simpleType name="Shader">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** String
+ A sequence of characters.
+ */ -->
+ <xs:simpleType name="String">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Style
+ Geometry can be filled, stroked or both.
+ */ -->
+ <xs:simpleType name="Style">
+ <xs:restriction base="xs:string">
+ <!-- @pattern fill The interior of the geometry is filled with the paint's color. -->
+ <xs:pattern value="fill"/>
+ <!-- @pattern stroke The outline of the geometry is stroked with the paint's color. -->
+ <xs:pattern value="stroke"/>
+ <!-- @pattern strokeAndFill The interior is filled and outline is stroked with the paint's color. -->
+ <xs:pattern value="strokeAndFill"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Text
+ The ID of a text element.
+ */ -->
+ <xs:simpleType name="Text">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** TextBoxAlign
+ Multiple lines of text may be aligned to the start of the box, the center, or the end.
+ */ -->
+ <xs:simpleType name="TextBoxAlign">
+ <xs:restriction base="xs:string">
+ <!-- @pattern start The text begins within the upper left of the box. -->
+ <xs:pattern value="start"/>
+ <!-- @pattern center The text is positioned in the center of the box. -->
+ <xs:pattern value="center"/>
+ <!-- @pattern end The text ends within the lower right of the box. -->
+ <xs:pattern value="end"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** TextBoxMode
+ Fitting the text may optionally introduce line breaks.
+ */ -->
+ <xs:simpleType name="TextBoxMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern oneLine No additional linebreaks are added. -->
+ <xs:pattern value="oneLine"/>
+ <!-- @pattern lineBreak Line breaks may be added to fit the text to the box. -->
+ <xs:pattern value="lineBreak"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** TileMode
+ A shader describes how to draw within a rectangle.
+ Outside of the rectangle, the shader may be ignored, clamped on the edges, or repeated.
+ The repetitions may be mirrored from the original shader.
+ */ -->
+ <xs:simpleType name="TileMode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern clamp The edge shader color is extended. -->
+ <xs:pattern value="clamp"/>
+ <!-- @pattern repeat The shader is repeated horizontally and vertically. -->
+ <xs:pattern value="repeat"/>
+ <!-- @pattern mirror The shader is mirrored horizontally and vertically. -->
+ <xs:pattern value="mirror"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Typeface
+ The ID of a typeface element.
+ */ -->
+ <xs:simpleType name="Typeface">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** UnknownArray
+ An array of values of any type.
+ */ -->
+ <xs:simpleType name="UnknownArray">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <!-- /** Xfermode
+ The operation applied when drawing a color to the destination background.
+ */ -->
+ <xs:simpleType name="Xfermode">
+ <xs:restriction base="xs:string">
+ <!-- @pattern clear Set the destination alpha to zero and the destination color to black. -->
+ <xs:pattern value="clear"/>
+ <!-- @pattern src Set the destination to the source alpha and color. -->
+ <xs:pattern value="src"/>
+ <!-- @pattern dst Set the destination to the destination alpha and color. -->
+ <xs:pattern value="dst"/>
+ <!-- @pattern srcOver The default. Set the destination to the source color blended
+ with the destination by the source alpha. -->
+ <xs:pattern value="srcOver"/>
+ <!-- @pattern dstOver Set the destination to the destination color blended
+ with the source by the destination alpha. -->
+ <xs:pattern value="dstOver"/>
+ <!-- @pattern srcIn Set the destination to the source color scaled by the destination
+ alpha. -->
+ <xs:pattern value="srcIn"/>
+ <!-- @pattern dstIn Set the destination to the destination color scaled by the source
+ alpha. -->
+ <xs:pattern value="dstIn"/>
+ <!-- @pattern srcOut Set the destination to the source color scaled by the
+ inverse of the destination alpha. -->
+ <xs:pattern value="srcOut"/>
+ <!-- @pattern dstOut Set the destination to the destination color scaled by the
+ inverse of the source alpha. -->
+ <xs:pattern value="dstOut"/>
+ <!-- @pattern srcATop Set the destination to the source color times the destination alpha,
+ blended with the destination times the inverse of the source alpha. -->
+ <xs:pattern value="srcATop"/>
+ <!-- @pattern dstATop Set the destination to the destination color times the source alpha,
+ blended with the source times the inverse of the destination alpha. -->
+ <xs:pattern value="dstATop"/>
+ <!-- @pattern xor Set the destination to the destination color times the
+ inverse of the source alpha,
+ blended with the source times the inverse of the destination alpha. -->
+ <xs:pattern value="xor"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- /** Math
+ Math provides functions and properties in the ECMAScript library to screenplay script expressions.
+ The Math element is always implicitly added at the top of every screenplay description, so
+ its functions and properties are always available.
+ */ -->
+ <xs:element name="Math">
+ <xs:complexType>
+ <!-- @attribute E The value 2.718281828. -->
+ <xs:attribute name="E" type="Sk:Float"/>
+ <!-- @attribute LN10 The value 2.302585093. -->
+ <xs:attribute name="LN10" type="Sk:Float"/>
+ <!-- @attribute LN2 The value 0.693147181. -->
+ <xs:attribute name="LN2" type="Sk:Float"/>
+ <!-- @attribute LOG10E The value 0.434294482. -->
+ <xs:attribute name="LOG10E" type="Sk:Float"/>
+ <!-- @attribute LOG2E The value 1.442695041. -->
+ <xs:attribute name="LOG2E" type="Sk:Float"/>
+ <!-- @attribute PI The value 3.141592654. -->
+ <xs:attribute name="PI" type="Sk:Float"/>
+ <!-- @attribute SQRT1_2 The value 0.707106781. -->
+ <xs:attribute name="SQRT1_2" type="Sk:Float"/>
+ <!-- @attribute SQRT2 The value 1.414213562. -->
+ <xs:attribute name="SQRT2" type="Sk:Float"/>
+ <!-- @attribute abs A function that returns the absolute value of its argument. -->
+ <xs:attribute name="abs" type="Sk:Float"/>
+ <!-- @attribute acos A function that returns the arc cosine of its argument. -->
+ <xs:attribute name="acos" type="Sk:Float"/>
+ <!-- @attribute asin A function that returns the arc sine of its argument. -->
+ <xs:attribute name="asin" type="Sk:Float"/>
+ <!-- @attribute atan A function that returns the arc tan of its argument. -->
+ <xs:attribute name="atan" type="Sk:Float"/>
+ <!-- @attribute atan2 A function that returns the arc tan of the ratio of its two arguments. -->
+ <xs:attribute name="atan2" type="Sk:Float"/>
+ <!-- @attribute ceil A function that returns the rounded up value of its argument. -->
+ <xs:attribute name="ceil" type="Sk:Float"/>
+ <!-- @attribute cos A function that returns the cosine of its argument. -->
+ <xs:attribute name="cos" type="Sk:Float"/>
+ <!-- @attribute exp A function that returns E raised to a power (the argument). -->
+ <xs:attribute name="exp" type="Sk:Float"/>
+ <!-- @attribute floor A function that returns the rounded down value of its argument. -->
+ <xs:attribute name="floor" type="Sk:Float"/>
+ <!-- @attribute log A function that returns the natural logarithm its argument. -->
+ <xs:attribute name="log" type="Sk:Float"/>
+ <!-- @attribute max A function that returns the largest of any number of arguments. -->
+ <xs:attribute name="max" type="Sk:Float"/>
+ <!-- @attribute min A function that returns the smallest of any number of arguments. -->
+ <xs:attribute name="min" type="Sk:Float"/>
+ <!-- @attribute pow A function that returns the first argument raised to the power of the second argument. -->
+ <xs:attribute name="pow" type="Sk:Float"/>
+ <!-- @attribute random A function that returns a random value from zero to one.
+ (See also the &lt;random&gt; element.) -->
+ <xs:attribute name="random" type="Sk:Float"/>
+ <!-- @attribute round A function that returns the rounded value of its argument. -->
+ <xs:attribute name="round" type="Sk:Float"/>
+ <!-- @attribute sin A function that returns the sine of its argument. -->
+ <xs:attribute name="sin" type="Sk:Float"/>
+ <!-- @attribute sqrt A function that returns the square root of its argument. -->
+ <xs:attribute name="sqrt" type="Sk:Float"/>
+ <!-- @attribute tan A function that returns the tangent of its argument. -->
+ <xs:attribute name="tan" type="Sk:Float"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** Number
+ Number provides properties in the ECMAScript library to screenplay script expressions.
+ The Number element is always implicitly added at the top of every screenplay description, so
+ its properties are always available.
+ */ -->
+ <xs:element name="Number">
+ <xs:complexType>
+ <!-- @attribute MAX_VALUE The maximum number value; approximately 32767.999985 fixed point,
+ 3.4028235e+38 floating point. -->
+ <xs:attribute name="MAX_VALUE" type="Sk:Float"/>
+ <!-- @attribute MIN_VALUE The minimum number value; approximately 0.000015 fixed point,
+ 1.1754944e-38 floating point. -->
+ <xs:attribute name="MIN_VALUE" type="Sk:Float"/>
+ <!-- @attribute NEGATIVE_INFINITY The most negative number value. Fixed point does not
+ have a value for negative infinity, and approximates it with -32767.999985. -->
+ <xs:attribute name="NEGATIVE_INFINITY" type="Sk:Float"/>
+ <!-- @attribute NaN A bit pattern representing "Not a Number". Fixed point does not
+ have a value for NaN, and approximates it with -32768. -->
+ <xs:attribute name="NaN" type="Sk:Float"/>
+ <!-- @attribute POSITIVE_INFINITY The greatest positive number value. Fixed point does not
+ have a value for positive infinity, and approximates it with 32767.999985. -->
+ <xs:attribute name="POSITIVE_INFINITY" type="Sk:Float"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** add
+ Add references a drawable element, and adds it to the display list.
+ If where and offset are omitted, the element is appended to the end of the display list.
+ If where is specified, the element is inserted at the first occurance of where in the display list.
+ If offset and where are specified, the element is inserted at where plus offset.
+ A positive offset without where inserts the element at the start of the list plus offset.
+ A negative offset without where inserts the element at the end of the list minus offset.
+ */ -->
+ <xs:element name="add">
+ <xs:complexType>
+ <!-- @attribute mode If indirect (the default), keep the add element in the display list,
+ and draw the add's use element. If immediate, put the add's use element in the display list. -->
+ <xs:attribute name="mode" type="Sk:AddMode"/>
+ <!-- @attribute offset The offset added to the insert index. -->
+ <xs:attribute name="offset" type="Sk:Int"/>
+ <!-- @attribute use The drawable element to add to the display list. -->
+ <xs:attribute name="use" type="Sk:Drawable"/>
+ <!-- @attribute where The drawable element marking where to insert. -->
+ <xs:attribute name="where" type="Sk:Drawable"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** addCircle
+ AddCircle adds a closed circle to the parent path element.
+ */ -->
+ <xs:element name="addCircle">
+ <xs:complexType>
+ <!-- @attribute direction One of @pattern. @patternDescription -->
+ <xs:attribute name="direction" type="Sk:PathDirection"/>
+ <!-- @attribute radius The distance from the center to the edge of the circle. -->
+ <xs:attribute name="radius" type="Sk:Float"/>
+ <!-- @attribute x The x coordinate of the circle's center. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The y coordinate of the circle's center.-->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** addOval
+ AddOval adds a closed oval described by its bounding box to the parent path element.
+ */ -->
+ <xs:element name="addOval">
+ <xs:complexType>
+ <!-- @attribute direction One of @pattern. @patternDescription -->
+ <xs:attribute name="direction" type="Sk:PathDirection"/>
+ <!-- @attribute bottom The bottom edge of the oval's bounding box. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the oval's bounding box. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute right The right edge of the oval's bounding box. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the oval's bounding box. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** addPath
+ AddPath adds a path to the parent path element.
+ An optional matrix may transform the path as it is added.
+ */ -->
+ <xs:element name="addPath">
+ <xs:complexType>
+ <!-- @attribute matrix The matrix applied to the path as it is added. -->
+ <xs:attribute name="matrix" type="Sk:Matrix"/>
+ <!-- @attribute path The path to add. -->
+ <xs:attribute name="path" type="Sk:Path"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** addRect
+ AddRect adds a closed rectangle to the parent path element.
+ */ -->
+ <xs:element name="addRect">
+ <xs:complexType>
+ <!-- @attribute direction One of @pattern. @patternDescription -->
+ <xs:attribute name="direction" type="Sk:PathDirection"/>
+ <!-- @attribute bottom The bottom edge of the rectangle. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the rectangle. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute right The right edge of the rectangle. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top" The top" edge of the rectangle. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** addRoundRect
+ AddRoundRect adds a closed rounded rectangle to the parent path element.
+ */ -->
+ <xs:element name="addRoundRect">
+ <xs:complexType>
+ <!-- @attribute direction One of @pattern. @patternDescription -->
+ <xs:attribute name="direction" type="Sk:PathDirection"/>
+ <!-- @attribute bottom The bottom edge of the rounded rectangle's bounding box. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the rounded rectangle's bounding box. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute right The right edge of the rounded rectangle's bounding box. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the rounded rectangle's bounding box. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute rx The X-radius of the oval used to round the corners. -->
+ <xs:attribute name="rx" type="Sk:Float"/>
+ <!-- @attribute ry The Y-radius of the oval used to round the corners. -->
+ <xs:attribute name="ry" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** animate
+ Animate varies the value of an element's attribute over time.
+ The animation may vary starting at the 'from' attribute, and ending at the 'to' attribute,
+ or may compute the value using the 'formula' attribute.
+ */ -->
+ <xs:element name="animate">
+ <xs:complexType>
+ <!-- @attribute begin An optional offset that must elapse before the animation begins. The apply
+ begin attribute is added to any animator's begin attribute. -->
+ <xs:attribute name="begin" type="Sk:MSec"/>
+ <!-- @attribute blend Specifies how the from and to values are blended. A value from 0.0 to
+ 1.0 specifies a cubic lag/log/lag blend (slow to change at the beginning and end); the closer
+ blend is to 1.0, the more linear the blend. If omitted, the blend is linear. -->
+ <xs:attribute name="blend" type="Sk:FloatArray"/>
+ <!-- @attribute dur The duration of the animation in milliseconds. -->
+ <xs:attribute name="dur" type="Sk:MSec"/>
+ <!-- @attribute dynamic If true, restart the animation if any of the simple values the 'from', 'formula',
+ 'lval', or 'to' attributes reference are changed. Simple values are contained by the array, boolean, float, int,
+ and string elements. -->
+ <xs:attribute name="dynamic" type="Sk:Boolean" />
+ <!-- @attribute field The attribute to animate. -->
+ <xs:attribute name="field" type="Sk:String"/>
+ <!-- @attribute formula A script to execute over time to compute the field's value. Typically,
+ the fomula is a script expression which includes a reference to the time attribute of the
+ containing apply element. Requires a dur. For animations that do not stop, set dur="Number.POSITIVE_INFINITY" -->
+ <xs:attribute name="formula" type="Sk:DynamicString"/>
+ <!-- @attribute from The starting value (requires a 'to' attribute) -->
+ <xs:attribute name="from" type="Sk:DynamicString"/>
+ <!-- @attribute lval An expression evaluating to the attribute to animate.
+ If present, lval overrides 'field'. The expression is typically an array element,
+ e.g. lval="x[y]" . -->
+ <xs:attribute name="lval" type="Sk:DynamicString"/>
+ <!-- @attribute mirror If true, reverses the interpolated value during even repeat cycles. -->
+ <xs:attribute name="mirror" type="Sk:Boolean"/>
+ <!-- @attribute repeat Specifies the number of times to repeat the animation.
+ (May be fractional.) -->
+ <xs:attribute name="repeat" type="Sk:Float"/>
+ <!-- @attribute reset If true, the computed value is the initial value after the
+ animation is complete. If false, or by default, the computed value is the final value
+ after the animation is complete. -->
+ <xs:attribute name="reset" type="Sk:Boolean"/>
+ <!-- @attribute step When the apply's attribute mode="immediate" or "create", the step attribute can be read by
+ script to determine the current animation iteration. -->
+ <xs:attribute name="step" type="Sk:Int" />
+ <!-- @attribute target The element to animate. By default, the element contained by the apply
+ or referenced by the apply's scope attribute is the animate target. -->
+ <xs:attribute name="target" type="Sk:DynamicString"/>
+ <!-- @attribute to The ending value (requires a 'from' attribute) -->
+ <xs:attribute name="to" type="Sk:DynamicString"/>
+ <!-- @attribute values [Depreciated] -->
+ <xs:attribute name="values" type="Sk:DynamicString"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** apply
+ Apply changes one or more attributes of an element.
+ Apply either contains one displayable element or references the element scoping the change
+ with the 'scope' attribute. Apply either contains one animator element or references it with
+ the 'animator' attribute.
+ In the display list, apply draws the element it scopes after evaluating the animation.
+ */ -->
+ <xs:element name="apply">
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="1">
+ <xs:element ref="Sk:animate"/>
+ <xs:element ref="Sk:set" />
+ <!-- not quite right; want to say 'one of the above, one of the below'
+ </xs:choice>
+ <xs:choice minOccurs="0" maxOccurs="1">
+ -->
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:array"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:boolean"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:drawTo"/>
+ <xs:element ref="Sk:float"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:int"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:post"/>
+ <xs:element ref="Sk:random"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:snapshot"/>
+ <xs:element ref="Sk:string"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute animator The description of how the element is changed over time. -->
+ <xs:attribute name="animator" type="Sk:Animate"/>
+ <!-- @attribute begin An optional offset that must elapse before the animation begins. The apply
+ begin attribute is added to any animator's begin attribute. -->
+ <xs:attribute name="begin" type="Sk:MSec" />
+ <!-- @attribute dontDraw Edits an element's attribute without drawing it; for instance,
+ to edit a clip's rectangle without drawing the rectangle, set dontDraw="true". -->
+ <xs:attribute name="dontDraw" type="Sk:Boolean"/>
+ <!-- @attribute dynamicScope The location in the display list where animations are stored. Use
+ dynamicScope instead of scope if a script expression with potentially different values is desired to
+ describe the scope. -->
+ <xs:attribute name="dynamicScope" type="Sk:String"/>
+ <!-- @attribute interval The optional time interval from one animation frame to the next. -->
+ <xs:attribute name="interval" type="Sk:MSec" />
+ <!-- @attribute mode One of @pattern. @patternDescription -->
+ <xs:attribute name="mode" type="Sk:ApplyMode"/>
+ <!-- @attribute pickup Starts the animation at the current target's attribute value. Enabling
+ 'pickup' permits omitting the 'from' attribute of the animator. -->
+ <xs:attribute name="pickup" type="Sk:Boolean"/>
+ <!-- @attribute restore If true, multiple references to the same apply statement save and
+ restore the interpolated target values. -->
+ <xs:attribute name="restore" type="Sk:Boolean"/>
+ <!-- @attribute scope The location in the display list where animations are stored. -->
+ <xs:attribute name="scope" type="Sk:Drawable"/>
+ <!-- @attribute step When mode="immediate" or "create", the step attribute can be read by
+ script to determine the current animation iteration. -->
+ <xs:attribute name="step" type="Sk:Int" />
+ <!-- @attribute steps When mode="immediate", the number of times the animation
+ is stepped. The animation iterates 'steps' times plus one. -->
+ <xs:attribute name="steps" type="Sk:Int" />
+ <!-- @attribute time When read from script, returns the animation time. Typically used by
+ an animate element's formula attribute. -->
+ <xs:attribute name="time" type="Sk:MSec" />
+ <!-- @attribute transition One of @pattern. @patternDescription -->
+ <xs:attribute name="transition" type="Sk:ApplyTransition"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** array
+ Array contains an array of values of the same type. The values may be
+ numbers or strings.
+ */ -->
+ <xs:element name="array">
+ <xs:complexType>
+ <!-- @attribute length The number of elements in the array (read only). -->
+ <xs:attribute name="length" type="Sk:Int"/>
+ <!-- @attribute values The elements in the array. -->
+ <xs:attribute name="values" type="Sk:UnknownArray"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** bitmap
+ Bitmap describes a rectangle of pixels.
+ Use the <drawTo> element to draw to a bitmap.
+ Add the bitmap to the display list to draw from a bitmap.
+ */ -->
+ <xs:element name="bitmap">
+ <xs:complexType>
+ <!-- @attribute erase The color, including the alpha, the bitmap is intially set to. -->
+ <xs:attribute name="erase" type="Sk:ARGB"/>
+ <!-- @attribute format One of @pattern. @patternDescription -->
+ <xs:attribute name="format" type="Sk:BitmapFormat"/>
+ <!-- @attribute height The height of the bitmap in pixels. -->
+ <xs:attribute name="height" type="Sk:Int"/>
+ <!-- @attribute rowBytes The number of byte describing each row of pixels (optional). -->
+ <xs:attribute name="rowBytes" type="Sk:Int"/>
+ <!-- @attribute width The height of the width in pixels. -->
+ <xs:attribute name="width" type="Sk:Int"/>
+ <!-- @attribute x The left edge of the bitmap in unit space. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The top edge of teh bitmap in unit space. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** bitmapShader
+ BitmapShader sets the paint shader to draw the bitmap as a texture.
+ */ -->
+ <xs:element name="bitmapShader">
+ <xs:complexType>
+ <xs:choice >
+ <xs:element ref="Sk:image" minOccurs="0" />
+ <xs:element ref="Sk:matrix" minOccurs="0" />
+ </xs:choice>
+ <!-- @attribute matrix Matrix applies a 3x3 transform to the gradient. -->
+ <xs:attribute name="matrix" type="Sk:Matrix"/>
+ <!-- @attribute tileMode One of @pattern. @patternDescription -->
+ <xs:attribute name="tileMode" type="Sk:TileMode"/>
+ <!-- @attribute filterType The bitmap filter to employ, one of @pattern. -->
+ <xs:attribute name="filterType" type="Sk:FilterType"/>
+ <!-- @attribute image The bitmap to draw. -->
+ <xs:attribute name="image" type="Sk:BaseBitmap"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+
+ <!-- /** blur
+ Blur describes an image filter in the paint that blurs the drawn geometry.
+ */ -->
+ <xs:element name="blur">
+ <xs:complexType>
+ <!-- @attribute blurStyle One of @pattern. @patternDescription -->
+ <xs:attribute name="blurStyle" type="Sk:MaskFilterBlurStyle"/>
+ <!-- @attribute radius The extent of the filter effect in unit space. If the radius is less
+ than zero, the blur has no effect. -->
+ <xs:attribute name="radius" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** boolean
+ Boolean contains an boolean. The boolean element cannot be added to a display list, but can
+ by set by animations and read by any attribute definition. An boolean element may be referenced,
+ for instance, by a group's condition attribute to make an animation conditionally execute.
+ */ -->
+ <xs:element name="boolean">
+ <xs:complexType>
+ <!-- @attribute value The contained boolean. -->
+ <xs:attribute name="value" type="Sk:Boolean"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** bounds
+ Bounds describes a bounding box that is not drawn. Bounds is used to specify a rectangle to
+ invalidate or record whether the specified area was drawn.
+ The width and height attribute compute the rectangle's right and bottom edges when the rectangle
+ description is first seen. Animating the rectangle's left or top will not recompute the right or bottom
+ if the width or height have been specified.
+ */ -->
+ <xs:element name="bounds">
+ <xs:complexType>
+ <!-- @attribute bottom The bottom edge of the rectangle. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute height The height of the rectangle. Setting height computes the
+ bottom attribute from the top attribute. -->
+ <xs:attribute name="height" type="Sk:Float"/>
+ <!-- @attribute inval If set to true, union the drawn bounds to compute an inval area. -->
+ <xs:attribute name="inval" type="Sk:Boolean"/>
+ <!-- @attribute left The left edge of the rectangle. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute needsRedraw Set to true if last draw was visible. -->
+ <xs:attribute name="needsRedraw" type="Sk:Boolean"/>
+ <!-- @attribute right The right edge of the rectangle. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the rectangle. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute width The width of the rectangle. -->
+ <xs:attribute name="width" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** clear
+ Clear removes all entries in the display list.
+ */ -->
+ <xs:element name="clear">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** clip
+ Clip sets the canvas to clip drawing to an element's geometry.
+ A clip element may contain an element or reference an element with the path or
+ rectangle attributes. To make the clip unrestricted, enclose a 'full' element.
+ */ -->
+ <xs:element name="clip">
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="1">
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ </xs:choice>
+ <!-- @attribute path A path-derived element to clip to: either an oval,
+ a path, a polygon, a polyline, or a roundRect. -->
+ <xs:attribute name="path" type="Sk:Path"/>
+ <!-- @attribute rect A rectangle element to clip to. -->
+ <xs:attribute name="rect" type="Sk:Rect"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** close
+ Close connects the last point in the path's contour to the first if the contour is not already closed.
+ */ -->
+ <xs:element name="close">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** color
+ Color describes a color in RGB space or HSV space, and its alpha (transparency).
+ */ -->
+ <xs:element name="color">
+ <xs:complexType>
+ <!-- @attribute alpha The alpha component, which describes transparency.
+ Alpha ranges from 0.0 (transparent) to 1.0 (completely opaque). -->
+ <xs:attribute name="alpha" type="Sk:Float"/>
+ <!-- @attribute blue The blue component of an RGB color. Blue ranges from 0 to 255. -->
+ <xs:attribute name="blue" type="Sk:Float"/>
+ <!-- @attribute color The complete color. The color can be specified by name,
+ by hexadecimal value, or with the rgb function. -->
+ <xs:attribute name="color" type="Sk:ARGB"/>
+ <!-- @attribute green The green component of an RGB color. Green ranges from 0 to 255. -->
+ <xs:attribute name="green" type="Sk:Float"/>
+ <!-- @attribute hue The hue component of an HSV color. Hue ranges from 0 to 360. -->
+ <xs:attribute name="hue" type="Sk:Float"/>
+ <!-- @attribute red The red component of an RGB color. Red ranges from 0 to 255. -->
+ <xs:attribute name="red" type="Sk:Float"/>
+ <!-- @attribute saturation The saturation component of an HSV color. Saturation ranges from 0 to 1. -->
+ <xs:attribute name="saturation" type="Sk:Float"/>
+ <!-- @attribute value The value component of an HSV color. Value ranges from 0 to 1. -->
+ <xs:attribute name="value" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** cubicTo
+ CubicTo adds a cubic to the path, using the last point in the path as the first point of the cubic.
+ */ -->
+ <xs:element name="cubicTo">
+ <xs:complexType>
+ <!-- @attribute x1 The x position of the first off-curve point. -->
+ <xs:attribute name="x1" type="Sk:Float"/>
+ <!-- @attribute x2 The x position of the second off-curve point. -->
+ <xs:attribute name="x2" type="Sk:Float"/>
+ <!-- @attribute x3 The x position of the final on-curve point. -->
+ <xs:attribute name="x3" type="Sk:Float"/>
+ <!-- @attribute y1 The y position of the first off-curve point. -->
+ <xs:attribute name="y1" type="Sk:Float"/>
+ <!-- @attribute y2 The y position of the second off-curve point. -->
+ <xs:attribute name="y2" type="Sk:Float"/>
+ <!-- @attribute y3 The y position of the final on-curve point. -->
+ <xs:attribute name="y3" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** dash
+ Dash describes an array of dashes and gaps that describe how the paint strokes lines,
+ rectangles, and paths. The intervals, phase, and dashed path are all measured in the same
+ unit space. The phase and distance between dashes is unaffected by the paint's stroke width.
+ */ -->
+ <xs:element name="dash">
+ <xs:complexType>
+ <!-- @attribute intervals An array of floats that alternately describe the lengths of
+ dashes and gaps. Intervals must contain an even number of entries. -->
+ <xs:attribute name="intervals" type="Sk:FloatArray"/>
+ <!-- @attribute phase Phase advances the placement of the first dash. A positive phase
+ preceeds the first dash with a gap. A negative phase shortens the length of the first dash. -->
+ <xs:attribute name="phase" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** data
+ Data provides metadata to an event. The metadata may be an integer, a float,
+ or a string.
+ */ -->
+ <xs:element name="data">
+ <xs:complexType>
+ <!-- @attribute float The float value associated with the metadata. -->
+ <xs:attribute name="float" type="Sk:Float"/>
+ <!-- @attribute initialized A read-only value set to false (unused by data). -->
+ <xs:attribute name="initialized" type="Sk:Boolean"/>
+ <!-- @attribute int The integer value associated with the metadata. -->
+ <xs:attribute name="int" type="Sk:Int"/>
+ <!-- @attribute name The name of the metadata. This is the name of the data. -->
+ <xs:attribute name="name" type="Sk:String"/>
+ <!-- @attribute string The string value associated with the metadata. -->
+ <xs:attribute name="string" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** discrete
+ Discrete alters the edge of the stroke randomly. Discrete is a path effect, and only has an
+ effect when referenced from a paint.. A <pathEffect/>
+ element with no attributes will dissable discrete.
+ */ -->
+ <xs:element name="discrete">
+ <xs:complexType>
+ <!-- @attribute deviation The amount of wobble in the stroke. -->
+ <xs:attribute name="deviation" type="Sk:Float"/>
+ <!-- @attribute segLength The length of wobble in the stroke. -->
+ <xs:attribute name="segLength" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** drawTo
+ DrawTo images to a bitmap. The bitmap can be added to the display list
+ to draw the composite image.
+ DrawTo can be used as an offscreen to speed complicated animations, and
+ for bitmap effects such as pixelated zooming.
+ DrawTo can only reference a single drawable element. Use <add>,
+ <group>, or <save> to draw multiple elements with <drawTo>.
+ */ -->
+ <xs:element name="drawTo">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded" >
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute drawOnce If set, the drawTo will only draw a single time. -->
+ <xs:attribute name="drawOnce" type="Sk:Boolean"/>
+ <!-- @attribute use The bitmap to draw into. -->
+ <xs:attribute name="use" type="Sk:bitmap"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** dump
+ Dump prints a list of the items in the display list and all items'
+ children to the debug console. Dump is only available in Debug
+ builds. */ -->
+ <xs:element name="dump">
+ <xs:complexType>
+ <!-- @attribute displayList Dumps the current display list if true. The display list is also
+ dumped if dump has no attributes. -->
+ <xs:attribute name="displayList" type="Sk:Boolean"/>
+ <!-- @attribute eventList Dumps the list of events, both enabled and disabled. -->
+ <xs:attribute name="eventList" type="Sk:Boolean"/>
+ <!-- @attribute events Outputs each event element as it is enabled. -->
+ <xs:attribute name="events" type="Sk:Boolean"/>
+ <!-- @attribute groups Outputs each group element as its condition is evaluated. -->
+ <xs:attribute name="groups" type="Sk:Boolean"/>
+ <!-- @attribute name Outputs the values associated with a single named element. -->
+ <xs:attribute name="name" type="Sk:String"/>
+ <!-- @attribute posts Outputs each post element as it is enabled. -->
+ <xs:attribute name="posts" type="Sk:Boolean"/>
+ <!-- @attribute script Evaluates the provided script -->
+ <xs:attribute name="script" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** emboss
+ PRELIMINARY [to be replaced with SkEmbossMaskFilter.h doxyfomation
+ at some point]
+ Emboss applies a mask filter to the paint that makes bias the object's color
+ towards white or black depending on the normals of the path contour, giving
+ the shape a 3D raised or depressed effect.
+ Embossing is replaced by subsequent mask filter elements, or
+ disabled a negative radius, or by an empty <mask filter> element.
+ */ -->
+ <xs:element name="emboss">
+ <xs:complexType>
+ <!-- @attribute ambient The amount of ambient light, from 0 to 1. -->
+ <xs:attribute name="ambient" type="Sk:Float"/>
+ <!-- @attribute direction The direction of the light source, as descibed by a 3D vector.
+ (The vector is normalized to a unit length of 1.0.) -->
+ <xs:attribute name="direction" type="Sk:FloatArray"/>
+ <!-- @attribute radius The extent of the filter effect in unit space. If the radius is less
+ than zero, the emboss has no effect. -->
+ <xs:attribute name="radius" type="Sk:Float"/>
+ <!-- @attribute specular The expotential intensity of the light, from 0 to 1.
+ Each increase of 0.0625 doubles the intensity. -->
+ <xs:attribute name="specular" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** event
+ Event contains a series of actions performed each time the event's criteria are satisfied.
+ These actions may modify the display list, may enable animations which in turn modify
+ elements' attributes, and may post other events.
+ */ -->
+ <xs:element name="event">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded" >
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:array"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:boolean"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:drawTo"/>
+ <xs:element ref="Sk:dump"/>
+ <xs:element ref="Sk:float"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:hitClear"/>
+ <xs:element ref="Sk:hitTest"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:input"/>
+ <xs:element ref="Sk:int"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:movie"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:post"/>
+ <xs:element ref="Sk:random"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:snapshot"/>
+ <xs:element ref="Sk:string"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute code The key code to match to a key press event, one of @pattern.
+ If the code is set to @pattern[0], the event is never activated. -->
+ <xs:attribute name="code" type="Sk:EventCode"/>
+ <!-- @attribute disable If true, the event cannot be activated. By default false.. -->
+ <xs:attribute name="disable" type="Sk:Boolean"/>
+ <!-- @attribute key The character code to match to a key down event.
+ When read, the key that activated this event. -->
+ <xs:attribute name="key" type="Sk:String"/>
+ <!-- @attribute keys A dash-separated continuous range of character codes to match
+ to a key down event. Read the key attribute to determine the key that activated this event. -->
+ <xs:attribute name="keys" type="Sk:String"/> <!-- single or range of keys -->
+ <!-- @attribute kind The event kind that activates this event, one of @pattern.
+ If kind equals keyChar, either attribute key or keys is expected.
+ If kind equals keyPress, attribute code is expected.
+ If kind equals onEnd, attribute target is expected.
+ If kind equals onLoad, the event is activated when the document containing the event
+ is loaded. The onLoad attribute cannot be activated through a post event.
+ If kind equals user, the event is activated when the posted event targets this event's ID. -->
+ <xs:attribute name="kind" type="Sk:EventKind"/>
+ <!-- @attribute target The element to listen to which activates this event. -->
+ <xs:attribute name="target" type="Sk:String" />
+ <!-- @attribute x For click events, the x-coordinate of the click. -->
+ <xs:attribute name="x" type="Sk:Float" />
+ <!-- @attribute y For click events, the y-coordinate of the click. -->
+ <xs:attribute name="y" type="Sk:Float" />
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** float
+ Float contains a signed fractional value. The float element cannot be added to a display list,
+ but can be set by animations and read by any attribute definition.
+ */ -->
+ <xs:element name="float">
+ <xs:complexType>
+ <!-- @attribute value The contained float. -->
+ <xs:attribute name="value" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** fromPath
+ FromPath concatenates the parent matrix with a new matrix
+ that maps a unit vector to a point on the given path.
+ A fromPath element may contain a path element, or may refer to a previously
+ defined path element with the path attribute.
+ */ -->
+ <xs:element name="fromPath">
+ <xs:complexType>
+ <xs:choice >
+ <!-- @element path The path to evaluate. -->
+ <xs:element ref="Sk:path" minOccurs="0" />
+ </xs:choice>
+ <!-- @attribute mode One of @pattern.
+ If mode is set to normal, the matrix maps the unit vector's angle and position.
+ If mode is set to angle, the matrix maps only the unit vector's angle.
+ If mode is set to position, the matrix maps only the unit vector's position. -->
+ <xs:attribute name="mode" type="Sk:FromPathMode"/>
+ <!-- @attribute offset The distance along the path to evaluate. -->
+ <xs:attribute name="offset" type="Sk:Float"/>
+ <!-- @attribute path The path to evaluate. -->
+ <xs:attribute name="path" type="Sk:Path"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** full
+ Full paints the entire canvas to the limit of the canvas' clip.
+ */ -->
+ <xs:element name="full">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** group
+ The group element collects a series of elements into a group. The group can be referenced
+ or defined within elements, like apply, which operate on any kind of element. Groups
+ may contain groups. An element in a group draws identically to an element outside a group.
+ */ -->
+ <xs:element name="group">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:array"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:boolean"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:drawTo"/>
+ <xs:element ref="Sk:float"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:hitClear"/>
+ <xs:element ref="Sk:hitTest"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:int"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:post"/>
+ <xs:element ref="Sk:random"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:snapshot"/>
+ <xs:element ref="Sk:string"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute condition If present and zero, the contained elements are ignored
+ when drawn. -->
+ <xs:attribute name="condition" type="Sk:DynamicString"/>
+ <!-- @attribute enableCondition If present and zero, the contained elements are ignored
+ when enabled. -->
+ <xs:attribute name="enableCondition" type="Sk:DynamicString"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="hitClear" >
+ <xs:complexType>
+ <xs:choice maxOccurs="1">
+ <xs:element ref="Sk:array"/>
+ </xs:choice>
+ <!-- @attribute targets An array of element IDs to clear their hit-tested state. -->
+ <xs:attribute name="targets" type="Sk:DisplayableArray"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="hitTest" >
+ <xs:complexType>
+ <xs:choice maxOccurs="2">
+ <xs:element ref="Sk:array"/>
+ </xs:choice>
+ <!-- @attribute bullets An array of element IDs to test for intersection with targets. -->
+ <xs:attribute name="bullets" type="Sk:DisplayableArray"/>
+ <!-- @attribute hits The targets the bullets hit. A read-only array of indices, one index
+ per bullet. The value of the array element is the index of the target hit, or -1 if no
+ target was hit. -->
+ <xs:attribute name="hits" type="Sk:IntArray"/>
+ <!-- @attribute targets An array of element IDs to test for intersection with bullets. -->
+ <xs:attribute name="targets" type="Sk:DisplayableArray"/>
+ <!-- @attribute value Read only; set to true if some bullet hit some target. -->
+ <xs:attribute name="value" type="Sk:Boolean"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** image
+ Image creates a reference to a JPEG, PNG or GIF. The image may be referenced
+ through the local file system, the internet, or embedded in the document in Base64
+ format. The specific image type is determined by examining the byte stream.
+ */ -->
+ <xs:element name="image">
+ <xs:complexType>
+ <!-- @attribute base64 The image in Base64 notation. See http://rfc.net/rfc2045.html
+ for the base64 format. -->
+ <xs:attribute name="base64" type="Sk:Base64"/>
+ <!-- @attribute height The height of the image (read-only). -->
+ <xs:attribute name="height" type="Sk:Int"/>
+ <!-- @attribute src The URI reference, local to the contaiing document. -->
+ <xs:attribute name="src" type="Sk:String"/>
+ <!-- @attribute width The width of the image (read-only). -->
+ <xs:attribute name="width" type="Sk:Int"/>
+ <!-- @attribute x The position of the left edge of the image in local coordinates. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The position of the top edge of the image in local coordinates. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** include
+ Include adds the referenced XML to the containing document. Unlike movie, the XML
+ directives can reference the document's IDs and can define new IDs that are referenced
+ by the remainder of the document or subsequent includes.
+ */ -->
+ <xs:element name="include">
+ <xs:complexType>
+ <!-- @attribute src The URI reference, local to the containing document,
+ containing the include's XML. -->
+ <xs:attribute name="src" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** input
+ Input captures the metadata passed from an event. When the metadata's name or id
+ matches the metadata's name, the metadata's payload is copied to the corresponding
+ input attribute.
+ */ -->
+ <xs:element name="input">
+ <xs:complexType>
+ <!-- @attribute float The floating point payload carried by the metadata. -->
+ <xs:attribute name="float" type="Sk:Float"/>
+ <!-- @attribute initialized A read-only value set to true if the input received a value
+ from the event. -->
+ <xs:attribute name="initialized" type="Sk:Boolean"/>
+ <!-- @attribute int The signed integer payload carried by the metadata. -->
+ <xs:attribute name="int" type="Sk:Int"/>
+ <!-- @attribute name The name of the metadata containing the payload. Note that
+ the name or id may match the payload, but that XML requires the id to be
+ uniquely defined in the document, while multiple input elements may reuse
+ the name. -->
+ <xs:attribute name="name" type="Sk:String"/>
+ <!-- @attribute string The text payload carried by the metadata. -->
+ <xs:attribute name="string" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** int
+ Int contains an integer. The int element cannot be added to a display list, but can
+ by set by animations and read by any attribute definition. An int element may be used,
+ for instance, to index through an array element.
+ */ -->
+ <xs:element name="int">
+ <xs:complexType>
+ <!-- @attribute value The contained integer. -->
+ <xs:attribute name="value" type="Sk:Int"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** line
+ Line describes a line between two points. As noted below, the paint's stroke and
+ strokeAndFill attributes are ignored.
+ */ -->
+ <xs:element name="line">
+ <xs:complexType>
+ <!-- @attribute x1 The start point's x value. -->
+ <xs:attribute name="x1" type="Sk:Float"/>
+ <!-- @attribute x2 The stop point's x value. -->
+ <xs:attribute name="x2" type="Sk:Float"/>
+ <!-- @attribute y1 The start point's y value. -->
+ <xs:attribute name="y1" type="Sk:Float"/>
+ <!-- @attribute y2 The stop point's y value. -->
+ <xs:attribute name="y2" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** lineTo
+ LineTo adds a line from the last point in a path to the specified point.
+ */ -->
+ <xs:element name="lineTo">
+ <xs:complexType>
+ <!-- @attribute x The final path x coordinate. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The final path y coordinate. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** linearGradient
+ LinearGradient sets the paint shader to ramp between two or more colors.
+ */ -->
+ <xs:element name="linearGradient">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:matrix"/>
+ </xs:choice>
+ <!-- @attribute matrix Matrix applies a 3x3 transform to the gradient. -->
+ <xs:attribute name="matrix" type="Sk:Matrix"/>
+ <!-- @attribute tileMode One of @pattern. @patternDescription -->
+ <xs:attribute name="tileMode" type="Sk:TileMode"/>
+ <!-- @attribute offsets An optional array of values used to bias the colors. The first entry
+ in the array must be 0.0, the last must be 1.0, and intermediate values must ascend. -->
+ <xs:attribute name="offsets" type="Sk:FloatArray"/>
+ <!-- @attribute points Two points describing the start and end of the gradient. -->
+ <xs:attribute name="points" type="Sk:Point"/> <!-- not right; should be array of 2 points -->
+ <!-- @attribute unitMapper A script that returns the mapping for [0,1] for the gradient.
+ The script can use the predefined variable 'unit' to compute the mapping. For instance,
+ "unit*unit" squares the value (while still keeping it in the range of [0,1].) The computed number
+ is pinned to from 0 to 1 after the script is executed. -->
+ <xs:attribute name="unitMapper" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** maskFilter
+ MaskFilter disables any mask filter referenced by the paint.
+ */ -->
+ <xs:element name="maskFilter">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** matrix
+ Matrix transforms all points drawn to the canvas. The matrix may translate, scale, skew, rotate,
+ or apply perspective, or apply any combination.
+ */ -->
+ <xs:element name="matrix">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <!-- @element fromPath FromPath maps a unit vector to a position and direction on a path. -->
+ <xs:element ref="Sk:fromPath"/>
+ <!-- @element polyToPoly PolyToPoly maps a points between two polygons. -->
+ <xs:element ref="Sk:polyToPoly"/>
+ <!-- @element rectToRect RectToRect maps a points between two rectangles. -->
+ <xs:element ref="Sk:rectToRect"/>
+ <!-- @element rotate Rotate computes the matrix rotation in degrees. -->
+ <xs:element ref="Sk:rotate"/>
+ <!-- @element scale Scale stretches or shrinks horizontally, vertically, or both. -->
+ <xs:element ref="Sk:scale"/>
+ <!-- @element skew Skew slants horizontally, vertically, or both. -->
+ <xs:element ref="Sk:skew"/>
+ <!-- @element translate Translate moves horizontally, vertically, or both. -->
+ <xs:element ref="Sk:translate"/>
+ </xs:choice>
+ <!-- @attribute matrix Nine floats describing a 3x3 matrix. -->
+ <xs:attribute name="matrix" type="Sk:FloatArray"/>
+ <!-- @attribute perspectX The [0][2] element of the 3x3 matrix. -->
+ <xs:attribute name="perspectX" type="Sk:Float"/>
+ <!-- @attribute perspectY The [1][2] element of the 3x3 matrix. -->
+ <xs:attribute name="perspectY" type="Sk:Float"/>
+ <!-- @attribute rotate The angle to rotate in degrees. -->
+ <xs:attribute name="rotate" type="Sk:Float"/>
+ <!-- @attribute scale The scale to apply in both X and Y.. -->
+ <xs:attribute name="scale" type="Sk:Float"/>
+ <!-- @attribute scaleX The [0][0] element of the 3x3 matrix. -->
+ <xs:attribute name="scaleX" type="Sk:Float"/>
+ <!-- @attribute scaleY The [1][1] element of the 3x3 matrix. -->
+ <xs:attribute name="scaleY" type="Sk:Float"/>
+ <!-- @attribute skewX The [0][1] element of the 3x3 matrix. -->
+ <xs:attribute name="skewX" type="Sk:Float"/>
+ <!-- @attribute skewY The [1][0] element of the 3x3 matrix. -->
+ <xs:attribute name="skewY" type="Sk:Float"/>
+ <!-- @attribute translate A point specifying the translation in X and Y. -->
+ <xs:attribute name="translate" type="Sk:Point"/>
+ <!-- @attribute translateX The [2][0] element of the 3x3 matrix. -->
+ <xs:attribute name="translateX" type="Sk:Float"/>
+ <!-- @attribute translateY The [2][1] element of the 3x3 matrix. -->
+ <xs:attribute name="translateY" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** move
+ Move an element in the display list in front or behind other elements.
+ If where and offset are omitted, the element is moved to the end of the display list.
+ If where is specified, the element is moved before the first occurance of where in the display list.
+ If offset and where are specified, the element is moved before where plus offset.
+ A positive offset without where moves the element to the start of the list plus offset.
+ A negative offset without where moves the element to the end of the list minus offset.
+ */ -->
+ <xs:element name="move">
+ <xs:complexType>
+ <!-- @attribute mode Has no effect. -->
+ <xs:attribute name="mode" type="Sk:AddMode"/>
+ <!-- @attribute offset The destination position using the rules listed above. -->
+ <xs:attribute name="offset" type="Sk:Int"/>
+ <!-- @attribute use The element to move. -->
+ <xs:attribute name="use" type="Sk:Drawable"/>
+ <!-- @attribute where The ID of the first display list entry to move to. -->
+ <xs:attribute name="where" type="Sk:Drawable"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** moveTo
+ MoveTo specifies the first point in a path contour.
+ */ -->
+ <xs:element name="moveTo">
+ <xs:complexType>
+ <!-- @attribute x The point's x coordinate. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The point's y coordinate. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** movie
+ Movie describes a display list within the current canvas and paint. Movies can contain
+ movies. One movie cannot affect how another movie draws, but movies can communicate
+ with each other by posting events.
+ */ -->
+ <xs:element name="movie">
+ <xs:complexType>
+ <!-- @attribute src The URI reference, local to the containing document, containing the movie's XML. -->
+ <xs:attribute name="src" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** oval
+ Oval describes a circle stretched to fit in a rectangle.
+ The width and height attribute compute the oval's right and bottom edges when the oval
+ description is first seen. Animating the oval's left or top will not recompute the right or bottom
+ if the width or height have been specified.
+ */ -->
+ <xs:element name="oval">
+ <xs:complexType>
+ <!-- @attribute bottom The bottom edge of the oval. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute height The height of the oval. -->
+ <xs:attribute name="height" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the oval. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute needsRedraw Set to true if last draw was visible. -->
+ <xs:attribute name="needsRedraw" type="Sk:Boolean"/>
+ <!-- @attribute right The right edge of the oval. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the oval. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute width The width of the oval. -->
+ <xs:attribute name="width" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** paint
+ Paint uses color, flags, path effects, mask filters, shaders, and stroke effects when drawing
+ geometries, images, and text.
+ */ -->
+ <xs:element name="paint">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <!-- @element bitmapShader Sets or cancels an image to draw as the color. -->
+ <xs:element ref="Sk:bitmapShader"/>
+ <!-- @element blur Blur radially draws the shape with varying transparency. -->
+ <xs:element ref="Sk:blur"/>
+ <!-- @element color Color specifies a solid color in RGB or HSV. -->
+ <xs:element ref="Sk:color"/>
+ <!-- @element dash Dashes alternates stroking with dashes and gaps. -->
+ <xs:element ref="Sk:dash"/>
+ <!-- @element discrete Discrete wobbles the geometry randomly. -->
+ <xs:element ref="Sk:discrete"/>
+ <!-- @element emboss Emboss simulates a 3D light to show highlights and relief. -->
+ <xs:element ref="Sk:emboss"/>
+ <!-- @element linearGradient LinearGradient linearly ramps between two or more colors. -->
+ <xs:element ref="Sk:linearGradient"/>
+ <!-- @element maskFilter MaskFilter cancels a blur or emboss. -->
+ <xs:element ref="Sk:maskFilter"/>
+ <!-- @element pathEffect PathEffect cancels a discrete or dash. -->
+ <xs:element ref="Sk:pathEffect"/>
+ <!-- @element radialGradient RadialGradient radially ramps between two or more colors. -->
+ <xs:element ref="Sk:radialGradient"/>
+ <!-- @element shader Shader cancels a linear or radial gradient. -->
+ <xs:element ref="Sk:shader"/>
+ <!-- @element typeface Typeface chooses a font out of a font family. -->
+ <xs:element ref="Sk:typeface"/>
+ <!-- @element transparentShader TransparentShader ? [not sure what this is for] -->
+ <xs:element ref="Sk:transparentShader"/>
+ </xs:choice>
+ <!-- @attribute antiAlias AntiAlias uses gray shades to increase the definition of paths. -->
+ <xs:attribute name="antiAlias" type="Sk:Boolean"/>
+ <!-- @attribute ascent Ascent returns the height above the baseline defined by the font. -->
+ <xs:attribute name="ascent" type="Sk:Float"/>
+ <!-- @attribute color Color sets the paint to the color element with this ID. -->
+ <xs:attribute name="color" type="Sk:Color"/>
+ <!-- @attribute descent Descent returns the height below the baseline defined by thte font -->
+ <xs:attribute name="descent" type="Sk:Float"/>
+ <!-- @attribute fakeBold FakeBold enables a faked bold for text. -->
+ <xs:attribute name="fakeBold" type="Sk:Boolean"/>
+ <!-- @attribute filterType FilterType -->
+ <xs:attribute name="filterType" type="Sk:FilterType"/>
+ <!-- @attribute linearText LinearText uses the ideal path metrics at all sizes to describe text. -->
+ <xs:attribute name="linearText" type="Sk:Boolean"/>
+ <!-- @attribute maskFilter MaskFilter specifies a blur or emboss with this ID. -->
+ <xs:attribute name="maskFilter" type="Sk:MaskFilter"/>
+ <!-- @attribute measureText MeasureText(String) returns the width of the string in this paint. -->
+ <xs:attribute name="measureText" type="Sk:Float"/>
+ <!-- @attribute pathEffect PathEffect specifies a discrete or dash with this ID. -->
+ <xs:attribute name="pathEffect" type="Sk:PathEffect"/>
+ <!-- @attribute shader Shader specifies a gradient with this ID. -->
+ <xs:attribute name="shader" type="Sk:Shader"/>
+ <!-- @attribute strikeThru StrikeThru adds a line through the middle of drawn text. -->
+ <xs:attribute name="strikeThru" type="Sk:Boolean"/>
+ <!-- @attribute stroke Stroke draws the outline of geometry according to the pen attributes.
+ If style is also present, its setting overrides stroke. -->
+ <xs:attribute name="stroke" type="Sk:Boolean"/>
+ <!-- @attribute strokeCap StrokeCap is one of @pattern. -->
+ <xs:attribute name="strokeCap" type="Sk:Cap"/>
+ <!-- @attribute strokeJoin StrokeJoin is one of @pattern. -->
+ <xs:attribute name="strokeJoin" type="Sk:Join"/>
+ <!-- @attribute strokeMiter StrokeMiter limits the pen's joins on narrow angles. -->
+ <xs:attribute name="strokeMiter" type="Sk:Float"/>
+ <!-- @attribute strokeWidth StrokeWidth specifies the width of the pen. -->
+ <xs:attribute name="strokeWidth" type="Sk:Float"/>
+ <!-- @attribute style Style fills, strokes, or strokes and fills the geometry with the paint's color. -->
+ <xs:attribute name="style" type="Sk:Style"/>
+ <!-- @attribute textAlign TextAlign is one of @pattern. -->
+ <xs:attribute name="textAlign" type="Sk:Align"/>
+ <!-- @attribute textScaleX TextScaleX condenses or exapnds the text. -->
+ <xs:attribute name="textScaleX" type="Sk:Float"/>
+ <!-- @attribute textSize TextSize specifies the point size of the text. -->
+ <xs:attribute name="textSize" type="Sk:Float"/>
+ <!-- @attribute textSkewX TextSkewX draws the text obliquely. -->
+ <xs:attribute name="textSkewX" type="Sk:Float"/>
+ <!-- @attribute textTracking TextTracking specifies the space between letters. -->
+ <xs:attribute name="textTracking" type="Sk:Float"/>
+ <!-- @attribute typeface Typeface specifies a typeface element with this ID. -->
+ <xs:attribute name="typeface" type="Sk:Typeface"/>
+ <!-- @attribute underline Underline draws a line under the baseline of the text. -->
+ <xs:attribute name="underline" type="Sk:Boolean"/>
+ <!-- @attribute xfermode Xfermode specifies a transfer mode, one of @pattern. -->
+ <xs:attribute name="xfermode" type="Sk:Xfermode"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** path
+ Path creates a geometry out of lines and curves.
+ */ -->
+ <xs:element name="path">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <!-- @element addCircle Adds a circle to the path. -->
+ <xs:element ref="Sk:addCircle"/>
+ <!-- @element addOval Adds an oval to the path. -->
+ <xs:element ref="Sk:addOval"/>
+ <!-- @element addPath Adds another path to the path. -->
+ <xs:element ref="Sk:addPath"/>
+ <!-- @element addRoundRect Adds a rounded-corner rectangle to the path. -->
+ <xs:element ref="Sk:addRoundRect"/>
+ <!-- @element close Connects the last point on the path to the first. -->
+ <xs:element ref="Sk:close"/>
+ <!-- @element cubicTo Extends the path with a cubic curve. -->
+ <xs:element ref="Sk:cubicTo"/>
+ <!-- @element lineTo Extends the path with a line. -->
+ <xs:element ref="Sk:lineTo"/>
+ <!-- @element moveTo Starts a new path contour. -->
+ <xs:element ref="Sk:moveTo"/>
+ <!-- @element quadTo Extends the path with a quadratic curve. -->
+ <xs:element ref="Sk:quadTo"/>
+ <!-- @element rCubicTo Extends the path with a cubic curve expressed with relative offsets. -->
+ <xs:element ref="Sk:rCubicTo"/>
+ <!-- @element rLineTo Extends the path with a line expressed with relative offsets. -->
+ <xs:element ref="Sk:rLineTo"/>
+ <!-- @element rMoveTo Starts a new path contour relative to the path's last point. -->
+ <xs:element ref="Sk:rMoveTo"/>
+ <!-- @element rQuadTo Extends the path with a quadratic curve expressed with relative offsets. -->
+ <xs:element ref="Sk:rQuadTo"/>
+ </xs:choice>
+ <!-- @attribute d Creates a path using SVG path notation. -->
+ <xs:attribute name="d" type="Sk:String"/>
+ <!-- @attribute fillType One of @pattern. -->
+ <xs:attribute name="fillType" type="Sk:FillType"/>
+ <!-- @attribute length Returns the length of the path. -->
+ <xs:attribute name="length" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** pathEffect
+ PathEffect cancels any current path effect within the paint, such as dashing or discrete.
+ */ -->
+ <xs:element name="pathEffect">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** point
+ Point describes a two dimensional point in space. The point element can be added
+ to the display list and drawn.
+ */ -->
+ <xs:element name="point">
+ <xs:complexType>
+ <!-- @attribute x The x coordinate of the point. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The y coordinate of the point. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** polygon
+ Polygon creates a geometry out of lines. Polygon is a specialization of path; element that
+ refers to a path can refer to a polygon also. A polygon specified through elements behaves identically
+ to a path. A polygon specified by the points attribute contains a single contour, and the contour is
+ automatically closed.
+ */ -->
+ <xs:element name="polygon">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <!-- @element close Connects the last point on the path to the first. -->
+ <xs:element ref="Sk:close"/>
+ <!-- @element addPath Adds another path to the path. -->
+ <xs:element ref="Sk:addPath"/>
+ <!-- @element lineTo Extends the path with a line. -->
+ <xs:element ref="Sk:lineTo"/>
+ <!-- @element moveTo Starts a new path contour. -->
+ <xs:element ref="Sk:moveTo"/>
+ <!-- @element rLineTo Extends the path with a line expressed with relative offsets. -->
+ <xs:element ref="Sk:rLineTo"/>
+ <!-- @element rMoveTo Starts a new path contour relative to the path's last point. -->
+ <xs:element ref="Sk:rMoveTo"/>
+ </xs:choice>
+ <!-- @attribute points An array of values that describe a sequence of points, compatible with SVG. -->
+ <xs:attribute name="points" type="Sk:FloatArray"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** polyline
+ Polyline creates a geometry out of lines. Polygon is a specialization of path; element that
+ refers to a path can refer to a polygon also. A polygon specified through elements behaves identically
+ to a path. A polygon specified by the points attribute contains a single contour, and the contour is
+ not automatically closed.
+ */ -->
+ <xs:element name="polyline">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <!-- @element close Connects the last point on the path to the first. -->
+ <xs:element ref="Sk:close"/>
+ <!-- @element addPath Adds another path to the path. -->
+ <xs:element ref="Sk:addPath"/>
+ <!-- @element lineTo Extends the path with a line. -->
+ <xs:element ref="Sk:lineTo"/>
+ <!-- @element moveTo Starts a new path contour. -->
+ <xs:element ref="Sk:moveTo"/>
+ <!-- @element rLineTo Extends the path with a line expressed with relative offsets. -->
+ <xs:element ref="Sk:rLineTo"/>
+ <!-- @element rMoveTo Starts a new path contour relative to the path's last point. -->
+ <xs:element ref="Sk:rMoveTo"/>
+ </xs:choice>
+ <!-- @attribute points An array of values that describe a sequence of points, compatible with SVG. -->
+ <xs:attribute name="points" type="Sk:FloatArray"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** polyToPoly
+ PolyToPoly creates a matrix which maps points proportionally from one polygon to the other.
+ */ -->
+ <xs:element name="polyToPoly">
+ <xs:complexType>
+ <xs:choice maxOccurs="2">
+ <xs:element ref="Sk:polygon"/>
+ </xs:choice>
+ <!-- @attribute source The polygon to map from.. -->
+ <xs:attribute name="source" type="Sk:polygon"/>
+ <!-- @attribute destination The polygon to map to.. -->
+ <xs:attribute name="destination" type="Sk:polygon"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** post
+ Post activates an event. The event can trigger one or more actions, and can carry a data payload.
+ */ -->
+ <xs:element name="post">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <xs:element ref="Sk:data"/>
+ </xs:choice>
+ <!-- @attribute delay Time in seconds that must elapse before the target event is activated. -->
+ <xs:attribute name="delay" type="Sk:MSec"/>
+ <!-- @attribute mode One of @pattern. @patternDescription -->
+ <xs:attribute name="mode" type="Sk:EventMode"/>
+ <!-- @attribute sink The optional named EventSink to direct the event to. -->
+ <xs:attribute name="sink" type="Sk:String"/>
+ <!-- @attribute target The ID of the user event to trigger. -->
+ <xs:attribute name="target" type="Sk:String"/>
+ <!-- @attribute type The name of the external event to post. -->
+ <xs:attribute name="type" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** quadTo
+ QuadTo adds a quadratic curve to a path.
+ */ -->
+ <xs:element name="quadTo">
+ <xs:complexType>
+ <!-- @attribute x1 The x position of the off-curve point. -->
+ <xs:attribute name="x1" type="Sk:Float"/>
+ <!-- @attribute x2 The x position of the final point. -->
+ <xs:attribute name="x2" type="Sk:Float"/>
+ <!-- @attribute y1 The y position of the off-curve point. -->
+ <xs:attribute name="y1" type="Sk:Float"/>
+ <!-- @attribute y2 The y position of the final point. -->
+ <xs:attribute name="y2" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rCubicTo
+ RCubicTo adds a cubic to the path, using the last point in the path as the first point of the cubic. THe
+ added points are offsets from the last point in the path.
+ */ -->
+ <xs:element name="rCubicTo">
+ <xs:complexType>
+ <!-- @attribute x1 The x offset of the first off-curve point. -->
+ <xs:attribute name="x1" type="Sk:Float"/>
+ <!-- @attribute x2 The x offset of the second off-curve point. -->
+ <xs:attribute name="x2" type="Sk:Float"/>
+ <!-- @attribute x3 The x offset of the final on-curve point. -->
+ <xs:attribute name="x3" type="Sk:Float"/>
+ <!-- @attribute y1 The y offset of the first off-curve point. -->
+ <xs:attribute name="y1" type="Sk:Float"/>
+ <!-- @attribute y2 The y offset of the second off-curve point. -->
+ <xs:attribute name="y2" type="Sk:Float"/>
+ <!-- @attribute y3 The y offset of the final on-curve point. -->
+ <xs:attribute name="y3" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rLineTo
+ RLineTo adds a line from the last point in a path to the specified point. The specified
+ point is relative to the last point in the path.
+ */ -->
+ <xs:element name="rLineTo">
+ <xs:complexType>
+ <!-- @attribute x The final path x coordinate. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The final path y coordinate. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rMoveTo
+ RMoveTo specifies the first point in a path contour. The specified
+ point is relative to the last point in the path.
+ */ -->
+ <xs:element name="rMoveTo">
+ <xs:complexType>
+ <!-- @attribute x The point's x coordinate. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The point's y coordinate. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rQuadTo
+ RQuadTo adds a quadratic curve to a path. The quadratic
+ points are relative to the last point in the path.
+ */ -->
+ <xs:element name="rQuadTo">
+ <xs:complexType>
+ <!-- @attribute x1 The x position of the off-curve point. -->
+ <xs:attribute name="x1" type="Sk:Float"/>
+ <!-- @attribute x2 The x position of the final point. -->
+ <xs:attribute name="x2" type="Sk:Float"/>
+ <!-- @attribute y1 The y position of the off-curve point. -->
+ <xs:attribute name="y1" type="Sk:Float"/>
+ <!-- @attribute y2 The y position of the final point. -->
+ <xs:attribute name="y2" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** radialGradient
+ RadialGradient sets the paint shader to ramp between two or more colors in concentric circles.
+ */ -->
+ <xs:element name="radialGradient">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:matrix"/>
+ </xs:choice>
+ <!-- @attribute matrix Matrix applies a 3x3 transform to the gradient. -->
+ <xs:attribute name="matrix" type="Sk:Matrix"/>
+ <!-- @attribute tileMode One of @pattern. @patternDescription -->
+ <xs:attribute name="tileMode" type="Sk:TileMode"/>
+ <!-- @attribute center The center point of the radial gradient. -->
+ <xs:attribute name="center" type="Sk:Point"/>
+ <!-- @attribute offsets An optional array of values used to bias the colors. The first entry
+ in the array must be 0.0, the last must be 1.0, and intermediate values must ascend. -->
+ <xs:attribute name="offsets" type="Sk:FloatArray"/>
+ <!-- @attribute radius The distance from the first color to the last color. -->
+ <xs:attribute name="radius" type="Sk:Float"/>
+ <!-- @attribute unitMapper A script that returns the mapping for [0,1] for the gradient.
+ The script can use the predefined variable 'unit' to compute the mapping. For instance,
+ "unit*unit" squares the value (while still keeping it in the range of [0,1].) The computed number
+ is pinned to from 0 to 1 after the script is executed. -->
+ <xs:attribute name="unitMapper" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** random
+ Random generates a random number, from min to max. Each time the random attribute is
+ read, a new random number is generated.
+ */ -->
+ <xs:element name="random">
+ <xs:complexType>
+ <!-- @attribute blend The random bias from 0.0 to 1.0.
+ 0.0 biias the number towards the start and end of the range.
+ 1.0 (the default) generates a linear distribution.-->
+ <xs:attribute name="blend" type="Sk:Float"/>
+ <!-- @attribute max The largest value to generate. -->
+ <xs:attribute name="max" type="Sk:Float"/>
+ <!-- @attribute min The smallest value to generate. -->
+ <xs:attribute name="min" type="Sk:Float"/>
+ <!-- @attribute random The generated value. -->
+ <xs:attribute name="random" type="Sk:Float"/>
+ <!-- @attribute seed The random seed. Identical seeds generate the same series of
+ numbers. -->
+ <xs:attribute name="seed" type="Sk:Int"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rect
+ Rect describes a bounding box.
+ The width and height attribute compute the rectangle's right and bottom edges when the rectangle
+ description is first seen. Animating the rectangle's left or top will not recompute the right or bottom
+ if the width or height have been specified.
+ */ -->
+ <xs:element name="rect">
+ <xs:complexType>
+ <!-- @attribute bottom The bottom edge of the rectangle. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute height The height of the rectangle. Setting height computes the
+ bottom attribute from the top attribute. -->
+ <xs:attribute name="height" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the rectangle. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute needsRedraw Set to true if last draw was visible. -->
+ <xs:attribute name="needsRedraw" type="Sk:Boolean"/>
+ <!-- @attribute right The right edge of the rectangle. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the rectangle. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute width The width of the rectangle. -->
+ <xs:attribute name="width" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rectToRect
+ RectToRect adds a matrix to map one rectangle's coordinates to another.
+ */ -->
+ <xs:element name="rectToRect">
+ <xs:complexType>
+ <xs:choice maxOccurs="2">
+ <xs:element ref="Sk:rect"/>
+ </xs:choice>
+ <!-- @attribute source The rectangle to map from. -->
+ <xs:attribute name="source" type="Sk:rect"/>
+ <!-- @attribute destination The rectangle to map to. -->
+ <xs:attribute name="destination" type="Sk:rect"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** remove
+ Remove an item from the display list.
+ If where is specified, the first occurance of where in the display list is removed.
+ If offset and where are specified, the element at where plus offset is removed.
+ A positive offset without where removes the element at the start of the list plus offset.
+ A negative offset without where removes the element at the end of the list minus offset.
+ */ -->
+ <xs:element name="remove">
+ <xs:complexType>
+ <!-- @attribute delete If true, reverse the action of apply's attribute mode="create".
+ (Experimental.) -->
+ <xs:attribute name="delete" type="Sk:Boolean"/>
+ <!-- @attribute offset The destination position using the rules listed above. -->
+ <xs:attribute name="offset" type="Sk:Int"/>
+ <!-- @attribute where The ID of the first display list entry to remove. -->
+ <xs:attribute name="where" type="Sk:Drawable"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** replace
+ Replace an item in the display list.
+ If where is specified, the first occurance of where in the display list is replaced by use.
+ If offset and where are specified, the element at where plus offset is replaced by use.
+ A positive offset without where replaces the element at the start of the list plus offset.
+ A negative offset without where replaces the element at the end of the list minus offset.
+ */ -->
+ <xs:element name="replace">
+ <xs:complexType>
+ <!-- @attribute mode Has no effect. -->
+ <xs:attribute name="mode" type="Sk:AddMode"/>
+ <!-- @attribute offset The destination position using the rules listed above. -->
+ <xs:attribute name="offset" type="Sk:Int"/>
+ <!-- @attribute use The element to be added to the display list.. -->
+ <xs:attribute name="use" type="Sk:Drawable"/>
+ <!-- @attribute where The ID of the first display list entry to remove. -->
+ <xs:attribute name="where" type="Sk:Drawable"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** rotate
+ Rotate creates a matrix that rotates a unit vector about a center point, and concatenated
+ with the containing matrix.
+ */ -->
+ <xs:element name="rotate">
+ <xs:complexType>
+ <!-- @attribute center A point the rotation is centered about; by default, [0.0, 0.0]. -->
+ <xs:attribute name="center" type="Sk:Point"/>
+ <!-- @attribute degrees The rotation in degrees. -->
+ <xs:attribute name="degrees" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** roundRect
+ RoundRect creates a rectangle with rounded corners. The rounded corners are specified by
+ two axes, which describe an quarter-section of the oval which is used in each corner.
+ The width and height attribute compute the rectangle's right and bottom edges when the rectangle
+ description is first seen. Animating the rectangle's left or top will not recompute the right or bottom
+ if the width or height have been specified.
+ */ -->
+ <xs:element name="roundRect">
+ <xs:complexType>
+ <!-- @attribute bottom The bottom edge of the rectangle. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute height The height of the rectangle. Setting height computes the
+ bottom attribute from the top attribute. -->
+ <xs:attribute name="height" type="Sk:Float"/>
+ <!-- @attribute left The left edge of the rectangle. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute needsRedraw Set to true if last draw was visible. -->
+ <xs:attribute name="needsRedraw" type="Sk:Boolean"/>
+ <!-- @attribute right The right edge of the rectangle. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute top The top edge of the rectangle. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute rx The radius of the corners on the x axis. -->
+ <xs:attribute name="rx" type="Sk:Float"/>
+ <!-- @attribute ry The radius of the corners on the y axis. -->
+ <xs:attribute name="ry" type="Sk:Float"/>
+ <!-- @attribute width The width of the rectangle. Setting width computes the
+ right attribute from the left attribute. -->
+ <xs:attribute name="width" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** save
+ The save element collects a series of elements into a group. The state of the paint and
+ canvas are saved, so that edits to the paint and canvas within the group are restored
+ to their original value at the end of the group.
+ The save element can be referenced
+ or defined within elements, like apply, which operate on any kind of element. Groups
+ may contain groups.
+ */ -->
+ <xs:element name="save">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded">
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:array"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:boolean"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:drawTo"/>
+ <xs:element ref="Sk:float"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:hitClear"/>
+ <xs:element ref="Sk:hitTest"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:int"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:post"/>
+ <xs:element ref="Sk:random"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:set"/>
+ <xs:element ref="Sk:snapshot"/>
+ <xs:element ref="Sk:string"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute condition If present and zero, the contained elements are ignored. -->
+ <xs:attribute name="condition" type="Sk:DynamicString"/>
+ <!-- @attribute enableCondition If present and zero, the contained elements are ignored
+ when enabled. -->
+ <xs:attribute name="enableCondition" type="Sk:DynamicString"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** scale
+ Scale creates a matrix that scales a unit vector about a center point, and concatenated
+ with the containing matrix.
+ */ -->
+ <xs:element name="scale">
+ <xs:complexType>
+ <!-- @attribute center A point the scale is centered about; by default, [0.0, 0.0]. -->
+ <xs:attribute name="center" type="Sk:Point"/>
+ <!-- @attribute x The factor all x values are scaled by; by default, 1.0. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The factor all y values are scaled by; by default, 1.0. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** screenplay
+ Screenplay contains all events and elements referenced by the events.
+ A document may only contain a single screenplay element.
+ */ -->
+ <xs:element name="screenplay">
+ <xs:complexType>
+ <xs:choice maxOccurs="unbounded" >
+ <xs:element ref="Sk:add"/>
+ <xs:element ref="Sk:apply"/>
+ <xs:element ref="Sk:array"/>
+ <xs:element ref="Sk:bitmap"/>
+ <xs:element ref="Sk:boolean"/>
+ <xs:element ref="Sk:bounds"/>
+ <!-- <xs:element ref="Sk3D:camera"/> -->
+ <xs:element ref="Sk:clear"/>
+ <xs:element ref="Sk:clip"/>
+ <xs:element ref="Sk:color"/>
+ <xs:element ref="Sk:drawTo"/>
+ <xs:element ref="Sk:event"/>
+ <xs:element ref="Sk:float"/>
+ <xs:element ref="Sk:full"/>
+ <xs:element ref="Sk:group"/>
+ <xs:element ref="Sk:hitClear"/>
+ <xs:element ref="Sk:hitTest"/>
+ <xs:element ref="Sk:image"/>
+ <xs:element ref="Sk:include"/>
+ <xs:element ref="Sk:int"/>
+ <xs:element ref="Sk:line"/>
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:move"/>
+ <xs:element ref="Sk:movie"/>
+ <xs:element ref="Sk:oval"/>
+ <xs:element ref="Sk:paint"/>
+ <!-- <xs:element ref="Sk:patch"/> -->
+ <xs:element ref="Sk:path"/>
+ <xs:element ref="Sk:point"/>
+ <xs:element ref="Sk:polygon"/>
+ <xs:element ref="Sk:polyline"/>
+ <xs:element ref="Sk:post"/>
+ <xs:element ref="Sk:random"/>
+ <xs:element ref="Sk:rect"/>
+ <xs:element ref="Sk:remove"/>
+ <xs:element ref="Sk:replace"/>
+ <xs:element ref="Sk:roundRect"/>
+ <xs:element ref="Sk:save"/>
+ <xs:element ref="Sk:set"/>
+ <xs:element ref="Sk:snapshot"/>
+ <xs:element ref="Sk:string"/>
+ <xs:element ref="Sk:text"/>
+ <xs:element ref="Sk:textBox"/>
+ <xs:element ref="Sk:textOnPath"/>
+ <xs:element ref="Sk:textToPath"/>
+ </xs:choice>
+ <!-- @attribute time The time of the draw (readable from script; not part of the document XML) -->
+ <xs:attribute name="time" type="Sk:MSec"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** set
+ Set animates the target element's attribute directly to the specified value.
+ */ -->
+ <xs:element name="set">
+ <xs:complexType>
+ <!-- @attribute begin An optional offset that must elapse before the animation begins. The apply
+ begin attribute is added to any animator's begin attribute. -->
+ <xs:attribute name="begin" type="Sk:MSec"/>
+ <!-- @attribute dur The duration of the animation in milliseconds. -->
+ <xs:attribute name="dur" type="Sk:MSec"/>
+ <!-- @attribute dynamic If true, restart the animation if any of the simple values the
+ 'lval' or 'to' attributes reference are changed. Simple values are contained by the array, boolean, float, int,
+ and string elements. -->
+ <!-- @attribute dynamic [Depreciated.] -->
+ <xs:attribute name="dynamic" type="Sk:Boolean" />
+ <!-- @attribute field The attribute to animate. -->
+ <xs:attribute name="field" type="Sk:String"/>
+ <!-- @attribute formula A script to execute over time to compute the field's value. Typically,
+ the fomula is a script expression which includes a reference to the time attribute of the
+ containing apply element. -->
+ <xs:attribute name="formula" type="Sk:DynamicString"/>
+ <!-- @attribute lval An expression evaluating to the attribute to animate.
+ If present, lval overrides 'field'. The expression is typically an array element,
+ e.g. lval="x[y]" . -->
+ <xs:attribute name="lval" type="Sk:DynamicString"/>
+ <!-- @attribute reset If true, the computed value is the initial value after the
+ animation is complete. If false, or by default, the computed value is the final value
+ after the animation is complete. -->
+ <xs:attribute name="reset" type="Sk:Boolean"/>
+ <!-- @attribute step When apply's attribute mode="immediate" or "create", the step attribute can be read by
+ script to determine the current animation iteration. -->
+ <xs:attribute name="step" type="Sk:Int" />
+ <!-- @attribute target The element to animate. By default, the element contained by the apply
+ or referenced by the apply's scope attribute is the animate target. -->
+ <xs:attribute name="target" type="Sk:DynamicString"/>
+ <!-- @attribute to The ending value (requires a 'from' attribute) -->
+ <xs:attribute name="to" type="Sk:DynamicString"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** skew
+ Skew creates a matrix that skews a unit vector about a center point, and concatenated
+ with the containing matrix.
+ */ -->
+ <xs:element name="skew">
+ <xs:complexType>
+ <!-- @attribute center A point the skew is centered about; by default, [0.0, 0.0]. -->
+ <xs:attribute name="center" type="Sk:Point"/>
+ <!-- @attribute x The factor all x values are skewed by; by default, 0.0. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The factor all y values are skewed by; by default, 0.0. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** snapshot
+ Snapshot creates an image file containing the display list.
+ */ -->
+ <xs:element name="snapshot">
+ <xs:complexType>
+ <!-- @attribute filename The name of the file to generate. -->
+ <xs:attribute name="filename" type="Sk:String"/>
+ <!-- @attribute quality The quality of the image, from 0 to 100. -->
+ <xs:attribute name="quality" type="Sk:Float"/>
+ <!-- @attribute sequence Set to true to number the filenames sequentially. -->
+ <xs:attribute name="sequence" type="Sk:Boolean"/>
+ <!-- @attribute type One of @pattern. The type of encoding to use. -->
+ <xs:attribute name="type" type="Sk:BitmapEncoding"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** string
+ String contains an array of characters.
+ */ -->
+ <xs:element name="string" >
+ <xs:complexType>
+ <!-- @attribute length The number of characters in the string (read only). -->
+ <xs:attribute name="length" type="Sk:Int"/>
+ <!-- @attribute slice An ECMAScript compatible function that returns part of the string. -->
+ <xs:attribute name="slice" type="Sk:String"/>
+ <!-- @attribute value The string itself. -->
+ <xs:attribute name="value" type="Sk:String"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** text
+ A drawable string with a position.
+ */ -->
+ <xs:element name="text">
+ <xs:complexType>
+ <!-- @attribute length The number of characters in the string (read only). -->
+ <xs:attribute name="length" type="Sk:Int"/>
+ <!-- @attribute text The string itself. -->
+ <xs:attribute name="text" type="Sk:String"/>
+ <!-- @attribute x The x coordinate of the string. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The y coordinate of the string. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** textBox
+ A drawable string fit into a box.
+ */ -->
+ <xs:element name="textBox" >
+ <xs:complexType>
+ <!-- @attribute bottom The bottom of the box. -->
+ <xs:attribute name="bottom" type="Sk:Float"/>
+ <!-- @attribute height The height of the box, computed from top and bottom. -->
+ <xs:attribute name="height" type="Sk:Float"/>
+ <!-- @attribute left The left side of the box. -->
+ <xs:attribute name="left" type="Sk:Float"/>
+ <!-- @attribute mode One of @pattern. -->
+ <xs:attribute name="mode" type="Sk:TextBoxMode"/>
+ <!-- @attribute needsRedraw Set to true if last draw was visible. -->
+ <xs:attribute name="needsRedraw" type="Sk:Boolean"/>
+ <!-- @attribute right The right side of the box. -->
+ <xs:attribute name="right" type="Sk:Float"/>
+ <!-- @attribute spacingAdd The extra spacing between lines. -->
+ <xs:attribute name="spacingAdd" type="Sk:Float"/>
+ <!-- @attribute spacingAlign One of @pattern. -->
+ <xs:attribute name="spacingAlign" type="Sk:TextBoxAlign"/>
+ <!-- @attribute spacingMul The line spacing scaled by the text height. -->
+ <xs:attribute name="spacingMul" type="Sk:Float"/>
+ <!-- @attribute text The text to fit to the box. -->
+ <xs:attribute name="text" type="Sk:String"/>
+ <!-- @attribute top The top of the box. -->
+ <xs:attribute name="top" type="Sk:Float"/>
+ <!-- @attribute width The width of the box, computed from left and right. -->
+ <xs:attribute name="width" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** textOnPath
+ TextOnPath specifies the baseline for a string of text with a path.
+ */ -->
+ <xs:element name="textOnPath">
+ <xs:complexType>
+ <xs:choice >
+ <xs:element ref="Sk:text" minOccurs="0" />
+ <xs:element ref="Sk:path" minOccurs="0" />
+ </xs:choice>
+ <!-- @attribute offset The distance along the path to place the first text character. -->
+ <xs:attribute name="offset" type="Sk:Float"/>
+ <!-- @attribute path The baseline of the text. -->
+ <xs:attribute name="path" type="Sk:Path"/>
+ <!-- @attribute text The text to place along the path. -->
+ <xs:attribute name="text" type="Sk:Text"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** textToPath
+ TextToPath sets the path to the contours described by the text's glyphs, using the current paint.
+ */ -->
+ <xs:element name="textToPath">
+ <xs:complexType>
+ <xs:choice >
+ <xs:element ref="Sk:text" minOccurs="0" />
+ <xs:element ref="Sk:paint" minOccurs="0" />
+ <xs:element ref="Sk:path" minOccurs="0" />
+ </xs:choice>
+ <!-- @attribute paint The paint selects the text font, size and other text properties. -->
+ <xs:attribute name="paint" type="Sk:Paint"/>
+ <!-- @attribute path The reference to the path element where the text as path is stored. -->
+ <xs:attribute name="path" type="Sk:Path"/>
+ <!-- @attribute text The reference to the text element to turn into a path. -->
+ <xs:attribute name="text" type="Sk:Text"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** translate
+ Translate concatenates a translation-only matrix onto the current matrix.
+ */ -->
+ <xs:element name="translate">
+ <xs:complexType>
+ <!-- @attribute x The translation in x. -->
+ <xs:attribute name="x" type="Sk:Float"/>
+ <!-- @attribute y The translation in y. -->
+ <xs:attribute name="y" type="Sk:Float"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** transparentShader
+ TransparentShader uses the background for its paint. Works well with emboss.
+ */ -->
+ <xs:element name="transparentShader">
+ <xs:complexType>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- /** typeface
+ Typeface describes the text font.
+ */ -->
+ <xs:element name="typeface">
+ <xs:complexType>
+ <!-- @attribute fontName The name of the font. -->
+ <xs:attribute name="fontName" type="Sk:String"/>
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
+
diff --git a/animator/SkAnimateSchema.xsx b/animator/SkAnimateSchema.xsx
new file mode 100644
index 00000000..ceb7d890
--- /dev/null
+++ b/animator/SkAnimateSchema.xsx
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--This file is auto-generated by the XML Schema Designer. It holds layout information for components on the designer surface.-->
+<XSDDesignerLayout />
diff --git a/animator/SkAnimateSet.cpp b/animator/SkAnimateSet.cpp
new file mode 100644
index 00000000..f153b16b
--- /dev/null
+++ b/animator/SkAnimateSet.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * 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 "SkAnimateSet.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateProperties.h"
+#include "SkParse.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkSet::fInfo[] = {
+ SK_MEMBER(begin, MSec),
+ SK_MEMBER(dur, MSec),
+ SK_MEMBER_PROPERTY(dynamic, Boolean),
+ SK_MEMBER(field, String),
+// SK_MEMBER(formula, DynamicString),
+ SK_MEMBER(lval, DynamicString),
+// SK_MEMBER_PROPERTY(reset, Boolean),
+ SK_MEMBER_PROPERTY(step, Int),
+ SK_MEMBER(target, DynamicString),
+ SK_MEMBER(to, DynamicString)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkSet);
+
+SkSet::SkSet() {
+ dur = 1;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkSet::dump(SkAnimateMaker* maker) {
+ INHERITED::dump(maker);
+ if (dur != 1) {
+ SkDebugf("dur=\"%g\" ", SkScalarToFloat(SkScalarDiv(dur,1000)));
+ }
+ //don't want double />\n's
+ SkDebugf("/>\n");
+
+}
+#endif
+
+void SkSet::refresh(SkAnimateMaker& maker) {
+ fFieldInfo->setValue(maker, &fValues, 0, fFieldInfo->fCount, NULL,
+ fFieldInfo->getType(), to);
+}
+
+void SkSet::onEndElement(SkAnimateMaker& maker) {
+ if (resolveCommon(maker) == false)
+ return;
+ if (fFieldInfo == NULL) {
+ maker.setErrorCode(SkDisplayXMLParserError::kFieldNotInTarget);
+ return;
+ }
+ fReset = dur != 1;
+ SkDisplayTypes outType = fFieldInfo->getType();
+ int comps = outType == SkType_String || outType == SkType_DynamicString ? 1 :
+ fFieldInfo->getSize((const SkDisplayable*) fTarget) / sizeof(int);
+ if (fValues.getType() == SkType_Unknown) {
+ fValues.setType(outType);
+ fValues.setCount(comps);
+ if (outType == SkType_String || outType == SkType_DynamicString)
+ fValues[0].fString = SkNEW(SkString);
+ else
+ memset(fValues.begin(), 0, fValues.count() * sizeof(fValues.begin()[0]));
+ } else {
+ SkASSERT(fValues.getType() == outType);
+ if (fFieldInfo->fType == SkType_Array)
+ comps = fValues.count();
+ else {
+ SkASSERT(fValues.count() == comps);
+ }
+ }
+ if (formula.size() > 0) {
+ comps = 1;
+ outType = SkType_MSec;
+ }
+ fFieldInfo->setValue(maker, &fValues, fFieldOffset, comps, this, outType, formula.size() > 0 ? formula : to);
+ fComponents = fValues.count();
+}
diff --git a/animator/SkAnimateSet.h b/animator/SkAnimateSet.h
new file mode 100644
index 00000000..b8f7bf54
--- /dev/null
+++ b/animator/SkAnimateSet.h
@@ -0,0 +1,27 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimateSet_DEFINED
+#define SkAnimateSet_DEFINED
+
+#include "SkAnimate.h"
+
+class SkSet : public SkAnimate {
+ DECLARE_MEMBER_INFO(Set);
+ SkSet();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual void refresh(SkAnimateMaker& );
+private:
+ typedef SkAnimate INHERITED;
+};
+
+#endif // SkAnimateSet_DEFINED
diff --git a/animator/SkAnimator.cpp b/animator/SkAnimator.cpp
new file mode 100644
index 00000000..1c53e30a
--- /dev/null
+++ b/animator/SkAnimator.cpp
@@ -0,0 +1,706 @@
+
+/*
+ * 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 "SkAnimator.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkDisplayApply.h"
+#include "SkDisplayMovie.h"
+#include "SkDisplayTypes.h"
+#include "SkDisplayXMLParser.h"
+#include "SkStream.h"
+#include "SkScript.h"
+#include "SkScript2.h" // compiled script experiment
+#include "SkSystemEventTypes.h"
+#include "SkTypedArray.h"
+#ifdef SK_BUILD_FOR_ANDROID
+#include "SkDrawExtraPathEffect.h"
+#endif
+#ifdef SK_DEBUG
+#include "SkTime.h"
+#endif
+
+#if defined SK_BUILD_FOR_WIN32 && defined SK_DEBUG
+ #define _static
+ extern const char gMathPrimerText[];
+ extern const char gMathPrimerBinary[];
+#else
+ #define _static static
+#endif
+
+_static const char gMathPrimerText[] =
+"<screenplay>"
+ "<Math id=\"Math\"/>"
+ "<Number id=\"Number\"/>"
+"</screenplay>";
+
+#define gMathPrimer gMathPrimerText
+
+SkAnimator::SkAnimator() : fMaker(NULL) {
+ initialize();
+}
+
+SkAnimator::~SkAnimator() {
+ SkDELETE(fMaker);
+}
+
+void SkAnimator::addExtras(SkExtras* extras) {
+ *fMaker->fExtras.append() = extras;
+}
+
+bool SkAnimator::appendStream(SkStream* stream) {
+ return decodeStream(stream);
+}
+
+bool SkAnimator::decodeMemory(const void* buffer, size_t size)
+{
+ fMaker->fFileName.reset();
+ SkDisplayXMLParser parser(*fMaker);
+ return parser.parse((const char*)buffer, size);
+}
+
+bool SkAnimator::decodeStream(SkStream* stream)
+{
+ SkDisplayXMLParser parser(*fMaker);
+ bool result = parser.parse(*stream);
+ fMaker->setErrorString();
+ return result;
+}
+
+bool SkAnimator::decodeDOM(const SkDOM& dom, const SkDOMNode* node)
+{
+ fMaker->fFileName.reset();
+ SkDisplayXMLParser parser(*fMaker);
+ return parser.parse(dom, node);
+}
+
+bool SkAnimator::decodeURI(const char uri[]) {
+// SkDebugf("animator decode %s\n", uri);
+
+// SkStream* stream = SkStream::GetURIStream(fMaker->fPrefix.c_str(), uri);
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(uri));
+ if (stream.get()) {
+ this->setURIBase(uri);
+ return decodeStream(stream);
+ } else {
+ return false;
+ }
+}
+
+bool SkAnimator::doCharEvent(SkUnichar code) {
+ if (code == 0)
+ return false;
+ struct SkEventState state;
+ state.fCode = code;
+ fMaker->fEnableTime = fMaker->getAppTime();
+ bool result = fMaker->fEvents.doEvent(*fMaker, SkDisplayEvent::kKeyChar, &state);
+ fMaker->notifyInval();
+ return result;
+}
+
+bool SkAnimator::doClickEvent(int clickState, SkScalar x, SkScalar y) {
+ SkASSERT(clickState >= 0 && clickState <= 2);
+ struct SkEventState state;
+ state.fX = x;
+ state.fY = y;
+ fMaker->fEnableTime = fMaker->getAppTime();
+ bool result = fMaker->fEvents.doEvent(*fMaker,
+ clickState == 0 ? SkDisplayEvent::kMouseDown :
+ clickState == 1 ? SkDisplayEvent::kMouseDrag :
+ SkDisplayEvent::kMouseUp, &state);
+ fMaker->notifyInval();
+ return result;
+}
+
+bool SkAnimator::doKeyEvent(SkKey code) {
+ if (code == 0)
+ return false;
+ struct SkEventState state;
+ state.fCode = code;
+ fMaker->fEnableTime = fMaker->getAppTime();
+ bool result = fMaker->fEvents.doEvent(*fMaker, SkDisplayEvent::kKeyPress, &state);
+ fMaker->notifyInval();
+ return result;
+}
+
+bool SkAnimator::doKeyUpEvent(SkKey code) {
+ if (code == 0)
+ return false;
+ struct SkEventState state;
+ state.fCode = code;
+ fMaker->fEnableTime = fMaker->getAppTime();
+ bool result = fMaker->fEvents.doEvent(*fMaker, SkDisplayEvent::kKeyPressUp, &state);
+ fMaker->notifyInval();
+ return result;
+}
+
+bool SkAnimator::doUserEvent(const SkEvent& evt) {
+ fMaker->fEnableTime = fMaker->getAppTime();
+ return onEvent(evt);
+}
+
+SkAnimator::DifferenceType SkAnimator::draw(SkCanvas* canvas, SkPaint* paint, SkMSec time) {
+ if (paint == NULL)
+ return draw(canvas, time);
+ fMaker->fScreenplay.time = time;
+ fMaker->fCanvas = canvas;
+ fMaker->fPaint = paint;
+ fMaker->fDisplayList.fHasUnion = false;
+ int result = fMaker->fDisplayList.draw(*fMaker, time);
+ if (result)
+ result += fMaker->fDisplayList.fHasUnion;
+ return (DifferenceType) result;
+}
+
+SkAnimator::DifferenceType SkAnimator::draw(SkCanvas* canvas, SkMSec time) {
+ SkPaint paint;
+ return draw(canvas, &paint, time);
+}
+
+#ifdef SK_DEBUG
+void SkAnimator::eventDone(const SkEvent& ) {
+}
+#endif
+
+bool SkAnimator::findClickEvent(SkScalar x, SkScalar y) {
+ struct SkEventState state;
+ state.fDisable = true;
+ state.fX = x;
+ state.fY = y;
+ fMaker->fEnableTime = fMaker->getAppTime();
+ bool result = fMaker->fEvents.doEvent(*fMaker, SkDisplayEvent::kMouseDown, &state);
+ fMaker->notifyInval();
+ return result;
+}
+
+const SkAnimator* SkAnimator::getAnimator(const SkDisplayable* displayable) const {
+ if (displayable->getType() != SkType_Movie)
+ return NULL;
+ const SkDisplayMovie* movie = (const SkDisplayMovie*) displayable;
+ return movie->getAnimator();
+}
+
+const SkDisplayable* SkAnimator::getElement(const char* id) {
+ SkDisplayable* element;
+ if (fMaker->find(id, &element) == false)
+ return NULL;
+ return (const SkDisplayable*) element;
+}
+
+SkElementType SkAnimator::getElementType(const SkDisplayable* ae) {
+ SkDisplayable* element = (SkDisplayable*) ae;
+ const SkMemberInfo* info = SkDisplayType::GetMembers(fMaker, element->getType(), NULL);
+ return (SkElementType) SkDisplayType::Find(fMaker, info);
+}
+
+SkElementType SkAnimator::getElementType(const char* id) {
+ const SkDisplayable* element = getElement(id);
+ return getElementType(element);
+}
+
+const SkMemberInfo* SkAnimator::getField(const SkDisplayable* ae, const char* field) {
+ SkDisplayable* element = (SkDisplayable*) ae;
+ const SkMemberInfo* info = element->getMember(field);
+ return (const SkMemberInfo*) info;
+}
+
+const SkMemberInfo* SkAnimator::getField(const char* elementID, const char* field) {
+ const SkDisplayable* element = getElement(elementID);
+ return getField(element, field);
+}
+
+SkFieldType SkAnimator::getFieldType(const SkMemberInfo* ai) {
+ const SkMemberInfo* info = (const SkMemberInfo*) ai;
+ return (SkFieldType) info->getType();
+}
+
+SkFieldType SkAnimator::getFieldType(const char* id, const char* fieldID) {
+ const SkMemberInfo* field = getField(id, fieldID);
+ return getFieldType(field);
+}
+
+static bool getArrayCommon(const SkDisplayable* ae, const SkMemberInfo* ai,
+ int index, SkOperand* operand) {
+ const SkDisplayable* element = (const SkDisplayable*) ae;
+ const SkMemberInfo* info = (const SkMemberInfo*) ai;
+ SkASSERT(info->fType == SkType_Array);
+ return info->getArrayValue(element, index, operand);
+}
+
+int32_t SkAnimator::getArrayInt(const SkDisplayable* ae,
+ const SkMemberInfo* ai, int index) {
+ SkOperand operand;
+ bool result = getArrayCommon(ae, ai, index, &operand);
+ return result ? operand.fS32 : SK_NaN32;
+}
+
+int32_t SkAnimator::getArrayInt(const char* id, const char* fieldID, int index) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return SK_NaN32;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return SK_NaN32;
+ return getArrayInt(element, field, index);
+}
+
+SkScalar SkAnimator::getArrayScalar(const SkDisplayable* ae,
+ const SkMemberInfo* ai, int index) {
+ SkOperand operand;
+ bool result = getArrayCommon(ae, ai, index, &operand);
+ return result ? operand.fScalar : SK_ScalarNaN;
+}
+
+SkScalar SkAnimator::getArrayScalar(const char* id, const char* fieldID, int index) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return SK_ScalarNaN;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return SK_ScalarNaN;
+ return getArrayScalar(element, field, index);
+}
+
+const char* SkAnimator::getArrayString(const SkDisplayable* ae,
+ const SkMemberInfo* ai, int index) {
+ SkOperand operand;
+ bool result = getArrayCommon(ae, ai, index, &operand);
+ return result ? operand.fString->c_str() : NULL;
+}
+
+const char* SkAnimator::getArrayString(const char* id, const char* fieldID, int index) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return NULL;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return NULL;
+ return getArrayString(element, field, index);
+}
+
+SkMSec SkAnimator::getInterval() {
+ return fMaker->fMinimumInterval == (SkMSec) -1 ? 0 : fMaker->fMinimumInterval;
+}
+
+void SkAnimator::getInvalBounds(SkRect* inval) {
+ if (fMaker->fDisplayList.fHasUnion) {
+ inval->fLeft = SkIntToScalar(fMaker->fDisplayList.fInvalBounds.fLeft);
+ inval->fTop = SkIntToScalar(fMaker->fDisplayList.fInvalBounds.fTop);
+ inval->fRight = SkIntToScalar(fMaker->fDisplayList.fInvalBounds.fRight);
+ inval->fBottom = SkIntToScalar(fMaker->fDisplayList.fInvalBounds.fBottom);
+ } else {
+ inval->fLeft = inval->fTop = -SK_ScalarMax;
+ inval->fRight = inval->fBottom = SK_ScalarMax;
+ }
+}
+
+const SkXMLParserError* SkAnimator::getParserError() {
+ return &fMaker->fError;
+}
+
+const char* SkAnimator::getParserErrorString() {
+ if (fMaker->fErrorString.size() == 0 && fMaker->fError.hasError())
+ fMaker->setErrorString();
+ return fMaker->fErrorString.c_str();
+}
+
+int32_t SkAnimator::getInt(const SkDisplayable* element, const SkMemberInfo* info) {
+ if (info->fType != SkType_MemberProperty) {
+ SkOperand operand;
+ if (info->getType() == SkType_Int) {
+ info->getValue(element, &operand, 1);
+ return operand.fS32;
+ }
+ return SK_NaN32;
+ }
+ SkScriptValue scriptValue;
+ bool success = element->getProperty(info->propertyIndex(), &scriptValue);
+ if (success && scriptValue.fType == SkType_Int)
+ return scriptValue.fOperand.fS32;
+ return SK_NaN32;
+}
+
+int32_t SkAnimator::getInt(const char* id, const char* fieldID) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return SK_NaN32;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return SK_NaN32;
+ return getInt(element, field);
+}
+
+SkScalar SkAnimator::getScalar(const SkDisplayable* element, const SkMemberInfo* info) {
+ if (info->fType != SkType_MemberProperty) {
+ SkOperand operand;
+ if (info->getType() == SkType_Float) {
+ info->getValue(element, &operand, 1);
+ return operand.fScalar;
+ }
+ return SK_ScalarNaN;
+ }
+ SkScriptValue scriptValue;
+ bool success = element->getProperty(info->propertyIndex(), &scriptValue);
+ if (success && scriptValue.fType == SkType_Float)
+ return scriptValue.fOperand.fScalar;
+ return SK_ScalarNaN;
+}
+
+SkScalar SkAnimator::getScalar(const char* id, const char* fieldID) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return SK_ScalarNaN;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return SK_ScalarNaN;
+ return getScalar(element, field);
+}
+
+const char* SkAnimator::getString(const SkDisplayable* ae,
+ const SkMemberInfo* ai) {
+ const SkDisplayable* element = (const SkDisplayable*) ae;
+ const SkMemberInfo* info = (const SkMemberInfo*) ai;
+ SkString* temp;
+ info->getString(element, &temp);
+ return temp->c_str();
+}
+
+const char* SkAnimator::getString(const char* id, const char* fieldID) {
+ const SkDisplayable* element = getElement(id);
+ if (element == NULL)
+ return NULL;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return NULL;
+ return getString(element, field);
+}
+
+const char* SkAnimator::getURIBase() {
+ return fMaker->fPrefix.c_str();
+}
+
+void SkAnimator::initialize() {
+ SkDELETE(fMaker);
+ fMaker = SkNEW_ARGS(SkAnimateMaker, (this, NULL, NULL));
+ decodeMemory(gMathPrimer, sizeof(gMathPrimer)-1);
+#ifdef SK_BUILD_FOR_ANDROID
+ InitializeSkExtraPathEffects(this);
+#endif
+}
+
+
+#ifdef SK_DEBUG
+bool SkAnimator::isTrackingEvents() {
+ return false;
+}
+#endif
+
+bool SkAnimator::onEvent(const SkEvent& evt) {
+#ifdef SK_DEBUG
+ SkAnimator* root = fMaker->getRoot();
+ if (root == NULL)
+ root = this;
+ if (root->isTrackingEvents())
+ root->eventDone(evt);
+#endif
+ if (evt.isType(SK_EventType_OnEnd)) {
+ SkEventState eventState;
+ SkDEBUGCODE(bool success =) evt.findPtr("anim", (void**) &eventState.fDisplayable);
+ SkASSERT(success);
+ SkDEBUGCODE(success =) evt.findS32("time", (int32_t*) &fMaker->fEnableTime);
+ SkASSERT(success);
+ fMaker->fAdjustedStart = fMaker->getAppTime() - fMaker->fEnableTime;
+ fMaker->fEvents.doEvent(*fMaker, SkDisplayEvent::kOnEnd, &eventState);
+ fMaker->fAdjustedStart = 0;
+ goto inval;
+ }
+ if (evt.isType(SK_EventType_Delay)) {
+ fMaker->doDelayedEvent();
+ goto inval;
+ }
+ {
+ const char* id = evt.findString("id");
+ if (id == NULL)
+ return false;
+ SkDisplayable** firstMovie = fMaker->fMovies.begin();
+ SkDisplayable** endMovie = fMaker->fMovies.end();
+ for (SkDisplayable** ptr = firstMovie; ptr < endMovie; ptr++) {
+ SkDisplayMovie* movie = (SkDisplayMovie*) *ptr;
+ movie->doEvent(evt);
+ }
+ {
+ SkDisplayable* event;
+ if (fMaker->find(id, &event) == false)
+ return false;
+ #if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkString debugOut;
+ SkMSec realTime = fMaker->getAppTime();
+ debugOut.appendS32(realTime - fMaker->fDebugTimeBase);
+ debugOut.append(" onEvent id=");
+ debugOut.append(id);
+ #endif
+ SkMSec time = evt.getFast32();
+ if (time != 0) {
+ SkMSec app = fMaker->getAppTime();
+ fMaker->setEnableTime(app, time);
+ #if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ debugOut.append(" time=");
+ debugOut.appendS32(time - fMaker->fDebugTimeBase);
+ debugOut.append(" adjust=");
+ debugOut.appendS32(fMaker->fAdjustedStart);
+ #endif
+ }
+ #if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkDebugf("%s\n", debugOut.c_str());
+ #endif
+ SkASSERT(event->isEvent());
+ SkDisplayEvent* displayEvent = (SkDisplayEvent*) event;
+ displayEvent->populateInput(*fMaker, evt);
+ displayEvent->enableEvent(*fMaker);
+ }
+ }
+inval:
+ fMaker->notifyInval();
+ return true;
+}
+
+void SkAnimator::onEventPost(SkEvent* evt, SkEventSinkID sinkID)
+{
+#ifdef SK_DEBUG
+ SkAnimator* root = fMaker->getRoot();
+ if (root) {
+ root->onEventPost(evt, sinkID);
+ return;
+ }
+#else
+ SkASSERT(sinkID == this->getSinkID() || this->getHostEventSinkID() == sinkID);
+#endif
+ evt->setTargetID(sinkID)->post();
+}
+
+void SkAnimator::onEventPostTime(SkEvent* evt, SkEventSinkID sinkID, SkMSec time)
+{
+#ifdef SK_DEBUG
+ SkAnimator* root = fMaker->getRoot();
+ if (root) {
+ root->onEventPostTime(evt, sinkID, time);
+ return;
+ }
+#else
+ SkASSERT(sinkID == this->getSinkID() || this->getHostEventSinkID() == sinkID);
+#endif
+ evt->setTargetID(sinkID)->postTime(time);
+}
+
+void SkAnimator::reset() {
+ fMaker->fDisplayList.reset();
+}
+
+SkEventSinkID SkAnimator::getHostEventSinkID() const {
+ return fMaker->fHostEventSinkID;
+}
+
+void SkAnimator::setHostEventSinkID(SkEventSinkID target) {
+ fMaker->fHostEventSinkID = target;
+}
+
+void SkAnimator::onSetHostHandler(Handler ) {
+}
+
+void SkAnimator::setJavaOwner(Handler ) {
+}
+
+bool SkAnimator::setArrayString(const char* id, const char* fieldID, const char** array, int num)
+{
+ SkTypedArray tArray(SkType_String);
+ tArray.setCount(num);
+ for (int i = 0; i < num; i++) {
+ SkOperand op;
+ op.fString = new SkString(array[i]);
+ tArray[i] = op;
+ }
+ return setArray(id, fieldID, tArray);
+}
+bool SkAnimator::setArrayInt(const char* id, const char* fieldID, const int* array, int num)
+{
+ SkTypedArray tArray(SkType_Int);
+ tArray.setCount(num);
+ for (int i = 0; i < num; i++) {
+ SkOperand op;
+ op.fS32 = array[i];
+ tArray[i] = op;
+ }
+ return setArray(id, fieldID, tArray);
+}
+
+bool SkAnimator::setArray(SkDisplayable* element, const SkMemberInfo* info, SkTypedArray array) {
+ if (info->fType != SkType_Array)
+ return false; //the field is not an array
+ //i think we can handle the case where the displayable itself is an array differently from the
+ //case where it has an array - for one thing, if it is an array, i think we can change its type
+ //if it's not, we cannot
+ SkDisplayTypes type = element->getType();
+ if (type == SkType_Array) {
+ SkDisplayArray* dispArray = (SkDisplayArray*) element;
+ dispArray->values = array;
+ return true;
+ }
+ else
+ return false; //currently i don't care about this case
+}
+
+bool SkAnimator::setArray(const char* id, const char* fieldID, SkTypedArray array) {
+ SkDisplayable* element = (SkDisplayable*) getElement(id);
+ //should I go ahead and change all 'NULL's to 'NULL'?
+ if (element == NULL)
+ return false;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return false;
+ return setArray(element, field, array);
+}
+
+bool SkAnimator::setInt(SkDisplayable* element, const SkMemberInfo* info, int32_t s32) {
+ if (info->fType != SkType_MemberProperty) {
+ SkOperand operand;
+ operand.fS32 = s32;
+ SkASSERT(info->getType() == SkType_Int);
+ info->setValue(element, &operand, 1);
+ } else {
+ SkScriptValue scriptValue;
+ scriptValue.fType = SkType_Int;
+ scriptValue.fOperand.fS32 = s32;
+ element->setProperty(info->propertyIndex(), scriptValue);
+ }
+ return true;
+}
+
+bool SkAnimator::setInt(const char* id, const char* fieldID, int32_t s32) {
+ SkDisplayable* element = (SkDisplayable*) getElement(id);
+ if (element == NULL)
+ return false;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return false;
+ return setInt(element, field, s32);
+}
+
+bool SkAnimator::setScalar(SkDisplayable* element, const SkMemberInfo* info, SkScalar scalar) {
+ if (info->fType != SkType_MemberProperty) {
+ SkOperand operand;
+ operand.fScalar = scalar;
+ SkASSERT(info->getType() == SkType_Float);
+ info->setValue(element, &operand, 1);
+ } else {
+ SkScriptValue scriptValue;
+ scriptValue.fType = SkType_Float;
+ scriptValue.fOperand.fScalar = scalar;
+ element->setProperty(info->propertyIndex(), scriptValue);
+ }
+ return true;
+}
+
+bool SkAnimator::setScalar(const char* id, const char* fieldID, SkScalar scalar) {
+ SkDisplayable* element = (SkDisplayable*) getElement(id);
+ if (element == NULL)
+ return false;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return false;
+ return setScalar(element, field, scalar);
+}
+
+bool SkAnimator::setString(SkDisplayable* element,
+ const SkMemberInfo* info, const char* str) {
+ // !!! until this is fixed, can't call script with global references from here
+ info->setValue(*fMaker, NULL, 0, info->fCount, element, info->getType(), str, strlen(str));
+ return true;
+}
+
+bool SkAnimator::setString(const char* id, const char* fieldID, const char* str) {
+ SkDisplayable* element = (SkDisplayable*) getElement(id);
+ if (element == NULL)
+ return false;
+ const SkMemberInfo* field = getField(element, fieldID);
+ if (field == NULL)
+ return false;
+ return setString(element, field, str);
+}
+
+void SkAnimator::setTimeline(const Timeline& timeline) {
+ fMaker->fTimeline = &timeline;
+}
+
+void SkAnimator::setURIBase(const char* uri) {
+ if (uri)
+ {
+ const char* tail = strrchr(uri, '/');
+ if (tail) {
+ SkString prefix(uri, tail - uri + 1);
+ if (uri[0] != '.' /*SkStream::IsAbsoluteURI(uri)*/)
+ fMaker->fPrefix.reset();
+ fMaker->fPrefix.append(prefix);
+ fMaker->fFileName.set(tail + 1);
+ } else
+ fMaker->fFileName.set(uri);
+ }
+}
+
+#ifdef SK_DEBUG
+bool SkAnimator::NoLeaks() {
+#ifdef SK_BUILD_FOR_MAC
+ if (SkDisplayable::fAllocations.count() == 0)
+ return true;
+// return SkDisplayable::fAllocationCount == 0;
+ SkDebugf("!!! leaked %d displayables:\n", SkDisplayable::fAllocations.count());
+ for (SkDisplayable** leak = SkDisplayable::fAllocations.begin(); leak < SkDisplayable::fAllocations.end(); leak++)
+ SkDebugf("%08x %s\n", *leak, (*leak)->id);
+#endif
+ return false;
+}
+#endif
+
+#ifdef SK_SUPPORT_UNITTEST
+#include "SkAnimatorScript.h"
+#include "SkBase64.h"
+#include "SkParse.h"
+#include "SkMemberInfo.h"
+
+#define unittestline(type) { #type , type::UnitTest }
+#endif
+
+
+#ifdef SK_SUPPORT_UNITTEST
+void SkAnimator::Init(bool runUnitTests) {
+ if (runUnitTests == false)
+ return;
+ static const struct {
+ const char* fTypeName;
+ void (*fUnitTest)( );
+ } gUnitTests[] = {
+ unittestline(SkBase64),
+ unittestline(SkDisplayType),
+ unittestline(SkParse),
+ unittestline(SkScriptEngine),
+// unittestline(SkScriptEngine2), // compiled script experiment
+ unittestline(SkAnimatorScript)
+ };
+ for (int i = 0; i < (int)SK_ARRAY_COUNT(gUnitTests); i++)
+ {
+ SkDebugf("SkAnimator: Running UnitTest for %s\n", gUnitTests[i].fTypeName);
+ gUnitTests[i].fUnitTest();
+ SkDebugf("SkAnimator: End UnitTest for %s\n", gUnitTests[i].fTypeName);
+ }
+}
+#else
+void SkAnimator::Init(bool) {}
+#endif
+
+void SkAnimator::Term() {
+}
diff --git a/animator/SkAnimatorScript.cpp b/animator/SkAnimatorScript.cpp
new file mode 100644
index 00000000..df2e5637
--- /dev/null
+++ b/animator/SkAnimatorScript.cpp
@@ -0,0 +1,594 @@
+
+/*
+ * 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 "SkAnimatorScript.h"
+#include "SkAnimateBase.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayTypes.h"
+#include "SkExtras.h"
+#include "SkMemberInfo.h"
+#include "SkParse.h"
+
+static const SkDisplayEnumMap gEnumMaps[] = {
+ { SkType_AddMode, "indirect|immediate" },
+ { SkType_Align, "left|center|right" },
+ { SkType_ApplyMode, "create|immediate|once" },
+ { SkType_ApplyTransition, "normal|reverse" },
+ { SkType_BitmapEncoding, "jpeg|png" },
+ { SkType_BitmapFormat, "none|A1|A8|Index8|RGB16|RGB32" },
+ { SkType_Boolean, "false|true" },
+ { SkType_Cap, "butt|round|square" },
+ { SkType_EventCode, "none|leftSoftKey|rightSoftKey|home|back|send|end|key0|key1|key2|key3|key4|key5|key6|key7|key8|key9|star|hash|up|down|left|right|OK|volUp|volDown|camera" },
+ { SkType_EventKind, "none|keyChar|keyPress|keyPressUp|mouseDown|mouseDrag|mouseMove|mouseUp|onEnd|onLoad|user" },
+ { SkType_EventMode, "deferred|immediate" },
+ { SkType_FillType, "winding|evenOdd" },
+ { SkType_FilterType, "none|bilinear" },
+ { SkType_FontStyle, "normal|bold|italic|boldItalic" },
+ { SkType_FromPathMode, "normal|angle|position" },
+ { SkType_Join, "miter|round|blunt" },
+ { SkType_MaskFilterBlurStyle, "normal|solid|outer|inner" },
+ { SkType_PathDirection, "cw|ccw" },
+ { SkType_Style, "fill|stroke|strokeAndFill" },
+ { SkType_TextBoxAlign, "start|center|end" },
+ { SkType_TextBoxMode, "oneLine|lineBreak" },
+ { SkType_TileMode, "clamp|repeat|mirror" },
+ { SkType_Xfermode, "clear|src|dst|srcOver|dstOver|srcIn|dstIn|srcOut|dstOut|"
+ "srcATop|dstATop|xor|darken|lighten" },
+};
+
+static int gEnumMapCount = SK_ARRAY_COUNT(gEnumMaps);
+
+SkAnimatorScript::SkAnimatorScript(SkAnimateMaker& maker, SkDisplayable* working, SkDisplayTypes type)
+ : SkScriptEngine(SkScriptEngine::ToOpType(type)), fMaker(maker), fParent(NULL), fWorking(working)
+{
+ memberCallBack(EvalMember, (void*) this);
+ memberFunctionCallBack(EvalMemberFunction, (void*) this);
+ boxCallBack(Box, (void*) this);
+ unboxCallBack(Unbox, (void*) &maker);
+ propertyCallBack(EvalID, (void*) this); // must be first (entries are prepended, will be last), since it never fails
+ propertyCallBack(Infinity, (void*) this);
+ propertyCallBack(NaN, (void*) this);
+ functionCallBack(Eval, (void*) this);
+ functionCallBack(IsFinite, (void*) this);
+ functionCallBack(IsNaN, (void*) this);
+ if (type == SkType_ARGB) {
+ functionCallBack(EvalRGB, (void*) this);
+ propertyCallBack(EvalNamedColor, (void*) &maker.fIDs);
+ }
+ if (SkDisplayType::IsEnum(&maker, type)) {
+ // !!! for SpiderMonkey, iterate through the enum values, and map them to globals
+ const SkDisplayEnumMap& map = GetEnumValues(type);
+ propertyCallBack(EvalEnum, (void*) map.fValues);
+ }
+ for (SkExtras** extraPtr = maker.fExtras.begin(); extraPtr < maker.fExtras.end(); extraPtr++) {
+ SkExtras* extra = *extraPtr;
+ if (extra->fExtraCallBack)
+ propertyCallBack(extra->fExtraCallBack, extra->fExtraStorage);
+ }
+}
+
+SkAnimatorScript::~SkAnimatorScript() {
+ for (SkDisplayable** dispPtr = fTrackDisplayable.begin(); dispPtr < fTrackDisplayable.end(); dispPtr++)
+ delete *dispPtr;
+}
+
+bool SkAnimatorScript::evaluate(const char* original, SkScriptValue* result, SkDisplayTypes type) {
+ const char* script = original;
+ bool success = evaluateScript(&script, result);
+ if (success == false || result->fType != type) {
+ fMaker.setScriptError(*this);
+ return false;
+ }
+ return true;
+}
+
+bool SkAnimatorScript::Box(void* user, SkScriptValue* scriptValue) {
+ SkAnimatorScript* engine = (SkAnimatorScript*) user;
+ SkDisplayTypes type = scriptValue->fType;
+ SkDisplayable* displayable;
+ switch (type) {
+ case SkType_Array: {
+ SkDisplayArray* boxedValue = new SkDisplayArray(*scriptValue->fOperand.fArray);
+ displayable = boxedValue;
+ } break;
+ case SkType_Boolean: {
+ SkDisplayBoolean* boxedValue = new SkDisplayBoolean;
+ displayable = boxedValue;
+ boxedValue->value = !! scriptValue->fOperand.fS32;
+ } break;
+ case SkType_Int: {
+ SkDisplayInt* boxedValue = new SkDisplayInt;
+ displayable = boxedValue;
+ boxedValue->value = scriptValue->fOperand.fS32;
+ } break;
+ case SkType_Float: {
+ SkDisplayFloat* boxedValue = new SkDisplayFloat;
+ displayable = boxedValue;
+ boxedValue->value = scriptValue->fOperand.fScalar;
+ } break;
+ case SkType_String: {
+ SkDisplayString* boxedValue = new SkDisplayString(*scriptValue->fOperand.fString);
+ displayable = boxedValue;
+ } break;
+ case SkType_Displayable:
+ scriptValue->fOperand.fObject = scriptValue->fOperand.fDisplayable;
+ scriptValue->fType = SkType_Displayable;
+ return true;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ engine->track(displayable);
+ scriptValue->fOperand.fObject = displayable;
+ scriptValue->fType = SkType_Displayable;
+ return true;
+}
+
+bool SkAnimatorScript::Eval(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* eng, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("eval", function, len) == false)
+ return false;
+ if (params.count() != 1)
+ return false;
+ SkAnimatorScript* host = (SkAnimatorScript*) eng;
+ SkAnimatorScript engine(host->fMaker, host->fWorking, SkScriptEngine::ToDisplayType(host->fReturnType));
+ SkScriptValue* scriptValue = params.begin();
+ bool success = true;
+ if (scriptValue->fType == SkType_String) {
+ const char* script = scriptValue->fOperand.fString->c_str();
+ success = engine.evaluateScript(&script, value);
+ } else
+ *value = *scriptValue;
+ return success;
+}
+
+bool SkAnimatorScript::EvalEnum(const char* token, size_t len, void* callBack, SkScriptValue* value) {
+ const char* tokens = (const char*) callBack;
+ value->fType = SkType_Int;
+ if (MapEnums(tokens, token, len, (int*)&value->fOperand.fS32))
+ return true;
+ return false;
+}
+
+bool SkAnimatorScript::EvalID(const char* token, size_t len, void* user, SkScriptValue* value) {
+ SkAnimatorScript* engine = (SkAnimatorScript*) user;
+ SkTDict<SkDisplayable*>* ids = &engine->fMaker.fIDs;
+ SkDisplayable* displayable;
+ bool success = ids->find(token, len, &displayable);
+ if (success == false) {
+ displayable = engine->fWorking;
+ if (SK_LITERAL_STR_EQUAL("parent", token, len)) {
+ SkDisplayable* parent = displayable->getParent();
+ if (parent == NULL)
+ parent = engine->fParent;
+ if (parent) {
+ value->fOperand.fDisplayable = parent;
+ value->fType = SkType_Displayable;
+ return true;
+ }
+ }
+ if (displayable && EvalMember(token, len, displayable, engine, value))
+ return true;
+ value->fOperand.fString = NULL;
+ value->fType = SkType_String;
+ } else {
+ SkDisplayable* working = engine->fWorking;
+ value->fOperand.fDisplayable = displayable;
+ value->fType = SkType_Displayable;
+ if (displayable->canContainDependents() && working && working->isAnimate()) {
+ SkAnimateBase* animator = (SkAnimateBase*) working;
+ if (animator->isDynamic()) {
+ SkDisplayDepend* depend = (SkDisplayDepend* ) displayable;
+ depend->addDependent(working);
+ }
+ }
+ }
+ return true;
+}
+
+bool SkAnimatorScript::EvalNamedColor(const char* token, size_t len, void* callback, SkScriptValue* value) {
+ value->fType = SkType_Int;
+ if (SkParse::FindNamedColor(token, len, (SkColor*) &value->fOperand.fS32) != NULL)
+ return true;
+ return false;
+}
+
+bool SkAnimatorScript::EvalRGB(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* eng, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("rgb", function, len) == false)
+ return false;
+ if (params.count() != 3)
+ return false;
+ SkScriptEngine* engine = (SkScriptEngine*) eng;
+ unsigned result = 0xFF000000;
+ int shift = 16;
+ for (SkScriptValue* valuePtr = params.begin(); valuePtr < params.end(); valuePtr++) {
+ engine->convertTo(SkType_Int, valuePtr);
+ result |= SkClampMax(valuePtr->fOperand.fS32, 255) << shift;
+ shift -= 8;
+ }
+ value->fOperand.fS32 = result;
+ value->fType = SkType_Int;
+ return true;
+}
+
+bool SkAnimatorScript::EvalMemberCommon(SkScriptEngine* engine, const SkMemberInfo* info,
+ SkDisplayable* displayable, SkScriptValue* value) {
+ SkDisplayTypes original;
+ SkDisplayTypes type = original = (SkDisplayTypes) info->getType();
+ if (info->fType == SkType_Array)
+ type = SkType_Array;
+ switch (type) {
+ case SkType_ARGB:
+ type = SkType_Int;
+ case SkType_Boolean:
+ case SkType_Int:
+ case SkType_MSec:
+ case SkType_Float:
+ SkASSERT(info->getCount() == 1);
+ if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction)
+ value->fOperand.fS32 = *(int32_t*) info->memberData(displayable); // OK for SkScalar too
+ if (type == SkType_MSec) {
+ value->fOperand.fScalar = SkScalarDiv((SkScalar) value->fOperand.fS32, 1000); // dividing two ints is the same as dividing two scalars
+ type = SkType_Float;
+ }
+ break;
+ case SkType_String: {
+ SkString* displayableString;
+ if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction) {
+ info->getString(displayable, &displayableString);
+ value->fOperand.fString = new SkString(*displayableString);
+ }
+ } break;
+ case SkType_Array: {
+ SkASSERT(info->fType != SkType_MemberProperty); // !!! incomplete
+ SkTDOperandArray* displayableArray = (SkTDOperandArray*) info->memberData(displayable);
+ if (displayable->getType() == SkType_Array) {
+ SkDisplayArray* typedArray = (SkDisplayArray*) displayable;
+ original = typedArray->values.getType();
+ }
+ SkASSERT(original != SkType_Unknown);
+ SkTypedArray* array = value->fOperand.fArray = new SkTypedArray(original);
+ engine->track(array);
+ int count = displayableArray->count();
+ if (count > 0) {
+ array->setCount(count);
+ memcpy(array->begin(), displayableArray->begin(), count * sizeof(SkOperand));
+ }
+ } break;
+ default:
+ SkASSERT(0); // unimplemented
+ }
+ value->fType = type;
+ return true;
+}
+
+bool SkAnimatorScript::EvalMember(const char* member, size_t len, void* object, void* eng,
+ SkScriptValue* value) {
+ SkScriptEngine* engine = (SkScriptEngine*) eng;
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ SkString name(member, len);
+ SkDisplayable* named = displayable->contains(name);
+ if (named) {
+ value->fOperand.fDisplayable = named;
+ value->fType = SkType_Displayable;
+ return true;
+ }
+ const SkMemberInfo* info = displayable->getMember(name.c_str());
+ if (info == NULL)
+ return false;
+ if (info->fType == SkType_MemberProperty) {
+ if (displayable->getProperty(info->propertyIndex(), value) == false) {
+ SkASSERT(0);
+ return false;
+ }
+ }
+ return EvalMemberCommon(engine, info, displayable, value);
+}
+
+bool SkAnimatorScript::EvalMemberFunction(const char* member, size_t len, void* object,
+ SkTDArray<SkScriptValue>& params, void* eng, SkScriptValue* value) {
+ SkScriptEngine* engine = (SkScriptEngine*) eng;
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ SkString name(member, len);
+ const SkMemberInfo* info = displayable->getMember(name.c_str());
+ SkASSERT(info != NULL); /* !!! error handling unimplemented */
+ if (info->fType != SkType_MemberFunction) {
+ SkASSERT(0);
+ return false;
+ }
+ displayable->executeFunction(displayable, info->functionIndex(), params, info->getType(),
+ value);
+ return EvalMemberCommon(engine, info, displayable, value);
+}
+
+bool SkAnimatorScript::EvaluateDisplayable(SkAnimateMaker& maker, SkDisplayable* displayable, const char* script, SkDisplayable** result) {
+ SkAnimatorScript engine(maker, displayable, SkType_Displayable);
+ SkScriptValue value;
+ bool success = engine.evaluate(script, &value, SkType_Displayable);
+ if (success)
+ *result = value.fOperand.fDisplayable;
+ return success;
+}
+
+bool SkAnimatorScript::EvaluateInt(SkAnimateMaker& maker, SkDisplayable* displayable, const char* script, int32_t* result) {
+ SkAnimatorScript engine(maker, displayable, SkType_Int);
+ SkScriptValue value;
+ bool success = engine.evaluate(script, &value, SkType_Int);
+ if (success)
+ *result = value.fOperand.fS32;
+ return success;
+}
+
+bool SkAnimatorScript::EvaluateFloat(SkAnimateMaker& maker, SkDisplayable* displayable, const char* script, SkScalar* result) {
+ SkAnimatorScript engine(maker, displayable, SkType_Float);
+ SkScriptValue value;
+ bool success = engine.evaluate(script, &value, SkType_Float);
+ if (success)
+ *result = value.fOperand.fScalar;
+ return success;
+}
+
+bool SkAnimatorScript::EvaluateString(SkAnimateMaker& maker, SkDisplayable* displayable, const char* script, SkString* result) {
+ SkAnimatorScript engine(maker, displayable, SkType_String);
+ SkScriptValue value;
+ bool success = engine.evaluate(script, &value, SkType_String);
+ if (success)
+ result->set(*(value.fOperand.fString));
+ return success;
+}
+
+bool SkAnimatorScript::EvaluateString(SkAnimateMaker& maker, SkDisplayable* displayable, SkDisplayable* parent, const char* script, SkString* result) {
+ SkAnimatorScript engine(maker, displayable, SkType_String);
+ engine.fParent = parent;
+ SkScriptValue value;
+ bool success = engine.evaluate(script, &value, SkType_String);
+ if (success)
+ result->set(*(value.fOperand.fString));
+ return success;
+}
+
+const SkDisplayEnumMap& SkAnimatorScript::GetEnumValues(SkDisplayTypes type) {
+ int index = SkTSearch<SkDisplayTypes>(&gEnumMaps[0].fType, gEnumMapCount, type,
+ sizeof(SkDisplayEnumMap));
+ SkASSERT(index >= 0);
+ return gEnumMaps[index];
+}
+
+bool SkAnimatorScript::Infinity(const char* token, size_t len, void* user, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("Infinity", token, len) == false)
+ return false;
+ value->fType = SkType_Float;
+ value->fOperand.fScalar = SK_ScalarInfinity;
+ return true;
+}
+
+bool SkAnimatorScript::IsFinite(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* eng, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL(function, "isFinite", len) == false)
+ return false;
+ if (params.count() != 1)
+ return false;
+ SkScriptValue* scriptValue = params.begin();
+ SkDisplayTypes type = scriptValue->fType;
+ SkScalar scalar = scriptValue->fOperand.fScalar;
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = type == SkType_Float ? SkScalarIsNaN(scalar) == false &&
+ SkScalarAbs(scalar) != SK_ScalarInfinity : type == SkType_Int;
+ return true;
+}
+
+bool SkAnimatorScript::IsNaN(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* eng, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("isNaN", function, len) == false)
+ return false;
+ if (params.count() != 1)
+ return false;
+ SkScriptValue* scriptValue = params.begin();
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = scriptValue->fType == SkType_Float ? SkScalarIsNaN(scriptValue->fOperand.fScalar) : 0;
+ return true;
+}
+
+bool SkAnimatorScript::MapEnums(const char* ptr, const char* match, size_t len, int* value) {
+ int index = 0;
+ bool more = true;
+ do {
+ const char* last = strchr(ptr, '|');
+ if (last == NULL) {
+ last = &ptr[strlen(ptr)];
+ more = false;
+ }
+ size_t length = last - ptr;
+ if (len == length && strncmp(ptr, match, length) == 0) {
+ *value = index;
+ return true;
+ }
+ index++;
+ ptr = last + 1;
+ } while (more);
+ return false;
+}
+
+bool SkAnimatorScript::NaN(const char* token, size_t len, void* user, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("NaN", token, len) == false)
+ return false;
+ value->fType = SkType_Float;
+ value->fOperand.fScalar = SK_ScalarNaN;
+ return true;
+}
+
+#if 0
+bool SkAnimatorScript::ObjectToString(void* object, void* user, SkScriptValue* value) {
+ SkTDict<SkDisplayable*>* ids = (SkTDict<SkDisplayable*>*) user;
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ const char* key;
+ bool success = ids->findKey(displayable, &key);
+ if (success == false)
+ return false;
+ value->fOperand.fString = new SkString(key);
+ value->fType = SkType_String;
+ return true;
+}
+#endif
+
+bool SkAnimatorScript::Unbox(void* m, SkScriptValue* scriptValue) {
+ SkAnimateMaker* maker = (SkAnimateMaker*) m;
+ SkASSERT((unsigned) scriptValue->fType == (unsigned) SkType_Displayable);
+ SkDisplayable* displayable = (SkDisplayable*) scriptValue->fOperand.fObject;
+ SkDisplayTypes type = displayable->getType();
+ switch (displayable->getType()) {
+ case SkType_Array: {
+ SkDisplayArray* boxedValue = (SkDisplayArray*) displayable;
+ scriptValue->fOperand.fArray = &boxedValue->values;
+ } break;
+ case SkType_Boolean: {
+ SkDisplayBoolean* boxedValue = (SkDisplayBoolean*) displayable;
+ scriptValue->fOperand.fS32 = boxedValue->value;
+ } break;
+ case SkType_Int: {
+ SkDisplayInt* boxedValue = (SkDisplayInt*) displayable;
+ scriptValue->fOperand.fS32 = boxedValue->value;
+ } break;
+ case SkType_Float: {
+ SkDisplayFloat* boxedValue = (SkDisplayFloat*) displayable;
+ scriptValue->fOperand.fScalar = boxedValue->value;
+ } break;
+ case SkType_String: {
+ SkDisplayString* boxedValue = (SkDisplayString*) displayable;
+ scriptValue->fOperand.fString = SkNEW_ARGS(SkString, (boxedValue->value));
+ } break;
+ default: {
+ const char* id = NULL;
+ SkDEBUGCODE(bool success = ) maker->findKey(displayable, &id);
+ SkASSERT(success);
+ scriptValue->fOperand.fString = SkNEW_ARGS(SkString, (id));
+ type = SkType_String;
+ }
+ }
+ scriptValue->fType = type;
+ return true;
+}
+
+#if defined SK_SUPPORT_UNITTEST
+
+#include "SkAnimator.h"
+
+static const char scriptTestSetup[] =
+"<screenplay>\n"
+ "<text id='label' text='defg'/>\n"
+ "<add id='addLabel' use='label'/>\n"
+ "<text id='text1' text='test'/>\n"
+ "<apply scope='addLabel'>\n"
+ "<set target='label' field='text' to='#script:text1.text'/>\n"
+ "</apply>\n"
+ "<apply>\n"
+ "<paint id='labelPaint'>\n"
+ "<emboss id='emboss' direction='[1,1,1]' />\n"
+ "</paint>\n"
+ "<animate id='animation' field='direction' target='emboss' from='[1,1,1]' to='[-1,1,1]' dur='1'/>\n"
+ "<set lval='direction[0]' target='emboss' to='-1' />\n"
+ "</apply>\n"
+ "<color id='testColor' color='0 ? rgb(0,0,0) : rgb(255,255,255)' />\n"
+ "<color id='xColor' color='rgb(12,34,56)' />\n"
+ "<array id='emptyArray' />\n"
+ "<array id='intArray' values='[1, 4, 6]' />\n"
+ "<int id='idx' value='2' />\n"
+ "<int id='idy' value='2' />\n"
+ "<string id='alpha' value='abc' />\n"
+ "<rect id='testRect' left='Math.cos(0)' top='2' right='12' bottom='5' />\n"
+ "<event id='evt'>\n"
+ "<input name='x' />\n"
+ "<apply scope='idy'>\n"
+ "<set field='value' to='evt.x.int' />\n"
+ "</apply>\n"
+ "</event>\n"
+"</screenplay>";
+
+#define DEFAULT_ANSWER , 0
+
+static const SkScriptNAnswer scriptTests[] = {
+ { "label.text.length == 4", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+// { "labelPaint.measureText(label.text) > 0 ? labelPaint.measureText(label.text)+10 : 40", SkType_Float, 0, SkIntToScalar(0x23) },
+ { "Number.POSITIVE_INFINITY >= Number.MAX_VALUE ? 1 : 0", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "Infinity >= Number.MAX_VALUE ? 1 : 0", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "Number.NEGATIVE_INFINITY <= -Number.MAX_VALUE ? 1 : 0", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "Number.MIN_VALUE > 0 ? 1 : 0", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "isNaN(Number.NaN)", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "isNaN(NaN)", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "Math.sin(0)", SkType_Float, 0, SkIntToScalar(0) DEFAULT_ANSWER },
+ { "alpha+alpha", SkType_String, 0, 0, "abcabc" },
+ { "intArray[4]", SkType_Unknown DEFAULT_ANSWER DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "emptyArray[4]", SkType_Unknown DEFAULT_ANSWER DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "idx", SkType_Int, 2 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "intArray.length", SkType_Int, 3 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "intArray.values[0]", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "intArray[0]", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "idx.value", SkType_Int, 2 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "alpha.value", SkType_String, 0, 0, "abc" },
+ { "alpha", SkType_String, 0, 0, "abc" },
+ { "alpha.value+alpha.value", SkType_String, 0, 0, "abcabc" },
+ { "alpha+idx", SkType_String, 0, 0, "abc2" },
+ { "idx+alpha", SkType_String, 0, 0, "2abc" },
+ { "intArray[idx]", SkType_Int, 6 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "alpha.slice(1,2)", SkType_String, 0, 0, "b" },
+ { "alpha.value.slice(1,2)", SkType_String, 0, 0, "b" },
+ { "testRect.left+2", SkType_Float, 0, SkIntToScalar(3) DEFAULT_ANSWER },
+ { "0 ? Math.sin(0) : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? intArray[0] : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? intArray.values[0] : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? idx : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? idx.value : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? alpha.slice(1,2) : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "0 ? alpha.value.slice(1,2) : 1", SkType_Int, 1 DEFAULT_ANSWER DEFAULT_ANSWER },
+ { "idy", SkType_Int, 3 DEFAULT_ANSWER DEFAULT_ANSWER }
+};
+
+#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
+
+void SkAnimatorScript::UnitTest() {
+#if defined(SK_SUPPORT_UNITTEST)
+ SkAnimator animator;
+ SkASSERT(animator.decodeMemory(scriptTestSetup, sizeof(scriptTestSetup)-1));
+ SkEvent evt;
+ evt.setString("id", "evt");
+ evt.setS32("x", 3);
+ animator.doUserEvent(evt);
+ // set up animator with memory script above, then run value tests
+ for (unsigned index = 0; index < SkScriptNAnswer_testCount; index++) {
+ SkAnimatorScript engine(*animator.fMaker, NULL, scriptTests[index].fType);
+ SkScriptValue value;
+ const char* script = scriptTests[index].fScript;
+ bool success = engine.evaluateScript(&script, &value);
+ if (success == false) {
+ SkDebugf("script failed: %s\n", scriptTests[index].fScript);
+ SkASSERT(scriptTests[index].fType == SkType_Unknown);
+ continue;
+ }
+ SkASSERT(value.fType == scriptTests[index].fType);
+ SkScalar error;
+ switch (value.fType) {
+ case SkType_Int:
+ SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
+ break;
+ case SkType_Float:
+ error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
+ SkASSERT(error < SK_Scalar1 / 10000);
+ break;
+ case SkType_String:
+ SkASSERT(strcmp(value.fOperand.fString->c_str(), scriptTests[index].fStringAnswer) == 0);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+#endif
+}
+
+#endif
diff --git a/animator/SkAnimatorScript.h b/animator/SkAnimatorScript.h
new file mode 100644
index 00000000..8589388e
--- /dev/null
+++ b/animator/SkAnimatorScript.h
@@ -0,0 +1,75 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAnimatorScript_DEFINED
+#define SkAnimatorScript_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkScript.h"
+#include "SkTypedArray.h"
+
+class SkAnimateMaker;
+struct SkMemberInfo;
+
+struct SkDisplayEnumMap {
+ SkDisplayTypes fType;
+ const char* fValues;
+};
+
+class SkAnimatorScript : public SkScriptEngine {
+public:
+ SkAnimatorScript(SkAnimateMaker& , SkDisplayable* , SkDisplayTypes type);
+ ~SkAnimatorScript();
+ bool evaluate(const char* script, SkScriptValue* , SkDisplayTypes type);
+ void track(SkDisplayable* displayable) {
+ SkASSERT(fTrackDisplayable.find(displayable) < 0);
+ *fTrackDisplayable.append() = displayable; }
+ static bool EvaluateDisplayable(SkAnimateMaker& , SkDisplayable* , const char* script, SkDisplayable** );
+ static bool EvaluateFloat(SkAnimateMaker& , SkDisplayable* , const char* script, SkScalar* );
+ static bool EvaluateInt(SkAnimateMaker& , SkDisplayable* , const char* script, int32_t* );
+ static bool EvaluateString(SkAnimateMaker& , SkDisplayable* , const char* script, SkString* );
+ static bool EvaluateString(SkAnimateMaker& , SkDisplayable* , SkDisplayable* parent, const char* script, SkString* );
+ static bool MapEnums(const char* ptr, const char* match, size_t len, int* value);
+protected:
+ static bool Box(void* user, SkScriptValue* );
+ static bool Eval(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* callBack, SkScriptValue* );
+ static bool EvalEnum(const char* token, size_t len, void* callBack, SkScriptValue* );
+ static bool EvalID(const char* token, size_t len, void* callBack, SkScriptValue* );
+ static bool EvalMember(const char* member, size_t len, void* object, void* eng,
+ SkScriptValue* value);
+ static bool EvalMemberCommon(SkScriptEngine* , const SkMemberInfo* info,
+ SkDisplayable* displayable, SkScriptValue* value);
+ static bool EvalMemberFunction(const char* member, size_t len, void* object,
+ SkTDArray<SkScriptValue>& params, void* user, SkScriptValue* value);
+ static bool EvalNamedColor(const char* token, size_t len, void* callBack, SkScriptValue* );
+ static bool EvalRGB(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* callBack, SkScriptValue* );
+ static const SkDisplayEnumMap& GetEnumValues(SkDisplayTypes type);
+ static bool Infinity(const char* token, size_t len, void* callBack, SkScriptValue* );
+ static bool IsFinite(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* callBack, SkScriptValue* );
+ static bool IsNaN(const char* function, size_t len, SkTDArray<SkScriptValue>& params,
+ void* callBack, SkScriptValue* );
+ static bool NaN(const char* token, size_t len, void* callBack, SkScriptValue* );
+ static bool Unbox(void* , SkScriptValue* scriptValue);
+ SkTDDisplayableArray fTrackDisplayable;
+ SkAnimateMaker& fMaker;
+ SkDisplayable* fParent;
+ SkDisplayable* fWorking;
+private:
+ friend class SkDump;
+ friend struct SkScriptNAnswer;
+#ifdef SK_SUPPORT_UNITTEST
+public:
+ static void UnitTest();
+#endif
+};
+
+#endif // SkAnimatorScript_DEFINED
diff --git a/animator/SkAnimatorScript2.cpp b/animator/SkAnimatorScript2.cpp
new file mode 100644
index 00000000..80ae0c6a
--- /dev/null
+++ b/animator/SkAnimatorScript2.cpp
@@ -0,0 +1,622 @@
+
+/*
+ * 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 "SkAnimatorScript2.h"
+#include "SkAnimateBase.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayTypes.h"
+#include "SkExtras.h"
+#include "SkMemberInfo.h"
+#include "SkOpArray.h"
+#include "SkParse.h"
+#include "SkScript2.h"
+#include "SkScriptCallBack.h"
+
+static const SkDisplayEnumMap gEnumMaps[] = {
+ { SkType_AddMode, "indirect|immediate" },
+ { SkType_Align, "left|center|right" },
+ { SkType_ApplyMode, "immediate|once" },
+ { SkType_ApplyTransition, "reverse" },
+ { SkType_BitmapEncoding, "jpeg|png" },
+ { SkType_BitmapFormat, "none|A1|A8|Index8|RGB16|RGB32" },
+ { SkType_Boolean, "false|true" },
+ { SkType_Cap, "butt|round|square" },
+ { SkType_EventCode, "none|up|down|left|right|back|end|OK|send|leftSoftKey|rightSoftKey|key0|key1|key2|key3|key4|key5|key6|key7|key8|key9|star|hash" },
+ { SkType_EventKind, "none|keyChar|keyPress|mouseDown|mouseDrag|mouseMove|mouseUp|onEnd|onLoad|user" },
+ { SkType_EventMode, "deferred|immediate" },
+ { SkType_FillType, "winding|evenOdd" },
+ { SkType_FilterType, "none|bilinear" },
+ { SkType_FromPathMode, "normal|angle|position" },
+ { SkType_Join, "miter|round|blunt" },
+ { SkType_MaskFilterBlurStyle, "normal|solid|outer|inner" },
+ { SkType_PathDirection, "cw|ccw" },
+ { SkType_Style, "fill|stroke|strokeAndFill" },
+ { SkType_TextBoxAlign, "start|center|end" },
+ { SkType_TextBoxMode, "oneLine|lineBreak" },
+ { SkType_TileMode, "clamp|repeat|mirror" },
+ { SkType_Xfermode, "clear|src|dst|srcOver|dstOver|srcIn|dstIn|srcOut|dstOut|"
+ "srcATop|dstATop|xor|darken|lighten" },
+};
+
+static int gEnumMapCount = SK_ARRAY_COUNT(gEnumMaps);
+
+
+class SkAnimatorScript_Box : public SkScriptCallBackConvert {
+public:
+ SkAnimatorScript_Box() {}
+
+ ~SkAnimatorScript_Box() {
+ for (SkDisplayable** dispPtr = fTrackDisplayable.begin(); dispPtr < fTrackDisplayable.end(); dispPtr++)
+ delete *dispPtr;
+ }
+
+ virtual bool convert(SkOperand2::OpType type, SkOperand2* operand) {
+ SkDisplayable* displayable;
+ switch (type) {
+ case SkOperand2::kArray: {
+ SkDisplayArray* boxedValue = new SkDisplayArray(*operand->fArray);
+ displayable = boxedValue;
+ } break;
+ case SkOperand2::kS32: {
+ SkDisplayInt* boxedValue = new SkDisplayInt;
+ displayable = boxedValue;
+ boxedValue->value = operand->fS32;
+ } break;
+ case SkOperand2::kScalar: {
+ SkDisplayFloat* boxedValue = new SkDisplayFloat;
+ displayable = boxedValue;
+ boxedValue->value = operand->fScalar;
+ } break;
+ case SkOperand2::kString: {
+ SkDisplayString* boxedValue = new SkDisplayString(*operand->fString);
+ displayable = boxedValue;
+ } break;
+ case SkOperand2::kObject:
+ return true;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ track(displayable);
+ operand->fObject = (void*) displayable;
+ return true;
+ }
+
+ virtual SkOperand2::OpType getReturnType(int index) {
+ return SkOperand2::kObject;
+ }
+
+ virtual Type getType() const {
+ return kBox;
+ }
+
+ void track(SkDisplayable* displayable) {
+ SkASSERT(fTrackDisplayable.find(displayable) < 0);
+ *fTrackDisplayable.append() = displayable;
+ }
+
+ SkTDDisplayableArray fTrackDisplayable;
+};
+
+
+class SkAnimatorScript_Enum : public SkScriptCallBackProperty {
+public:
+ SkAnimatorScript_Enum(const char* tokens) : fTokens(tokens) {}
+
+ virtual bool getConstValue(const char* name, int len, SkOperand2* value) {
+ return SkAnimatorScript2::MapEnums(fTokens, name, len, &value->fS32);
+ }
+
+private:
+ const char* fTokens;
+};
+
+ // !!! if type is string, call invoke
+ // if any other type, return original value
+ // distinction is undone: could do this by returning index == 0 only if param is string
+ // still, caller of getParamTypes will attempt to convert param to string (I guess)
+class SkAnimatorScript_Eval : public SkScriptCallBackFunction {
+public:
+ SkAnimatorScript_Eval(SkAnimatorScript2* engine) : fEngine(engine) {}
+
+ virtual bool getIndex(const char* name, int len, size_t* result) {
+ if (SK_LITERAL_STR_EQUAL("eval", name, len) != 0)
+ return false;
+ *result = 0;
+ return true;
+ }
+
+ virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) {
+ types->setCount(1);
+ SkOperand2::OpType* type = types->begin();
+ type[0] = SkOperand2::kString;
+ }
+
+ virtual bool invoke(size_t index, SkOpArray* params, SkOperand2* answer) {
+ SkAnimatorScript2 engine(fEngine->getMaker(), fEngine->getWorking(),
+ SkAnimatorScript2::ToDisplayType(fEngine->getReturnType()));
+ SkOperand2* op = params->begin();
+ const char* script = op->fString->c_str();
+ SkScriptValue2 value;
+ return engine.evaluateScript(&script, &value);
+ SkASSERT(value.fType == fEngine->getReturnType());
+ *answer = value.fOperand;
+ // !!! incomplete ?
+ return true;
+ }
+
+private:
+ SkAnimatorScript2* fEngine;
+};
+
+class SkAnimatorScript_ID : public SkScriptCallBackProperty {
+public:
+ SkAnimatorScript_ID(SkAnimatorScript2* engine) : fEngine(engine) {}
+
+ virtual bool getIndex(const char* token, int len, size_t* result) {
+ SkDisplayable* displayable;
+ bool success = fEngine->getMaker().find(token, len, &displayable);
+ if (success == false) {
+ *result = 0;
+ } else {
+ *result = (size_t) displayable;
+ SkDisplayable* working = fEngine->getWorking();
+ if (displayable->canContainDependents() && working && working->isAnimate()) {
+ SkAnimateBase* animator = (SkAnimateBase*) working;
+ if (animator->isDynamic()) {
+ SkDisplayDepend* depend = (SkDisplayDepend* ) displayable;
+ depend->addDependent(working);
+ }
+ }
+ }
+ return true;
+ }
+
+ virtual bool getResult(size_t ref, SkOperand2* answer) {
+ answer->fObject = (void*) ref;
+ return true;
+ }
+
+ virtual SkOperand2::OpType getReturnType(size_t index) {
+ return index == 0 ? SkOperand2::kString : SkOperand2::kObject;
+ }
+
+private:
+ SkAnimatorScript2* fEngine;
+};
+
+
+class SkAnimatorScript_Member : public SkScriptCallBackMember {
+public:
+
+ SkAnimatorScript_Member(SkAnimatorScript2* engine) : fEngine(engine) {}
+
+ bool getMemberReference(const char* member, size_t len, void* object, SkScriptValue2* ref) {
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ SkString name(member, len);
+ SkDisplayable* named = displayable->contains(name);
+ if (named) {
+ ref->fType = SkOperand2::kObject;
+ ref->fOperand.fObject = named;
+ return true;
+ }
+ const SkMemberInfo* info = displayable->getMember(name.c_str());
+ if (info == NULL)
+ return false; // !!! add additional error info?
+ ref->fType = SkAnimatorScript2::ToOpType(info->getType());
+ ref->fOperand.fObject = (void*) info;
+ return true;
+ }
+
+ bool invoke(size_t ref, void* object, SkOperand2* value) {
+ const SkMemberInfo* info = (const SkMemberInfo* ) ref;
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ if (info->fType == SkType_MemberProperty) {
+ if (displayable->getProperty2(info->propertyIndex(), value) == false) {
+ return false;
+ }
+ }
+ return fEngine->evalMemberCommon(info, displayable, value);
+ }
+
+ SkAnimatorScript2* fEngine;
+};
+
+
+class SkAnimatorScript_MemberFunction : public SkScriptCallBackMemberFunction {
+public:
+ SkAnimatorScript_MemberFunction(SkAnimatorScript2* engine) : fEngine(engine) {}
+
+ bool getMemberReference(const char* member, size_t len, void* object, SkScriptValue2* ref) {
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ SkString name(member, len);
+ const SkMemberInfo* info = displayable->getMember(name.c_str());
+ if (info == NULL || info->fType != SkType_MemberFunction)
+ return false; // !!! add additional error info?
+ ref->fType = SkAnimatorScript2::ToOpType(info->getType());
+ ref->fOperand.fObject = (void*) info;
+ return true;
+ }
+
+ virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) {
+ types->setCount(3);
+ SkOperand2::OpType* type = types->begin();
+ type[0] = type[1] = type[2] = SkOperand2::kS32;
+ }
+
+ bool invoke(size_t ref, void* object, SkOpArray* params, SkOperand2* value)
+ {
+ const SkMemberInfo* info = (const SkMemberInfo* ) ref;
+ SkDisplayable* displayable = (SkDisplayable*) object;
+ displayable->executeFunction2(displayable, info->functionIndex(), params, info->getType(),
+ value);
+ return fEngine->evalMemberCommon(info, displayable, value);
+ }
+
+ SkAnimatorScript2* fEngine;
+};
+
+
+class SkAnimatorScript_NamedColor : public SkScriptCallBackProperty {
+public:
+ virtual bool getConstValue(const char* name, int len, SkOperand2* value) {
+ return SkParse::FindNamedColor(name, len, (SkColor*) &value->fS32) != NULL;
+ }
+};
+
+
+class SkAnimatorScript_RGB : public SkScriptCallBackFunction {
+public:
+ virtual bool getIndex(const char* name, int len, size_t* result) {
+ if (SK_LITERAL_STR_EQUAL("rgb", name, len) != 0)
+ return false;
+ *result = 0;
+ return true;
+ }
+
+ virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) {
+ types->setCount(3);
+ SkOperand2::OpType* type = types->begin();
+ type[0] = type[1] = type[2] = SkOperand2::kS32;
+ }
+
+ virtual bool invoke(size_t index, SkOpArray* params, SkOperand2* answer) {
+ SkASSERT(index == 0);
+ unsigned result = 0xFF000000;
+ int shift = 16;
+ for (int index = 0; index < 3; index++) {
+ result |= SkClampMax(params->begin()[index].fS32, 255) << shift;
+ shift -= 8;
+ }
+ answer->fS32 = result;
+ return true;
+ }
+
+};
+
+
+class SkAnimatorScript_Unbox : public SkScriptCallBackConvert {
+public:
+ SkAnimatorScript_Unbox(SkAnimatorScript2* engine) : fEngine(engine) {}
+
+ virtual bool convert(SkOperand2::OpType type, SkOperand2* operand) {
+ SkASSERT(type == SkOperand2::kObject);
+ SkDisplayable* displayable = (SkDisplayable*) operand->fObject;
+ switch (displayable->getType()) {
+ case SkType_Array: {
+ SkDisplayArray* boxedValue = (SkDisplayArray*) displayable;
+ operand->fArray = new SkOpArray(SkAnimatorScript2::ToOpType(boxedValue->values.getType()));
+ int count = boxedValue->values.count();
+ operand->fArray->setCount(count);
+ memcpy(operand->fArray->begin(), boxedValue->values.begin(), count * sizeof(SkOperand2));
+ fEngine->track(operand->fArray);
+ } break;
+ case SkType_Boolean: {
+ SkDisplayBoolean* boxedValue = (SkDisplayBoolean*) displayable;
+ operand->fS32 = boxedValue->value;
+ } break;
+ case SkType_Int: {
+ SkDisplayInt* boxedValue = (SkDisplayInt*) displayable;
+ operand->fS32 = boxedValue->value;
+ } break;
+ case SkType_Float: {
+ SkDisplayFloat* boxedValue = (SkDisplayFloat*) displayable;
+ operand->fScalar = boxedValue->value;
+ } break;
+ case SkType_String: {
+ SkDisplayString* boxedValue = (SkDisplayString*) displayable;
+ operand->fString = SkNEW_ARGS(SkString, (boxedValue->value));
+ } break;
+ default: {
+ const char* id;
+ bool success = fEngine->getMaker().findKey(displayable, &id);
+ SkASSERT(success);
+ operand->fString = SkNEW_ARGS(SkString, (id));
+ }
+ }
+ return true;
+ }
+
+ virtual SkOperand2::OpType getReturnType(int /*index*/, SkOperand2* operand) {
+ SkDisplayable* displayable = (SkDisplayable*) operand->fObject;
+ switch (displayable->getType()) {
+ case SkType_Array:
+ return SkOperand2::kArray;
+ case SkType_Int:
+ return SkOperand2::kS32;
+ case SkType_Float:
+ return SkOperand2::kScalar;
+ case SkType_String:
+ default:
+ return SkOperand2::kString;
+ }
+ }
+
+ virtual Type getType() const {
+ return kUnbox;
+ }
+
+ SkAnimatorScript2* fEngine;
+};
+
+SkAnimatorScript2::SkAnimatorScript2(SkAnimateMaker& maker, SkDisplayable* working, SkDisplayTypes type) :
+ SkScriptEngine2(ToOpType(type)), fMaker(maker), fWorking(working) {
+ *fCallBackArray.append() = new SkAnimatorScript_Member(this);
+ *fCallBackArray.append() = new SkAnimatorScript_MemberFunction(this);
+ *fCallBackArray.append() = new SkAnimatorScript_Box();
+ *fCallBackArray.append() = new SkAnimatorScript_Unbox(this);
+ *fCallBackArray.append() = new SkAnimatorScript_ID(this);
+ if (type == SkType_ARGB) {
+ *fCallBackArray.append() = new SkAnimatorScript_RGB();
+ *fCallBackArray.append() = new SkAnimatorScript_NamedColor();
+ }
+ if (SkDisplayType::IsEnum(&maker, type)) {
+ // !!! for SpiderMonkey, iterate through the enum values, and map them to globals
+ const SkDisplayEnumMap& map = GetEnumValues(type);
+ *fCallBackArray.append() = new SkAnimatorScript_Enum(map.fValues);
+ }
+ *fCallBackArray.append() = new SkAnimatorScript_Eval(this);
+#if 0 // !!! no extra support for now
+ for (SkExtras** extraPtr = maker.fExtras.begin(); extraPtr < maker.fExtras.end(); extraPtr++) {
+ SkExtras* extra = *extraPtr;
+ if (extra->fExtraCallBack)
+ *fCallBackArray.append() = new propertyCallBack(extra->fExtraCallBack, extra->fExtraStorage);
+ }
+#endif
+}
+
+SkAnimatorScript2::~SkAnimatorScript2() {
+ SkScriptCallBack** end = fCallBackArray.end();
+ for (SkScriptCallBack** ptr = fCallBackArray.begin(); ptr < end; ptr++)
+ delete *ptr;
+}
+
+bool SkAnimatorScript2::evalMemberCommon(const SkMemberInfo* info,
+ SkDisplayable* displayable, SkOperand2* value) {
+ SkDisplayTypes original;
+ SkDisplayTypes type = original = (SkDisplayTypes) info->getType();
+ if (info->fType == SkType_Array)
+ type = SkType_Array;
+ switch (type) {
+ case SkType_ARGB:
+ type = SkType_Int;
+ case SkType_Boolean:
+ case SkType_Int:
+ case SkType_MSec:
+ case SkType_Float:
+ SkASSERT(info->getCount() == 1);
+ if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction)
+ value->fS32 = *(int32_t*) info->memberData(displayable); // OK for SkScalar too
+ if (type == SkType_MSec) {
+ value->fScalar = SkScalarDiv((SkScalar) value->fS32, 1000); // dividing two ints is the same as dividing two scalars
+ type = SkType_Float;
+ }
+ break;
+ case SkType_String: {
+ SkString* displayableString;
+ if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction) {
+ info->getString(displayable, &displayableString);
+ value->fString = new SkString(*displayableString);
+ }
+ } break;
+ case SkType_Array: {
+ SkASSERT(info->fType != SkType_MemberProperty); // !!! incomplete
+ SkTDOperandArray* displayableArray = (SkTDOperandArray*) info->memberData(displayable);
+ if (displayable->getType() == SkType_Array) {
+ SkDisplayArray* typedArray = (SkDisplayArray*) displayable;
+ original = typedArray->values.getType();
+ }
+ SkASSERT(original != SkType_Unknown);
+ SkOpArray* array = value->fArray = new SkOpArray(ToOpType(original));
+ track(array);
+ int count = displayableArray->count();
+ if (count > 0) {
+ array->setCount(count);
+ memcpy(array->begin(), displayableArray->begin(), count * sizeof(SkOperand2));
+ }
+ } break;
+ default:
+ SkASSERT(0); // unimplemented
+ }
+ return true;
+}
+
+const SkDisplayEnumMap& SkAnimatorScript2::GetEnumValues(SkDisplayTypes type) {
+ int index = SkTSearch<SkDisplayTypes>(&gEnumMaps[0].fType, gEnumMapCount, type,
+ sizeof(SkDisplayEnumMap));
+ SkASSERT(index >= 0);
+ return gEnumMaps[index];
+}
+
+SkDisplayTypes SkAnimatorScript2::ToDisplayType(SkOperand2::OpType type) {
+ int val = type;
+ switch (val) {
+ case SkOperand2::kNoType:
+ return SkType_Unknown;
+ case SkOperand2::kS32:
+ return SkType_Int;
+ case SkOperand2::kScalar:
+ return SkType_Float;
+ case SkOperand2::kString:
+ return SkType_String;
+ case SkOperand2::kArray:
+ return SkType_Array;
+ case SkOperand2::kObject:
+ return SkType_Displayable;
+ default:
+ SkASSERT(0);
+ return SkType_Unknown;
+ }
+}
+
+SkOperand2::OpType SkAnimatorScript2::ToOpType(SkDisplayTypes type) {
+ if (SkDisplayType::IsDisplayable(NULL /* fMaker */, type))
+ return SkOperand2::kObject;
+ if (SkDisplayType::IsEnum(NULL /* fMaker */, type))
+ return SkOperand2::kS32;
+ switch (type) {
+ case SkType_ARGB:
+ case SkType_MSec:
+ case SkType_Int:
+ return SkOperand2::kS32;
+ case SkType_Float:
+ case SkType_Point:
+ case SkType_3D_Point:
+ return SkOperand2::kScalar;
+ case SkType_Base64:
+ case SkType_DynamicString:
+ case SkType_String:
+ return SkOperand2::kString;
+ case SkType_Array:
+ return SkOperand2::kArray;
+ case SkType_Unknown:
+ return SkOperand2::kNoType;
+ default:
+ SkASSERT(0);
+ return SkOperand2::kNoType;
+ }
+}
+
+bool SkAnimatorScript2::MapEnums(const char* ptr, const char* match, size_t len, int* value) {
+ int index = 0;
+ bool more = true;
+ do {
+ const char* last = strchr(ptr, '|');
+ if (last == NULL) {
+ last = &ptr[strlen(ptr)];
+ more = false;
+ }
+ size_t length = last - ptr;
+ if (len == length && strncmp(ptr, match, length) == 0) {
+ *value = index;
+ return true;
+ }
+ index++;
+ ptr = last + 1;
+ } while (more);
+ return false;
+}
+
+#if defined SK_DEBUG
+
+#include "SkAnimator.h"
+
+static const char scriptTestSetup[] =
+"<screenplay>"
+ "<apply>"
+ "<paint>"
+ "<emboss id='emboss' direction='[1,1,1]' />"
+ "</paint>"
+ "<animateField id='animation' field='direction' target='emboss' from='[1,1,1]' to='[-1,1,1]' dur='1'/>"
+ "<set lval='direction[0]' target='emboss' to='-1' />"
+ "</apply>"
+ "<color id='testColor' color='0 ? rgb(0,0,0) : rgb(255,255,255)' />"
+ "<color id='xColor' color='rgb(12,34,56)' />"
+ "<typedArray id='emptyArray' />"
+ "<typedArray id='intArray' values='[1, 4, 6]' />"
+ "<s32 id='idx' value='2' />"
+ "<s32 id='idy' value='2' />"
+ "<string id='alpha' value='abc' />"
+ "<rectangle id='testRect' left='Math.cos(0)' top='2' right='12' bottom='5' />"
+ "<event id='evt'>"
+ "<input name='x' />"
+ "<apply scope='idy'>"
+ "<set field='value' to='evt.x.s32' />"
+ "</apply>"
+ "</event>"
+"</screenplay>";
+
+static const SkScriptNAnswer scriptTests[] = {
+ { "alpha+alpha", SkType_String, 0, 0, "abcabc" },
+ { "0 ? Math.sin(0) : 1", SkType_Int, 1 },
+ { "intArray[4]", SkType_Unknown },
+ { "emptyArray[4]", SkType_Unknown },
+ { "idx", SkType_Int, 2 },
+ { "intArray.length", SkType_Int, 3 },
+ { "intArray.values[0]", SkType_Int, 1 },
+ { "intArray[0]", SkType_Int, 1 },
+ { "idx.value", SkType_Int, 2 },
+ { "alpha.value", SkType_String, 0, 0, "abc" },
+ { "alpha", SkType_String, 0, 0, "abc" },
+ { "alpha.value+alpha.value", SkType_String, 0, 0, "abcabc" },
+ { "alpha+idx", SkType_String, 0, 0, "abc2" },
+ { "idx+alpha", SkType_String, 0, 0, "2abc" },
+ { "intArray[idx]", SkType_Int, 6 },
+ { "alpha.slice(1,2)", SkType_String, 0, 0, "b" },
+ { "alpha.value.slice(1,2)", SkType_String, 0, 0, "b" },
+ { "Math.sin(0)", SkType_Float, 0, SkIntToScalar(0) },
+ { "testRect.left+2", SkType_Float, 0, SkIntToScalar(3) },
+ { "0 ? intArray[0] : 1", SkType_Int, 1 },
+ { "0 ? intArray.values[0] : 1", SkType_Int, 1 },
+ { "0 ? idx : 1", SkType_Int, 1 },
+ { "0 ? idx.value : 1", SkType_Int, 1 },
+ { "0 ? alpha.slice(1,2) : 1", SkType_Int, 1 },
+ { "0 ? alpha.value.slice(1,2) : 1", SkType_Int, 1 },
+ { "idy", SkType_Int, 3 }
+};
+
+#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
+
+void SkAnimatorScript2::UnitTest() {
+#if defined(SK_SUPPORT_UNITTEST)
+ SkAnimator animator;
+ SkASSERT(animator.decodeMemory(scriptTestSetup, sizeof(scriptTestSetup)-1));
+ SkEvent evt;
+ evt.setString("id", "evt");
+ evt.setS32("x", 3);
+ animator.doUserEvent(evt);
+ // set up animator with memory script above, then run value tests
+ for (int index = 0; index < SkScriptNAnswer_testCount; index++) {
+ SkAnimatorScript2 engine(*animator.fMaker, NULL, scriptTests[index].fType);
+ SkScriptValue2 value;
+ const char* script = scriptTests[index].fScript;
+ bool success = engine.evaluateScript(&script, &value);
+ if (success == false) {
+ SkASSERT(scriptTests[index].fType == SkType_Unknown);
+ continue;
+ }
+ SkASSERT(value.fType == ToOpType(scriptTests[index].fType));
+ SkScalar error;
+ switch (value.fType) {
+ case SkOperand2::kS32:
+ SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
+ break;
+ case SkOperand2::kScalar:
+ error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
+ SkASSERT(error < SK_Scalar1 / 10000);
+ break;
+ case SkOperand2::kString:
+ SkASSERT(value.fOperand.fString->equals(scriptTests[index].fStringAnswer));
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+#endif
+}
+
+#endif
diff --git a/animator/SkAnimatorScript2.h b/animator/SkAnimatorScript2.h
new file mode 100644
index 00000000..c3995f6f
--- /dev/null
+++ b/animator/SkAnimatorScript2.h
@@ -0,0 +1,50 @@
+
+/*
+ * 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 SkAnimatorScript2_DEFINED
+#define SkAnimatorScript2_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkScript2.h"
+#include "SkTypedArray.h"
+
+class SkAnimateMaker;
+struct SkMemberInfo;
+
+#ifndef SkAnimatorScript_DEFINED
+struct SkDisplayEnumMap {
+ SkDisplayTypes fType;
+ const char* fValues;
+};
+#endif
+
+class SkAnimatorScript2 : public SkScriptEngine2 {
+public:
+ SkAnimatorScript2(SkAnimateMaker& , SkDisplayable* working, SkDisplayTypes type);
+ ~SkAnimatorScript2();
+ bool evalMemberCommon(const SkMemberInfo* info,
+ SkDisplayable* displayable, SkOperand2* value);
+ SkAnimateMaker& getMaker() { return fMaker; }
+ SkDisplayable* getWorking() { return fWorking; }
+ static bool MapEnums(const char* ptr, const char* match, size_t len, int* value);
+ static const SkDisplayEnumMap& GetEnumValues(SkDisplayTypes type);
+ static SkDisplayTypes ToDisplayType(SkOperand2::OpType type);
+ static SkOperand2::OpType ToOpType(SkDisplayTypes type);
+private:
+ SkAnimateMaker& fMaker;
+ SkDisplayable* fWorking;
+ friend class SkDump;
+ friend struct SkScriptNAnswer;
+ // illegal
+ SkAnimatorScript2& operator=(const SkAnimatorScript2&);
+#ifdef SK_DEBUG
+public:
+ static void UnitTest();
+#endif
+};
+
+#endif // SkAnimatorScript2_DEFINED
diff --git a/animator/SkBoundable.cpp b/animator/SkBoundable.cpp
new file mode 100644
index 00000000..64a70057
--- /dev/null
+++ b/animator/SkBoundable.cpp
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "SkBoundable.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+
+SkBoundable::SkBoundable() {
+ clearBounds();
+ fBounds.fTop = 0;
+ fBounds.fRight = 0;
+ fBounds.fBottom = 0;
+}
+
+void SkBoundable::clearBounder() {
+ fBounds.fLeft = 0x7fff;
+}
+
+void SkBoundable::getBounds(SkRect* rect) {
+ SkASSERT(rect);
+ if (fBounds.fLeft == (int16_t)0x8000U) {
+ INHERITED::getBounds(rect);
+ return;
+ }
+ rect->fLeft = SkIntToScalar(fBounds.fLeft);
+ rect->fTop = SkIntToScalar(fBounds.fTop);
+ rect->fRight = SkIntToScalar(fBounds.fRight);
+ rect->fBottom = SkIntToScalar(fBounds.fBottom);
+}
+
+void SkBoundable::enableBounder() {
+ fBounds.fLeft = 0;
+}
+
+
+SkBoundableAuto::SkBoundableAuto(SkBoundable* boundable,
+ SkAnimateMaker& maker) : fBoundable(boundable), fMaker(maker) {
+ if (fBoundable->hasBounds()) {
+ fMaker.fCanvas->setBounder(&maker.fDisplayList);
+ fMaker.fDisplayList.fBounds.setEmpty();
+ }
+}
+
+SkBoundableAuto::~SkBoundableAuto() {
+ if (fBoundable->hasBounds() == false)
+ return;
+ fMaker.fCanvas->setBounder(NULL);
+ fBoundable->setBounds(fMaker.fDisplayList.fBounds);
+}
diff --git a/animator/SkBoundable.h b/animator/SkBoundable.h
new file mode 100644
index 00000000..daeda23c
--- /dev/null
+++ b/animator/SkBoundable.h
@@ -0,0 +1,41 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBoundable_DEFINED
+#define SkBoundable_DEFINED
+
+#include "SkDrawable.h"
+#include "SkRect.h"
+
+class SkBoundable : public SkDrawable {
+public:
+ SkBoundable();
+ virtual void clearBounder();
+ virtual void enableBounder();
+ virtual void getBounds(SkRect* );
+ bool hasBounds() { return fBounds.fLeft != (int16_t)0x8000U; }
+ void setBounds(SkIRect& bounds) { fBounds = bounds; }
+protected:
+ void clearBounds() { fBounds.fLeft = (int16_t) SkToU16(0x8000); }; // mark bounds as unset
+ SkIRect fBounds;
+private:
+ typedef SkDrawable INHERITED;
+};
+
+class SkBoundableAuto {
+public:
+ SkBoundableAuto(SkBoundable* boundable, SkAnimateMaker& maker);
+ ~SkBoundableAuto();
+private:
+ SkBoundable* fBoundable;
+ SkAnimateMaker& fMaker;
+ SkBoundableAuto& operator= (const SkBoundableAuto& );
+};
+
+#endif // SkBoundable_DEFINED
diff --git a/animator/SkBuildCondensedInfo.cpp b/animator/SkBuildCondensedInfo.cpp
new file mode 100644
index 00000000..411be904
--- /dev/null
+++ b/animator/SkBuildCondensedInfo.cpp
@@ -0,0 +1,282 @@
+
+/*
+ * 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 "SkTypes.h"
+#if defined SK_BUILD_CONDENSED
+#include "SkMemberInfo.h"
+#if SK_USE_CONDENSED_INFO == 1
+#error "SK_USE_CONDENSED_INFO must be zero to build condensed info"
+#endif
+#if !defined SK_BUILD_FOR_WIN32
+#error "SK_BUILD_FOR_WIN32 must be defined to build condensed info"
+#endif
+#include "SkDisplayType.h"
+#include "SkIntArray.h"
+#include <stdio.h>
+
+SkTDMemberInfoArray gInfos;
+SkTDIntArray gInfosCounts;
+SkTDDisplayTypesArray gInfosTypeIDs;
+SkTDMemberInfoArray gUnknowns;
+SkTDIntArray gUnknownsCounts;
+
+static void AddInfo(SkDisplayTypes type, const SkMemberInfo* info, int infoCount) {
+ SkASSERT(gInfos[type] == NULL);
+ gInfos[type] = info;
+ gInfosCounts[type] = infoCount;
+ *gInfosTypeIDs.append() = type;
+ size_t allStrs = 0;
+ for (int inner = 0; inner < infoCount; inner++) {
+ SkASSERT(info[inner].fCount < 256);
+ int offset = (int) info[inner].fOffset;
+ SkASSERT(offset < 128 && offset > -129);
+ SkASSERT(allStrs < 256);
+ if (info[inner].fType == SkType_BaseClassInfo) {
+ const SkMemberInfo* innerInfo = (const SkMemberInfo*) info[inner].fName;
+ if (gUnknowns.find(innerInfo) == -1) {
+ *gUnknowns.append() = innerInfo;
+ *gUnknownsCounts.append() = info[inner].fCount;
+ }
+ }
+ if (info[inner].fType != SkType_BaseClassInfo && info[inner].fName)
+ allStrs += strlen(info[inner].fName);
+ allStrs += 1;
+ SkASSERT(info[inner].fType < 256);
+ }
+}
+
+static void WriteInfo(FILE* condensed, const SkMemberInfo* info, int infoCount,
+ const char* typeName, bool draw, bool display) {
+ fprintf(condensed, "static const char g%sStrings[] = \n", typeName);
+ int inner;
+ // write strings
+ for (inner = 0; inner < infoCount; inner++) {
+ const char* name = (info[inner].fType != SkType_BaseClassInfo && info[inner].fName) ?
+ info[inner].fName : "";
+ const char* zero = inner < infoCount - 1 ? "\\0" : "";
+ fprintf(condensed, "\t\"%s%s\"\n", name, zero);
+ }
+ fprintf(condensed, ";\n\nstatic const SkMemberInfo g%s", draw ? "Draw" : display ? "Display" : "");
+ fprintf(condensed, "%sInfo[] = {", typeName);
+ size_t nameOffset = 0;
+ // write info tables
+ for (inner = 0; inner < infoCount; inner++) {
+ size_t offset = info[inner].fOffset;
+ if (info[inner].fType == SkType_BaseClassInfo) {
+ offset = (size_t) gInfos.find((const SkMemberInfo* ) info[inner].fName);
+ SkASSERT((int) offset >= 0);
+ offset = gInfosTypeIDs.find((SkDisplayTypes) offset);
+ SkASSERT((int) offset >= 0);
+ }
+ fprintf(condensed, "\n\t{%d, %d, %d, %d}", nameOffset, offset,
+ info[inner].fType, info[inner].fCount);
+ if (inner < infoCount - 1)
+ putc(',', condensed);
+ if (info[inner].fType != SkType_BaseClassInfo && info[inner].fName)
+ nameOffset += strlen(info[inner].fName);
+ nameOffset += 1;
+ }
+ fprintf(condensed, "\n};\n\n");
+}
+
+static void Get3DName(char* scratch, const char* name) {
+ if (strncmp("skia3d:", name, sizeof("skia3d:") - 1) == 0) {
+ strcpy(scratch, "3D_");
+ scratch[3]= name[7] & ~0x20;
+ strcpy(&scratch[4], &name[8]);
+ } else {
+ scratch[0] = name[0] & ~0x20;
+ strcpy(&scratch[1], &name[1]);
+ }
+}
+
+int type_compare(const void* a, const void* b) {
+ SkDisplayTypes first = *(SkDisplayTypes*) a;
+ SkDisplayTypes second = *(SkDisplayTypes*) b;
+ return first < second ? -1 : first == second ? 0 : 1;
+}
+
+void SkDisplayType::BuildCondensedInfo(SkAnimateMaker* maker) {
+ gInfos.setCount(kNumberOfTypes);
+ memset(gInfos.begin(), 0, sizeof(gInfos[0]) * kNumberOfTypes);
+ gInfosCounts.setCount(kNumberOfTypes);
+ memset(gInfosCounts.begin(), -1, sizeof(gInfosCounts[0]) * kNumberOfTypes);
+ // check to see if it is condensable
+ int index, infoCount;
+ for (index = 0; index < kTypeNamesSize; index++) {
+ const SkMemberInfo* info = GetMembers(maker, gTypeNames[index].fType, &infoCount);
+ if (info == NULL)
+ continue;
+ AddInfo(gTypeNames[index].fType, info, infoCount);
+ }
+ const SkMemberInfo* extraInfo =
+ SkDisplayType::GetMembers(maker, SkType_3D_Point, &infoCount);
+ AddInfo(SkType_Point, extraInfo, infoCount);
+ AddInfo(SkType_3D_Point, extraInfo, infoCount);
+// int baseInfos = gInfos.count();
+ do {
+ SkTDMemberInfoArray oldRefs = gUnknowns;
+ SkTDIntArray oldRefCounts = gUnknownsCounts;
+ gUnknowns.reset();
+ gUnknownsCounts.reset();
+ for (index = 0; index < oldRefs.count(); index++) {
+ const SkMemberInfo* info = oldRefs[index];
+ if (gInfos.find(info) == -1) {
+ int typeIndex = 0;
+ for (; typeIndex < kNumberOfTypes; typeIndex++) {
+ const SkMemberInfo* temp = SkDisplayType::GetMembers(
+ maker, (SkDisplayTypes) typeIndex, NULL);
+ if (temp == info)
+ break;
+ }
+ SkASSERT(typeIndex < kNumberOfTypes);
+ AddInfo((SkDisplayTypes) typeIndex, info, oldRefCounts[index]);
+ }
+ }
+ } while (gUnknowns.count() > 0);
+ qsort(gInfosTypeIDs.begin(), gInfosTypeIDs.count(), sizeof(gInfosTypeIDs[0]), &type_compare);
+#ifdef SK_DEBUG
+ FILE* condensed = fopen("../../src/animator/SkCondensedDebug.cpp", "w+");
+ fprintf(condensed, "#include \"SkTypes.h\"\n");
+ fprintf(condensed, "#ifdef SK_DEBUG\n");
+#else
+ FILE* condensed = fopen("../../src/animator/SkCondensedRelease.cpp", "w+");
+ fprintf(condensed, "#include \"SkTypes.h\"\n");
+ fprintf(condensed, "#ifdef SK_RELEASE\n");
+#endif
+ // write header
+ fprintf(condensed, "// This file was automatically generated.\n");
+ fprintf(condensed, "// To change it, edit the file with the matching debug info.\n");
+ fprintf(condensed, "// Then execute SkDisplayType::BuildCondensedInfo() to "
+ "regenerate this file.\n\n");
+ // write name of memberInfo
+ int typeNameIndex = 0;
+ int unknown = 1;
+ for (index = 0; index < gInfos.count(); index++) {
+ const SkMemberInfo* info = gInfos[index];
+ if (info == NULL)
+ continue;
+ char scratch[64];
+ bool drawPrefix, displayPrefix;
+ while (gTypeNames[typeNameIndex].fType < index)
+ typeNameIndex++;
+ if (gTypeNames[typeNameIndex].fType == index) {
+ Get3DName(scratch, gTypeNames[typeNameIndex].fName);
+ drawPrefix = gTypeNames[typeNameIndex].fDrawPrefix;
+ displayPrefix = gTypeNames[typeNameIndex].fDisplayPrefix;
+ } else {
+ sprintf(scratch, "Unknown%d", unknown++);
+ drawPrefix = displayPrefix = false;
+ }
+ WriteInfo(condensed, info, gInfosCounts[index], scratch, drawPrefix, displayPrefix);
+ }
+ // write array of table pointers
+// start here;
+ fprintf(condensed, "static const SkMemberInfo* const gInfoTables[] = {");
+ typeNameIndex = 0;
+ unknown = 1;
+ for (index = 0; index < gInfos.count(); index++) {
+ const SkMemberInfo* info = gInfos[index];
+ if (info == NULL)
+ continue;
+ char scratch[64];
+ bool drawPrefix, displayPrefix;
+ while (gTypeNames[typeNameIndex].fType < index)
+ typeNameIndex++;
+ if (gTypeNames[typeNameIndex].fType == index) {
+ Get3DName(scratch, gTypeNames[typeNameIndex].fName);
+ drawPrefix = gTypeNames[typeNameIndex].fDrawPrefix;
+ displayPrefix = gTypeNames[typeNameIndex].fDisplayPrefix;
+ } else {
+ sprintf(scratch, "Unknown%d", unknown++);
+ drawPrefix = displayPrefix = false;
+ }
+ fprintf(condensed, "\n\tg");
+ if (drawPrefix)
+ fprintf(condensed, "Draw");
+ if (displayPrefix)
+ fprintf(condensed, "Display");
+ fprintf(condensed, "%sInfo", scratch);
+ if (index < gInfos.count() - 1)
+ putc(',', condensed);
+ }
+ fprintf(condensed, "\n};\n\n");
+ // write the array of number of entries in the info table
+ fprintf(condensed, "static const unsigned char gInfoCounts[] = {\n\t");
+ int written = 0;
+ for (index = 0; index < gInfosCounts.count(); index++) {
+ int count = gInfosCounts[index];
+ if (count < 0)
+ continue;
+ if (written > 0)
+ putc(',', condensed);
+ if (written % 20 == 19)
+ fprintf(condensed, "\n\t");
+ fprintf(condensed, "%d",count);
+ written++;
+ }
+ fprintf(condensed, "\n};\n\n");
+ // write array of type ids table entries correspond to
+ fprintf(condensed, "static const unsigned char gTypeIDs[] = {\n\t");
+ int typeIDCount = 0;
+ typeNameIndex = 0;
+ unknown = 1;
+ for (index = 0; index < gInfosCounts.count(); index++) {
+ const SkMemberInfo* info = gInfos[index];
+ if (info == NULL)
+ continue;
+ typeIDCount++;
+ char scratch[64];
+ while (gTypeNames[typeNameIndex].fType < index)
+ typeNameIndex++;
+ if (gTypeNames[typeNameIndex].fType == index) {
+ Get3DName(scratch, gTypeNames[typeNameIndex].fName);
+ } else
+ sprintf(scratch, "Unknown%d", unknown++);
+ fprintf(condensed, "%d%c // %s\n\t", index,
+ index < gInfosCounts.count() ? ',' : ' ', scratch);
+ }
+ fprintf(condensed, "\n};\n\n");
+ fprintf(condensed, "static const int kTypeIDs = %d;\n\n", typeIDCount);
+ // write the array of string pointers
+ fprintf(condensed, "static const char* const gInfoNames[] = {");
+ typeNameIndex = 0;
+ unknown = 1;
+ written = 0;
+ for (index = 0; index < gInfosCounts.count(); index++) {
+ const SkMemberInfo* info = gInfos[index];
+ if (info == NULL)
+ continue;
+ if (written > 0)
+ putc(',', condensed);
+ written++;
+ fprintf(condensed, "\n\tg");
+ char scratch[64];
+ while (gTypeNames[typeNameIndex].fType < index)
+ typeNameIndex++;
+ if (gTypeNames[typeNameIndex].fType == index) {
+ Get3DName(scratch, gTypeNames[typeNameIndex].fName);
+ } else
+ sprintf(scratch, "Unknown%d", unknown++);
+ fprintf(condensed, "%sStrings", scratch);
+ }
+ fprintf(condensed, "\n};\n\n");
+ fprintf(condensed, "#endif\n");
+ fclose(condensed);
+ gInfos.reset();
+ gInfosCounts.reset();
+ gInfosTypeIDs.reset();
+ gUnknowns.reset();
+ gUnknownsCounts.reset();
+}
+
+#elif defined SK_DEBUG
+#include "SkDisplayType.h"
+void SkDisplayType::BuildCondensedInfo(SkAnimateMaker* ) {}
+#endif
diff --git a/animator/SkCondensedDebug.cpp b/animator/SkCondensedDebug.cpp
new file mode 100644
index 00000000..dcebe004
--- /dev/null
+++ b/animator/SkCondensedDebug.cpp
@@ -0,0 +1,1387 @@
+
+/*
+ * 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 "SkTypes.h"
+#ifndef SK_BUILD_FOR_UNIX
+#ifdef SK_DEBUG
+// This file was automatically generated.
+// To change it, edit the file with the matching debug info.
+// Then execute SkDisplayType::BuildCondensedInfo() to regenerate this file.
+
+static const char gMathStrings[] =
+ "E\0"
+ "LN10\0"
+ "LN2\0"
+ "LOG10E\0"
+ "LOG2E\0"
+ "PI\0"
+ "SQRT1_2\0"
+ "SQRT2\0"
+ "abs\0"
+ "acos\0"
+ "asin\0"
+ "atan\0"
+ "atan2\0"
+ "ceil\0"
+ "cos\0"
+ "exp\0"
+ "floor\0"
+ "log\0"
+ "max\0"
+ "min\0"
+ "pow\0"
+ "random\0"
+ "round\0"
+ "sin\0"
+ "sqrt\0"
+ "tan"
+;
+
+static const SkMemberInfo gMathInfo[] = {
+ {0, -1, 67, 98},
+ {2, -2, 67, 98},
+ {7, -3, 67, 98},
+ {11, -4, 67, 98},
+ {18, -5, 67, 98},
+ {24, -6, 67, 98},
+ {27, -7, 67, 98},
+ {35, -8, 67, 98},
+ {41, -1, 66, 98},
+ {45, -2, 66, 98},
+ {50, -3, 66, 98},
+ {55, -4, 66, 98},
+ {60, -5, 66, 98},
+ {66, -6, 66, 98},
+ {71, -7, 66, 98},
+ {75, -8, 66, 98},
+ {79, -9, 66, 98},
+ {85, -10, 66, 98},
+ {89, -11, 66, 98},
+ {93, -12, 66, 98},
+ {97, -13, 66, 98},
+ {101, -14, 66, 98},
+ {108, -15, 66, 98},
+ {114, -16, 66, 98},
+ {118, -17, 66, 98},
+ {123, -18, 66, 98}
+};
+
+static const char gAddStrings[] =
+ "inPlace\0"
+ "offset\0"
+ "use\0"
+ "where"
+;
+
+static const SkMemberInfo gAddInfo[] = {
+ {0, 16, 26, 1},
+ {8, 20, 96, 1},
+ {15, 24, 37, 1},
+ {19, 28, 37, 1}
+};
+
+static const char gAddCircleStrings[] =
+ "\0"
+ "radius\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gAddCircleInfo[] = {
+ {0, 3, 18, 1},
+ {1, 24, 98, 1},
+ {8, 28, 98, 1},
+ {10, 32, 98, 1}
+};
+
+static const char gUnknown1Strings[] =
+ "direction"
+;
+
+static const SkMemberInfo gUnknown1Info[] = {
+ {0, 20, 75, 1}
+};
+
+static const char gAddOvalStrings[] =
+ ""
+;
+
+static const SkMemberInfo gAddOvalInfo[] = {
+ {0, 6, 18, 5}
+};
+
+static const char gAddPathStrings[] =
+ "matrix\0"
+ "path"
+;
+
+static const SkMemberInfo gAddPathInfo[] = {
+ {0, 20, 65, 1},
+ {7, 24, 74, 1}
+};
+
+static const char gAddRectangleStrings[] =
+ "\0"
+ "bottom\0"
+ "left\0"
+ "right\0"
+ "top"
+;
+
+static const SkMemberInfo gAddRectangleInfo[] = {
+ {0, 3, 18, 1},
+ {1, 36, 98, 1},
+ {8, 24, 98, 1},
+ {13, 32, 98, 1},
+ {19, 28, 98, 1}
+};
+
+static const char gAddRoundRectStrings[] =
+ "\0"
+ "rx\0"
+ "ry"
+;
+
+static const SkMemberInfo gAddRoundRectInfo[] = {
+ {0, 6, 18, 5},
+ {1, 40, 98, 1},
+ {4, 44, 98, 1}
+};
+
+static const char gUnknown2Strings[] =
+ "begin\0"
+ "blend\0"
+ "dur\0"
+ "dynamic\0"
+ "field\0"
+ "formula\0"
+ "from\0"
+ "mirror\0"
+ "repeat\0"
+ "reset\0"
+ "target\0"
+ "to\0"
+ "values"
+;
+
+static const SkMemberInfo gUnknown2Info[] = {
+ {0, 16, 71, 1},
+ {6, 20, 119, 98},
+ {12, 36, 71, 1},
+ {16, -1, 67, 26},
+ {24, 40, 108, 2},
+ {30, 48, 40, 2},
+ {38, 56, 40, 2},
+ {43, -2, 67, 26},
+ {50, 64, 98, 1},
+ {57, -3, 67, 26},
+ {63, 68, 40, 2},
+ {70, 76, 40, 2},
+ {73, -4, 67, 40}
+};
+
+static const char gAnimateFieldStrings[] =
+ ""
+;
+
+static const SkMemberInfo gAnimateFieldInfo[] = {
+ {0, 8, 18, 13}
+};
+
+static const char gApplyStrings[] =
+ "animator\0"
+ "begin\0"
+ "dontDraw\0"
+ "dynamicScope\0"
+ "interval\0"
+ "mode\0"
+ "pickup\0"
+ "restore\0"
+ "scope\0"
+ "step\0"
+ "steps\0"
+ "time\0"
+ "transition"
+;
+
+static const SkMemberInfo gApplyInfo[] = {
+ {0, -1, 67, 10},
+ {9, 16, 71, 1},
+ {15, 20, 26, 1},
+ {24, 24, 108, 2},
+ {37, 32, 71, 1},
+ {46, 36, 13, 1},
+ {51, 40, 26, 1},
+ {58, 44, 26, 1},
+ {66, 48, 37, 1},
+ {72, -2, 67, 96},
+ {77, 52, 96, 1},
+ {83, -3, 67, 71},
+ {88, 56, 14, 1}
+};
+
+static const char gUnknown3Strings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gUnknown3Info[] = {
+ {0, 48, 98, 1},
+ {2, 52, 98, 1}
+};
+
+static const char gBitmapStrings[] =
+ "\0"
+ "erase\0"
+ "format\0"
+ "height\0"
+ "rowBytes\0"
+ "width"
+;
+
+static const SkMemberInfo gDrawBitmapInfo[] = {
+ {0, 11, 18, 2},
+ {1, -1, 67, 15},
+ {7, 56, 21, 1},
+ {14, 60, 96, 1},
+ {21, 64, 96, 1},
+ {30, 68, 96, 1}
+};
+
+static const char gBitmapShaderStrings[] =
+ "\0"
+ "filterType\0"
+ "image"
+;
+
+static const SkMemberInfo gDrawBitmapShaderInfo[] = {
+ {0, 67, 18, 2},
+ {1, 28, 47, 1},
+ {12, 32, 17, 1}
+};
+
+static const char gBlurStrings[] =
+ "blurStyle\0"
+ "radius"
+;
+
+static const SkMemberInfo gDrawBlurInfo[] = {
+ {0, 24, 63, 1},
+ {10, 20, 98, 1}
+};
+
+static const char gBoundsStrings[] =
+ "\0"
+ "inval"
+;
+
+static const SkMemberInfo gDisplayBoundsInfo[] = {
+ {0, 58, 18, 7},
+ {1, 44, 26, 1}
+};
+
+static const char gClipStrings[] =
+ "path\0"
+ "rectangle"
+;
+
+static const SkMemberInfo gDrawClipInfo[] = {
+ {0, 20, 74, 1},
+ {5, 16, 91, 1}
+};
+
+static const char gColorStrings[] =
+ "alpha\0"
+ "blue\0"
+ "color\0"
+ "green\0"
+ "hue\0"
+ "red\0"
+ "saturation\0"
+ "value"
+;
+
+static const SkMemberInfo gDrawColorInfo[] = {
+ {0, -1, 67, 98},
+ {6, -2, 67, 98},
+ {11, 20, 15, 1},
+ {17, -3, 67, 98},
+ {23, -4, 67, 98},
+ {27, -5, 67, 98},
+ {31, -6, 67, 98},
+ {42, -7, 67, 98}
+};
+
+static const char gCubicToStrings[] =
+ "x1\0"
+ "x2\0"
+ "x3\0"
+ "y1\0"
+ "y2\0"
+ "y3"
+;
+
+static const SkMemberInfo gCubicToInfo[] = {
+ {0, 20, 98, 1},
+ {3, 28, 98, 1},
+ {6, 36, 98, 1},
+ {9, 24, 98, 1},
+ {12, 32, 98, 1},
+ {15, 40, 98, 1}
+};
+
+static const char gDashStrings[] =
+ "intervals\0"
+ "phase"
+;
+
+static const SkMemberInfo gDashInfo[] = {
+ {0, 20, 119, 98},
+ {10, 36, 98, 1}
+};
+
+static const char gDataStrings[] =
+ "\0"
+ "name"
+;
+
+static const SkMemberInfo gDataInfo[] = {
+ {0, 33, 18, 3},
+ {1, 32, 108, 2}
+};
+
+static const char gDiscreteStrings[] =
+ "deviation\0"
+ "segLength"
+;
+
+static const SkMemberInfo gDiscreteInfo[] = {
+ {0, 20, 98, 1},
+ {10, 24, 98, 1}
+};
+
+static const char gDrawToStrings[] =
+ "drawOnce\0"
+ "use"
+;
+
+static const SkMemberInfo gDrawToInfo[] = {
+ {0, 72, 26, 1},
+ {9, 76, 19, 1}
+};
+
+static const char gDumpStrings[] =
+ "displayList\0"
+ "eventList\0"
+ "events\0"
+ "groups\0"
+ "name\0"
+ "posts"
+;
+
+static const SkMemberInfo gDumpInfo[] = {
+ {0, 16, 26, 1},
+ {12, 20, 26, 1},
+ {22, 24, 26, 1},
+ {29, 36, 26, 1},
+ {36, 28, 108, 2},
+ {41, 40, 26, 1}
+};
+
+static const char gEmbossStrings[] =
+ "ambient\0"
+ "direction\0"
+ "radius\0"
+ "specular"
+;
+
+static const SkMemberInfo gDrawEmbossInfo[] = {
+ {0, -1, 67, 98},
+ {8, 20, 119, 98},
+ {18, 36, 98, 1},
+ {25, -2, 67, 98}
+};
+
+static const char gEventStrings[] =
+ "code\0"
+ "disable\0"
+ "key\0"
+ "keys\0"
+ "kind\0"
+ "target\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gDisplayEventInfo[] = {
+ {0, 16, 43, 1},
+ {5, 20, 26, 1},
+ {13, -1, 67, 108},
+ {17, -2, 67, 108},
+ {22, 24, 44, 1},
+ {27, 28, 108, 2},
+ {34, 36, 98, 1},
+ {36, 40, 98, 1}
+};
+
+static const char gFromPathStrings[] =
+ "mode\0"
+ "offset\0"
+ "path"
+;
+
+static const SkMemberInfo gFromPathInfo[] = {
+ {0, 20, 49, 1},
+ {5, 24, 98, 1},
+ {12, 28, 74, 1}
+};
+
+static const char gUnknown4Strings[] =
+ "\0"
+ "offsets\0"
+ "unitMapper"
+;
+
+static const SkMemberInfo gUnknown4Info[] = {
+ {0, 67, 18, 2},
+ {1, 28, 119, 98},
+ {9, 44, 108, 2}
+};
+
+static const char gGStrings[] =
+ "condition\0"
+ "enableCondition"
+;
+
+static const SkMemberInfo gGInfo[] = {
+ {0, 16, 40, 2},
+ {10, 24, 40, 2}
+};
+
+static const char gHitClearStrings[] =
+ "targets"
+;
+
+static const SkMemberInfo gHitClearInfo[] = {
+ {0, 16, 119, 36}
+};
+
+static const char gHitTestStrings[] =
+ "bullets\0"
+ "hits\0"
+ "targets\0"
+ "value"
+;
+
+static const SkMemberInfo gHitTestInfo[] = {
+ {0, 16, 119, 36},
+ {8, 32, 119, 96},
+ {13, 48, 119, 36},
+ {21, 64, 26, 1}
+};
+
+static const char gImageStrings[] =
+ "\0"
+ "base64\0"
+ "src"
+;
+
+static const SkMemberInfo gImageInfo[] = {
+ {0, 11, 18, 2},
+ {1, 56, 16, 2},
+ {8, 64, 108, 2}
+};
+
+static const char gIncludeStrings[] =
+ "src"
+;
+
+static const SkMemberInfo gIncludeInfo[] = {
+ {0, 16, 108, 2}
+};
+
+static const char gInputStrings[] =
+ "s32\0"
+ "scalar\0"
+ "string"
+;
+
+static const SkMemberInfo gInputInfo[] = {
+ {0, 16, 96, 1},
+ {4, 20, 98, 1},
+ {11, 24, 108, 2}
+};
+
+static const char gLineStrings[] =
+ "x1\0"
+ "x2\0"
+ "y1\0"
+ "y2"
+;
+
+static const SkMemberInfo gLineInfo[] = {
+ {0, 24, 98, 1},
+ {3, 28, 98, 1},
+ {6, 32, 98, 1},
+ {9, 36, 98, 1}
+};
+
+static const char gLineToStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gLineToInfo[] = {
+ {0, 20, 98, 1},
+ {2, 24, 98, 1}
+};
+
+static const char gLinearGradientStrings[] =
+ "\0"
+ "points"
+;
+
+static const SkMemberInfo gLinearGradientInfo[] = {
+ {0, 27, 18, 3},
+ {1, 88, 77, 4}
+};
+
+static const char gMatrixStrings[] =
+ "matrix\0"
+ "perspectX\0"
+ "perspectY\0"
+ "rotate\0"
+ "scale\0"
+ "scaleX\0"
+ "scaleY\0"
+ "skewX\0"
+ "skewY\0"
+ "translate\0"
+ "translateX\0"
+ "translateY"
+;
+
+static const SkMemberInfo gDrawMatrixInfo[] = {
+ {0, 16, 119, 98},
+ {7, -1, 67, 98},
+ {17, -2, 67, 98},
+ {27, -3, 67, 98},
+ {34, -4, 67, 98},
+ {40, -5, 67, 98},
+ {47, -6, 67, 98},
+ {54, -7, 67, 98},
+ {60, -8, 67, 98},
+ {66, -9, 67, 77},
+ {76, -10, 67, 98},
+ {87, -11, 67, 98}
+};
+
+static const char gMoveStrings[] =
+ ""
+;
+
+static const SkMemberInfo gMoveInfo[] = {
+ {0, 1, 18, 4}
+};
+
+static const char gMoveToStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gMoveToInfo[] = {
+ {0, 20, 98, 1},
+ {2, 24, 98, 1}
+};
+
+static const char gMovieStrings[] =
+ "src"
+;
+
+static const SkMemberInfo gMovieInfo[] = {
+ {0, 16, 108, 2}
+};
+
+static const char gOvalStrings[] =
+ ""
+;
+
+static const SkMemberInfo gOvalInfo[] = {
+ {0, 58, 18, 7}
+};
+
+static const char gPaintStrings[] =
+ "antiAlias\0"
+ "ascent\0"
+ "color\0"
+ "descent\0"
+ "filterType\0"
+ "linearText\0"
+ "maskFilter\0"
+ "measureText\0"
+ "pathEffect\0"
+ "shader\0"
+ "strikeThru\0"
+ "stroke\0"
+ "strokeCap\0"
+ "strokeJoin\0"
+ "strokeMiter\0"
+ "strokeWidth\0"
+ "style\0"
+ "textAlign\0"
+ "textScaleX\0"
+ "textSize\0"
+ "textSkewX\0"
+ "textTracking\0"
+ "typeface\0"
+ "underline\0"
+ "xfermode"
+;
+
+static const SkMemberInfo gDrawPaintInfo[] = {
+ {0, 16, 26, 1},
+ {10, -1, 67, 98},
+ {17, 20, 31, 1},
+ {23, -2, 67, 98},
+ {31, 24, 47, 1},
+ {42, 28, 26, 1},
+ {53, 32, 62, 1},
+ {64, -1, 66, 98},
+ {76, 36, 76, 1},
+ {87, 40, 102, 1},
+ {94, 44, 26, 1},
+ {105, 48, 26, 1},
+ {112, 52, 27, 1},
+ {122, 56, 58, 1},
+ {133, 60, 98, 1},
+ {145, 64, 98, 1},
+ {157, 68, 109, 1},
+ {163, 72, 9, 1},
+ {173, 76, 98, 1},
+ {184, 80, 98, 1},
+ {193, 84, 98, 1},
+ {203, 88, 98, 1},
+ {216, 92, 120, 1},
+ {225, 96, 26, 1},
+ {235, 100, 121, 1}
+};
+
+static const char gPathStrings[] =
+ "d\0"
+ "fillType\0"
+ "length"
+;
+
+static const SkMemberInfo gDrawPathInfo[] = {
+ {0, 52, 108, 2},
+ {2, -1, 67, 46},
+ {11, -2, 67, 98}
+};
+
+static const char gUnknown5Strings[] =
+ "x\0"
+ "y\0"
+ "z"
+;
+
+static const SkMemberInfo gUnknown5Info[] = {
+ {0, 0, 98, 1},
+ {2, 4, 98, 1},
+ {4, 8, 98, 1}
+};
+
+static const char gPointStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gDrawPointInfo[] = {
+ {0, 16, 98, 1},
+ {2, 20, 98, 1}
+};
+
+static const char gPolyToPolyStrings[] =
+ "destination\0"
+ "source"
+;
+
+static const SkMemberInfo gPolyToPolyInfo[] = {
+ {0, 24, 80, 1},
+ {12, 20, 80, 1}
+};
+
+static const char gPolygonStrings[] =
+ ""
+;
+
+static const SkMemberInfo gPolygonInfo[] = {
+ {0, 48, 18, 1}
+};
+
+static const char gPolylineStrings[] =
+ "points"
+;
+
+static const SkMemberInfo gPolylineInfo[] = {
+ {0, 88, 119, 98}
+};
+
+static const char gPostStrings[] =
+ "delay\0"
+ "initialized\0"
+ "mode\0"
+ "sink\0"
+ "target\0"
+ "type"
+;
+
+static const SkMemberInfo gPostInfo[] = {
+ {0, 16, 71, 1},
+ {6, 20, 26, 1},
+ {18, 24, 45, 1},
+ {23, -1, 67, 108},
+ {28, -2, 67, 108},
+ {35, -3, 67, 108}
+};
+
+static const char gQuadToStrings[] =
+ "x1\0"
+ "x2\0"
+ "y1\0"
+ "y2"
+;
+
+static const SkMemberInfo gQuadToInfo[] = {
+ {0, 20, 98, 1},
+ {3, 28, 98, 1},
+ {6, 24, 98, 1},
+ {9, 32, 98, 1}
+};
+
+static const char gRCubicToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRCubicToInfo[] = {
+ {0, 18, 18, 6}
+};
+
+static const char gRLineToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRLineToInfo[] = {
+ {0, 35, 18, 2}
+};
+
+static const char gRMoveToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRMoveToInfo[] = {
+ {0, 39, 18, 2}
+};
+
+static const char gRQuadToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRQuadToInfo[] = {
+ {0, 50, 18, 4}
+};
+
+static const char gRadialGradientStrings[] =
+ "\0"
+ "center\0"
+ "radius"
+;
+
+static const SkMemberInfo gRadialGradientInfo[] = {
+ {0, 27, 18, 3},
+ {1, 88, 77, 2},
+ {8, 96, 98, 1}
+};
+
+static const char gRandomStrings[] =
+ "blend\0"
+ "max\0"
+ "min\0"
+ "random\0"
+ "seed"
+;
+
+static const SkMemberInfo gDisplayRandomInfo[] = {
+ {0, 16, 98, 1},
+ {6, 24, 98, 1},
+ {10, 20, 98, 1},
+ {14, 1, 67, 98},
+ {21, -2, 67, 96}
+};
+
+static const char gRectToRectStrings[] =
+ "destination\0"
+ "source"
+;
+
+static const SkMemberInfo gRectToRectInfo[] = {
+ {0, 24, 91, 1},
+ {12, 20, 91, 1}
+};
+
+static const char gRectangleStrings[] =
+ "bottom\0"
+ "height\0"
+ "left\0"
+ "needsRedraw\0"
+ "right\0"
+ "top\0"
+ "width"
+;
+
+static const SkMemberInfo gRectangleInfo[] = {
+ {0, 36, 98, 1},
+ {7, -1, 67, 98},
+ {14, 24, 98, 1},
+ {19, -2, 67, 26},
+ {31, 32, 98, 1},
+ {37, 28, 98, 1},
+ {41, -3, 67, 98}
+};
+
+static const char gRemoveStrings[] =
+ "offset\0"
+ "where"
+;
+
+static const SkMemberInfo gRemoveInfo[] = {
+ {0, 20, 96, 1},
+ {7, 28, 37, 1}
+};
+
+static const char gReplaceStrings[] =
+ ""
+;
+
+static const SkMemberInfo gReplaceInfo[] = {
+ {0, 1, 18, 4}
+};
+
+static const char gRotateStrings[] =
+ "center\0"
+ "degrees"
+;
+
+static const SkMemberInfo gRotateInfo[] = {
+ {0, 24, 77, 2},
+ {7, 20, 98, 1}
+};
+
+static const char gRoundRectStrings[] =
+ "\0"
+ "rx\0"
+ "ry"
+;
+
+static const SkMemberInfo gRoundRectInfo[] = {
+ {0, 58, 18, 7},
+ {1, 44, 98, 1},
+ {4, 48, 98, 1}
+};
+
+static const char gS32Strings[] =
+ "value"
+;
+
+static const SkMemberInfo gS32Info[] = {
+ {0, 16, 96, 1}
+};
+
+static const char gScalarStrings[] =
+ "value"
+;
+
+static const SkMemberInfo gScalarInfo[] = {
+ {0, 16, 98, 1}
+};
+
+static const char gScaleStrings[] =
+ "center\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gScaleInfo[] = {
+ {0, 28, 77, 2},
+ {7, 20, 98, 1},
+ {9, 24, 98, 1}
+};
+
+static const char gSetStrings[] =
+ "begin\0"
+ "dur\0"
+ "dynamic\0"
+ "field\0"
+ "formula\0"
+ "reset\0"
+ "target\0"
+ "to"
+;
+
+static const SkMemberInfo gSetInfo[] = {
+ {0, 16, 71, 1},
+ {6, 36, 71, 1},
+ {10, -1, 67, 26},
+ {18, 40, 108, 2},
+ {24, 48, 40, 2},
+ {32, -3, 67, 26},
+ {38, 68, 40, 2},
+ {45, 76, 40, 2}
+};
+
+static const char gShaderStrings[] =
+ "matrix\0"
+ "tileMode"
+;
+
+static const SkMemberInfo gShaderInfo[] = {
+ {0, 20, 65, 1},
+ {7, 24, 116, 1}
+};
+
+static const char gSkewStrings[] =
+ "center\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gSkewInfo[] = {
+ {0, 28, 77, 2},
+ {7, 20, 98, 1},
+ {9, 24, 98, 1}
+};
+
+static const char g3D_CameraStrings[] =
+ "axis\0"
+ "hackHeight\0"
+ "hackWidth\0"
+ "location\0"
+ "observer\0"
+ "patch\0"
+ "zenith"
+;
+
+static const SkMemberInfo g3D_CameraInfo[] = {
+ {0, 36, 106, 3},
+ {5, 20, 98, 1},
+ {16, 16, 98, 1},
+ {26, 24, 106, 3},
+ {35, 60, 106, 3},
+ {44, 108, 105, 1},
+ {50, 48, 106, 3}
+};
+
+static const char g3D_PatchStrings[] =
+ "origin\0"
+ "rotateDegrees\0"
+ "u\0"
+ "v"
+;
+
+static const SkMemberInfo g3D_PatchInfo[] = {
+ {0, 40, 106, 3},
+ {7, -1, 66, 98},
+ {21, 16, 106, 3},
+ {23, 28, 106, 3}
+};
+
+static const char gUnknown6Strings[] =
+ "x\0"
+ "y\0"
+ "z"
+;
+
+static const SkMemberInfo gUnknown6Info[] = {
+ {0, 0, 98, 1},
+ {2, 4, 98, 1},
+ {4, 8, 98, 1}
+};
+
+static const char gSnapshotStrings[] =
+ "filename\0"
+ "quality\0"
+ "sequence\0"
+ "type"
+;
+
+static const SkMemberInfo gSnapshotInfo[] = {
+ {0, 16, 108, 2},
+ {9, 24, 98, 1},
+ {17, 28, 26, 1},
+ {26, 32, 20, 1}
+};
+
+static const char gStringStrings[] =
+ "length\0"
+ "slice\0"
+ "value"
+;
+
+static const SkMemberInfo gStringInfo[] = {
+ {0, -1, 67, 96},
+ {7, -1, 66, 108},
+ {13, 16, 108, 2}
+};
+
+static const char gTextStrings[] =
+ "length\0"
+ "text\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gTextInfo[] = {
+ {0, -1, 67, 96},
+ {7, 24, 108, 2},
+ {12, 32, 98, 1},
+ {14, 36, 98, 1}
+};
+
+static const char gTextBoxStrings[] =
+ "\0"
+ "mode\0"
+ "spacingAdd\0"
+ "spacingAlign\0"
+ "spacingMul\0"
+ "text"
+;
+
+static const SkMemberInfo gTextBoxInfo[] = {
+ {0, 58, 18, 7},
+ {1, 60, 113, 1},
+ {6, 56, 98, 1},
+ {17, 64, 112, 1},
+ {30, 52, 98, 1},
+ {41, 44, 108, 2}
+};
+
+static const char gTextOnPathStrings[] =
+ "offset\0"
+ "path\0"
+ "text"
+;
+
+static const SkMemberInfo gTextOnPathInfo[] = {
+ {0, 24, 98, 1},
+ {7, 28, 74, 1},
+ {12, 32, 110, 1}
+};
+
+static const char gTextToPathStrings[] =
+ "path\0"
+ "text"
+;
+
+static const SkMemberInfo gTextToPathInfo[] = {
+ {0, 16, 74, 1},
+ {5, 20, 110, 1}
+};
+
+static const char gTranslateStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gTranslateInfo[] = {
+ {0, 20, 98, 1},
+ {2, 24, 98, 1}
+};
+
+static const char gTypedArrayStrings[] =
+ "length\0"
+ "values"
+;
+
+static const SkMemberInfo gTypedArrayInfo[] = {
+ {0, -1, 67, 96},
+ {7, 16, 119, 0}
+};
+
+static const char gTypefaceStrings[] =
+ "fontName"
+;
+
+static const SkMemberInfo gTypefaceInfo[] = {
+ {0, 20, 108, 2}
+};
+
+static const SkMemberInfo* const gInfoTables[] = {
+ gMathInfo,
+ gAddInfo,
+ gAddCircleInfo,
+ gUnknown1Info,
+ gAddOvalInfo,
+ gAddPathInfo,
+ gAddRectangleInfo,
+ gAddRoundRectInfo,
+ gUnknown2Info,
+ gAnimateFieldInfo,
+ gApplyInfo,
+ gUnknown3Info,
+ gDrawBitmapInfo,
+ gDrawBitmapShaderInfo,
+ gDrawBlurInfo,
+ gDisplayBoundsInfo,
+ gDrawClipInfo,
+ gDrawColorInfo,
+ gCubicToInfo,
+ gDashInfo,
+ gDataInfo,
+ gDiscreteInfo,
+ gDrawToInfo,
+ gDumpInfo,
+ gDrawEmbossInfo,
+ gDisplayEventInfo,
+ gFromPathInfo,
+ gUnknown4Info,
+ gGInfo,
+ gHitClearInfo,
+ gHitTestInfo,
+ gImageInfo,
+ gIncludeInfo,
+ gInputInfo,
+ gLineInfo,
+ gLineToInfo,
+ gLinearGradientInfo,
+ gDrawMatrixInfo,
+ gMoveInfo,
+ gMoveToInfo,
+ gMovieInfo,
+ gOvalInfo,
+ gDrawPaintInfo,
+ gDrawPathInfo,
+ gUnknown5Info,
+ gDrawPointInfo,
+ gPolyToPolyInfo,
+ gPolygonInfo,
+ gPolylineInfo,
+ gPostInfo,
+ gQuadToInfo,
+ gRCubicToInfo,
+ gRLineToInfo,
+ gRMoveToInfo,
+ gRQuadToInfo,
+ gRadialGradientInfo,
+ gDisplayRandomInfo,
+ gRectToRectInfo,
+ gRectangleInfo,
+ gRemoveInfo,
+ gReplaceInfo,
+ gRotateInfo,
+ gRoundRectInfo,
+ gS32Info,
+ gScalarInfo,
+ gScaleInfo,
+ gSetInfo,
+ gShaderInfo,
+ gSkewInfo,
+ g3D_CameraInfo,
+ g3D_PatchInfo,
+ gUnknown6Info,
+ gSnapshotInfo,
+ gStringInfo,
+ gTextInfo,
+ gTextBoxInfo,
+ gTextOnPathInfo,
+ gTextToPathInfo,
+ gTranslateInfo,
+ gTypedArrayInfo,
+ gTypefaceInfo,
+};
+
+static const unsigned char gInfoCounts[] = {
+ 26,4,4,1,1,2,5,3,13,1,13,2,6,3,2,2,2,8,6,
+ 2,2,2,2,6,4,8,3,3,2,1,4,3,1,3,4,2,2,12,1,
+ 2,1,1,25,3,3,2,2,1,1,6,4,1,1,1,1,3,5,2,7,
+ 2,1,2,3,1,1,3,8,2,3,7,4,3,4,3,4,6,3,2,2,
+ 2,1
+};
+
+static const unsigned char gTypeIDs[] = {
+ 1, // Math
+ 2, // Add
+ 3, // AddCircle
+ 4, // Unknown1
+ 5, // AddOval
+ 6, // AddPath
+ 7, // AddRectangle
+ 8, // AddRoundRect
+ 10, // Unknown2
+ 11, // AnimateField
+ 12, // Apply
+ 17, // Unknown3
+ 19, // Bitmap
+ 22, // BitmapShader
+ 23, // Blur
+ 25, // Bounds
+ 29, // Clip
+ 31, // Color
+ 32, // CubicTo
+ 33, // Dash
+ 34, // Data
+ 35, // Discrete
+ 38, // DrawTo
+ 39, // Dump
+ 41, // Emboss
+ 42, // Event
+ 48, // FromPath
+ 51, // Unknown4
+ 52, // G
+ 53, // HitClear
+ 54, // HitTest
+ 55, // Image
+ 56, // Include
+ 57, // Input
+ 59, // Line
+ 60, // LineTo
+ 61, // LinearGradient
+ 65, // Matrix
+ 68, // Move
+ 69, // MoveTo
+ 70, // Movie
+ 72, // Oval
+ 73, // Paint
+ 74, // Path
+ 77, // Unknown5
+ 78, // Point
+ 79, // PolyToPoly
+ 80, // Polygon
+ 81, // Polyline
+ 82, // Post
+ 83, // QuadTo
+ 84, // RCubicTo
+ 85, // RLineTo
+ 86, // RMoveTo
+ 87, // RQuadTo
+ 88, // RadialGradient
+ 89, // Random
+ 90, // RectToRect
+ 91, // Rectangle
+ 92, // Remove
+ 93, // Replace
+ 94, // Rotate
+ 95, // RoundRect
+ 96, // S32
+ 98, // Scalar
+ 99, // Scale
+ 101, // Set
+ 102, // Shader
+ 103, // Skew
+ 104, // 3D_Camera
+ 105, // 3D_Patch
+ 106, // Unknown6
+ 107, // Snapshot
+ 108, // String
+ 110, // Text
+ 111, // TextBox
+ 114, // TextOnPath
+ 115, // TextToPath
+ 117, // Translate
+ 119, // TypedArray
+ 120, // Typeface
+
+};
+
+static const int kTypeIDs = 81;
+
+static const char* const gInfoNames[] = {
+ gMathStrings,
+ gAddStrings,
+ gAddCircleStrings,
+ gUnknown1Strings,
+ gAddOvalStrings,
+ gAddPathStrings,
+ gAddRectangleStrings,
+ gAddRoundRectStrings,
+ gUnknown2Strings,
+ gAnimateFieldStrings,
+ gApplyStrings,
+ gUnknown3Strings,
+ gBitmapStrings,
+ gBitmapShaderStrings,
+ gBlurStrings,
+ gBoundsStrings,
+ gClipStrings,
+ gColorStrings,
+ gCubicToStrings,
+ gDashStrings,
+ gDataStrings,
+ gDiscreteStrings,
+ gDrawToStrings,
+ gDumpStrings,
+ gEmbossStrings,
+ gEventStrings,
+ gFromPathStrings,
+ gUnknown4Strings,
+ gGStrings,
+ gHitClearStrings,
+ gHitTestStrings,
+ gImageStrings,
+ gIncludeStrings,
+ gInputStrings,
+ gLineStrings,
+ gLineToStrings,
+ gLinearGradientStrings,
+ gMatrixStrings,
+ gMoveStrings,
+ gMoveToStrings,
+ gMovieStrings,
+ gOvalStrings,
+ gPaintStrings,
+ gPathStrings,
+ gUnknown5Strings,
+ gPointStrings,
+ gPolyToPolyStrings,
+ gPolygonStrings,
+ gPolylineStrings,
+ gPostStrings,
+ gQuadToStrings,
+ gRCubicToStrings,
+ gRLineToStrings,
+ gRMoveToStrings,
+ gRQuadToStrings,
+ gRadialGradientStrings,
+ gRandomStrings,
+ gRectToRectStrings,
+ gRectangleStrings,
+ gRemoveStrings,
+ gReplaceStrings,
+ gRotateStrings,
+ gRoundRectStrings,
+ gS32Strings,
+ gScalarStrings,
+ gScaleStrings,
+ gSetStrings,
+ gShaderStrings,
+ gSkewStrings,
+ g3D_CameraStrings,
+ g3D_PatchStrings,
+ gUnknown6Strings,
+ gSnapshotStrings,
+ gStringStrings,
+ gTextStrings,
+ gTextBoxStrings,
+ gTextOnPathStrings,
+ gTextToPathStrings,
+ gTranslateStrings,
+ gTypedArrayStrings,
+ gTypefaceStrings
+};
+
+#endif
+#endif
diff --git a/animator/SkCondensedRelease.cpp b/animator/SkCondensedRelease.cpp
new file mode 100644
index 00000000..12224960
--- /dev/null
+++ b/animator/SkCondensedRelease.cpp
@@ -0,0 +1,1365 @@
+
+/*
+ * 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 "SkTypes.h"
+#ifndef SK_BUILD_FOR_UNIX
+#ifdef SK_RELEASE
+// This file was automatically generated.
+// To change it, edit the file with the matching debug info.
+// Then execute SkDisplayType::BuildCondensedInfo() to regenerate this file.
+
+static const char gMathStrings[] =
+ "E\0"
+ "LN10\0"
+ "LN2\0"
+ "LOG10E\0"
+ "LOG2E\0"
+ "PI\0"
+ "SQRT1_2\0"
+ "SQRT2\0"
+ "abs\0"
+ "acos\0"
+ "asin\0"
+ "atan\0"
+ "atan2\0"
+ "ceil\0"
+ "cos\0"
+ "exp\0"
+ "floor\0"
+ "log\0"
+ "max\0"
+ "min\0"
+ "pow\0"
+ "random\0"
+ "round\0"
+ "sin\0"
+ "sqrt\0"
+ "tan"
+;
+
+static const SkMemberInfo gMathInfo[] = {
+ {0, -1, 67, 98},
+ {2, -2, 67, 98},
+ {7, -3, 67, 98},
+ {11, -4, 67, 98},
+ {18, -5, 67, 98},
+ {24, -6, 67, 98},
+ {27, -7, 67, 98},
+ {35, -8, 67, 98},
+ {41, -1, 66, 98},
+ {45, -2, 66, 98},
+ {50, -3, 66, 98},
+ {55, -4, 66, 98},
+ {60, -5, 66, 98},
+ {66, -6, 66, 98},
+ {71, -7, 66, 98},
+ {75, -8, 66, 98},
+ {79, -9, 66, 98},
+ {85, -10, 66, 98},
+ {89, -11, 66, 98},
+ {93, -12, 66, 98},
+ {97, -13, 66, 98},
+ {101, -14, 66, 98},
+ {108, -15, 66, 98},
+ {114, -16, 66, 98},
+ {118, -17, 66, 98},
+ {123, -18, 66, 98}
+};
+
+static const char gAddStrings[] =
+ "inPlace\0"
+ "offset\0"
+ "use\0"
+ "where"
+;
+
+static const SkMemberInfo gAddInfo[] = {
+ {0, 4, 26, 1},
+ {8, 8, 96, 1},
+ {15, 12, 37, 1},
+ {19, 16, 37, 1}
+};
+
+static const char gAddCircleStrings[] =
+ "\0"
+ "radius\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gAddCircleInfo[] = {
+ {0, 3, 18, 1},
+ {1, 12, 98, 1},
+ {8, 16, 98, 1},
+ {10, 20, 98, 1}
+};
+
+static const char gUnknown1Strings[] =
+ "direction"
+;
+
+static const SkMemberInfo gUnknown1Info[] = {
+ {0, 8, 75, 1}
+};
+
+static const char gAddOvalStrings[] =
+ ""
+;
+
+static const SkMemberInfo gAddOvalInfo[] = {
+ {0, 6, 18, 5}
+};
+
+static const char gAddPathStrings[] =
+ "matrix\0"
+ "path"
+;
+
+static const SkMemberInfo gAddPathInfo[] = {
+ {0, 8, 65, 1},
+ {7, 12, 74, 1}
+};
+
+static const char gAddRectangleStrings[] =
+ "\0"
+ "bottom\0"
+ "left\0"
+ "right\0"
+ "top"
+;
+
+static const SkMemberInfo gAddRectangleInfo[] = {
+ {0, 3, 18, 1},
+ {1, 24, 98, 1},
+ {8, 12, 98, 1},
+ {13, 20, 98, 1},
+ {19, 16, 98, 1}
+};
+
+static const char gAddRoundRectStrings[] =
+ "\0"
+ "rx\0"
+ "ry"
+;
+
+static const SkMemberInfo gAddRoundRectInfo[] = {
+ {0, 6, 18, 5},
+ {1, 28, 98, 1},
+ {4, 32, 98, 1}
+};
+
+static const char gUnknown2Strings[] =
+ "begin\0"
+ "blend\0"
+ "dur\0"
+ "dynamic\0"
+ "field\0"
+ "formula\0"
+ "from\0"
+ "mirror\0"
+ "repeat\0"
+ "reset\0"
+ "target\0"
+ "to\0"
+ "values"
+;
+
+static const SkMemberInfo gUnknown2Info[] = {
+ {0, 4, 71, 1},
+ {6, 8, 119, 98},
+ {12, 16, 71, 1},
+ {16, -1, 67, 26},
+ {24, 20, 108, 1},
+ {30, 24, 40, 1},
+ {38, 28, 40, 1},
+ {43, -2, 67, 26},
+ {50, 32, 98, 1},
+ {57, -3, 67, 26},
+ {63, 36, 40, 1},
+ {70, 40, 40, 1},
+ {73, -4, 67, 40}
+};
+
+static const char gAnimateFieldStrings[] =
+ ""
+;
+
+static const SkMemberInfo gAnimateFieldInfo[] = {
+ {0, 8, 18, 13}
+};
+
+static const char gApplyStrings[] =
+ "animator\0"
+ "begin\0"
+ "dontDraw\0"
+ "dynamicScope\0"
+ "interval\0"
+ "mode\0"
+ "pickup\0"
+ "restore\0"
+ "scope\0"
+ "step\0"
+ "steps\0"
+ "time\0"
+ "transition"
+;
+
+static const SkMemberInfo gApplyInfo[] = {
+ {0, -1, 67, 10},
+ {9, 4, 71, 1},
+ {15, 8, 26, 1},
+ {24, 12, 108, 1},
+ {37, 16, 71, 1},
+ {46, 20, 13, 1},
+ {51, 24, 26, 1},
+ {58, 28, 26, 1},
+ {66, 32, 37, 1},
+ {72, -2, 67, 96},
+ {77, 36, 96, 1},
+ {83, -3, 67, 71},
+ {88, 40, 14, 1}
+};
+
+static const char gUnknown3Strings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gUnknown3Info[] = {
+ {0, 36, 98, 1},
+ {2, 40, 98, 1}
+};
+
+static const char gBitmapStrings[] =
+ "\0"
+ "erase\0"
+ "format\0"
+ "height\0"
+ "rowBytes\0"
+ "width"
+;
+
+static const SkMemberInfo gDrawBitmapInfo[] = {
+ {0, 11, 18, 2},
+ {1, -1, 67, 15},
+ {7, 44, 21, 1},
+ {14, 48, 96, 1},
+ {21, 52, 96, 1},
+ {30, 56, 96, 1}
+};
+
+static const char gBitmapShaderStrings[] =
+ "\0"
+ "filterType\0"
+ "image"
+;
+
+static const SkMemberInfo gDrawBitmapShaderInfo[] = {
+ {0, 66, 18, 2},
+ {1, 16, 47, 1},
+ {12, 20, 17, 1}
+};
+
+static const char gBlurStrings[] =
+ "blurStyle\0"
+ "radius"
+;
+
+static const SkMemberInfo gDrawBlurInfo[] = {
+ {0, 12, 63, 1},
+ {10, 8, 98, 1}
+};
+
+static const char gBoundsStrings[] =
+ "\0"
+ "inval"
+;
+
+static const SkMemberInfo gDisplayBoundsInfo[] = {
+ {0, 57, 18, 7},
+ {1, 32, 26, 1}
+};
+
+static const char gClipStrings[] =
+ "path\0"
+ "rectangle"
+;
+
+static const SkMemberInfo gDrawClipInfo[] = {
+ {0, 8, 74, 1},
+ {5, 4, 91, 1}
+};
+
+static const char gColorStrings[] =
+ "alpha\0"
+ "blue\0"
+ "color\0"
+ "green\0"
+ "hue\0"
+ "red\0"
+ "saturation\0"
+ "value"
+;
+
+static const SkMemberInfo gDrawColorInfo[] = {
+ {0, -1, 67, 98},
+ {6, -2, 67, 98},
+ {11, 8, 15, 1},
+ {17, -3, 67, 98},
+ {23, -4, 67, 98},
+ {27, -5, 67, 98},
+ {31, -6, 67, 98},
+ {42, -7, 67, 98}
+};
+
+static const char gCubicToStrings[] =
+ "x1\0"
+ "x2\0"
+ "x3\0"
+ "y1\0"
+ "y2\0"
+ "y3"
+;
+
+static const SkMemberInfo gCubicToInfo[] = {
+ {0, 8, 98, 1},
+ {3, 16, 98, 1},
+ {6, 24, 98, 1},
+ {9, 12, 98, 1},
+ {12, 20, 98, 1},
+ {15, 28, 98, 1}
+};
+
+static const char gDashStrings[] =
+ "intervals\0"
+ "phase"
+;
+
+static const SkMemberInfo gDashInfo[] = {
+ {0, 8, 119, 98},
+ {10, 16, 98, 1}
+};
+
+static const char gDataStrings[] =
+ "\0"
+ "name"
+;
+
+static const SkMemberInfo gDataInfo[] = {
+ {0, 32, 18, 3},
+ {1, 16, 108, 1}
+};
+
+static const char gDiscreteStrings[] =
+ "deviation\0"
+ "segLength"
+;
+
+static const SkMemberInfo gDiscreteInfo[] = {
+ {0, 8, 98, 1},
+ {10, 12, 98, 1}
+};
+
+static const char gDrawToStrings[] =
+ "drawOnce\0"
+ "use"
+;
+
+static const SkMemberInfo gDrawToInfo[] = {
+ {0, 36, 26, 1},
+ {9, 40, 19, 1}
+};
+
+static const char gEmbossStrings[] =
+ "ambient\0"
+ "direction\0"
+ "radius\0"
+ "specular"
+;
+
+static const SkMemberInfo gDrawEmbossInfo[] = {
+ {0, -1, 67, 98},
+ {8, 8, 119, 98},
+ {18, 16, 98, 1},
+ {25, -2, 67, 98}
+};
+
+static const char gEventStrings[] =
+ "code\0"
+ "disable\0"
+ "key\0"
+ "keys\0"
+ "kind\0"
+ "target\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gDisplayEventInfo[] = {
+ {0, 4, 43, 1},
+ {5, 8, 26, 1},
+ {13, -1, 67, 108},
+ {17, -2, 67, 108},
+ {22, 12, 44, 1},
+ {27, 16, 108, 1},
+ {34, 20, 98, 1},
+ {36, 24, 98, 1}
+};
+
+static const char gFromPathStrings[] =
+ "mode\0"
+ "offset\0"
+ "path"
+;
+
+static const SkMemberInfo gFromPathInfo[] = {
+ {0, 8, 49, 1},
+ {5, 12, 98, 1},
+ {12, 16, 74, 1}
+};
+
+static const char gUnknown4Strings[] =
+ "\0"
+ "offsets\0"
+ "unitMapper"
+;
+
+static const SkMemberInfo gUnknown4Info[] = {
+ {0, 66, 18, 2},
+ {1, 16, 119, 98},
+ {9, 24, 108, 1}
+};
+
+static const char gGStrings[] =
+ "condition\0"
+ "enableCondition"
+;
+
+static const SkMemberInfo gGInfo[] = {
+ {0, 4, 40, 1},
+ {10, 8, 40, 1}
+};
+
+static const char gHitClearStrings[] =
+ "targets"
+;
+
+static const SkMemberInfo gHitClearInfo[] = {
+ {0, 4, 119, 36}
+};
+
+static const char gHitTestStrings[] =
+ "bullets\0"
+ "hits\0"
+ "targets\0"
+ "value"
+;
+
+static const SkMemberInfo gHitTestInfo[] = {
+ {0, 4, 119, 36},
+ {8, 12, 119, 96},
+ {13, 20, 119, 36},
+ {21, 28, 26, 1}
+};
+
+static const char gImageStrings[] =
+ "\0"
+ "base64\0"
+ "src"
+;
+
+static const SkMemberInfo gImageInfo[] = {
+ {0, 11, 18, 2},
+ {1, 44, 16, 2},
+ {8, 52, 108, 1}
+};
+
+static const char gIncludeStrings[] =
+ "src"
+;
+
+static const SkMemberInfo gIncludeInfo[] = {
+ {0, 4, 108, 1}
+};
+
+static const char gInputStrings[] =
+ "s32\0"
+ "scalar\0"
+ "string"
+;
+
+static const SkMemberInfo gInputInfo[] = {
+ {0, 4, 96, 1},
+ {4, 8, 98, 1},
+ {11, 12, 108, 1}
+};
+
+static const char gLineStrings[] =
+ "x1\0"
+ "x2\0"
+ "y1\0"
+ "y2"
+;
+
+static const SkMemberInfo gLineInfo[] = {
+ {0, 12, 98, 1},
+ {3, 16, 98, 1},
+ {6, 20, 98, 1},
+ {9, 24, 98, 1}
+};
+
+static const char gLineToStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gLineToInfo[] = {
+ {0, 8, 98, 1},
+ {2, 12, 98, 1}
+};
+
+static const char gLinearGradientStrings[] =
+ "\0"
+ "points"
+;
+
+static const SkMemberInfo gLinearGradientInfo[] = {
+ {0, 26, 18, 3},
+ {1, 48, 77, 4}
+};
+
+static const char gMatrixStrings[] =
+ "matrix\0"
+ "perspectX\0"
+ "perspectY\0"
+ "rotate\0"
+ "scale\0"
+ "scaleX\0"
+ "scaleY\0"
+ "skewX\0"
+ "skewY\0"
+ "translate\0"
+ "translateX\0"
+ "translateY"
+;
+
+static const SkMemberInfo gDrawMatrixInfo[] = {
+ {0, 4, 119, 98},
+ {7, -1, 67, 98},
+ {17, -2, 67, 98},
+ {27, -3, 67, 98},
+ {34, -4, 67, 98},
+ {40, -5, 67, 98},
+ {47, -6, 67, 98},
+ {54, -7, 67, 98},
+ {60, -8, 67, 98},
+ {66, -9, 67, 77},
+ {76, -10, 67, 98},
+ {87, -11, 67, 98}
+};
+
+static const char gMoveStrings[] =
+ ""
+;
+
+static const SkMemberInfo gMoveInfo[] = {
+ {0, 1, 18, 4}
+};
+
+static const char gMoveToStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gMoveToInfo[] = {
+ {0, 8, 98, 1},
+ {2, 12, 98, 1}
+};
+
+static const char gMovieStrings[] =
+ "src"
+;
+
+static const SkMemberInfo gMovieInfo[] = {
+ {0, 4, 108, 1}
+};
+
+static const char gOvalStrings[] =
+ ""
+;
+
+static const SkMemberInfo gOvalInfo[] = {
+ {0, 57, 18, 7}
+};
+
+static const char gPaintStrings[] =
+ "antiAlias\0"
+ "ascent\0"
+ "color\0"
+ "descent\0"
+ "filterType\0"
+ "linearText\0"
+ "maskFilter\0"
+ "measureText\0"
+ "pathEffect\0"
+ "shader\0"
+ "strikeThru\0"
+ "stroke\0"
+ "strokeCap\0"
+ "strokeJoin\0"
+ "strokeMiter\0"
+ "strokeWidth\0"
+ "style\0"
+ "textAlign\0"
+ "textScaleX\0"
+ "textSize\0"
+ "textSkewX\0"
+ "textTracking\0"
+ "typeface\0"
+ "underline\0"
+ "xfermode"
+;
+
+static const SkMemberInfo gDrawPaintInfo[] = {
+ {0, 4, 26, 1},
+ {10, -1, 67, 98},
+ {17, 8, 31, 1},
+ {23, -2, 67, 98},
+ {31, 12, 47, 1},
+ {42, 16, 26, 1},
+ {53, 20, 62, 1},
+ {64, -1, 66, 98},
+ {76, 24, 76, 1},
+ {87, 28, 102, 1},
+ {94, 32, 26, 1},
+ {105, 36, 26, 1},
+ {112, 40, 27, 1},
+ {122, 44, 58, 1},
+ {133, 48, 98, 1},
+ {145, 52, 98, 1},
+ {157, 56, 109, 1},
+ {163, 60, 9, 1},
+ {173, 64, 98, 1},
+ {184, 68, 98, 1},
+ {193, 72, 98, 1},
+ {203, 76, 98, 1},
+ {216, 80, 120, 1},
+ {225, 84, 26, 1},
+ {235, 88, 121, 1}
+};
+
+static const char gPathStrings[] =
+ "d\0"
+ "fillType\0"
+ "length"
+;
+
+static const SkMemberInfo gDrawPathInfo[] = {
+ {0, 32, 108, 1},
+ {2, -1, 67, 46},
+ {11, -2, 67, 98}
+};
+
+static const char gUnknown5Strings[] =
+ "x\0"
+ "y\0"
+ "z"
+;
+
+static const SkMemberInfo gUnknown5Info[] = {
+ {0, 0, 98, 1},
+ {2, 4, 98, 1},
+ {4, 8, 98, 1}
+};
+
+static const char gPointStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gDrawPointInfo[] = {
+ {0, 4, 98, 1},
+ {2, 8, 98, 1}
+};
+
+static const char gPolyToPolyStrings[] =
+ "destination\0"
+ "source"
+;
+
+static const SkMemberInfo gPolyToPolyInfo[] = {
+ {0, 12, 80, 1},
+ {12, 8, 80, 1}
+};
+
+static const char gPolygonStrings[] =
+ ""
+;
+
+static const SkMemberInfo gPolygonInfo[] = {
+ {0, 47, 18, 1}
+};
+
+static const char gPolylineStrings[] =
+ "points"
+;
+
+static const SkMemberInfo gPolylineInfo[] = {
+ {0, 56, 119, 98}
+};
+
+static const char gPostStrings[] =
+ "delay\0"
+ "initialized\0"
+ "mode\0"
+ "sink\0"
+ "target\0"
+ "type"
+;
+
+static const SkMemberInfo gPostInfo[] = {
+ {0, 4, 71, 1},
+ {6, 8, 26, 1},
+ {18, 12, 45, 1},
+ {23, -1, 67, 108},
+ {28, -2, 67, 108},
+ {35, -3, 67, 108}
+};
+
+static const char gQuadToStrings[] =
+ "x1\0"
+ "x2\0"
+ "y1\0"
+ "y2"
+;
+
+static const SkMemberInfo gQuadToInfo[] = {
+ {0, 8, 98, 1},
+ {3, 16, 98, 1},
+ {6, 12, 98, 1},
+ {9, 20, 98, 1}
+};
+
+static const char gRCubicToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRCubicToInfo[] = {
+ {0, 18, 18, 6}
+};
+
+static const char gRLineToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRLineToInfo[] = {
+ {0, 34, 18, 2}
+};
+
+static const char gRMoveToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRMoveToInfo[] = {
+ {0, 38, 18, 2}
+};
+
+static const char gRQuadToStrings[] =
+ ""
+;
+
+static const SkMemberInfo gRQuadToInfo[] = {
+ {0, 49, 18, 4}
+};
+
+static const char gRadialGradientStrings[] =
+ "\0"
+ "center\0"
+ "radius"
+;
+
+static const SkMemberInfo gRadialGradientInfo[] = {
+ {0, 26, 18, 3},
+ {1, 48, 77, 2},
+ {8, 56, 98, 1}
+};
+
+static const char gRandomStrings[] =
+ "blend\0"
+ "max\0"
+ "min\0"
+ "random\0"
+ "seed"
+;
+
+static const SkMemberInfo gDisplayRandomInfo[] = {
+ {0, 4, 98, 1},
+ {6, 12, 98, 1},
+ {10, 8, 98, 1},
+ {14, 1, 67, 98},
+ {21, -2, 67, 96}
+};
+
+static const char gRectToRectStrings[] =
+ "destination\0"
+ "source"
+;
+
+static const SkMemberInfo gRectToRectInfo[] = {
+ {0, 12, 91, 1},
+ {12, 8, 91, 1}
+};
+
+static const char gRectangleStrings[] =
+ "bottom\0"
+ "height\0"
+ "left\0"
+ "needsRedraw\0"
+ "right\0"
+ "top\0"
+ "width"
+;
+
+static const SkMemberInfo gRectangleInfo[] = {
+ {0, 24, 98, 1},
+ {7, -1, 67, 98},
+ {14, 12, 98, 1},
+ {19, -2, 67, 26},
+ {31, 20, 98, 1},
+ {37, 16, 98, 1},
+ {41, -3, 67, 98}
+};
+
+static const char gRemoveStrings[] =
+ "offset\0"
+ "where"
+;
+
+static const SkMemberInfo gRemoveInfo[] = {
+ {0, 8, 96, 1},
+ {7, 16, 37, 1}
+};
+
+static const char gReplaceStrings[] =
+ ""
+;
+
+static const SkMemberInfo gReplaceInfo[] = {
+ {0, 1, 18, 4}
+};
+
+static const char gRotateStrings[] =
+ "center\0"
+ "degrees"
+;
+
+static const SkMemberInfo gRotateInfo[] = {
+ {0, 12, 77, 2},
+ {7, 8, 98, 1}
+};
+
+static const char gRoundRectStrings[] =
+ "\0"
+ "rx\0"
+ "ry"
+;
+
+static const SkMemberInfo gRoundRectInfo[] = {
+ {0, 57, 18, 7},
+ {1, 32, 98, 1},
+ {4, 36, 98, 1}
+};
+
+static const char gS32Strings[] =
+ "value"
+;
+
+static const SkMemberInfo gS32Info[] = {
+ {0, 4, 96, 1}
+};
+
+static const char gScalarStrings[] =
+ "value"
+;
+
+static const SkMemberInfo gScalarInfo[] = {
+ {0, 4, 98, 1}
+};
+
+static const char gScaleStrings[] =
+ "center\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gScaleInfo[] = {
+ {0, 16, 77, 2},
+ {7, 8, 98, 1},
+ {9, 12, 98, 1}
+};
+
+static const char gSetStrings[] =
+ "begin\0"
+ "dur\0"
+ "dynamic\0"
+ "field\0"
+ "formula\0"
+ "reset\0"
+ "target\0"
+ "to"
+;
+
+static const SkMemberInfo gSetInfo[] = {
+ {0, 4, 71, 1},
+ {6, 16, 71, 1},
+ {10, -1, 67, 26},
+ {18, 20, 108, 1},
+ {24, 24, 40, 1},
+ {32, -3, 67, 26},
+ {38, 36, 40, 1},
+ {45, 40, 40, 1}
+};
+
+static const char gShaderStrings[] =
+ "matrix\0"
+ "tileMode"
+;
+
+static const SkMemberInfo gShaderInfo[] = {
+ {0, 8, 65, 1},
+ {7, 12, 116, 1}
+};
+
+static const char gSkewStrings[] =
+ "center\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gSkewInfo[] = {
+ {0, 16, 77, 2},
+ {7, 8, 98, 1},
+ {9, 12, 98, 1}
+};
+
+static const char g3D_CameraStrings[] =
+ "axis\0"
+ "hackHeight\0"
+ "hackWidth\0"
+ "location\0"
+ "observer\0"
+ "patch\0"
+ "zenith"
+;
+
+static const SkMemberInfo g3D_CameraInfo[] = {
+ {0, 24, 106, 3},
+ {5, 8, 98, 1},
+ {16, 4, 98, 1},
+ {26, 12, 106, 3},
+ {35, 48, 106, 3},
+ {44, 96, 105, 1},
+ {50, 36, 106, 3}
+};
+
+static const char g3D_PatchStrings[] =
+ "origin\0"
+ "rotateDegrees\0"
+ "u\0"
+ "v"
+;
+
+static const SkMemberInfo g3D_PatchInfo[] = {
+ {0, 28, 106, 3},
+ {7, -1, 66, 98},
+ {21, 4, 106, 3},
+ {23, 16, 106, 3}
+};
+
+static const char gUnknown6Strings[] =
+ "x\0"
+ "y\0"
+ "z"
+;
+
+static const SkMemberInfo gUnknown6Info[] = {
+ {0, 0, 98, 1},
+ {2, 4, 98, 1},
+ {4, 8, 98, 1}
+};
+
+static const char gSnapshotStrings[] =
+ "filename\0"
+ "quality\0"
+ "sequence\0"
+ "type"
+;
+
+static const SkMemberInfo gSnapshotInfo[] = {
+ {0, 4, 108, 1},
+ {9, 8, 98, 1},
+ {17, 12, 26, 1},
+ {26, 16, 20, 1}
+};
+
+static const char gStringStrings[] =
+ "length\0"
+ "slice\0"
+ "value"
+;
+
+static const SkMemberInfo gStringInfo[] = {
+ {0, -1, 67, 96},
+ {7, -1, 66, 108},
+ {13, 4, 108, 1}
+};
+
+static const char gTextStrings[] =
+ "length\0"
+ "text\0"
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gTextInfo[] = {
+ {0, -1, 67, 96},
+ {7, 12, 108, 1},
+ {12, 16, 98, 1},
+ {14, 20, 98, 1}
+};
+
+static const char gTextBoxStrings[] =
+ "\0"
+ "mode\0"
+ "spacingAdd\0"
+ "spacingAlign\0"
+ "spacingMul\0"
+ "text"
+;
+
+static const SkMemberInfo gTextBoxInfo[] = {
+ {0, 57, 18, 7},
+ {1, 44, 113, 1},
+ {6, 40, 98, 1},
+ {17, 48, 112, 1},
+ {30, 36, 98, 1},
+ {41, 32, 108, 1}
+};
+
+static const char gTextOnPathStrings[] =
+ "offset\0"
+ "path\0"
+ "text"
+;
+
+static const SkMemberInfo gTextOnPathInfo[] = {
+ {0, 12, 98, 1},
+ {7, 16, 74, 1},
+ {12, 20, 110, 1}
+};
+
+static const char gTextToPathStrings[] =
+ "path\0"
+ "text"
+;
+
+static const SkMemberInfo gTextToPathInfo[] = {
+ {0, 4, 74, 1},
+ {5, 8, 110, 1}
+};
+
+static const char gTranslateStrings[] =
+ "x\0"
+ "y"
+;
+
+static const SkMemberInfo gTranslateInfo[] = {
+ {0, 8, 98, 1},
+ {2, 12, 98, 1}
+};
+
+static const char gTypedArrayStrings[] =
+ "length\0"
+ "values"
+;
+
+static const SkMemberInfo gTypedArrayInfo[] = {
+ {0, -1, 67, 96},
+ {7, 4, 119, 0}
+};
+
+static const char gTypefaceStrings[] =
+ "fontName"
+;
+
+static const SkMemberInfo gTypefaceInfo[] = {
+ {0, 8, 108, 1}
+};
+
+static const SkMemberInfo* const gInfoTables[] = {
+ gMathInfo,
+ gAddInfo,
+ gAddCircleInfo,
+ gUnknown1Info,
+ gAddOvalInfo,
+ gAddPathInfo,
+ gAddRectangleInfo,
+ gAddRoundRectInfo,
+ gUnknown2Info,
+ gAnimateFieldInfo,
+ gApplyInfo,
+ gUnknown3Info,
+ gDrawBitmapInfo,
+ gDrawBitmapShaderInfo,
+ gDrawBlurInfo,
+ gDisplayBoundsInfo,
+ gDrawClipInfo,
+ gDrawColorInfo,
+ gCubicToInfo,
+ gDashInfo,
+ gDataInfo,
+ gDiscreteInfo,
+ gDrawToInfo,
+ gDrawEmbossInfo,
+ gDisplayEventInfo,
+ gFromPathInfo,
+ gUnknown4Info,
+ gGInfo,
+ gHitClearInfo,
+ gHitTestInfo,
+ gImageInfo,
+ gIncludeInfo,
+ gInputInfo,
+ gLineInfo,
+ gLineToInfo,
+ gLinearGradientInfo,
+ gDrawMatrixInfo,
+ gMoveInfo,
+ gMoveToInfo,
+ gMovieInfo,
+ gOvalInfo,
+ gDrawPaintInfo,
+ gDrawPathInfo,
+ gUnknown5Info,
+ gDrawPointInfo,
+ gPolyToPolyInfo,
+ gPolygonInfo,
+ gPolylineInfo,
+ gPostInfo,
+ gQuadToInfo,
+ gRCubicToInfo,
+ gRLineToInfo,
+ gRMoveToInfo,
+ gRQuadToInfo,
+ gRadialGradientInfo,
+ gDisplayRandomInfo,
+ gRectToRectInfo,
+ gRectangleInfo,
+ gRemoveInfo,
+ gReplaceInfo,
+ gRotateInfo,
+ gRoundRectInfo,
+ gS32Info,
+ gScalarInfo,
+ gScaleInfo,
+ gSetInfo,
+ gShaderInfo,
+ gSkewInfo,
+ g3D_CameraInfo,
+ g3D_PatchInfo,
+ gUnknown6Info,
+ gSnapshotInfo,
+ gStringInfo,
+ gTextInfo,
+ gTextBoxInfo,
+ gTextOnPathInfo,
+ gTextToPathInfo,
+ gTranslateInfo,
+ gTypedArrayInfo,
+ gTypefaceInfo,
+};
+
+static const unsigned char gInfoCounts[] = {
+ 26,4,4,1,1,2,5,3,13,1,13,2,6,3,2,2,2,8,6,
+ 2,2,2,2,4,8,3,3,2,1,4,3,1,3,4,2,2,12,1,2,
+ 1,1,25,3,3,2,2,1,1,6,4,1,1,1,1,3,5,2,7,2,
+ 1,2,3,1,1,3,8,2,3,7,4,3,4,3,4,6,3,2,2,2,
+ 1
+};
+
+static const unsigned char gTypeIDs[] = {
+ 1, // Math
+ 2, // Add
+ 3, // AddCircle
+ 4, // Unknown1
+ 5, // AddOval
+ 6, // AddPath
+ 7, // AddRectangle
+ 8, // AddRoundRect
+ 10, // Unknown2
+ 11, // AnimateField
+ 12, // Apply
+ 17, // Unknown3
+ 19, // Bitmap
+ 22, // BitmapShader
+ 23, // Blur
+ 25, // Bounds
+ 29, // Clip
+ 31, // Color
+ 32, // CubicTo
+ 33, // Dash
+ 34, // Data
+ 35, // Discrete
+ 38, // DrawTo
+ 41, // Emboss
+ 42, // Event
+ 48, // FromPath
+ 51, // Unknown4
+ 52, // G
+ 53, // HitClear
+ 54, // HitTest
+ 55, // Image
+ 56, // Include
+ 57, // Input
+ 59, // Line
+ 60, // LineTo
+ 61, // LinearGradient
+ 65, // Matrix
+ 68, // Move
+ 69, // MoveTo
+ 70, // Movie
+ 72, // Oval
+ 73, // Paint
+ 74, // Path
+ 77, // Unknown5
+ 78, // Point
+ 79, // PolyToPoly
+ 80, // Polygon
+ 81, // Polyline
+ 82, // Post
+ 83, // QuadTo
+ 84, // RCubicTo
+ 85, // RLineTo
+ 86, // RMoveTo
+ 87, // RQuadTo
+ 88, // RadialGradient
+ 89, // Random
+ 90, // RectToRect
+ 91, // Rectangle
+ 92, // Remove
+ 93, // Replace
+ 94, // Rotate
+ 95, // RoundRect
+ 96, // S32
+ 98, // Scalar
+ 99, // Scale
+ 101, // Set
+ 102, // Shader
+ 103, // Skew
+ 104, // 3D_Camera
+ 105, // 3D_Patch
+ 106, // Unknown6
+ 107, // Snapshot
+ 108, // String
+ 110, // Text
+ 111, // TextBox
+ 114, // TextOnPath
+ 115, // TextToPath
+ 117, // Translate
+ 119, // TypedArray
+ 120, // Typeface
+
+};
+
+static const int kTypeIDs = 80;
+
+static const char* const gInfoNames[] = {
+ gMathStrings,
+ gAddStrings,
+ gAddCircleStrings,
+ gUnknown1Strings,
+ gAddOvalStrings,
+ gAddPathStrings,
+ gAddRectangleStrings,
+ gAddRoundRectStrings,
+ gUnknown2Strings,
+ gAnimateFieldStrings,
+ gApplyStrings,
+ gUnknown3Strings,
+ gBitmapStrings,
+ gBitmapShaderStrings,
+ gBlurStrings,
+ gBoundsStrings,
+ gClipStrings,
+ gColorStrings,
+ gCubicToStrings,
+ gDashStrings,
+ gDataStrings,
+ gDiscreteStrings,
+ gDrawToStrings,
+ gEmbossStrings,
+ gEventStrings,
+ gFromPathStrings,
+ gUnknown4Strings,
+ gGStrings,
+ gHitClearStrings,
+ gHitTestStrings,
+ gImageStrings,
+ gIncludeStrings,
+ gInputStrings,
+ gLineStrings,
+ gLineToStrings,
+ gLinearGradientStrings,
+ gMatrixStrings,
+ gMoveStrings,
+ gMoveToStrings,
+ gMovieStrings,
+ gOvalStrings,
+ gPaintStrings,
+ gPathStrings,
+ gUnknown5Strings,
+ gPointStrings,
+ gPolyToPolyStrings,
+ gPolygonStrings,
+ gPolylineStrings,
+ gPostStrings,
+ gQuadToStrings,
+ gRCubicToStrings,
+ gRLineToStrings,
+ gRMoveToStrings,
+ gRQuadToStrings,
+ gRadialGradientStrings,
+ gRandomStrings,
+ gRectToRectStrings,
+ gRectangleStrings,
+ gRemoveStrings,
+ gReplaceStrings,
+ gRotateStrings,
+ gRoundRectStrings,
+ gS32Strings,
+ gScalarStrings,
+ gScaleStrings,
+ gSetStrings,
+ gShaderStrings,
+ gSkewStrings,
+ g3D_CameraStrings,
+ g3D_PatchStrings,
+ gUnknown6Strings,
+ gSnapshotStrings,
+ gStringStrings,
+ gTextStrings,
+ gTextBoxStrings,
+ gTextOnPathStrings,
+ gTextToPathStrings,
+ gTranslateStrings,
+ gTypedArrayStrings,
+ gTypefaceStrings
+};
+#endif
+#endif
diff --git a/animator/SkDisplayAdd.cpp b/animator/SkDisplayAdd.cpp
new file mode 100644
index 00000000..2cb5e979
--- /dev/null
+++ b/animator/SkDisplayAdd.cpp
@@ -0,0 +1,245 @@
+
+/*
+ * 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 "SkDisplayAdd.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayApply.h"
+#include "SkDisplayList.h"
+#include "SkDrawable.h"
+#include "SkDrawGroup.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAdd::fInfo[] = {
+ SK_MEMBER(mode, AddMode),
+ SK_MEMBER(offset, Int),
+ SK_MEMBER(use, Drawable),
+ SK_MEMBER(where, Drawable)
+};
+
+#endif
+
+// start here;
+// add onEndElement to turn where string into f_Where
+// probably need new SkAnimateMaker::resolve flavor that takes
+// where="id", where="event-target" or not-specified
+// offset="#" (implements before, after, and index if no 'where')
+
+DEFINE_GET_MEMBER(SkAdd);
+
+SkAdd::SkAdd() : mode(kMode_indirect),
+ offset(SK_MaxS32), use(NULL), where(NULL) {
+}
+
+SkDisplayable* SkAdd::deepCopy(SkAnimateMaker* maker) {
+ SkDrawable* saveUse = use;
+ SkDrawable* saveWhere = where;
+ use = NULL;
+ where = NULL;
+ SkAdd* copy = (SkAdd*) INHERITED::deepCopy(maker);
+ copy->use = use = saveUse;
+ copy->where = where = saveWhere;
+ return copy;
+}
+
+bool SkAdd::draw(SkAnimateMaker& maker) {
+ SkASSERT(use);
+ SkASSERT(use->isDrawable());
+ if (mode == kMode_indirect)
+ use->draw(maker);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkAdd::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpAttrs(maker);
+ if (where)
+ SkDebugf("where=\"%s\" ", where->id);
+ if (mode == kMode_immediate)
+ SkDebugf("mode=\"immediate\" ");
+ SkDebugf(">\n");
+ SkDisplayList::fIndent += 4;
+ int save = SkDisplayList::fDumpIndex;
+ if (use) //just in case
+ use->dump(maker);
+ SkDisplayList::fIndent -= 4;
+ SkDisplayList::fDumpIndex = save;
+ dumpEnd(maker);
+}
+#endif
+
+bool SkAdd::enable(SkAnimateMaker& maker ) {
+ SkDisplayTypes type = getType();
+ SkDisplayList& displayList = maker.fDisplayList;
+ SkTDDrawableArray* parentList = displayList.getDrawList();
+ if (type == SkType_Add) {
+ if (use == NULL) // not set in apply yet
+ return true;
+ }
+ bool skipAddToParent = true;
+ SkASSERT(type != SkType_Replace || where);
+ SkTDDrawableArray* grandList SK_INIT_TO_AVOID_WARNING;
+ SkGroup* parentGroup = NULL;
+ SkGroup* thisGroup = NULL;
+ int index = where ? displayList.findGroup(where, &parentList, &parentGroup,
+ &thisGroup, &grandList) : 0;
+ if (index < 0)
+ return true;
+ int max = parentList->count();
+ if (where == NULL && type == SkType_Move)
+ index = max;
+ if (offset != SK_MaxS32) {
+ index += offset;
+ if (index > max) {
+ maker.setErrorCode(SkDisplayXMLParserError::kIndexOutOfRange);
+ return true; // caller should not add
+ }
+ }
+ if (offset < 0 && where == NULL)
+ index += max + 1;
+ switch (type) {
+ case SkType_Add:
+ if (offset == SK_MaxS32 && where == NULL) {
+ if (use->isDrawable()) {
+ skipAddToParent = mode == kMode_immediate;
+ if (skipAddToParent) {
+ if (where == NULL) {
+ SkTDDrawableArray* useParentList;
+ index = displayList.findGroup(this, &useParentList, &parentGroup,
+ &thisGroup, &grandList);
+ if (index >= 0) {
+ parentGroup->markCopySize(index);
+ parentGroup->markCopySet(index);
+ useParentList->begin()[index] = use;
+ break;
+ }
+ }
+ *parentList->append() = use;
+ }
+ }
+ break;
+ } else {
+ if (thisGroup)
+ thisGroup->markCopySize(index);
+ *parentList->insert(index) = use;
+ if (thisGroup)
+ thisGroup->markCopySet(index);
+ if (use->isApply())
+ ((SkApply*) use)->setEmbedded();
+ }
+ break;
+ case SkType_Move: {
+ int priorLocation = parentList->find(use);
+ if (priorLocation < 0)
+ break;
+ *parentList->insert(index) = use;
+ if (index < priorLocation)
+ priorLocation++;
+ parentList->remove(priorLocation);
+ } break;
+ case SkType_Remove: {
+ SkDisplayable* old = (*parentList)[index];
+ if (((SkRemove*)(this))->fDelete) {
+ delete old;
+ goto noHelperNeeded;
+ }
+ for (int inner = 0; inner < maker.fChildren.count(); inner++) {
+ SkDisplayable* child = maker.fChildren[inner];
+ if (child == old || child->contains(old))
+ goto noHelperNeeded;
+ }
+ if (maker.fHelpers.find(old) < 0)
+ maker.helperAdd(old);
+noHelperNeeded:
+ parentList->remove(index);
+ } break;
+ case SkType_Replace:
+ if (thisGroup) {
+ thisGroup->markCopySize(index);
+ if (thisGroup->markedForDelete(index)) {
+ SkDisplayable* old = (*parentList)[index];
+ if (maker.fHelpers.find(old) < 0)
+ maker.helperAdd(old);
+ }
+ }
+ (*parentList)[index] = use;
+ if (thisGroup)
+ thisGroup->markCopySet(index);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ if (type == SkType_Remove)
+ return true;
+ if (use->hasEnable())
+ use->enable(maker);
+ return skipAddToParent; // append if indirect: *parentList->append() = this;
+}
+
+bool SkAdd::hasEnable() const {
+ return true;
+}
+
+void SkAdd::initialize() {
+ if (use)
+ use->initialize();
+}
+
+bool SkAdd::isDrawable() const {
+ return getType() == SkType_Add && mode == kMode_indirect && offset == SK_MaxS32 &&
+ where == NULL && use != NULL && use->isDrawable();
+}
+
+//SkDisplayable* SkAdd::resolveTarget(SkAnimateMaker& maker) {
+// return use;
+//}
+
+
+bool SkClear::enable(SkAnimateMaker& maker ) {
+ SkDisplayList& displayList = maker.fDisplayList;
+ displayList.clear();
+ return true;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkMove::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkMove);
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRemove::fInfo[] = {
+ SK_MEMBER_ALIAS(delete, fDelete, Boolean), // !!! experimental
+ SK_MEMBER(offset, Int),
+ SK_MEMBER(where, Drawable)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRemove);
+
+SkRemove::SkRemove() : fDelete(false) {
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkReplace::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkReplace);
diff --git a/animator/SkDisplayAdd.h b/animator/SkDisplayAdd.h
new file mode 100644
index 00000000..d16492b0
--- /dev/null
+++ b/animator/SkDisplayAdd.h
@@ -0,0 +1,71 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayAdd_DEFINED
+#define SkDisplayAdd_DEFINED
+
+#include "SkDrawable.h"
+#include "SkMemberInfo.h"
+
+class SkAdd : public SkDrawable {
+ DECLARE_MEMBER_INFO(Add);
+ SkAdd();
+
+ enum Mode {
+ kMode_indirect,
+ kMode_immediate
+ };
+
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool enable(SkAnimateMaker& );
+ virtual bool hasEnable() const;
+ virtual void initialize();
+ virtual bool isDrawable() const;
+protected:
+// struct _A {
+ Mode mode;
+ int32_t offset;
+ SkDrawable* use;
+ SkDrawable* where; // if NULL, offset becomes index
+// } A;
+private:
+ typedef SkDrawable INHERITED;
+};
+
+class SkClear : public SkDisplayable {
+ virtual bool enable(SkAnimateMaker& );
+};
+
+class SkMove : public SkAdd {
+ DECLARE_MEMBER_INFO(Move);
+private:
+ typedef SkAdd INHERITED;
+};
+
+class SkRemove : public SkAdd {
+ DECLARE_MEMBER_INFO(Remove);
+ SkRemove();
+protected:
+ SkBool fDelete;
+private:
+ friend class SkAdd;
+ typedef SkAdd INHERITED;
+};
+
+class SkReplace : public SkAdd {
+ DECLARE_MEMBER_INFO(Replace);
+private:
+ typedef SkAdd INHERITED;
+};
+
+#endif // SkDisplayAdd_DEFINED
diff --git a/animator/SkDisplayApply.cpp b/animator/SkDisplayApply.cpp
new file mode 100644
index 00000000..7f0ec1c8
--- /dev/null
+++ b/animator/SkDisplayApply.cpp
@@ -0,0 +1,804 @@
+
+/*
+ * 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 "SkDisplayApply.h"
+#include "SkAnimateActive.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateSet.h"
+#include "SkAnimatorScript.h"
+#include "SkDisplayType.h"
+#include "SkDrawGroup.h"
+#include "SkParse.h"
+#include "SkScript.h"
+#include "SkSystemEventTypes.h"
+#ifdef SK_DEBUG
+#include "SkTime.h"
+#endif
+#include <ctype.h>
+
+enum SkApply_Properties {
+ SK_PROPERTY(animator),
+ SK_PROPERTY(step),
+ SK_PROPERTY(steps),
+ SK_PROPERTY(time)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+// if no attibutes, enclosed displayable is both scope & target
+// only if both scope & target are specified, or if target and enclosed displayable, are scope and target different
+const SkMemberInfo SkApply::fInfo[] = {
+ SK_MEMBER_PROPERTY(animator, Animate),
+ SK_MEMBER(begin, MSec),
+ SK_MEMBER(dontDraw, Boolean),
+ SK_MEMBER(dynamicScope, String),
+ SK_MEMBER(interval, MSec), // recommended redraw interval
+ SK_MEMBER(mode, ApplyMode),
+#if 0
+ SK_MEMBER(pickup, Boolean),
+#endif
+ SK_MEMBER(restore, Boolean),
+ SK_MEMBER(scope, Drawable), // thing that scopes animation (unnamed enclosed displayable goes here)
+ SK_MEMBER_PROPERTY(step, Int),
+ SK_MEMBER_PROPERTY(steps, Int),
+ SK_MEMBER_PROPERTY(time, MSec),
+ SK_MEMBER(transition, ApplyTransition)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkApply);
+
+SkApply::SkApply() : begin(0), dontDraw(false), interval((SkMSec) -1), mode((Mode) -1), /*pickup(false), */
+ restore(false), scope(NULL), steps(-1), transition((Transition) -1), fActive(NULL), /*fCurrentScope(NULL),*/
+ fLastTime(0), fAppended(false), fContainsScope(false), fDeleteScope(false), fEmbedded(false),
+ fEnabled(false), fEnabling(false) {
+}
+
+SkApply::~SkApply() {
+ for (SkDrawable** curPtr = fScopes.begin(); curPtr < fScopes.end(); curPtr++)
+ delete *curPtr;
+ if (fDeleteScope)
+ delete scope;
+ // !!! caller must call maker.removeActive(fActive)
+ delete fActive;
+}
+
+void SkApply::activate(SkAnimateMaker& maker) {
+ if (fActive != NULL) {
+ if (fActive->fDrawIndex == 0 && fActive->fDrawMax == 0)
+ return; // if only one use, nothing more to do
+ if (restore == false)
+ return; // all share same state, regardless of instance number
+ bool save = fActive->initializeSave();
+ fActive->fixInterpolator(save);
+ } else {
+ fActive = new SkActive(*this, maker);
+ fActive->init();
+ maker.appendActive(fActive);
+ if (restore) {
+ fActive->initializeSave();
+ int animators = fAnimators.count();
+ for (int index = 0; index < animators; index++)
+ fActive->saveInterpolatorValues(index);
+ }
+ }
+}
+
+void SkApply::append(SkApply* apply) {
+ if (fActive == NULL)
+ return;
+ int oldCount = fActive->fAnimators.count();
+ fActive->append(apply);
+ if (restore) {
+ fActive->appendSave(oldCount);
+ int newCount = fActive->fAnimators.count();
+ for (int index = oldCount; index < newCount; index++)
+ fActive->saveInterpolatorValues(index);
+ }
+}
+
+void SkApply::applyValues(int animatorIndex, SkOperand* values, int count,
+ SkDisplayTypes valuesType, SkMSec time)
+{
+ SkAnimateBase* animator = fActive->fAnimators[animatorIndex];
+ const SkMemberInfo * info = animator->fFieldInfo;
+ SkASSERT(animator);
+ SkASSERT(info != NULL);
+ SkDisplayTypes type = (SkDisplayTypes) info->fType;
+ SkDisplayable* target = getTarget(animator);
+ if (animator->hasExecute() || type == SkType_MemberFunction || type == SkType_MemberProperty) {
+ SkDisplayable* executor = animator->hasExecute() ? animator : target;
+ if (type != SkType_MemberProperty) {
+ SkTDArray<SkScriptValue> typedValues;
+ for (int index = 0; index < count; index++) {
+ SkScriptValue temp;
+ temp.fType = valuesType;
+ temp.fOperand = values[index];
+ *typedValues.append() = temp;
+ }
+ executor->executeFunction(target, info->functionIndex(), typedValues, info->getType(), NULL);
+ } else {
+ SkScriptValue scriptValue;
+ scriptValue.fOperand = values[0];
+ scriptValue.fType = info->getType();
+ target->setProperty(info->propertyIndex(), scriptValue);
+ }
+ } else {
+ SkTypedArray converted;
+ if (type == SkType_ARGB) {
+ if (count == 4) {
+ // !!! assert that it is SkType_Float ?
+ animator->packARGB(&values->fScalar, count, &converted);
+ values = converted.begin();
+ count = converted.count();
+ } else {
+ SkASSERT(count == 1);
+ }
+ }
+// SkASSERT(type == SkType_ARGB || type == SkType_String ||info->isSettable());
+ if (type == SkType_String || type == SkType_DynamicString)
+ info->setString(target, values->fString);
+ else if (type == SkType_Drawable || type == SkType_Displayable)
+ target->setReference(info, values->fDisplayable);
+ else
+ info->setValue(target, values, count);
+ }
+}
+
+bool SkApply::contains(SkDisplayable* child) {
+ for (SkDrawable** curPtr = fScopes.begin(); curPtr < fScopes.end(); curPtr++) {
+ if (*curPtr == child || (*curPtr)->contains(child))
+ return true;
+ }
+ return fDeleteScope && scope == child;
+}
+
+SkDisplayable* SkApply::deepCopy(SkAnimateMaker* maker) {
+ SkDrawable* saveScope = scope;
+ scope = NULL;
+ SkApply* result = (SkApply*) INHERITED::deepCopy(maker);
+ result->scope = scope = saveScope;
+ SkAnimateBase** end = fAnimators.end();
+ for (SkAnimateBase** animPtr = fAnimators.begin(); animPtr < end; animPtr++) {
+ SkAnimateBase* anim = (SkAnimateBase*) (*animPtr)->deepCopy(maker);
+ *result->fAnimators.append() = anim;
+ maker->helperAdd(anim);
+ }
+ return result;
+}
+
+void SkApply::disable() {
+ //!!! this is the right thing to do, but has bad side effects because of other problems
+ // currently, if an apply is in a g and scopes a statement in another g, it ends up as members
+ // of both containers. The disabling here incorrectly disables both instances
+ // maybe the fEnabled flag needs to be moved to the fActive data so that both
+ // instances are not affected.
+// fEnabled = false;
+}
+
+bool SkApply::draw(SkAnimateMaker& maker) {
+ if (scope ==NULL)
+ return false;
+ if (scope->isApply() || scope->isDrawable() == false)
+ return false;
+ if (fEnabled == false)
+ enable(maker);
+ SkASSERT(scope);
+ activate(maker);
+ if (mode == kMode_immediate)
+ return fActive->draw();
+ bool result = interpolate(maker, maker.getInTime());
+ if (dontDraw == false) {
+// if (scope->isDrawable())
+ result |= scope->draw(maker);
+ }
+ if (restore) {
+ for (int index = 0; index < fActive->fAnimators.count(); index++)
+ endSave(index);
+ fActive->advance();
+ }
+ return result;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkApply::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ if (dynamicScope.isEmpty() == false)
+ SkDebugf("dynamicScope=\"%s\" ", dynamicScope.c_str());
+ if (dontDraw)
+ SkDebugf("dontDraw=\"true\" ");
+ if (begin != 0) //perhaps we want this no matter what?
+ SkDebugf("begin=\"%g\" ", (float) begin/1000.0f); //is this correct?
+ if (interval != (SkMSec) -1)
+ SkDebugf("interval=\"%g\" ", (float) interval/1000.0f);
+ if (steps != -1)
+ SkDebugf("steps=\"%d\" ", steps);
+ if (restore)
+ SkDebugf("restore=\"true\" ");
+ if (transition == kTransition_reverse)
+ SkDebugf("transition=\"reverse\" ");
+ if (mode == kMode_immediate) {
+ SkDebugf("mode=\"immediate\" ");
+ }
+ else if (mode == kMode_create) {
+ SkDebugf("mode=\"create\" ");
+ }
+ bool closedYet = false;
+ SkDisplayList::fIndent += 4;
+ int save = SkDisplayList::fDumpIndex;
+ if (scope) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ scope->dump(maker);
+ }
+ int index;
+// if (fActive) {
+ for (index = 0; index < fAnimators.count(); index++) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ SkAnimateBase* animator = fAnimators[index];
+ animator->dump(maker);
+// }
+ }
+ SkDisplayList::fIndent -= 4;
+ SkDisplayList::fDumpIndex = save;
+ if (closedYet)
+ dumpEnd(maker);
+ else
+ SkDebugf("/>\n");
+}
+#endif
+
+bool SkApply::enable(SkAnimateMaker& maker) {
+ fEnabled = true;
+ bool initialized = fActive != NULL;
+ if (dynamicScope.size() > 0)
+ enableDynamic(maker);
+ if (maker.fError.hasError())
+ return false;
+ int animators = fAnimators.count();
+ int index;
+ for (index = 0; index < animators; index++) {
+ SkAnimateBase* animator = fAnimators[index];
+ animator->fStart = maker.fEnableTime;
+ animator->fResetPending = animator->fReset;
+ }
+ if (scope && scope->isApply())
+ ((SkApply*) scope)->setEmbedded();
+/* if (mode == kMode_once) {
+ if (scope) {
+ activate(maker);
+ interpolate(maker, maker.fEnableTime);
+ inactivate(maker);
+ }
+ return true;
+ }*/
+ if ((mode == kMode_immediate || mode == kMode_create) && scope == NULL)
+ return false; // !!! error?
+ bool enableMe = scope && (scope->hasEnable() || scope->isApply() || scope->isDrawable() == false);
+ if ((mode == kMode_immediate && enableMe) || mode == kMode_create)
+ activate(maker); // for non-drawables like post, prime them here
+ if (mode == kMode_immediate && enableMe)
+ fActive->enable();
+ if (mode == kMode_create && scope != NULL) {
+ enableCreate(maker);
+ return true;
+ }
+ if (mode == kMode_immediate) {
+ return scope->isApply() || scope->isDrawable() == false;
+ }
+ refresh(maker);
+ SkDisplayList& displayList = maker.fDisplayList;
+ SkDrawable* drawable;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkString debugOut;
+ SkMSec time = maker.getAppTime();
+ debugOut.appendS32(time - maker.fDebugTimeBase);
+ debugOut.append(" apply enable id=");
+ debugOut.append(_id);
+ debugOut.append("; start=");
+ debugOut.appendS32(maker.fEnableTime - maker.fDebugTimeBase);
+ SkDebugf("%s\n", debugOut.c_str());
+#endif
+ if (scope == NULL || scope->isApply() || scope->getType() == SkType_Movie || scope->isDrawable() == false) {
+ activate(maker); // for non-drawables like post, prime them here
+ if (initialized) {
+ append(this);
+ }
+ fEnabling = true;
+ interpolate(maker, maker.fEnableTime);
+ fEnabling = false;
+ if (scope != NULL && dontDraw == false)
+ scope->enable(maker);
+ return true;
+ } else if (initialized && restore == false)
+ append(this);
+#if 0
+ bool wasActive = inactivate(maker); // start fresh
+ if (wasActive) {
+ activate(maker);
+ interpolate(maker, maker.fEnableTime);
+ return true;
+ }
+#endif
+// start here;
+ // now that one apply might embed another, only the parent apply should replace the scope
+ // or get appended to the display list
+ // similarly, an apply added by an add immediate has already been located in the display list
+ // and should not get moved or added again here
+ if (fEmbedded) {
+ return false; // already added to display list by embedder
+ }
+ drawable = (SkDrawable*) scope;
+ SkTDDrawableArray* parentList;
+ SkTDDrawableArray* grandList;
+ SkGroup* parentGroup;
+ SkGroup* thisGroup;
+ int old = displayList.findGroup(drawable, &parentList, &parentGroup, &thisGroup, &grandList);
+ if (old < 0)
+ goto append;
+ else if (fContainsScope) {
+ if ((*parentList)[old] != this || restore) {
+append:
+ if (parentGroup)
+ parentGroup->markCopySize(old);
+ if (parentList->count() < 10000) {
+ fAppended = true;
+ *parentList->append() = this;
+ } else
+ maker.setErrorCode(SkDisplayXMLParserError::kDisplayTreeTooDeep);
+ old = -1;
+ } else
+ reset();
+ } else {
+ SkASSERT(old < parentList->count());
+ if ((*parentList)[old]->isApply()) {
+ SkApply* apply = (SkApply*) (*parentList)[old];
+ if (apply != this && apply->fActive == NULL)
+ apply->activate(maker);
+ apply->append(this);
+ parentGroup = NULL;
+ } else {
+ if (parentGroup)
+ parentGroup->markCopySize(old);
+ SkDrawable** newApplyLocation = &(*parentList)[old];
+ SkGroup* pGroup;
+ int oldApply = displayList.findGroup(this, &parentList, &pGroup, &thisGroup, &grandList);
+ if (oldApply >= 0) {
+ (*parentList)[oldApply] = (SkDrawable*) SkDisplayType::CreateInstance(&maker, SkType_Apply);
+ parentGroup = NULL;
+ fDeleteScope = true;
+ }
+ *newApplyLocation = this;
+ }
+ }
+ if (parentGroup) {
+ parentGroup->markCopySet(old);
+ fDeleteScope = dynamicScope.size() == 0;
+ }
+ return true;
+}
+
+void SkApply::enableCreate(SkAnimateMaker& maker) {
+ SkString newID;
+ for (int step = 0; step <= steps; step++) {
+ fLastTime = step * SK_MSec1;
+ bool success = maker.computeID(scope, this, &newID);
+ if (success == false)
+ return;
+ if (maker.find(newID.c_str(), NULL))
+ continue;
+ SkApply* copy = (SkApply*) deepCopy(&maker); // work on copy of animator state
+ if (mode == kMode_create)
+ copy->mode = (Mode) -1;
+ SkDrawable* copyScope = copy->scope = (SkDrawable*) scope->deepCopy(&maker);
+ *fScopes.append() = copyScope;
+ if (copyScope->resolveIDs(maker, scope, this)) {
+ step = steps; // quit
+ goto next; // resolveIDs failed
+ }
+ if (newID.size() > 0)
+ maker.setID(copyScope, newID);
+ if (copy->resolveIDs(maker, this, this)) { // fix up all fields, including target
+ step = steps; // quit
+ goto next; // resolveIDs failed
+ }
+ copy->activate(maker);
+ copy->interpolate(maker, step * SK_MSec1);
+ maker.removeActive(copy->fActive);
+ next:
+ delete copy;
+ }
+}
+
+void SkApply::enableDynamic(SkAnimateMaker& maker) {
+ SkASSERT(mode != kMode_create); // create + dynamic are not currently compatible
+ SkDisplayable* newScope;
+ bool success = SkAnimatorScript::EvaluateDisplayable(maker, this, dynamicScope.c_str(),
+ &newScope);
+ if (success && scope != newScope) {
+ SkTDDrawableArray* pList, * gList;
+ SkGroup* pGroup = NULL, * found = NULL;
+ int old = maker.fDisplayList.findGroup(scope, &pList, &pGroup, &found, &gList);
+ if (pList && old >= 0 && (*pList)[old]->isApply() && (*pList)[old] != this) {
+ if (fAppended == false) {
+ if (found != NULL) {
+ SkDisplayable* oldChild = (*pList)[old];
+ if (oldChild->isApply() && found->copySet(old)) {
+ found->markCopyClear(old);
+ // delete oldChild;
+ }
+ }
+ (*pList)[old] = scope;
+ } else
+ pList->remove(old);
+ }
+ scope = (SkDrawable*) newScope;
+ onEndElement(maker);
+ }
+ maker.removeActive(fActive);
+ delete fActive;
+ fActive = NULL;
+}
+
+void SkApply::endSave(int index) {
+ SkAnimateBase* animate = fActive->fAnimators[index];
+ const SkMemberInfo* info = animate->fFieldInfo;
+ SkDisplayTypes type = (SkDisplayTypes) info->fType;
+ if (type == SkType_MemberFunction)
+ return;
+ SkDisplayable* target = getTarget(animate);
+ size_t size = info->getSize(target);
+ int count = (int) (size / sizeof(SkScalar));
+ int activeIndex = fActive->fDrawIndex + index;
+ SkOperand* last = new SkOperand[count];
+ SkAutoTDelete<SkOperand> autoLast(last);
+ if (type != SkType_MemberProperty) {
+ info->getValue(target, last, count);
+ SkOperand* saveOperand = fActive->fSaveRestore[activeIndex];
+ if (saveOperand)
+ info->setValue(target, fActive->fSaveRestore[activeIndex], count);
+ } else {
+ SkScriptValue scriptValue;
+ SkDEBUGCODE(bool success = ) target->getProperty(info->propertyIndex(), &scriptValue);
+ SkASSERT(success == true);
+ last[0] = scriptValue.fOperand;
+ scriptValue.fOperand = fActive->fSaveRestore[activeIndex][0];
+ target->setProperty(info->propertyIndex(), scriptValue);
+ }
+ SkOperand* save = fActive->fSaveRestore[activeIndex];
+ if (save)
+ memcpy(save, last, count * sizeof(SkOperand));
+}
+
+bool SkApply::getProperty(int index, SkScriptValue* value) const {
+ switch (index) {
+ case SK_PROPERTY(step):
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = fLastTime / SK_MSec1;
+ break;
+ case SK_PROPERTY(steps):
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = steps;
+ break;
+ case SK_PROPERTY(time):
+ value->fType = SkType_MSec;
+ value->fOperand.fS32 = fLastTime;
+ break;
+ default:
+ // SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+void SkApply::getStep(SkScriptValue* value) {
+ getProperty(SK_PROPERTY(step), value);
+}
+
+SkDrawable* SkApply::getTarget(SkAnimateBase* animate) {
+ if (animate->fTargetIsScope == false || mode != kMode_create)
+ return animate->fTarget;
+ return scope;
+}
+
+bool SkApply::hasDelayedAnimator() const {
+ SkAnimateBase* const* animEnd = fAnimators.end();
+ for (SkAnimateBase* const* animPtr = fAnimators.begin(); animPtr < animEnd; animPtr++) {
+ SkAnimateBase* const animator = *animPtr;
+ if (animator->fDelayed)
+ return true;
+ }
+ return false;
+}
+
+bool SkApply::hasEnable() const {
+ return true;
+}
+
+bool SkApply::inactivate(SkAnimateMaker& maker) {
+ if (fActive == NULL)
+ return false;
+ maker.removeActive(fActive);
+ delete fActive;
+ fActive = NULL;
+ return true;
+}
+
+#ifdef SK_DEBUG
+SkMSec lastTime = (SkMSec) -1;
+#endif
+
+bool SkApply::interpolate(SkAnimateMaker& maker, SkMSec rawTime) {
+ if (fActive == NULL)
+ return false;
+ bool result = false;
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ SkMSec time = maker.getAppTime();
+ if (lastTime == (SkMSec) -1)
+ lastTime = rawTime - 1;
+ if (fActive != NULL &&
+ strcmp(id, "a3") == 0 && rawTime > lastTime) {
+ lastTime += 1000;
+ SkString debugOut;
+ debugOut.appendS32(time - maker.fDebugTimeBase);
+ debugOut.append(" apply id=");
+ debugOut.append(_id);
+ debugOut.append("; ");
+ debugOut.append(fActive->fAnimators[0]->_id);
+ debugOut.append("=");
+ debugOut.appendS32(rawTime - fActive->fState[0].fStartTime);
+ debugOut.append(")");
+ SkDebugf("%s\n", debugOut.c_str());
+ }
+#endif
+ fActive->start();
+ if (restore)
+ fActive->initializeSave();
+ int animators = fActive->fAnimators.count();
+ for (int inner = 0; inner < animators; inner++) {
+ SkAnimateBase* animate = fActive->fAnimators[inner];
+ if (animate->fChanged) {
+ animate->fChanged = false;
+ animate->fStart = rawTime;
+ // SkTypedArray values;
+ // int count = animate->fValues.count();
+ // values.setCount(count);
+ // memcpy(values.begin(), animate->fValues.begin(), sizeof(SkOperand) * count);
+ animate->onEndElement(maker);
+ // if (memcmp(values.begin(), animate->fValues.begin(), sizeof(SkOperand) * count) != 0) {
+ fActive->append(this);
+ fActive->start();
+ // }
+ }
+ SkMSec time = fActive->getTime(rawTime, inner);
+ SkActive::SkState& state = fActive->fState[inner];
+ if (SkMSec_LT(rawTime, state.fStartTime)) {
+ if (fEnabling) {
+ animate->fDelayed = true;
+ maker.delayEnable(this, state.fStartTime);
+ }
+ continue;
+ } else
+ animate->fDelayed = false;
+ SkMSec innerTime = fLastTime = state.getRelativeTime(time);
+ if (restore)
+ fActive->restoreInterpolatorValues(inner);
+ if (animate->fReset) {
+ if (transition != SkApply::kTransition_reverse) {
+ if (SkMSec_LT(state.fBegin + state.fDuration, innerTime)) {
+ if (animate->fResetPending) {
+ innerTime = 0;
+ animate->fResetPending = false;
+ } else
+ continue;
+ }
+ } else if (innerTime == 0) {
+ if (animate->fResetPending) {
+ innerTime = state.fBegin + state.fDuration;
+ animate->fResetPending = false;
+ } else
+ continue;
+ }
+ }
+ int count = animate->components();
+ SkAutoSTMalloc<16, SkOperand> values(count);
+ SkInterpolatorBase::Result interpResult = fActive->fInterpolators[inner]->timeToValues(
+ innerTime, values.get());
+ result |= (interpResult != SkInterpolatorBase::kFreezeEnd_Result);
+ if (((transition != SkApply::kTransition_reverse && interpResult == SkInterpolatorBase::kFreezeEnd_Result) ||
+ (transition == SkApply::kTransition_reverse && fLastTime == 0)) && state.fUnpostedEndEvent) {
+// SkDEBUGF(("interpolate: post on end\n"));
+ state.fUnpostedEndEvent = false;
+ maker.postOnEnd(animate, state.fBegin + state.fDuration);
+ maker.fAdjustedStart = 0; // !!! left over from synchronizing animation days, undoubtably out of date (and broken)
+ }
+ if (animate->formula.size() > 0) {
+ if (fLastTime > animate->dur)
+ fLastTime = animate->dur;
+ SkTypedArray formulaValues;
+ formulaValues.setCount(count);
+ SkDEBUGCODE(bool success = ) animate->fFieldInfo->setValue(maker, &formulaValues, 0, 0, NULL,
+ animate->getValuesType(), animate->formula);
+ SkASSERT(success);
+ if (restore)
+ save(inner); // save existing value
+ applyValues(inner, formulaValues.begin(), count, animate->getValuesType(), innerTime);
+ } else {
+ if (restore)
+ save(inner); // save existing value
+ applyValues(inner, values.get(), count, animate->getValuesType(), innerTime);
+ }
+ }
+ return result;
+}
+
+void SkApply::initialize() {
+ if (scope == NULL)
+ return;
+ if (scope->isApply() || scope->isDrawable() == false)
+ return;
+ scope->initialize();
+}
+
+void SkApply::onEndElement(SkAnimateMaker& maker)
+{
+ SkDrawable* scopePtr = scope;
+ while (scopePtr && scopePtr->isApply()) {
+ SkApply* scopedApply = (SkApply*) scopePtr;
+ if (scopedApply->scope == this) {
+ maker.setErrorCode(SkDisplayXMLParserError::kApplyScopesItself);
+ return;
+ }
+ scopePtr = scopedApply->scope;
+ }
+ if (mode == kMode_create)
+ return;
+ if (scope != NULL && steps >= 0 && scope->isApply() == false && scope->isDrawable())
+ scope->setSteps(steps);
+ for (SkAnimateBase** animPtr = fAnimators.begin(); animPtr < fAnimators.end(); animPtr++) {
+ SkAnimateBase* anim = *animPtr;
+ //for reusing apply statements with dynamic scope
+ if (anim->fTarget == NULL || anim->fTargetIsScope) {
+ anim->fTargetIsScope = true;
+ if (scope)
+ anim->fTarget = scope;
+ else
+ anim->setTarget(maker);
+ anim->onEndElement(maker); // allows animate->fFieldInfo to be set
+ }
+ if (scope != NULL && steps >= 0 && anim->fTarget != scope && anim->fTarget->isDrawable())
+ anim->fTarget->setSteps(steps);
+ }
+}
+
+const SkMemberInfo* SkApply::preferredChild(SkDisplayTypes type) {
+ SkASSERT(SkDisplayType::IsAnimate(type) == false);
+ fContainsScope = true;
+ return getMember("scope"); // !!! cwap! need to refer to member through enum like kScope instead
+}
+
+void SkApply::refresh(SkAnimateMaker& maker) {
+ for (SkAnimateBase** animPtr = fAnimators.begin(); animPtr < fAnimators.end(); animPtr++) {
+ SkAnimateBase* animate = *animPtr;
+ animate->onEndElement(maker);
+ }
+ if (fActive)
+ fActive->resetInterpolators();
+}
+
+void SkApply::reset() {
+ if (fActive)
+ fActive->resetState();
+}
+
+bool SkApply::resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* apply) { // replace to/formula strings in animators of the form xxx.step with the step value, if xxx.step is in scope
+ if (resolveField(maker, apply, &dynamicScope) == false)
+ return true; // failed
+ SkAnimateBase** endPtr = fAnimators.end();
+ SkAnimateBase** origPtr = ((SkApply*) original)->fAnimators.begin();
+ for (SkAnimateBase** animPtr = fAnimators.begin(); animPtr < endPtr; ) {
+ SkAnimateBase* animator = *animPtr++;
+ maker.resolveID(animator, *origPtr++);
+ if (resolveField(maker, this, &animator->target) == false)
+ return true;
+ if (resolveField(maker, this, &animator->from) == false)
+ return true;
+ if (resolveField(maker, this, &animator->to) == false)
+ return true;
+ if (resolveField(maker, this, &animator->formula) == false)
+ return true;
+ }
+// setEmbedded();
+ onEndElement(maker);
+ return false; // succeeded
+}
+
+bool SkApply::resolveField(SkAnimateMaker& maker, SkDisplayable* parent, SkString* str) {
+ const char* script = str->c_str();
+ if (str->startsWith("#string:") == false)
+ return true;
+ script += sizeof("#string:") - 1;
+ return SkAnimatorScript::EvaluateString(maker, this, parent, script, str);
+}
+
+void SkApply::save(int index) {
+ SkAnimateBase* animate = fActive->fAnimators[index];
+ const SkMemberInfo * info = animate->fFieldInfo;
+ SkDisplayable* target = getTarget(animate);
+// if (animate->hasExecute())
+// info = animate->getResolvedInfo();
+ SkDisplayTypes type = (SkDisplayTypes) info->fType;
+ if (type == SkType_MemberFunction)
+ return; // nothing to save
+ size_t size = info->getSize(target);
+ int count = (int) (size / sizeof(SkScalar));
+ bool useLast = true;
+// !!! this all may be unneeded, at least in the dynamic case ??
+ int activeIndex = fActive->fDrawIndex + index;
+ SkTDOperandArray last;
+ if (fActive->fSaveRestore[activeIndex] == NULL) {
+ fActive->fSaveRestore[activeIndex] = new SkOperand[count];
+ useLast = false;
+ } else {
+ last.setCount(count);
+ memcpy(last.begin(), fActive->fSaveRestore[activeIndex], count * sizeof(SkOperand));
+ }
+ if (type != SkType_MemberProperty) {
+ info->getValue(target, fActive->fSaveRestore[activeIndex], count);
+ if (useLast)
+ info->setValue(target, last.begin(), count);
+ } else {
+ SkScriptValue scriptValue;
+ SkDEBUGCODE(bool success = ) target->getProperty(info->propertyIndex(), &scriptValue);
+ SkASSERT(success == true);
+ SkASSERT(scriptValue.fType == SkType_Float);
+ fActive->fSaveRestore[activeIndex][0] = scriptValue.fOperand;
+ if (useLast) {
+ SkScriptValue scriptValue;
+ scriptValue.fType = type;
+ scriptValue.fOperand = last[0];
+ target->setProperty(info->propertyIndex(), scriptValue);
+ }
+ }
+// !!! end of unneeded
+}
+
+bool SkApply::setProperty(int index, SkScriptValue& scriptValue) {
+ switch (index) {
+ case SK_PROPERTY(animator): {
+ SkAnimateBase* animate = (SkAnimateBase*) scriptValue.fOperand.fDisplayable;
+ SkASSERT(animate->isAnimate());
+ *fAnimators.append() = animate;
+ return true;
+ }
+ case SK_PROPERTY(steps):
+ steps = scriptValue.fOperand.fS32;
+ if (fActive)
+ fActive->setSteps(steps);
+ return true;
+ }
+ return false;
+}
+
+void SkApply::setSteps(int _steps) {
+ steps = _steps;
+}
+
+#ifdef SK_DEBUG
+void SkApply::validate() {
+ if (fActive)
+ fActive->validate();
+}
+#endif
diff --git a/animator/SkDisplayApply.h b/animator/SkDisplayApply.h
new file mode 100644
index 00000000..e128c292
--- /dev/null
+++ b/animator/SkDisplayApply.h
@@ -0,0 +1,106 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayApply_DEFINED
+#define SkDisplayApply_DEFINED
+
+#include "SkAnimateBase.h"
+#include "SkDrawable.h"
+#include "SkIntArray.h"
+
+class SkActive;
+
+class SkApply : public SkDrawable {
+ DECLARE_MEMBER_INFO(Apply);
+public:
+
+ SkApply();
+ virtual ~SkApply();
+
+ enum Transition {
+ kTransition_normal,
+ kTransition_reverse
+ };
+
+ enum Mode {
+ kMode_create,
+ kMode_immediate,
+ //kMode_once
+ };
+ void activate(SkAnimateMaker& );
+ void append(SkApply* apply);
+ void appendActive(SkActive* );
+ void applyValues(int animatorIndex, SkOperand* values, int count,
+ SkDisplayTypes , SkMSec time);
+ virtual bool contains(SkDisplayable*);
+// void createActive(SkAnimateMaker& );
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ void disable();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool enable(SkAnimateMaker& );
+ void enableCreate(SkAnimateMaker& );
+ void enableDynamic(SkAnimateMaker& );
+ void endSave(int index);
+ Mode getMode() { return mode; }
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ SkDrawable* getScope() { return scope; }
+ void getStep(SkScriptValue* );
+ SkDrawable* getTarget(SkAnimateBase* );
+ bool hasDelayedAnimator() const;
+ virtual bool hasEnable() const;
+ bool inactivate(SkAnimateMaker& maker);
+ virtual void initialize();
+ bool interpolate(SkAnimateMaker& , SkMSec time);
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual const SkMemberInfo* preferredChild(SkDisplayTypes type);
+ void refresh(SkAnimateMaker& );
+ void reset();
+ virtual bool resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* );
+ bool resolveField(SkAnimateMaker& , SkDisplayable* parent, SkString* str);
+ void save(int index);
+ void setEmbedded() { fEmbedded = true; }
+ virtual bool setProperty(int index, SkScriptValue& );
+ virtual void setSteps(int _steps);
+// virtual void setTime(SkMSec time);
+#ifdef SK_DEBUG
+ virtual void validate();
+#endif
+private:
+ SkMSec begin;
+ SkBool dontDraw;
+ SkString dynamicScope;
+ SkMSec interval;
+ Mode mode;
+#if 0
+ SkBool pickup;
+#endif
+ SkBool restore;
+ SkDrawable* scope;
+ int32_t steps;
+ Transition transition;
+ SkActive* fActive;
+ SkTDAnimateArray fAnimators;
+// SkDrawable* fCurrentScope;
+ SkMSec fLastTime; // used only to return script property time
+ SkTDDrawableArray fScopes;
+ SkBool fAppended : 1;
+ SkBool fContainsScope : 1;
+ SkBool fDeleteScope : 1;
+ SkBool fEmbedded : 1;
+ SkBool fEnabled : 1;
+ SkBool fEnabling : 1; // set if calling interpolate from enable
+ friend class SkActive;
+ friend class SkDisplayList;
+ typedef SkDrawable INHERITED;
+};
+
+#endif // SkDisplayApply_DEFINED
diff --git a/animator/SkDisplayBounds.cpp b/animator/SkDisplayBounds.cpp
new file mode 100644
index 00000000..49ec9b96
--- /dev/null
+++ b/animator/SkDisplayBounds.cpp
@@ -0,0 +1,43 @@
+
+/*
+ * 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 "SkDisplayBounds.h"
+#include "SkAnimateMaker.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayBounds::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(inval, Boolean)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayBounds);
+
+SkDisplayBounds::SkDisplayBounds() : inval(false) {
+}
+
+bool SkDisplayBounds::draw(SkAnimateMaker& maker) {
+ maker.fDisplayList.fUnionBounds = SkToBool(inval);
+ maker.fDisplayList.fDrawBounds = false;
+ fBounds.setEmpty();
+ bool result = INHERITED::draw(maker);
+ maker.fDisplayList.fUnionBounds = false;
+ maker.fDisplayList.fDrawBounds = true;
+ if (inval && fBounds.isEmpty() == false) {
+ SkIRect& rect = maker.fDisplayList.fInvalBounds;
+ maker.fDisplayList.fHasUnion = true;
+ if (rect.isEmpty())
+ rect = fBounds;
+ else
+ rect.join(fBounds);
+ }
+ return result;
+}
diff --git a/animator/SkDisplayBounds.h b/animator/SkDisplayBounds.h
new file mode 100644
index 00000000..0511ed74
--- /dev/null
+++ b/animator/SkDisplayBounds.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayBounds_DEFINED
+#define SkDisplayBounds_DEFINED
+
+#include "SkDrawRectangle.h"
+
+class SkDisplayBounds : public SkDrawRect {
+ DECLARE_DISPLAY_MEMBER_INFO(Bounds);
+ SkDisplayBounds();
+ virtual bool draw(SkAnimateMaker& );
+private:
+ SkBool inval;
+ typedef SkDrawRect INHERITED;
+};
+
+#endif // SkDisplayBounds_DEFINED
diff --git a/animator/SkDisplayEvent.cpp b/animator/SkDisplayEvent.cpp
new file mode 100644
index 00000000..a0cfcb10
--- /dev/null
+++ b/animator/SkDisplayEvent.cpp
@@ -0,0 +1,252 @@
+
+/*
+ * 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 "SkDisplayEvent.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayApply.h"
+#include "SkDisplayInput.h"
+#include "SkDisplayList.h"
+#ifdef SK_DEBUG
+#include "SkDump.h"
+#endif
+#include "SkEvent.h"
+#include "SkDisplayInput.h"
+#include "SkKey.h"
+#include "SkMetaData.h"
+#include "SkScript.h"
+#include "SkUtils.h"
+
+enum SkDisplayEvent_Properties {
+ SK_PROPERTY(key),
+ SK_PROPERTY(keys)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayEvent::fInfo[] = {
+ SK_MEMBER(code, EventCode),
+ SK_MEMBER(disable, Boolean),
+ SK_MEMBER_PROPERTY(key, String), // a single key (also last key pressed)
+ SK_MEMBER_PROPERTY(keys, String), // a single key or dash-delimited range of keys
+ SK_MEMBER(kind, EventKind),
+ SK_MEMBER(target, String),
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayEvent);
+
+SkDisplayEvent::SkDisplayEvent() : code((SkKey) -1), disable(false),
+ kind(kUser), x(0), y(0), fLastCode((SkKey) -1), fMax((SkKey) -1), fTarget(NULL) {
+}
+
+SkDisplayEvent::~SkDisplayEvent() {
+ deleteMembers();
+}
+
+bool SkDisplayEvent::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ *fChildren.append() = child;
+ return true;
+}
+
+bool SkDisplayEvent::contains(SkDisplayable* match) {
+ for (int index = 0; index < fChildren.count(); index++) {
+ if (fChildren[index] == match || fChildren[index]->contains(match))
+ return true;
+ }
+ return false;
+}
+
+SkDisplayable* SkDisplayEvent::contains(const SkString& match) {
+ for (int index = 0; index < fChildren.count(); index++) {
+ SkDisplayable* child = fChildren[index];
+ if (child->contains(match))
+ return child;
+ }
+ return NULL;
+}
+
+void SkDisplayEvent::deleteMembers() {
+ for (int index = 0; index < fChildren.count(); index++) {
+ SkDisplayable* evt = fChildren[index];
+ delete evt;
+ }
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayEvent::dumpEvent(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkString str;
+ SkDump::GetEnumString(SkType_EventKind, kind, &str);
+ SkDebugf("kind=\"%s\" ", str.c_str());
+ if (kind == SkDisplayEvent::kKeyPress || kind == SkDisplayEvent::kKeyPressUp) {
+ if (code >= 0)
+ SkDump::GetEnumString(SkType_EventCode, code, &str);
+ else
+ str.set("none");
+ SkDebugf("code=\"%s\" ", str.c_str());
+ }
+ if (kind == SkDisplayEvent::kKeyChar) {
+ if (fMax != (SkKey) -1 && fMax != code)
+ SkDebugf("keys=\"%c - %c\" ", code, fMax);
+ else
+ SkDebugf("key=\"%c\" ", code);
+ }
+ if (fTarget != NULL) {
+ SkDebugf("target=\"%s\" ", fTarget->id);
+ }
+ if (kind >= SkDisplayEvent::kMouseDown && kind <= SkDisplayEvent::kMouseUp) {
+ SkDebugf("x=\"%g\" y=\"%g\" ", SkScalarToFloat(x), SkScalarToFloat(y));
+ }
+ if (disable)
+ SkDebugf("disable=\"true\" ");
+ SkDebugf("/>\n");
+}
+#endif
+
+bool SkDisplayEvent::enableEvent(SkAnimateMaker& maker)
+{
+ maker.fActiveEvent = this;
+ if (fChildren.count() == 0)
+ return false;
+ if (disable)
+ return false;
+#ifdef SK_DUMP_ENABLED
+ if (maker.fDumpEvents) {
+ SkDebugf("enable: ");
+ dumpEvent(&maker);
+ }
+#endif
+ SkDisplayList& displayList = maker.fDisplayList;
+ for (int index = 0; index < fChildren.count(); index++) {
+ SkDisplayable* displayable = fChildren[index];
+ if (displayable->isGroup()) {
+ SkTDDrawableArray* parentList = displayList.getDrawList();
+ *parentList->append() = (SkDrawable*) displayable; // make it findable before children are enabled
+ }
+ if (displayable->enable(maker))
+ continue;
+ if (maker.hasError())
+ return true;
+ if (displayable->isDrawable() == false)
+ return true; // error
+ SkDrawable* drawable = (SkDrawable*) displayable;
+ SkTDDrawableArray* parentList = displayList.getDrawList();
+ *parentList->append() = drawable;
+ }
+ return false;
+}
+
+bool SkDisplayEvent::getProperty(int index, SkScriptValue* value) const {
+ switch (index) {
+ case SK_PROPERTY(key):
+ case SK_PROPERTY(keys): {
+ value->fType = SkType_String;
+ char scratch[8];
+ SkKey convert = index == SK_PROPERTY(keys) ? code : fLastCode;
+ size_t size = convert > 0 ? SkUTF8_FromUnichar(convert, scratch) : 0;
+ fKeyString.set(scratch, size);
+ value->fOperand.fString = &fKeyString;
+ if (index != SK_PROPERTY(keys) || fMax == (SkKey) -1 || fMax == code)
+ break;
+ value->fOperand.fString->append("-");
+ size = SkUTF8_FromUnichar(fMax, scratch);
+ value->fOperand.fString->append(scratch, size);
+ } break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+void SkDisplayEvent::onEndElement(SkAnimateMaker& maker)
+{
+ if (kind == kUser)
+ return;
+ maker.fEvents.addEvent(this);
+ if (kind == kOnEnd) {
+ SkDEBUGCODE(bool found = ) maker.find(target.c_str(), &fTarget);
+ SkASSERT(found);
+ SkASSERT(fTarget && fTarget->isAnimate());
+ SkAnimateBase* animate = (SkAnimateBase*) fTarget;
+ animate->setHasEndEvent();
+ }
+}
+
+void SkDisplayEvent::populateInput(SkAnimateMaker& maker, const SkEvent& fEvent) {
+ const SkMetaData& meta = fEvent.getMetaData();
+ SkMetaData::Iter iter(meta);
+ SkMetaData::Type type;
+ int number;
+ const char* name;
+ while ((name = iter.next(&type, &number)) != NULL) {
+ if (name[0] == '\0')
+ continue;
+ SkDisplayable* displayable;
+ SkInput* input;
+ for (int index = 0; index < fChildren.count(); index++) {
+ displayable = fChildren[index];
+ if (displayable->getType() != SkType_Input)
+ continue;
+ input = (SkInput*) displayable;
+ if (input->name.equals(name))
+ goto found;
+ }
+ if (!maker.find(name, &displayable) || displayable->getType() != SkType_Input)
+ continue;
+ input = (SkInput*) displayable;
+ found:
+ switch (type) {
+ case SkMetaData::kS32_Type:
+ meta.findS32(name, &input->fInt);
+ break;
+ case SkMetaData::kScalar_Type:
+ meta.findScalar(name, &input->fFloat);
+ break;
+ case SkMetaData::kPtr_Type:
+ SkASSERT(0);
+ break; // !!! not handled for now
+ case SkMetaData::kString_Type:
+ input->string.set(meta.findString(name));
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+ // re-evaluate all animators that may have built their values from input strings
+ for (SkDisplayable** childPtr = fChildren.begin(); childPtr < fChildren.end(); childPtr++) {
+ SkDisplayable* displayable = *childPtr;
+ if (displayable->isApply() == false)
+ continue;
+ SkApply* apply = (SkApply*) displayable;
+ apply->refresh(maker);
+ }
+}
+
+bool SkDisplayEvent::setProperty(int index, SkScriptValue& value) {
+ SkASSERT(index == SK_PROPERTY(key) || index == SK_PROPERTY(keys));
+ SkASSERT(value.fType == SkType_String);
+ SkString* string = value.fOperand.fString;
+ const char* chars = string->c_str();
+ int count = SkUTF8_CountUnichars(chars);
+ SkASSERT(count >= 1);
+ code = (SkKey) SkUTF8_NextUnichar(&chars);
+ fMax = code;
+ SkASSERT(count == 1 || index == SK_PROPERTY(keys));
+ if (--count > 0) {
+ SkASSERT(*chars == '-');
+ chars++;
+ fMax = (SkKey) SkUTF8_NextUnichar(&chars);
+ SkASSERT(fMax >= code);
+ }
+ return true;
+}
diff --git a/animator/SkDisplayEvent.h b/animator/SkDisplayEvent.h
new file mode 100644
index 00000000..952faeac
--- /dev/null
+++ b/animator/SkDisplayEvent.h
@@ -0,0 +1,66 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayEvent_DEFINED
+#define SkDisplayEvent_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkIntArray.h"
+#include "SkKey.h"
+
+class SkEvent;
+
+class SkDisplayEvent : public SkDisplayable {
+ DECLARE_DISPLAY_MEMBER_INFO(Event);
+ enum Kind {
+ kNo_kind,
+ kKeyChar,
+ kKeyPress,
+ kKeyPressUp, //i assume the order here is intended to match with skanimatorscript.cpp
+ kMouseDown,
+ kMouseDrag,
+ kMouseMove,
+ kMouseUp,
+ kOnEnd,
+ kOnload,
+ kUser
+ };
+ SkDisplayEvent();
+ virtual ~SkDisplayEvent();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool contains(SkDisplayable*);
+ virtual SkDisplayable* contains(const SkString& );
+#ifdef SK_DEBUG
+ void dumpEvent(SkAnimateMaker* );
+#endif
+ bool enableEvent(SkAnimateMaker& );
+ virtual bool getProperty(int index, SkScriptValue* ) const;
+ virtual void onEndElement(SkAnimateMaker& maker);
+ void populateInput(SkAnimateMaker& , const SkEvent& fEvent);
+ virtual bool setProperty(int index, SkScriptValue& );
+protected:
+ SkKey code;
+ SkBool disable;
+ Kind kind;
+ SkString target;
+ SkScalar x;
+ SkScalar y;
+ SkTDDisplayableArray fChildren;
+ mutable SkString fKeyString;
+ SkKey fLastCode; // last key to trigger this event
+ SkKey fMax; // if the code expresses a range
+ SkDisplayable* fTarget; // used by onEnd
+private:
+ void deleteMembers();
+ friend class SkEvents;
+ typedef SkDisplayable INHERITED;
+};
+
+#endif // SkDisplayEvent_DEFINED
diff --git a/animator/SkDisplayEvents.cpp b/animator/SkDisplayEvents.cpp
new file mode 100644
index 00000000..c42fbdf3
--- /dev/null
+++ b/animator/SkDisplayEvents.cpp
@@ -0,0 +1,113 @@
+
+/*
+ * 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 "SkDisplayEvents.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimator.h"
+#include "SkDisplayEvent.h"
+#include "SkDisplayMovie.h"
+#include "SkDrawable.h"
+#ifdef SK_DEBUG
+#include "SkDump.h"
+#endif
+
+SkEventState::SkEventState() : fCode(0), fDisable(false), fDisplayable(0), fX(0), fY(0) {
+}
+
+SkEvents::SkEvents() {
+}
+
+SkEvents::~SkEvents() {
+}
+
+bool SkEvents::doEvent(SkAnimateMaker& maker, SkDisplayEvent::Kind kind, SkEventState* state) {
+/*#ifdef SK_DUMP_ENABLED
+ if (maker.fDumpEvents) {
+ SkDebugf("doEvent: ");
+ SkString str;
+ SkDump::GetEnumString(SkType_EventKind, kind, &str);
+ SkDebugf("kind=%s ", str.c_str());
+ if (state && state->fDisplayable)
+ state->fDisplayable->SkDisplayable::dump(&maker);
+ else
+ SkDebugf("\n");
+ }
+#endif*/
+ bool handled = false;
+ SkDisplayable** firstMovie = maker.fMovies.begin();
+ SkDisplayable** endMovie = maker.fMovies.end();
+ for (SkDisplayable** ptr = firstMovie; ptr < endMovie; ptr++) {
+ SkDisplayMovie* movie = (SkDisplayMovie*) *ptr;
+ if (kind != SkDisplayEvent::kOnload)
+ movie->doEvent(kind, state);
+ }
+ SkDisplayable* displayable = state ? state->fDisplayable : NULL;
+ int keyCode = state ? state->fCode : 0;
+ int count = fEvents.count();
+ for (int index = 0; index < count; index++) {
+ SkDisplayEvent* evt = fEvents[index];
+ if (evt->disable)
+ continue;
+ if (evt->kind != kind)
+ continue;
+ if (evt->code != (SkKey) -1) {
+ if ((int) evt->code > keyCode || (int) (evt->fMax != (SkKey) -1 ? evt->fMax : evt->code) < keyCode)
+ continue;
+ evt->fLastCode = (SkKey) keyCode;
+ }
+ if (evt->fTarget != NULL && evt->fTarget != displayable)
+ continue;
+ if (state == NULL || state->fDisable == 0) {
+ if (kind >= SkDisplayEvent::kMouseDown && kind <= SkDisplayEvent::kMouseUp) {
+ evt->x = state->fX;
+ evt->y = state->fY;
+ }
+ if (evt->enableEvent(maker))
+ fError = true;
+ }
+ handled = true;
+ }
+ return handled;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkEvents::dump(SkAnimateMaker& maker) {
+ int index;
+ SkTDDrawableArray& drawArray = maker.fDisplayList.fDrawList;
+ int count = drawArray.count();
+ for (index = 0; index < count; index++) {
+ SkDrawable* drawable = drawArray[index];
+ drawable->dumpEvents();
+ }
+ count = fEvents.count();
+ for (index = 0; index < count; index++) {
+ SkDisplayEvent* evt = fEvents[index];
+ evt->dumpEvent(&maker);
+ }
+}
+#endif
+
+// currently this only removes onLoad events
+void SkEvents::removeEvent(SkDisplayEvent::Kind kind, SkEventState* state) {
+ int keyCode = state ? state->fCode : 0;
+ SkDisplayable* displayable = state ? state->fDisplayable : NULL;
+ for (SkDisplayEvent** evtPtr = fEvents.begin(); evtPtr < fEvents.end(); evtPtr++) {
+ SkDisplayEvent* evt = *evtPtr;
+ if (evt->kind != kind)
+ continue;
+ if (evt->code != (SkKey) -1) {
+ if ((int) evt->code > keyCode || (int) (evt->fMax != (SkKey) -1 ? evt->fMax : evt->code) < keyCode)
+ continue;
+ }
+ if (evt->fTarget != NULL && evt->fTarget != displayable)
+ continue;
+ int index = fEvents.find(evt);
+ fEvents.remove(index);
+ }
+}
diff --git a/animator/SkDisplayEvents.h b/animator/SkDisplayEvents.h
new file mode 100644
index 00000000..276955ba
--- /dev/null
+++ b/animator/SkDisplayEvents.h
@@ -0,0 +1,42 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayEvents_DEFINED
+#define SkDisplayEvents_DEFINED
+
+#include "SkEvent.h"
+#include "SkDisplayEvent.h"
+
+struct SkEventState {
+ SkEventState();
+ int fCode;
+ SkBool fDisable;
+ SkDisplayable* fDisplayable;
+ SkScalar fX;
+ SkScalar fY;
+};
+
+class SkEvents {
+public:
+ SkEvents();
+ ~SkEvents();
+ void addEvent(SkDisplayEvent* evt) { *fEvents.append() = evt; }
+ bool doEvent(SkAnimateMaker& , SkDisplayEvent::Kind , SkEventState* );
+#ifdef SK_DUMP_ENABLED
+ void dump(SkAnimateMaker& );
+#endif
+ void reset() { fEvents.reset(); }
+ void removeEvent(SkDisplayEvent::Kind kind, SkEventState* );
+private:
+ SkTDDisplayEventArray fEvents;
+ SkBool fError;
+ friend class SkDisplayXMLParser;
+};
+
+#endif // SkDisplayEvents_DEFINED
diff --git a/animator/SkDisplayInclude.cpp b/animator/SkDisplayInclude.cpp
new file mode 100644
index 00000000..860264eb
--- /dev/null
+++ b/animator/SkDisplayInclude.cpp
@@ -0,0 +1,59 @@
+
+/*
+ * 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 "SkDisplayInclude.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimator.h"
+
+#if 0
+#undef SK_MEMBER
+#define SK_MEMBER(_member, _type) \
+ { #_member, SK_OFFSETOF(BASE_CLASS::_A, _member), SkType_##_type, \
+ sizeof(((BASE_CLASS::_A*) 0)->_member) / sizeof(SkScalar) }
+#endif
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkInclude::fInfo[] = {
+ SK_MEMBER(src, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkInclude);
+
+//SkInclude::SkInclude() {
+// src.init();
+//}
+
+//SkInclude::~SkInclude() {
+// src.unref();
+//}
+
+bool SkInclude::enable(SkAnimateMaker & ) {
+ return true;
+}
+
+bool SkInclude::hasEnable() const {
+ return true;
+}
+
+void SkInclude::onEndElement(SkAnimateMaker& maker) {
+ maker.fInInclude = true;
+ if (src.size() == 0 || maker.decodeURI(src.c_str()) == false) {
+ if (maker.getErrorCode() != SkXMLParserError::kNoError || maker.getNativeCode() != -1) {
+ maker.setInnerError(&maker, src);
+ maker.setErrorCode(SkDisplayXMLParserError::kInInclude);
+ } else {
+ maker.setErrorNoun(src);
+ maker.setErrorCode(SkDisplayXMLParserError::kIncludeNameUnknownOrMissing);
+ }
+ }
+ maker.fInInclude = false;
+}
diff --git a/animator/SkDisplayInclude.h b/animator/SkDisplayInclude.h
new file mode 100644
index 00000000..41b5d297
--- /dev/null
+++ b/animator/SkDisplayInclude.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayInclude_DEFINED
+#define SkDisplayInclude_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+
+class SkInclude : public SkDisplayable {
+ DECLARE_MEMBER_INFO(Include);
+ virtual void onEndElement(SkAnimateMaker & );
+ virtual bool enable(SkAnimateMaker & );
+ virtual bool hasEnable() const;
+protected:
+ SkString src;
+};
+
+#endif // SkDisplayInclude_DEFINED
diff --git a/animator/SkDisplayInput.cpp b/animator/SkDisplayInput.cpp
new file mode 100644
index 00000000..7061aa8e
--- /dev/null
+++ b/animator/SkDisplayInput.cpp
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "SkDisplayInput.h"
+
+enum SkInput_Properties {
+ SK_PROPERTY(initialized)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkInput::fInfo[] = {
+ SK_MEMBER_ALIAS(float, fFloat, Float),
+ SK_MEMBER_PROPERTY(initialized, Boolean),
+ SK_MEMBER_ALIAS(int, fInt, Int),
+ SK_MEMBER(name, String),
+ SK_MEMBER(string, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkInput);
+
+SkInput::SkInput() : fInt((int) SK_NaN32), fFloat(SK_ScalarNaN) {}
+
+SkDisplayable* SkInput::contains(const SkString& string) {
+ return string.equals(name) ? this : NULL;
+}
+
+bool SkInput::enable(SkAnimateMaker & ) {
+ return true;
+}
+
+bool SkInput::getProperty(int index, SkScriptValue* value) const {
+ switch (index) {
+ case SK_PROPERTY(initialized):
+ value->fType = SkType_Boolean;
+ value->fOperand.fS32 = fInt != (int) SK_NaN32 ||
+ SkScalarIsNaN(fFloat) == false || string.size() > 0;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool SkInput::hasEnable() const {
+ return true;
+}
diff --git a/animator/SkDisplayInput.h b/animator/SkDisplayInput.h
new file mode 100644
index 00000000..d9871e21
--- /dev/null
+++ b/animator/SkDisplayInput.h
@@ -0,0 +1,33 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayInput_DEFINED
+#define SkDisplayInput_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+
+class SkInput : public SkDisplayable {
+ DECLARE_MEMBER_INFO(Input);
+ SkInput();
+ virtual SkDisplayable* contains(const SkString& );
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool enable(SkAnimateMaker & );
+ virtual bool hasEnable() const;
+protected:
+ SkString name;
+ int32_t fInt;
+ SkScalar fFloat;
+ SkString string;
+private:
+ friend class SkDisplayEvent;
+ friend class SkPost;
+};
+
+#endif // SkDisplayInput_DEFINED
diff --git a/animator/SkDisplayList.cpp b/animator/SkDisplayList.cpp
new file mode 100644
index 00000000..6434601f
--- /dev/null
+++ b/animator/SkDisplayList.cpp
@@ -0,0 +1,158 @@
+
+/*
+ * 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 "SkDisplayList.h"
+#include "SkAnimateActive.h"
+#include "SkAnimateBase.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayApply.h"
+#include "SkDrawable.h"
+#include "SkDrawGroup.h"
+#include "SkDrawMatrix.h"
+#include "SkInterpolator.h"
+#include "SkTime.h"
+
+SkDisplayList::SkDisplayList() : fDrawBounds(true), fUnionBounds(false), fInTime(0) {
+}
+
+SkDisplayList::~SkDisplayList() {
+}
+
+void SkDisplayList::append(SkActive* active) {
+ *fActiveList.append() = active;
+}
+
+bool SkDisplayList::draw(SkAnimateMaker& maker, SkMSec inTime) {
+ validate();
+ fInTime = inTime;
+ bool result = false;
+ fInvalBounds.setEmpty();
+ if (fDrawList.count()) {
+ for (SkActive** activePtr = fActiveList.begin(); activePtr < fActiveList.end(); activePtr++) {
+ SkActive* active = *activePtr;
+ active->reset();
+ }
+ for (int index = 0; index < fDrawList.count(); index++) {
+ SkDrawable* draw = fDrawList[index];
+ draw->initialize(); // allow matrices to reset themselves
+ SkASSERT(draw->isDrawable());
+ validate();
+ result |= draw->draw(maker);
+ }
+ }
+ validate();
+ return result;
+}
+
+int SkDisplayList::findGroup(SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray**grandList) {
+ *parent = NULL;
+ *list = &fDrawList;
+ *grandList = &fDrawList;
+ return SearchForMatch(match, list, parent, found, grandList);
+}
+
+void SkDisplayList::hardReset() {
+ fDrawList.reset();
+ fActiveList.reset();
+}
+
+bool SkDisplayList::onIRect(const SkIRect& r) {
+ fBounds = r;
+ return fDrawBounds;
+}
+
+int SkDisplayList::SearchForMatch(SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray**grandList) {
+ *found = NULL;
+ for (int index = 0; index < (*list)->count(); index++) {
+ SkDrawable* draw = (**list)[index];
+ if (draw == match)
+ return index;
+ if (draw->isApply()) {
+ SkApply* apply = (SkApply*) draw;
+ if (apply->scope == match)
+ return index;
+ if (apply->scope->isGroup() && SearchGroupForMatch(apply->scope, match, list, parent, found, grandList, index))
+ return index;
+ if (apply->mode == SkApply::kMode_create) {
+ for (SkDrawable** ptr = apply->fScopes.begin(); ptr < apply->fScopes.end(); ptr++) {
+ SkDrawable* scope = *ptr;
+ if (scope == match)
+ return index;
+ //perhaps should call SearchGroupForMatch here as well (on scope)
+ }
+ }
+ }
+ if (draw->isGroup() && SearchGroupForMatch(draw, match, list, parent, found, grandList, index))
+ return index;
+
+ }
+ return -1;
+}
+
+bool SkDisplayList::SearchGroupForMatch(SkDrawable* draw, SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray** grandList, int &index) {
+ SkGroup* group = (SkGroup*) draw;
+ if (group->getOriginal() == match)
+ return true;
+ SkTDDrawableArray* saveList = *list;
+ int groupIndex = group->findGroup(match, list, parent, found, grandList);
+ if (groupIndex >= 0) {
+ *found = group;
+ index = groupIndex;
+ return true;
+ }
+ *list = saveList;
+ return false;
+ }
+
+void SkDisplayList::reset() {
+ for (int index = 0; index < fDrawList.count(); index++) {
+ SkDrawable* draw = fDrawList[index];
+ if (draw->isApply() == false)
+ continue;
+ SkApply* apply = (SkApply*) draw;
+ apply->reset();
+ }
+}
+
+void SkDisplayList::remove(SkActive* active) {
+ int index = fActiveList.find(active);
+ SkASSERT(index >= 0);
+ fActiveList.remove(index); // !!! could use shuffle instead
+ SkASSERT(fActiveList.find(active) < 0);
+}
+
+#ifdef SK_DUMP_ENABLED
+int SkDisplayList::fDumpIndex;
+int SkDisplayList::fIndent;
+
+void SkDisplayList::dump(SkAnimateMaker* maker) {
+ fIndent = 0;
+ dumpInner(maker);
+}
+
+void SkDisplayList::dumpInner(SkAnimateMaker* maker) {
+ for (int index = 0; index < fDrawList.count(); index++) {
+ fDumpIndex = index;
+ fDrawList[fDumpIndex]->dump(maker);
+ }
+}
+
+#endif
+
+#ifdef SK_DEBUG
+void SkDisplayList::validate() {
+ for (int index = 0; index < fDrawList.count(); index++) {
+ SkDrawable* draw = fDrawList[index];
+ draw->validate();
+ }
+}
+#endif
diff --git a/animator/SkDisplayList.h b/animator/SkDisplayList.h
new file mode 100644
index 00000000..b8705982
--- /dev/null
+++ b/animator/SkDisplayList.h
@@ -0,0 +1,70 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayList_DEFINED
+#define SkDisplayList_DEFINED
+
+#include "SkOperand.h"
+#include "SkIntArray.h"
+#include "SkBounder.h"
+#include "SkRect.h"
+
+class SkAnimateMaker;
+class SkActive;
+class SkApply;
+class SkDrawable;
+class SkGroup;
+
+class SkDisplayList : public SkBounder {
+public:
+ SkDisplayList();
+ virtual ~SkDisplayList();
+ void append(SkActive* );
+ void clear() { fDrawList.reset(); }
+ int count() { return fDrawList.count(); }
+ bool draw(SkAnimateMaker& , SkMSec time);
+#ifdef SK_DUMP_ENABLED
+ void dump(SkAnimateMaker* maker);
+ void dumpInner(SkAnimateMaker* maker);
+ static int fIndent;
+ static int fDumpIndex;
+#endif
+ int findGroup(SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray** grandList);
+ SkDrawable* get(int index) { return fDrawList[index]; }
+ SkMSec getTime() { return fInTime; }
+ SkTDDrawableArray* getDrawList() { return &fDrawList; }
+ void hardReset();
+ virtual bool onIRect(const SkIRect& r);
+ void reset();
+ void remove(SkActive* );
+#ifdef SK_DEBUG
+ void validate();
+#else
+ void validate() {}
+#endif
+ static int SearchForMatch(SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray**grandList);
+ static bool SearchGroupForMatch(SkDrawable* draw, SkDrawable* match,
+ SkTDDrawableArray** list, SkGroup** parent, SkGroup** found, SkTDDrawableArray** grandList,
+ int &index);
+public:
+ SkIRect fBounds;
+ SkIRect fInvalBounds;
+ bool fDrawBounds;
+ bool fHasUnion;
+ bool fUnionBounds;
+private:
+ SkTDDrawableArray fDrawList;
+ SkTDActiveArray fActiveList;
+ SkMSec fInTime;
+ friend class SkEvents;
+};
+
+#endif // SkDisplayList_DEFINED
diff --git a/animator/SkDisplayMath.cpp b/animator/SkDisplayMath.cpp
new file mode 100644
index 00000000..bdf377b3
--- /dev/null
+++ b/animator/SkDisplayMath.cpp
@@ -0,0 +1,240 @@
+
+/*
+ * 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 "SkDisplayMath.h"
+
+enum SkDisplayMath_Properties {
+ SK_PROPERTY(E),
+ SK_PROPERTY(LN10),
+ SK_PROPERTY(LN2),
+ SK_PROPERTY(LOG10E),
+ SK_PROPERTY(LOG2E),
+ SK_PROPERTY(PI),
+ SK_PROPERTY(SQRT1_2),
+ SK_PROPERTY(SQRT2)
+};
+
+const SkScalar SkDisplayMath::gConstants[] = {
+#ifdef SK_SCALAR_IS_FLOAT
+ 2.718281828f, // E
+ 2.302585093f, // LN10
+ 0.693147181f, // LN2
+ 0.434294482f, // LOG10E
+ 1.442695041f, // LOG2E
+ 3.141592654f, // PI
+ 0.707106781f, // SQRT1_2
+ 1.414213562f // SQRT2
+#else
+ 0x2B7E1, // E
+ 0x24D76, // LN10
+ 0xB172, // LN2
+ 0x6F2E, // LOG10E
+ 0x17154, // LOG2E
+ 0x3243F, // PI
+ 0xB505, // SQRT1_2
+ 0x16A0A // SQRT2
+#endif
+};
+
+enum SkDisplayMath_Functions {
+ SK_FUNCTION(abs),
+ SK_FUNCTION(acos),
+ SK_FUNCTION(asin),
+ SK_FUNCTION(atan),
+ SK_FUNCTION(atan2),
+ SK_FUNCTION(ceil),
+ SK_FUNCTION(cos),
+ SK_FUNCTION(exp),
+ SK_FUNCTION(floor),
+ SK_FUNCTION(log),
+ SK_FUNCTION(max),
+ SK_FUNCTION(min),
+ SK_FUNCTION(pow),
+ SK_FUNCTION(random),
+ SK_FUNCTION(round),
+ SK_FUNCTION(sin),
+ SK_FUNCTION(sqrt),
+ SK_FUNCTION(tan)
+};
+
+const SkFunctionParamType SkDisplayMath::fFunctionParameters[] = {
+ (SkFunctionParamType) SkType_Float, // abs
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // acos
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // asin
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // atan
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // atan2
+ (SkFunctionParamType) SkType_Float,
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // ceil
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // cos
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // exp
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // floor
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // log
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Array, // max
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Array, // min
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // pow
+ (SkFunctionParamType) SkType_Float,
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // random
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // round
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // sin
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // sqrt
+ (SkFunctionParamType) 0,
+ (SkFunctionParamType) SkType_Float, // tan
+ (SkFunctionParamType) 0
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayMath::fInfo[] = {
+ SK_MEMBER_PROPERTY(E, Float),
+ SK_MEMBER_PROPERTY(LN10, Float),
+ SK_MEMBER_PROPERTY(LN2, Float),
+ SK_MEMBER_PROPERTY(LOG10E, Float),
+ SK_MEMBER_PROPERTY(LOG2E, Float),
+ SK_MEMBER_PROPERTY(PI, Float),
+ SK_MEMBER_PROPERTY(SQRT1_2, Float),
+ SK_MEMBER_PROPERTY(SQRT2, Float),
+ SK_MEMBER_FUNCTION(abs, Float),
+ SK_MEMBER_FUNCTION(acos, Float),
+ SK_MEMBER_FUNCTION(asin, Float),
+ SK_MEMBER_FUNCTION(atan, Float),
+ SK_MEMBER_FUNCTION(atan2, Float),
+ SK_MEMBER_FUNCTION(ceil, Float),
+ SK_MEMBER_FUNCTION(cos, Float),
+ SK_MEMBER_FUNCTION(exp, Float),
+ SK_MEMBER_FUNCTION(floor, Float),
+ SK_MEMBER_FUNCTION(log, Float),
+ SK_MEMBER_FUNCTION(max, Float),
+ SK_MEMBER_FUNCTION(min, Float),
+ SK_MEMBER_FUNCTION(pow, Float),
+ SK_MEMBER_FUNCTION(random, Float),
+ SK_MEMBER_FUNCTION(round, Float),
+ SK_MEMBER_FUNCTION(sin, Float),
+ SK_MEMBER_FUNCTION(sqrt, Float),
+ SK_MEMBER_FUNCTION(tan, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayMath);
+
+void SkDisplayMath::executeFunction(SkDisplayable* target, int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* scriptValue) {
+ if (scriptValue == NULL)
+ return;
+ SkASSERT(target == this);
+ SkScriptValue* array = parameters.begin();
+ SkScriptValue* end = parameters.end();
+ SkScalar input = parameters[0].fOperand.fScalar;
+ SkScalar scalarResult;
+ switch (index) {
+ case SK_FUNCTION(abs):
+ scalarResult = SkScalarAbs(input);
+ break;
+ case SK_FUNCTION(acos):
+ scalarResult = SkScalarACos(input);
+ break;
+ case SK_FUNCTION(asin):
+ scalarResult = SkScalarASin(input);
+ break;
+ case SK_FUNCTION(atan):
+ scalarResult = SkScalarATan2(input, SK_Scalar1);
+ break;
+ case SK_FUNCTION(atan2):
+ scalarResult = SkScalarATan2(input, parameters[1].fOperand.fScalar);
+ break;
+ case SK_FUNCTION(ceil):
+ scalarResult = SkIntToScalar(SkScalarCeil(input));
+ break;
+ case SK_FUNCTION(cos):
+ scalarResult = SkScalarCos(input);
+ break;
+ case SK_FUNCTION(exp):
+ scalarResult = SkScalarExp(input);
+ break;
+ case SK_FUNCTION(floor):
+ scalarResult = SkIntToScalar(SkScalarFloor(input));
+ break;
+ case SK_FUNCTION(log):
+ scalarResult = SkScalarLog(input);
+ break;
+ case SK_FUNCTION(max):
+ scalarResult = -SK_ScalarMax;
+ while (array < end) {
+ scalarResult = SkMaxScalar(scalarResult, array->fOperand.fScalar);
+ array++;
+ }
+ break;
+ case SK_FUNCTION(min):
+ scalarResult = SK_ScalarMax;
+ while (array < end) {
+ scalarResult = SkMinScalar(scalarResult, array->fOperand.fScalar);
+ array++;
+ }
+ break;
+ case SK_FUNCTION(pow):
+ // not the greatest -- but use x^y = e^(y * ln(x))
+ scalarResult = SkScalarLog(input);
+ scalarResult = SkScalarMul(parameters[1].fOperand.fScalar, scalarResult);
+ scalarResult = SkScalarExp(scalarResult);
+ break;
+ case SK_FUNCTION(random):
+ scalarResult = fRandom.nextUScalar1();
+ break;
+ case SK_FUNCTION(round):
+ scalarResult = SkIntToScalar(SkScalarRound(input));
+ break;
+ case SK_FUNCTION(sin):
+ scalarResult = SkScalarSin(input);
+ break;
+ case SK_FUNCTION(sqrt): {
+ SkASSERT(parameters.count() == 1);
+ SkASSERT(type == SkType_Float);
+ scalarResult = SkScalarSqrt(input);
+ } break;
+ case SK_FUNCTION(tan):
+ scalarResult = SkScalarTan(input);
+ break;
+ default:
+ SkASSERT(0);
+ scalarResult = SK_ScalarNaN;
+ }
+ scriptValue->fOperand.fScalar = scalarResult;
+ scriptValue->fType = SkType_Float;
+}
+
+const SkFunctionParamType* SkDisplayMath::getFunctionsParameters() {
+ return fFunctionParameters;
+}
+
+bool SkDisplayMath::getProperty(int index, SkScriptValue* value) const {
+ if ((unsigned)index < SK_ARRAY_COUNT(gConstants)) {
+ value->fOperand.fScalar = gConstants[index];
+ value->fType = SkType_Float;
+ return true;
+ }
+ SkASSERT(0);
+ return false;
+}
diff --git a/animator/SkDisplayMath.h b/animator/SkDisplayMath.h
new file mode 100644
index 00000000..91537953
--- /dev/null
+++ b/animator/SkDisplayMath.h
@@ -0,0 +1,31 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayMath_DEFINED
+#define SkDisplayMath_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkRandom.h"
+
+class SkDisplayMath : public SkDisplayable {
+ DECLARE_DISPLAY_MEMBER_INFO(Math);
+ virtual void executeFunction(SkDisplayable* , int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* );
+ virtual const SkFunctionParamType* getFunctionsParameters();
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+private:
+ mutable SkRandom fRandom;
+ static const SkScalar gConstants[];
+ static const SkFunctionParamType fFunctionParameters[];
+
+};
+
+#endif // SkDisplayMath_DEFINED
diff --git a/animator/SkDisplayMovie.cpp b/animator/SkDisplayMovie.cpp
new file mode 100644
index 00000000..ea832dcb
--- /dev/null
+++ b/animator/SkDisplayMovie.cpp
@@ -0,0 +1,128 @@
+
+/*
+ * 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 "SkDisplayMovie.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayMovie::fInfo[] = {
+ SK_MEMBER(src, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayMovie);
+
+SkDisplayMovie::SkDisplayMovie() : fDecodedSuccessfully(false), fLoaded(false), fMovieBuilt(false) {
+ fMovie.fMaker->fInMovie = true;
+}
+
+SkDisplayMovie::~SkDisplayMovie() {
+}
+
+void SkDisplayMovie::buildMovie() {
+ if (fMovieBuilt)
+ return;
+ SkAnimateMaker* movieMaker = fMovie.fMaker;
+ SkAnimateMaker* parentMaker = movieMaker->fParentMaker;
+ if (src.size() == 0 || parentMaker == NULL)
+ return;
+ movieMaker->fPrefix.set(parentMaker->fPrefix);
+ fDecodedSuccessfully = fMovie.fMaker->decodeURI(src.c_str());
+ if (fDecodedSuccessfully == false) {
+
+ if (movieMaker->getErrorCode() != SkXMLParserError::kNoError || movieMaker->getNativeCode() != -1) {
+ movieMaker->setInnerError(parentMaker, src);
+ parentMaker->setErrorCode(SkDisplayXMLParserError::kInMovie);
+ } else {
+ parentMaker->setErrorNoun(src);
+ parentMaker->setErrorCode(SkDisplayXMLParserError::kMovieNameUnknownOrMissing);
+ }
+ }
+ fMovieBuilt = true;
+}
+
+SkDisplayable* SkDisplayMovie::deepCopy(SkAnimateMaker* maker) {
+ SkDisplayMovie* copy = (SkDisplayMovie*) INHERITED::deepCopy(maker);
+ copy->fMovie.fMaker->fParentMaker = fMovie.fMaker->fParentMaker;
+ copy->fMovie.fMaker->fHostEventSinkID = fMovie.fMaker->fHostEventSinkID;
+ copy->fMovieBuilt = false;
+ *fMovie.fMaker->fParentMaker->fMovies.append() = copy;
+ return copy;
+}
+
+void SkDisplayMovie::dirty() {
+ buildMovie();
+}
+
+bool SkDisplayMovie::doEvent(SkDisplayEvent::Kind kind, SkEventState* state) {
+ if (fLoaded == false)
+ return false;
+ fMovie.fMaker->fEnableTime = fMovie.fMaker->fParentMaker->fEnableTime;
+ return fMovie.fMaker->fEvents.doEvent(*fMovie.fMaker, kind, state);
+}
+
+bool SkDisplayMovie::draw(SkAnimateMaker& maker) {
+ if (fDecodedSuccessfully == false)
+ return false;
+ if (fLoaded == false)
+ enable(maker);
+ maker.fCanvas->save();
+ SkPaint local = SkPaint(*maker.fPaint);
+ bool result = fMovie.draw(maker.fCanvas, &local,
+ maker.fDisplayList.getTime()) != SkAnimator::kNotDifferent;
+ maker.fDisplayList.fInvalBounds.join(fMovie.fMaker->fDisplayList.fInvalBounds);
+ maker.fCanvas->restore();
+ return result;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayMovie::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("src=\"%s\"/>\n", src.c_str());
+ SkAnimateMaker* movieMaker = fMovie.fMaker;
+ SkDisplayList::fIndent += 4;
+ movieMaker->fDisplayList.dumpInner(movieMaker);
+ SkDisplayList::fIndent -= 4;
+ dumpEnd(maker);
+}
+
+void SkDisplayMovie::dumpEvents() {
+ fMovie.fMaker->fEvents.dump(*fMovie.fMaker);
+}
+#endif
+
+bool SkDisplayMovie::enable(SkAnimateMaker&) {
+ if (fDecodedSuccessfully == false)
+ return false;
+ SkAnimateMaker* movieMaker = fMovie.fMaker;
+ movieMaker->fEvents.doEvent(*movieMaker, SkDisplayEvent::kOnload, NULL);
+ movieMaker->fEvents.removeEvent(SkDisplayEvent::kOnload, NULL);
+ fLoaded = true;
+ movieMaker->fLoaded = true;
+ return false;
+}
+
+bool SkDisplayMovie::hasEnable() const {
+ return true;
+}
+
+void SkDisplayMovie::onEndElement(SkAnimateMaker& maker) {
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ fMovie.fMaker->fDebugTimeBase = maker.fDebugTimeBase;
+#endif
+ fMovie.fMaker->fPrefix.set(maker.fPrefix);
+ fMovie.fMaker->fHostEventSinkID = maker.fHostEventSinkID;
+ fMovie.fMaker->fParentMaker = &maker;
+ buildMovie();
+ *maker.fMovies.append() = this;
+}
diff --git a/animator/SkDisplayMovie.h b/animator/SkDisplayMovie.h
new file mode 100644
index 00000000..6210602a
--- /dev/null
+++ b/animator/SkDisplayMovie.h
@@ -0,0 +1,51 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayMovie_DEFINED
+#define SkDisplayMovie_DEFINED
+
+#include "SkAnimator.h"
+#include "SkDrawable.h"
+#include "SkMemberInfo.h"
+
+struct SkEventState;
+
+class SkDisplayMovie : public SkDrawable {
+ DECLARE_DISPLAY_MEMBER_INFO(Movie);
+ SkDisplayMovie();
+ virtual ~SkDisplayMovie();
+ void buildMovie();
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual void dirty();
+ bool doEvent(const SkEvent& evt) {
+ return fLoaded && fMovie.doEvent(evt);
+ }
+ virtual bool doEvent(SkDisplayEvent::Kind , SkEventState* state );
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+ virtual void dumpEvents();
+#endif
+ virtual bool enable(SkAnimateMaker& );
+ const SkAnimator* getAnimator() const { return &fMovie; }
+ virtual bool hasEnable() const;
+ virtual void onEndElement(SkAnimateMaker& );
+protected:
+ SkString src;
+ SkAnimator fMovie;
+ SkBool8 fDecodedSuccessfully;
+ SkBool8 fLoaded;
+ SkBool8 fMovieBuilt;
+ friend class SkAnimateMaker;
+ friend class SkPost;
+private:
+ typedef SkDrawable INHERITED;
+};
+
+#endif // SkDisplayMovie_DEFINED
diff --git a/animator/SkDisplayNumber.cpp b/animator/SkDisplayNumber.cpp
new file mode 100644
index 00000000..82b658f7
--- /dev/null
+++ b/animator/SkDisplayNumber.cpp
@@ -0,0 +1,70 @@
+
+/*
+ * 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 "SkDisplayNumber.h"
+
+enum SkDisplayNumber_Properties {
+ SK_PROPERTY(MAX_VALUE),
+ SK_PROPERTY(MIN_VALUE),
+ SK_PROPERTY(NEGATIVE_INFINITY),
+ SK_PROPERTY(NaN),
+ SK_PROPERTY(POSITIVE_INFINITY)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayNumber::fInfo[] = {
+ SK_MEMBER_PROPERTY(MAX_VALUE, Float),
+ SK_MEMBER_PROPERTY(MIN_VALUE, Float),
+ SK_MEMBER_PROPERTY(NEGATIVE_INFINITY, Float),
+ SK_MEMBER_PROPERTY(NaN, Float),
+ SK_MEMBER_PROPERTY(POSITIVE_INFINITY, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayNumber);
+
+#if defined _WIN32
+#pragma warning ( push )
+// we are intentionally causing an overflow here
+// (warning C4756: overflow in constant arithmetic)
+#pragma warning ( disable : 4756 )
+#endif
+
+bool SkDisplayNumber::getProperty(int index, SkScriptValue* value) const {
+ SkScalar constant;
+ switch (index) {
+ case SK_PROPERTY(MAX_VALUE):
+ constant = SK_ScalarMax;
+ break;
+ case SK_PROPERTY(MIN_VALUE):
+ constant = SK_ScalarMin;
+ break;
+ case SK_PROPERTY(NEGATIVE_INFINITY):
+ constant = -SK_ScalarInfinity;
+ break;
+ case SK_PROPERTY(NaN):
+ constant = SK_ScalarNaN;
+ break;
+ case SK_PROPERTY(POSITIVE_INFINITY):
+ constant = SK_ScalarInfinity;
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ value->fOperand.fScalar = constant;
+ value->fType = SkType_Float;
+ return true;
+}
+
+#if defined _WIN32
+#pragma warning ( pop )
+#endif
diff --git a/animator/SkDisplayNumber.h b/animator/SkDisplayNumber.h
new file mode 100644
index 00000000..16c6f4fd
--- /dev/null
+++ b/animator/SkDisplayNumber.h
@@ -0,0 +1,22 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayNumber_DEFINED
+#define SkDisplayNumber_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+
+class SkDisplayNumber : public SkDisplayable {
+ DECLARE_DISPLAY_MEMBER_INFO(Number);
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+private:
+};
+
+#endif // SkDisplayNumber_DEFINED
diff --git a/animator/SkDisplayPost.cpp b/animator/SkDisplayPost.cpp
new file mode 100644
index 00000000..cc45b216
--- /dev/null
+++ b/animator/SkDisplayPost.cpp
@@ -0,0 +1,298 @@
+
+/*
+ * 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 "SkDisplayPost.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimator.h"
+#include "SkDisplayMovie.h"
+#include "SkPostParts.h"
+#include "SkScript.h"
+#ifdef SK_DEBUG
+#include "SkDump.h"
+#include "SkTime.h"
+#endif
+
+enum SkPost_Properties {
+ SK_PROPERTY(target),
+ SK_PROPERTY(type)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkPost::fInfo[] = {
+ SK_MEMBER(delay, MSec),
+// SK_MEMBER(initialized, Boolean),
+ SK_MEMBER(mode, EventMode),
+ SK_MEMBER(sink, String),
+ SK_MEMBER_PROPERTY(target, String),
+ SK_MEMBER_PROPERTY(type, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkPost);
+
+SkPost::SkPost() : delay(0), /*initialized(SkBool(-1)), */ mode(kImmediate), fMaker(NULL),
+ fSinkID(0), fTargetMaker(NULL), fChildHasID(false), fDirty(false) {
+}
+
+SkPost::~SkPost() {
+ for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+}
+
+bool SkPost::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ SkASSERT(child && child->isDataInput());
+ SkDataInput* part = (SkDataInput*) child;
+ *fParts.append() = part;
+ return true;
+}
+
+bool SkPost::childrenNeedDisposing() const {
+ return false;
+}
+
+void SkPost::dirty() {
+ fDirty = true;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkPost::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkString* eventType = new SkString();
+ fEvent.getType(eventType);
+ if (eventType->equals("user")) {
+ const char* target = fEvent.findString("id");
+ SkDebugf("target=\"%s\" ", target);
+ }
+ else
+ SkDebugf("type=\"%s\" ", eventType->c_str());
+ delete eventType;
+
+ if (delay > 0) {
+ SkDebugf("delay=\"%g\" ", SkScalarToFloat(SkScalarDiv(delay, 1000)));
+ }
+// if (initialized == false)
+// SkDebugf("(uninitialized) ");
+ SkString string;
+ SkDump::GetEnumString(SkType_EventMode, mode, &string);
+ if (!string.equals("immediate"))
+ SkDebugf("mode=\"%s\" ", string.c_str());
+ // !!! could enhance this to search through make hierarchy to show name of sink
+ if (sink.size() > 0) {
+ SkDebugf("sink=\"%s\" sinkID=\"%d\" ", sink.c_str(), fSinkID);
+ } else if (fSinkID != maker->getAnimator()->getSinkID() && fSinkID != 0) {
+ SkDebugf("sinkID=\"%d\" ", fSinkID);
+ }
+ const SkMetaData& meta = fEvent.getMetaData();
+ SkMetaData::Iter iter(meta);
+ SkMetaData::Type type;
+ int number;
+ const char* name;
+ bool closedYet = false;
+ SkDisplayList::fIndent += 4;
+ //this seems to work, but kinda hacky
+ //for some reason the last part is id, which i don't want
+ //and the parts seem to be in the reverse order from the one in which we find the
+ //data itself
+ //SkDataInput** ptr = fParts.end();
+ //SkDataInput* data;
+ //const char* ID;
+ while ((name = iter.next(&type, &number)) != NULL) {
+ //ptr--;
+ if (strcmp(name, "id") == 0)
+ continue;
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ //data = *ptr;
+ //if (data->id)
+ // ID = data->id;
+ //else
+ // ID = "";
+ SkDebugf("%*s<data name=\"%s\" ", SkDisplayList::fIndent, "", name);
+ switch (type) {
+ case SkMetaData::kS32_Type: {
+ int32_t s32;
+ meta.findS32(name, &s32);
+ SkDebugf("int=\"%d\" ", s32);
+ } break;
+ case SkMetaData::kScalar_Type: {
+ SkScalar scalar;
+ meta.findScalar(name, &scalar);
+ SkDebugf("float=\"%g\" ", SkScalarToFloat(scalar));
+ } break;
+ case SkMetaData::kString_Type:
+ SkDebugf("string=\"%s\" ", meta.findString(name));
+ break;
+ case SkMetaData::kPtr_Type: {//when do we have a pointer
+ void* ptr;
+ meta.findPtr(name, &ptr);
+ SkDebugf("0x%08x ", ptr);
+ } break;
+ case SkMetaData::kBool_Type: {
+ bool boolean;
+ meta.findBool(name, &boolean);
+ SkDebugf("boolean=\"%s\" ", boolean ? "true " : "false ");
+ } break;
+ default:
+ break;
+ }
+ SkDebugf("/>\n");
+ //ptr++;
+/* perhaps this should only be done in the case of a pointer?
+ SkDisplayable* displayable;
+ if (maker->find(name, &displayable))
+ displayable->dump(maker);
+ else
+ SkDebugf("\n");*/
+ }
+ SkDisplayList::fIndent -= 4;
+ if (closedYet)
+ dumpEnd(maker);
+ else
+ SkDebugf("/>\n");
+
+}
+#endif
+
+bool SkPost::enable(SkAnimateMaker& maker ) {
+ if (maker.hasError())
+ return true;
+ if (fDirty) {
+ if (sink.size() > 0)
+ findSinkID();
+ if (fChildHasID) {
+ SkString preserveID(fEvent.findString("id"));
+ fEvent.getMetaData().reset();
+ if (preserveID.size() > 0)
+ fEvent.setString("id", preserveID);
+ for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++) {
+ if ((*part)->add())
+ maker.setErrorCode(SkDisplayXMLParserError::kErrorAddingDataToPost);
+ }
+ }
+ fDirty = false;
+ }
+#ifdef SK_DUMP_ENABLED
+ if (maker.fDumpPosts) {
+ SkDebugf("post enable: ");
+ dump(&maker);
+ }
+#if defined SK_DEBUG_ANIMATION_TIMING
+ SkString debugOut;
+ SkMSec time = maker.getAppTime();
+ debugOut.appendS32(time - maker.fDebugTimeBase);
+ debugOut.append(" post id=");
+ debugOut.append(_id);
+ debugOut.append(" enable=");
+ debugOut.appendS32(maker.fEnableTime - maker.fDebugTimeBase);
+ debugOut.append(" delay=");
+ debugOut.appendS32(delay);
+#endif
+#endif
+// SkMSec adjustedDelay = maker.adjustDelay(maker.fEnableTime, delay);
+ SkMSec futureTime = maker.fEnableTime + delay;
+ fEvent.setFast32(futureTime);
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ debugOut.append(" future=");
+ debugOut.appendS32(futureTime - maker.fDebugTimeBase);
+ SkDebugf("%s\n", debugOut.c_str());
+#endif
+ SkEventSinkID targetID = fSinkID;
+ bool isAnimatorEvent = true;
+ SkAnimator* anim = maker.getAnimator();
+ if (targetID == 0) {
+ isAnimatorEvent = fEvent.findString("id") != NULL;
+ if (isAnimatorEvent)
+ targetID = anim->getSinkID();
+ else if (maker.fHostEventSinkID)
+ targetID = maker.fHostEventSinkID;
+ else
+ return true;
+ } else
+ anim = fTargetMaker->getAnimator();
+ if (delay == 0) {
+ if (isAnimatorEvent && mode == kImmediate)
+ fTargetMaker->doEvent(fEvent);
+ else
+ anim->onEventPost(new SkEvent(fEvent), targetID);
+ } else
+ anim->onEventPostTime(new SkEvent(fEvent), targetID, futureTime);
+ return true;
+}
+
+void SkPost::findSinkID() {
+ // get the next delimiter '.' if any
+ fTargetMaker = fMaker;
+ const char* ch = sink.c_str();
+ do {
+ const char* end = strchr(ch, '.');
+ size_t len = end ? (size_t) (end - ch) : strlen(ch);
+ SkDisplayable* displayable = NULL;
+ if (SK_LITERAL_STR_EQUAL("parent", ch, len)) {
+ if (fTargetMaker->fParentMaker)
+ fTargetMaker = fTargetMaker->fParentMaker;
+ else {
+ fTargetMaker->setErrorCode(SkDisplayXMLParserError::kNoParentAvailable);
+ return;
+ }
+ } else {
+ fTargetMaker->find(ch, len, &displayable);
+ if (displayable == NULL || displayable->getType() != SkType_Movie) {
+ fTargetMaker->setErrorCode(SkDisplayXMLParserError::kExpectedMovie);
+ return;
+ }
+ SkDisplayMovie* movie = (SkDisplayMovie*) displayable;
+ fTargetMaker = movie->fMovie.fMaker;
+ }
+ if (end == NULL)
+ break;
+ ch = ++end;
+ } while (true);
+ SkAnimator* anim = fTargetMaker->getAnimator();
+ fSinkID = anim->getSinkID();
+}
+
+bool SkPost::hasEnable() const {
+ return true;
+}
+
+void SkPost::onEndElement(SkAnimateMaker& maker) {
+ fTargetMaker = fMaker = &maker;
+ if (fChildHasID == false) {
+ for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+ fParts.reset();
+ }
+}
+
+void SkPost::setChildHasID() {
+ fChildHasID = true;
+}
+
+bool SkPost::setProperty(int index, SkScriptValue& value) {
+ SkASSERT(value.fType == SkType_String);
+ SkString* string = value.fOperand.fString;
+ switch(index) {
+ case SK_PROPERTY(target): {
+ fEvent.setType("user");
+ fEvent.setString("id", *string);
+ mode = kImmediate;
+ } break;
+ case SK_PROPERTY(type):
+ fEvent.setType(*string);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
diff --git a/animator/SkDisplayPost.h b/animator/SkDisplayPost.h
new file mode 100644
index 00000000..cd223068
--- /dev/null
+++ b/animator/SkDisplayPost.h
@@ -0,0 +1,59 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayPost_DEFINED
+#define SkDisplayPost_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkEvent.h"
+#include "SkEventSink.h"
+#include "SkMemberInfo.h"
+#include "SkIntArray.h"
+
+class SkDataInput;
+class SkAnimateMaker;
+
+class SkPost : public SkDisplayable {
+ DECLARE_MEMBER_INFO(Post);
+ enum Mode {
+ kDeferred,
+ kImmediate
+ };
+ SkPost();
+ virtual ~SkPost();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool childrenNeedDisposing() const;
+ virtual void dirty();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool enable(SkAnimateMaker& );
+ virtual bool hasEnable() const;
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual void setChildHasID();
+ virtual bool setProperty(int index, SkScriptValue& );
+protected:
+ SkMSec delay;
+ SkString sink;
+// SkBool initialized;
+ Mode mode;
+ SkEvent fEvent;
+ SkAnimateMaker* fMaker;
+ SkTDDataArray fParts;
+ SkEventSinkID fSinkID;
+ SkAnimateMaker* fTargetMaker;
+ SkBool8 fChildHasID;
+ SkBool8 fDirty;
+private:
+ void findSinkID();
+ friend class SkDataInput;
+ typedef SkDisplayable INHERITED;
+};
+
+#endif //SkDisplayPost_DEFINED
diff --git a/animator/SkDisplayRandom.cpp b/animator/SkDisplayRandom.cpp
new file mode 100644
index 00000000..2efe8dc9
--- /dev/null
+++ b/animator/SkDisplayRandom.cpp
@@ -0,0 +1,65 @@
+
+/*
+ * 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 "SkDisplayRandom.h"
+#include "SkInterpolator.h"
+
+enum SkDisplayRandom_Properties {
+ SK_PROPERTY(random),
+ SK_PROPERTY(seed)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayRandom::fInfo[] = {
+ SK_MEMBER(blend, Float),
+ SK_MEMBER(max, Float),
+ SK_MEMBER(min, Float),
+ SK_MEMBER_DYNAMIC_PROPERTY(random, Float),
+ SK_MEMBER_PROPERTY(seed, Int)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayRandom);
+
+SkDisplayRandom::SkDisplayRandom() : blend(0), min(0), max(SK_Scalar1) {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayRandom::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("min=\"%g\" ", SkScalarToFloat(min));
+ SkDebugf("max=\"%g\" ", SkScalarToFloat(max));
+ SkDebugf("blend=\"%g\" ", SkScalarToFloat(blend));
+ SkDebugf("/>\n");
+}
+#endif
+
+bool SkDisplayRandom::getProperty(int index, SkScriptValue* value) const {
+ switch(index) {
+ case SK_PROPERTY(random): {
+ SkScalar random = fRandom.nextUScalar1();
+ SkScalar relativeT = SkUnitCubicInterp(random, SK_Scalar1 - blend, 0, 0, SK_Scalar1 - blend);
+ value->fOperand.fScalar = min + SkScalarMul(max - min, relativeT);
+ value->fType = SkType_Float;
+ return true;
+ }
+ default:
+ SkASSERT(0);
+ }
+ return false;
+}
+
+bool SkDisplayRandom::setProperty(int index, SkScriptValue& value) {
+ SkASSERT(index == SK_PROPERTY(seed));
+ SkASSERT(value.fType == SkType_Int);
+ fRandom.setSeed(value.fOperand.fS32);
+ return true;
+}
diff --git a/animator/SkDisplayRandom.h b/animator/SkDisplayRandom.h
new file mode 100644
index 00000000..1c386533
--- /dev/null
+++ b/animator/SkDisplayRandom.h
@@ -0,0 +1,40 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayRandom_DEFINED
+#define SkDisplayRandom_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkRandom.h"
+
+#ifdef min
+#undef min
+#endif
+
+#ifdef max
+#undef max
+#endif
+
+class SkDisplayRandom : public SkDisplayable {
+ DECLARE_DISPLAY_MEMBER_INFO(Random);
+ SkDisplayRandom();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool setProperty(int index, SkScriptValue& );
+private:
+ SkScalar blend;
+ SkScalar min;
+ SkScalar max;
+ mutable SkRandom fRandom;
+};
+
+#endif // SkDisplayRandom_DEFINED
diff --git a/animator/SkDisplayScreenplay.cpp b/animator/SkDisplayScreenplay.cpp
new file mode 100644
index 00000000..2663b431
--- /dev/null
+++ b/animator/SkDisplayScreenplay.cpp
@@ -0,0 +1,20 @@
+
+/*
+ * 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 "SkDisplayScreenplay.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayScreenplay::fInfo[] = {
+ SK_MEMBER(time, MSec)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayScreenplay);
diff --git a/animator/SkDisplayScreenplay.h b/animator/SkDisplayScreenplay.h
new file mode 100644
index 00000000..0265548e
--- /dev/null
+++ b/animator/SkDisplayScreenplay.h
@@ -0,0 +1,21 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayScreenplay_DEFINED
+#define SkDisplayScreenplay_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+
+class SkDisplayScreenplay : public SkDisplayable {
+ DECLARE_DISPLAY_MEMBER_INFO(Screenplay);
+ SkMSec time;
+};
+
+#endif // SkDisplayScreenplay_DEFINED
diff --git a/animator/SkDisplayType.cpp b/animator/SkDisplayType.cpp
new file mode 100644
index 00000000..dc52f0ca
--- /dev/null
+++ b/animator/SkDisplayType.cpp
@@ -0,0 +1,766 @@
+
+/*
+ * 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 "SkDisplayType.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateSet.h"
+#include "SkDisplayAdd.h"
+#include "SkDisplayApply.h"
+#include "SkDisplayBounds.h"
+#include "SkDisplayEvent.h"
+#include "SkDisplayInclude.h"
+#ifdef SK_DEBUG
+#include "SkDisplayList.h"
+#endif
+#include "SkDisplayMath.h"
+#include "SkDisplayMovie.h"
+#include "SkDisplayNumber.h"
+#include "SkDisplayPost.h"
+#include "SkDisplayRandom.h"
+#include "SkDisplayTypes.h"
+#include "SkDraw3D.h"
+#include "SkDrawBitmap.h"
+#include "SkDrawClip.h"
+#include "SkDrawDash.h"
+#include "SkDrawDiscrete.h"
+#include "SkDrawEmboss.h"
+#include "SkDrawFull.h"
+#include "SkDrawGradient.h"
+#include "SkDrawLine.h"
+#include "SkDrawMatrix.h"
+#include "SkDrawOval.h"
+#include "SkDrawPaint.h"
+#include "SkDrawPath.h"
+#include "SkDrawPoint.h"
+#include "SkDrawSaveLayer.h"
+#include "SkDrawText.h"
+#include "SkDrawTextBox.h"
+#include "SkDrawTo.h"
+#include "SkDrawTransparentShader.h"
+#include "SkDump.h"
+#include "SkExtras.h"
+#include "SkHitClear.h"
+#include "SkHitTest.h"
+#include "SkMatrixParts.h"
+#include "SkPathParts.h"
+#include "SkPostParts.h"
+#include "SkSnapshot.h"
+#include "SkTextOnPath.h"
+#include "SkTextToPath.h"
+#include "SkTSearch.h"
+
+#define CASE_NEW(_class) \
+ case SkType_##_class: result = new Sk##_class(); break
+#define CASE_DRAW_NEW(_class) \
+ case SkType_##_class: result = new SkDraw##_class(); break
+#define CASE_DISPLAY_NEW(_class) \
+ case SkType_##_class: result = new SkDisplay##_class(); break
+#ifdef SK_DEBUG
+ #define CASE_DEBUG_RETURN_NIL(_class) \
+ case SkType_##_class: return NULL
+#else
+ #define CASE_DEBUG_RETURN_NIL(_class)
+#endif
+
+
+SkDisplayTypes SkDisplayType::gNewTypes = kNumberOfTypes;
+
+SkDisplayable* SkDisplayType::CreateInstance(SkAnimateMaker* maker, SkDisplayTypes type) {
+ SkDisplayable* result = NULL;
+ switch (type) {
+ // unknown
+ CASE_DISPLAY_NEW(Math);
+ CASE_DISPLAY_NEW(Number);
+ CASE_NEW(Add);
+ CASE_NEW(AddCircle);
+ // addgeom
+ CASE_DEBUG_RETURN_NIL(AddMode);
+ CASE_NEW(AddOval);
+ CASE_NEW(AddPath);
+ CASE_NEW(AddRect);
+ CASE_NEW(AddRoundRect);
+ CASE_DEBUG_RETURN_NIL(Align);
+ CASE_NEW(Animate);
+ // animatebase
+ CASE_NEW(Apply);
+ CASE_DEBUG_RETURN_NIL(ApplyMode);
+ CASE_DEBUG_RETURN_NIL(ApplyTransition);
+ CASE_DISPLAY_NEW(Array);
+ // argb
+ // base64
+ // basebitmap
+ // baseclassinfo
+ CASE_DRAW_NEW(Bitmap);
+ // bitmapencoding
+ // bitmapformat
+ CASE_DRAW_NEW(BitmapShader);
+ CASE_DRAW_NEW(Blur);
+ CASE_DISPLAY_NEW(Boolean);
+ // boundable
+ CASE_DISPLAY_NEW(Bounds);
+ CASE_DEBUG_RETURN_NIL(Cap);
+ CASE_NEW(Clear);
+ CASE_DRAW_NEW(Clip);
+ CASE_NEW(Close);
+ CASE_DRAW_NEW(Color);
+ CASE_NEW(CubicTo);
+ CASE_NEW(Dash);
+ CASE_NEW(DataInput);
+ CASE_NEW(Discrete);
+ // displayable
+ // drawable
+ CASE_NEW(DrawTo);
+ CASE_NEW(Dump);
+ // dynamicstring
+ CASE_DRAW_NEW(Emboss);
+ CASE_DISPLAY_NEW(Event);
+ CASE_DEBUG_RETURN_NIL(EventCode);
+ CASE_DEBUG_RETURN_NIL(EventKind);
+ CASE_DEBUG_RETURN_NIL(EventMode);
+ // filltype
+ // filtertype
+ CASE_DISPLAY_NEW(Float);
+ CASE_NEW(FromPath);
+ CASE_DEBUG_RETURN_NIL(FromPathMode);
+ CASE_NEW(Full);
+ // gradient
+ CASE_NEW(Group);
+ CASE_NEW(HitClear);
+ CASE_NEW(HitTest);
+ CASE_NEW(ImageBaseBitmap);
+ CASE_NEW(Include);
+ CASE_NEW(Input);
+ CASE_DISPLAY_NEW(Int);
+ CASE_DEBUG_RETURN_NIL(Join);
+ CASE_NEW(Line);
+ CASE_NEW(LineTo);
+ CASE_NEW(DrawLinearGradient);
+ CASE_DRAW_NEW(MaskFilter);
+ CASE_DEBUG_RETURN_NIL(MaskFilterBlurStyle);
+ // maskfilterlight
+ CASE_DRAW_NEW(Matrix);
+ // memberfunction
+ // memberproperty
+ CASE_NEW(Move);
+ CASE_NEW(MoveTo);
+ CASE_DISPLAY_NEW(Movie);
+ // msec
+ CASE_NEW(Oval);
+ CASE_DRAW_NEW(Paint);
+ CASE_DRAW_NEW(Path);
+ // pathdirection
+ CASE_DRAW_NEW(PathEffect);
+ // point
+ CASE_NEW(DrawPoint);
+ CASE_NEW(PolyToPoly);
+ CASE_NEW(Polygon);
+ CASE_NEW(Polyline);
+ CASE_NEW(Post);
+ CASE_NEW(QuadTo);
+ CASE_NEW(RCubicTo);
+ CASE_NEW(RLineTo);
+ CASE_NEW(RMoveTo);
+ CASE_NEW(RQuadTo);
+ CASE_NEW(DrawRadialGradient);
+ CASE_DISPLAY_NEW(Random);
+ CASE_DRAW_NEW(Rect);
+ CASE_NEW(RectToRect);
+ CASE_NEW(Remove);
+ CASE_NEW(Replace);
+ CASE_NEW(Rotate);
+ CASE_NEW(RoundRect);
+ CASE_NEW(Save);
+ CASE_NEW(SaveLayer);
+ CASE_NEW(Scale);
+ // screenplay
+ CASE_NEW(Set);
+ CASE_DRAW_NEW(Shader);
+ CASE_NEW(Skew);
+ CASE_NEW(3D_Camera);
+ CASE_NEW(3D_Patch);
+ // 3dpoint
+ CASE_NEW(Snapshot);
+ CASE_DISPLAY_NEW(String);
+ // style
+ CASE_NEW(Text);
+ CASE_DRAW_NEW(TextBox);
+ // textboxalign
+ // textboxmode
+ CASE_NEW(TextOnPath);
+ CASE_NEW(TextToPath);
+ CASE_DEBUG_RETURN_NIL(TileMode);
+ CASE_NEW(Translate);
+ CASE_DRAW_NEW(TransparentShader);
+ CASE_DRAW_NEW(Typeface);
+ CASE_DEBUG_RETURN_NIL(Xfermode);
+ default:
+ SkExtras** end = maker->fExtras.end();
+ for (SkExtras** extraPtr = maker->fExtras.begin(); extraPtr < end; extraPtr++) {
+ if ((result = (*extraPtr)->createInstance(type)) != NULL)
+ return result;
+ }
+ SkASSERT(0);
+ }
+ return result;
+}
+
+#undef CASE_NEW
+#undef CASE_DRAW_NEW
+#undef CASE_DISPLAY_NEW
+
+#if SK_USE_CONDENSED_INFO == 0
+
+#define CASE_GET_INFO(_class) case SkType_##_class: \
+ info = Sk##_class::fInfo; infoCount = Sk##_class::fInfoCount; break
+#define CASE_GET_DRAW_INFO(_class) case SkType_##_class: \
+ info = SkDraw##_class::fInfo; infoCount = SkDraw##_class::fInfoCount; break
+#define CASE_GET_DISPLAY_INFO(_class) case SkType_##_class: \
+ info = SkDisplay##_class::fInfo; infoCount = SkDisplay##_class::fInfoCount; \
+ break
+
+const SkMemberInfo* SkDisplayType::GetMembers(SkAnimateMaker* maker,
+ SkDisplayTypes type, int* infoCountPtr) {
+ const SkMemberInfo* info = NULL;
+ int infoCount = 0;
+ switch (type) {
+ // unknown
+ CASE_GET_DISPLAY_INFO(Math);
+ CASE_GET_DISPLAY_INFO(Number);
+ CASE_GET_INFO(Add);
+ CASE_GET_INFO(AddCircle);
+ CASE_GET_INFO(AddGeom);
+ // addmode
+ CASE_GET_INFO(AddOval);
+ CASE_GET_INFO(AddPath);
+ CASE_GET_INFO(AddRect);
+ CASE_GET_INFO(AddRoundRect);
+ // align
+ CASE_GET_INFO(Animate);
+ CASE_GET_INFO(AnimateBase);
+ CASE_GET_INFO(Apply);
+ // applymode
+ // applytransition
+ CASE_GET_DISPLAY_INFO(Array);
+ // argb
+ // base64
+ CASE_GET_INFO(BaseBitmap);
+ // baseclassinfo
+ CASE_GET_DRAW_INFO(Bitmap);
+ // bitmapencoding
+ // bitmapformat
+ CASE_GET_DRAW_INFO(BitmapShader);
+ CASE_GET_DRAW_INFO(Blur);
+ CASE_GET_DISPLAY_INFO(Boolean);
+ // boundable
+ CASE_GET_DISPLAY_INFO(Bounds);
+ // cap
+ // clear
+ CASE_GET_DRAW_INFO(Clip);
+ // close
+ CASE_GET_DRAW_INFO(Color);
+ CASE_GET_INFO(CubicTo);
+ CASE_GET_INFO(Dash);
+ CASE_GET_INFO(DataInput);
+ CASE_GET_INFO(Discrete);
+ // displayable
+ // drawable
+ CASE_GET_INFO(DrawTo);
+ CASE_GET_INFO(Dump);
+ // dynamicstring
+ CASE_GET_DRAW_INFO(Emboss);
+ CASE_GET_DISPLAY_INFO(Event);
+ // eventcode
+ // eventkind
+ // eventmode
+ // filltype
+ // filtertype
+ CASE_GET_DISPLAY_INFO(Float);
+ CASE_GET_INFO(FromPath);
+ // frompathmode
+ // full
+ CASE_GET_INFO(DrawGradient);
+ CASE_GET_INFO(Group);
+ CASE_GET_INFO(HitClear);
+ CASE_GET_INFO(HitTest);
+ CASE_GET_INFO(ImageBaseBitmap);
+ CASE_GET_INFO(Include);
+ CASE_GET_INFO(Input);
+ CASE_GET_DISPLAY_INFO(Int);
+ // join
+ CASE_GET_INFO(Line);
+ CASE_GET_INFO(LineTo);
+ CASE_GET_INFO(DrawLinearGradient);
+ // maskfilter
+ // maskfilterblurstyle
+ // maskfilterlight
+ CASE_GET_DRAW_INFO(Matrix);
+ // memberfunction
+ // memberproperty
+ CASE_GET_INFO(Move);
+ CASE_GET_INFO(MoveTo);
+ CASE_GET_DISPLAY_INFO(Movie);
+ // msec
+ CASE_GET_INFO(Oval);
+ CASE_GET_DRAW_INFO(Path);
+ CASE_GET_DRAW_INFO(Paint);
+ // pathdirection
+ // patheffect
+ case SkType_Point: info = Sk_Point::fInfo; infoCount = Sk_Point::fInfoCount; break; // no virtual flavor
+ CASE_GET_INFO(DrawPoint); // virtual flavor
+ CASE_GET_INFO(PolyToPoly);
+ CASE_GET_INFO(Polygon);
+ CASE_GET_INFO(Polyline);
+ CASE_GET_INFO(Post);
+ CASE_GET_INFO(QuadTo);
+ CASE_GET_INFO(RCubicTo);
+ CASE_GET_INFO(RLineTo);
+ CASE_GET_INFO(RMoveTo);
+ CASE_GET_INFO(RQuadTo);
+ CASE_GET_INFO(DrawRadialGradient);
+ CASE_GET_DISPLAY_INFO(Random);
+ CASE_GET_DRAW_INFO(Rect);
+ CASE_GET_INFO(RectToRect);
+ CASE_GET_INFO(Remove);
+ CASE_GET_INFO(Replace);
+ CASE_GET_INFO(Rotate);
+ CASE_GET_INFO(RoundRect);
+ CASE_GET_INFO(Save);
+ CASE_GET_INFO(SaveLayer);
+ CASE_GET_INFO(Scale);
+ // screenplay
+ CASE_GET_INFO(Set);
+ CASE_GET_DRAW_INFO(Shader);
+ CASE_GET_INFO(Skew);
+ CASE_GET_INFO(3D_Camera);
+ CASE_GET_INFO(3D_Patch);
+ CASE_GET_INFO(3D_Point);
+ CASE_GET_INFO(Snapshot);
+ CASE_GET_DISPLAY_INFO(String);
+ // style
+ CASE_GET_INFO(Text);
+ CASE_GET_DRAW_INFO(TextBox);
+ // textboxalign
+ // textboxmode
+ CASE_GET_INFO(TextOnPath);
+ CASE_GET_INFO(TextToPath);
+ // tilemode
+ CASE_GET_INFO(Translate);
+ // transparentshader
+ CASE_GET_DRAW_INFO(Typeface);
+ // xfermode
+ // knumberoftypes
+ default:
+ if (maker) {
+ SkExtras** end = maker->fExtras.end();
+ for (SkExtras** extraPtr = maker->fExtras.begin(); extraPtr < end; extraPtr++) {
+ if ((info = (*extraPtr)->getMembers(type, infoCountPtr)) != NULL)
+ return info;
+ }
+ }
+ return NULL;
+ }
+ if (infoCountPtr)
+ *infoCountPtr = infoCount;
+ return info;
+}
+
+const SkMemberInfo* SkDisplayType::GetMember(SkAnimateMaker* maker,
+ SkDisplayTypes type, const char** matchPtr ) {
+ int infoCount;
+ const SkMemberInfo* info = GetMembers(maker, type, &infoCount);
+ info = SkMemberInfo::Find(info, infoCount, matchPtr);
+// SkASSERT(info);
+ return info;
+}
+
+#undef CASE_GET_INFO
+#undef CASE_GET_DRAW_INFO
+#undef CASE_GET_DISPLAY_INFO
+
+#endif // SK_USE_CONDENSED_INFO == 0
+
+#if defined SK_DEBUG || defined SK_BUILD_CONDENSED
+ #define DRAW_NAME(_name, _type) {_name, _type, true, false }
+ #define DISPLAY_NAME(_name, _type) {_name, _type, false, true }
+ #define INIT_BOOL_FIELDS , false, false
+#else
+ #define DRAW_NAME(_name, _type) {_name, _type }
+ #define DISPLAY_NAME(_name, _type) {_name, _type }
+ #define INIT_BOOL_FIELDS
+#endif
+
+const TypeNames gTypeNames[] = {
+ // unknown
+ { "Math", SkType_Math INIT_BOOL_FIELDS },
+ { "Number", SkType_Number INIT_BOOL_FIELDS },
+ { "add", SkType_Add INIT_BOOL_FIELDS },
+ { "addCircle", SkType_AddCircle INIT_BOOL_FIELDS },
+ // addgeom
+ // addmode
+ { "addOval", SkType_AddOval INIT_BOOL_FIELDS },
+ { "addPath", SkType_AddPath INIT_BOOL_FIELDS },
+ { "addRect", SkType_AddRect INIT_BOOL_FIELDS },
+ { "addRoundRect", SkType_AddRoundRect INIT_BOOL_FIELDS },
+ // align
+ { "animate", SkType_Animate INIT_BOOL_FIELDS },
+ // animateBase
+ { "apply", SkType_Apply INIT_BOOL_FIELDS },
+ // applymode
+ // applytransition
+ { "array", SkType_Array INIT_BOOL_FIELDS },
+ // argb
+ // base64
+ // basebitmap
+ // baseclassinfo
+ DRAW_NAME("bitmap", SkType_Bitmap),
+ // bitmapencoding
+ // bitmapformat
+ DRAW_NAME("bitmapShader", SkType_BitmapShader),
+ DRAW_NAME("blur", SkType_Blur),
+ { "boolean", SkType_Boolean INIT_BOOL_FIELDS },
+ // boundable
+ DISPLAY_NAME("bounds", SkType_Bounds),
+ // cap
+ { "clear", SkType_Clear INIT_BOOL_FIELDS },
+ DRAW_NAME("clip", SkType_Clip),
+ { "close", SkType_Close INIT_BOOL_FIELDS },
+ DRAW_NAME("color", SkType_Color),
+ { "cubicTo", SkType_CubicTo INIT_BOOL_FIELDS },
+ { "dash", SkType_Dash INIT_BOOL_FIELDS },
+ { "data", SkType_DataInput INIT_BOOL_FIELDS },
+ { "discrete", SkType_Discrete INIT_BOOL_FIELDS },
+ // displayable
+ // drawable
+ { "drawTo", SkType_DrawTo INIT_BOOL_FIELDS },
+ { "dump", SkType_Dump INIT_BOOL_FIELDS },
+ // dynamicstring
+ DRAW_NAME("emboss", SkType_Emboss),
+ DISPLAY_NAME("event", SkType_Event),
+ // eventcode
+ // eventkind
+ // eventmode
+ // filltype
+ // filtertype
+ { "float", SkType_Float INIT_BOOL_FIELDS },
+ { "fromPath", SkType_FromPath INIT_BOOL_FIELDS },
+ // frompathmode
+ { "full", SkType_Full INIT_BOOL_FIELDS },
+ // gradient
+ { "group", SkType_Group INIT_BOOL_FIELDS },
+ { "hitClear", SkType_HitClear INIT_BOOL_FIELDS },
+ { "hitTest", SkType_HitTest INIT_BOOL_FIELDS },
+ { "image", SkType_ImageBaseBitmap INIT_BOOL_FIELDS },
+ { "include", SkType_Include INIT_BOOL_FIELDS },
+ { "input", SkType_Input INIT_BOOL_FIELDS },
+ { "int", SkType_Int INIT_BOOL_FIELDS },
+ // join
+ { "line", SkType_Line INIT_BOOL_FIELDS },
+ { "lineTo", SkType_LineTo INIT_BOOL_FIELDS },
+ { "linearGradient", SkType_DrawLinearGradient INIT_BOOL_FIELDS },
+ { "maskFilter", SkType_MaskFilter INIT_BOOL_FIELDS },
+ // maskfilterblurstyle
+ // maskfilterlight
+ DRAW_NAME("matrix", SkType_Matrix),
+ // memberfunction
+ // memberproperty
+ { "move", SkType_Move INIT_BOOL_FIELDS },
+ { "moveTo", SkType_MoveTo INIT_BOOL_FIELDS },
+ { "movie", SkType_Movie INIT_BOOL_FIELDS },
+ // msec
+ { "oval", SkType_Oval INIT_BOOL_FIELDS },
+ DRAW_NAME("paint", SkType_Paint),
+ DRAW_NAME("path", SkType_Path),
+ // pathdirection
+ { "pathEffect", SkType_PathEffect INIT_BOOL_FIELDS },
+ // point
+ DRAW_NAME("point", SkType_DrawPoint),
+ { "polyToPoly", SkType_PolyToPoly INIT_BOOL_FIELDS },
+ { "polygon", SkType_Polygon INIT_BOOL_FIELDS },
+ { "polyline", SkType_Polyline INIT_BOOL_FIELDS },
+ { "post", SkType_Post INIT_BOOL_FIELDS },
+ { "quadTo", SkType_QuadTo INIT_BOOL_FIELDS },
+ { "rCubicTo", SkType_RCubicTo INIT_BOOL_FIELDS },
+ { "rLineTo", SkType_RLineTo INIT_BOOL_FIELDS },
+ { "rMoveTo", SkType_RMoveTo INIT_BOOL_FIELDS },
+ { "rQuadTo", SkType_RQuadTo INIT_BOOL_FIELDS },
+ { "radialGradient", SkType_DrawRadialGradient INIT_BOOL_FIELDS },
+ DISPLAY_NAME("random", SkType_Random),
+ { "rect", SkType_Rect INIT_BOOL_FIELDS },
+ { "rectToRect", SkType_RectToRect INIT_BOOL_FIELDS },
+ { "remove", SkType_Remove INIT_BOOL_FIELDS },
+ { "replace", SkType_Replace INIT_BOOL_FIELDS },
+ { "rotate", SkType_Rotate INIT_BOOL_FIELDS },
+ { "roundRect", SkType_RoundRect INIT_BOOL_FIELDS },
+ { "save", SkType_Save INIT_BOOL_FIELDS },
+ { "saveLayer", SkType_SaveLayer INIT_BOOL_FIELDS },
+ { "scale", SkType_Scale INIT_BOOL_FIELDS },
+ // screenplay
+ { "set", SkType_Set INIT_BOOL_FIELDS },
+ { "shader", SkType_Shader INIT_BOOL_FIELDS },
+ { "skew", SkType_Skew INIT_BOOL_FIELDS },
+ { "skia3d:camera", SkType_3D_Camera INIT_BOOL_FIELDS },
+ { "skia3d:patch", SkType_3D_Patch INIT_BOOL_FIELDS },
+ // point
+ { "snapshot", SkType_Snapshot INIT_BOOL_FIELDS },
+ { "string", SkType_String INIT_BOOL_FIELDS },
+ // style
+ { "text", SkType_Text INIT_BOOL_FIELDS },
+ { "textBox", SkType_TextBox INIT_BOOL_FIELDS },
+ // textboxalign
+ // textboxmode
+ { "textOnPath", SkType_TextOnPath INIT_BOOL_FIELDS },
+ { "textToPath", SkType_TextToPath INIT_BOOL_FIELDS },
+ // tilemode
+ { "translate", SkType_Translate INIT_BOOL_FIELDS },
+ DRAW_NAME("transparentShader", SkType_TransparentShader),
+ { "typeface", SkType_Typeface INIT_BOOL_FIELDS }
+ // xfermode
+ // knumberoftypes
+};
+
+const int kTypeNamesSize = SK_ARRAY_COUNT(gTypeNames);
+
+SkDisplayTypes SkDisplayType::Find(SkAnimateMaker* maker, const SkMemberInfo* match) {
+ for (int index = 0; index < kTypeNamesSize; index++) {
+ SkDisplayTypes type = gTypeNames[index].fType;
+ const SkMemberInfo* info = SkDisplayType::GetMembers(maker, type, NULL);
+ if (info == match)
+ return type;
+ }
+ return (SkDisplayTypes) -1;
+}
+
+// !!! optimize this by replacing function with a byte-sized lookup table
+SkDisplayTypes SkDisplayType::GetParent(SkAnimateMaker* maker, SkDisplayTypes base) {
+ if (base == SkType_Group || base == SkType_Save || base == SkType_SaveLayer) //!!! cheat a little until we have a lookup table
+ return SkType_Displayable;
+ if (base == SkType_Set)
+ return SkType_Animate; // another cheat until we have a lookup table
+ const SkMemberInfo* info = GetMembers(maker, base, NULL); // get info for this type
+ SkASSERT(info);
+ if (info->fType != SkType_BaseClassInfo)
+ return SkType_Unknown; // if no base, done
+ // !!! could change SK_MEMBER_INHERITED macro to take type, stuff in offset, so that
+ // this (and table builder) could know type without the following steps:
+ const SkMemberInfo* inherited = info->getInherited();
+ SkDisplayTypes result = (SkDisplayTypes) (SkType_Unknown + 1);
+ for (; result <= SkType_Xfermode; result = (SkDisplayTypes) (result + 1)) {
+ const SkMemberInfo* match = GetMembers(maker, result, NULL);
+ if (match == inherited)
+ break;
+ }
+ SkASSERT(result <= SkType_Xfermode);
+ return result;
+}
+
+SkDisplayTypes SkDisplayType::GetType(SkAnimateMaker* maker, const char match[], size_t len ) {
+ int index = SkStrSearch(&gTypeNames[0].fName, kTypeNamesSize, match,
+ len, sizeof(gTypeNames[0]));
+ if (index >= 0 && index < kTypeNamesSize)
+ return gTypeNames[index].fType;
+ SkExtras** end = maker->fExtras.end();
+ for (SkExtras** extraPtr = maker->fExtras.begin(); extraPtr < end; extraPtr++) {
+ SkDisplayTypes result = (*extraPtr)->getType(match, len);
+ if (result != SkType_Unknown)
+ return result;
+ }
+ return (SkDisplayTypes) -1;
+}
+
+bool SkDisplayType::IsEnum(SkAnimateMaker* , SkDisplayTypes type) {
+ switch (type) {
+ case SkType_AddMode:
+ case SkType_Align:
+ case SkType_ApplyMode:
+ case SkType_ApplyTransition:
+ case SkType_BitmapEncoding:
+ case SkType_BitmapFormat:
+ case SkType_Boolean:
+ case SkType_Cap:
+ case SkType_EventCode:
+ case SkType_EventKind:
+ case SkType_EventMode:
+ case SkType_FillType:
+ case SkType_FilterType:
+ case SkType_FontStyle:
+ case SkType_FromPathMode:
+ case SkType_Join:
+ case SkType_MaskFilterBlurStyle:
+ case SkType_PathDirection:
+ case SkType_Style:
+ case SkType_TextBoxAlign:
+ case SkType_TextBoxMode:
+ case SkType_TileMode:
+ case SkType_Xfermode:
+ return true;
+ default: // to avoid warnings
+ break;
+ }
+ return false;
+}
+
+bool SkDisplayType::IsDisplayable(SkAnimateMaker* , SkDisplayTypes type) {
+ switch (type) {
+ case SkType_Add:
+ case SkType_AddCircle:
+ case SkType_AddOval:
+ case SkType_AddPath:
+ case SkType_AddRect:
+ case SkType_AddRoundRect:
+ case SkType_Animate:
+ case SkType_AnimateBase:
+ case SkType_Apply:
+ case SkType_BaseBitmap:
+ case SkType_Bitmap:
+ case SkType_BitmapShader:
+ case SkType_Blur:
+ case SkType_Clear:
+ case SkType_Clip:
+ case SkType_Close:
+ case SkType_Color:
+ case SkType_CubicTo:
+ case SkType_Dash:
+ case SkType_DataInput:
+ case SkType_Discrete:
+ case SkType_Displayable:
+ case SkType_Drawable:
+ case SkType_DrawTo:
+ case SkType_Emboss:
+ case SkType_Event:
+ case SkType_FromPath:
+ case SkType_Full:
+ case SkType_Group:
+ case SkType_ImageBaseBitmap:
+ case SkType_Input:
+ case SkType_Line:
+ case SkType_LineTo:
+ case SkType_DrawLinearGradient:
+ case SkType_Matrix:
+ case SkType_Move:
+ case SkType_MoveTo:
+ case SkType_Movie:
+ case SkType_Oval:
+ case SkType_Paint:
+ case SkType_Path:
+ case SkType_PolyToPoly:
+ case SkType_Polygon:
+ case SkType_Polyline:
+ case SkType_Post:
+ case SkType_QuadTo:
+ case SkType_RCubicTo:
+ case SkType_RLineTo:
+ case SkType_RMoveTo:
+ case SkType_RQuadTo:
+ case SkType_DrawRadialGradient:
+ case SkType_Random:
+ case SkType_Rect:
+ case SkType_RectToRect:
+ case SkType_Remove:
+ case SkType_Replace:
+ case SkType_Rotate:
+ case SkType_RoundRect:
+ case SkType_Save:
+ case SkType_SaveLayer:
+ case SkType_Scale:
+ case SkType_Set:
+ case SkType_Shader:
+ case SkType_Skew:
+ case SkType_3D_Camera:
+ case SkType_3D_Patch:
+ case SkType_Snapshot:
+ case SkType_Text:
+ case SkType_TextBox:
+ case SkType_TextOnPath:
+ case SkType_TextToPath:
+ case SkType_Translate:
+ case SkType_TransparentShader:
+ return true;
+ default: // to avoid warnings
+ break;
+ }
+ return false;
+}
+
+bool SkDisplayType::IsStruct(SkAnimateMaker* , SkDisplayTypes type) {
+ switch (type) {
+ case SkType_Point:
+ case SkType_3D_Point:
+ return true;
+ default: // to avoid warnings
+ break;
+ }
+ return false;
+}
+
+
+SkDisplayTypes SkDisplayType::RegisterNewType() {
+ gNewTypes = (SkDisplayTypes) (gNewTypes + 1);
+ return gNewTypes;
+}
+
+
+
+#ifdef SK_DEBUG
+const char* SkDisplayType::GetName(SkAnimateMaker* maker, SkDisplayTypes type) {
+ for (int index = 0; index < kTypeNamesSize - 1; index++) {
+ if (gTypeNames[index].fType == type)
+ return gTypeNames[index].fName;
+ }
+ SkExtras** end = maker->fExtras.end();
+ for (SkExtras** extraPtr = maker->fExtras.begin(); extraPtr < end; extraPtr++) {
+ const char* result = (*extraPtr)->getName(type);
+ if (result != NULL)
+ return result;
+ }
+ return NULL;
+}
+#endif
+
+#ifdef SK_SUPPORT_UNITTEST
+void SkDisplayType::UnitTest() {
+ SkAnimator animator;
+ SkAnimateMaker* maker = animator.fMaker;
+ int index;
+ for (index = 0; index < kTypeNamesSize - 1; index++) {
+ SkASSERT(strcmp(gTypeNames[index].fName, gTypeNames[index + 1].fName) < 0);
+ SkASSERT(gTypeNames[index].fType < gTypeNames[index + 1].fType);
+ }
+ for (index = 0; index < kTypeNamesSize; index++) {
+ SkDisplayable* test = CreateInstance(maker, gTypeNames[index].fType);
+ if (test == NULL)
+ continue;
+#if defined _WIN32 && _MSC_VER >= 1300 && defined _INC_CRTDBG // only on windows, only if using "crtdbg.h"
+ // we know that crtdbg puts 0xfdfdfdfd at the end of the block
+ // look for unitialized memory, signature 0xcdcdcdcd prior to that
+ int* start = (int*) test;
+ while (*start != 0xfdfdfdfd) {
+ SkASSERT(*start != 0xcdcdcdcd);
+ start++;
+ }
+#endif
+ delete test;
+ }
+ for (index = 0; index < kTypeNamesSize; index++) {
+ int infoCount;
+ const SkMemberInfo* info = GetMembers(maker, gTypeNames[index].fType, &infoCount);
+ if (info == NULL)
+ continue;
+#if SK_USE_CONDENSED_INFO == 0
+ for (int inner = 0; inner < infoCount - 1; inner++) {
+ if (info[inner].fType == SkType_BaseClassInfo)
+ continue;
+ SkASSERT(strcmp(info[inner].fName, info[inner + 1].fName) < 0);
+ }
+#endif
+ }
+#if defined SK_DEBUG || defined SK_BUILD_CONDENSED
+ BuildCondensedInfo(maker);
+#endif
+}
+#endif
diff --git a/animator/SkDisplayType.h b/animator/SkDisplayType.h
new file mode 100644
index 00000000..474a65e8
--- /dev/null
+++ b/animator/SkDisplayType.h
@@ -0,0 +1,206 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayType_DEFINED
+#define SkDisplayType_DEFINED
+
+#include "SkMath.h"
+#include "SkScalar.h"
+
+#ifdef SK_DEBUG
+ #define SK_DUMP_ENABLED
+ #ifdef SK_BUILD_FOR_MAC
+ #define SK_FIND_LEAKS
+ #endif
+#endif
+
+#define SK_LITERAL_STR_EQUAL(str, token, len) (sizeof(str) - 1 == len \
+ && strncmp(str, token, sizeof(str) - 1) == 0)
+
+class SkAnimateMaker;
+class SkDisplayable;
+struct SkMemberInfo;
+
+enum SkDisplayTypes {
+ SkType_Unknown,
+ SkType_Math, // for ecmascript compatible Math functions and constants
+ SkType_Number, // for for ecmascript compatible Number functions and constants
+ SkType_Add,
+ SkType_AddCircle,
+ SkType_AddGeom,
+ SkType_AddMode,
+ SkType_AddOval,
+ SkType_AddPath,
+ SkType_AddRect, // path part
+ SkType_AddRoundRect,
+ SkType_Align,
+ SkType_Animate,
+ SkType_AnimateBase, // base type for animate, set
+ SkType_Apply,
+ SkType_ApplyMode,
+ SkType_ApplyTransition,
+ SkType_Array,
+ SkType_ARGB,
+ SkType_Base64,
+ SkType_BaseBitmap,
+ SkType_BaseClassInfo,
+ SkType_Bitmap,
+ SkType_BitmapEncoding,
+ SkType_BitmapFormat,
+ SkType_BitmapShader,
+ SkType_Blur,
+ SkType_Boolean, // can have values -1 (uninitialized), 0, 1
+ SkType_Boundable,
+ SkType_Bounds,
+ SkType_Cap,
+ SkType_Clear,
+ SkType_Clip,
+ SkType_Close,
+ SkType_Color,
+ SkType_CubicTo,
+ SkType_Dash,
+ SkType_DataInput,
+ SkType_Discrete,
+ SkType_Displayable,
+ SkType_Drawable,
+ SkType_DrawTo,
+ SkType_Dump,
+ SkType_DynamicString, // evaluate at draw time
+ SkType_Emboss,
+ SkType_Event,
+ SkType_EventCode,
+ SkType_EventKind,
+ SkType_EventMode,
+ SkType_FillType,
+ SkType_FilterType,
+ SkType_Float,
+ SkType_FontStyle,
+ SkType_FromPath,
+ SkType_FromPathMode,
+ SkType_Full,
+ SkType_DrawGradient,
+ SkType_Group,
+ SkType_HitClear,
+ SkType_HitTest,
+ SkType_ImageBaseBitmap,
+ SkType_Include,
+ SkType_Input,
+ SkType_Int,
+ SkType_Join,
+ SkType_Line, // simple line primitive
+ SkType_LineTo, // used as part of path construction
+ SkType_DrawLinearGradient,
+ SkType_MaskFilter,
+ SkType_MaskFilterBlurStyle,
+ SkType_MaskFilterLight,
+ SkType_Matrix,
+ SkType_MemberFunction,
+ SkType_MemberProperty,
+ SkType_Move,
+ SkType_MoveTo,
+ SkType_Movie,
+ SkType_MSec,
+ SkType_Oval,
+ SkType_Paint,
+ SkType_Path,
+ SkType_PathDirection,
+ SkType_PathEffect,
+ SkType_Point, // used inside other structures, no vtable
+ SkType_DrawPoint, // used to draw points, has a vtable
+ SkType_PolyToPoly,
+ SkType_Polygon,
+ SkType_Polyline,
+ SkType_Post,
+ SkType_QuadTo,
+ SkType_RCubicTo,
+ SkType_RLineTo,
+ SkType_RMoveTo,
+ SkType_RQuadTo,
+ SkType_DrawRadialGradient,
+ SkType_Random,
+ SkType_Rect,
+ SkType_RectToRect,
+ SkType_Remove,
+ SkType_Replace,
+ SkType_Rotate,
+ SkType_RoundRect,
+ SkType_Save,
+ SkType_SaveLayer,
+ SkType_Scale,
+ SkType_Screenplay,
+ SkType_Set,
+ SkType_Shader,
+ SkType_Skew,
+ SkType_3D_Camera,
+ SkType_3D_Patch,
+ SkType_3D_Point,
+ SkType_Snapshot,
+ SkType_String, // pointer to SkString
+ SkType_Style,
+ SkType_Text,
+ SkType_TextBox,
+ SkType_TextBoxAlign,
+ SkType_TextBoxMode,
+ SkType_TextOnPath,
+ SkType_TextToPath,
+ SkType_TileMode,
+ SkType_Translate,
+ SkType_TransparentShader,
+ SkType_Typeface,
+ SkType_Xfermode,
+ kNumberOfTypes
+};
+
+struct TypeNames {
+ const char* fName;
+ SkDisplayTypes fType;
+#if defined SK_DEBUG || defined SK_BUILD_CONDENSED
+ bool fDrawPrefix;
+ bool fDisplayPrefix;
+#endif
+};
+
+#ifdef SK_DEBUG
+typedef SkDisplayTypes SkFunctionParamType;
+#else
+typedef unsigned char SkFunctionParamType;
+#endif
+
+extern const TypeNames gTypeNames[];
+extern const int kTypeNamesSize;
+
+class SkDisplayType {
+public:
+ static SkDisplayTypes Find(SkAnimateMaker* , const SkMemberInfo* );
+ static const SkMemberInfo* GetMember(SkAnimateMaker* , SkDisplayTypes , const char** );
+ static const SkMemberInfo* GetMembers(SkAnimateMaker* , SkDisplayTypes , int* infoCountPtr);
+ static SkDisplayTypes GetParent(SkAnimateMaker* , SkDisplayTypes );
+ static bool IsDisplayable(SkAnimateMaker* , SkDisplayTypes );
+ static bool IsEnum(SkAnimateMaker* , SkDisplayTypes );
+ static bool IsStruct(SkAnimateMaker* , SkDisplayTypes );
+ static SkDisplayTypes RegisterNewType();
+ static SkDisplayTypes Resolve(const char[] , const SkMemberInfo** );
+#ifdef SK_DEBUG
+ static bool IsAnimate(SkDisplayTypes type ) { return type == SkType_Animate ||
+ type == SkType_Set; }
+ static const char* GetName(SkAnimateMaker* , SkDisplayTypes );
+#endif
+#ifdef SK_SUPPORT_UNITTEST
+ static void UnitTest();
+#endif
+#if defined SK_DEBUG || defined SK_BUILD_CONDENSED
+ static void BuildCondensedInfo(SkAnimateMaker* );
+#endif
+ static SkDisplayTypes GetType(SkAnimateMaker* , const char[] , size_t len);
+ static SkDisplayable* CreateInstance(SkAnimateMaker* , SkDisplayTypes );
+private:
+ static SkDisplayTypes gNewTypes;
+};
+
+#endif // SkDisplayType_DEFINED
diff --git a/animator/SkDisplayTypes.cpp b/animator/SkDisplayTypes.cpp
new file mode 100644
index 00000000..287ca6e4
--- /dev/null
+++ b/animator/SkDisplayTypes.cpp
@@ -0,0 +1,214 @@
+
+/*
+ * 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 "SkDisplayTypes.h"
+#include "SkAnimateBase.h"
+
+bool SkDisplayDepend::canContainDependents() const {
+ return true;
+}
+
+void SkDisplayDepend::dirty() {
+ SkDisplayable** last = fDependents.end();
+ for (SkDisplayable** depPtr = fDependents.begin(); depPtr < last; depPtr++) {
+ SkAnimateBase* animate = (SkAnimateBase* ) *depPtr;
+ animate->setChanged(true);
+ }
+}
+
+// Boolean
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayBoolean::fInfo[] = {
+ SK_MEMBER(value, Boolean)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayBoolean);
+
+SkDisplayBoolean::SkDisplayBoolean() : value(false) {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayBoolean::dump(SkAnimateMaker* maker){
+ dumpBase(maker);
+ SkDebugf("value=\"%s\" />\n", value ? "true" : "false");
+}
+#endif
+
+// int32_t
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayInt::fInfo[] = {
+ SK_MEMBER(value, Int)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayInt);
+
+SkDisplayInt::SkDisplayInt() : value(0) {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayInt::dump(SkAnimateMaker* maker){
+ dumpBase(maker);
+ SkDebugf("value=\"%d\" />\n", value);
+}
+#endif
+
+// SkScalar
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayFloat::fInfo[] = {
+ SK_MEMBER(value, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayFloat);
+
+SkDisplayFloat::SkDisplayFloat() : value(0) {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayFloat::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("value=\"%g\" />\n", SkScalarToFloat(value));
+}
+#endif
+
+// SkString
+enum SkDisplayString_Functions {
+ SK_FUNCTION(slice)
+};
+
+enum SkDisplayString_Properties {
+ SK_PROPERTY(length)
+};
+
+const SkFunctionParamType SkDisplayString::fFunctionParameters[] = {
+ (SkFunctionParamType) SkType_Int, // slice
+ (SkFunctionParamType) SkType_Int,
+ (SkFunctionParamType) 0
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayString::fInfo[] = {
+ SK_MEMBER_PROPERTY(length, Int),
+ SK_MEMBER_FUNCTION(slice, String),
+ SK_MEMBER(value, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayString);
+
+SkDisplayString::SkDisplayString() {
+}
+
+SkDisplayString::SkDisplayString(SkString& copyFrom) : value(copyFrom) {
+}
+
+void SkDisplayString::executeFunction(SkDisplayable* target, int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* scriptValue) {
+ if (scriptValue == NULL)
+ return;
+ SkASSERT(target == this);
+ switch (index) {
+ case SK_FUNCTION(slice):
+ scriptValue->fType = SkType_String;
+ SkASSERT(parameters[0].fType == SkType_Int);
+ int start = parameters[0].fOperand.fS32;
+ if (start < 0)
+ start = (int) (value.size() - start);
+ int end = (int) value.size();
+ if (parameters.count() > 1) {
+ SkASSERT(parameters[1].fType == SkType_Int);
+ end = parameters[1].fOperand.fS32;
+ }
+ //if (end >= 0 && end < (int) value.size())
+ if (end >= 0 && end <= (int) value.size())
+ scriptValue->fOperand.fString = new SkString(&value.c_str()[start], end - start);
+ else
+ scriptValue->fOperand.fString = new SkString(value);
+ break;
+ }
+}
+
+const SkFunctionParamType* SkDisplayString::getFunctionsParameters() {
+ return fFunctionParameters;
+}
+
+bool SkDisplayString::getProperty(int index, SkScriptValue* scriptValue) const {
+ switch (index) {
+ case SK_PROPERTY(length):
+ scriptValue->fType = SkType_Int;
+ scriptValue->fOperand.fS32 = (int32_t) value.size();
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+
+// SkArray
+#if 0 // !!! reason enough to qualify enum with class name or move typedArray into its own file
+enum SkDisplayArray_Properties {
+ SK_PROPERTY(length)
+};
+#endif
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDisplayArray::fInfo[] = {
+ SK_MEMBER_PROPERTY(length, Int),
+ SK_MEMBER_ARRAY(values, Unknown)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDisplayArray);
+
+SkDisplayArray::SkDisplayArray() {
+}
+
+SkDisplayArray::SkDisplayArray(SkTypedArray& copyFrom) : values(copyFrom) {
+
+}
+
+SkDisplayArray::~SkDisplayArray() {
+ if (values.getType() == SkType_String) {
+ for (int index = 0; index < values.count(); index++)
+ delete values[index].fString;
+ return;
+ }
+ if (values.getType() == SkType_Array) {
+ for (int index = 0; index < values.count(); index++)
+ delete values[index].fArray;
+ }
+}
+
+bool SkDisplayArray::getProperty(int index, SkScriptValue* value) const {
+ switch (index) {
+ case SK_PROPERTY(length):
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = values.count();
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
diff --git a/animator/SkDisplayTypes.h b/animator/SkDisplayTypes.h
new file mode 100644
index 00000000..1a3d0e5a
--- /dev/null
+++ b/animator/SkDisplayTypes.h
@@ -0,0 +1,106 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayTypes_DEFINED
+#define SkDisplayTypes_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkTypedArray.h"
+
+class SkOpArray; // compiled script experiment
+
+
+class SkDisplayDepend : public SkDisplayable {
+public:
+ virtual bool canContainDependents() const;
+ void addDependent(SkDisplayable* displayable) {
+ if (fDependents.find(displayable) < 0)
+ *fDependents.append() = displayable;
+ }
+ virtual void dirty();
+private:
+ SkTDDisplayableArray fDependents;
+ typedef SkDisplayable INHERITED;
+};
+
+class SkDisplayBoolean : public SkDisplayDepend {
+ DECLARE_DISPLAY_MEMBER_INFO(Boolean);
+ SkDisplayBoolean();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ SkBool value;
+ friend class SkAnimatorScript;
+ friend class SkAnimatorScript_Box;
+ friend class SkAnimatorScript_Unbox;
+ typedef SkDisplayDepend INHERITED;
+};
+
+class SkDisplayInt : public SkDisplayDepend {
+ DECLARE_DISPLAY_MEMBER_INFO(Int);
+ SkDisplayInt();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+private:
+ int32_t value;
+ friend class SkAnimatorScript;
+ friend class SkAnimatorScript_Box;
+ friend class SkAnimatorScript_Unbox;
+ typedef SkDisplayDepend INHERITED;
+};
+
+class SkDisplayFloat : public SkDisplayDepend {
+ DECLARE_DISPLAY_MEMBER_INFO(Float);
+ SkDisplayFloat();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+private:
+ SkScalar value;
+ friend class SkAnimatorScript;
+ friend class SkAnimatorScript_Box;
+ friend class SkAnimatorScript_Unbox;
+ typedef SkDisplayDepend INHERITED;
+};
+
+class SkDisplayString : public SkDisplayDepend {
+ DECLARE_DISPLAY_MEMBER_INFO(String);
+ SkDisplayString();
+ SkDisplayString(SkString& );
+ virtual void executeFunction(SkDisplayable* , int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* );
+ virtual const SkFunctionParamType* getFunctionsParameters();
+ virtual bool getProperty(int index, SkScriptValue* ) const;
+ SkString value;
+private:
+ static const SkFunctionParamType fFunctionParameters[];
+};
+
+class SkDisplayArray : public SkDisplayDepend {
+ DECLARE_DISPLAY_MEMBER_INFO(Array);
+ SkDisplayArray();
+ SkDisplayArray(SkTypedArray& );
+ SkDisplayArray(SkOpArray& ); // compiled script experiment
+ virtual ~SkDisplayArray();
+ virtual bool getProperty(int index, SkScriptValue* ) const;
+private:
+ SkTypedArray values;
+ friend class SkAnimator;
+ friend class SkAnimatorScript;
+ friend class SkAnimatorScript2;
+ friend class SkAnimatorScript_Unbox;
+ friend class SkDisplayable;
+ friend struct SkMemberInfo;
+ friend class SkScriptEngine;
+};
+
+#endif // SkDisplayTypes_DEFINED
diff --git a/animator/SkDisplayXMLParser.cpp b/animator/SkDisplayXMLParser.cpp
new file mode 100644
index 00000000..3ebf9dc4
--- /dev/null
+++ b/animator/SkDisplayXMLParser.cpp
@@ -0,0 +1,316 @@
+
+/*
+ * 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 "SkDisplayXMLParser.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayApply.h"
+#include "SkUtils.h"
+#ifdef SK_DEBUG
+#include "SkTime.h"
+#endif
+
+static char const* const gErrorStrings[] = {
+ "unknown error ",
+ "apply scopes itself",
+ "display tree too deep (circular reference?) ",
+ "element missing parent ",
+ "element type not allowed in parent ",
+ "error adding <data> to <post> ",
+ "error adding to <matrix> ",
+ "error adding to <paint> ",
+ "error adding to <path> ",
+ "error in attribute value ",
+ "error in script ",
+ "expected movie in sink attribute ",
+ "field not in target ",
+ "number of offsets in gradient must match number of colors",
+ "no offset in gradient may be greater than one",
+ "last offset in gradient must be one",
+ "offsets in gradient must be increasing",
+ "first offset in gradient must be zero",
+ "gradient attribute \"points\" must have length of four",
+ "in include ",
+ "in movie ",
+ "include name unknown or missing ",
+ "index out of range ",
+ "movie name unknown or missing ",
+ "no parent available to resolve sink attribute ",
+ "parent element can't contain ",
+ "saveLayer must specify a bounds",
+ "target id not found ",
+ "unexpected type "
+};
+
+SkDisplayXMLParserError::~SkDisplayXMLParserError() {
+}
+
+void SkDisplayXMLParserError::getErrorString(SkString* str) const {
+ if (fCode > kUnknownError)
+ str->set(gErrorStrings[fCode - kUnknownError]);
+ else
+ str->reset();
+ INHERITED::getErrorString(str);
+}
+
+void SkDisplayXMLParserError::setInnerError(SkAnimateMaker* parent, const SkString& src) {
+ SkString inner;
+ getErrorString(&inner);
+ inner.prepend(": ");
+ inner.prependS32(getLineNumber());
+ inner.prepend(", line ");
+ inner.prepend(src);
+ parent->setErrorNoun(inner);
+}
+
+
+SkDisplayXMLParser::SkDisplayXMLParser(SkAnimateMaker& maker)
+ : INHERITED(&maker.fError), fMaker(maker), fInInclude(maker.fInInclude),
+ fInSkia(maker.fInInclude), fCurrDisplayable(NULL)
+{
+}
+
+SkDisplayXMLParser::~SkDisplayXMLParser() {
+ if (fCurrDisplayable && fMaker.fChildren.find(fCurrDisplayable) < 0)
+ delete fCurrDisplayable;
+ for (Parent* parPtr = fParents.begin() + 1; parPtr < fParents.end(); parPtr++) {
+ SkDisplayable* displayable = parPtr->fDisplayable;
+ if (displayable == fCurrDisplayable)
+ continue;
+ SkASSERT(fMaker.fChildren.find(displayable) < 0);
+ if (fMaker.fHelpers.find(displayable) < 0)
+ delete displayable;
+ }
+}
+
+
+
+bool SkDisplayXMLParser::onAddAttribute(const char name[], const char value[]) {
+ return onAddAttributeLen(name, value, strlen(value));
+}
+
+bool SkDisplayXMLParser::onAddAttributeLen(const char attrName[], const char attrValue[],
+ size_t attrValueLen)
+{
+ if (fCurrDisplayable == NULL) // this signals we should ignore attributes for this element
+ return strncmp(attrName, "xmlns", sizeof("xmlns") - 1) != 0;
+ SkDisplayable* displayable = fCurrDisplayable;
+ SkDisplayTypes type = fCurrType;
+
+ if (strcmp(attrName, "id") == 0) {
+ if (fMaker.find(attrValue, attrValueLen, NULL)) {
+ fError->setNoun(attrValue, attrValueLen);
+ fError->setCode(SkXMLParserError::kDuplicateIDs);
+ return true;
+ }
+#ifdef SK_DEBUG
+ displayable->_id.set(attrValue, attrValueLen);
+ displayable->id = displayable->_id.c_str();
+#endif
+ fMaker.idsSet(attrValue, attrValueLen, displayable);
+ int parentIndex = fParents.count() - 1;
+ if (parentIndex > 0) {
+ SkDisplayable* parent = fParents[parentIndex - 1].fDisplayable;
+ parent->setChildHasID();
+ }
+ return false;
+ }
+ const char* name = attrName;
+ const SkMemberInfo* info = SkDisplayType::GetMember(&fMaker, type, &name);
+ if (info == NULL) {
+ fError->setNoun(name);
+ fError->setCode(SkXMLParserError::kUnknownAttributeName);
+ return true;
+ }
+ if (info->setValue(fMaker, NULL, 0, info->getCount(), displayable, info->getType(), attrValue,
+ attrValueLen))
+ return false;
+ if (fMaker.fError.hasError()) {
+ fError->setNoun(attrValue, attrValueLen);
+ return true;
+ }
+ SkDisplayable* ref = NULL;
+ if (fMaker.find(attrValue, attrValueLen, &ref) == false) {
+ ref = fMaker.createInstance(attrValue, attrValueLen);
+ if (ref == NULL) {
+ fError->setNoun(attrValue, attrValueLen);
+ fError->setCode(SkXMLParserError::kErrorInAttributeValue);
+ return true;
+ } else
+ fMaker.helperAdd(ref);
+ }
+ if (info->fType != SkType_MemberProperty) {
+ fError->setNoun(name);
+ fError->setCode(SkXMLParserError::kUnknownAttributeName);
+ return true;
+ }
+ SkScriptValue scriptValue;
+ scriptValue.fOperand.fDisplayable = ref;
+ scriptValue.fType = ref->getType();
+ displayable->setProperty(info->propertyIndex(), scriptValue);
+ return false;
+}
+
+#if defined(SK_BUILD_FOR_WIN32)
+ #define SK_strcasecmp _stricmp
+ #define SK_strncasecmp _strnicmp
+#else
+ #define SK_strcasecmp strcasecmp
+ #define SK_strncasecmp strncasecmp
+#endif
+
+bool SkDisplayXMLParser::onEndElement(const char elem[])
+{
+ int parentIndex = fParents.count() - 1;
+ if (parentIndex >= 0) {
+ Parent& container = fParents[parentIndex];
+ SkDisplayable* displayable = container.fDisplayable;
+ fMaker.fEndDepth = parentIndex;
+ displayable->onEndElement(fMaker);
+ if (fMaker.fError.hasError())
+ return true;
+ if (parentIndex > 0) {
+ SkDisplayable* parent = fParents[parentIndex - 1].fDisplayable;
+ bool result = parent->addChild(fMaker, displayable);
+ if (fMaker.hasError())
+ return true;
+ if (result == false) {
+ int infoCount;
+ const SkMemberInfo* info =
+ SkDisplayType::GetMembers(&fMaker, fParents[parentIndex - 1].fType, &infoCount);
+ const SkMemberInfo* foundInfo;
+ if ((foundInfo = searchContainer(info, infoCount)) != NULL) {
+ parent->setReference(foundInfo, displayable);
+ // if (displayable->isHelper() == false)
+ fMaker.helperAdd(displayable);
+ } else {
+ fMaker.setErrorCode(SkDisplayXMLParserError::kElementTypeNotAllowedInParent);
+ return true;
+ }
+ }
+ if (parent->childrenNeedDisposing())
+ delete displayable;
+ }
+ fParents.remove(parentIndex);
+ }
+ fCurrDisplayable = NULL;
+ if (fInInclude == false && SK_strcasecmp(elem, "screenplay") == 0) {
+ if (fMaker.fInMovie == false) {
+ fMaker.fEnableTime = fMaker.getAppTime();
+#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
+ if (fMaker.fDebugTimeBase == (SkMSec) -1)
+ fMaker.fDebugTimeBase = fMaker.fEnableTime;
+ SkString debugOut;
+ SkMSec time = fMaker.getAppTime();
+ debugOut.appendS32(time - fMaker.fDebugTimeBase);
+ debugOut.append(" onLoad enable=");
+ debugOut.appendS32(fMaker.fEnableTime - fMaker.fDebugTimeBase);
+ SkDebugf("%s\n", debugOut.c_str());
+#endif
+ fMaker.fEvents.doEvent(fMaker, SkDisplayEvent::kOnload, NULL);
+ if (fMaker.fError.hasError())
+ return true;
+ fMaker.fEvents.removeEvent(SkDisplayEvent::kOnload, NULL);
+
+ }
+ fInSkia = false;
+ }
+ return false;
+}
+
+bool SkDisplayXMLParser::onStartElement(const char name[])
+{
+ return onStartElementLen(name, strlen(name));
+}
+
+bool SkDisplayXMLParser::onStartElementLen(const char name[], size_t len) {
+ fCurrDisplayable = NULL; // init so we'll ignore attributes if we exit early
+
+ if (SK_strncasecmp(name, "screenplay", len) == 0) {
+ fInSkia = true;
+ if (fInInclude == false)
+ fMaker.idsSet(name, len, &fMaker.fScreenplay);
+ return false;
+ }
+ if (fInSkia == false)
+ return false;
+
+ SkDisplayable* displayable = fMaker.createInstance(name, len);
+ if (displayable == NULL) {
+ fError->setNoun(name, len);
+ fError->setCode(SkXMLParserError::kUnknownElement);
+ return true;
+ }
+ SkDisplayTypes type = displayable->getType();
+ Parent record = { displayable, type };
+ *fParents.append() = record;
+ if (fParents.count() == 1)
+ fMaker.childrenAdd(displayable);
+ else {
+ Parent* parent = fParents.end() - 2;
+ if (displayable->setParent(parent->fDisplayable)) {
+ fError->setNoun(name, len);
+ getError()->setCode(SkDisplayXMLParserError::kParentElementCantContain);
+ return true;
+ }
+ }
+
+ // set these for subsequent calls to addAttribute()
+ fCurrDisplayable = displayable;
+ fCurrType = type;
+ return false;
+}
+
+const SkMemberInfo* SkDisplayXMLParser::searchContainer(const SkMemberInfo* infoBase,
+ int infoCount) {
+ const SkMemberInfo* bestDisplayable = NULL;
+ const SkMemberInfo* lastResort = NULL;
+ for (int index = 0; index < infoCount; index++) {
+ const SkMemberInfo* info = &infoBase[index];
+ if (info->fType == SkType_BaseClassInfo) {
+ const SkMemberInfo* inherited = info->getInherited();
+ const SkMemberInfo* result = searchContainer(inherited, info->fCount);
+ if (result != NULL)
+ return result;
+ continue;
+ }
+ Parent* container = fParents.end() - 1;
+ SkDisplayTypes type = (SkDisplayTypes) info->fType;
+ if (type == SkType_MemberProperty)
+ type = info->propertyType();
+ SkDisplayTypes containerType = container->fType;
+ if (type == containerType && (type == SkType_Rect || type == SkType_Polygon ||
+ type == SkType_Array || type == SkType_Int || type == SkType_Bitmap))
+ goto rectNext;
+ while (type != containerType) {
+ if (containerType == SkType_Displayable)
+ goto next;
+ containerType = SkDisplayType::GetParent(&fMaker, containerType);
+ if (containerType == SkType_Unknown)
+ goto next;
+ }
+ return info;
+next:
+ if (type == SkType_Drawable || (type == SkType_Displayable &&
+ container->fDisplayable->isDrawable())) {
+rectNext:
+ if (fParents.count() > 1) {
+ Parent* parent = fParents.end() - 2;
+ if (info == parent->fDisplayable->preferredChild(type))
+ bestDisplayable = info;
+ else
+ lastResort = info;
+ }
+ }
+ }
+ if (bestDisplayable)
+ return bestDisplayable;
+ if (lastResort)
+ return lastResort;
+ return NULL;
+}
diff --git a/animator/SkDisplayXMLParser.h b/animator/SkDisplayXMLParser.h
new file mode 100644
index 00000000..9c561eda
--- /dev/null
+++ b/animator/SkDisplayXMLParser.h
@@ -0,0 +1,91 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayXMLParser_DEFINED
+#define SkDisplayXMLParser_DEFINED
+
+#include "SkIntArray.h"
+#include "SkTDict.h"
+#include "SkDisplayType.h"
+#include "SkXMLParser.h"
+
+class SkAnimateMaker;
+class SkDisplayable;
+
+class SkDisplayXMLParserError : public SkXMLParserError {
+public:
+ enum ErrorCode {
+ kApplyScopesItself = kUnknownError + 1,
+ kDisplayTreeTooDeep,
+ kElementMissingParent,
+ kElementTypeNotAllowedInParent,
+ kErrorAddingDataToPost,
+ kErrorAddingToMatrix,
+ kErrorAddingToPaint,
+ kErrorAddingToPath,
+ kErrorInAttributeValue,
+ kErrorInScript,
+ kExpectedMovie,
+ kFieldNotInTarget,
+ kGradientOffsetsDontMatchColors,
+ kGradientOffsetsMustBeNoMoreThanOne,
+ kGradientOffsetsMustEndWithOne,
+ kGradientOffsetsMustIncrease,
+ kGradientOffsetsMustStartWithZero,
+ kGradientPointsLengthMustBeFour,
+ kInInclude,
+ kInMovie,
+ kIncludeNameUnknownOrMissing,
+ kIndexOutOfRange,
+ kMovieNameUnknownOrMissing,
+ kNoParentAvailable,
+ kParentElementCantContain,
+ kSaveLayerNeedsBounds,
+ kTargetIDNotFound,
+ kUnexpectedType
+ };
+ virtual ~SkDisplayXMLParserError();
+ virtual void getErrorString(SkString* str) const;
+ void setCode(ErrorCode code) { INHERITED::setCode((INHERITED::ErrorCode) code); }
+ void setInnerError(SkAnimateMaker* maker, const SkString& str);
+ typedef SkXMLParserError INHERITED;
+ friend class SkDisplayXMLParser;
+};
+
+class SkDisplayXMLParser : public SkXMLParser {
+public:
+ SkDisplayXMLParser(SkAnimateMaker& maker);
+ virtual ~SkDisplayXMLParser();
+protected:
+ virtual bool onAddAttribute(const char name[], const char value[]);
+ bool onAddAttributeLen(const char name[], const char value[], size_t len);
+ virtual bool onEndElement(const char elem[]);
+ virtual bool onStartElement(const char elem[]);
+ bool onStartElementLen(const char elem[], size_t len);
+private:
+ struct Parent {
+ SkDisplayable* fDisplayable;
+ SkDisplayTypes fType;
+ };
+ SkTDArray<Parent> fParents;
+ SkDisplayXMLParser& operator= (const SkDisplayXMLParser& );
+ SkDisplayXMLParserError* getError() { return (SkDisplayXMLParserError*) fError; }
+ const SkMemberInfo* searchContainer(const SkMemberInfo* ,
+ int infoCount);
+ SkAnimateMaker& fMaker;
+ SkBool fInInclude;
+ SkBool fInSkia;
+ // local state between onStartElement and onAddAttribute
+ SkDisplayable* fCurrDisplayable;
+ SkDisplayTypes fCurrType;
+ friend class SkXMLAnimatorWriter;
+ typedef SkXMLParser INHERITED;
+};
+
+#endif // SkDisplayXMLParser_DEFINED
diff --git a/animator/SkDisplayable.cpp b/animator/SkDisplayable.cpp
new file mode 100644
index 00000000..aff3aaef
--- /dev/null
+++ b/animator/SkDisplayable.cpp
@@ -0,0 +1,540 @@
+
+/*
+ * 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 "SkDisplayable.h"
+#include "SkDisplayApply.h"
+#include "SkParse.h"
+#ifdef SK_DEBUG
+#include "SkDisplayList.h"
+#endif
+#include "SkDisplayTypes.h"
+
+#ifdef SK_FIND_LEAKS
+// int SkDisplayable::fAllocationCount;
+SkTDDisplayableArray SkDisplayable::fAllocations;
+#endif
+
+#ifdef SK_DEBUG
+SkDisplayable::SkDisplayable() {
+ id = _id.c_str();
+#ifdef SK_FIND_LEAKS
+ // fAllocationCount++;
+ *fAllocations.append() = this;
+#endif
+}
+#endif
+
+SkDisplayable::~SkDisplayable() {
+#ifdef SK_FIND_LEAKS
+ // fAllocationCount--;
+ int index = fAllocations.find(this);
+ SkASSERT(index >= 0);
+ fAllocations.remove(index);
+#endif
+}
+
+bool SkDisplayable::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ return false;
+}
+
+//void SkDisplayable::apply(SkAnimateMaker& , const SkMemberInfo* ,
+// SkDisplayable* , SkScalar [], int count) {
+// SkASSERT(0);
+//}
+
+bool SkDisplayable::canContainDependents() const {
+ return false;
+}
+
+bool SkDisplayable::childrenNeedDisposing() const {
+ return false;
+}
+
+void SkDisplayable::clearBounder() {
+}
+
+bool SkDisplayable::contains(SkDisplayable* ) {
+ return false;
+}
+
+SkDisplayable* SkDisplayable::contains(const SkString& ) {
+ return NULL;
+}
+
+SkDisplayable* SkDisplayable::deepCopy(SkAnimateMaker* maker) {
+ SkDisplayTypes type = getType();
+ if (type == SkType_Unknown) {
+ SkASSERT(0);
+ return NULL;
+ }
+ SkDisplayable* copy = SkDisplayType::CreateInstance(maker, type);
+ int index = -1;
+ int propIndex = 0;
+ const SkMemberInfo* info;
+ do {
+ info = copy->getMember(++index);
+ if (info == NULL)
+ break;
+ if (info->fType == SkType_MemberProperty) {
+ SkScriptValue value;
+ if (getProperty(propIndex, &value))
+ copy->setProperty(propIndex, value);
+ propIndex++;
+ continue;
+ }
+ if (info->fType == SkType_MemberFunction)
+ continue;
+ if (info->fType == SkType_Array) {
+ SkTDOperandArray* array = (SkTDOperandArray*) info->memberData(this);
+ int arrayCount;
+ if (array == NULL || (arrayCount = array->count()) == 0)
+ continue;
+ SkTDOperandArray* copyArray = (SkTDOperandArray*) info->memberData(copy);
+ copyArray->setCount(arrayCount);
+ SkDisplayTypes elementType;
+ if (type == SkType_Array) {
+ SkDisplayArray* dispArray = (SkDisplayArray*) this;
+ elementType = dispArray->values.getType();
+ } else
+ elementType = info->arrayType();
+ size_t elementSize = SkMemberInfo::GetSize(elementType);
+ size_t byteSize = elementSize * arrayCount;
+ memcpy(copyArray->begin(), array->begin(), byteSize);
+ continue;
+ }
+ if (SkDisplayType::IsDisplayable(maker, info->fType)) {
+ SkDisplayable** displayable = (SkDisplayable**) info->memberData(this);
+ if (*displayable == NULL || *displayable == (SkDisplayable*) -1)
+ continue;
+ SkDisplayable* deeper = (*displayable)->deepCopy(maker);
+ info->setMemberData(copy, deeper, sizeof(deeper));
+ continue;
+ }
+ if (info->fType == SkType_String || info->fType == SkType_DynamicString) {
+ SkString* string;
+ info->getString(this, &string);
+ info->setString(copy, string);
+ continue;
+ }
+ void* data = info->memberData(this);
+ size_t size = SkMemberInfo::GetSize(info->fType);
+ info->setMemberData(copy, data, size);
+ } while (true);
+ copy->dirty();
+ return copy;
+}
+
+void SkDisplayable::dirty() {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDisplayable::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+#if SK_USE_CONDENSED_INFO == 0
+ this->dumpAttrs(maker);
+ this->dumpChildren(maker);
+#endif
+}
+
+void SkDisplayable::dumpAttrs(SkAnimateMaker* maker) {
+ SkDisplayTypes type = getType();
+ if (type == SkType_Unknown) {
+ //SkDebugf("/>\n");
+ return;
+ }
+ SkDisplayable* blankCopy = SkDisplayType::CreateInstance(maker, type);
+
+ int index = -1;
+ int propIndex = 0;
+ const SkMemberInfo* info;
+ const SkMemberInfo* blankInfo;
+ SkScriptValue value;
+ SkScriptValue blankValue;
+ SkOperand values[2];
+ SkOperand blankValues[2];
+ do {
+ info = this->getMember(++index);
+ if (NULL == info) {
+ //SkDebugf("\n");
+ break;
+ }
+ if (SkType_MemberProperty == info->fType) {
+ if (getProperty(propIndex, &value)) {
+ blankCopy->getProperty(propIndex, &blankValue);
+ //last two are dummies
+ dumpValues(info, value.fType, value.fOperand, blankValue.fOperand, value.fOperand, blankValue.fOperand);
+ }
+
+ propIndex++;
+ continue;
+ }
+ if (SkDisplayType::IsDisplayable(maker, info->fType)) {
+ continue;
+ }
+
+ if (info->fType == SkType_MemberFunction)
+ continue;
+
+
+ if (info->fType == SkType_Array) {
+ SkTDOperandArray* array = (SkTDOperandArray*) info->memberData(this);
+ int arrayCount;
+ if (array == NULL || (arrayCount = array->count()) == 0)
+ continue;
+ SkDisplayTypes elementType;
+ if (type == SkType_Array) {
+ SkDisplayArray* dispArray = (SkDisplayArray*) this;
+ elementType = dispArray->values.getType();
+ } else
+ elementType = info->arrayType();
+ bool firstElem = true;
+ SkDebugf("%s=\"[", info->fName);
+ for (SkOperand* op = array->begin(); op < array->end(); op++) {
+ if (!firstElem) SkDebugf(",");
+ switch (elementType) {
+ case SkType_Displayable:
+ SkDebugf("%s", op->fDisplayable->id);
+ break;
+ case SkType_Int:
+ SkDebugf("%d", op->fS32);
+ break;
+ case SkType_Float:
+ SkDebugf("%g", SkScalarToFloat(op->fScalar));
+ break;
+ case SkType_String:
+ case SkType_DynamicString:
+ SkDebugf("%s", op->fString->c_str());
+ break;
+ default:
+ break;
+ }
+ firstElem = false;
+ }
+ SkDebugf("]\" ");
+ continue;
+ }
+
+ if (info->fType == SkType_String || info->fType == SkType_DynamicString) {
+ SkString* string;
+ info->getString(this, &string);
+ if (string->isEmpty() == false)
+ SkDebugf("%s=\"%s\"\t", info->fName, string->c_str());
+ continue;
+ }
+
+
+ blankInfo = blankCopy->getMember(index);
+ int i = info->fCount;
+ info->getValue(this, values, i);
+ blankInfo->getValue(blankCopy, blankValues, i);
+ dumpValues(info, info->fType, values[0], blankValues[0], values[1], blankValues[1]);
+ } while (true);
+ delete blankCopy;
+}
+
+void SkDisplayable::dumpBase(SkAnimateMaker* maker) {
+ SkDisplayTypes type = getType();
+ const char* elementName = "(unknown)";
+ if (type != SkType_Unknown && type != SkType_Screenplay)
+ elementName = SkDisplayType::GetName(maker, type);
+ SkDebugf("%*s", SkDisplayList::fIndent, "");
+ if (SkDisplayList::fDumpIndex != 0 && SkDisplayList::fIndent == 0)
+ SkDebugf("%d: ", SkDisplayList::fDumpIndex);
+ SkDebugf("<%s ", elementName);
+ if (strcmp(id,"") != 0)
+ SkDebugf("id=\"%s\" ", id);
+}
+
+void SkDisplayable::dumpChildren(SkAnimateMaker* maker, bool closedAngle) {
+
+ int index = -1;
+ const SkMemberInfo* info;
+ index = -1;
+ SkDisplayList::fIndent += 4;
+ do {
+ info = this->getMember(++index);
+ if (NULL == info) {
+ break;
+ }
+ if (SkDisplayType::IsDisplayable(maker, info->fType)) {
+ SkDisplayable** displayable = (SkDisplayable**) info->memberData(this);
+ if (*displayable == NULL || *displayable == (SkDisplayable*) -1)
+ continue;
+ if (closedAngle == false) {
+ SkDebugf(">\n");
+ closedAngle = true;
+ }
+ (*displayable)->dump(maker);
+ }
+ } while (true);
+ SkDisplayList::fIndent -= 4;
+ if (closedAngle)
+ dumpEnd(maker);
+ else
+ SkDebugf("/>\n");
+}
+
+void SkDisplayable::dumpEnd(SkAnimateMaker* maker) {
+ SkDisplayTypes type = getType();
+ const char* elementName = "(unknown)";
+ if (type != SkType_Unknown && type != SkType_Screenplay)
+ elementName = SkDisplayType::GetName(maker, type);
+ SkDebugf("%*s", SkDisplayList::fIndent, "");
+ SkDebugf("</%s>\n", elementName);
+}
+
+void SkDisplayable::dumpEvents() {
+}
+
+void SkDisplayable::dumpValues(const SkMemberInfo* info, SkDisplayTypes type, SkOperand op, SkOperand blankOp,
+ SkOperand op2, SkOperand blankOp2) {
+ switch (type) {
+ case SkType_BitmapEncoding:
+ switch (op.fS32) {
+ case 0 : SkDebugf("type=\"jpeg\" ");
+ break;
+ case 1 : SkDebugf("type=\"png\" ");
+ break;
+ default: SkDebugf("type=\"UNDEFINED\" ");
+ }
+ break;
+ //should make this a separate case in dump attrs, rather than make dump values have a larger signature
+ case SkType_Point:
+ if (op.fScalar != blankOp.fScalar || op2.fScalar != blankOp.fScalar) {
+ SkDebugf("%s=\"[%g,%g]\" ", info->fName, SkScalarToFloat(op.fScalar), SkScalarToFloat(op2.fScalar));
+ }
+ break;
+ case SkType_FromPathMode:
+ switch (op.fS32) {
+ case 0:
+ //don't want to print anything for 0, just adding it to remove it from default:
+ break;
+ case 1:
+ SkDebugf("%s=\"%s\" ", info->fName, "angle");
+ break;
+ case 2:
+ SkDebugf("%s=\"%s\" ", info->fName, "position");
+ break;
+ default:
+ SkDebugf("%s=\"INVALID\" ", info->fName);
+ }
+ break;
+ case SkType_MaskFilterBlurStyle:
+ switch (op.fS32) {
+ case 0:
+ break;
+ case 1:
+ SkDebugf("%s=\"%s\" ", info->fName, "solid");
+ break;
+ case 2:
+ SkDebugf("%s=\"%s\" ", info->fName, "outer");
+ break;
+ case 3:
+ SkDebugf("%s=\"%s\" ", info->fName, "inner");
+ break;
+ default:
+ SkDebugf("%s=\"INVALID\" ", info->fName);
+ }
+ break;
+ case SkType_FilterType:
+ if (op.fS32 == 1)
+ SkDebugf("%s=\"%s\" ", info->fName, "bilinear");
+ break;
+ case SkType_PathDirection:
+ SkDebugf("%s=\"%s\" ", info->fName, op.fS32 == 0 ? "cw" : "ccw");
+ break;
+ case SkType_FillType:
+ SkDebugf("%s=\"%s\" ", info->fName, op.fS32 == 0 ? "winding" : "evenOdd");
+ break;
+ case SkType_TileMode:
+ //correct to look at the S32?
+ if (op.fS32 != blankOp.fS32)
+ SkDebugf("%s=\"%s\" ", info->fName, op.fS32 == 0 ? "clamp" : op.fS32 == 1 ? "repeat" : "mirror");
+ break;
+ case SkType_Boolean:
+ if (op.fS32 != blankOp.fS32)
+ SkDebugf("%s=\"%s\" ", info->fName, op.fS32 == 0 ? "false" : "true");
+ break;
+ case SkType_Int:
+ if (op.fS32 != blankOp.fS32)
+ SkDebugf(" %s=\"%d\" ", info->fName, op.fS32);
+ break;
+ case SkType_Float:
+ if (op.fScalar != blankOp.fScalar) { //or /65536?
+ SkDebugf("%s=\"%g\" ", info->fName, SkScalarToFloat(op.fScalar));
+ }
+ break;
+ case SkType_String:
+ case SkType_DynamicString:
+ if (op.fString->size() > 0)
+ SkDebugf("%s=\"%s\" ", info->fName, op.fString->c_str());
+ break;
+ case SkType_MSec:
+ if (op.fS32 != blankOp.fS32) {
+ SkDebugf(" %s=\"%g\" ", info->fName, SkScalarToFloat(SkScalarDiv(op.fS32, 1000)));
+ }
+ default:
+ SkDebugf("");
+ }
+}
+
+#endif
+
+bool SkDisplayable::enable( SkAnimateMaker& ) {
+ return false;
+}
+
+void SkDisplayable::enableBounder() {
+}
+
+void SkDisplayable::executeFunction(SkDisplayable* , int index,
+ SkTDArray<SkScriptValue>& , SkDisplayTypes, SkScriptValue* ) {
+ SkASSERT(0);
+}
+
+void SkDisplayable::executeFunction(SkDisplayable* target,
+ const SkMemberInfo* info, SkTypedArray* values, SkScriptValue* value) {
+ SkTDArray<SkScriptValue> typedValues;
+ for (SkOperand* op = values->begin(); op < values->end(); op++) {
+ SkScriptValue temp;
+ temp.fType = values->getType();
+ temp.fOperand = *op;
+ *typedValues.append() = temp;
+ }
+ executeFunction(target, info->functionIndex(), typedValues, info->getType(), value);
+}
+
+void SkDisplayable::executeFunction2(SkDisplayable* , int index,
+ SkOpArray* params, SkDisplayTypes, SkOperand2* ) {
+ SkASSERT(0);
+}
+
+void SkDisplayable::getBounds(SkRect* rect) {
+ SkASSERT(rect);
+ rect->fLeft = rect->fTop = SK_ScalarMax;
+ rect->fRight= rect->fBottom = -SK_ScalarMax;
+}
+
+const SkFunctionParamType* SkDisplayable::getFunctionsParameters() {
+ return NULL;
+}
+
+const SkMemberInfo* SkDisplayable::getMember(int index) {
+ return NULL;
+}
+
+const SkMemberInfo* SkDisplayable::getMember(const char name[]) {
+ return NULL;
+}
+
+const SkFunctionParamType* SkDisplayable::getParameters(const SkMemberInfo* info,
+ int* paramCount) {
+ const SkFunctionParamType* params = getFunctionsParameters();
+ SkASSERT(params != NULL);
+ int funcIndex = info->functionIndex();
+ // !!! eventually break traversing params into an external function (maybe this whole function)
+ int index = funcIndex;
+ int offset = 0;
+ while (--index >= 0) {
+ while (params[offset] != 0)
+ offset++;
+ offset++;
+ }
+ int count = 0;
+ while (params[offset] != 0) {
+ count++;
+ offset++;
+ }
+ *paramCount = count;
+ return &params[offset - count];
+}
+
+SkDisplayable* SkDisplayable::getParent() const {
+ return NULL;
+}
+
+bool SkDisplayable::getProperty(int index, SkScriptValue* ) const {
+// SkASSERT(0);
+ return false;
+}
+
+bool SkDisplayable::getProperty2(int index, SkOperand2* value) const {
+ SkASSERT(0);
+ return false;
+}
+
+SkDisplayTypes SkDisplayable::getType() const {
+ return SkType_Unknown;
+}
+
+bool SkDisplayable::hasEnable() const {
+ return false;
+}
+
+bool SkDisplayable::isDrawable() const {
+ return false;
+}
+
+void SkDisplayable::onEndElement(SkAnimateMaker& ) {}
+
+const SkMemberInfo* SkDisplayable::preferredChild(SkDisplayTypes type) {
+ return NULL;
+}
+
+bool SkDisplayable::resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* apply) {
+ return false;
+}
+
+//SkDisplayable* SkDisplayable::resolveTarget(SkAnimateMaker& ) {
+// return this;
+//}
+
+void SkDisplayable::setChildHasID() {
+}
+
+bool SkDisplayable::setParent(SkDisplayable* ) {
+ return false;
+}
+
+bool SkDisplayable::setProperty(int index, SkScriptValue& ) {
+ //SkASSERT(0);
+ return false;
+}
+
+void SkDisplayable::setReference(const SkMemberInfo* info, SkDisplayable* displayable) {
+ if (info->fType == SkType_MemberProperty) {
+ SkScriptValue scriptValue;
+ scriptValue.fOperand.fDisplayable = displayable;
+ scriptValue.fType = displayable->getType();
+ setProperty(info->propertyIndex(), scriptValue);
+ } else if (info->fType == SkType_Array) {
+ SkASSERT(displayable->getType() == SkType_Array);
+ SkDisplayArray* dispArray = (SkDisplayArray*) displayable;
+ SkTDScalarArray* array = (SkTDScalarArray* ) info->memberData(this);
+ array->setCount(dispArray->values.count());
+ memcpy(array->begin(), dispArray->values.begin(), dispArray->values.count() * sizeof(int));
+ //
+
+ // !!! need a way for interpreter engine to own array
+ // !!! probably need to replace all scriptable arrays with single bigger array
+ // that has operand and type on every element -- or
+ // when array is dirtied, need to get parent to reparse to local array
+ } else {
+ void* storage = info->memberData(this);
+ memcpy(storage, &displayable, sizeof(SkDisplayable*));
+ }
+// !!! unclear why displayable is dirtied here
+// if this is called, this breaks fromPath.xml
+// displayable->dirty();
+}
+
+#ifdef SK_DEBUG
+void SkDisplayable::validate() {
+}
+#endif
diff --git a/animator/SkDisplayable.h b/animator/SkDisplayable.h
new file mode 100644
index 00000000..4fd47abc
--- /dev/null
+++ b/animator/SkDisplayable.h
@@ -0,0 +1,112 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDisplayable_DEFINED
+#define SkDisplayable_DEFINED
+
+#include "SkOperand.h"
+#ifdef SK_DEBUG
+#include "SkString.h"
+#endif
+#include "SkIntArray.h"
+#include "SkRect.h"
+#include "SkTDArray.h"
+
+class SkAnimateMaker;
+class SkApply;
+class SkEvents;
+struct SkMemberInfo;
+struct SkScriptValue;
+class SkOpArray; // compiled scripting experiment
+union SkOperand2; // compiled scripting experiment
+
+class SkDisplayable {
+public:
+#ifdef SK_DEBUG
+ SkDisplayable();
+#endif
+ virtual ~SkDisplayable();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child);
+ virtual bool canContainDependents() const;
+ virtual bool childrenNeedDisposing() const;
+ virtual void clearBounder();
+ virtual bool contains(SkDisplayable* );
+ virtual SkDisplayable* contains(const SkString& );
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual void dirty();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+ void dumpAttrs(SkAnimateMaker* );
+ void dumpBase(SkAnimateMaker* );
+ void dumpChildren(SkAnimateMaker* maker, bool closedAngle = false );
+ void dumpEnd(SkAnimateMaker* );
+ virtual void dumpEvents();
+#endif
+ virtual bool enable( SkAnimateMaker& );
+ virtual void enableBounder();
+ virtual void executeFunction(SkDisplayable* , int functionIndex,
+ SkTDArray<SkScriptValue>& , SkDisplayTypes , SkScriptValue* );
+ void executeFunction(SkDisplayable* , const SkMemberInfo* ,
+ SkTypedArray* , SkScriptValue* );
+ virtual void executeFunction2(SkDisplayable* , int functionIndex,
+ SkOpArray* params , SkDisplayTypes , SkOperand2* ); // compiled scripting experiment
+ virtual void getBounds(SkRect* );
+ virtual const SkFunctionParamType* getFunctionsParameters();
+ virtual const SkMemberInfo* getMember(int index);
+ virtual const SkMemberInfo* getMember(const char name[]);
+ const SkFunctionParamType* getParameters(const SkMemberInfo* info,
+ int* paramCount);
+ virtual SkDisplayable* getParent() const;
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool getProperty2(int index, SkOperand2* value) const; // compiled scripting experiment
+ virtual SkDisplayTypes getType() const;
+ virtual bool hasEnable() const;
+ bool isAnimate() const {
+ SkDisplayTypes type = getType();
+ return type == SkType_Animate || type == SkType_Set; }
+ bool isApply() const { return getType() == SkType_Apply; }
+ bool isColor() const { return getType() == SkType_Color; }
+ virtual bool isDrawable() const;
+ bool isGroup() const { return getType() == SkType_Group ||
+ getType() == SkType_Save || getType() == SkType_DrawTo ||
+ getType() == SkType_SaveLayer; }
+ bool isMatrix() const { return getType() == SkType_Matrix; }
+ virtual bool isPaint() const { return getType() == SkType_Paint; }
+ virtual bool isPath() const { return false; }
+ bool isPost() const { return getType() == SkType_Post; }
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual const SkMemberInfo* preferredChild(SkDisplayTypes type);
+ virtual bool resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* );
+ virtual void setChildHasID();
+ virtual bool setParent(SkDisplayable* );
+ virtual bool setProperty(int index, SkScriptValue& );
+ void setReference(const SkMemberInfo* info, SkDisplayable* ref);
+#ifdef SK_DEBUG
+ bool isDataInput() const { return getType() == SkType_DataInput; };
+ bool isEvent() const { return getType() == SkType_Event; }
+ virtual bool isMatrixPart() const { return false; }
+ bool isPatch() const { return getType() == SkType_3D_Patch; }
+ virtual bool isPaintPart() const { return false; }
+ virtual bool isPathPart() const { return false; }
+ virtual void validate();
+ SkString _id;
+ const char* id;
+// static int fAllocationCount;
+ static SkTDDisplayableArray fAllocations;
+#else
+ void validate() {}
+#endif
+#ifdef SK_DUMP_ENABLED
+private:
+ void dumpValues(const SkMemberInfo* info, SkDisplayTypes type, SkOperand op, SkOperand blankOp,
+ SkOperand op2, SkOperand blankOp2);
+#endif
+};
+
+#endif // SkDisplayable_DEFINED
diff --git a/animator/SkDraw3D.cpp b/animator/SkDraw3D.cpp
new file mode 100644
index 00000000..9e1c2cc0
--- /dev/null
+++ b/animator/SkDraw3D.cpp
@@ -0,0 +1,106 @@
+
+/*
+ * 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 "SkDraw3D.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkTypedArray.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo Sk3D_Point::fInfo[] = {
+ SK_MEMBER_ALIAS(x, fPoint.fX, Float),
+ SK_MEMBER_ALIAS(y, fPoint.fY, Float),
+ SK_MEMBER_ALIAS(z, fPoint.fZ, Float)
+};
+
+#endif
+
+DEFINE_NO_VIRTUALS_GET_MEMBER(Sk3D_Point);
+
+Sk3D_Point::Sk3D_Point() {
+ fPoint.set(0, 0, 0);
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo Sk3D_Camera::fInfo[] = {
+ SK_MEMBER_ALIAS(axis, fCamera.fAxis, 3D_Point),
+ SK_MEMBER(hackHeight, Float),
+ SK_MEMBER(hackWidth, Float),
+ SK_MEMBER_ALIAS(location, fCamera.fLocation, 3D_Point),
+ SK_MEMBER_ALIAS(observer, fCamera.fObserver, 3D_Point),
+ SK_MEMBER(patch, 3D_Patch),
+ SK_MEMBER_ALIAS(zenith, fCamera.fZenith, 3D_Point),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(Sk3D_Camera);
+
+Sk3D_Camera::Sk3D_Camera() : hackWidth(0), hackHeight(0), patch(NULL) {
+}
+
+Sk3D_Camera::~Sk3D_Camera() {
+}
+
+bool Sk3D_Camera::draw(SkAnimateMaker& maker) {
+ fCamera.update();
+ SkMatrix matrix;
+ fCamera.patchToMatrix(patch->fPatch, &matrix);
+ matrix.preTranslate(hackWidth / 2, -hackHeight / 2);
+ matrix.postTranslate(hackWidth / 2, hackHeight / 2);
+ maker.fCanvas->concat(matrix);
+ return false;
+}
+
+
+enum Sk3D_Patch_Functions {
+ SK_FUNCTION(rotateDegrees)
+};
+
+const SkFunctionParamType Sk3D_Patch::fFunctionParameters[] = {
+ (SkFunctionParamType) SkType_Float,
+ (SkFunctionParamType) SkType_Float,
+ (SkFunctionParamType) SkType_Float,
+ (SkFunctionParamType) 0 // terminator for parameter list (there may be multiple parameter lists)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo Sk3D_Patch::fInfo[] = {
+ SK_MEMBER_ALIAS(origin, fPatch.fOrigin, 3D_Point),
+ SK_MEMBER_FUNCTION(rotateDegrees, Float),
+ SK_MEMBER_ALIAS(u, fPatch.fU, 3D_Point),
+ SK_MEMBER_ALIAS(v, fPatch.fV, 3D_Point)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(Sk3D_Patch);
+
+void Sk3D_Patch::executeFunction(SkDisplayable* target, int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* ) {
+ SkASSERT(target == this);
+ switch (index) {
+ case SK_FUNCTION(rotateDegrees):
+ SkASSERT(parameters.count() == 3);
+ SkASSERT(type == SkType_Float);
+ fPatch.rotateDegrees(parameters[0].fOperand.fScalar,
+ parameters[1].fOperand.fScalar, parameters[2].fOperand.fScalar);
+ break;
+ default:
+ SkASSERT(0);
+ }
+}
+
+const SkFunctionParamType* Sk3D_Patch::getFunctionsParameters() {
+ return fFunctionParameters;
+}
diff --git a/animator/SkDraw3D.h b/animator/SkDraw3D.h
new file mode 100644
index 00000000..a204044b
--- /dev/null
+++ b/animator/SkDraw3D.h
@@ -0,0 +1,50 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDraw3D_DEFINED
+#define SkDraw3D_DEFINED
+
+#include "SkCamera.h"
+#include "SkDrawable.h"
+#include "SkMemberInfo.h"
+
+class Sk3D_Patch;
+
+struct Sk3D_Point {
+ DECLARE_NO_VIRTUALS_MEMBER_INFO(3D_Point);
+ Sk3D_Point();
+private:
+ SkPoint3D fPoint;
+};
+
+class Sk3D_Camera : public SkDrawable {
+ DECLARE_MEMBER_INFO(3D_Camera);
+ Sk3D_Camera();
+ virtual ~Sk3D_Camera();
+ virtual bool draw(SkAnimateMaker& );
+private:
+ SkScalar hackWidth;
+ SkScalar hackHeight;
+ SkCamera3D fCamera;
+ Sk3D_Patch* patch;
+};
+
+class Sk3D_Patch : public SkDisplayable {
+ DECLARE_MEMBER_INFO(3D_Patch);
+private:
+ virtual void executeFunction(SkDisplayable* , int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* );
+ virtual const SkFunctionParamType* getFunctionsParameters();
+ SkPatch3D fPatch;
+ static const SkFunctionParamType fFunctionParameters[];
+ friend class Sk3D_Camera;
+};
+
+#endif // SkDraw3D_DEFINED
diff --git a/animator/SkDrawBitmap.cpp b/animator/SkDrawBitmap.cpp
new file mode 100644
index 00000000..9f657ab2
--- /dev/null
+++ b/animator/SkDrawBitmap.cpp
@@ -0,0 +1,197 @@
+
+/*
+ * 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 "SkDrawBitmap.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkImageDecoder.h"
+#include "SkPaint.h"
+#include "SkStream.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkBaseBitmap::fInfo[] = {
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkBaseBitmap);
+
+SkBaseBitmap::SkBaseBitmap() : x(0), y(0) {
+}
+
+SkBaseBitmap::~SkBaseBitmap() {
+}
+
+bool SkBaseBitmap::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawBitmap(fBitmap, x, y, maker.fPaint);
+ return false;
+}
+
+enum SkDrawBitmap_Properties {
+ SK_PROPERTY(erase)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawBitmap::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER_PROPERTY(erase, ARGB),
+ SK_MEMBER(format, BitmapFormat),
+ SK_MEMBER(height, Int),
+ SK_MEMBER(rowBytes, Int),
+ SK_MEMBER(width, Int),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawBitmap);
+
+SkDrawBitmap::SkDrawBitmap() : format((SkBitmap::Config) -1), height(-1),
+ rowBytes(0), width(-1), fColor(0), fColorSet(false) {
+}
+
+SkDrawBitmap::~SkDrawBitmap() {
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawBitmap::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpAttrs(maker);
+ if (fColorSet)
+ SkDebugf("erase=\"argb(%d,%d,%d,%d)\" ", SkColorGetA(fColor)/255, SkColorGetR(fColor),
+ SkColorGetG(fColor), SkColorGetB(fColor));
+ if (rowBytes > 0)
+ SkDebugf("rowBytes=\"%d\" ", rowBytes);
+ const char* formatName;
+ switch (format) {
+ case 0: formatName = "none"; break;
+ case 1: formatName = "A1"; break;
+ case 2: formatName = "A8"; break;
+ case 3: formatName = "Index8"; break;
+ case 4: formatName = "RGB16"; break;
+ case 5: formatName = "RGB32"; break;
+ }
+ SkDebugf("format=\"%s\" />\n", formatName);
+}
+#endif
+
+void SkDrawBitmap::onEndElement(SkAnimateMaker&) {
+ SkASSERT(width != -1);
+ SkASSERT(height != -1);
+ SkASSERT(rowBytes >= 0);
+ fBitmap.setConfig((SkBitmap::Config) format, width, height, rowBytes);
+ fBitmap.allocPixels();
+ if (fColorSet)
+ fBitmap.eraseColor(fColor);
+}
+
+bool SkDrawBitmap::setProperty(int index, SkScriptValue& value)
+{
+ switch (index) {
+ case SK_PROPERTY(erase):
+ SkASSERT(value.fType == SkType_ARGB);
+ fColor = value.fOperand.fS32;
+ fColorSet = true;
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+
+enum SkImageBaseBitmap_Properties {
+ SK_PROPERTY(height),
+ SK_PROPERTY(width)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkImageBaseBitmap::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(base64, Base64),
+ SK_MEMBER_PROPERTY(height, Int),
+ SK_MEMBER(src, String),
+ SK_MEMBER_PROPERTY(width, Int)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkImageBaseBitmap);
+
+SkImageBaseBitmap::SkImageBaseBitmap() : fDirty(true), fUriBase(NULL) {
+ base64.fData = NULL;
+ base64.fLength = 0;
+}
+
+SkImageBaseBitmap::~SkImageBaseBitmap() {
+ delete[] base64.fData;
+}
+
+SkDisplayable* SkImageBaseBitmap::deepCopy(SkAnimateMaker* maker) {
+ SkDisplayable* copy = INHERITED::deepCopy(maker);
+ ((SkImageBaseBitmap*) copy)->fUriBase = ((SkImageBaseBitmap*) this)->fUriBase;
+ return copy;
+}
+
+void SkImageBaseBitmap::dirty() {
+ fDirty = true;
+}
+
+bool SkImageBaseBitmap::draw(SkAnimateMaker& maker) {
+ if (fDirty)
+ resolve();
+ return INHERITED::draw(maker);
+}
+
+bool SkImageBaseBitmap::getProperty(int index, SkScriptValue* value) const {
+ if (fDirty)
+ resolve();
+ switch (index) {
+ case SK_PROPERTY(height):
+ value->fOperand.fS32 = fBitmap.height();
+ break;
+ case SK_PROPERTY(width):
+ value->fOperand.fS32 = fBitmap.width();
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ value->fType = SkType_Int;
+ return true;
+}
+
+void SkImageBaseBitmap::onEndElement(SkAnimateMaker& maker) {
+ fUriBase = maker.fPrefix.c_str();
+}
+
+void SkImageBaseBitmap::resolve() {
+ fDirty = false;
+ if (base64.fData) {
+ fBitmap.reset();
+ SkImageDecoder::DecodeMemory(base64.fData, base64.fLength, &fBitmap);
+ } else if (src.size()) {
+ if (fLast.equals(src))
+ return;
+ fLast.set(src);
+ fBitmap.reset();
+
+ //SkStream* stream = SkStream::GetURIStream(fUriBase, src.c_str());
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(src.c_str()));
+ if (stream.get()) {
+ SkImageDecoder::DecodeStream(stream, &fBitmap);
+ }
+ }
+}
diff --git a/animator/SkDrawBitmap.h b/animator/SkDrawBitmap.h
new file mode 100644
index 00000000..f9a92126
--- /dev/null
+++ b/animator/SkDrawBitmap.h
@@ -0,0 +1,74 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawBitmap_DEFINED
+#define SkDrawBitmap_DEFINED
+
+#include "SkBoundable.h"
+#include "SkBase64.h"
+#include "SkBitmap.h"
+// #include "SkImageDecoder.h"
+#include "SkMemberInfo.h"
+
+class SkBaseBitmap : public SkBoundable {
+ DECLARE_MEMBER_INFO(BaseBitmap);
+ SkBaseBitmap();
+ virtual ~SkBaseBitmap();
+ virtual bool draw(SkAnimateMaker& );
+protected:
+ SkBitmap fBitmap;
+ SkScalar x;
+ SkScalar y;
+private:
+ friend class SkDrawTo;
+ friend class SkDrawBitmapShader;
+ typedef SkBoundable INHERITED;
+};
+
+class SkDrawBitmap : public SkBaseBitmap {
+ DECLARE_DRAW_MEMBER_INFO(Bitmap);
+ SkDrawBitmap();
+ virtual ~SkDrawBitmap();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual bool setProperty(int index, SkScriptValue& value);
+protected:
+ int /*SkBitmap::Config*/ format;
+ int32_t height;
+ int32_t rowBytes;
+ int32_t width;
+ SkColor fColor;
+ SkBool fColorSet;
+ typedef SkBaseBitmap INHERITED;
+};
+
+class SkImageBaseBitmap : public SkBaseBitmap {
+ DECLARE_MEMBER_INFO(ImageBaseBitmap);
+ SkImageBaseBitmap();
+ virtual ~SkImageBaseBitmap();
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual void dirty();
+ virtual bool draw(SkAnimateMaker& );
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual void onEndElement(SkAnimateMaker& maker);
+private:
+ void resolve() const { (const_cast<SkImageBaseBitmap*>(this))->resolve(); }
+ void resolve();
+protected:
+ SkBase64 base64;
+ SkString src;
+ SkString fLast; // cache of src so that stream isn't unnecessarily decoded
+ SkBool fDirty;
+ const char* fUriBase;
+ typedef SkBaseBitmap INHERITED;
+};
+
+#endif // SkDrawBitmap_DEFINED
diff --git a/animator/SkDrawBlur.cpp b/animator/SkDrawBlur.cpp
new file mode 100644
index 00000000..d66fc564
--- /dev/null
+++ b/animator/SkDrawBlur.cpp
@@ -0,0 +1,31 @@
+
+/*
+ * 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 "SkDrawBlur.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawBlur::fInfo[] = {
+ SK_MEMBER(blurStyle, MaskFilterBlurStyle),
+ SK_MEMBER(radius, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawBlur);
+
+SkDrawBlur::SkDrawBlur() : radius(-1),
+ blurStyle(SkBlurMaskFilter::kNormal_BlurStyle) {
+}
+
+SkMaskFilter* SkDrawBlur::getMaskFilter() {
+ if (radius < 0)
+ return NULL;
+ return SkBlurMaskFilter::Create(radius, (SkBlurMaskFilter::BlurStyle) blurStyle);
+}
diff --git a/animator/SkDrawBlur.h b/animator/SkDrawBlur.h
new file mode 100644
index 00000000..24cb4aea
--- /dev/null
+++ b/animator/SkDrawBlur.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawBlur_DEFINED
+#define SkDrawBlur_DEFINED
+
+#include "SkPaintParts.h"
+#include "SkBlurMaskFilter.h"
+
+class SkDrawBlur : public SkDrawMaskFilter {
+ DECLARE_DRAW_MEMBER_INFO(Blur);
+ SkDrawBlur();
+ virtual SkMaskFilter* getMaskFilter();
+protected:
+ SkScalar radius;
+ int /*SkBlurMaskFilter::BlurStyle*/ blurStyle;
+};
+
+#endif // SkDrawBlur_DEFINED
diff --git a/animator/SkDrawClip.cpp b/animator/SkDrawClip.cpp
new file mode 100644
index 00000000..e26a1bf4
--- /dev/null
+++ b/animator/SkDrawClip.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * 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 "SkDrawClip.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkDrawRectangle.h"
+#include "SkDrawPath.h"
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawClip::fInfo[] = {
+ SK_MEMBER(path, Path),
+ SK_MEMBER(rect, Rect)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawClip);
+
+SkDrawClip::SkDrawClip() : rect(NULL), path(NULL) {
+}
+
+bool SkDrawClip::draw(SkAnimateMaker& maker ) {
+ if (rect != NULL)
+ maker.fCanvas->clipRect(rect->fRect);
+ else {
+ SkASSERT(path != NULL);
+ maker.fCanvas->clipPath(path->fPath);
+ }
+ return false;
+}
diff --git a/animator/SkDrawClip.h b/animator/SkDrawClip.h
new file mode 100644
index 00000000..62657752
--- /dev/null
+++ b/animator/SkDrawClip.h
@@ -0,0 +1,29 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawClip_DEFINED
+#define SkDrawClip_DEFINED
+
+#include "SkDrawable.h"
+#include "SkMemberInfo.h"
+#include "SkRegion.h"
+
+class SkDrawPath;
+class SkDrawRect;
+
+class SkDrawClip : public SkDrawable {
+ DECLARE_DRAW_MEMBER_INFO(Clip);
+ SkDrawClip();
+ virtual bool draw(SkAnimateMaker& );
+private:
+ SkDrawRect* rect;
+ SkDrawPath* path;
+};
+
+#endif // SkDrawClip_DEFINED
diff --git a/animator/SkDrawColor.cpp b/animator/SkDrawColor.cpp
new file mode 100644
index 00000000..b6bc261d
--- /dev/null
+++ b/animator/SkDrawColor.cpp
@@ -0,0 +1,269 @@
+
+/*
+ * 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 "SkDrawColor.h"
+#ifdef SK_DEBUG
+#include "SkDisplayList.h"
+#endif
+#include "SkDrawPaint.h"
+#include "SkParse.h"
+#include "SkScript.h"
+
+enum HSV_Choice {
+ kGetHue,
+ kGetSaturation,
+ kGetValue
+};
+
+static SkScalar RGB_to_HSV(SkColor color, HSV_Choice choice) {
+ SkScalar red = SkIntToScalar(SkColorGetR(color));
+ SkScalar green = SkIntToScalar(SkColorGetG(color));
+ SkScalar blue = SkIntToScalar(SkColorGetB(color));
+ SkScalar min = SkMinScalar(SkMinScalar(red, green), blue);
+ SkScalar value = SkMaxScalar(SkMaxScalar(red, green), blue);
+ if (choice == kGetValue)
+ return value/255;
+ SkScalar delta = value - min;
+ SkScalar saturation = value == 0 ? 0 : SkScalarDiv(delta, value);
+ if (choice == kGetSaturation)
+ return saturation;
+ SkScalar hue;
+ if (saturation == 0)
+ hue = 0;
+ else {
+ SkScalar part60 = SkScalarDiv(60 * SK_Scalar1, delta);
+ if (red == value) {
+ hue = SkScalarMul(green - blue, part60);
+ if (hue < 0)
+ hue += 360 * SK_Scalar1;
+ }
+ else if (green == value)
+ hue = 120 * SK_Scalar1 + SkScalarMul(blue - red, part60);
+ else // blue == value
+ hue = 240 * SK_Scalar1 + SkScalarMul(red - green, part60);
+ }
+ SkASSERT(choice == kGetHue);
+ return hue;
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable 'red', etc. may be used without having been initialized
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+static SkColor HSV_to_RGB(SkColor color, HSV_Choice choice, SkScalar hsv) {
+ SkScalar hue = choice == kGetHue ? hsv : RGB_to_HSV(color, kGetHue);
+ SkScalar saturation = choice == kGetSaturation ? hsv : RGB_to_HSV(color, kGetSaturation);
+ SkScalar value = choice == kGetValue ? hsv : RGB_to_HSV(color, kGetValue);
+ value *= 255;
+ SkScalar red SK_INIT_TO_AVOID_WARNING;
+ SkScalar green SK_INIT_TO_AVOID_WARNING;
+ SkScalar blue SK_INIT_TO_AVOID_WARNING;
+ if (saturation == 0) // color is on black-and-white center line
+ red = green = blue = value;
+ else {
+ //SkScalar fraction = SkScalarMod(hue, 60 * SK_Scalar1);
+ int sextant = SkScalarFloor(hue / 60);
+ SkScalar fraction = hue / 60 - SkIntToScalar(sextant);
+ SkScalar p = SkScalarMul(value , SK_Scalar1 - saturation);
+ SkScalar q = SkScalarMul(value, SK_Scalar1 - SkScalarMul(saturation, fraction));
+ SkScalar t = SkScalarMul(value, SK_Scalar1 -
+ SkScalarMul(saturation, SK_Scalar1 - fraction));
+ switch (sextant % 6) {
+ case 0: red = value; green = t; blue = p; break;
+ case 1: red = q; green = value; blue = p; break;
+ case 2: red = p; green = value; blue = t; break;
+ case 3: red = p; green = q; blue = value; break;
+ case 4: red = t; green = p; blue = value; break;
+ case 5: red = value; green = p; blue = q; break;
+ }
+ }
+ //used to say SkToU8((U8CPU) red) etc
+ return SkColorSetARGB(SkColorGetA(color), SkScalarRound(red),
+ SkScalarRound(green), SkScalarRound(blue));
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+enum SkDrawColor_Properties {
+ SK_PROPERTY(alpha),
+ SK_PROPERTY(blue),
+ SK_PROPERTY(green),
+ SK_PROPERTY(hue),
+ SK_PROPERTY(red),
+ SK_PROPERTY(saturation),
+ SK_PROPERTY(value)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawColor::fInfo[] = {
+ SK_MEMBER_PROPERTY(alpha, Float),
+ SK_MEMBER_PROPERTY(blue, Float),
+ SK_MEMBER(color, ARGB),
+ SK_MEMBER_PROPERTY(green, Float),
+ SK_MEMBER_PROPERTY(hue, Float),
+ SK_MEMBER_PROPERTY(red, Float),
+ SK_MEMBER_PROPERTY(saturation, Float),
+ SK_MEMBER_PROPERTY(value, Float),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawColor);
+
+SkDrawColor::SkDrawColor() : fDirty(false) {
+ color = SK_ColorBLACK;
+ fHue = fSaturation = fValue = SK_ScalarNaN;
+}
+
+bool SkDrawColor::add() {
+ if (fPaint->color != NULL)
+ return true; // error (probably color in paint as attribute as well)
+ fPaint->color = this;
+ fPaint->fOwnsColor = true;
+ return false;
+}
+
+SkDisplayable* SkDrawColor::deepCopy(SkAnimateMaker*) {
+ SkDrawColor* copy = new SkDrawColor();
+ copy->color = color;
+ copy->fHue = fHue;
+ copy->fSaturation = fSaturation;
+ copy->fValue = fValue;
+ copy->fDirty = fDirty;
+ return copy;
+}
+
+void SkDrawColor::dirty(){
+ fDirty = true;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawColor::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("alpha=\"%d\" red=\"%d\" green=\"%d\" blue=\"%d\" />\n",
+ SkColorGetA(color)/255, SkColorGetR(color),
+ SkColorGetG(color), SkColorGetB(color));
+}
+#endif
+
+SkColor SkDrawColor::getColor() {
+ if (fDirty) {
+ if (SkScalarIsNaN(fValue) == false)
+ color = HSV_to_RGB(color, kGetValue, fValue);
+ if (SkScalarIsNaN(fSaturation) == false)
+ color = HSV_to_RGB(color, kGetSaturation, fSaturation);
+ if (SkScalarIsNaN(fHue) == false)
+ color = HSV_to_RGB(color, kGetHue, fHue);
+ fDirty = false;
+ }
+ return color;
+}
+
+SkDisplayable* SkDrawColor::getParent() const {
+ return fPaint;
+}
+
+bool SkDrawColor::getProperty(int index, SkScriptValue* value) const {
+ value->fType = SkType_Float;
+ SkScalar result;
+ switch(index) {
+ case SK_PROPERTY(alpha):
+ result = SkIntToScalar(SkColorGetA(color)) / 255;
+ break;
+ case SK_PROPERTY(blue):
+ result = SkIntToScalar(SkColorGetB(color));
+ break;
+ case SK_PROPERTY(green):
+ result = SkIntToScalar(SkColorGetG(color));
+ break;
+ case SK_PROPERTY(hue):
+ result = RGB_to_HSV(color, kGetHue);
+ break;
+ case SK_PROPERTY(red):
+ result = SkIntToScalar(SkColorGetR(color));
+ break;
+ case SK_PROPERTY(saturation):
+ result = RGB_to_HSV(color, kGetSaturation);
+ break;
+ case SK_PROPERTY(value):
+ result = RGB_to_HSV(color, kGetValue);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ value->fOperand.fScalar = result;
+ return true;
+}
+
+void SkDrawColor::onEndElement(SkAnimateMaker&) {
+ fDirty = true;
+}
+
+bool SkDrawColor::setParent(SkDisplayable* parent) {
+ SkASSERT(parent != NULL);
+ if (parent->getType() == SkType_DrawLinearGradient || parent->getType() == SkType_DrawRadialGradient)
+ return false;
+ if (parent->isPaint() == false)
+ return true;
+ fPaint = (SkDrawPaint*) parent;
+ return false;
+}
+
+bool SkDrawColor::setProperty(int index, SkScriptValue& value) {
+ SkASSERT(value.fType == SkType_Float);
+ SkScalar scalar = value.fOperand.fScalar;
+ switch (index) {
+ case SK_PROPERTY(alpha):
+ uint8_t alpha;
+ #ifdef SK_SCALAR_IS_FLOAT
+ alpha = scalar == SK_Scalar1 ? 255 : SkToU8((U8CPU) (scalar * 256));
+ #else
+ alpha = SkToU8((scalar - (scalar >= SK_ScalarHalf)) >> 8);
+ #endif
+ color = SkColorSetARGB(alpha, SkColorGetR(color),
+ SkColorGetG(color), SkColorGetB(color));
+ break;
+ case SK_PROPERTY(blue):
+ scalar = SkScalarClampMax(scalar, 255 * SK_Scalar1);
+ color = SkColorSetARGB(SkColorGetA(color), SkColorGetR(color),
+ SkColorGetG(color), SkToU8((U8CPU) scalar));
+ break;
+ case SK_PROPERTY(green):
+ scalar = SkScalarClampMax(scalar, 255 * SK_Scalar1);
+ color = SkColorSetARGB(SkColorGetA(color), SkColorGetR(color),
+ SkToU8((U8CPU) scalar), SkColorGetB(color));
+ break;
+ case SK_PROPERTY(hue):
+ fHue = scalar;//RGB_to_HSV(color, kGetHue);
+ fDirty = true;
+ break;
+ case SK_PROPERTY(red):
+ scalar = SkScalarClampMax(scalar, 255 * SK_Scalar1);
+ color = SkColorSetARGB(SkColorGetA(color), SkToU8((U8CPU) scalar),
+ SkColorGetG(color), SkColorGetB(color));
+ break;
+ case SK_PROPERTY(saturation):
+ fSaturation = scalar;//RGB_to_HSV(color, kGetSaturation);
+ fDirty = true;
+ break;
+ case SK_PROPERTY(value):
+ fValue = scalar;//RGB_to_HSV(color, kGetValue);
+ fDirty = true;
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
diff --git a/animator/SkDrawColor.h b/animator/SkDrawColor.h
new file mode 100644
index 00000000..281af0fb
--- /dev/null
+++ b/animator/SkDrawColor.h
@@ -0,0 +1,42 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawColor_DEFINED
+#define SkDrawColor_DEFINED
+
+#include "SkPaintParts.h"
+#include "SkColor.h"
+
+class SkDrawColor : public SkPaintPart {
+ DECLARE_DRAW_MEMBER_INFO(Color);
+ SkDrawColor();
+ virtual bool add();
+ virtual void dirty();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ SkColor getColor();
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual SkDisplayable* getParent() const;
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual bool setParent(SkDisplayable* parent);
+ virtual bool setProperty(int index, SkScriptValue&);
+protected:
+ SkColor color;
+ SkScalar fHue;
+ SkScalar fSaturation;
+ SkScalar fValue;
+ SkBool fDirty;
+private:
+ friend class SkDrawGradient;
+ typedef SkPaintPart INHERITED;
+};
+
+#endif // SkDrawColor_DEFINED
diff --git a/animator/SkDrawDash.cpp b/animator/SkDrawDash.cpp
new file mode 100644
index 00000000..8e73aa1c
--- /dev/null
+++ b/animator/SkDrawDash.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * 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 "SkDrawDash.h"
+#include "SkDashPathEffect.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDash::fInfo[] = {
+ SK_MEMBER_ARRAY(intervals, Float),
+ SK_MEMBER(phase, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDash);
+
+SkDash::SkDash() : phase(0) {
+}
+
+SkDash::~SkDash() {
+}
+
+SkPathEffect* SkDash::getPathEffect() {
+ int count = intervals.count();
+ if (count == 0)
+ return NULL;
+ return new SkDashPathEffect(intervals.begin(), count, phase);
+}
diff --git a/animator/SkDrawDash.h b/animator/SkDrawDash.h
new file mode 100644
index 00000000..0000462b
--- /dev/null
+++ b/animator/SkDrawDash.h
@@ -0,0 +1,26 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawDash_DEFINED
+#define SkDrawDash_DEFINED
+
+#include "SkPaintParts.h"
+#include "SkIntArray.h"
+
+class SkDash : public SkDrawPathEffect {
+ DECLARE_MEMBER_INFO(Dash);
+ SkDash();
+ virtual ~SkDash();
+ virtual SkPathEffect* getPathEffect();
+private:
+ SkTDScalarArray intervals;
+ SkScalar phase;
+};
+
+#endif // SkDrawDash_DEFINED
diff --git a/animator/SkDrawDiscrete.cpp b/animator/SkDrawDiscrete.cpp
new file mode 100644
index 00000000..18c3ee0f
--- /dev/null
+++ b/animator/SkDrawDiscrete.cpp
@@ -0,0 +1,34 @@
+
+/*
+ * 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 "SkDrawDiscrete.h"
+#include "SkAnimateMaker.h"
+#include "SkPaint.h"
+#include "SkDiscretePathEffect.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDiscrete::fInfo[] = {
+ SK_MEMBER(deviation, Float),
+ SK_MEMBER(segLength, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDiscrete);
+
+SkDiscrete::SkDiscrete() : deviation(0), segLength(0) {
+}
+
+SkPathEffect* SkDiscrete::getPathEffect() {
+ if (deviation <= 0 || segLength <= 0)
+ return NULL;
+ else
+ return new SkDiscretePathEffect(segLength, deviation);
+}
diff --git a/animator/SkDrawDiscrete.h b/animator/SkDrawDiscrete.h
new file mode 100644
index 00000000..bd33d2fc
--- /dev/null
+++ b/animator/SkDrawDiscrete.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawDiscrete_DEFINED
+#define SkDrawDiscrete_DEFINED
+
+#include "SkPaintParts.h"
+
+class SkDiscrete : public SkDrawPathEffect {
+ DECLARE_MEMBER_INFO(Discrete);
+ SkDiscrete();
+ virtual SkPathEffect* getPathEffect();
+private:
+ SkScalar deviation;
+ SkScalar segLength;
+};
+
+#endif //SkDrawDiscrete_DEFINED
diff --git a/animator/SkDrawEmboss.cpp b/animator/SkDrawEmboss.cpp
new file mode 100644
index 00000000..5eed370a
--- /dev/null
+++ b/animator/SkDrawEmboss.cpp
@@ -0,0 +1,33 @@
+
+/*
+ * 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 "SkDrawEmboss.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawEmboss::fInfo[] = {
+ SK_MEMBER(ambient, Float),
+ SK_MEMBER_ARRAY(direction, Float),
+ SK_MEMBER(radius, Float),
+ SK_MEMBER(specular, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawEmboss);
+
+SkDrawEmboss::SkDrawEmboss() : radius(-1) {
+ direction.setCount(3);
+}
+
+SkMaskFilter* SkDrawEmboss::getMaskFilter() {
+ if (radius < 0 || direction.count() !=3)
+ return NULL;
+ return SkBlurMaskFilter::CreateEmboss(direction.begin(), ambient, specular, radius);
+}
diff --git a/animator/SkDrawEmboss.h b/animator/SkDrawEmboss.h
new file mode 100644
index 00000000..6e619975
--- /dev/null
+++ b/animator/SkDrawEmboss.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawEmboss_DEFINED
+#define SkDrawEmboss_DEFINED
+
+#include "SkDrawBlur.h"
+
+class SkDrawEmboss : public SkDrawMaskFilter {
+ DECLARE_DRAW_MEMBER_INFO(Emboss);
+ SkDrawEmboss();
+ virtual SkMaskFilter* getMaskFilter();
+protected:
+ SkTDScalarArray direction;
+ SkScalar radius, ambient, specular;
+};
+
+#endif // SkDrawEmboss_DEFINED
diff --git a/animator/SkDrawExtraPathEffect.cpp b/animator/SkDrawExtraPathEffect.cpp
new file mode 100644
index 00000000..e973dbf7
--- /dev/null
+++ b/animator/SkDrawExtraPathEffect.cpp
@@ -0,0 +1,515 @@
+
+/*
+ * 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 "SkDrawExtraPathEffect.h"
+#include "SkDrawPath.h"
+#include "Sk1DPathEffect.h"
+#include "Sk2DPathEffect.h"
+#include "SkMemberInfo.h"
+#include "SkPaintParts.h"
+#include "SkPathEffect.h"
+#include "SkCornerPathEffect.h"
+
+#include "SkDashPathEffect.h"
+
+class SkDrawShapePathEffect : public SkDrawPathEffect {
+ DECLARE_PRIVATE_MEMBER_INFO(DrawShapePathEffect);
+ SkDrawShapePathEffect();
+ virtual ~SkDrawShapePathEffect();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
+ virtual SkPathEffect* getPathEffect();
+protected:
+ SkDrawable* addPath;
+ SkDrawable* addMatrix;
+ SkDrawPath* path;
+ SkPathEffect* fPathEffect;
+ friend class SkShape1DPathEffect;
+ friend class SkShape2DPathEffect;
+};
+
+class SkDrawShape1DPathEffect : public SkDrawShapePathEffect {
+ DECLARE_EXTRAS_MEMBER_INFO(SkDrawShape1DPathEffect);
+ SkDrawShape1DPathEffect(SkDisplayTypes );
+ virtual ~SkDrawShape1DPathEffect();
+ virtual void onEndElement(SkAnimateMaker& );
+private:
+ SkString phase;
+ SkString spacing;
+ friend class SkShape1DPathEffect;
+ typedef SkDrawShapePathEffect INHERITED;
+};
+
+class SkDrawShape2DPathEffect : public SkDrawShapePathEffect {
+ DECLARE_EXTRAS_MEMBER_INFO(SkDrawShape2DPathEffect);
+ SkDrawShape2DPathEffect(SkDisplayTypes );
+ virtual ~SkDrawShape2DPathEffect();
+ virtual void onEndElement(SkAnimateMaker& );
+private:
+ SkDrawMatrix* matrix;
+ friend class SkShape2DPathEffect;
+ typedef SkDrawShapePathEffect INHERITED;
+};
+
+class SkDrawComposePathEffect : public SkDrawPathEffect {
+ DECLARE_EXTRAS_MEMBER_INFO(SkDrawComposePathEffect);
+ SkDrawComposePathEffect(SkDisplayTypes );
+ virtual ~SkDrawComposePathEffect();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE;
+ virtual SkPathEffect* getPathEffect();
+ virtual bool isPaint() const;
+private:
+ SkDrawPathEffect* effect1;
+ SkDrawPathEffect* effect2;
+};
+
+class SkDrawCornerPathEffect : public SkDrawPathEffect {
+ DECLARE_EXTRAS_MEMBER_INFO(SkDrawCornerPathEffect);
+ SkDrawCornerPathEffect(SkDisplayTypes );
+ virtual ~SkDrawCornerPathEffect();
+ virtual SkPathEffect* getPathEffect();
+private:
+ SkScalar radius;
+};
+
+//////////// SkShape1DPathEffect
+
+#include "SkAnimateMaker.h"
+#include "SkAnimatorScript.h"
+#include "SkDisplayApply.h"
+#include "SkDrawMatrix.h"
+#include "SkPaint.h"
+
+class SkShape1DPathEffect : public Sk1DPathEffect {
+public:
+ SkShape1DPathEffect(SkDrawShape1DPathEffect* draw, SkAnimateMaker* maker) :
+ fDraw(draw), fMaker(maker) {
+ }
+
+ SK_DECLARE_UNFLATTENABLE_OBJECT()
+
+protected:
+ virtual SkScalar begin(SkScalar contourLength) const {
+ SkScriptValue value;
+ SkAnimatorScript engine(*fMaker, NULL, SkType_Float);
+ engine.propertyCallBack(GetContourLength, &contourLength);
+ value.fOperand.fScalar = 0;
+ engine.evaluate(fDraw->phase.c_str(), &value, SkType_Float);
+ return value.fOperand.fScalar;
+ }
+
+ virtual SkScalar next(SkPath* dst, SkScalar distance, SkPathMeasure&) const {
+ fMaker->setExtraPropertyCallBack(fDraw->fType, GetDistance, &distance);
+ SkDrawPath* drawPath = NULL;
+ if (fDraw->addPath->isPath()) {
+ drawPath = (SkDrawPath*) fDraw->addPath;
+ } else {
+ SkApply* apply = (SkApply*) fDraw->addPath;
+ apply->refresh(*fMaker);
+ apply->activate(*fMaker);
+ apply->interpolate(*fMaker, SkScalarMulRound(distance, 1000));
+ drawPath = (SkDrawPath*) apply->getScope();
+ }
+ SkMatrix m;
+ m.reset();
+ if (fDraw->addMatrix) {
+ SkDrawMatrix* matrix;
+ if (fDraw->addMatrix->getType() == SkType_Matrix)
+ matrix = (SkDrawMatrix*) fDraw->addMatrix;
+ else {
+ SkApply* apply = (SkApply*) fDraw->addMatrix;
+ apply->refresh(*fMaker);
+ apply->activate(*fMaker);
+ apply->interpolate(*fMaker, SkScalarMulRound(distance, 1000));
+ matrix = (SkDrawMatrix*) apply->getScope();
+ }
+ if (matrix) {
+ m = matrix->getMatrix();
+ }
+ }
+ SkScalar result = 0;
+ SkAnimatorScript::EvaluateFloat(*fMaker, NULL, fDraw->spacing.c_str(), &result);
+ if (drawPath)
+ dst->addPath(drawPath->getPath(), m);
+ fMaker->clearExtraPropertyCallBack(fDraw->fType);
+ return result;
+ }
+
+private:
+ static bool GetContourLength(const char* token, size_t len, void* clen, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("contourLength", token, len)) {
+ value->fOperand.fScalar = *(SkScalar*) clen;
+ value->fType = SkType_Float;
+ return true;
+ }
+ return false;
+ }
+
+ static bool GetDistance(const char* token, size_t len, void* dist, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("distance", token, len)) {
+ value->fOperand.fScalar = *(SkScalar*) dist;
+ value->fType = SkType_Float;
+ return true;
+ }
+ return false;
+ }
+
+ SkDrawShape1DPathEffect* fDraw;
+ SkAnimateMaker* fMaker;
+};
+
+//////////// SkDrawShapePathEffect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawShapePathEffect::fInfo[] = {
+ SK_MEMBER(addMatrix, Drawable), // either matrix or apply
+ SK_MEMBER(addPath, Drawable), // either path or apply
+ SK_MEMBER(path, Path),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawShapePathEffect);
+
+SkDrawShapePathEffect::SkDrawShapePathEffect() :
+ addPath(NULL), addMatrix(NULL), path(NULL), fPathEffect(NULL) {
+}
+
+SkDrawShapePathEffect::~SkDrawShapePathEffect() {
+ SkSafeUnref(fPathEffect);
+}
+
+bool SkDrawShapePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ path = (SkDrawPath*) child;
+ return true;
+}
+
+SkPathEffect* SkDrawShapePathEffect::getPathEffect() {
+ fPathEffect->ref();
+ return fPathEffect;
+}
+
+//////////// SkDrawShape1DPathEffect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawShape1DPathEffect::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(phase, String),
+ SK_MEMBER(spacing, String),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawShape1DPathEffect);
+
+SkDrawShape1DPathEffect::SkDrawShape1DPathEffect(SkDisplayTypes type) : fType(type) {
+}
+
+SkDrawShape1DPathEffect::~SkDrawShape1DPathEffect() {
+}
+
+void SkDrawShape1DPathEffect::onEndElement(SkAnimateMaker& maker) {
+ if (addPath == NULL || (addPath->isPath() == false && addPath->isApply() == false))
+ maker.setErrorCode(SkDisplayXMLParserError::kUnknownError); // !!! add error
+ else
+ fPathEffect = new SkShape1DPathEffect(this, &maker);
+}
+
+////////// SkShape2DPathEffect
+
+class SkShape2DPathEffect : public Sk2DPathEffect {
+public:
+ SkShape2DPathEffect(SkDrawShape2DPathEffect* draw, SkAnimateMaker* maker,
+ const SkMatrix& matrix) : Sk2DPathEffect(matrix), fDraw(draw), fMaker(maker) {
+ }
+
+protected:
+ virtual void begin(const SkIRect& uvBounds, SkPath*) const SK_OVERRIDE {
+ const_cast<SkShape2DPathEffect*>(this)->setUVBounds(uvBounds);
+ }
+
+ virtual void next(const SkPoint& loc, int u, int v, SkPath* dst) const SK_OVERRIDE {
+ const_cast<SkShape2DPathEffect*>(this)->addPath(loc, u, v, dst);
+ }
+
+private:
+ void setUVBounds(const SkIRect& uvBounds) {
+ fUVBounds.set(SkIntToScalar(uvBounds.fLeft), SkIntToScalar(uvBounds.fTop),
+ SkIntToScalar(uvBounds.fRight), SkIntToScalar(uvBounds.fBottom));
+ }
+
+ void addPath(const SkPoint& loc, int u, int v, SkPath* dst) {
+ fLoc = loc;
+ fU = u;
+ fV = v;
+ SkDrawPath* drawPath;
+ fMaker->setExtraPropertyCallBack(fDraw->fType, Get2D, this);
+ if (fDraw->addPath->isPath()) {
+ drawPath = (SkDrawPath*) fDraw->addPath;
+ } else {
+ SkApply* apply = (SkApply*) fDraw->addPath;
+ apply->refresh(*fMaker);
+ apply->activate(*fMaker);
+ apply->interpolate(*fMaker, v);
+ drawPath = (SkDrawPath*) apply->getScope();
+ }
+ if (drawPath == NULL)
+ goto clearCallBack;
+ if (fDraw->matrix) {
+ SkDrawMatrix* matrix;
+ if (fDraw->matrix->getType() == SkType_Matrix)
+ matrix = (SkDrawMatrix*) fDraw->matrix;
+ else {
+ SkApply* apply = (SkApply*) fDraw->matrix;
+ apply->activate(*fMaker);
+ apply->interpolate(*fMaker, v);
+ matrix = (SkDrawMatrix*) apply->getScope();
+ }
+ if (matrix) {
+ dst->addPath(drawPath->getPath(), matrix->getMatrix());
+ goto clearCallBack;
+ }
+ }
+ dst->addPath(drawPath->getPath());
+clearCallBack:
+ fMaker->clearExtraPropertyCallBack(fDraw->fType);
+ }
+
+ static bool Get2D(const char* token, size_t len, void* s2D, SkScriptValue* value) {
+ static const char match[] = "locX|locY|left|top|right|bottom|u|v" ;
+ SkShape2DPathEffect* shape2D = (SkShape2DPathEffect*) s2D;
+ int index;
+ if (SkAnimatorScript::MapEnums(match, token, len, &index) == false)
+ return false;
+ SkASSERT((sizeof(SkPoint) + sizeof(SkRect)) / sizeof(SkScalar) == 6);
+ if (index < 6) {
+ value->fType = SkType_Float;
+ value->fOperand.fScalar = (&shape2D->fLoc.fX)[index];
+ } else {
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = (&shape2D->fU)[index - 6];
+ }
+ return true;
+ }
+
+ SkPoint fLoc;
+ SkRect fUVBounds;
+ int32_t fU;
+ int32_t fV;
+ SkDrawShape2DPathEffect* fDraw;
+ SkAnimateMaker* fMaker;
+
+ // illegal
+ SkShape2DPathEffect(const SkShape2DPathEffect&);
+ SkShape2DPathEffect& operator=(const SkShape2DPathEffect&);
+};
+
+////////// SkDrawShape2DPathEffect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawShape2DPathEffect::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(matrix, Matrix)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawShape2DPathEffect);
+
+SkDrawShape2DPathEffect::SkDrawShape2DPathEffect(SkDisplayTypes type) : fType(type) {
+}
+
+SkDrawShape2DPathEffect::~SkDrawShape2DPathEffect() {
+}
+
+void SkDrawShape2DPathEffect::onEndElement(SkAnimateMaker& maker) {
+ if (addPath == NULL || (addPath->isPath() == false && addPath->isApply() == false) ||
+ matrix == NULL)
+ maker.setErrorCode(SkDisplayXMLParserError::kUnknownError); // !!! add error
+ else
+ fPathEffect = new SkShape2DPathEffect(this, &maker, matrix->getMatrix());
+}
+
+////////// SkDrawComposePathEffect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawComposePathEffect::fInfo[] = {
+ SK_MEMBER(effect1, PathEffect),
+ SK_MEMBER(effect2, PathEffect)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawComposePathEffect);
+
+SkDrawComposePathEffect::SkDrawComposePathEffect(SkDisplayTypes type) : fType(type),
+ effect1(NULL), effect2(NULL) {
+}
+
+SkDrawComposePathEffect::~SkDrawComposePathEffect() {
+ delete effect1;
+ delete effect2;
+}
+
+bool SkDrawComposePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ if (effect1 == NULL)
+ effect1 = (SkDrawPathEffect*) child;
+ else
+ effect2 = (SkDrawPathEffect*) child;
+ return true;
+}
+
+SkPathEffect* SkDrawComposePathEffect::getPathEffect() {
+ SkPathEffect* e1 = effect1->getPathEffect();
+ SkPathEffect* e2 = effect2->getPathEffect();
+ SkPathEffect* composite = new SkComposePathEffect(e1, e2);
+ e1->unref();
+ e2->unref();
+ return composite;
+}
+
+bool SkDrawComposePathEffect::isPaint() const {
+ return true;
+}
+
+//////////// SkDrawCornerPathEffect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawCornerPathEffect::fInfo[] = {
+ SK_MEMBER(radius, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawCornerPathEffect);
+
+SkDrawCornerPathEffect::SkDrawCornerPathEffect(SkDisplayTypes type):
+ fType(type), radius(0) {
+}
+
+SkDrawCornerPathEffect::~SkDrawCornerPathEffect() {
+}
+
+SkPathEffect* SkDrawCornerPathEffect::getPathEffect() {
+ return new SkCornerPathEffect(radius);
+}
+
+/////////
+
+#include "SkExtras.h"
+
+const char kDrawShape1DPathEffectName[] = "pathEffect:shape1D";
+const char kDrawShape2DPathEffectName[] = "pathEffect:shape2D";
+const char kDrawComposePathEffectName[] = "pathEffect:compose";
+const char kDrawCornerPathEffectName[] = "pathEffect:corner";
+
+class SkExtraPathEffects : public SkExtras {
+public:
+ SkExtraPathEffects() :
+ skDrawShape1DPathEffectType(SkType_Unknown),
+ skDrawShape2DPathEffectType(SkType_Unknown),
+ skDrawComposePathEffectType(SkType_Unknown),
+ skDrawCornerPathEffectType(SkType_Unknown) {
+ }
+
+ virtual SkDisplayable* createInstance(SkDisplayTypes type) {
+ SkDisplayable* result = NULL;
+ if (skDrawShape1DPathEffectType == type)
+ result = new SkDrawShape1DPathEffect(type);
+ else if (skDrawShape2DPathEffectType == type)
+ result = new SkDrawShape2DPathEffect(type);
+ else if (skDrawComposePathEffectType == type)
+ result = new SkDrawComposePathEffect(type);
+ else if (skDrawCornerPathEffectType == type)
+ result = new SkDrawCornerPathEffect(type);
+ return result;
+ }
+
+ virtual bool definesType(SkDisplayTypes type) {
+ return type == skDrawShape1DPathEffectType ||
+ type == skDrawShape2DPathEffectType ||
+ type == skDrawComposePathEffectType ||
+ type == skDrawCornerPathEffectType;
+ }
+
+#if SK_USE_CONDENSED_INFO == 0
+ virtual const SkMemberInfo* getMembers(SkDisplayTypes type, int* infoCountPtr) {
+ const SkMemberInfo* info = NULL;
+ int infoCount = 0;
+ if (skDrawShape1DPathEffectType == type) {
+ info = SkDrawShape1DPathEffect::fInfo;
+ infoCount = SkDrawShape1DPathEffect::fInfoCount;
+ } else if (skDrawShape2DPathEffectType == type) {
+ info = SkDrawShape2DPathEffect::fInfo;
+ infoCount = SkDrawShape2DPathEffect::fInfoCount;
+ } else if (skDrawComposePathEffectType == type) {
+ info = SkDrawComposePathEffect::fInfo;
+ infoCount = SkDrawShape1DPathEffect::fInfoCount;
+ } else if (skDrawCornerPathEffectType == type) {
+ info = SkDrawCornerPathEffect::fInfo;
+ infoCount = SkDrawCornerPathEffect::fInfoCount;
+ }
+ if (infoCountPtr)
+ *infoCountPtr = infoCount;
+ return info;
+ }
+#endif
+
+#ifdef SK_DEBUG
+ virtual const char* getName(SkDisplayTypes type) {
+ if (skDrawShape1DPathEffectType == type)
+ return kDrawShape1DPathEffectName;
+ else if (skDrawShape2DPathEffectType == type)
+ return kDrawShape2DPathEffectName;
+ else if (skDrawComposePathEffectType == type)
+ return kDrawComposePathEffectName;
+ else if (skDrawCornerPathEffectType == type)
+ return kDrawCornerPathEffectName;
+ return NULL;
+ }
+#endif
+
+ virtual SkDisplayTypes getType(const char name[], size_t len ) {
+ SkDisplayTypes* type = NULL;
+ if (SK_LITERAL_STR_EQUAL(kDrawShape1DPathEffectName, name, len))
+ type = &skDrawShape1DPathEffectType;
+ else if (SK_LITERAL_STR_EQUAL(kDrawShape2DPathEffectName, name, len))
+ type = &skDrawShape2DPathEffectType;
+ else if (SK_LITERAL_STR_EQUAL(kDrawComposePathEffectName, name, len))
+ type = &skDrawComposePathEffectType;
+ else if (SK_LITERAL_STR_EQUAL(kDrawCornerPathEffectName, name, len))
+ type = &skDrawCornerPathEffectType;
+ if (type) {
+ if (*type == SkType_Unknown)
+ *type = SkDisplayType::RegisterNewType();
+ return *type;
+ }
+ return SkType_Unknown;
+ }
+
+private:
+ SkDisplayTypes skDrawShape1DPathEffectType;
+ SkDisplayTypes skDrawShape2DPathEffectType;
+ SkDisplayTypes skDrawComposePathEffectType;
+ SkDisplayTypes skDrawCornerPathEffectType;
+};
+
+void InitializeSkExtraPathEffects(SkAnimator* animator) {
+ animator->addExtras(new SkExtraPathEffects());
+}
+
+////////////////
+
+
+SkExtras::SkExtras() : fExtraCallBack(NULL), fExtraStorage(NULL) {
+}
diff --git a/animator/SkDrawFull.cpp b/animator/SkDrawFull.cpp
new file mode 100644
index 00000000..a1a5fc9f
--- /dev/null
+++ b/animator/SkDrawFull.cpp
@@ -0,0 +1,18 @@
+
+/*
+ * 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 "SkDrawFull.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+
+bool SkFull::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawPaint(*maker.fPaint);
+ return false;
+}
diff --git a/animator/SkDrawFull.h b/animator/SkDrawFull.h
new file mode 100644
index 00000000..a2dcf497
--- /dev/null
+++ b/animator/SkDrawFull.h
@@ -0,0 +1,22 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawFull_DEFINED
+#define SkDrawFull_DEFINED
+
+#include "SkBoundable.h"
+
+class SkFull : public SkBoundable {
+ DECLARE_EMPTY_MEMBER_INFO(Full);
+ virtual bool draw(SkAnimateMaker& );
+private:
+ typedef SkBoundable INHERITED;
+};
+
+#endif // SkDrawFull_DEFINED
diff --git a/animator/SkDrawGradient.cpp b/animator/SkDrawGradient.cpp
new file mode 100644
index 00000000..44086e42
--- /dev/null
+++ b/animator/SkDrawGradient.cpp
@@ -0,0 +1,226 @@
+
+/*
+ * 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 "SkDrawGradient.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimatorScript.h"
+#include "SkGradientShader.h"
+#include "SkUnitMapper.h"
+
+static SkScalar SkUnitToScalar(U16CPU x) {
+#ifdef SK_SCALAR_IS_FLOAT
+ return x / 65535.0f;
+#else
+ return x + (x >> 8);
+#endif
+}
+
+static U16CPU SkScalarToUnit(SkScalar x) {
+ SkScalar pin = SkScalarPin(x, 0, SK_Scalar1);
+#ifdef SK_SCALAR_IS_FLOAT
+ return (int) (pin * 65535.0f);
+#else
+ return pin - (pin >= 32768);
+#endif
+}
+
+class SkDrawGradientUnitMapper : public SkUnitMapper {
+public:
+ SkDrawGradientUnitMapper(SkAnimateMaker* maker, const char* script) : fMaker(maker), fScript(script) {
+ }
+
+ SK_DECLARE_UNFLATTENABLE_OBJECT()
+
+protected:
+ virtual uint16_t mapUnit16(uint16_t x) {
+ fUnit = SkUnitToScalar(x);
+ SkScriptValue value;
+ SkAnimatorScript engine(*fMaker, NULL, SkType_Float);
+ engine.propertyCallBack(GetUnitValue, &fUnit);
+ if (engine.evaluate(fScript, &value, SkType_Float))
+ x = SkScalarToUnit(value.fOperand.fScalar);
+ return x;
+ }
+
+ static bool GetUnitValue(const char* token, size_t len, void* unitPtr, SkScriptValue* value) {
+ if (SK_LITERAL_STR_EQUAL("unit", token, len)) {
+ value->fOperand.fScalar = *(SkScalar*) unitPtr;
+ value->fType = SkType_Float;
+ return true;
+ }
+ return false;
+ }
+
+ SkAnimateMaker* fMaker;
+ const char* fScript;
+ SkScalar fUnit;
+};
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawGradient::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER_ARRAY(offsets, Float),
+ SK_MEMBER(unitMapper, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawGradient);
+
+SkDrawGradient::SkDrawGradient() : fUnitMapper(NULL) {
+}
+
+SkDrawGradient::~SkDrawGradient() {
+ for (int index = 0; index < fDrawColors.count(); index++)
+ delete fDrawColors[index];
+ delete fUnitMapper;
+}
+
+bool SkDrawGradient::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ SkASSERT(child);
+ if (child->isColor()) {
+ SkDrawColor* color = (SkDrawColor*) child;
+ *fDrawColors.append() = color;
+ return true;
+ }
+ return false;
+}
+
+int SkDrawGradient::addPrelude() {
+ int count = fDrawColors.count();
+ fColors.setCount(count);
+ for (int index = 0; index < count; index++)
+ fColors[index] = fDrawColors[index]->color;
+ return count;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawGradient::dumpRest(SkAnimateMaker* maker) {
+ dumpAttrs(maker);
+ //can a gradient have no colors?
+ bool closedYet = false;
+ SkDisplayList::fIndent += 4;
+ for (SkDrawColor** ptr = fDrawColors.begin(); ptr < fDrawColors.end(); ptr++) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ SkDrawColor* color = *ptr;
+ color->dump(maker);
+ }
+ SkDisplayList::fIndent -= 4;
+ dumpChildren(maker, closedYet); //dumps the matrix if it has one
+}
+#endif
+
+void SkDrawGradient::onEndElement(SkAnimateMaker& maker) {
+ if (offsets.count() != 0) {
+ if (offsets.count() != fDrawColors.count()) {
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientOffsetsDontMatchColors);
+ return;
+ }
+ if (offsets[0] != 0) {
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientOffsetsMustStartWithZero);
+ return;
+ }
+ if (offsets[offsets.count()-1] != SK_Scalar1) {
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientOffsetsMustEndWithOne);
+ return;
+ }
+ for (int i = 1; i < offsets.count(); i++) {
+ if (offsets[i] <= offsets[i-1]) {
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientOffsetsMustIncrease);
+ return;
+ }
+ if (offsets[i] > SK_Scalar1) {
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientOffsetsMustBeNoMoreThanOne);
+ return;
+ }
+ }
+ }
+ if (unitMapper.size() > 0)
+ fUnitMapper = new SkDrawGradientUnitMapper(&maker, unitMapper.c_str());
+ INHERITED::onEndElement(maker);
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawLinearGradient::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER_ARRAY(points, Float),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawLinearGradient);
+
+SkDrawLinearGradient::SkDrawLinearGradient() {
+}
+
+void SkDrawLinearGradient::onEndElement(SkAnimateMaker& maker)
+{
+ if (points.count() != 4)
+ maker.setErrorCode(SkDisplayXMLParserError::kGradientPointsLengthMustBeFour);
+ INHERITED::onEndElement(maker);
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawLinearGradient::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpRest(maker);
+ }
+#endif
+
+SkShader* SkDrawLinearGradient::getShader() {
+ if (addPrelude() == 0 || points.count() != 4)
+ return NULL;
+ SkShader* shader = SkGradientShader::CreateLinear((SkPoint*)points.begin(),
+ fColors.begin(), offsets.begin(), fColors.count(), (SkShader::TileMode) tileMode, fUnitMapper);
+ SkAutoTDelete<SkShader> autoDel(shader);
+ addPostlude(shader);
+ (void)autoDel.detach();
+ return shader;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawRadialGradient::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(center, Point),
+ SK_MEMBER(radius, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawRadialGradient);
+
+SkDrawRadialGradient::SkDrawRadialGradient() : radius(0) {
+ center.set(0, 0);
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawRadialGradient::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpRest(maker);
+}
+#endif
+
+SkShader* SkDrawRadialGradient::getShader() {
+ if (addPrelude() == 0)
+ return NULL;
+ SkShader* shader = SkGradientShader::CreateRadial(center,
+ radius, fColors.begin(), offsets.begin(), fColors.count(), (SkShader::TileMode) tileMode, fUnitMapper);
+ SkAutoTDelete<SkShader> autoDel(shader);
+ addPostlude(shader);
+ (void)autoDel.detach();
+ return shader;
+}
diff --git a/animator/SkDrawGradient.h b/animator/SkDrawGradient.h
new file mode 100644
index 00000000..ff79e3fb
--- /dev/null
+++ b/animator/SkDrawGradient.h
@@ -0,0 +1,67 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawGradient_DEFINED
+#define SkDrawGradient_DEFINED
+
+#include "SkDrawColor.h"
+#include "SkDrawShader.h"
+#include "SkIntArray.h"
+
+class SkUnitMapper;
+
+class SkDrawGradient : public SkDrawShader {
+ DECLARE_PRIVATE_MEMBER_INFO(DrawGradient);
+ SkDrawGradient();
+ virtual ~SkDrawGradient();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+#ifdef SK_DUMP_ENABLED
+ virtual void dumpRest(SkAnimateMaker*);
+#endif
+ virtual void onEndElement(SkAnimateMaker& );
+protected:
+ SkTDScalarArray offsets;
+ SkString unitMapper;
+ SkTDColorArray fColors;
+ SkTDDrawColorArray fDrawColors;
+ SkUnitMapper* fUnitMapper;
+ int addPrelude();
+private:
+ typedef SkDrawShader INHERITED;
+};
+
+class SkDrawLinearGradient : public SkDrawGradient {
+ DECLARE_MEMBER_INFO(DrawLinearGradient);
+ SkDrawLinearGradient();
+ virtual void onEndElement(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker*);
+#endif
+ virtual SkShader* getShader();
+protected:
+ SkTDScalarArray points;
+private:
+ typedef SkDrawGradient INHERITED;
+};
+
+class SkDrawRadialGradient : public SkDrawGradient {
+ DECLARE_MEMBER_INFO(DrawRadialGradient);
+ SkDrawRadialGradient();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker*);
+#endif
+ virtual SkShader* getShader();
+protected:
+ SkPoint center;
+ SkScalar radius;
+private:
+ typedef SkDrawGradient INHERITED;
+};
+
+#endif // SkDrawGradient_DEFINED
diff --git a/animator/SkDrawGroup.cpp b/animator/SkDrawGroup.cpp
new file mode 100644
index 00000000..f4bc3c63
--- /dev/null
+++ b/animator/SkDrawGroup.cpp
@@ -0,0 +1,321 @@
+
+/*
+ * 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 "SkDrawGroup.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimatorScript.h"
+#include "SkCanvas.h"
+#include "SkDisplayApply.h"
+#include "SkPaint.h"
+#ifdef SK_DEBUG
+#include "SkDisplayList.h"
+#endif
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkGroup::fInfo[] = {
+ SK_MEMBER(condition, String),
+ SK_MEMBER(enableCondition, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkGroup);
+
+SkGroup::SkGroup() : fParentList(NULL), fOriginal(NULL) {
+}
+
+SkGroup::~SkGroup() {
+ if (fOriginal) // has been copied
+ return;
+ int index = 0;
+ int max = fCopies.count() << 5;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ if (index >= max || markedForDelete(index))
+ delete *ptr;
+// else {
+// SkApply* apply = (SkApply*) *ptr;
+// SkASSERT(apply->isApply());
+// SkASSERT(apply->getScope());
+// delete apply->getScope();
+// }
+ index++;
+ }
+}
+
+bool SkGroup::addChild(SkAnimateMaker& , SkDisplayable* child) {
+ SkASSERT(child);
+// SkASSERT(child->isDrawable());
+ *fChildren.append() = (SkDrawable*) child;
+ if (child->isGroup()) {
+ SkGroup* groupie = (SkGroup*) child;
+ SkASSERT(groupie->fParentList == NULL);
+ groupie->fParentList = &fChildren;
+ }
+ return true;
+}
+
+bool SkGroup::contains(SkDisplayable* match) {
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable == match || drawable->contains(match))
+ return true;
+ }
+ return false;
+}
+
+SkGroup* SkGroup::copy() {
+ SkGroup* result = new SkGroup();
+ result->fOriginal = this;
+ result->fChildren = fChildren;
+ return result;
+}
+
+SkBool SkGroup::copySet(int index) {
+ return (fCopies[index >> 5] & 1 << (index & 0x1f)) != 0;
+}
+
+SkDisplayable* SkGroup::deepCopy(SkAnimateMaker* maker) {
+ SkDisplayable* copy = INHERITED::deepCopy(maker);
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDisplayable* displayable = (SkDisplayable*)*ptr;
+ SkDisplayable* deeperCopy = displayable->deepCopy(maker);
+ ((SkGroup*)copy)->addChild(*maker, deeperCopy);
+ }
+ return copy;
+}
+
+bool SkGroup::doEvent(SkDisplayEvent::Kind kind, SkEventState* state) {
+ bool handled = false;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable->isDrawable() == false)
+ continue;
+ handled |= drawable->doEvent(kind, state);
+ }
+ return handled;
+}
+
+bool SkGroup::draw(SkAnimateMaker& maker) {
+ bool conditionTrue = ifCondition(maker, this, condition);
+ bool result = false;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable->isDrawable() == false)
+ continue;
+ if (conditionTrue == false) {
+ if (drawable->isApply())
+ ((SkApply*) drawable)->disable();
+ continue;
+ }
+ maker.validate();
+ result |= drawable->draw(maker);
+ maker.validate();
+ }
+ return result;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkGroup::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ if (condition.size() > 0)
+ SkDebugf("condition=\"%s\" ", condition.c_str());
+ if (enableCondition.size() > 0)
+ SkDebugf("enableCondition=\"%s\" ", enableCondition.c_str());
+ dumpDrawables(maker);
+}
+
+void SkGroup::dumpDrawables(SkAnimateMaker* maker) {
+ SkDisplayList::fIndent += 4;
+ int save = SkDisplayList::fDumpIndex;
+ SkDisplayList::fDumpIndex = 0;
+ bool closedYet = false;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ if (closedYet == false) {
+ closedYet = true;
+ SkDebugf(">\n");
+ }
+ SkDrawable* drawable = *ptr;
+ drawable->dump(maker);
+ SkDisplayList::fDumpIndex++;
+ }
+ SkDisplayList::fIndent -= 4;
+ SkDisplayList::fDumpIndex = save;
+ if (closedYet) //we had children, now it's time to close the group
+ dumpEnd(maker);
+ else //no children
+ SkDebugf("/>\n");
+}
+
+void SkGroup::dumpEvents() {
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ drawable->dumpEvents();
+ }
+}
+#endif
+
+bool SkGroup::enable(SkAnimateMaker& maker ) {
+ reset();
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (ifCondition(maker, drawable, enableCondition) == false)
+ continue;
+ drawable->enable(maker);
+ }
+ return true; // skip add; already added so that scope is findable by children
+}
+
+int SkGroup::findGroup(SkDrawable* match, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray** grandList) {
+ *list = &fChildren;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable->isGroup()) {
+ SkGroup* childGroup = (SkGroup*) drawable;
+ if (childGroup->fOriginal == match)
+ goto foundMatch;
+ }
+ if (drawable == match) {
+foundMatch:
+ *parent = this;
+ return (int) (ptr - fChildren.begin());
+ }
+ }
+ *grandList = &fChildren;
+ return SkDisplayList::SearchForMatch(match, list, parent, found, grandList);
+}
+
+bool SkGroup::hasEnable() const {
+ return true;
+}
+
+bool SkGroup::ifCondition(SkAnimateMaker& maker, SkDrawable*,
+ SkString& conditionString) {
+ if (conditionString.size() == 0)
+ return true;
+ int32_t result;
+ bool success = SkAnimatorScript::EvaluateInt(maker, this, conditionString.c_str(), &result);
+#ifdef SK_DUMP_ENABLED
+ if (maker.fDumpGConditions) {
+ SkDebugf("group: ");
+ dumpBase(&maker);
+ SkDebugf("condition=%s ", conditionString.c_str());
+ if (success == false)
+ SkDebugf("(script failed)\n");
+ else
+ SkDebugf("success=%s\n", result != 0 ? "true" : "false");
+ }
+#endif
+ return success && result != 0;
+}
+
+void SkGroup::initialize() {
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable->isDrawable() == false)
+ continue;
+ drawable->initialize();
+ }
+}
+
+void SkGroup::markCopyClear(int index) {
+ if (index < 0)
+ index = fChildren.count();
+ fCopies[index >> 5] &= ~(1 << (index & 0x1f));
+}
+
+void SkGroup::markCopySet(int index) {
+ if (index < 0)
+ index = fChildren.count();
+ fCopies[index >> 5] |= 1 << (index & 0x1f);
+}
+
+void SkGroup::markCopySize(int index) {
+ if (index < 0)
+ index = fChildren.count() + 1;
+ int oldLongs = fCopies.count();
+ int newLongs = (index >> 5) + 1;
+ if (oldLongs < newLongs) {
+ fCopies.setCount(newLongs);
+ memset(&fCopies[oldLongs], 0, (newLongs - oldLongs) << 2);
+ }
+}
+
+void SkGroup::reset() {
+ if (fOriginal) // has been copied
+ return;
+ int index = 0;
+ int max = fCopies.count() << 5;
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ if (index >= max || copySet(index) == false)
+ continue;
+ SkApply* apply = (SkApply*) *ptr;
+ SkASSERT(apply->isApply());
+ SkASSERT(apply->getScope());
+ *ptr = apply->getScope();
+ markCopyClear(index);
+ index++;
+ }
+}
+
+bool SkGroup::resolveIDs(SkAnimateMaker& maker, SkDisplayable* orig, SkApply* apply) {
+ SkGroup* original = (SkGroup*) orig;
+ SkTDDrawableArray& originalChildren = original->fChildren;
+ SkDrawable** originalPtr = originalChildren.begin();
+ SkDrawable** ptr = fChildren.begin();
+ SkDrawable** end = fChildren.end();
+ SkDrawable** origChild = ((SkGroup*) orig)->fChildren.begin();
+ while (ptr < end) {
+ SkDrawable* drawable = *ptr++;
+ maker.resolveID(drawable, *origChild++);
+ if (drawable->resolveIDs(maker, *originalPtr++, apply) == true)
+ return true; // failed
+ }
+ return false;
+}
+
+void SkGroup::setSteps(int steps) {
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ if (drawable->isDrawable() == false)
+ continue;
+ drawable->setSteps(steps);
+ }
+}
+
+#ifdef SK_DEBUG
+void SkGroup::validate() {
+ for (SkDrawable** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkDrawable* drawable = *ptr;
+ drawable->validate();
+ }
+}
+#endif
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkSave::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkSave);
+
+bool SkSave::draw(SkAnimateMaker& maker) {
+ maker.fCanvas->save();
+ SkPaint* save = maker.fPaint;
+ SkPaint local = SkPaint(*maker.fPaint);
+ maker.fPaint = &local;
+ bool result = INHERITED::draw(maker);
+ maker.fPaint = save;
+ maker.fCanvas->restore();
+ return result;
+}
diff --git a/animator/SkDrawGroup.h b/animator/SkDrawGroup.h
new file mode 100644
index 00000000..336040c1
--- /dev/null
+++ b/animator/SkDrawGroup.h
@@ -0,0 +1,72 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawGroup_DEFINED
+#define SkDrawGroup_DEFINED
+
+#include "SkDrawable.h"
+#include "SkIntArray.h"
+#include "SkMemberInfo.h"
+
+class SkGroup : public SkDrawable { //interface for schema element <g>
+public:
+ DECLARE_MEMBER_INFO(Group);
+ SkGroup();
+ virtual ~SkGroup();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool contains(SkDisplayable* );
+ SkGroup* copy();
+ SkBool copySet(int index);
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual bool doEvent(SkDisplayEvent::Kind , SkEventState* state );
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+ virtual void dumpDrawables(SkAnimateMaker* );
+ virtual void dumpEvents();
+#endif
+ int findGroup(SkDrawable* drawable, SkTDDrawableArray** list,
+ SkGroup** parent, SkGroup** found, SkTDDrawableArray** grandList);
+ virtual bool enable(SkAnimateMaker& );
+ SkTDDrawableArray* getChildren() { return &fChildren; }
+ SkGroup* getOriginal() { return fOriginal; }
+ virtual bool hasEnable() const;
+ virtual void initialize();
+ SkBool isACopy() { return fOriginal != NULL; }
+ void markCopyClear(int index);
+ void markCopySet(int index);
+ void markCopySize(int index);
+ bool markedForDelete(int index) const { return (fCopies[index >> 5] & 1 << (index & 0x1f)) == 0; }
+ void reset();
+ bool resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* );
+ virtual void setSteps(int steps);
+#ifdef SK_DEBUG
+ virtual void validate();
+#endif
+protected:
+ bool ifCondition(SkAnimateMaker& maker, SkDrawable* drawable,
+ SkString& conditionString);
+ SkString condition;
+ SkString enableCondition;
+ SkTDDrawableArray fChildren;
+ SkTDDrawableArray* fParentList;
+ SkTDIntArray fCopies;
+ SkGroup* fOriginal;
+private:
+ typedef SkDrawable INHERITED;
+};
+
+class SkSave: public SkGroup {
+ DECLARE_MEMBER_INFO(Save);
+ virtual bool draw(SkAnimateMaker& );
+private:
+ typedef SkGroup INHERITED;
+};
+
+#endif // SkDrawGroup_DEFINED
diff --git a/animator/SkDrawLine.cpp b/animator/SkDrawLine.cpp
new file mode 100644
index 00000000..d0ae7d92
--- /dev/null
+++ b/animator/SkDrawLine.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * 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 "SkDrawLine.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkLine::fInfo[] = {
+ SK_MEMBER(x1, Float),
+ SK_MEMBER(x2, Float),
+ SK_MEMBER(y1, Float),
+ SK_MEMBER(y2, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkLine);
+
+SkLine::SkLine() : x1(0), x2(0), y1(0), y2(0) {
+}
+
+bool SkLine::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawLine(x1, y1, x2, y2, *maker.fPaint);
+ return false;
+}
diff --git a/animator/SkDrawLine.h b/animator/SkDrawLine.h
new file mode 100644
index 00000000..b287802e
--- /dev/null
+++ b/animator/SkDrawLine.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawLine_DEFINED
+#define SkDrawLine_DEFINED
+
+#include "SkBoundable.h"
+#include "SkMemberInfo.h"
+
+class SkLine : public SkBoundable {
+ DECLARE_MEMBER_INFO(Line);
+ SkLine();
+ virtual bool draw(SkAnimateMaker& );
+private:
+ SkScalar x1;
+ SkScalar x2;
+ SkScalar y1;
+ SkScalar y2;
+ typedef SkBoundable INHERITED;
+};
+
+#endif // SkDrawLine_DEFINED
diff --git a/animator/SkDrawMatrix.cpp b/animator/SkDrawMatrix.cpp
new file mode 100644
index 00000000..80b04c1b
--- /dev/null
+++ b/animator/SkDrawMatrix.cpp
@@ -0,0 +1,268 @@
+
+/*
+ * 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 "SkDrawMatrix.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkParse.h"
+#include "SkMatrixParts.h"
+#include "SkScript.h"
+#include "SkTypedArray.h"
+
+enum SkDrawMatrix_Properties {
+ SK_PROPERTY(perspectX),
+ SK_PROPERTY(perspectY),
+ SK_PROPERTY(rotate),
+ SK_PROPERTY(scale),
+ SK_PROPERTY(scaleX),
+ SK_PROPERTY(scaleY),
+ SK_PROPERTY(skewX),
+ SK_PROPERTY(skewY),
+ SK_PROPERTY(translate),
+ SK_PROPERTY(translateX),
+ SK_PROPERTY(translateY)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawMatrix::fInfo[] = {
+ SK_MEMBER_ARRAY(matrix, Float),
+ SK_MEMBER_PROPERTY(perspectX, Float),
+ SK_MEMBER_PROPERTY(perspectY, Float),
+ SK_MEMBER_PROPERTY(rotate, Float),
+ SK_MEMBER_PROPERTY(scale, Float),
+ SK_MEMBER_PROPERTY(scaleX, Float),
+ SK_MEMBER_PROPERTY(scaleY, Float),
+ SK_MEMBER_PROPERTY(skewX, Float),
+ SK_MEMBER_PROPERTY(skewY, Float),
+ SK_MEMBER_PROPERTY(translate, Point),
+ SK_MEMBER_PROPERTY(translateX, Float),
+ SK_MEMBER_PROPERTY(translateY, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawMatrix);
+
+SkDrawMatrix::SkDrawMatrix() : fChildHasID(false), fDirty(false) {
+ fConcat.reset();
+ fMatrix.reset();
+}
+
+SkDrawMatrix::~SkDrawMatrix() {
+ for (SkMatrixPart** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+}
+
+bool SkDrawMatrix::addChild(SkAnimateMaker& maker, SkDisplayable* child) {
+ SkASSERT(child && child->isMatrixPart());
+ SkMatrixPart* part = (SkMatrixPart*) child;
+ *fParts.append() = part;
+ if (part->add())
+ maker.setErrorCode(SkDisplayXMLParserError::kErrorAddingToMatrix);
+ return true;
+}
+
+bool SkDrawMatrix::childrenNeedDisposing() const {
+ return false;
+}
+
+SkDisplayable* SkDrawMatrix::deepCopy(SkAnimateMaker* maker) {
+ SkDrawMatrix* copy = (SkDrawMatrix*)
+ SkDisplayType::CreateInstance(maker, SkType_Matrix);
+ SkASSERT(fParts.count() == 0);
+ copy->fMatrix = fMatrix;
+ copy->fConcat = fConcat;
+ return copy;
+}
+
+void SkDrawMatrix::dirty() {
+ fDirty = true;
+}
+
+bool SkDrawMatrix::draw(SkAnimateMaker& maker) {
+ SkMatrix& concat = getMatrix();
+ maker.fCanvas->concat(concat);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawMatrix::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ if (fMatrix.isIdentity()) {
+ SkDebugf("matrix=\"identity\"/>\n");
+ return;
+ }
+ SkScalar result;
+ result = fMatrix[SkMatrix::kMScaleX];
+ if (result != SK_Scalar1)
+ SkDebugf("sx=\"%g\" ", SkScalarToFloat(result));
+ result = fMatrix.getScaleY();
+ if (result != SK_Scalar1)
+ SkDebugf("sy=\"%g\" ", SkScalarToFloat(result));
+ result = fMatrix.getSkewX();
+ if (result)
+ SkDebugf("skew-x=\"%g\" ", SkScalarToFloat(result));
+ result = fMatrix.getSkewY();
+ if (result)
+ SkDebugf("skew-y=\"%g\" ", SkScalarToFloat(result));
+ result = fMatrix.getTranslateX();
+ if (result)
+ SkDebugf("tx=\"%g\" ", SkScalarToFloat(result));
+ result = fMatrix.getTranslateY();
+ if (result)
+ SkDebugf("ty=\"%g\" ", SkScalarToFloat(result));
+ result = SkPerspToScalar(fMatrix.getPerspX());
+ if (result)
+ SkDebugf("perspect-x=\"%g\" ", SkScalarToFloat(result));
+ result = SkPerspToScalar(fMatrix.getPerspY());
+ if (result)
+ SkDebugf("perspect-y=\"%g\" ", SkScalarToFloat(result));
+ SkDebugf("/>\n");
+}
+#endif
+
+SkMatrix& SkDrawMatrix::getMatrix() {
+ if (fDirty == false)
+ return fConcat;
+ fMatrix.reset();
+ for (SkMatrixPart** part = fParts.begin(); part < fParts.end(); part++) {
+ (*part)->add();
+ fConcat = fMatrix;
+ }
+ fDirty = false;
+ return fConcat;
+}
+
+bool SkDrawMatrix::getProperty(int index, SkScriptValue* value) const {
+ value->fType = SkType_Float;
+ SkScalar result;
+ switch (index) {
+ case SK_PROPERTY(perspectX):
+ result = fMatrix.getPerspX();
+ break;
+ case SK_PROPERTY(perspectY):
+ result = fMatrix.getPerspY();
+ break;
+ case SK_PROPERTY(scaleX):
+ result = fMatrix.getScaleX();
+ break;
+ case SK_PROPERTY(scaleY):
+ result = fMatrix.getScaleY();
+ break;
+ case SK_PROPERTY(skewX):
+ result = fMatrix.getSkewX();
+ break;
+ case SK_PROPERTY(skewY):
+ result = fMatrix.getSkewY();
+ break;
+ case SK_PROPERTY(translateX):
+ result = fMatrix.getTranslateX();
+ break;
+ case SK_PROPERTY(translateY):
+ result = fMatrix.getTranslateY();
+ break;
+ default:
+// SkASSERT(0);
+ return false;
+ }
+ value->fOperand.fScalar = result;
+ return true;
+}
+
+void SkDrawMatrix::initialize() {
+ fConcat = fMatrix;
+}
+
+void SkDrawMatrix::onEndElement(SkAnimateMaker& ) {
+ if (matrix.count() > 0) {
+ SkScalar* vals = matrix.begin();
+ fMatrix.setScaleX(vals[0]);
+ fMatrix.setSkewX(vals[1]);
+ fMatrix.setTranslateX(vals[2]);
+ fMatrix.setSkewY(vals[3]);
+ fMatrix.setScaleY(vals[4]);
+ fMatrix.setTranslateY(vals[5]);
+ fMatrix.setPerspX(SkScalarToPersp(vals[6]));
+ fMatrix.setPerspY(SkScalarToPersp(vals[7]));
+// fMatrix.setPerspW(SkScalarToPersp(vals[8]));
+ goto setConcat;
+ }
+ if (fChildHasID == false) {
+ {
+ for (SkMatrixPart** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+ }
+ fParts.reset();
+setConcat:
+ fConcat = fMatrix;
+ fDirty = false;
+ }
+}
+
+void SkDrawMatrix::setChildHasID() {
+ fChildHasID = true;
+}
+
+bool SkDrawMatrix::setProperty(int index, SkScriptValue& scriptValue) {
+ SkScalar number = scriptValue.fOperand.fScalar;
+ switch (index) {
+ case SK_PROPERTY(translate):
+ // SkScalar xy[2];
+ SkASSERT(scriptValue.fType == SkType_Array);
+ SkASSERT(scriptValue.fOperand.fArray->getType() == SkType_Float);
+ SkASSERT(scriptValue.fOperand.fArray->count() == 2);
+ // SkParse::FindScalars(scriptValue.fOperand.fString->c_str(), xy, 2);
+ fMatrix.setTranslateX((*scriptValue.fOperand.fArray)[0].fScalar);
+ fMatrix.setTranslateY((*scriptValue.fOperand.fArray)[1].fScalar);
+ return true;
+ case SK_PROPERTY(perspectX):
+ fMatrix.setPerspX(SkScalarToPersp((number)));
+ break;
+ case SK_PROPERTY(perspectY):
+ fMatrix.setPerspY(SkScalarToPersp((number)));
+ break;
+ case SK_PROPERTY(rotate): {
+ SkMatrix temp;
+ temp.setRotate(number, 0, 0);
+ fMatrix.setScaleX(temp.getScaleX());
+ fMatrix.setScaleY(temp.getScaleY());
+ fMatrix.setSkewX(temp.getSkewX());
+ fMatrix.setSkewY(temp.getSkewY());
+ } break;
+ case SK_PROPERTY(scale):
+ fMatrix.setScaleX(number);
+ fMatrix.setScaleY(number);
+ break;
+ case SK_PROPERTY(scaleX):
+ fMatrix.setScaleX(number);
+ break;
+ case SK_PROPERTY(scaleY):
+ fMatrix.setScaleY(number);
+ break;
+ case SK_PROPERTY(skewX):
+ fMatrix.setSkewX(number);
+ break;
+ case SK_PROPERTY(skewY):
+ fMatrix.setSkewY(number);
+ break;
+ case SK_PROPERTY(translateX):
+ fMatrix.setTranslateX(number);
+ break;
+ case SK_PROPERTY(translateY):
+ fMatrix.setTranslateY(number);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ fConcat = fMatrix;
+ return true;
+}
diff --git a/animator/SkDrawMatrix.h b/animator/SkDrawMatrix.h
new file mode 100644
index 00000000..e3c389a2
--- /dev/null
+++ b/animator/SkDrawMatrix.h
@@ -0,0 +1,74 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawMatrix_DEFINED
+#define SkDrawMatrix_DEFINED
+
+#include "SkDrawable.h"
+#include "SkMatrix.h"
+#include "SkMemberInfo.h"
+#include "SkIntArray.h"
+
+class SkMatrixPart;
+
+class SkDrawMatrix : public SkDrawable {
+ DECLARE_DRAW_MEMBER_INFO(Matrix);
+ SkDrawMatrix();
+ virtual ~SkDrawMatrix();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ virtual bool childrenNeedDisposing() const;
+ virtual void dirty();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ SkMatrix& getMatrix();
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual void initialize();
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual void setChildHasID();
+ virtual bool setProperty(int index, SkScriptValue& );
+
+ void concat(SkMatrix& inMatrix) {
+ fConcat.preConcat(inMatrix);
+ }
+
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+
+
+ void rotate(SkScalar degrees, SkPoint& center) {
+ fMatrix.preRotate(degrees, center.fX, center.fY);
+ }
+
+ void set(SkMatrix& src) {
+ fMatrix.preConcat(src);
+ }
+
+ void scale(SkScalar scaleX, SkScalar scaleY, SkPoint& center) {
+ fMatrix.preScale(scaleX, scaleY, center.fX, center.fY);
+ }
+
+ void skew(SkScalar skewX, SkScalar skewY, SkPoint& center) {
+ fMatrix.preSkew(skewX, skewY, center.fX, center.fY);
+ }
+
+ void translate(SkScalar x, SkScalar y) {
+ fMatrix.preTranslate(x, y);
+ }
+private:
+ SkTDScalarArray matrix;
+ SkMatrix fConcat;
+ SkMatrix fMatrix;
+ SkTDMatrixPartArray fParts;
+ SkBool8 fChildHasID;
+ SkBool8 fDirty;
+ typedef SkDrawable INHERITED;
+};
+
+#endif // SkDrawMatrix_DEFINED
diff --git a/animator/SkDrawOval.cpp b/animator/SkDrawOval.cpp
new file mode 100644
index 00000000..e5efa7d5
--- /dev/null
+++ b/animator/SkDrawOval.cpp
@@ -0,0 +1,28 @@
+
+/*
+ * 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 "SkDrawOval.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkOval::fInfo[] = {
+ SK_MEMBER_INHERITED,
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkOval);
+
+bool SkOval::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawOval(fRect, *maker.fPaint);
+ return false;
+}
diff --git a/animator/SkDrawOval.h b/animator/SkDrawOval.h
new file mode 100644
index 00000000..3c09e0fa
--- /dev/null
+++ b/animator/SkDrawOval.h
@@ -0,0 +1,22 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawOval_DEFINED
+#define SkDrawOval_DEFINED
+
+#include "SkDrawRectangle.h"
+
+class SkOval : public SkDrawRect {
+ DECLARE_MEMBER_INFO(Oval);
+ virtual bool draw(SkAnimateMaker& );
+private:
+ typedef SkDrawRect INHERITED;
+};
+
+#endif // SkDrawOval_DEFINED
diff --git a/animator/SkDrawPaint.cpp b/animator/SkDrawPaint.cpp
new file mode 100644
index 00000000..22d99e4f
--- /dev/null
+++ b/animator/SkDrawPaint.cpp
@@ -0,0 +1,269 @@
+
+/*
+ * 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 "SkDrawPaint.h"
+#include "SkAnimateMaker.h"
+#include "SkDrawColor.h"
+#include "SkDrawShader.h"
+#include "SkMaskFilter.h"
+#include "SkPaintParts.h"
+#include "SkPathEffect.h"
+
+enum SkPaint_Functions {
+ SK_FUNCTION(measureText)
+};
+
+enum SkPaint_Properties {
+ SK_PROPERTY(ascent),
+ SK_PROPERTY(descent)
+};
+
+// !!! in the future, this could be compiled by build-condensed-info into an array of parameters
+// with a lookup table to find the first parameter -- for now, it is iteratively searched through
+const SkFunctionParamType SkDrawPaint::fFunctionParameters[] = {
+ (SkFunctionParamType) SkType_String,
+ (SkFunctionParamType) 0 // terminator for parameter list (there may be multiple parameter lists)
+};
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawPaint::fInfo[] = {
+ SK_MEMBER(antiAlias, Boolean),
+ SK_MEMBER_PROPERTY(ascent, Float),
+ SK_MEMBER(color, Color),
+ SK_MEMBER_PROPERTY(descent, Float),
+ SK_MEMBER(fakeBold, Boolean),
+ SK_MEMBER(filterBitmap, Boolean),
+ SK_MEMBER(linearText, Boolean),
+ SK_MEMBER(maskFilter, MaskFilter),
+ SK_MEMBER_FUNCTION(measureText, Float),
+ SK_MEMBER(pathEffect, PathEffect),
+ SK_MEMBER(shader, Shader),
+ SK_MEMBER(strikeThru, Boolean),
+ SK_MEMBER(stroke, Boolean),
+ SK_MEMBER(strokeCap, Cap),
+ SK_MEMBER(strokeJoin, Join),
+ SK_MEMBER(strokeMiter, Float),
+ SK_MEMBER(strokeWidth, Float),
+ SK_MEMBER(style, Style),
+ SK_MEMBER(textAlign, Align),
+ SK_MEMBER(textScaleX, Float),
+ SK_MEMBER(textSize, Float),
+ SK_MEMBER(textSkewX, Float),
+ SK_MEMBER(typeface, Typeface),
+ SK_MEMBER(underline, Boolean),
+ SK_MEMBER(xfermode, Xfermode)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawPaint);
+
+SkDrawPaint::SkDrawPaint() : antiAlias(-1), color(NULL), fakeBold(-1), filterBitmap(-1),
+ linearText(-1), maskFilter((SkDrawMaskFilter*) -1), pathEffect((SkDrawPathEffect*) -1),
+ shader((SkDrawShader*) -1), strikeThru(-1), stroke(-1),
+ strokeCap((SkPaint::Cap) -1), strokeJoin((SkPaint::Join) -1), strokeMiter(SK_ScalarNaN),
+ strokeWidth(SK_ScalarNaN), style((SkPaint::Style) -1),
+ textAlign((SkPaint::Align) -1), textScaleX(SK_ScalarNaN), textSize(SK_ScalarNaN),
+ textSkewX(SK_ScalarNaN), typeface((SkDrawTypeface*) -1),
+ underline(-1), xfermode((SkXfermode::Mode) -1), fOwnsColor(false), fOwnsMaskFilter(false),
+ fOwnsPathEffect(false), fOwnsShader(false), fOwnsTypeface(false) {
+}
+
+SkDrawPaint::~SkDrawPaint() {
+ if (fOwnsColor)
+ delete color;
+ if (fOwnsMaskFilter)
+ delete maskFilter;
+ if (fOwnsPathEffect)
+ delete pathEffect;
+ if (fOwnsShader)
+ delete shader;
+ if (fOwnsTypeface)
+ delete typeface;
+}
+
+bool SkDrawPaint::add(SkAnimateMaker* maker, SkDisplayable* child) {
+ SkASSERT(child && child->isPaintPart());
+ SkPaintPart* part = (SkPaintPart*) child;
+ if (part->add() && maker)
+ maker->setErrorCode(SkDisplayXMLParserError::kErrorAddingToPaint);
+ return true;
+}
+
+SkDisplayable* SkDrawPaint::deepCopy(SkAnimateMaker* maker) {
+ SkDrawColor* tempColor = color;
+ color = NULL;
+ SkDrawPaint* copy = (SkDrawPaint*) INHERITED::deepCopy(maker);
+ color = tempColor;
+ tempColor = (SkDrawColor*) color->deepCopy(maker);
+ tempColor->setParent(copy);
+ tempColor->add();
+ copy->fOwnsColor = true;
+ return copy;
+}
+
+bool SkDrawPaint::draw(SkAnimateMaker& maker) {
+ SkPaint* paint = maker.fPaint;
+ setupPaint(paint);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawPaint::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpAttrs(maker);
+ bool closedYet = false;
+ SkDisplayList::fIndent +=4;
+ //should i say if (maskFilter && ...?
+ if (maskFilter != (SkDrawMaskFilter*)-1) {
+ SkDebugf(">\n");
+ maskFilter->dump(maker);
+ closedYet = true;
+ }
+ if (pathEffect != (SkDrawPathEffect*) -1) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ pathEffect->dump(maker);
+ }
+ if (fOwnsTypeface) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ typeface->dump(maker);
+ }
+ SkDisplayList::fIndent -= 4;
+ dumpChildren(maker, closedYet);
+}
+#endif
+
+void SkDrawPaint::executeFunction(SkDisplayable* target, int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* scriptValue) {
+ if (scriptValue == NULL)
+ return;
+ SkASSERT(target == this);
+ switch (index) {
+ case SK_FUNCTION(measureText): {
+ SkASSERT(parameters.count() == 1);
+ SkASSERT(type == SkType_Float);
+ SkPaint paint;
+ setupPaint(&paint);
+ scriptValue->fType = SkType_Float;
+ SkASSERT(parameters[0].fType == SkType_String);
+ scriptValue->fOperand.fScalar = paint.measureText(parameters[0].fOperand.fString->c_str(),
+ parameters[0].fOperand.fString->size());
+// SkDebugf("measureText: %s = %g\n", parameters[0].fOperand.fString->c_str(),
+// scriptValue->fOperand.fScalar / 65536.0f);
+ } break;
+ default:
+ SkASSERT(0);
+ }
+}
+
+const SkFunctionParamType* SkDrawPaint::getFunctionsParameters() {
+ return fFunctionParameters;
+}
+
+bool SkDrawPaint::getProperty(int index, SkScriptValue* value) const {
+ SkPaint::FontMetrics metrics;
+ SkPaint paint;
+ setupPaint(&paint);
+ paint.getFontMetrics(&metrics);
+ switch (index) {
+ case SK_PROPERTY(ascent):
+ value->fOperand.fScalar = metrics.fAscent;
+ break;
+ case SK_PROPERTY(descent):
+ value->fOperand.fScalar = metrics.fDescent;
+ break;
+ // should consider returning fLeading as well (or roll it into ascent/descent somehow
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ value->fType = SkType_Float;
+ return true;
+}
+
+bool SkDrawPaint::resolveIDs(SkAnimateMaker& maker, SkDisplayable* origDisp, SkApply* ) {
+ SkASSERT(origDisp->isPaint());
+ SkDrawPaint* original = (SkDrawPaint*) origDisp;
+ if (fOwnsColor && maker.resolveID(color, original->color) == false)
+ return true;
+ if (fOwnsMaskFilter && maker.resolveID(maskFilter, original->maskFilter) == false)
+ return true;
+ if (fOwnsPathEffect && maker.resolveID(pathEffect, original->pathEffect) == false)
+ return true;
+ if (fOwnsShader && maker.resolveID(shader, original->shader) == false)
+ return true;
+ if (fOwnsTypeface && maker.resolveID(typeface, original->typeface) == false)
+ return true;
+ return false; // succeeded
+}
+
+void SkDrawPaint::setupPaint(SkPaint* paint) const {
+ if (antiAlias != -1)
+ paint->setAntiAlias(SkToBool(antiAlias));
+ if (color != NULL)
+ paint->setColor(color->getColor());
+ if (fakeBold != -1)
+ paint->setFakeBoldText(SkToBool(fakeBold));
+ if (filterBitmap != -1)
+ paint->setFilterBitmap(SkToBool(filterBitmap));
+ // stroke is legacy; style setting if present overrides stroke
+ if (stroke != -1)
+ paint->setStyle(SkToBool(stroke) ? SkPaint::kStroke_Style : SkPaint::kFill_Style);
+ if (style != -1)
+ paint->setStyle((SkPaint::Style) style);
+ if (linearText != -1)
+ paint->setLinearText(SkToBool(linearText));
+ if (maskFilter == NULL)
+ paint->setMaskFilter(NULL);
+ else if (maskFilter != (SkDrawMaskFilter*) -1)
+ SkSafeUnref(paint->setMaskFilter(maskFilter->getMaskFilter()));
+ if (pathEffect == NULL)
+ paint->setPathEffect(NULL);
+ else if (pathEffect != (SkDrawPathEffect*) -1)
+ SkSafeUnref(paint->setPathEffect(pathEffect->getPathEffect()));
+ if (shader == NULL)
+ paint->setShader(NULL);
+ else if (shader != (SkDrawShader*) -1)
+ SkSafeUnref(paint->setShader(shader->getShader()));
+ if (strikeThru != -1)
+ paint->setStrikeThruText(SkToBool(strikeThru));
+ if (strokeCap != -1)
+ paint->setStrokeCap((SkPaint::Cap) strokeCap);
+ if (strokeJoin != -1)
+ paint->setStrokeJoin((SkPaint::Join) strokeJoin);
+ if (SkScalarIsNaN(strokeMiter) == false)
+ paint->setStrokeMiter(strokeMiter);
+ if (SkScalarIsNaN(strokeWidth) == false)
+ paint->setStrokeWidth(strokeWidth);
+ if (textAlign != -1)
+ paint->setTextAlign((SkPaint::Align) textAlign);
+ if (SkScalarIsNaN(textScaleX) == false)
+ paint->setTextScaleX(textScaleX);
+ if (SkScalarIsNaN(textSize) == false)
+ paint->setTextSize(textSize);
+ if (SkScalarIsNaN(textSkewX) == false)
+ paint->setTextSkewX(textSkewX);
+ if (typeface == NULL)
+ paint->setTypeface(NULL);
+ else if (typeface != (SkDrawTypeface*) -1)
+ SkSafeUnref(paint->setTypeface(typeface->getTypeface()));
+ if (underline != -1)
+ paint->setUnderlineText(SkToBool(underline));
+ if (xfermode != -1)
+ paint->setXfermodeMode((SkXfermode::Mode) xfermode);
+}
diff --git a/animator/SkDrawPaint.h b/animator/SkDrawPaint.h
new file mode 100644
index 00000000..3caf6b6e
--- /dev/null
+++ b/animator/SkDrawPaint.h
@@ -0,0 +1,79 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawPaint_DEFINED
+#define SkDrawPaint_DEFINED
+
+#include "SkDrawable.h"
+#include "SkIntArray.h"
+#include "SkMemberInfo.h"
+#include "SkPaint.h"
+#include "SkXfermode.h"
+
+class SkDrawMaskFilter;
+class SkDrawPathEffect;
+class SkDrawShader;
+class SkTransferMode;
+class SkDrawTypeface;
+
+class SkDrawPaint : public SkDrawable {
+ DECLARE_DRAW_MEMBER_INFO(Paint);
+ SkDrawPaint();
+ virtual ~SkDrawPaint();
+ virtual bool add(SkAnimateMaker* , SkDisplayable* child);
+ virtual SkDisplayable* deepCopy(SkAnimateMaker* );
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void executeFunction(SkDisplayable* target, int index,
+ SkTDArray<SkScriptValue>& parameters, SkDisplayTypes type,
+ SkScriptValue* );
+ virtual const SkFunctionParamType* getFunctionsParameters();
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool resolveIDs(SkAnimateMaker& maker, SkDisplayable* original, SkApply* apply);
+protected:
+ static const SkFunctionParamType fFunctionParameters[];
+ void setupPaint(SkPaint* paint) const;
+public:
+ SkBool antiAlias;
+ SkDrawColor* color;
+ SkBool fakeBold;
+ SkBool filterBitmap;
+ SkBool linearText;
+ SkDrawMaskFilter* maskFilter;
+ SkDrawPathEffect* pathEffect;
+ SkDrawShader* shader;
+ SkBool strikeThru;
+ SkBool stroke;
+ int /*SkPaint::Cap*/ strokeCap;
+ int /*SkPaint::Join */ strokeJoin;
+ SkScalar strokeMiter;
+ SkScalar strokeWidth;
+ int /* SkPaint::Style */ style;
+ int /* SkPaint::Align */ textAlign;
+ SkScalar textScaleX;
+ SkScalar textSize;
+ SkScalar textSkewX;
+ SkDrawTypeface* typeface;
+ SkBool underline;
+ int /*SkXfermode::Modes*/ xfermode;
+ SkBool8 fOwnsColor;
+ SkBool8 fOwnsMaskFilter;
+ SkBool8 fOwnsPathEffect;
+ SkBool8 fOwnsShader;
+ SkBool8 fOwnsTransferMode;
+ SkBool8 fOwnsTypeface;
+private:
+ typedef SkDrawable INHERITED;
+ friend class SkTextToPath;
+ friend class SkSaveLayer;
+};
+
+#endif // SkDrawPaint_DEFINED
diff --git a/animator/SkDrawPath.cpp b/animator/SkDrawPath.cpp
new file mode 100644
index 00000000..f62f7c04
--- /dev/null
+++ b/animator/SkDrawPath.cpp
@@ -0,0 +1,220 @@
+
+/*
+ * 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 "SkDrawPath.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkMath.h"
+#include "SkMatrixParts.h"
+#include "SkPaint.h"
+#include "SkPathParts.h"
+
+enum SkPath_Properties {
+ SK_PROPERTY(fillType),
+ SK_PROPERTY(length)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawPath::fInfo[] = {
+ SK_MEMBER(d, String),
+ SK_MEMBER_PROPERTY(fillType, FillType),
+ SK_MEMBER_PROPERTY(length, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawPath);
+
+SkDrawPath::SkDrawPath()
+{
+ fParent = NULL;
+ fLength = SK_ScalarNaN;
+ fChildHasID = false;
+ fDirty = false;
+}
+
+SkDrawPath::~SkDrawPath() {
+ for (SkPathPart** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+}
+
+bool SkDrawPath::addChild(SkAnimateMaker& maker, SkDisplayable* child) {
+ SkASSERT(child && child->isPathPart());
+ SkPathPart* part = (SkPathPart*) child;
+ *fParts.append() = part;
+ if (part->add())
+ maker.setErrorCode(SkDisplayXMLParserError::kErrorAddingToPath);
+ fDirty = false;
+ return true;
+}
+
+bool SkDrawPath::childrenNeedDisposing() const {
+ return false;
+}
+
+void SkDrawPath::dirty() {
+ fDirty = true;
+ fLength = SK_ScalarNaN;
+ if (fParent)
+ fParent->dirty();
+}
+
+bool SkDrawPath::draw(SkAnimateMaker& maker) {
+ SkPath& path = getPath();
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawPath(path, *maker.fPaint);
+ return false;
+}
+
+SkDisplayable* SkDrawPath::getParent() const {
+ return fParent;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawPath::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpAttrs(maker);
+ bool closedYet = false;
+ SkDisplayList::fIndent += 4;
+ for(SkPathPart** part = fParts.begin(); part < fParts.end(); part++) {
+ if (closedYet == false) {
+ SkDebugf(">\n");
+ closedYet = true;
+ }
+ (*part)->dump(maker);
+ }
+ SkDisplayList::fIndent -= 4;
+ if (closedYet)
+ dumpEnd(maker);
+ else
+ SkDebugf("/>\n");
+}
+#endif
+
+SkPath& SkDrawPath::getPath() {
+ if (fDirty == false)
+ return fPath;
+ if (d.size() > 0)
+ {
+ parseSVG();
+ d.reset();
+ }
+ else
+ {
+ fPath.reset();
+ for (SkPathPart** part = fParts.begin(); part < fParts.end(); part++)
+ (*part)->add();
+ }
+ fDirty = false;
+ return fPath;
+}
+
+void SkDrawPath::onEndElement(SkAnimateMaker& ) {
+ if (d.size() > 0) {
+ parseSVG();
+ d.reset();
+ fDirty = false;
+ return;
+ }
+ if (fChildHasID == false) {
+ for (SkPathPart** part = fParts.begin(); part < fParts.end(); part++)
+ delete *part;
+ fParts.reset();
+ fDirty = false;
+ }
+}
+
+bool SkDrawPath::getProperty(int index, SkScriptValue* value) const {
+ switch (index) {
+ case SK_PROPERTY(length):
+ if (SkScalarIsNaN(fLength)) {
+ const SkPath& path = ((SkDrawPath*) this)->getPath();
+ SkPathMeasure pathMeasure(path, false);
+ fLength = pathMeasure.getLength();
+ }
+ value->fType = SkType_Float;
+ value->fOperand.fScalar = fLength;
+ break;
+ case SK_PROPERTY(fillType):
+ value->fType = SkType_FillType;
+ value->fOperand.fS32 = (int) fPath.getFillType();
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+void SkDrawPath::setChildHasID() {
+ fChildHasID = true;
+}
+
+bool SkDrawPath::setParent(SkDisplayable* parent) {
+ fParent = parent;
+ return false;
+}
+
+bool SkDrawPath::setProperty(int index, SkScriptValue& value)
+{
+ switch (index) {
+ case SK_PROPERTY(fillType):
+ SkASSERT(value.fType == SkType_FillType);
+ SkASSERT(value.fOperand.fS32 >= SkPath::kWinding_FillType &&
+ value.fOperand.fS32 <= SkPath::kEvenOdd_FillType);
+ fPath.setFillType((SkPath::FillType) value.fOperand.fS32);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkPolyline::fInfo[] = {
+ SK_MEMBER_ARRAY(points, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkPolyline);
+
+bool SkPolyline::addChild(SkAnimateMaker& , SkDisplayable*) {
+ return false;
+}
+
+void SkPolyline::onEndElement(SkAnimateMaker& maker) {
+ INHERITED::onEndElement(maker);
+ if (points.count() <= 0)
+ return;
+ fPath.reset();
+ fPath.moveTo(points[0], points[1]);
+ int count = points.count();
+ for (int index = 2; index < count; index += 2)
+ fPath.lineTo(points[index], points[index+1]);
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkPolygon::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkPolygon);
+
+void SkPolygon::onEndElement(SkAnimateMaker& maker) {
+ INHERITED::onEndElement(maker);
+ fPath.close();
+}
diff --git a/animator/SkDrawPath.h b/animator/SkDrawPath.h
new file mode 100644
index 00000000..9c4d3059
--- /dev/null
+++ b/animator/SkDrawPath.h
@@ -0,0 +1,69 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawPath_DEFINED
+#define SkDrawPath_DEFINED
+
+#include "SkBoundable.h"
+#include "SkIntArray.h"
+#include "SkMemberInfo.h"
+#include "SkPath.h"
+
+class SkDrawPath : public SkBoundable {
+ DECLARE_DRAW_MEMBER_INFO(Path);
+ SkDrawPath();
+ virtual ~SkDrawPath();
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable* child) SK_OVERRIDE;
+ bool childHasID() { return SkToBool(fChildHasID); }
+ virtual bool childrenNeedDisposing() const;
+ virtual void dirty();
+ virtual bool draw(SkAnimateMaker& );
+ virtual SkDisplayable* getParent() const;
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ SkPath& getPath();
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool setProperty(int index, SkScriptValue& value);
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual void setChildHasID();
+ virtual bool setParent(SkDisplayable* parent);
+ virtual bool isPath() const { return true; }
+public:
+ SkPath fPath;
+protected:
+ void parseSVG();
+ SkString d;
+ SkTDPathPartArray fParts;
+ mutable SkScalar fLength;
+ SkDisplayable* fParent; // SkPolyToPoly or SkFromPath, for instance
+ SkBool8 fChildHasID;
+ SkBool8 fDirty;
+private:
+ typedef SkBoundable INHERITED;
+};
+
+class SkPolyline : public SkDrawPath {
+ DECLARE_MEMBER_INFO(Polyline);
+ virtual bool addChild(SkAnimateMaker& , SkDisplayable*) SK_OVERRIDE;
+ virtual void onEndElement(SkAnimateMaker& );
+protected:
+ SkTDScalarArray points;
+private:
+ typedef SkDrawPath INHERITED;
+};
+
+class SkPolygon : public SkPolyline {
+ DECLARE_MEMBER_INFO(Polygon);
+ virtual void onEndElement(SkAnimateMaker& );
+private:
+ typedef SkPolyline INHERITED;
+};
+
+#endif // SkDrawPath_DEFINED
diff --git a/animator/SkDrawPoint.cpp b/animator/SkDrawPoint.cpp
new file mode 100644
index 00000000..41a6be4b
--- /dev/null
+++ b/animator/SkDrawPoint.cpp
@@ -0,0 +1,44 @@
+
+/*
+ * 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 "SkDrawPoint.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo Sk_Point::fInfo[] = {
+ SK_MEMBER_ALIAS(x, fPoint.fX, Float),
+ SK_MEMBER_ALIAS(y, fPoint.fY, Float)
+};
+
+#endif
+
+DEFINE_NO_VIRTUALS_GET_MEMBER(Sk_Point);
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawPoint::fInfo[] = {
+ SK_MEMBER_ALIAS(x, fPoint.fX, Float),
+ SK_MEMBER_ALIAS(y, fPoint.fY, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawPoint);
+
+SkDrawPoint::SkDrawPoint() {
+ fPoint.set(0, 0);
+}
+
+void SkDrawPoint::getBounds(SkRect* rect ) {
+ rect->fLeft = rect->fRight = fPoint.fX;
+ rect->fTop = rect->fBottom = fPoint.fY;
+}
diff --git a/animator/SkDrawPoint.h b/animator/SkDrawPoint.h
new file mode 100644
index 00000000..0ecf4471
--- /dev/null
+++ b/animator/SkDrawPoint.h
@@ -0,0 +1,33 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawPoint_DEFINED
+#define SkDrawPoint_DEFINED
+
+#include "SkBoundable.h"
+#include "SkMemberInfo.h"
+#include "SkPoint.h"
+
+struct Sk_Point {
+ DECLARE_NO_VIRTUALS_MEMBER_INFO(_Point);
+ Sk_Point();
+private:
+ SkPoint fPoint;
+};
+
+class SkDrawPoint : public SkDisplayable {
+ DECLARE_MEMBER_INFO(DrawPoint);
+ SkDrawPoint();
+ virtual void getBounds(SkRect* );
+private:
+ SkPoint fPoint;
+ typedef SkDisplayable INHERITED;
+};
+
+#endif // SkDrawPoint_DEFINED
diff --git a/animator/SkDrawRectangle.cpp b/animator/SkDrawRectangle.cpp
new file mode 100644
index 00000000..c3fb7ff7
--- /dev/null
+++ b/animator/SkDrawRectangle.cpp
@@ -0,0 +1,142 @@
+
+/*
+ * 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 "SkDrawRectangle.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkMatrixParts.h"
+#include "SkPaint.h"
+#include "SkScript.h"
+
+enum SkRectangle_Properties {
+ SK_PROPERTY(height),
+ SK_PROPERTY(needsRedraw),
+ SK_PROPERTY(width)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawRect::fInfo[] = {
+ SK_MEMBER_ALIAS(bottom, fRect.fBottom, Float),
+ SK_MEMBER_PROPERTY(height, Float),
+ SK_MEMBER_ALIAS(left, fRect.fLeft, Float),
+ SK_MEMBER_PROPERTY(needsRedraw, Boolean),
+ SK_MEMBER_ALIAS(right, fRect.fRight, Float),
+ SK_MEMBER_ALIAS(top, fRect.fTop, Float),
+ SK_MEMBER_PROPERTY(width, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawRect);
+
+SkDrawRect::SkDrawRect() : fParent(NULL) {
+ fRect.setEmpty();
+}
+
+void SkDrawRect::dirty() {
+ if (fParent)
+ fParent->dirty();
+}
+
+bool SkDrawRect::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawRect(fRect, *maker.fPaint);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawRect::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("left=\"%g\" top=\"%g\" right=\"%g\" bottom=\"%g\" />\n",
+ SkScalarToFloat(fRect.fLeft), SkScalarToFloat(fRect.fTop), SkScalarToFloat(fRect.fRight),
+ SkScalarToFloat(fRect.fBottom));
+}
+#endif
+
+SkDisplayable* SkDrawRect::getParent() const {
+ return fParent;
+}
+
+bool SkDrawRect::getProperty(int index, SkScriptValue* value) const {
+ SkScalar result;
+ switch (index) {
+ case SK_PROPERTY(height):
+ result = fRect.height();
+ break;
+ case SK_PROPERTY(needsRedraw):
+ value->fType = SkType_Boolean;
+ value->fOperand.fS32 = fBounds.isEmpty() == false;
+ return true;
+ case SK_PROPERTY(width):
+ result = fRect.width();
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ value->fType = SkType_Float;
+ value->fOperand.fScalar = result;
+ return true;
+}
+
+
+bool SkDrawRect::setParent(SkDisplayable* parent) {
+ fParent = parent;
+ return false;
+}
+
+bool SkDrawRect::setProperty(int index, SkScriptValue& value) {
+ SkScalar scalar = value.fOperand.fScalar;
+ switch (index) {
+ case SK_PROPERTY(height):
+ SkASSERT(value.fType == SkType_Float);
+ fRect.fBottom = scalar + fRect.fTop;
+ return true;
+ case SK_PROPERTY(needsRedraw):
+ return false;
+ case SK_PROPERTY(width):
+ SkASSERT(value.fType == SkType_Float);
+ fRect.fRight = scalar + fRect.fLeft;
+ return true;
+ default:
+ SkASSERT(0);
+ }
+ return false;
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRoundRect::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(rx, Float),
+ SK_MEMBER(ry, Float),
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRoundRect);
+
+SkRoundRect::SkRoundRect() : rx(0), ry(0) {
+}
+
+bool SkRoundRect::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawRoundRect(fRect, rx, ry, *maker.fPaint);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkRoundRect::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("left=\"%g\" top=\"%g\" right=\"%g\" bottom=\"%g\" rx=\"%g\" ry=\"%g\" />\n",
+ SkScalarToFloat(fRect.fLeft), SkScalarToFloat(fRect.fTop), SkScalarToFloat(fRect.fRight),
+ SkScalarToFloat(fRect.fBottom), SkScalarToFloat(rx), SkScalarToFloat(ry));
+}
+#endif
diff --git a/animator/SkDrawRectangle.h b/animator/SkDrawRectangle.h
new file mode 100644
index 00000000..42af02b1
--- /dev/null
+++ b/animator/SkDrawRectangle.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawRectangle_DEFINED
+#define SkDrawRectangle_DEFINED
+
+#include "SkBoundable.h"
+#include "SkMemberInfo.h"
+#include "SkRect.h"
+
+class SkRectToRect;
+
+class SkDrawRect : public SkBoundable {
+ DECLARE_DRAW_MEMBER_INFO(Rect);
+ SkDrawRect();
+ virtual void dirty();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual SkDisplayable* getParent() const;
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool setParent(SkDisplayable* parent);
+ virtual bool setProperty(int index, SkScriptValue& );
+protected:
+ SkRect fRect;
+ SkDisplayable* fParent;
+private:
+ friend class SkDrawClip;
+ friend class SkRectToRect;
+ friend class SkSaveLayer;
+ typedef SkBoundable INHERITED;
+};
+
+class SkRoundRect : public SkDrawRect {
+ DECLARE_MEMBER_INFO(RoundRect);
+ SkRoundRect();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+protected:
+ SkScalar rx;
+ SkScalar ry;
+private:
+ typedef SkDrawRect INHERITED;
+};
+
+#endif // SkDrawRectangle_DEFINED
diff --git a/animator/SkDrawSaveLayer.cpp b/animator/SkDrawSaveLayer.cpp
new file mode 100644
index 00000000..4e97a044
--- /dev/null
+++ b/animator/SkDrawSaveLayer.cpp
@@ -0,0 +1,76 @@
+
+/*
+ * 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 "SkDrawSaveLayer.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkDrawPaint.h"
+#include "SkDrawRectangle.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkSaveLayer::fInfo[] = {
+ SK_MEMBER(bounds, Rect),
+ SK_MEMBER(paint, Paint)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkSaveLayer);
+
+SkSaveLayer::SkSaveLayer() : paint(NULL), bounds(NULL) {
+}
+
+SkSaveLayer::~SkSaveLayer(){
+}
+
+bool SkSaveLayer::draw(SkAnimateMaker& maker)
+{
+ if (!bounds) {
+ return false;
+ }
+ SkPaint* save = maker.fPaint;
+ //paint is an SkDrawPaint
+ if (paint)
+ {
+ SkPaint realPaint;
+ paint->setupPaint(&realPaint);
+ maker.fCanvas->saveLayer(&bounds->fRect, &realPaint, SkCanvas::kHasAlphaLayer_SaveFlag);
+ }
+ else
+ maker.fCanvas->saveLayer(&bounds->fRect, save, SkCanvas::kHasAlphaLayer_SaveFlag);
+ SkPaint local = SkPaint(*maker.fPaint);
+ maker.fPaint = &local;
+ bool result = INHERITED::draw(maker);
+ maker.fPaint = save;
+ maker.fCanvas->restore();
+ return result;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkSaveLayer::dump(SkAnimateMaker* maker)
+{
+ dumpBase(maker);
+ //would dump enabled be defined but not debug?
+#ifdef SK_DEBUG
+ if (paint)
+ SkDebugf("paint=\"%s\" ", paint->id);
+ if (bounds)
+ SkDebugf("bounds=\"%s\" ", bounds->id);
+#endif
+ dumpDrawables(maker);
+}
+#endif
+
+void SkSaveLayer::onEndElement(SkAnimateMaker& maker)
+{
+ if (!bounds)
+ maker.setErrorCode(SkDisplayXMLParserError::kSaveLayerNeedsBounds);
+ INHERITED::onEndElement(maker);
+}
diff --git a/animator/SkDrawSaveLayer.h b/animator/SkDrawSaveLayer.h
new file mode 100644
index 00000000..5c3e068e
--- /dev/null
+++ b/animator/SkDrawSaveLayer.h
@@ -0,0 +1,36 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawSaveLayer_DEFINED
+#define SkDrawSaveLayer_DEFINED
+
+#include "SkDrawGroup.h"
+#include "SkMemberInfo.h"
+
+class SkDrawPaint;
+class SkDrawRect;
+
+class SkSaveLayer : public SkGroup {
+ DECLARE_MEMBER_INFO(SaveLayer);
+ SkSaveLayer();
+ virtual ~SkSaveLayer();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void onEndElement(SkAnimateMaker& );
+protected:
+ SkDrawPaint* paint;
+ SkDrawRect* bounds;
+private:
+ typedef SkGroup INHERITED;
+
+};
+
+#endif //SkDrawSaveLayer_DEFINED
diff --git a/animator/SkDrawShader.cpp b/animator/SkDrawShader.cpp
new file mode 100644
index 00000000..e3aa4da0
--- /dev/null
+++ b/animator/SkDrawShader.cpp
@@ -0,0 +1,83 @@
+
+/*
+ * 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 "SkDrawShader.h"
+#include "SkDrawBitmap.h"
+#include "SkDrawMatrix.h"
+#include "SkDrawPaint.h"
+#include "SkTemplates.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawShader::fInfo[] = {
+ SK_MEMBER(matrix, Matrix),
+ SK_MEMBER(tileMode, TileMode)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawShader);
+
+SkDrawShader::SkDrawShader() : matrix(NULL),
+ tileMode(SkShader::kClamp_TileMode) {
+}
+
+bool SkDrawShader::add() {
+ if (fPaint->shader != (SkDrawShader*) -1)
+ return true;
+ fPaint->shader = this;
+ fPaint->fOwnsShader = true;
+ return false;
+}
+
+void SkDrawShader::addPostlude(SkShader* shader) {
+ if (matrix)
+ shader->setLocalMatrix(matrix->getMatrix());
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawBitmapShader::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(filterBitmap, Boolean),
+ SK_MEMBER(image, BaseBitmap)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawBitmapShader);
+
+SkDrawBitmapShader::SkDrawBitmapShader() : filterBitmap(-1), image(NULL) {}
+
+bool SkDrawBitmapShader::add() {
+ if (fPaint->shader != (SkDrawShader*) -1)
+ return true;
+ fPaint->shader = this;
+ fPaint->fOwnsShader = true;
+ return false;
+}
+
+SkShader* SkDrawBitmapShader::getShader() {
+ if (image == NULL)
+ return NULL;
+
+ // note: bitmap shader now supports independent tile modes for X and Y
+ // we pass the same to both, but later we should extend this flexibility
+ // to the xml (e.g. tileModeX="repeat" tileModeY="clmap")
+ //
+ // oops, bitmapshader no longer takes filterBitmap, but deduces it at
+ // draw-time from the paint
+ SkShader* shader = SkShader::CreateBitmapShader(image->fBitmap,
+ (SkShader::TileMode) tileMode,
+ (SkShader::TileMode) tileMode);
+ SkAutoTDelete<SkShader> autoDel(shader);
+ addPostlude(shader);
+ (void)autoDel.detach();
+ return shader;
+}
diff --git a/animator/SkDrawShader.h b/animator/SkDrawShader.h
new file mode 100644
index 00000000..b6a487ac
--- /dev/null
+++ b/animator/SkDrawShader.h
@@ -0,0 +1,30 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawShader_DEFINED
+#define SkDrawShader_DEFINED
+
+#include "SkPaintParts.h"
+#include "SkShader.h"
+
+class SkBaseBitmap;
+
+class SkDrawBitmapShader : public SkDrawShader {
+ DECLARE_DRAW_MEMBER_INFO(BitmapShader);
+ SkDrawBitmapShader();
+ virtual bool add();
+ virtual SkShader* getShader();
+protected:
+ SkBool filterBitmap;
+ SkBaseBitmap* image;
+private:
+ typedef SkDrawShader INHERITED;
+};
+
+#endif // SkDrawShader_DEFINED
diff --git a/animator/SkDrawText.cpp b/animator/SkDrawText.cpp
new file mode 100644
index 00000000..e7e5faca
--- /dev/null
+++ b/animator/SkDrawText.cpp
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "SkDrawText.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+enum SkText_Properties {
+ SK_PROPERTY(length)
+};
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkText::fInfo[] = {
+ SK_MEMBER_PROPERTY(length, Int),
+ SK_MEMBER(text, String),
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkText);
+
+SkText::SkText() : x(0), y(0) {
+}
+
+SkText::~SkText() {
+}
+
+bool SkText::draw(SkAnimateMaker& maker) {
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawText(text.c_str(), text.size(), x, y, *maker.fPaint);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkText::dump(SkAnimateMaker* maker) {
+ INHERITED::dump(maker);
+}
+#endif
+
+bool SkText::getProperty(int index, SkScriptValue* value) const {
+ SkASSERT(index == SK_PROPERTY(length));
+ value->fType = SkType_Int;
+ value->fOperand.fS32 = (int32_t) text.size();
+ return true;
+}
diff --git a/animator/SkDrawText.h b/animator/SkDrawText.h
new file mode 100644
index 00000000..3ac24791
--- /dev/null
+++ b/animator/SkDrawText.h
@@ -0,0 +1,36 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawText_DEFINED
+#define SkDrawText_DEFINED
+
+#include "SkBoundable.h"
+#include "SkMemberInfo.h"
+
+class SkText : public SkBoundable {
+ DECLARE_MEMBER_INFO(Text);
+ SkText();
+ virtual ~SkText();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool getProperty(int index, SkScriptValue* value) const ;
+ const char* getText() { return text.c_str(); }
+ size_t getSize() { return text.size(); }
+protected:
+ SkString text;
+ SkScalar x;
+ SkScalar y;
+private:
+ friend class SkTextToPath;
+ typedef SkBoundable INHERITED;
+};
+
+#endif // SkDrawText_DEFINED
diff --git a/animator/SkDrawTextBox.cpp b/animator/SkDrawTextBox.cpp
new file mode 100644
index 00000000..7a3251a2
--- /dev/null
+++ b/animator/SkDrawTextBox.cpp
@@ -0,0 +1,80 @@
+
+/*
+ * 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 "SkDrawTextBox.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+enum SkDrawTextBox_Properties {
+ foo = 100,
+ SK_PROPERTY(spacingAlign),
+ SK_PROPERTY(mode)
+};
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawTextBox::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(mode, TextBoxMode),
+ SK_MEMBER_ALIAS(spacingAdd, fSpacingAdd, Float),
+ SK_MEMBER(spacingAlign, TextBoxAlign),
+ SK_MEMBER_ALIAS(spacingMul, fSpacingMul, Float),
+ SK_MEMBER_ALIAS(text, fText, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawTextBox);
+
+SkDrawTextBox::SkDrawTextBox()
+{
+ fSpacingMul = SK_Scalar1;
+ fSpacingAdd = 0;
+ spacingAlign = SkTextBox::kStart_SpacingAlign;
+ mode = SkTextBox::kLineBreak_Mode;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawTextBox::dump(SkAnimateMaker* maker)
+{
+ dumpBase(maker);
+ dumpAttrs(maker);
+ if (mode == 0)
+ SkDebugf("mode=\"oneLine\" ");
+ if (spacingAlign == 1)
+ SkDebugf("spacingAlign=\"center\" ");
+ else if (spacingAlign == 2)
+ SkDebugf("spacingAlign=\"end\" ");
+ SkDebugf("/>\n");
+}
+#endif
+
+bool SkDrawTextBox::getProperty(int index, SkScriptValue* value) const
+{
+ return this->INHERITED::getProperty(index, value);
+}
+
+bool SkDrawTextBox::setProperty(int index, SkScriptValue& scriptValue)
+{
+ return this->INHERITED::setProperty(index, scriptValue);
+}
+
+bool SkDrawTextBox::draw(SkAnimateMaker& maker)
+{
+ SkTextBox box;
+ box.setMode((SkTextBox::Mode) mode);
+ box.setSpacingAlign((SkTextBox::SpacingAlign) spacingAlign);
+ box.setBox(fRect);
+ box.setSpacing(fSpacingMul, fSpacingAdd);
+ SkBoundableAuto boundable(this, maker);
+ box.draw(maker.fCanvas, fText.c_str(), fText.size(), *maker.fPaint);
+ return false;
+}
diff --git a/animator/SkDrawTextBox.h b/animator/SkDrawTextBox.h
new file mode 100644
index 00000000..6155befb
--- /dev/null
+++ b/animator/SkDrawTextBox.h
@@ -0,0 +1,38 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawTextBox_DEFINED
+#define SkDrawTextBox_DEFINED
+
+#include "SkDrawRectangle.h"
+#include "SkTextBox.h"
+
+class SkDrawTextBox : public SkDrawRect {
+ DECLARE_DRAW_MEMBER_INFO(TextBox);
+ SkDrawTextBox();
+
+ // overrides
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual bool getProperty(int index, SkScriptValue* value) const;
+ virtual bool setProperty(int index, SkScriptValue& );
+
+private:
+ SkString fText;
+ SkScalar fSpacingMul;
+ SkScalar fSpacingAdd;
+ int /*SkTextBox::Mode*/ mode;
+ int /*SkTextBox::SpacingAlign*/ spacingAlign;
+
+ typedef SkDrawRect INHERITED;
+};
+
+#endif // SkDrawTextBox_DEFINED
diff --git a/animator/SkDrawTo.cpp b/animator/SkDrawTo.cpp
new file mode 100644
index 00000000..ef084d12
--- /dev/null
+++ b/animator/SkDrawTo.cpp
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "SkDrawTo.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkDrawBitmap.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawTo::fInfo[] = {
+ SK_MEMBER(drawOnce, Boolean),
+ SK_MEMBER(use, Bitmap)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawTo);
+
+SkDrawTo::SkDrawTo() : drawOnce(false), use(NULL), fDrawnOnce(false) {
+}
+
+#if 0
+SkDrawTo::~SkDrawTo() {
+ SkASSERT(0);
+}
+#endif
+
+bool SkDrawTo::draw(SkAnimateMaker& maker) {
+ if (fDrawnOnce)
+ return false;
+ SkCanvas canvas(use->fBitmap);
+ SkCanvas* save = maker.fCanvas;
+ maker.fCanvas = &canvas;
+ INHERITED::draw(maker);
+ maker.fCanvas = save;
+ fDrawnOnce = drawOnce;
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawTo::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ dumpAttrs(maker);
+ if (use)
+ SkDebugf("use=\"%s\" ", use->id);
+ dumpDrawables(maker);
+}
+#endif
diff --git a/animator/SkDrawTo.h b/animator/SkDrawTo.h
new file mode 100644
index 00000000..b6365afa
--- /dev/null
+++ b/animator/SkDrawTo.h
@@ -0,0 +1,34 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawTo_DEFINED
+#define SkDrawTo_DEFINED
+
+#include "SkDrawGroup.h"
+#include "SkMemberInfo.h"
+
+class SkDrawBitmap;
+
+class SkDrawTo : public SkGroup {
+ DECLARE_MEMBER_INFO(DrawTo);
+ SkDrawTo();
+// virtual ~SkDrawTo();
+ virtual bool draw(SkAnimateMaker& );
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+protected:
+ SkBool drawOnce;
+ SkDrawBitmap* use;
+private:
+ typedef SkGroup INHERITED;
+ SkBool fDrawnOnce;
+};
+
+#endif // SkDrawTo_DEFINED
diff --git a/animator/SkDrawTransparentShader.cpp b/animator/SkDrawTransparentShader.cpp
new file mode 100644
index 00000000..2f286f4b
--- /dev/null
+++ b/animator/SkDrawTransparentShader.cpp
@@ -0,0 +1,15 @@
+
+/*
+ * 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 "SkDrawTransparentShader.h"
+#include "SkTransparentShader.h"
+
+SkShader* SkDrawTransparentShader::getShader() {
+ return new SkTransparentShader();
+}
diff --git a/animator/SkDrawTransparentShader.h b/animator/SkDrawTransparentShader.h
new file mode 100644
index 00000000..bf661740
--- /dev/null
+++ b/animator/SkDrawTransparentShader.h
@@ -0,0 +1,20 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawTransparentShader_DEFINED
+#define SkDrawTransparentShader_DEFINED
+
+#include "SkPaintParts.h"
+
+class SkDrawTransparentShader : public SkDrawShader {
+ DECLARE_EMPTY_MEMBER_INFO(TransparentShader);
+ virtual SkShader* getShader();
+};
+
+#endif // SkDrawTransparentShader_DEFINED
diff --git a/animator/SkDrawable.cpp b/animator/SkDrawable.cpp
new file mode 100644
index 00000000..610c3976
--- /dev/null
+++ b/animator/SkDrawable.cpp
@@ -0,0 +1,24 @@
+
+/*
+ * 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 "SkDrawable.h"
+
+bool SkDrawable::doEvent(SkDisplayEvent::Kind , SkEventState* ) {
+ return false;
+}
+
+bool SkDrawable::isDrawable() const {
+ return true;
+}
+
+void SkDrawable::initialize() {
+}
+
+void SkDrawable::setSteps(int steps) {
+}
diff --git a/animator/SkDrawable.h b/animator/SkDrawable.h
new file mode 100644
index 00000000..6bb96085
--- /dev/null
+++ b/animator/SkDrawable.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDrawable_DEFINED
+#define SkDrawable_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkDisplayEvent.h"
+#include "SkMath.h"
+
+struct SkEventState;
+
+class SkDrawable : public SkDisplayable {
+public:
+ virtual bool doEvent(SkDisplayEvent::Kind , SkEventState* state );
+ virtual bool draw(SkAnimateMaker& ) = 0;
+ virtual void initialize();
+ virtual bool isDrawable() const;
+ virtual void setSteps(int steps);
+};
+
+#endif // SkDrawable_DEFINED
diff --git a/animator/SkDump.cpp b/animator/SkDump.cpp
new file mode 100644
index 00000000..9f297e9d
--- /dev/null
+++ b/animator/SkDump.cpp
@@ -0,0 +1,150 @@
+
+/*
+ * 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 "SkDump.h"
+
+#ifdef SK_DUMP_ENABLED
+
+#include "SkAnimateMaker.h"
+#include "SkAnimatorScript.h"
+#include "SkDisplayEvents.h"
+#include "SkDisplayList.h"
+#include "SkString.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDump::fInfo[] = {
+ SK_MEMBER(displayList, Boolean),
+ SK_MEMBER(eventList, Boolean),
+ SK_MEMBER(events, Boolean),
+ SK_MEMBER(groups, Boolean),
+ SK_MEMBER(name, String),
+ SK_MEMBER(posts, Boolean),
+ SK_MEMBER(script, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDump);
+
+SkDump::SkDump() : displayList(-1), eventList(-1), events(-1), groups(-1), posts(-1) {
+}
+
+bool SkDump::enable(SkAnimateMaker& maker ) {
+ if (script.size() > 0)
+ return evaluate(maker);
+ bool hasAttr = false;
+ if (events > 0)
+ hasAttr |= maker.fDumpEvents = true;
+ if (posts > 0)
+ hasAttr |= maker.fDumpPosts = true;
+ if (groups > 0)
+ hasAttr |= maker.fDumpGConditions = true;
+ if ((hasAttr |= (eventList > 0)) == true)
+ maker.fEvents.dump(maker);
+ if ((hasAttr |= (name.size() > 0)) == true)
+ maker.dump(name.c_str());
+ if (displayList > 0 || (displayList != 0 && hasAttr == false))
+ maker.fDisplayList.dump(&maker);
+ return true;
+}
+
+bool SkDump::evaluate(SkAnimateMaker &maker) {
+ SkAnimatorScript scriptEngine(maker, NULL, SkType_Int);
+ SkScriptValue value;
+ const char* cScript = script.c_str();
+ bool success = scriptEngine.evaluateScript(&cScript, &value);
+ SkDebugf("%*s<dump script=\"%s\" answer=\" ", SkDisplayList::fIndent, "", script.c_str());
+ if (success == false) {
+ SkDebugf("INVALID\" />\n");
+ return false;
+ }
+ switch (value.fType) {
+ case SkType_Float:
+ SkDebugf("%g\" />\n", SkScalarToFloat(value.fOperand.fScalar));
+ break;
+ case SkType_Int:
+ SkDebugf("%d\" />\n", value.fOperand.fS32);
+ break;
+ case SkType_String:
+ SkDebugf("%s\" />\n", value.fOperand.fString->c_str());
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool SkDump::hasEnable() const {
+ return true;
+}
+
+void SkDump::GetEnumString(SkDisplayTypes type, int index, SkString* result) {
+ int badEnum = index;
+ const SkDisplayEnumMap& map = SkAnimatorScript::GetEnumValues(type);
+ const char* str = map.fValues;
+ while (--index >= 0) {
+ str = strchr(str, '|');
+ if (str == NULL) {
+ result->reset();
+ result->appendS32(badEnum);
+ return;
+ }
+ str += 1;
+ }
+ const char* end = strchr(str, '|');
+ if (end == NULL)
+ end = str + strlen(str);
+ result->set(str, end - str);
+}
+
+#else
+
+// in the release version, <dump> is allowed, and its attributes are defined, but
+// are not stored and have no effect
+
+#if SK_USE_CONDENSED_INFO == 0
+
+enum SkDump_Properties {
+ SK_PROPERTY(displayList),
+ SK_PROPERTY(eventList),
+ SK_PROPERTY(events),
+ SK_PROPERTY(groups),
+ SK_PROPERTY(name),
+ SK_PROPERTY(posts),
+ SK_PROPERTY(script)
+};
+
+const SkMemberInfo SkDump::fInfo[] = {
+ SK_MEMBER_PROPERTY(displayList, Boolean),
+ SK_MEMBER_PROPERTY(eventList, Boolean),
+ SK_MEMBER_PROPERTY(events, Boolean),
+ SK_MEMBER_PROPERTY(groups, Boolean),
+ SK_MEMBER_PROPERTY(name, String),
+ SK_MEMBER_PROPERTY(posts, Boolean),
+ SK_MEMBER_PROPERTY(script, String)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDump);
+
+bool SkDump::enable(SkAnimateMaker&) {
+ return true;
+}
+
+bool SkDump::hasEnable() const {
+ return true;
+}
+
+bool SkDump::setProperty(int index, SkScriptValue&) {
+ return index <= SK_PROPERTY(posts);
+}
+
+#endif
diff --git a/animator/SkDump.h b/animator/SkDump.h
new file mode 100644
index 00000000..0a31b1c0
--- /dev/null
+++ b/animator/SkDump.h
@@ -0,0 +1,42 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDump_DEFINED
+#define SkDump_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+
+class SkAnimateMaker;
+class SkString;
+
+class SkDump : public SkDisplayable {
+ DECLARE_MEMBER_INFO(Dump);
+#ifdef SK_DUMP_ENABLED
+ SkDump();
+ virtual bool enable(SkAnimateMaker & );
+ bool evaluate(SkAnimateMaker &);
+ virtual bool hasEnable() const;
+ static void GetEnumString(SkDisplayTypes , int index, SkString* result);
+ SkBool displayList;
+ SkBool eventList;
+ SkBool events;
+ SkString name;
+ SkBool groups;
+ SkBool posts;
+ SkString script;
+#else
+ virtual bool enable(SkAnimateMaker & );
+ virtual bool hasEnable() const;
+ virtual bool setProperty(int index, SkScriptValue& );
+#endif
+};
+
+
+#endif // SkDump_DEFINED
diff --git a/animator/SkExtraPathEffects.xsd b/animator/SkExtraPathEffects.xsd
new file mode 100644
index 00000000..9592443a
--- /dev/null
+++ b/animator/SkExtraPathEffects.xsd
@@ -0,0 +1,33 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+xmlns:Sk="urn:screenplay"
+xmlns:extra="urn:extraPathEffects" targetNamespace="urn:extraPathEffects" >
+ <xs:import namespace="urn:screenplay"
+ schemaLocation="SkAnimateSchema.xsd" />
+
+ <xs:element name="composePathEffect" >
+ <xs:complexType>
+ <xs:choice maxOccurs="1">
+ <xs:element ref="Sk:dash"/>
+ <xs:element ref="extra:shape1DPathEffect"/>
+ </xs:choice>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="shape1DPathEffect" >
+ <xs:complexType>
+ <xs:choice maxOccurs="1">
+ <xs:element ref="Sk:matrix"/>
+ <xs:element ref="Sk:path"/>
+ </xs:choice>
+ <xs:attribute name="addPath" type="Sk:DynamicString" />
+ <xs:attribute name="matrix" type="Sk:DynamicString" />
+ <xs:attribute name="path" type="Sk:Path" />
+ <xs:attribute name="phase" type="Sk:DynamicString"/>
+ <xs:attribute name="spacing" type="Sk:DynamicString"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
+
diff --git a/animator/SkExtras.h b/animator/SkExtras.h
new file mode 100644
index 00000000..dcd39050
--- /dev/null
+++ b/animator/SkExtras.h
@@ -0,0 +1,34 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkExtras_DEFINED
+#define SkExtras_DEFINED
+
+#include "SkScript.h"
+
+class SkExtras {
+public:
+ SkExtras();
+ virtual ~SkExtras() {}
+
+ virtual SkDisplayable* createInstance(SkDisplayTypes type) = 0;
+ virtual bool definesType(SkDisplayTypes type) = 0;
+#if SK_USE_CONDENSED_INFO == 0
+ virtual const SkMemberInfo* getMembers(SkDisplayTypes type, int* infoCountPtr) = 0;
+#endif
+#ifdef SK_DEBUG
+ virtual const char* getName(SkDisplayTypes type) = 0;
+#endif
+ virtual SkDisplayTypes getType(const char match[], size_t len ) = 0;
+
+ SkScriptEngine::_propertyCallBack fExtraCallBack;
+ void* fExtraStorage;
+};
+
+#endif // SkExtras_DEFINED
diff --git a/animator/SkGetCondensedInfo.cpp b/animator/SkGetCondensedInfo.cpp
new file mode 100644
index 00000000..f2471bbe
--- /dev/null
+++ b/animator/SkGetCondensedInfo.cpp
@@ -0,0 +1,121 @@
+
+/*
+ * 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 "SkMemberInfo.h"
+
+#if SK_USE_CONDENSED_INFO == 1
+
+// SkCondensed.cpp is auto-generated
+// To generate it, execute SkDisplayType::BuildCondensedInfo()
+#ifdef SK_DEBUG
+#include "SkCondensedDebug.cpp"
+#else
+#include "SkCondensedRelease.cpp"
+#endif
+
+static int _searchByName(const unsigned char* lengths, int count, const char* strings, const char target[]) {
+ int lo = 0;
+ int hi = count - 1;
+ while (lo < hi) {
+ int mid = (hi + lo) >> 1;
+ if (strcmp(&strings[lengths[mid << 2]], target) < 0)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+ if (strcmp(&strings[lengths[hi << 2]], target) != 0)
+ return -1;
+ return hi;
+}
+
+static int _searchByType(SkDisplayTypes type) {
+ unsigned char match = (unsigned char) type;
+ int lo = 0;
+ int hi = kTypeIDs - 1;
+ while (lo < hi) {
+ int mid = (hi + lo) >> 1;
+ if (gTypeIDs[mid] < match)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+ if (gTypeIDs[hi] != type)
+ return -1;
+ return hi;
+}
+
+const SkMemberInfo* SkDisplayType::GetMembers(SkAnimateMaker* , SkDisplayTypes type, int* infoCountPtr) {
+ int lookup = _searchByType(type);
+ if (lookup < 0)
+ return NULL;
+ if (infoCountPtr)
+ *infoCountPtr = gInfoCounts[lookup];
+ return gInfoTables[lookup];
+}
+
+// !!! replace with inline
+const SkMemberInfo* SkDisplayType::GetMember(SkAnimateMaker* , SkDisplayTypes type, const char** matchPtr ) {
+ const SkMemberInfo* info = SkMemberInfo::Find(type, matchPtr);
+ SkASSERT(info);
+ return info;
+}
+
+static const SkMemberInfo* _lookup(int lookup, const char** matchPtr) {
+ int count = gInfoCounts[lookup];
+ const SkMemberInfo* info = gInfoTables[lookup];
+ if (info->fType == SkType_BaseClassInfo) {
+ int baseTypeLookup = info->fOffset;
+ const SkMemberInfo* result = _lookup(baseTypeLookup, matchPtr);
+ if (result != NULL)
+ return result;
+ if (--count == 0)
+ return NULL;
+ info++;
+ }
+ SkASSERT(info->fType != SkType_BaseClassInfo);
+ const char* match = *matchPtr;
+ const char* strings = gInfoNames[lookup];
+ int index = _searchByName(&info->fName, count, strings, match);
+ if (index < 0)
+ return NULL;
+ return &info[index];
+}
+
+const SkMemberInfo* SkMemberInfo::Find(SkDisplayTypes type, int* index) {
+ int count = gInfoCounts[lookup];
+ const SkMemberInfo* info = gInfoTables[lookup];
+ if (info->fType == SkType_BaseClassInfo) {
+ int baseTypeLookup = info->fOffset;
+ const SkMemberInfo* result = Find(baseTypeLookup, index);
+ if (result != NULL)
+ return result;
+ if (--count == 0)
+ return NULL;
+ info++;
+ }
+ SkASSERT(info->fType != SkType_BaseClassInfo);
+ if (*index >= count) {
+ *index -= count;
+ return NULL;
+ }
+ return &info[index];
+}
+
+const SkMemberInfo* SkMemberInfo::Find(SkDisplayTypes type, const char** matchPtr) {
+ int lookup = _searchByType(type);
+ SkASSERT(lookup >= 0);
+ return _lookup(lookup, matchPtr);
+}
+
+const SkMemberInfo* SkMemberInfo::getInherited() const {
+ int baseTypeLookup = fOffset;
+ return gInfoTables[baseTypeLookup];
+}
+
+#endif
diff --git a/animator/SkHitClear.cpp b/animator/SkHitClear.cpp
new file mode 100644
index 00000000..3ac521ae
--- /dev/null
+++ b/animator/SkHitClear.cpp
@@ -0,0 +1,32 @@
+
+/*
+ * 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 "SkHitClear.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkHitClear::fInfo[] = {
+ SK_MEMBER_ARRAY(targets, Displayable)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkHitClear);
+
+bool SkHitClear::enable(SkAnimateMaker&) {
+ for (int tIndex = 0; tIndex < targets.count(); tIndex++) {
+ SkDisplayable* target = targets[tIndex];
+ target->clearBounder();
+ }
+ return true;
+}
+
+bool SkHitClear::hasEnable() const {
+ return true;
+}
diff --git a/animator/SkHitClear.h b/animator/SkHitClear.h
new file mode 100644
index 00000000..9c402090
--- /dev/null
+++ b/animator/SkHitClear.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkHitClear_DEFINED
+#define SkHitClear_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkTypedArray.h"
+
+class SkHitClear : public SkDisplayable {
+ DECLARE_MEMBER_INFO(HitClear);
+ virtual bool enable(SkAnimateMaker& );
+ virtual bool hasEnable() const;
+private:
+ SkTDDisplayableArray targets;
+};
+
+#endif // SkHitClear_DEFINED
diff --git a/animator/SkHitTest.cpp b/animator/SkHitTest.cpp
new file mode 100644
index 00000000..79dd25bd
--- /dev/null
+++ b/animator/SkHitTest.cpp
@@ -0,0 +1,74 @@
+
+/*
+ * 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 "SkHitTest.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkHitTest::fInfo[] = {
+ SK_MEMBER_ARRAY(bullets, Displayable),
+ SK_MEMBER_ARRAY(hits, Int),
+ SK_MEMBER_ARRAY(targets, Displayable),
+ SK_MEMBER(value, Boolean)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkHitTest);
+
+SkHitTest::SkHitTest() : value(false) {
+}
+
+bool SkHitTest::draw(SkAnimateMaker&) {
+ hits.setCount(bullets.count());
+ value = false;
+ int bulletCount = bullets.count();
+ int targetCount = targets.count();
+ for (int bIndex = 0; bIndex < bulletCount; bIndex++) {
+ SkDisplayable* bullet = bullets[bIndex];
+ SkRect bBounds;
+ bullet->getBounds(&bBounds);
+ hits[bIndex] = -1;
+ if (bBounds.fLeft == (int16_t)0x8000U)
+ continue;
+ for (int tIndex = 0; tIndex < targetCount; tIndex++) {
+ SkDisplayable* target = targets[tIndex];
+ SkRect tBounds;
+ target->getBounds(&tBounds);
+ if (bBounds.intersect(tBounds)) {
+ hits[bIndex] = tIndex;
+ value = true;
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+bool SkHitTest::enable(SkAnimateMaker&) {
+ for (int bIndex = 0; bIndex < bullets.count(); bIndex++) {
+ SkDisplayable* bullet = bullets[bIndex];
+ bullet->enableBounder();
+ }
+ for (int tIndex = 0; tIndex < targets.count(); tIndex++) {
+ SkDisplayable* target = targets[tIndex];
+ target->enableBounder();
+ }
+ return false;
+}
+
+bool SkHitTest::hasEnable() const {
+ return true;
+}
+
+const SkMemberInfo* SkHitTest::preferredChild(SkDisplayTypes) {
+ if (bullets.count() == 0)
+ return getMember("bullets");
+ return getMember("targets"); // !!! cwap! need to refer to member through enum like kScope instead
+}
diff --git a/animator/SkHitTest.h b/animator/SkHitTest.h
new file mode 100644
index 00000000..68d5cc5e
--- /dev/null
+++ b/animator/SkHitTest.h
@@ -0,0 +1,30 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkHitTest_DEFINED
+#define SkHitTest_DEFINED
+
+#include "SkDrawable.h"
+#include "SkTypedArray.h"
+
+class SkHitTest : public SkDrawable {
+ DECLARE_MEMBER_INFO(HitTest);
+ SkHitTest();
+ virtual bool draw(SkAnimateMaker& );
+ virtual bool enable(SkAnimateMaker& );
+ virtual bool hasEnable() const;
+ virtual const SkMemberInfo* preferredChild(SkDisplayTypes type);
+private:
+ SkTDDisplayableArray bullets;
+ SkTDIntArray hits;
+ SkTDDisplayableArray targets;
+ SkBool value;
+};
+
+#endif // SkHitTest_DEFINED
diff --git a/animator/SkIntArray.h b/animator/SkIntArray.h
new file mode 100644
index 00000000..401e51b8
--- /dev/null
+++ b/animator/SkIntArray.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkIntArray_DEFINED
+#define SkIntArray_DEFINED
+
+#include "SkColor.h"
+#include "SkDisplayType.h"
+#include "SkMath.h"
+#include "SkTDArray_Experimental.h"
+
+class SkActive;
+class SkAnimateBase;
+class SkDataInput;
+class SkDisplayable;
+class SkDisplayEvent;
+class SkDrawable;
+class SkDrawColor;
+class SkMatrixPart;
+struct SkMemberInfo;
+class SkPathPart;
+class SkPaintPart;
+class SkTypedArray;
+class SkString;
+union SkOperand;
+
+typedef SkIntArray(int) SkTDIntArray;
+typedef SkIntArray(SkColor) SkTDColorArray;
+typedef SkIntArray(SkDisplayTypes) SkTDDisplayTypesArray;
+typedef SkIntArray(SkMSec) SkTDMSecArray;
+typedef SkIntArray(SkScalar) SkTDScalarArray;
+
+typedef SkLongArray(SkActive*) SkTDActiveArray;
+typedef SkLongArray(SkAnimateBase*) SkTDAnimateArray;
+typedef SkLongArray(SkDataInput*) SkTDDataArray;
+typedef SkLongArray(SkDisplayable*) SkTDDisplayableArray;
+typedef SkLongArray(SkDisplayEvent*) SkTDDisplayEventArray;
+typedef SkLongArray(SkDrawable*) SkTDDrawableArray;
+typedef SkLongArray(SkDrawColor*) SkTDDrawColorArray;
+typedef SkLongArray(SkMatrixPart*) SkTDMatrixPartArray;
+typedef SkLongArray(const SkMemberInfo*) SkTDMemberInfoArray;
+typedef SkLongArray(SkPaintPart*) SkTDPaintPartArray;
+typedef SkLongArray(SkPathPart*) SkTDPathPartArray;
+typedef SkLongArray(SkTypedArray*) SkTDTypedArrayArray;
+typedef SkLongArray(SkString*) SkTDStringArray;
+typedef SkLongArray(SkOperand) SkTDOperandArray;
+typedef SkLongArray(SkOperand*) SkTDOperandPtrArray;
+
+#endif // SkIntArray_DEFINED
diff --git a/animator/SkMatrixParts.cpp b/animator/SkMatrixParts.cpp
new file mode 100644
index 00000000..a2f3a9a5
--- /dev/null
+++ b/animator/SkMatrixParts.cpp
@@ -0,0 +1,292 @@
+
+/*
+ * 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 "SkMatrixParts.h"
+#include "SkAnimateMaker.h"
+#include "SkDrawMatrix.h"
+#include "SkDrawRectangle.h"
+#include "SkDrawPath.h"
+
+SkMatrixPart::SkMatrixPart() : fMatrix(NULL) {
+}
+
+void SkMatrixPart::dirty() {
+ fMatrix->dirty();
+}
+
+SkDisplayable* SkMatrixPart::getParent() const {
+ return fMatrix;
+}
+
+bool SkMatrixPart::setParent(SkDisplayable* parent) {
+ SkASSERT(parent != NULL);
+ if (parent->isMatrix() == false)
+ return true;
+ fMatrix = (SkDrawMatrix*) parent;
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRotate::fInfo[] = {
+ SK_MEMBER(center, Point),
+ SK_MEMBER(degrees, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRotate);
+
+SkRotate::SkRotate() : degrees(0) {
+ center.fX = center.fY = 0;
+}
+
+bool SkRotate::add() {
+ fMatrix->rotate(degrees, center);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkScale::fInfo[] = {
+ SK_MEMBER(center, Point),
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkScale);
+
+SkScale::SkScale() : x(SK_Scalar1), y(SK_Scalar1) {
+ center.fX = center.fY = 0;
+}
+
+bool SkScale::add() {
+ fMatrix->scale(x, y, center);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkSkew::fInfo[] = {
+ SK_MEMBER(center, Point),
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkSkew);
+
+SkSkew::SkSkew() : x(0), y(0) {
+ center.fX = center.fY = 0;
+}
+
+bool SkSkew::add() {
+ fMatrix->skew(x, y, center);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkTranslate::fInfo[] = {
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkTranslate);
+
+SkTranslate::SkTranslate() : x(0), y(0) {
+}
+
+bool SkTranslate::add() {
+ fMatrix->translate(x, y);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkFromPath::fInfo[] = {
+ SK_MEMBER(mode, FromPathMode),
+ SK_MEMBER(offset, Float),
+ SK_MEMBER(path, Path)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkFromPath);
+
+SkFromPath::SkFromPath() :
+ mode(0), offset(0), path(NULL) {
+}
+
+SkFromPath::~SkFromPath() {
+}
+
+bool SkFromPath::add() {
+ if (path == NULL)
+ return true;
+ static const uint8_t gFlags[] = {
+ SkPathMeasure::kGetPosAndTan_MatrixFlag, // normal
+ SkPathMeasure::kGetTangent_MatrixFlag, // angle
+ SkPathMeasure::kGetPosition_MatrixFlag // position
+ };
+ if ((unsigned)mode >= SK_ARRAY_COUNT(gFlags))
+ return true;
+ SkMatrix result;
+ fPathMeasure.setPath(&path->getPath(), false);
+ if (fPathMeasure.getMatrix(offset, &result, (SkPathMeasure::MatrixFlags)gFlags[mode]))
+ fMatrix->set(result);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRectToRect::fInfo[] = {
+ SK_MEMBER(destination, Rect),
+ SK_MEMBER(source, Rect)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRectToRect);
+
+SkRectToRect::SkRectToRect() :
+ source(NULL), destination(NULL) {
+}
+
+SkRectToRect::~SkRectToRect() {
+}
+
+bool SkRectToRect::add() {
+ if (source == NULL || destination == NULL)
+ return true;
+ SkMatrix temp;
+ temp.setRectToRect(source->fRect, destination->fRect,
+ SkMatrix::kFill_ScaleToFit);
+ fMatrix->set(temp);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkRectToRect::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("/>\n");
+ SkDisplayList::fIndent += 4;
+ if (source) {
+ SkDebugf("%*s<source>\n", SkDisplayList::fIndent, "");
+ SkDisplayList::fIndent += 4;
+ source->dump(maker);
+ SkDisplayList::fIndent -= 4;
+ SkDebugf("%*s</source>\n", SkDisplayList::fIndent, "");
+ }
+ if (destination) {
+ SkDebugf("%*s<destination>\n", SkDisplayList::fIndent, "");
+ SkDisplayList::fIndent += 4;
+ destination->dump(maker);
+ SkDisplayList::fIndent -= 4;
+ SkDebugf("%*s</destination>\n", SkDisplayList::fIndent, "");
+ }
+ SkDisplayList::fIndent -= 4;
+ dumpEnd(maker);
+}
+#endif
+
+const SkMemberInfo* SkRectToRect::preferredChild(SkDisplayTypes ) {
+ if (source == NULL)
+ return getMember("source"); // !!! cwap! need to refer to member through enum like kScope instead
+ else {
+ SkASSERT(destination == NULL);
+ return getMember("destination");
+ }
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkPolyToPoly::fInfo[] = {
+ SK_MEMBER(destination, Polygon),
+ SK_MEMBER(source, Polygon)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkPolyToPoly);
+
+SkPolyToPoly::SkPolyToPoly() : source(NULL), destination(NULL) {
+}
+
+SkPolyToPoly::~SkPolyToPoly() {
+}
+
+bool SkPolyToPoly::add() {
+ SkASSERT(source);
+ SkASSERT(destination);
+ SkPoint src[4];
+ SkPoint dst[4];
+ SkPath& sourcePath = source->getPath();
+ int srcPts = sourcePath.getPoints(src, 4);
+ SkPath& destPath = destination->getPath();
+ int dstPts = destPath.getPoints(dst, 4);
+ if (srcPts != dstPts)
+ return true;
+ SkMatrix temp;
+ temp.setPolyToPoly(src, dst, srcPts);
+ fMatrix->set(temp);
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkPolyToPoly::dump(SkAnimateMaker* maker) {
+ dumpBase(maker);
+ SkDebugf("/>\n");
+ SkDisplayList::fIndent += 4;
+ if (source) {
+ SkDebugf("%*s<source>\n", SkDisplayList::fIndent, "");
+ SkDisplayList::fIndent += 4;
+ source->dump(maker);
+ SkDisplayList::fIndent -= 4;
+ SkDebugf("%*s</source>\n", SkDisplayList::fIndent, "");
+ }
+ if (destination) {
+ SkDebugf("%*s<destination>\n", SkDisplayList::fIndent, "");
+ SkDisplayList::fIndent += 4;
+ destination->dump(maker);
+ SkDisplayList::fIndent -= 4;
+ SkDebugf("%*s</destination>\n", SkDisplayList::fIndent, "");
+ }
+ SkDisplayList::fIndent -= 4;
+ dumpEnd(maker);
+}
+#endif
+
+void SkPolyToPoly::onEndElement(SkAnimateMaker& ) {
+ SkASSERT(source);
+ SkASSERT(destination);
+ if (source->childHasID() || destination->childHasID())
+ fMatrix->setChildHasID();
+}
+
+const SkMemberInfo* SkPolyToPoly::preferredChild(SkDisplayTypes ) {
+ if (source == NULL)
+ return getMember("source"); // !!! cwap! need to refer to member through enum like kScope instead
+ else {
+ SkASSERT(destination == NULL);
+ return getMember("destination");
+ }
+}
diff --git a/animator/SkMatrixParts.h b/animator/SkMatrixParts.h
new file mode 100644
index 00000000..51c95599
--- /dev/null
+++ b/animator/SkMatrixParts.h
@@ -0,0 +1,119 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkMatrixParts_DEFINED
+#define SkMatrixParts_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkPathMeasure.h"
+
+class SkDrawPath;
+class SkDrawRect;
+class SkPolygon;
+
+class SkDrawMatrix;
+// class SkMatrix;
+
+class SkMatrixPart : public SkDisplayable {
+public:
+ SkMatrixPart();
+ virtual bool add() = 0;
+ virtual void dirty();
+ virtual SkDisplayable* getParent() const;
+ virtual bool setParent(SkDisplayable* parent);
+#ifdef SK_DEBUG
+ virtual bool isMatrixPart() const { return true; }
+#endif
+protected:
+ SkDrawMatrix* fMatrix;
+};
+
+class SkRotate : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(Rotate);
+ SkRotate();
+protected:
+ virtual bool add();
+ SkScalar degrees;
+ SkPoint center;
+};
+
+class SkScale : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(Scale);
+ SkScale();
+protected:
+ virtual bool add();
+ SkScalar x;
+ SkScalar y;
+ SkPoint center;
+};
+
+class SkSkew : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(Skew);
+ SkSkew();
+protected:
+ virtual bool add();
+ SkScalar x;
+ SkScalar y;
+ SkPoint center;
+};
+
+class SkTranslate : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(Translate);
+ SkTranslate();
+protected:
+ virtual bool add();
+ SkScalar x;
+ SkScalar y;
+};
+
+class SkFromPath : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(FromPath);
+ SkFromPath();
+ virtual ~SkFromPath();
+protected:
+ virtual bool add();
+ int32_t mode;
+ SkScalar offset;
+ SkDrawPath* path;
+ SkPathMeasure fPathMeasure;
+};
+
+class SkRectToRect : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(RectToRect);
+ SkRectToRect();
+ virtual ~SkRectToRect();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual const SkMemberInfo* preferredChild(SkDisplayTypes type);
+protected:
+ virtual bool add();
+ SkDrawRect* source;
+ SkDrawRect* destination;
+};
+
+class SkPolyToPoly : public SkMatrixPart {
+ DECLARE_MEMBER_INFO(PolyToPoly);
+ SkPolyToPoly();
+ virtual ~SkPolyToPoly();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker* );
+#endif
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual const SkMemberInfo* preferredChild(SkDisplayTypes type);
+protected:
+ virtual bool add();
+ SkPolygon* source;
+ SkPolygon* destination;
+};
+
+// !!! add concat matrix ?
+
+#endif // SkMatrixParts_DEFINED
diff --git a/animator/SkMemberInfo.cpp b/animator/SkMemberInfo.cpp
new file mode 100644
index 00000000..5e54b53a
--- /dev/null
+++ b/animator/SkMemberInfo.cpp
@@ -0,0 +1,559 @@
+
+/*
+ * 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 "SkMemberInfo.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimatorScript.h"
+#include "SkBase64.h"
+#include "SkCamera.h"
+#include "SkDisplayable.h"
+#include "SkDisplayTypes.h"
+#include "SkDraw3D.h"
+#include "SkDrawColor.h"
+#include "SkParse.h"
+#include "SkScript.h"
+#include "SkTSearch.h"
+#include "SkTypedArray.h"
+
+size_t SkMemberInfo::GetSize(SkDisplayTypes type) { // size of simple types only
+ size_t byteSize;
+ switch (type) {
+ case SkType_ARGB:
+ byteSize = sizeof(SkColor);
+ break;
+ case SkType_AddMode:
+ case SkType_Align:
+ case SkType_ApplyMode:
+ case SkType_ApplyTransition:
+ case SkType_BitmapEncoding:
+ case SkType_Boolean:
+ case SkType_Cap:
+ case SkType_EventCode:
+ case SkType_EventKind:
+ case SkType_EventMode:
+ case SkType_FilterType:
+ case SkType_FontStyle:
+ case SkType_FromPathMode:
+ case SkType_Join:
+ case SkType_MaskFilterBlurStyle:
+ case SkType_PathDirection:
+ case SkType_Style:
+ case SkType_TileMode:
+ case SkType_Xfermode:
+ byteSize = sizeof(int);
+ break;
+ case SkType_Base64: // assume base64 data is always const, copied by ref
+ case SkType_Displayable:
+ case SkType_Drawable:
+ case SkType_Matrix:
+ byteSize = sizeof(void*);
+ break;
+ case SkType_MSec:
+ byteSize = sizeof(SkMSec);
+ break;
+ case SkType_Point:
+ byteSize = sizeof(SkPoint);
+ break;
+ case SkType_3D_Point:
+ byteSize = sizeof(Sk3D_Point);
+ break;
+ case SkType_Int:
+ byteSize = sizeof(int32_t);
+ break;
+ case SkType_Float:
+ byteSize = sizeof(SkScalar);
+ break;
+ case SkType_DynamicString:
+ case SkType_String:
+ byteSize = sizeof(SkString); // assume we'll copy by reference, not value
+ break;
+ default:
+// SkASSERT(0);
+ byteSize = 0;
+ }
+ return byteSize;
+}
+
+bool SkMemberInfo::getArrayValue(const SkDisplayable* displayable, int index, SkOperand* value) const {
+ SkASSERT(fType != SkType_String && fType != SkType_MemberProperty);
+ char* valuePtr = (char*) *(SkOperand**) memberData(displayable);
+ SkDisplayTypes type = (SkDisplayTypes) 0;
+ if (displayable->getType() == SkType_Array) {
+ SkDisplayArray* dispArray = (SkDisplayArray*) displayable;
+ if (dispArray->values.count() <= index)
+ return false;
+ type = dispArray->values.getType();
+ } else {
+ SkASSERT(0); // incomplete
+ }
+ size_t byteSize = GetSize(type);
+ memcpy(value, valuePtr + index * byteSize, byteSize);
+ return true;
+}
+
+size_t SkMemberInfo::getSize(const SkDisplayable* displayable) const {
+ size_t byteSize;
+ switch (fType) {
+ case SkType_MemberProperty:
+ byteSize = GetSize(propertyType());
+ break;
+ case SkType_Array: {
+ SkDisplayTypes type;
+ if (displayable == NULL)
+ return sizeof(int);
+ if (displayable->getType() == SkType_Array) {
+ SkDisplayArray* dispArray = (SkDisplayArray*) displayable;
+ type = dispArray->values.getType();
+ } else
+ type = propertyType();
+ SkTDOperandArray* array = (SkTDOperandArray*) memberData(displayable);
+ byteSize = GetSize(type) * array->count();
+ } break;
+ default:
+ byteSize = GetSize((SkDisplayTypes) fType);
+ }
+ return byteSize;
+}
+
+void SkMemberInfo::getString(const SkDisplayable* displayable, SkString** string) const {
+ if (fType == SkType_MemberProperty) {
+ SkScriptValue value;
+ displayable->getProperty(propertyIndex(), &value);
+ SkASSERT(value.fType == SkType_String);
+ *string = value.fOperand.fString;
+ return;
+ }
+ SkASSERT(fCount == sizeof(SkString) / sizeof(SkScalar));
+ SkASSERT(fType == SkType_String || fType == SkType_DynamicString);
+ void* valuePtr = memberData(displayable);
+ *string = (SkString*) valuePtr;
+}
+
+void SkMemberInfo::getValue(const SkDisplayable* displayable, SkOperand value[], int count) const {
+ SkASSERT(fType != SkType_String && fType != SkType_MemberProperty);
+ SkASSERT(count == fCount);
+ void* valuePtr = memberData(displayable);
+ size_t byteSize = getSize(displayable);
+ SkASSERT(sizeof(value[0].fScalar) == sizeof(value[0])); // no support for 64 bit pointers, yet
+ memcpy(value, valuePtr, byteSize);
+}
+
+void SkMemberInfo::setString(SkDisplayable* displayable, SkString* value) const {
+ SkString* string = (SkString*) memberData(displayable);
+ string->set(*value);
+ displayable->dirty();
+}
+
+void SkMemberInfo::setValue(SkDisplayable* displayable, const SkOperand values[],
+ int count) const {
+ SkASSERT(sizeof(values[0].fScalar) == sizeof(values[0])); // no support for 64 bit pointers, yet
+ char* dst = (char*) memberData(displayable);
+ if (fType == SkType_Array) {
+ SkTDScalarArray* array = (SkTDScalarArray* ) dst;
+ array->setCount(count);
+ dst = (char*) array->begin();
+ }
+ memcpy(dst, values, count * sizeof(SkOperand));
+ displayable->dirty();
+}
+
+
+static inline bool is_between(int c, int min, int max)
+{
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_hex(int c)
+{
+ if (is_between(c, '0', '9'))
+ return true;
+ c |= 0x20; // make us lower-case
+ if (is_between(c, 'a', 'f'))
+ return true;
+ return false;
+}
+
+
+bool SkMemberInfo::setValue(SkAnimateMaker& maker, SkTDOperandArray* arrayStorage,
+ int storageOffset, int maxStorage, SkDisplayable* displayable, SkDisplayTypes outType,
+ const char rawValue[], size_t rawValueLen) const
+{
+ SkString valueStr(rawValue, rawValueLen);
+ SkScriptValue scriptValue;
+ scriptValue.fType = SkType_Unknown;
+ scriptValue.fOperand.fS32 = 0;
+ SkDisplayTypes type = getType();
+ SkAnimatorScript engine(maker, displayable, type);
+ if (arrayStorage)
+ displayable = NULL;
+ bool success = true;
+ void* untypedStorage = NULL;
+ if (displayable && fType != SkType_MemberProperty && fType != SkType_MemberFunction)
+ untypedStorage = (SkTDOperandArray*) memberData(displayable);
+
+ if (type == SkType_ARGB) {
+ // for both SpiderMonkey and SkiaScript, substitute any #xyz or #xxyyzz first
+ // it's enough to expand the colors into 0xFFxxyyzz
+ const char* poundPos;
+ while ((poundPos = strchr(valueStr.c_str(), '#')) != NULL) {
+ size_t offset = poundPos - valueStr.c_str();
+ if (valueStr.size() - offset < 4)
+ break;
+ char r = poundPos[1];
+ char g = poundPos[2];
+ char b = poundPos[3];
+ if (is_hex(r) == false || is_hex(g) == false || is_hex(b) == false)
+ break;
+ char hex = poundPos[4];
+ if (is_hex(hex) == false) {
+ valueStr.insertUnichar(offset + 1, r);
+ valueStr.insertUnichar(offset + 3, g);
+ valueStr.insertUnichar(offset + 5, b);
+ }
+ *(char*) poundPos = '0'; // overwrite '#'
+ valueStr.insert(offset + 1, "xFF");
+ }
+ }
+ if (SkDisplayType::IsDisplayable(&maker, type) || SkDisplayType::IsEnum(&maker, type) || type == SkType_ARGB)
+ goto scriptCommon;
+ switch (type) {
+ case SkType_String:
+#if 0
+ if (displayable && displayable->isAnimate()) {
+
+ goto noScriptString;
+ }
+ if (strncmp(rawValue, "#string:", sizeof("#string:") - 1) == 0) {
+ SkASSERT(sizeof("string") == sizeof("script"));
+ char* stringHeader = valueStr.writable_str();
+ memcpy(&stringHeader[1], "script", sizeof("script") - 1);
+ rawValue = valueStr.c_str();
+ goto noScriptString;
+ } else
+#endif
+ if (strncmp(rawValue, "#script:", sizeof("#script:") - 1) != 0)
+ goto noScriptString;
+ valueStr.remove(0, 8);
+ case SkType_Unknown:
+ case SkType_Int:
+ case SkType_MSec: // for the purposes of script, MSec is treated as a Scalar
+ case SkType_Point:
+ case SkType_3D_Point:
+ case SkType_Float:
+ case SkType_Array:
+scriptCommon: {
+ const char* script = valueStr.c_str();
+ success = engine.evaluateScript(&script, &scriptValue);
+ if (success == false) {
+ maker.setScriptError(engine);
+ return false;
+ }
+ }
+ SkASSERT(success);
+ if (scriptValue.fType == SkType_Displayable) {
+ if (type == SkType_String) {
+ const char* charPtr = NULL;
+ maker.findKey(scriptValue.fOperand.fDisplayable, &charPtr);
+ scriptValue.fOperand.fString = new SkString(charPtr);
+ scriptValue.fType = SkType_String;
+ engine.SkScriptEngine::track(scriptValue.fOperand.fString);
+ break;
+ }
+ SkASSERT(SkDisplayType::IsDisplayable(&maker, type));
+ if (displayable)
+ displayable->setReference(this, scriptValue.fOperand.fDisplayable);
+ else
+ arrayStorage->begin()[0].fDisplayable = scriptValue.fOperand.fDisplayable;
+ return true;
+ }
+ if (type != scriptValue.fType) {
+ if (scriptValue.fType == SkType_Array) {
+ engine.forget(scriptValue.getArray());
+ goto writeStruct; // real structs have already been written by script
+ }
+ switch (type) {
+ case SkType_String:
+ success = engine.convertTo(SkType_String, &scriptValue);
+ break;
+ case SkType_MSec:
+ case SkType_Float:
+ success = engine.convertTo(SkType_Float, &scriptValue);
+ break;
+ case SkType_Int:
+ success = engine.convertTo(SkType_Int, &scriptValue);
+ break;
+ case SkType_Array:
+ success = engine.convertTo(arrayType(), &scriptValue);
+ // !!! incomplete; create array of appropriate type and add scriptValue to it
+ SkASSERT(0);
+ break;
+ case SkType_Displayable:
+ case SkType_Drawable:
+ return false; // no way to convert other types to this
+ default: // to avoid warnings
+ break;
+ }
+ if (success == false)
+ return false;
+ }
+ if (type == SkType_MSec)
+ scriptValue.fOperand.fMSec = SkScalarMulRound(scriptValue.fOperand.fScalar, 1000);
+ scriptValue.fType = type;
+ break;
+ noScriptString:
+ case SkType_DynamicString:
+ if (fType == SkType_MemberProperty && displayable) {
+ SkString string(rawValue, rawValueLen);
+ SkScriptValue scriptValue;
+ scriptValue.fOperand.fString = &string;
+ scriptValue.fType = SkType_String;
+ displayable->setProperty(propertyIndex(), scriptValue);
+ } else if (displayable) {
+ SkString* string = (SkString*) memberData(displayable);
+ string->set(rawValue, rawValueLen);
+ } else {
+ SkASSERT(arrayStorage->count() == 1);
+ arrayStorage->begin()->fString->set(rawValue, rawValueLen);
+ }
+ goto dirty;
+ case SkType_Base64: {
+ SkBase64 base64;
+ base64.decode(rawValue, rawValueLen);
+ *(SkBase64* ) untypedStorage = base64;
+ } goto dirty;
+ default:
+ SkASSERT(0);
+ break;
+ }
+// if (SkDisplayType::IsStruct(type) == false)
+ {
+writeStruct:
+ if (writeValue(displayable, arrayStorage, storageOffset, maxStorage,
+ untypedStorage, outType, scriptValue)) {
+ maker.setErrorCode(SkDisplayXMLParserError::kUnexpectedType);
+ return false;
+ }
+ }
+dirty:
+ if (displayable)
+ displayable->dirty();
+ return true;
+}
+
+bool SkMemberInfo::setValue(SkAnimateMaker& maker, SkTDOperandArray* arrayStorage,
+ int storageOffset, int maxStorage, SkDisplayable* displayable, SkDisplayTypes outType,
+ SkString& raw) const {
+ return setValue(maker, arrayStorage, storageOffset, maxStorage, displayable, outType, raw.c_str(),
+ raw.size());
+}
+
+bool SkMemberInfo::writeValue(SkDisplayable* displayable, SkTDOperandArray* arrayStorage,
+ int storageOffset, int maxStorage, void* untypedStorage, SkDisplayTypes outType,
+ SkScriptValue& scriptValue) const
+{
+ SkOperand* storage = untypedStorage ? (SkOperand*) untypedStorage : arrayStorage ?
+ arrayStorage->begin() : NULL;
+ if (storage)
+ storage += storageOffset;
+ SkDisplayTypes type = getType();
+ if (fType == SkType_MemberProperty) {
+ if(displayable)
+ displayable->setProperty(propertyIndex(), scriptValue);
+ else {
+ SkASSERT(storageOffset < arrayStorage->count());
+ switch (scriptValue.fType) {
+ case SkType_Boolean:
+ case SkType_Float:
+ case SkType_Int:
+ memcpy(&storage->fScalar, &scriptValue.fOperand.fScalar, sizeof(SkScalar));
+ break;
+ case SkType_Array:
+ memcpy(&storage->fScalar, scriptValue.fOperand.fArray->begin(), scriptValue.fOperand.fArray->count() * sizeof(SkScalar));
+ break;
+ case SkType_String:
+ storage->fString->set(*scriptValue.fOperand.fString);
+ break;
+ default:
+ SkASSERT(0); // type isn't handled yet
+ }
+ }
+ } else if (fType == SkType_MemberFunction) {
+ SkASSERT(scriptValue.fType == SkType_Array);
+ if (displayable)
+ displayable->executeFunction(displayable, this, scriptValue.fOperand.fArray, NULL);
+ else {
+ int count = scriptValue.fOperand.fArray->count();
+ // SkASSERT(maxStorage == 0 || count == maxStorage);
+ if (arrayStorage->count() == 2)
+ arrayStorage->setCount(2 * count);
+ else {
+ storageOffset *= count;
+ SkASSERT(count + storageOffset <= arrayStorage->count());
+ }
+ memcpy(&(*arrayStorage)[storageOffset], scriptValue.fOperand.fArray->begin(), count * sizeof(SkOperand));
+ }
+
+ } else if (fType == SkType_Array) {
+ SkTypedArray* destArray = (SkTypedArray*) (untypedStorage ? untypedStorage : arrayStorage);
+ SkASSERT(destArray);
+ // destArray->setCount(0);
+ if (scriptValue.fType != SkType_Array) {
+ SkASSERT(type == scriptValue.fType);
+ // SkASSERT(storageOffset + 1 <= maxStorage);
+ destArray->setCount(storageOffset + 1);
+ (*destArray)[storageOffset] = scriptValue.fOperand;
+ } else {
+ if (type == SkType_Unknown) {
+ type = scriptValue.fOperand.fArray->getType();
+ destArray->setType(type);
+ }
+ SkASSERT(type == scriptValue.fOperand.fArray->getType());
+ int count = scriptValue.fOperand.fArray->count();
+ // SkASSERT(storageOffset + count <= maxStorage);
+ destArray->setCount(storageOffset + count);
+ memcpy(destArray->begin() + storageOffset, scriptValue.fOperand.fArray->begin(), sizeof(SkOperand) * count);
+ }
+ } else if (type == SkType_String) {
+ SkString* string = untypedStorage ? (SkString*) untypedStorage : (*arrayStorage)[storageOffset].fString;
+ string->set(*scriptValue.fOperand.fString);
+ } else if (type == SkType_ARGB && outType == SkType_Float) {
+ SkTypedArray* array = scriptValue.fOperand.fArray;
+ SkASSERT(scriptValue.fType == SkType_Int || scriptValue.fType == SkType_ARGB ||
+ scriptValue.fType == SkType_Array);
+ SkASSERT(scriptValue.fType != SkType_Array || (array != NULL &&
+ array->getType() == SkType_Int));
+ int numberOfColors = scriptValue.fType == SkType_Array ? array->count() : 1;
+ int numberOfComponents = numberOfColors * 4;
+ // SkASSERT(maxStorage == 0 || maxStorage == numberOfComponents);
+ if (maxStorage == 0)
+ arrayStorage->setCount(numberOfComponents);
+ for (int index = 0; index < numberOfColors; index++) {
+ SkColor color = scriptValue.fType == SkType_Array ?
+ (SkColor) array->begin()[index].fS32 : (SkColor) scriptValue.fOperand.fS32;
+ storage[0].fScalar = SkIntToScalar(SkColorGetA(color));
+ storage[1].fScalar = SkIntToScalar(SkColorGetR(color));
+ storage[2].fScalar = SkIntToScalar(SkColorGetG(color));
+ storage[3].fScalar = SkIntToScalar(SkColorGetB(color));
+ storage += 4;
+ }
+ } else if (SkDisplayType::IsStruct(NULL /* !!! maker*/, type)) {
+ if (scriptValue.fType != SkType_Array)
+ return true; // error
+ SkASSERT(sizeof(SkScalar) == sizeof(SkOperand)); // !!! no 64 bit pointer support yet
+ int count = scriptValue.fOperand.fArray->count();
+ if (count > 0) {
+ SkASSERT(fCount == count);
+ memcpy(storage, scriptValue.fOperand.fArray->begin(), count * sizeof(SkOperand));
+ }
+ } else if (scriptValue.fType == SkType_Array) {
+ SkASSERT(scriptValue.fOperand.fArray->getType() == type);
+ SkASSERT(scriptValue.fOperand.fArray->count() == getCount());
+ memcpy(storage, scriptValue.fOperand.fArray->begin(), getCount() * sizeof(SkOperand));
+ } else {
+ memcpy(storage, &scriptValue.fOperand, sizeof(SkOperand));
+ }
+ return false;
+}
+
+
+//void SkMemberInfo::setValue(SkDisplayable* displayable, const char value[], const char name[]) const {
+// void* valuePtr = (void*) ((char*) displayable + fOffset);
+// switch (fType) {
+// case SkType_Point3D: {
+// static const char xyz[] = "x|y|z";
+// int index = find_one(xyz, name);
+// SkASSERT(index >= 0);
+// valuePtr = (void*) ((char*) valuePtr + index * sizeof(SkScalar));
+// } break;
+// default:
+// SkASSERT(0);
+// }
+// SkParse::FindScalar(value, (SkScalar*) valuePtr);
+// displayable->dirty();
+//}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+// Find Nth memberInfo
+const SkMemberInfo* SkMemberInfo::Find(const SkMemberInfo info[], int count, int* index) {
+ SkASSERT(*index >= 0);
+ if (info->fType == SkType_BaseClassInfo) {
+ const SkMemberInfo* inherited = (SkMemberInfo*) info->fName;
+ const SkMemberInfo* result = SkMemberInfo::Find(inherited, info->fCount, index);
+ if (result != NULL)
+ return result;
+ if (--count == 0)
+ return NULL;
+ info++;
+ }
+ SkASSERT(info->fName);
+ SkASSERT(info->fType != SkType_BaseClassInfo);
+ if (*index >= count) {
+ *index -= count;
+ return NULL;
+ }
+ return &info[*index];
+}
+
+// Find named memberinfo
+const SkMemberInfo* SkMemberInfo::Find(const SkMemberInfo info[], int count, const char** matchPtr) {
+ const char* match = *matchPtr;
+ if (info->fType == SkType_BaseClassInfo) {
+ const SkMemberInfo* inherited = (SkMemberInfo*) info->fName;
+ const SkMemberInfo* result = SkMemberInfo::Find(inherited, info->fCount, matchPtr);
+ if (result != NULL)
+ return result;
+ if (--count == 0)
+ return NULL;
+ info++;
+ }
+ SkASSERT(info->fName);
+ SkASSERT(info->fType != SkType_BaseClassInfo);
+ int index = SkStrSearch(&info->fName, count, match, sizeof(*info));
+ if (index < 0 || index >= count)
+ return NULL;
+ return &info[index];
+}
+
+const SkMemberInfo* SkMemberInfo::getInherited() const {
+ return (SkMemberInfo*) fName;
+}
+
+#endif // SK_USE_CONDENSED_INFO == 0
+
+#if 0
+bool SkMemberInfo::SetValue(void* valuePtr, const char value[], SkDisplayTypes type,
+ int count) {
+ switch (type) {
+ case SkType_Animate:
+ case SkType_BaseBitmap:
+ case SkType_Bitmap:
+ case SkType_Dash:
+ case SkType_Displayable:
+ case SkType_Drawable:
+ case SkType_Matrix:
+ case SkType_Path:
+ case SkType_Text:
+ case SkType_3D_Patch:
+ return false; // ref to object; caller must resolve
+ case SkType_MSec: {
+ SkParse::FindMSec(value, (SkMSec*) valuePtr);
+ } break;
+ case SkType_3D_Point:
+ case SkType_Point:
+ // case SkType_PointArray:
+ case SkType_ScalarArray:
+ SkParse::FindScalars(value, (SkScalar*) valuePtr, count);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ return true;
+}
+#endif
diff --git a/animator/SkMemberInfo.h b/animator/SkMemberInfo.h
new file mode 100644
index 00000000..a1b19411
--- /dev/null
+++ b/animator/SkMemberInfo.h
@@ -0,0 +1,270 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkMemberInfo_DEFINED
+#define SkMemberInfo_DEFINED
+
+#if defined SK_BUILD_CONDENSED
+ #define SK_USE_CONDENSED_INFO 0
+#endif
+
+#include "SkDisplayType.h"
+#include "SkScript.h"
+#include "SkString.h"
+#include "SkIntArray.h"
+
+class SkAnimateMaker;
+class SkDisplayable;
+class SkScriptEngine;
+
+// temporary hacks until name change is more complete
+#define SkFloat SkScalar
+#define SkInt SkS32
+
+struct SkMemberInfo {
+ //!!! alternative:
+ // if fCount == 0, record is member property
+ // then fType can be type, so caller doesn't have to check
+#if SK_USE_CONDENSED_INFO == 0
+ const char* fName; // may be NULL for anonymous functions
+ size_t fOffset; // if negative, is index into member pointer table (for properties and functions)
+ SkDisplayTypes fType;
+ int fCount; // for properties, actual type (count is always assumed to be 1)
+#else
+ unsigned char fName;
+ signed char fOffset;
+ unsigned char fType;
+ signed char fCount;
+#endif
+ SkDisplayTypes arrayType() const {
+ SkASSERT(fType == SkType_Array);
+ return (SkDisplayTypes) fCount; // hack, but worth it?
+ }
+ int functionIndex() const {
+ SkASSERT(fType == SkType_MemberFunction);
+ return (signed) fOffset > 0 ? -1 + (int) fOffset : -1 - (int) fOffset;
+ }
+ bool getArrayValue(const SkDisplayable* displayable, int index, SkOperand* value) const;
+ int getCount() const {
+ return fType == SkType_MemberProperty || fType == SkType_Array ||
+ fType == SkType_MemberFunction ? 1 : fCount;
+ }
+ const SkMemberInfo* getInherited() const;
+ size_t getSize(const SkDisplayable* ) const;
+ void getString(const SkDisplayable* , SkString** string) const;
+ SkDisplayTypes getType() const {
+ return fType == SkType_MemberProperty || fType == SkType_Array ||
+ fType == SkType_MemberFunction ? (SkDisplayTypes) fCount : (SkDisplayTypes) fType;
+ }
+ void getValue(const SkDisplayable* , SkOperand values[], int count) const;
+ bool isEnum() const;
+ const char* mapEnums(const char* match, int* value) const;
+ void* memberData(const SkDisplayable* displayable) const {
+ SkASSERT(fType != SkType_MemberProperty && fType != SkType_MemberFunction);
+ return (void*) ((const char*) displayable + fOffset);
+ }
+ int propertyIndex() const {
+ SkASSERT(fType == SkType_MemberProperty);
+ return (signed) fOffset > 0 ? -1 + (int) fOffset : -1 - (int) fOffset;
+ }
+ SkDisplayTypes propertyType() const {
+ SkASSERT(fType == SkType_MemberProperty || fType == SkType_Array);
+ return (SkDisplayTypes) fCount; // hack, but worth it?
+ }
+ void setMemberData(SkDisplayable* displayable, const void* child, size_t size) const {
+ SkASSERT(fType != SkType_MemberProperty && fType != SkType_MemberFunction);
+ memcpy((char*) displayable + fOffset, child, size);
+ }
+ void setString(SkDisplayable* , SkString* ) const;
+ void setValue(SkDisplayable* , const SkOperand values[], int count) const;
+ bool setValue(SkAnimateMaker& , SkTDOperandArray* storage,
+ int storageOffset, int maxStorage, SkDisplayable* ,
+ SkDisplayTypes outType, const char value[], size_t len) const;
+ bool setValue(SkAnimateMaker& , SkTDOperandArray* storage,
+ int storageOffset, int maxStorage, SkDisplayable* ,
+ SkDisplayTypes outType, SkString& str) const;
+// void setValue(SkDisplayable* , const char value[], const char name[]) const;
+ bool writeValue(SkDisplayable* displayable, SkTDOperandArray* arrayStorage,
+ int storageOffset, int maxStorage, void* untypedStorage, SkDisplayTypes outType,
+ SkScriptValue& scriptValue) const;
+#if SK_USE_CONDENSED_INFO == 0
+ static const SkMemberInfo* Find(const SkMemberInfo [], int count, int* index);
+ static const SkMemberInfo* Find(const SkMemberInfo [], int count, const char** name);
+#else
+ static const SkMemberInfo* Find(SkDisplayTypes type, int* index);
+ static const SkMemberInfo* Find(SkDisplayTypes type, const char** name);
+#endif
+ static size_t GetSize(SkDisplayTypes type); // size of simple types only
+// static bool SetValue(void* value, const char* name, SkDisplayTypes , int count);
+};
+
+#define SK_MEMBER(_member, _type) \
+ { #_member, SK_OFFSETOF(BASE_CLASS, _member), SkType_##_type, \
+ sizeof(((BASE_CLASS*) 1)->_member) / sizeof(SkScalar) }
+
+#define SK_MEMBER_ALIAS(_member, _alias, _type) \
+ { #_member, SK_OFFSETOF(BASE_CLASS, _alias), SkType_##_type, \
+ sizeof(((BASE_CLASS*) 1)->_alias) / sizeof(SkScalar) }
+
+#define SK_MEMBER_ARRAY(_member, _type) \
+ { #_member, SK_OFFSETOF(BASE_CLASS, _member), SkType_Array, \
+ (int) SkType_##_type }
+
+#define SK_MEMBER_INHERITED \
+ { (const char*) INHERITED::fInfo, 0, SkType_BaseClassInfo, INHERITED::fInfoCount }
+
+// #define SK_MEMBER_KEY_TYPE(_member, _type)
+// {#_member, (size_t) -1, SkType_##_type, 0}
+
+#define SK_FUNCTION(_member) \
+ k_##_member##Function
+
+#define SK_PROPERTY(_member) \
+ k_##_member##Property
+
+#define SK_MEMBER_DYNAMIC_FUNCTION(_member, _type) \
+ {#_member, (size_t) (+1 + SK_FUNCTION(_member)), SkType_MemberFunction, \
+ (int) SkType_##_type }
+
+#define SK_MEMBER_DYNAMIC_PROPERTY(_member, _type) \
+ {#_member, (size_t) (1 + SK_PROPERTY(_member)), SkType_MemberProperty, \
+ (int) SkType_##_type }
+
+#define SK_MEMBER_FUNCTION(_member, _type) \
+ {#_member, (size_t) (-1 - SK_FUNCTION(_member)), SkType_MemberFunction, \
+ (int) SkType_##_type }
+
+#define SK_MEMBER_PROPERTY(_member, _type) \
+ {#_member, (size_t) (-1 - SK_PROPERTY(_member)), SkType_MemberProperty, \
+ (int) SkType_##_type }
+
+#if SK_USE_CONDENSED_INFO == 0
+
+#define DECLARE_PRIVATE_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ virtual const SkMemberInfo* getMember(int index); \
+ virtual const SkMemberInfo* getMember(const char name[]); \
+ typedef Sk##_type BASE_CLASS
+
+#define DECLARE_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ virtual const SkMemberInfo* getMember(int index); \
+ virtual const SkMemberInfo* getMember(const char name[]); \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef Sk##_type BASE_CLASS
+
+#define DECLARE_DRAW_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ virtual const SkMemberInfo* getMember(int index); \
+ virtual const SkMemberInfo* getMember(const char name[]); \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef SkDraw##_type BASE_CLASS
+
+#define DECLARE_DISPLAY_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ virtual const SkMemberInfo* getMember(int index); \
+ virtual const SkMemberInfo* getMember(const char name[]); \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef SkDisplay##_type BASE_CLASS
+
+#define DECLARE_EMPTY_MEMBER_INFO(_type) \
+public: \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; }
+
+#define DECLARE_EXTRAS_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ virtual const SkMemberInfo* getMember(int index); \
+ virtual const SkMemberInfo* getMember(const char name[]); \
+ SkDisplayTypes fType; \
+ virtual SkDisplayTypes getType() const { return fType; } \
+ typedef _type BASE_CLASS
+
+#define DECLARE_NO_VIRTUALS_MEMBER_INFO(_type) \
+public: \
+ static const SkMemberInfo fInfo[]; \
+ static const int fInfoCount; \
+ typedef Sk##_type BASE_CLASS
+
+#define DEFINE_GET_MEMBER(_class) \
+ const SkMemberInfo* _class::getMember(int index) { \
+ const SkMemberInfo* result = SkMemberInfo::Find(fInfo, SK_ARRAY_COUNT(fInfo), &index); \
+ return result; \
+ } \
+ const SkMemberInfo* _class::getMember(const char name[]) { \
+ const SkMemberInfo* result = SkMemberInfo::Find(fInfo, SK_ARRAY_COUNT(fInfo), &name); \
+ return result; \
+ } \
+ const int _class::fInfoCount = SK_ARRAY_COUNT(fInfo)
+
+#define DEFINE_NO_VIRTUALS_GET_MEMBER(_class) \
+ const int _class::fInfoCount = SK_ARRAY_COUNT(fInfo)
+
+#else
+
+#define DECLARE_PRIVATE_MEMBER_INFO(_type) \
+public: \
+ typedef Sk##_type BASE_CLASS
+
+#define DECLARE_MEMBER_INFO(_type) \
+public: \
+ virtual const SkMemberInfo* getMember(int index) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &index); } \
+ virtual const SkMemberInfo* getMember(const char name[]) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &name); } \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef Sk##_type BASE_CLASS
+
+#define DECLARE_DRAW_MEMBER_INFO(_type) \
+public: \
+ virtual const SkMemberInfo* getMember(int index) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &index); } \
+ virtual const SkMemberInfo* getMember(const char name[]) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &name); } \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef SkDraw##_type BASE_CLASS
+
+#define DECLARE_DISPLAY_MEMBER_INFO(_type) \
+public: \
+ virtual const SkMemberInfo* getMember(int index) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &index); } \
+ virtual const SkMemberInfo* getMember(const char name[]) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &name); } \
+ virtual SkDisplayTypes getType() const { return SkType_##_type; } \
+ typedef SkDisplay##_type BASE_CLASS
+
+#define DECLARE_EXTRAS_MEMBER_INFO(_type) \
+public: \
+ virtual const SkMemberInfo* getMember(int index) { \
+ return SkDisplayType::GetMember(NULL, SkType_##_type, &index); } \
+ virtual const SkMemberInfo* getMember(const char name[]) { \
+ return SkDisplayType::GetMember(NULL, fType, &name); } \
+ SkDisplayTypes fType; \
+ virtual SkDisplayTypes getType() const { return fType; } \
+ typedef _type BASE_CLASS
+
+#define DECLARE_NO_VIRTUALS_MEMBER_INFO(_type) \
+public: \
+ typedef Sk##_type BASE_CLASS
+
+#define DEFINE_GET_MEMBER(_class)
+#define DEFINE_NO_VIRTUALS_GET_MEMBER(_class)
+
+#endif
+
+#endif // SkMemberInfo_DEFINED
diff --git a/animator/SkOpArray.cpp b/animator/SkOpArray.cpp
new file mode 100644
index 00000000..94298cc8
--- /dev/null
+++ b/animator/SkOpArray.cpp
@@ -0,0 +1,23 @@
+
+/*
+ * 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 "SkOpArray.h"
+
+SkOpArray::SkOpArray() : fType(SkOperand2::kNoType) {
+}
+
+SkOpArray::SkOpArray(SkOperand2::OpType type) : fType(type) {
+}
+
+bool SkOpArray::getIndex(int index, SkOperand2* operand) {
+ if (index >= count()) {
+ SkASSERT(0);
+ return false;
+ }
+ *operand = begin()[index];
+ return true;
+}
diff --git a/animator/SkOpArray.h b/animator/SkOpArray.h
new file mode 100644
index 00000000..260bf78b
--- /dev/null
+++ b/animator/SkOpArray.h
@@ -0,0 +1,29 @@
+
+/*
+ * 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 SkOpArray_DEFINED
+#define SkOpArray_DEFINED
+
+#include "SkOperand2.h"
+#include "SkTDArray_Experimental.h"
+
+typedef SkLongArray(SkOperand2) SkTDOperand2Array;
+
+class SkOpArray : public SkTDOperand2Array {
+public:
+ SkOpArray();
+ SkOpArray(SkOperand2::OpType type);
+ bool getIndex(int index, SkOperand2* operand);
+ SkOperand2::OpType getType() { return fType; }
+ void setType(SkOperand2::OpType type) {
+ fType = type;
+ }
+protected:
+ SkOperand2::OpType fType;
+};
+
+#endif // SkOpArray_DEFINED
diff --git a/animator/SkOperand.h b/animator/SkOperand.h
new file mode 100644
index 00000000..0bd1fa34
--- /dev/null
+++ b/animator/SkOperand.h
@@ -0,0 +1,46 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkOperand_DEFINED
+#define SkOperand_DEFINED
+
+#include "SkDisplayType.h"
+
+class SkTypedArray;
+class SkDisplayable;
+class SkDrawable;
+class SkString;
+
+union SkOperand {
+// SkOperand() {}
+// SkOperand(SkScalar scalar) : fScalar(scalar) {}
+ SkTypedArray* fArray;
+ SkDisplayable* fDisplayable;
+ SkDrawable* fDrawable;
+ void* fObject;
+ int32_t fS32;
+ SkMSec fMSec;
+ SkScalar fScalar;
+ SkString* fString;
+};
+
+struct SkScriptValue {
+ SkOperand fOperand;
+ SkDisplayTypes fType;
+ SkTypedArray* getArray() { SkASSERT(fType == SkType_Array); return fOperand.fArray; }
+ SkDisplayable* getDisplayable() { SkASSERT(fType == SkType_Displayable); return fOperand.fDisplayable; }
+ SkDrawable* getDrawable() { SkASSERT(fType == SkType_Drawable); return fOperand.fDrawable; }
+ int32_t getS32(SkAnimateMaker* maker) { SkASSERT(fType == SkType_Int || fType == SkType_Boolean ||
+ SkDisplayType::IsEnum(maker, fType)); return fOperand.fS32; }
+ SkMSec getMSec() { SkASSERT(fType == SkType_MSec); return fOperand.fMSec; }
+ SkScalar getScalar() { SkASSERT(fType == SkType_Float); return fOperand.fScalar; }
+ SkString* getString() { SkASSERT(fType == SkType_String); return fOperand.fString; }
+};
+
+#endif // SkOperand_DEFINED
diff --git a/animator/SkOperand2.h b/animator/SkOperand2.h
new file mode 100644
index 00000000..f844b6b4
--- /dev/null
+++ b/animator/SkOperand2.h
@@ -0,0 +1,54 @@
+
+/*
+ * 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 SkOperand2_DEFINED
+#define SkOperand2_DEFINED
+
+#include "SkScalar.h"
+
+class SkOpArray;
+class SkString;
+
+union SkOperand2 {
+ enum OpType {
+ kNoType,
+ kS32 = 1,
+ kScalar = 2,
+ kString = 4,
+ kArray = 8,
+ kObject = 16
+ };
+ SkOpArray* fArray;
+ void* fObject;
+ size_t fReference;
+ int32_t fS32;
+ SkScalar fScalar;
+ SkString* fString;
+};
+
+struct SkScriptValue2 {
+ enum IsConstant {
+ kConstant,
+ kVariable
+ };
+ enum IsWritten {
+ kUnwritten,
+ kWritten
+ };
+ SkOperand2 fOperand;
+ SkOperand2::OpType fType : 8;
+ IsConstant fIsConstant : 8;
+ IsWritten fIsWritten : 8;
+ SkOpArray* getArray() { SkASSERT(fType == SkOperand2::kArray); return fOperand.fArray; }
+ void* getObject() { SkASSERT(fType == SkOperand2::kObject); return fOperand.fObject; }
+ int32_t getS32() { SkASSERT(fType == SkOperand2::kS32); return fOperand.fS32; }
+ SkScalar getScalar() { SkASSERT(fType == SkOperand2::kScalar); return fOperand.fScalar; }
+ SkString* getString() { SkASSERT(fType == SkOperand2::kString); return fOperand.fString; }
+ bool isConstant() const { return fIsConstant == kConstant; }
+};
+
+#endif // SkOperand2_DEFINED
diff --git a/animator/SkOperandInterpolator.h b/animator/SkOperandInterpolator.h
new file mode 100644
index 00000000..adbe69f1
--- /dev/null
+++ b/animator/SkOperandInterpolator.h
@@ -0,0 +1,47 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkOperandInterpolator_DEFINED
+#define SkOperandInterpolator_DEFINED
+
+#include "SkDisplayType.h"
+#include "SkInterpolator.h"
+#include "SkOperand.h"
+
+class SkOperandInterpolator : public SkInterpolatorBase {
+public:
+ SkOperandInterpolator();
+ SkOperandInterpolator(int elemCount, int frameCount, SkDisplayTypes type);
+ SkOperand* getValues() { return fValues; }
+ int getValuesCount() { return fFrameCount * fElemCount; }
+ void reset(int elemCount, int frameCount, SkDisplayTypes type);
+
+ /** Add or replace a key frame, copying the values[] data into the interpolator.
+ @param index The index of this frame (frames must be ordered by time)
+ @param time The millisecond time for this frame
+ @param values The array of values [elemCount] for this frame. The data is copied
+ into the interpolator.
+ @param blend A positive scalar specifying how to blend between this and the next key frame.
+ [0...1) is a cubic lag/log/lag blend (slow to change at the beginning and end)
+ 1 is a linear blend (default)
+ (1...inf) is a cubic log/lag/log blend (fast to change at the beginning and end)
+ */
+ bool setKeyFrame(int index, SkMSec time, const SkOperand values[], SkScalar blend = SK_Scalar1);
+ Result timeToValues(SkMSec time, SkOperand values[]) const;
+ SkDEBUGCODE(static void UnitTest();)
+private:
+ SkDisplayTypes fType;
+ SkOperand* fValues; // pointer into fStorage
+#ifdef SK_DEBUG
+ SkOperand(* fValuesArray)[10];
+#endif
+ typedef SkInterpolatorBase INHERITED;
+};
+
+#endif // SkOperandInterpolator_DEFINED
diff --git a/animator/SkOperandIterpolator.cpp b/animator/SkOperandIterpolator.cpp
new file mode 100644
index 00000000..7822ee28
--- /dev/null
+++ b/animator/SkOperandIterpolator.cpp
@@ -0,0 +1,149 @@
+
+/*
+ * 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 "SkOperandInterpolator.h"
+#include "SkScript.h"
+
+SkOperandInterpolator::SkOperandInterpolator() {
+ INHERITED::reset(0, 0);
+ fType = SkType_Unknown;
+}
+
+SkOperandInterpolator::SkOperandInterpolator(int elemCount, int frameCount,
+ SkDisplayTypes type)
+{
+ this->reset(elemCount, frameCount, type);
+}
+
+void SkOperandInterpolator::reset(int elemCount, int frameCount, SkDisplayTypes type)
+{
+// SkASSERT(type == SkType_String || type == SkType_Float || type == SkType_Int ||
+// type == SkType_Displayable || type == SkType_Drawable);
+ INHERITED::reset(elemCount, frameCount);
+ fType = type;
+ fStorage = sk_malloc_throw((sizeof(SkOperand) * elemCount + sizeof(SkTimeCode)) * frameCount);
+ fTimes = (SkTimeCode*) fStorage;
+ fValues = (SkOperand*) ((char*) fStorage + sizeof(SkTimeCode) * frameCount);
+#ifdef SK_DEBUG
+ fTimesArray = (SkTimeCode(*)[10]) fTimes;
+ fValuesArray = (SkOperand(*)[10]) fValues;
+#endif
+}
+
+bool SkOperandInterpolator::setKeyFrame(int index, SkMSec time, const SkOperand values[], SkScalar blend)
+{
+ SkASSERT(values != NULL);
+ blend = SkScalarPin(blend, 0, SK_Scalar1);
+
+ bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
+ SkASSERT(success);
+ if (success) {
+ SkTimeCode* timeCode = &fTimes[index];
+ timeCode->fTime = time;
+ timeCode->fBlend[0] = SK_Scalar1 - blend;
+ timeCode->fBlend[1] = 0;
+ timeCode->fBlend[2] = 0;
+ timeCode->fBlend[3] = SK_Scalar1 - blend;
+ SkOperand* dst = &fValues[fElemCount * index];
+ memcpy(dst, values, fElemCount * sizeof(SkOperand));
+ }
+ return success;
+}
+
+SkInterpolatorBase::Result SkOperandInterpolator::timeToValues(SkMSec time, SkOperand values[]) const
+{
+ SkScalar T;
+ int index;
+ SkBool exact;
+ Result result = timeToT(time, &T, &index, &exact);
+ if (values)
+ {
+ const SkOperand* nextSrc = &fValues[index * fElemCount];
+
+ if (exact)
+ memcpy(values, nextSrc, fElemCount * sizeof(SkScalar));
+ else
+ {
+ SkASSERT(index > 0);
+
+ const SkOperand* prevSrc = nextSrc - fElemCount;
+
+ if (fType == SkType_Float || fType == SkType_3D_Point) {
+ for (int i = fElemCount - 1; i >= 0; --i)
+ values[i].fScalar = SkScalarInterp(prevSrc[i].fScalar, nextSrc[i].fScalar, T);
+ } else if (fType == SkType_Int || fType == SkType_MSec) {
+ for (int i = fElemCount - 1; i >= 0; --i) {
+ int32_t a = prevSrc[i].fS32;
+ int32_t b = nextSrc[i].fS32;
+ values[i].fS32 = a + SkScalarRound((b - a) * T);
+ }
+ } else
+ memcpy(values, prevSrc, sizeof(SkOperand) * fElemCount);
+ }
+ }
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+#ifdef SK_SUPPORT_UNITTEST
+ static SkOperand* iset(SkOperand array[3], int a, int b, int c)
+ {
+ array[0].fScalar = SkIntToScalar(a);
+ array[1].fScalar = SkIntToScalar(b);
+ array[2].fScalar = SkIntToScalar(c);
+ return array;
+ }
+#endif
+
+void SkOperandInterpolator::UnitTest()
+{
+#ifdef SK_SUPPORT_UNITTEST
+ SkOperandInterpolator inter(3, 2, SkType_Float);
+ SkOperand v1[3], v2[3], v[3], vv[3];
+ Result result;
+
+ inter.setKeyFrame(0, 100, iset(v1, 10, 20, 30), 0);
+ inter.setKeyFrame(1, 200, iset(v2, 110, 220, 330));
+
+ result = inter.timeToValues(0, v);
+ SkASSERT(result == kFreezeStart_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(99, v);
+ SkASSERT(result == kFreezeStart_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(100, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(200, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, v2, sizeof(v)) == 0);
+
+ result = inter.timeToValues(201, v);
+ SkASSERT(result == kFreezeEnd_Result);
+ SkASSERT(memcmp(v, v2, sizeof(v)) == 0);
+
+ result = inter.timeToValues(150, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, iset(vv, 60, 120, 180), sizeof(v)) == 0);
+
+ result = inter.timeToValues(125, v);
+ SkASSERT(result == kNormal_Result);
+ result = inter.timeToValues(175, v);
+ SkASSERT(result == kNormal_Result);
+#endif
+}
+
+#endif
diff --git a/animator/SkPaintParts.cpp b/animator/SkPaintParts.cpp
new file mode 100644
index 00000000..22119a4f
--- /dev/null
+++ b/animator/SkPaintParts.cpp
@@ -0,0 +1,101 @@
+
+/*
+ * 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 "SkPaintParts.h"
+#include "SkDrawPaint.h"
+#ifdef SK_DUMP_ENABLED
+#include "SkDisplayList.h"
+#include "SkDump.h"
+#endif
+
+SkPaintPart::SkPaintPart() : fPaint(NULL) {
+}
+
+SkDisplayable* SkPaintPart::getParent() const {
+ return fPaint;
+}
+
+bool SkPaintPart::setParent(SkDisplayable* parent) {
+ SkASSERT(parent != NULL);
+ if (parent->isPaint() == false)
+ return true;
+ fPaint = (SkDrawPaint*) parent;
+ return false;
+}
+
+
+// SkDrawMaskFilter
+bool SkDrawMaskFilter::add() {
+ if (fPaint->maskFilter != (SkDrawMaskFilter*) -1)
+ return true;
+ fPaint->maskFilter = this;
+ fPaint->fOwnsMaskFilter = true;
+ return false;
+}
+
+SkMaskFilter* SkDrawMaskFilter::getMaskFilter() {
+ return NULL;
+}
+
+
+// SkDrawPathEffect
+bool SkDrawPathEffect::add() {
+ if (fPaint->isPaint()) {
+ if (fPaint->pathEffect != (SkDrawPathEffect*) -1)
+ return true;
+ fPaint->pathEffect = this;
+ fPaint->fOwnsPathEffect = true;
+ return false;
+ }
+ fPaint->add(NULL, this);
+ return false;
+}
+
+SkPathEffect* SkDrawPathEffect::getPathEffect() {
+ return NULL;
+}
+
+
+// SkDrawShader
+SkShader* SkDrawShader::getShader() {
+ return NULL;
+}
+
+
+// Typeface
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDrawTypeface::fInfo[] = {
+ SK_MEMBER(fontName, String),
+ SK_MEMBER(style, FontStyle)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDrawTypeface);
+
+SkDrawTypeface::SkDrawTypeface() : style (SkTypeface::kNormal){
+}
+
+bool SkDrawTypeface::add() {
+ if (fPaint->typeface != (SkDrawTypeface*) -1)
+ return true;
+ fPaint->typeface = this;
+ fPaint->fOwnsTypeface = true;
+ return false;
+}
+
+#ifdef SK_DUMP_ENABLED
+void SkDrawTypeface::dump(SkAnimateMaker*) {
+ SkDebugf("%*s<typeface fontName=\"%s\" ", SkDisplayList::fIndent, "", fontName.c_str());
+ SkString string;
+ SkDump::GetEnumString(SkType_FontStyle, style, &string);
+ SkDebugf("style=\"%s\" />\n", string.c_str());
+}
+#endif
diff --git a/animator/SkPaintParts.h b/animator/SkPaintParts.h
new file mode 100644
index 00000000..964bc359
--- /dev/null
+++ b/animator/SkPaintParts.h
@@ -0,0 +1,75 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkPaintParts_DEFINED
+#define SkPaintParts_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkPaint.h"
+#include "SkShader.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+class SkDrawPaint;
+class SkDrawMatrix;
+
+class SkPaintPart : public SkDisplayable {
+public:
+ SkPaintPart();
+ virtual bool add() = 0;
+ virtual SkDisplayable* getParent() const;
+ virtual bool setParent(SkDisplayable* parent);
+#ifdef SK_DEBUG
+ virtual bool isPaintPart() const { return true; }
+#endif
+protected:
+ SkDrawPaint* fPaint;
+};
+
+class SkDrawMaskFilter : public SkPaintPart {
+ DECLARE_EMPTY_MEMBER_INFO(MaskFilter);
+ virtual SkMaskFilter* getMaskFilter();
+protected:
+ virtual bool add();
+};
+
+class SkDrawPathEffect : public SkPaintPart {
+ DECLARE_EMPTY_MEMBER_INFO(PathEffect);
+ virtual SkPathEffect* getPathEffect();
+protected:
+ virtual bool add();
+};
+
+class SkDrawShader : public SkPaintPart {
+ DECLARE_DRAW_MEMBER_INFO(Shader);
+ SkDrawShader();
+ virtual SkShader* getShader();
+protected:
+ virtual bool add();
+ void addPostlude(SkShader* shader);
+ SkDrawMatrix* matrix;
+ int /*SkShader::TileMode*/ tileMode;
+};
+
+class SkDrawTypeface : public SkPaintPart {
+ DECLARE_DRAW_MEMBER_INFO(Typeface);
+ SkDrawTypeface();
+#ifdef SK_DUMP_ENABLED
+ virtual void dump(SkAnimateMaker *);
+#endif
+ SkTypeface* getTypeface() {
+ return SkTypeface::CreateFromName(fontName.c_str(), style); }
+protected:
+ virtual bool add();
+ SkString fontName;
+ SkTypeface::Style style;
+};
+
+#endif // SkPaintParts_DEFINED
diff --git a/animator/SkParseSVGPath.cpp b/animator/SkParseSVGPath.cpp
new file mode 100644
index 00000000..4b548e28
--- /dev/null
+++ b/animator/SkParseSVGPath.cpp
@@ -0,0 +1,234 @@
+
+/*
+ * 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 <ctype.h>
+#include "SkDrawPath.h"
+#include "SkParse.h"
+#include "SkPoint.h"
+#include "SkUtils.h"
+#define QUADRATIC_APPROXIMATION 1
+
+#if QUADRATIC_APPROXIMATION
+////////////////////////////////////////////////////////////////////////////////////
+//functions to approximate a cubic using two quadratics
+
+// midPt sets the first argument to be the midpoint of the other two
+// it is used by quadApprox
+static inline void midPt(SkPoint& dest,const SkPoint& a,const SkPoint& b)
+{
+ dest.set(SkScalarAve(a.fX, b.fX),SkScalarAve(a.fY, b.fY));
+}
+// quadApprox - makes an approximation, which we hope is faster
+static void quadApprox(SkPath &fPath, const SkPoint &p0, const SkPoint &p1, const SkPoint &p2)
+{
+ //divide the cubic up into two cubics, then convert them into quadratics
+ //define our points
+ SkPoint c,j,k,l,m,n,o,p,q, mid;
+ fPath.getLastPt(&c);
+ midPt(j, p0, c);
+ midPt(k, p0, p1);
+ midPt(l, p1, p2);
+ midPt(o, j, k);
+ midPt(p, k, l);
+ midPt(q, o, p);
+ //compute the first half
+ m.set(SkScalarHalf(3*j.fX - c.fX), SkScalarHalf(3*j.fY - c.fY));
+ n.set(SkScalarHalf(3*o.fX -q.fX), SkScalarHalf(3*o.fY - q.fY));
+ midPt(mid,m,n);
+ fPath.quadTo(mid,q);
+ c = q;
+ //compute the second half
+ m.set(SkScalarHalf(3*p.fX - c.fX), SkScalarHalf(3*p.fY - c.fY));
+ n.set(SkScalarHalf(3*l.fX -p2.fX),SkScalarHalf(3*l.fY -p2.fY));
+ midPt(mid,m,n);
+ fPath.quadTo(mid,p2);
+}
+#endif
+
+
+static inline bool is_between(int c, int min, int max)
+{
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_ws(int c)
+{
+ return is_between(c, 1, 32);
+}
+
+static inline bool is_digit(int c)
+{
+ return is_between(c, '0', '9');
+}
+
+static inline bool is_sep(int c)
+{
+ return is_ws(c) || c == ',';
+}
+
+static const char* skip_ws(const char str[])
+{
+ SkASSERT(str);
+ while (is_ws(*str))
+ str++;
+ return str;
+}
+
+static const char* skip_sep(const char str[])
+{
+ SkASSERT(str);
+ while (is_sep(*str))
+ str++;
+ return str;
+}
+
+static const char* find_points(const char str[], SkPoint value[], int count,
+ bool isRelative, SkPoint* relative)
+{
+ str = SkParse::FindScalars(str, &value[0].fX, count * 2);
+ if (isRelative) {
+ for (int index = 0; index < count; index++) {
+ value[index].fX += relative->fX;
+ value[index].fY += relative->fY;
+ }
+ }
+ return str;
+}
+
+static const char* find_scalar(const char str[], SkScalar* value,
+ bool isRelative, SkScalar relative)
+{
+ str = SkParse::FindScalar(str, value);
+ if (isRelative)
+ *value += relative;
+ return str;
+}
+
+void SkDrawPath::parseSVG() {
+ fPath.reset();
+ const char* data = d.c_str();
+ SkPoint f = {0, 0};
+ SkPoint c = {0, 0};
+ SkPoint lastc = {0, 0};
+ SkPoint points[3];
+ char op = '\0';
+ char previousOp = '\0';
+ bool relative = false;
+ do {
+ data = skip_ws(data);
+ if (data[0] == '\0')
+ break;
+ char ch = data[0];
+ if (is_digit(ch) || ch == '-' || ch == '+') {
+ if (op == '\0')
+ return;
+ }
+ else {
+ op = ch;
+ relative = false;
+ if (islower(op)) {
+ op = (char) toupper(op);
+ relative = true;
+ }
+ data++;
+ data = skip_sep(data);
+ }
+ switch (op) {
+ case 'M':
+ data = find_points(data, points, 1, relative, &c);
+ fPath.moveTo(points[0]);
+ op = 'L';
+ c = points[0];
+ break;
+ case 'L':
+ data = find_points(data, points, 1, relative, &c);
+ fPath.lineTo(points[0]);
+ c = points[0];
+ break;
+ case 'H': {
+ SkScalar x;
+ data = find_scalar(data, &x, relative, c.fX);
+ fPath.lineTo(x, c.fY);
+ c.fX = x;
+ }
+ break;
+ case 'V': {
+ SkScalar y;
+ data = find_scalar(data, &y, relative, c.fY);
+ fPath.lineTo(c.fX, y);
+ c.fY = y;
+ }
+ break;
+ case 'C':
+ data = find_points(data, points, 3, relative, &c);
+ goto cubicCommon;
+ case 'S':
+ data = find_points(data, &points[1], 2, relative, &c);
+ points[0] = c;
+ if (previousOp == 'C' || previousOp == 'S') {
+ points[0].fX -= lastc.fX - c.fX;
+ points[0].fY -= lastc.fY - c.fY;
+ }
+ cubicCommon:
+ // if (data[0] == '\0')
+ // return;
+#if QUADRATIC_APPROXIMATION
+ quadApprox(fPath, points[0], points[1], points[2]);
+#else //this way just does a boring, slow old cubic
+ fPath.cubicTo(points[0], points[1], points[2]);
+#endif
+ //if we are using the quadApprox, lastc is what it would have been if we had used
+ //cubicTo
+ lastc = points[1];
+ c = points[2];
+ break;
+ case 'Q': // Quadratic Bezier Curve
+ data = find_points(data, points, 2, relative, &c);
+ goto quadraticCommon;
+ case 'T':
+ data = find_points(data, &points[1], 1, relative, &c);
+ points[0] = points[1];
+ if (previousOp == 'Q' || previousOp == 'T') {
+ points[0].fX = c.fX * 2 - lastc.fX;
+ points[0].fY = c.fY * 2 - lastc.fY;
+ }
+ quadraticCommon:
+ fPath.quadTo(points[0], points[1]);
+ lastc = points[0];
+ c = points[1];
+ break;
+ case 'Z':
+ fPath.close();
+#if 0 // !!! still a bug?
+ if (fPath.isEmpty() && (f.fX != 0 || f.fY != 0)) {
+ c.fX -= SkScalar.Epsilon; // !!! enough?
+ fPath.moveTo(c);
+ fPath.lineTo(f);
+ fPath.close();
+ }
+#endif
+ c = f;
+ op = '\0';
+ break;
+ case '~': {
+ SkPoint args[2];
+ data = find_points(data, args, 2, false, NULL);
+ fPath.moveTo(args[0].fX, args[0].fY);
+ fPath.lineTo(args[1].fX, args[1].fY);
+ }
+ break;
+ default:
+ SkASSERT(0);
+ return;
+ }
+ if (previousOp == 0)
+ f = c;
+ previousOp = op;
+ } while (data[0] > 0);
+}
diff --git a/animator/SkPathParts.cpp b/animator/SkPathParts.cpp
new file mode 100644
index 00000000..efc7c335
--- /dev/null
+++ b/animator/SkPathParts.cpp
@@ -0,0 +1,318 @@
+
+/*
+ * 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 "SkPathParts.h"
+#include "SkAnimateMaker.h"
+#include "SkDrawMatrix.h"
+#include "SkDrawRectangle.h"
+#include "SkDrawPath.h"
+
+SkPathPart::SkPathPart() : fPath(NULL) {
+}
+
+void SkPathPart::dirty() {
+ fPath->dirty();
+}
+
+SkDisplayable* SkPathPart::getParent() const {
+ return fPath;
+}
+
+bool SkPathPart::setParent(SkDisplayable* parent) {
+ SkASSERT(parent != NULL);
+ if (parent->isPath() == false)
+ return true;
+ fPath = (SkDrawPath*) parent;
+ return false;
+}
+
+// MoveTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkMoveTo::fInfo[] = {
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkMoveTo);
+
+SkMoveTo::SkMoveTo() : x(0), y(0) {
+}
+
+bool SkMoveTo::add() {
+ fPath->fPath.moveTo(x, y);
+ return false;
+}
+
+
+// RMoveTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRMoveTo::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRMoveTo);
+
+bool SkRMoveTo::add() {
+ fPath->fPath.rMoveTo(x, y);
+ return false;
+}
+
+
+// LineTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkLineTo::fInfo[] = {
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkLineTo);
+
+SkLineTo::SkLineTo() : x(0), y(0) {
+}
+
+bool SkLineTo::add() {
+ fPath->fPath.lineTo(x, y);
+ return false;
+}
+
+
+// RLineTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRLineTo::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRLineTo);
+
+bool SkRLineTo::add() {
+ fPath->fPath.rLineTo(x, y);
+ return false;
+}
+
+
+// QuadTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkQuadTo::fInfo[] = {
+ SK_MEMBER(x1, Float),
+ SK_MEMBER(x2, Float),
+ SK_MEMBER(y1, Float),
+ SK_MEMBER(y2, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkQuadTo);
+
+SkQuadTo::SkQuadTo() : x1(0), y1(0), x2(0), y2(0) {
+}
+
+bool SkQuadTo::add() {
+ fPath->fPath.quadTo(x1, y1, x2, y2);
+ return false;
+}
+
+
+// RQuadTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRQuadTo::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRQuadTo);
+
+bool SkRQuadTo::add() {
+ fPath->fPath.rQuadTo(x1, y1, x2, y2);
+ return false;
+}
+
+
+// CubicTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkCubicTo::fInfo[] = {
+ SK_MEMBER(x1, Float),
+ SK_MEMBER(x2, Float),
+ SK_MEMBER(x3, Float),
+ SK_MEMBER(y1, Float),
+ SK_MEMBER(y2, Float),
+ SK_MEMBER(y3, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkCubicTo);
+
+SkCubicTo::SkCubicTo() : x1(0), y1(0), x2(0), y2(0), x3(0), y3(0) {
+}
+
+bool SkCubicTo::add() {
+ fPath->fPath.cubicTo(x1, y1, x2, y2, x3, y3);
+ return false;
+}
+
+
+// RCubicTo
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkRCubicTo::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkRCubicTo);
+
+bool SkRCubicTo::add() {
+ fPath->fPath.rCubicTo(x1, y1, x2, y2, x3, y3);
+ return false;
+}
+
+
+// SkClose
+bool SkClose::add() {
+ fPath->fPath.close();
+ return false;
+}
+
+
+// SkAddGeom
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddGeom::fInfo[] = {
+ SK_MEMBER(direction, PathDirection)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddGeom);
+
+SkAddGeom::SkAddGeom() : direction(SkPath::kCCW_Direction) {
+}
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddRect::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER_ALIAS(bottom, fRect.fBottom, Float),
+ SK_MEMBER_ALIAS(left, fRect.fLeft, Float),
+ SK_MEMBER_ALIAS(right, fRect.fRight, Float),
+ SK_MEMBER_ALIAS(top, fRect.fTop, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddRect);
+
+SkAddRect::SkAddRect() {
+ fRect.setEmpty();
+}
+
+bool SkAddRect::add() {
+ fPath->fPath.addRect(fRect, (SkPath::Direction) direction);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddOval::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddOval);
+
+bool SkAddOval::add() {
+ fPath->fPath.addOval(fRect, (SkPath::Direction) direction);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddCircle::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(radius, Float),
+ SK_MEMBER(x, Float),
+ SK_MEMBER(y, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddCircle);
+
+SkAddCircle::SkAddCircle() : radius(0), x(0), y(0) {
+}
+
+bool SkAddCircle::add() {
+ fPath->fPath.addCircle(x, y, radius, (SkPath::Direction) direction);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddRoundRect::fInfo[] = {
+ SK_MEMBER_INHERITED,
+ SK_MEMBER(rx, Float),
+ SK_MEMBER(ry, Float)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddRoundRect);
+
+SkAddRoundRect::SkAddRoundRect() : rx(0), ry(0) {
+}
+
+bool SkAddRoundRect::add() {
+ fPath->fPath.addRoundRect(fRect, rx, ry, (SkPath::Direction) direction);
+ return false;
+}
+
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkAddPath::fInfo[] = {
+ SK_MEMBER(matrix, Matrix),
+ SK_MEMBER(path, Path)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkAddPath);
+
+SkAddPath::SkAddPath() : matrix(NULL), path(NULL) {
+}
+
+bool SkAddPath::add() {
+ SkASSERT (path != NULL);
+ if (matrix)
+ fPath->fPath.addPath(path->fPath, matrix->getMatrix());
+ else
+ fPath->fPath.addPath(path->fPath);
+ return false;
+}
diff --git a/animator/SkPathParts.h b/animator/SkPathParts.h
new file mode 100644
index 00000000..f82aa745
--- /dev/null
+++ b/animator/SkPathParts.h
@@ -0,0 +1,164 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkPathParts_DEFINED
+#define SkPathParts_DEFINED
+
+#include "SkDisplayable.h"
+#include "SkMemberInfo.h"
+#include "SkPath.h"
+
+class SkDrawPath;
+class SkDrawMatrix;
+
+class SkPathPart : public SkDisplayable {
+public:
+ SkPathPart();
+ virtual bool add() = 0;
+ virtual void dirty();
+ virtual SkDisplayable* getParent() const;
+ virtual bool setParent(SkDisplayable* parent);
+#ifdef SK_DEBUG
+ virtual bool isPathPart() const { return true; }
+#endif
+protected:
+ SkDrawPath* fPath;
+};
+
+class SkMoveTo : public SkPathPart {
+ DECLARE_MEMBER_INFO(MoveTo);
+ SkMoveTo();
+ virtual bool add();
+protected:
+ SkScalar x;
+ SkScalar y;
+};
+
+class SkRMoveTo : public SkMoveTo {
+ DECLARE_MEMBER_INFO(RMoveTo);
+ virtual bool add();
+private:
+ typedef SkMoveTo INHERITED;
+};
+
+class SkLineTo : public SkPathPart {
+ DECLARE_MEMBER_INFO(LineTo);
+ SkLineTo();
+ virtual bool add();
+protected:
+ SkScalar x;
+ SkScalar y;
+};
+
+class SkRLineTo : public SkLineTo {
+ DECLARE_MEMBER_INFO(RLineTo);
+ virtual bool add();
+private:
+ typedef SkLineTo INHERITED;
+};
+
+class SkQuadTo : public SkPathPart {
+ DECLARE_MEMBER_INFO(QuadTo);
+ SkQuadTo();
+ virtual bool add();
+protected:
+ SkScalar x1;
+ SkScalar y1;
+ SkScalar x2;
+ SkScalar y2;
+};
+
+class SkRQuadTo : public SkQuadTo {
+ DECLARE_MEMBER_INFO(RQuadTo);
+ virtual bool add();
+private:
+ typedef SkQuadTo INHERITED;
+};
+
+class SkCubicTo : public SkPathPart {
+ DECLARE_MEMBER_INFO(CubicTo);
+ SkCubicTo();
+ virtual bool add();
+protected:
+ SkScalar x1;
+ SkScalar y1;
+ SkScalar x2;
+ SkScalar y2;
+ SkScalar x3;
+ SkScalar y3;
+};
+
+class SkRCubicTo : public SkCubicTo {
+ DECLARE_MEMBER_INFO(RCubicTo);
+ virtual bool add();
+private:
+ typedef SkCubicTo INHERITED;
+};
+
+class SkClose : public SkPathPart {
+ DECLARE_EMPTY_MEMBER_INFO(Close);
+ virtual bool add();
+};
+
+class SkAddGeom : public SkPathPart {
+ DECLARE_PRIVATE_MEMBER_INFO(AddGeom);
+ SkAddGeom();
+protected:
+ int /*SkPath::Direction*/ direction;
+};
+
+class SkAddRect : public SkAddGeom {
+ DECLARE_MEMBER_INFO(AddRect);
+ SkAddRect();
+ virtual bool add();
+protected:
+ SkRect fRect;
+private:
+ typedef SkAddGeom INHERITED;
+};
+
+class SkAddOval : public SkAddRect {
+ DECLARE_MEMBER_INFO(AddOval);
+ virtual bool add();
+private:
+ typedef SkAddRect INHERITED;
+};
+
+class SkAddCircle : public SkAddGeom {
+ DECLARE_MEMBER_INFO(AddCircle);
+ SkAddCircle();
+ virtual bool add();
+private:
+ SkScalar radius;
+ SkScalar x;
+ SkScalar y;
+ typedef SkAddGeom INHERITED;
+};
+
+class SkAddRoundRect : public SkAddRect {
+ DECLARE_MEMBER_INFO(AddRoundRect);
+ SkAddRoundRect();
+ virtual bool add();
+private:
+ SkScalar rx;
+ SkScalar ry;
+ typedef SkAddRect INHERITED;
+};
+
+class SkAddPath : public SkPathPart {
+ DECLARE_MEMBER_INFO(AddPath);
+ SkAddPath();
+ virtual bool add();
+private:
+ typedef SkPathPart INHERITED;
+ SkDrawMatrix* matrix;
+ SkDrawPath* path;
+};
+
+#endif // SkPathParts_DEFINED
diff --git a/animator/SkPostParts.cpp b/animator/SkPostParts.cpp
new file mode 100644
index 00000000..7c0931ea
--- /dev/null
+++ b/animator/SkPostParts.cpp
@@ -0,0 +1,56 @@
+
+/*
+ * 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 "SkPostParts.h"
+#include "SkDisplayPost.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkDataInput::fInfo[] = {
+ SK_MEMBER_INHERITED
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkDataInput);
+
+SkDataInput::SkDataInput() : fParent(NULL) {}
+
+bool SkDataInput::add() {
+ SkASSERT(name.size() > 0);
+ const char* dataName = name.c_str();
+ if (fInt != (int) SK_NaN32)
+ fParent->fEvent.setS32(dataName, fInt);
+ else if (SkScalarIsNaN(fFloat) == false)
+ fParent->fEvent.setScalar(dataName, fFloat);
+ else if (string.size() > 0)
+ fParent->fEvent.setString(dataName, string);
+// else
+// SkASSERT(0);
+ return false;
+}
+
+void SkDataInput::dirty() {
+ fParent->dirty();
+}
+
+SkDisplayable* SkDataInput::getParent() const {
+ return fParent;
+}
+
+bool SkDataInput::setParent(SkDisplayable* displayable) {
+ if (displayable->isPost() == false)
+ return true;
+ fParent = (SkPost*) displayable;
+ return false;
+}
+
+void SkDataInput::onEndElement(SkAnimateMaker&) {
+ add();
+}
diff --git a/animator/SkPostParts.h b/animator/SkPostParts.h
new file mode 100644
index 00000000..4101b2b2
--- /dev/null
+++ b/animator/SkPostParts.h
@@ -0,0 +1,31 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkPostParts_DEFINED
+#define SkPostParts_DEFINED
+
+#include "SkDisplayInput.h"
+
+class SkPost;
+
+class SkDataInput: public SkInput {
+ DECLARE_MEMBER_INFO(DataInput);
+ SkDataInput();
+ bool add();
+ virtual void dirty();
+ virtual SkDisplayable* getParent() const;
+ virtual void onEndElement(SkAnimateMaker& );
+ virtual bool setParent(SkDisplayable* );
+protected:
+ SkPost* fParent;
+ typedef SkInput INHERITED;
+ friend class SkPost;
+};
+
+#endif // SkPostParts_DEFINED
diff --git a/animator/SkScript.cpp b/animator/SkScript.cpp
new file mode 100644
index 00000000..14ca6259
--- /dev/null
+++ b/animator/SkScript.cpp
@@ -0,0 +1,1895 @@
+
+/*
+ * 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 "SkScript.h"
+#include "SkMath.h"
+#include "SkParse.h"
+#include "SkString.h"
+#include "SkTypedArray.h"
+
+/* things to do
+ ? re-enable support for struct literals (e.g., for initializing points or rects)
+ {x:1, y:2}
+ ? use standard XML / script notation like document.getElementById("canvas");
+ finish support for typed arrays
+ ? allow indexing arrays by string
+ this could map to the 'name' attribute of a given child of an array
+ ? allow multiple types in the array
+ remove SkDisplayType.h // from SkOperand.h
+ merge type and operand arrays into scriptvalue array
+*/
+
+#ifdef SK_DEBUG
+static const char* errorStrings[] = {
+ "array index of out bounds", // kArrayIndexOutOfBounds
+ "could not find reference id", // kCouldNotFindReferencedID
+ "dot operator expects object", // kDotOperatorExpectsObject
+ "error in array index", // kErrorInArrrayIndex
+ "error in function parameters", // kErrorInFunctionParameters
+ "expected array", // kExpectedArray
+ "expected boolean expression", // kExpectedBooleanExpression
+ "expected field name", // kExpectedFieldName
+ "expected hex", // kExpectedHex
+ "expected int for condition operator", // kExpectedIntForConditionOperator
+ "expected number", // kExpectedNumber
+ "expected number for array index", // kExpectedNumberForArrayIndex
+ "expected operator", // kExpectedOperator
+ "expected token", // kExpectedToken
+ "expected token before dot operator", // kExpectedTokenBeforeDotOperator
+ "expected value", // kExpectedValue
+ "handle member failed", // kHandleMemberFailed
+ "handle member function failed", // kHandleMemberFunctionFailed
+ "handle unbox failed", // kHandleUnboxFailed
+ "index out of range", // kIndexOutOfRange
+ "mismatched array brace", // kMismatchedArrayBrace
+ "mismatched brackets", // kMismatchedBrackets
+ "no function handler found", // kNoFunctionHandlerFound
+ "premature end", // kPrematureEnd
+ "too many parameters", // kTooManyParameters
+ "type conversion failed", // kTypeConversionFailed
+ "unterminated string" // kUnterminatedString
+};
+#endif
+
+const SkScriptEngine::SkOperatorAttributes SkScriptEngine::gOpAttributes[] = {
+ { kNoType, kNoType, kNoBias }, // kUnassigned,
+ { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsString }, // kAdd
+ // kAddInt = kAdd,
+ { kNoType, kNoType, kNoBias }, // kAddScalar,
+ { kNoType, kNoType, kNoBias }, // kAddString,
+ { kNoType, kNoType, kNoBias }, // kArrayOp,
+ { kInt, kInt, kNoBias }, // kBitAnd
+ { kNoType, kInt, kNoBias }, // kBitNot
+ { kInt, kInt, kNoBias }, // kBitOr
+ { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kDivide
+ // kDivideInt = kDivide
+ { kNoType, kNoType, kNoBias }, // kDivideScalar
+ { kNoType, kNoType, kNoBias }, // kElse
+ { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, // kEqual
+ // kEqualInt = kEqual
+ { kNoType, kNoType, kNoBias }, // kEqualScalar
+ { kNoType, kNoType, kNoBias }, // kEqualString
+ { kInt, kNoType, kNoBias }, // kFlipOps
+ { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, // kGreaterEqual
+ // kGreaterEqualInt = kGreaterEqual
+ { kNoType, kNoType, kNoBias }, // kGreaterEqualScalar
+ { kNoType, kNoType, kNoBias }, // kGreaterEqualString
+ { kNoType, kNoType, kNoBias }, // kIf
+ { kNoType, kInt, kNoBias }, // kLogicalAnd (really, ToBool)
+ { kNoType, kInt, kNoBias }, // kLogicalNot
+ { kInt, kInt, kNoBias }, // kLogicalOr
+ { kNoType, SkOpType(kInt | kScalar), kNoBias }, // kMinus
+ // kMinusInt = kMinus
+ { kNoType, kNoType, kNoBias }, // kMinusScalar
+ { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kModulo
+ // kModuloInt = kModulo
+ { kNoType, kNoType, kNoBias }, // kModuloScalar
+ { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kMultiply
+ // kMultiplyInt = kMultiply
+ { kNoType, kNoType, kNoBias }, // kMultiplyScalar
+ { kNoType, kNoType, kNoBias }, // kParen
+ { kInt, kInt, kNoBias }, // kShiftLeft
+ { kInt, kInt, kNoBias }, // kShiftRight
+ { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kSubtract
+ // kSubtractInt = kSubtract
+ { kNoType, kNoType, kNoBias }, // kSubtractScalar
+ { kInt, kInt, kNoBias } // kXor
+};
+
+// Note that the real precedence for () [] is '2'
+// but here, precedence means 'while an equal or smaller precedence than the current operator
+// is on the stack, process it. This allows 3+5*2 to defer the add until after the multiply
+// is preformed, since the add precedence is not smaller than multiply.
+// But, (3*4 does not process the '(', since brackets are greater than all other precedences
+#define kBracketPrecedence 16
+#define kIfElsePrecedence 15
+
+const signed char SkScriptEngine::gPrecedence[] = {
+ -1, // kUnassigned,
+ 6, // kAdd,
+ // kAddInt = kAdd,
+ 6, // kAddScalar,
+ 6, // kAddString, // string concat
+ kBracketPrecedence, // kArrayOp,
+ 10, // kBitAnd,
+ 4, // kBitNot,
+ 12, // kBitOr,
+ 5, // kDivide,
+ // kDivideInt = kDivide,
+ 5, // kDivideScalar,
+ kIfElsePrecedence, // kElse,
+ 9, // kEqual,
+ // kEqualInt = kEqual,
+ 9, // kEqualScalar,
+ 9, // kEqualString,
+ -1, // kFlipOps,
+ 8, // kGreaterEqual,
+ // kGreaterEqualInt = kGreaterEqual,
+ 8, // kGreaterEqualScalar,
+ 8, // kGreaterEqualString,
+ kIfElsePrecedence, // kIf,
+ 13, // kLogicalAnd,
+ 4, // kLogicalNot,
+ 14, // kLogicalOr,
+ 4, // kMinus,
+ // kMinusInt = kMinus,
+ 4, // kMinusScalar,
+ 5, // kModulo,
+ // kModuloInt = kModulo,
+ 5, // kModuloScalar,
+ 5, // kMultiply,
+ // kMultiplyInt = kMultiply,
+ 5, // kMultiplyScalar,
+ kBracketPrecedence, // kParen,
+ 7, // kShiftLeft,
+ 7, // kShiftRight, // signed
+ 6, // kSubtract,
+ // kSubtractInt = kSubtract,
+ 6, // kSubtractScalar,
+ 11, // kXor
+};
+
+static inline bool is_between(int c, int min, int max)
+{
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_ws(int c)
+{
+ return is_between(c, 1, 32);
+}
+
+static int token_length(const char* start) {
+ char ch = start[0];
+ if (! is_between(ch, 'a' , 'z') && ! is_between(ch, 'A', 'Z') && ch != '_' && ch != '$')
+ return -1;
+ int length = 0;
+ do
+ ch = start[++length];
+ while (is_between(ch, 'a' , 'z') || is_between(ch, 'A', 'Z') || is_between(ch, '0', '9') ||
+ ch == '_' || ch == '$');
+ return length;
+}
+
+SkScriptEngine::SkScriptEngine(SkOpType returnType) :
+ fTokenLength(0), fReturnType(returnType), fError(kNoError)
+{
+ SkSuppress noInitialSuppress;
+ noInitialSuppress.fOperator = kUnassigned;
+ noInitialSuppress.fOpStackDepth = 0;
+ noInitialSuppress.fSuppress = false;
+ noInitialSuppress.fElse = 0;
+ fSuppressStack.push(noInitialSuppress);
+ *fOpStack.push() = kParen;
+ fTrackArray.appendClear();
+ fTrackString.appendClear();
+}
+
+SkScriptEngine::~SkScriptEngine() {
+ for (SkString** stringPtr = fTrackString.begin(); stringPtr < fTrackString.end(); stringPtr++)
+ delete *stringPtr;
+ for (SkTypedArray** arrayPtr = fTrackArray.begin(); arrayPtr < fTrackArray.end(); arrayPtr++)
+ delete *arrayPtr;
+}
+
+int SkScriptEngine::arithmeticOp(char ch, char nextChar, bool lastPush) {
+ SkOp op = kUnassigned;
+ bool reverseOperands = false;
+ bool negateResult = false;
+ int advance = 1;
+ switch (ch) {
+ case '+':
+ // !!! ignoring unary plus as implemented here has the side effect of
+ // suppressing errors like +"hi"
+ if (lastPush == false) // unary plus, don't push an operator
+ goto returnAdv;
+ op = kAdd;
+ break;
+ case '-':
+ op = lastPush ? kSubtract : kMinus;
+ break;
+ case '*':
+ op = kMultiply;
+ break;
+ case '/':
+ op = kDivide;
+ break;
+ case '>':
+ if (nextChar == '>') {
+ op = kShiftRight;
+ goto twoChar;
+ }
+ op = kGreaterEqual;
+ if (nextChar == '=')
+ goto twoChar;
+ reverseOperands = negateResult = true;
+ break;
+ case '<':
+ if (nextChar == '<') {
+ op = kShiftLeft;
+ goto twoChar;
+ }
+ op = kGreaterEqual;
+ reverseOperands = nextChar == '=';
+ negateResult = ! reverseOperands;
+ advance += reverseOperands;
+ break;
+ case '=':
+ if (nextChar == '=') {
+ op = kEqual;
+ goto twoChar;
+ }
+ break;
+ case '!':
+ if (nextChar == '=') {
+ op = kEqual;
+ negateResult = true;
+twoChar:
+ advance++;
+ break;
+ }
+ op = kLogicalNot;
+ break;
+ case '?':
+ op = kIf;
+ break;
+ case ':':
+ op = kElse;
+ break;
+ case '^':
+ op = kXor;
+ break;
+ case '(':
+ *fOpStack.push() = kParen; // push even if eval is suppressed
+ goto returnAdv;
+ case '&':
+ SkASSERT(nextChar != '&');
+ op = kBitAnd;
+ break;
+ case '|':
+ SkASSERT(nextChar != '|');
+ op = kBitOr;
+ break;
+ case '%':
+ op = kModulo;
+ break;
+ case '~':
+ op = kBitNot;
+ break;
+ }
+ if (op == kUnassigned)
+ return 0;
+ if (fSuppressStack.top().fSuppress == false) {
+ signed char precedence = gPrecedence[op];
+ do {
+ int idx = 0;
+ SkOp compare;
+ do {
+ compare = fOpStack.index(idx);
+ if ((compare & kArtificialOp) == 0)
+ break;
+ idx++;
+ } while (true);
+ signed char topPrecedence = gPrecedence[compare];
+ SkASSERT(topPrecedence != -1);
+ if (topPrecedence > precedence || (topPrecedence == precedence &&
+ gOpAttributes[op].fLeftType == kNoType)) {
+ break;
+ }
+ if (processOp() == false)
+ return 0; // error
+ } while (true);
+ if (negateResult)
+ *fOpStack.push() = (SkOp) (kLogicalNot | kArtificialOp);
+ fOpStack.push(op);
+ if (reverseOperands)
+ *fOpStack.push() = (SkOp) (kFlipOps | kArtificialOp);
+ }
+returnAdv:
+ return advance;
+}
+
+void SkScriptEngine::boxCallBack(_boxCallBack func, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fBoxCallBack = func;
+ commonCallBack(kBox, callBack, userStorage);
+}
+
+void SkScriptEngine::commonCallBack(CallBackType type, UserCallBack& callBack, void* userStorage) {
+ callBack.fCallBackType = type;
+ callBack.fUserStorage = userStorage;
+ *fUserCallBacks.prepend() = callBack;
+}
+
+bool SkScriptEngine::convertParams(SkTDArray<SkScriptValue>& params,
+ const SkFunctionParamType* paramTypes, int paramCount) {
+ if (params.count() > paramCount) {
+ fError = kTooManyParameters;
+ return false; // too many parameters passed
+ }
+ for (int index = 0; index < params.count(); index++) {
+ if (convertTo((SkDisplayTypes) paramTypes[index], &params[index]) == false)
+ return false;
+ }
+ return true;
+}
+
+bool SkScriptEngine::convertTo(SkDisplayTypes toType, SkScriptValue* value ) {
+ SkDisplayTypes type = value->fType;
+ if (type == toType)
+ return true;
+ if (ToOpType(type) == kObject) {
+#if 0 // !!! I want object->string to get string from displaystringtype, not id
+ if (ToOpType(toType) == kString) {
+ bool success = handleObjectToString(value->fOperand.fObject);
+ if (success == false)
+ return false;
+ SkOpType type;
+ fTypeStack.pop(&type);
+ value->fType = ToDisplayType(type);
+ fOperandStack.pop(&value->fOperand);
+ return true;
+ }
+#endif
+ if (handleUnbox(value) == false) {
+ fError = kHandleUnboxFailed;
+ return false;
+ }
+ return convertTo(toType, value);
+ }
+ return ConvertTo(this, toType, value);
+}
+
+bool SkScriptEngine::evaluateDot(const char*& script, bool suppressed) {
+ size_t fieldLength = token_length(++script); // skip dot
+ if (fieldLength == 0) {
+ fError = kExpectedFieldName;
+ return false;
+ }
+ const char* field = script;
+ script += fieldLength;
+ bool success = handleProperty(suppressed);
+ if (success == false) {
+ fError = kCouldNotFindReferencedID; // note: never generated by standard animator plugins
+ return false;
+ }
+ return evaluateDotParam(script, suppressed, field, fieldLength);
+}
+
+bool SkScriptEngine::evaluateDotParam(const char*& script, bool suppressed,
+ const char* field, size_t fieldLength) {
+ void* object;
+ if (suppressed)
+ object = NULL;
+ else {
+ if (fTypeStack.top() != kObject) {
+ fError = kDotOperatorExpectsObject;
+ return false;
+ }
+ object = fOperandStack.top().fObject;
+ fTypeStack.pop();
+ fOperandStack.pop();
+ }
+ char ch; // see if it is a simple member or a function
+ while (is_ws(ch = script[0]))
+ script++;
+ bool success = true;
+ if (ch != '(') {
+ if (suppressed == false) {
+ if ((success = handleMember(field, fieldLength, object)) == false)
+ fError = kHandleMemberFailed;
+ }
+ } else {
+ SkTDArray<SkScriptValue> params;
+ *fBraceStack.push() = kFunctionBrace;
+ success = functionParams(&script, params);
+ if (success && suppressed == false &&
+ (success = handleMemberFunction(field, fieldLength, object, params)) == false)
+ fError = kHandleMemberFunctionFailed;
+ }
+ return success;
+}
+
+bool SkScriptEngine::evaluateScript(const char** scriptPtr, SkScriptValue* value) {
+#ifdef SK_DEBUG
+ const char** original = scriptPtr;
+#endif
+ bool success;
+ const char* inner;
+ if (strncmp(*scriptPtr, "#script:", sizeof("#script:") - 1) == 0) {
+ *scriptPtr += sizeof("#script:") - 1;
+ if (fReturnType == kNoType || fReturnType == kString) {
+ success = innerScript(scriptPtr, value);
+ if (success == false)
+ goto end;
+ inner = value->fOperand.fString->c_str();
+ scriptPtr = &inner;
+ }
+ }
+ {
+ success = innerScript(scriptPtr, value);
+ if (success == false)
+ goto end;
+ const char* script = *scriptPtr;
+ char ch;
+ while (is_ws(ch = script[0]))
+ script++;
+ if (ch != '\0') {
+ // error may trigger on scripts like "50,0" that were intended to be written as "[50, 0]"
+ fError = kPrematureEnd;
+ success = false;
+ }
+ }
+end:
+#ifdef SK_DEBUG
+ if (success == false) {
+ SkDebugf("script failed: %s", *original);
+ if (fError)
+ SkDebugf(" %s", errorStrings[fError - 1]);
+ SkDebugf("\n");
+ }
+#endif
+ return success;
+}
+
+void SkScriptEngine::forget(SkTypedArray* array) {
+ if (array->getType() == SkType_String) {
+ for (int index = 0; index < array->count(); index++) {
+ SkString* string = (*array)[index].fString;
+ int found = fTrackString.find(string);
+ if (found >= 0)
+ fTrackString.remove(found);
+ }
+ return;
+ }
+ if (array->getType() == SkType_Array) {
+ for (int index = 0; index < array->count(); index++) {
+ SkTypedArray* child = (*array)[index].fArray;
+ forget(child); // forgets children of child
+ int found = fTrackArray.find(child);
+ if (found >= 0)
+ fTrackArray.remove(found);
+ }
+ }
+}
+
+void SkScriptEngine::functionCallBack(_functionCallBack func, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fFunctionCallBack = func;
+ commonCallBack(kFunction, callBack, userStorage);
+}
+
+bool SkScriptEngine::functionParams(const char** scriptPtr, SkTDArray<SkScriptValue>& params) {
+ (*scriptPtr)++; // skip open paren
+ *fOpStack.push() = kParen;
+ *fBraceStack.push() = kFunctionBrace;
+ SkBool suppressed = fSuppressStack.top().fSuppress;
+ do {
+ SkScriptValue value;
+ bool success = innerScript(scriptPtr, suppressed ? NULL : &value);
+ if (success == false) {
+ fError = kErrorInFunctionParameters;
+ return false;
+ }
+ if (suppressed)
+ continue;
+ *params.append() = value;
+ } while ((*scriptPtr)[-1] == ',');
+ fBraceStack.pop();
+ fOpStack.pop(); // pop paren
+ (*scriptPtr)++; // advance beyond close paren
+ return true;
+}
+
+#ifdef SK_DEBUG
+bool SkScriptEngine::getErrorString(SkString* str) const {
+ if (fError)
+ str->set(errorStrings[fError - 1]);
+ return fError != 0;
+}
+#endif
+
+bool SkScriptEngine::innerScript(const char** scriptPtr, SkScriptValue* value) {
+ const char* script = *scriptPtr;
+ char ch;
+ bool lastPush = false;
+ bool success = true;
+ int opBalance = fOpStack.count();
+ int baseBrace = fBraceStack.count();
+ int suppressBalance = fSuppressStack.count();
+ while ((ch = script[0]) != '\0') {
+ if (is_ws(ch)) {
+ script++;
+ continue;
+ }
+ SkBool suppressed = fSuppressStack.top().fSuppress;
+ SkOperand operand;
+ const char* dotCheck;
+ if (fBraceStack.count() > baseBrace) {
+#if 0 // disable support for struct brace
+ if (ch == ':') {
+ SkASSERT(fTokenLength > 0);
+ SkASSERT(fBraceStack.top() == kStructBrace);
+ ++script;
+ SkASSERT(fDisplayable);
+ SkString token(fToken, fTokenLength);
+ fTokenLength = 0;
+ const char* tokenName = token.c_str();
+ const SkMemberInfo* tokenInfo SK_INIT_TO_AVOID_WARNING;
+ if (suppressed == false) {
+ SkDisplayTypes type = fInfo->getType();
+ tokenInfo = SkDisplayType::GetMember(type, &tokenName);
+ SkASSERT(tokenInfo);
+ }
+ SkScriptValue tokenValue;
+ success = innerScript(&script, &tokenValue); // terminate and return on comma, close brace
+ SkASSERT(success);
+ if (suppressed == false) {
+ if (tokenValue.fType == SkType_Displayable) {
+ SkASSERT(SkDisplayType::IsDisplayable(tokenInfo->getType()));
+ fDisplayable->setReference(tokenInfo, tokenValue.fOperand.fDisplayable);
+ } else {
+ if (tokenValue.fType != tokenInfo->getType()) {
+ if (convertTo(tokenInfo->getType(), &tokenValue) == false)
+ return false;
+ }
+ tokenInfo->writeValue(fDisplayable, NULL, 0, 0,
+ (void*) ((char*) fInfo->memberData(fDisplayable) + tokenInfo->fOffset + fArrayOffset),
+ tokenInfo->getType(), tokenValue);
+ }
+ }
+ lastPush = false;
+ continue;
+ } else
+#endif
+ if (fBraceStack.top() == kArrayBrace) {
+ SkScriptValue tokenValue;
+ success = innerScript(&script, &tokenValue); // terminate and return on comma, close brace
+ if (success == false) {
+ fError = kErrorInArrrayIndex;
+ return false;
+ }
+ if (suppressed == false) {
+#if 0 // no support for structures for now
+ if (tokenValue.fType == SkType_Structure) {
+ fArrayOffset += (int) fInfo->getSize(fDisplayable);
+ } else
+#endif
+ {
+ SkDisplayTypes type = ToDisplayType(fReturnType);
+ if (fReturnType == kNoType) {
+ // !!! short sighted; in the future, allow each returned array component to carry
+ // its own type, and let caller do any needed conversions
+ if (value->fOperand.fArray->count() == 0)
+ value->fOperand.fArray->setType(type = tokenValue.fType);
+ else
+ type = value->fOperand.fArray->getType();
+ }
+ if (tokenValue.fType != type) {
+ if (convertTo(type, &tokenValue) == false)
+ return false;
+ }
+ *value->fOperand.fArray->append() = tokenValue.fOperand;
+ }
+ }
+ lastPush = false;
+ continue;
+ } else {
+ if (token_length(script) == 0) {
+ fError = kExpectedToken;
+ return false;
+ }
+ }
+ }
+ if (lastPush != false && fTokenLength > 0) {
+ if (ch == '(') {
+ *fBraceStack.push() = kFunctionBrace;
+ if (handleFunction(&script, SkToBool(suppressed)) == false)
+ return false;
+ lastPush = true;
+ continue;
+ } else if (ch == '[') {
+ if (handleProperty(SkToBool(suppressed)) == false)
+ return false; // note: never triggered by standard animator plugins
+ if (handleArrayIndexer(&script, SkToBool(suppressed)) == false)
+ return false;
+ lastPush = true;
+ continue;
+ } else if (ch != '.') {
+ if (handleProperty(SkToBool(suppressed)) == false)
+ return false; // note: never triggered by standard animator plugins
+ lastPush = true;
+ continue;
+ }
+ }
+ if (ch == '0' && (script[1] & ~0x20) == 'X') {
+ if (lastPush != false) {
+ fError = kExpectedOperator;
+ return false;
+ }
+ script += 2;
+ script = SkParse::FindHex(script, (uint32_t*)&operand.fS32);
+ if (script == NULL) {
+ fError = kExpectedHex;
+ return false;
+ }
+ goto intCommon;
+ }
+ if (lastPush == false && ch == '.')
+ goto scalarCommon;
+ if (ch >= '0' && ch <= '9') {
+ if (lastPush != false) {
+ fError = kExpectedOperator;
+ return false;
+ }
+ dotCheck = SkParse::FindS32(script, &operand.fS32);
+ if (dotCheck[0] != '.') {
+ script = dotCheck;
+intCommon:
+ if (suppressed == false)
+ *fTypeStack.push() = kInt;
+ } else {
+scalarCommon:
+ script = SkParse::FindScalar(script, &operand.fScalar);
+ if (suppressed == false)
+ *fTypeStack.push() = kScalar;
+ }
+ if (suppressed == false)
+ fOperandStack.push(operand);
+ lastPush = true;
+ continue;
+ }
+ int length = token_length(script);
+ if (length > 0) {
+ if (lastPush != false) {
+ fError = kExpectedOperator;
+ return false;
+ }
+ fToken = script;
+ fTokenLength = length;
+ script += length;
+ lastPush = true;
+ continue;
+ }
+ char startQuote = ch;
+ if (startQuote == '\'' || startQuote == '\"') {
+ if (lastPush != false) {
+ fError = kExpectedOperator;
+ return false;
+ }
+ operand.fString = new SkString();
+ track(operand.fString);
+ ++script;
+
+ // <mrr> this is a lot of calls to append() one char at at time
+ // how hard to preflight script so we know how much to grow fString by?
+ do {
+ if (script[0] == '\\')
+ ++script;
+ operand.fString->append(script, 1);
+ ++script;
+ if (script[0] == '\0') {
+ fError = kUnterminatedString;
+ return false;
+ }
+ } while (script[0] != startQuote);
+ ++script;
+ if (suppressed == false) {
+ *fTypeStack.push() = kString;
+ fOperandStack.push(operand);
+ }
+ lastPush = true;
+ continue;
+ }
+ ;
+ if (ch == '.') {
+ if (fTokenLength == 0) {
+ SkScriptValue scriptValue;
+ SkDEBUGCODE(scriptValue.fOperand.fObject = NULL);
+ int tokenLength = token_length(++script);
+ const char* token = script;
+ script += tokenLength;
+ if (suppressed == false) {
+ if (fTypeStack.count() == 0) {
+ fError = kExpectedTokenBeforeDotOperator;
+ return false;
+ }
+ SkOpType topType;
+ fTypeStack.pop(&topType);
+ fOperandStack.pop(&scriptValue.fOperand);
+ scriptValue.fType = ToDisplayType(topType);
+ handleBox(&scriptValue);
+ }
+ success = evaluateDotParam(script, SkToBool(suppressed), token, tokenLength);
+ if (success == false)
+ return false;
+ lastPush = true;
+ continue;
+ }
+ // get next token, and evaluate immediately
+ success = evaluateDot(script, SkToBool(suppressed));
+ if (success == false)
+ return false;
+ lastPush = true;
+ continue;
+ }
+ if (ch == '[') {
+ if (lastPush == false) {
+ script++;
+ *fBraceStack.push() = kArrayBrace;
+ if (suppressed)
+ continue;
+ operand.fArray = value->fOperand.fArray = new SkTypedArray(ToDisplayType(fReturnType));
+ track(value->fOperand.fArray);
+ *fTypeStack.push() = (SkOpType) kArray;
+ fOperandStack.push(operand);
+ continue;
+ }
+ if (handleArrayIndexer(&script, SkToBool(suppressed)) == false)
+ return false;
+ lastPush = true;
+ continue;
+ }
+#if 0 // structs not supported for now
+ if (ch == '{') {
+ if (lastPush == false) {
+ script++;
+ *fBraceStack.push() = kStructBrace;
+ if (suppressed)
+ continue;
+ operand.fS32 = 0;
+ *fTypeStack.push() = (SkOpType) kStruct;
+ fOperandStack.push(operand);
+ continue;
+ }
+ SkASSERT(0); // braces in other contexts aren't supported yet
+ }
+#endif
+ if (ch == ')' && fBraceStack.count() > 0) {
+ SkBraceStyle braceStyle = fBraceStack.top();
+ if (braceStyle == kFunctionBrace) {
+ fBraceStack.pop();
+ break;
+ }
+ }
+ if (ch == ',' || ch == ']') {
+ if (ch != ',') {
+ SkBraceStyle match;
+ fBraceStack.pop(&match);
+ if (match != kArrayBrace) {
+ fError = kMismatchedArrayBrace;
+ return false;
+ }
+ }
+ script++;
+ // !!! see if brace or bracket is correct closer
+ break;
+ }
+ char nextChar = script[1];
+ int advance = logicalOp(ch, nextChar);
+ if (advance < 0) // error
+ return false;
+ if (advance == 0)
+ advance = arithmeticOp(ch, nextChar, lastPush);
+ if (advance == 0) // unknown token
+ return false;
+ if (advance > 0)
+ script += advance;
+ lastPush = ch == ']' || ch == ')';
+ }
+ bool suppressed = SkToBool(fSuppressStack.top().fSuppress);
+ if (fTokenLength > 0) {
+ success = handleProperty(suppressed);
+ if (success == false)
+ return false; // note: never triggered by standard animator plugins
+ }
+ while (fOpStack.count() > opBalance) { // leave open paren
+ if ((fError = opError()) != kNoError)
+ return false;
+ if (processOp() == false)
+ return false;
+ }
+ SkOpType topType = fTypeStack.count() > 0 ? fTypeStack.top() : kNoType;
+ if (suppressed == false && topType != fReturnType &&
+ topType == kString && fReturnType != kNoType) { // if result is a string, give handle property a chance to convert it to the property value
+ SkString* string = fOperandStack.top().fString;
+ fToken = string->c_str();
+ fTokenLength = string->size();
+ fOperandStack.pop();
+ fTypeStack.pop();
+ success = handleProperty(SkToBool(fSuppressStack.top().fSuppress));
+ if (success == false) { // if it couldn't convert, return string (error?)
+ SkOperand operand;
+ operand.fS32 = 0;
+ *fTypeStack.push() = kString;
+ operand.fString = string;
+ fOperandStack.push(operand);
+ }
+ }
+ if (value) {
+ if (fOperandStack.count() == 0)
+ return false;
+ SkASSERT(fOperandStack.count() >= 1);
+ SkASSERT(fTypeStack.count() >= 1);
+ fOperandStack.pop(&value->fOperand);
+ SkOpType type;
+ fTypeStack.pop(&type);
+ value->fType = ToDisplayType(type);
+// SkASSERT(value->fType != SkType_Unknown);
+ if (topType != fReturnType && topType == kObject && fReturnType != kNoType) {
+ if (convertTo(ToDisplayType(fReturnType), value) == false)
+ return false;
+ }
+ }
+ while (fSuppressStack.count() > suppressBalance)
+ fSuppressStack.pop();
+ *scriptPtr = script;
+ return true; // no error
+}
+
+void SkScriptEngine::memberCallBack(_memberCallBack member , void* userStorage) {
+ UserCallBack callBack;
+ callBack.fMemberCallBack = member;
+ commonCallBack(kMember, callBack, userStorage);
+}
+
+void SkScriptEngine::memberFunctionCallBack(_memberFunctionCallBack func, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fMemberFunctionCallBack = func;
+ commonCallBack(kMemberFunction, callBack, userStorage);
+}
+
+#if 0
+void SkScriptEngine::objectToStringCallBack(_objectToStringCallBack func, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fObjectToStringCallBack = func;
+ commonCallBack(kObjectToString, callBack, userStorage);
+}
+#endif
+
+bool SkScriptEngine::handleArrayIndexer(const char** scriptPtr, bool suppressed) {
+ SkScriptValue scriptValue;
+ (*scriptPtr)++;
+ *fOpStack.push() = kParen;
+ *fBraceStack.push() = kArrayBrace;
+ SkOpType saveType = fReturnType;
+ fReturnType = kInt;
+ bool success = innerScript(scriptPtr, suppressed == false ? &scriptValue : NULL);
+ if (success == false)
+ return false;
+ fReturnType = saveType;
+ if (suppressed == false) {
+ if (convertTo(SkType_Int, &scriptValue) == false)
+ return false;
+ int index = scriptValue.fOperand.fS32;
+ SkScriptValue scriptValue;
+ SkOpType type;
+ fTypeStack.pop(&type);
+ fOperandStack.pop(&scriptValue.fOperand);
+ scriptValue.fType = ToDisplayType(type);
+ if (type == kObject) {
+ success = handleUnbox(&scriptValue);
+ if (success == false)
+ return false;
+ if (ToOpType(scriptValue.fType) != kArray) {
+ fError = kExpectedArray;
+ return false;
+ }
+ }
+ *fTypeStack.push() = scriptValue.fOperand.fArray->getOpType();
+// SkASSERT(index >= 0);
+ if ((unsigned) index >= (unsigned) scriptValue.fOperand.fArray->count()) {
+ fError = kArrayIndexOutOfBounds;
+ return false;
+ }
+ scriptValue.fOperand = scriptValue.fOperand.fArray->begin()[index];
+ fOperandStack.push(scriptValue.fOperand);
+ }
+ fOpStack.pop(); // pop paren
+ return success;
+}
+
+bool SkScriptEngine::handleBox(SkScriptValue* scriptValue) {
+ bool success = true;
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kBox)
+ continue;
+ success = (*callBack->fBoxCallBack)(callBack->fUserStorage, scriptValue);
+ if (success) {
+ fOperandStack.push(scriptValue->fOperand);
+ *fTypeStack.push() = ToOpType(scriptValue->fType);
+ goto done;
+ }
+ }
+done:
+ return success;
+}
+
+bool SkScriptEngine::handleFunction(const char** scriptPtr, bool suppressed) {
+ SkScriptValue callbackResult;
+ SkTDArray<SkScriptValue> params;
+ SkString functionName(fToken, fTokenLength);
+ fTokenLength = 0;
+ bool success = functionParams(scriptPtr, params);
+ if (success == false)
+ goto done;
+ if (suppressed == true)
+ return true;
+ {
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kFunction)
+ continue;
+ success = (*callBack->fFunctionCallBack)(functionName.c_str(), functionName.size(), params,
+ callBack->fUserStorage, &callbackResult);
+ if (success) {
+ fOperandStack.push(callbackResult.fOperand);
+ *fTypeStack.push() = ToOpType(callbackResult.fType);
+ goto done;
+ }
+ }
+ }
+ fError = kNoFunctionHandlerFound;
+ return false;
+done:
+ return success;
+}
+
+bool SkScriptEngine::handleMember(const char* field, size_t len, void* object) {
+ SkScriptValue callbackResult;
+ bool success = true;
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kMember)
+ continue;
+ success = (*callBack->fMemberCallBack)(field, len, object, callBack->fUserStorage, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkType_String)
+ track(callbackResult.fOperand.fString);
+ fOperandStack.push(callbackResult.fOperand);
+ *fTypeStack.push() = ToOpType(callbackResult.fType);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+bool SkScriptEngine::handleMemberFunction(const char* field, size_t len, void* object, SkTDArray<SkScriptValue>& params) {
+ SkScriptValue callbackResult;
+ bool success = true;
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kMemberFunction)
+ continue;
+ success = (*callBack->fMemberFunctionCallBack)(field, len, object, params,
+ callBack->fUserStorage, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkType_String)
+ track(callbackResult.fOperand.fString);
+ fOperandStack.push(callbackResult.fOperand);
+ *fTypeStack.push() = ToOpType(callbackResult.fType);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+#if 0
+bool SkScriptEngine::handleObjectToString(void* object) {
+ SkScriptValue callbackResult;
+ bool success = true;
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kObjectToString)
+ continue;
+ success = (*callBack->fObjectToStringCallBack)(object,
+ callBack->fUserStorage, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkType_String)
+ track(callbackResult.fOperand.fString);
+ fOperandStack.push(callbackResult.fOperand);
+ *fTypeStack.push() = ToOpType(callbackResult.fType);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+#endif
+
+bool SkScriptEngine::handleProperty(bool suppressed) {
+ SkScriptValue callbackResult;
+ bool success = true;
+ if (suppressed)
+ goto done;
+ success = false; // note that with standard animator-script plugins, callback never returns false
+ {
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kProperty)
+ continue;
+ success = (*callBack->fPropertyCallBack)(fToken, fTokenLength,
+ callBack->fUserStorage, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkType_String && callbackResult.fOperand.fString == NULL) {
+ callbackResult.fOperand.fString = new SkString(fToken, fTokenLength);
+ track(callbackResult.fOperand.fString);
+ }
+ fOperandStack.push(callbackResult.fOperand);
+ *fTypeStack.push() = ToOpType(callbackResult.fType);
+ goto done;
+ }
+ }
+ }
+done:
+ fTokenLength = 0;
+ return success;
+}
+
+bool SkScriptEngine::handleUnbox(SkScriptValue* scriptValue) {
+ bool success = true;
+ for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
+ if (callBack->fCallBackType != kUnbox)
+ continue;
+ success = (*callBack->fUnboxCallBack)(callBack->fUserStorage, scriptValue);
+ if (success) {
+ if (scriptValue->fType == SkType_String)
+ track(scriptValue->fOperand.fString);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+// note that entire expression is treated as if it were enclosed in parens
+// an open paren is always the first thing in the op stack
+
+int SkScriptEngine::logicalOp(char ch, char nextChar) {
+ int advance = 1;
+ SkOp match;
+ signed char precedence;
+ switch (ch) {
+ case ')':
+ match = kParen;
+ break;
+ case ']':
+ match = kArrayOp;
+ break;
+ case '?':
+ match = kIf;
+ break;
+ case ':':
+ match = kElse;
+ break;
+ case '&':
+ if (nextChar != '&')
+ goto noMatch;
+ match = kLogicalAnd;
+ advance = 2;
+ break;
+ case '|':
+ if (nextChar != '|')
+ goto noMatch;
+ match = kLogicalOr;
+ advance = 2;
+ break;
+ default:
+noMatch:
+ return 0;
+ }
+ SkSuppress suppress;
+ precedence = gPrecedence[match];
+ if (fSuppressStack.top().fSuppress) {
+ if (fSuppressStack.top().fOpStackDepth < fOpStack.count()) {
+ SkOp topOp = fOpStack.top();
+ if (gPrecedence[topOp] <= precedence)
+ fOpStack.pop();
+ goto goHome;
+ }
+ bool changedPrecedence = gPrecedence[fSuppressStack.top().fOperator] < precedence;
+ if (changedPrecedence)
+ fSuppressStack.pop();
+ if (precedence == kIfElsePrecedence) {
+ if (match == kIf) {
+ if (changedPrecedence)
+ fOpStack.pop();
+ else
+ *fOpStack.push() = kIf;
+ } else {
+ if (fSuppressStack.top().fOpStackDepth == fOpStack.count()) {
+ goto flipSuppress;
+ }
+ fOpStack.pop();
+ }
+ }
+ if (changedPrecedence == false)
+ goto goHome;
+ }
+ while (gPrecedence[fOpStack.top() & ~kArtificialOp] < precedence) {
+ if (processOp() == false)
+ return false;
+ }
+ if (fSuppressStack.top().fOpStackDepth > fOpStack.count())
+ fSuppressStack.pop();
+ switch (match) {
+ case kParen:
+ case kArrayOp:
+ if (fOpStack.count() <= 1 || fOpStack.top() != match) {
+ fError = kMismatchedBrackets;
+ return -1;
+ }
+ if (match == kParen)
+ fOpStack.pop();
+ else {
+ SkOpType indexType;
+ fTypeStack.pop(&indexType);
+ if (indexType != kInt && indexType != kScalar) {
+ fError = kExpectedNumberForArrayIndex; // (although, could permit strings eventually)
+ return -1;
+ }
+ SkOperand indexOperand;
+ fOperandStack.pop(&indexOperand);
+ int index = indexType == kScalar ? SkScalarFloor(indexOperand.fScalar) :
+ indexOperand.fS32;
+ SkOpType arrayType;
+ fTypeStack.pop(&arrayType);
+ if ((unsigned)arrayType != (unsigned)kArray) {
+ fError = kExpectedArray;
+ return -1;
+ }
+ SkOperand arrayOperand;
+ fOperandStack.pop(&arrayOperand);
+ SkTypedArray* array = arrayOperand.fArray;
+ SkOperand operand;
+ if (array->getIndex(index, &operand) == false) {
+ fError = kIndexOutOfRange;
+ return -1;
+ }
+ SkOpType resultType = array->getOpType();
+ fTypeStack.push(resultType);
+ fOperandStack.push(operand);
+ }
+ break;
+ case kIf: {
+ SkScriptValue ifValue;
+ SkOpType ifType;
+ fTypeStack.pop(&ifType);
+ ifValue.fType = ToDisplayType(ifType);
+ fOperandStack.pop(&ifValue.fOperand);
+ if (convertTo(SkType_Int, &ifValue) == false)
+ return -1;
+ if (ifValue.fType != SkType_Int) {
+ fError = kExpectedIntForConditionOperator;
+ return -1;
+ }
+ suppress.fSuppress = ifValue.fOperand.fS32 == 0;
+ suppress.fOperator = kIf;
+ suppress.fOpStackDepth = fOpStack.count();
+ suppress.fElse = false;
+ fSuppressStack.push(suppress);
+ // if left is true, do only up to colon
+ // if left is false, do only after colon
+ } break;
+ case kElse:
+flipSuppress:
+ if (fSuppressStack.top().fElse)
+ fSuppressStack.pop();
+ fSuppressStack.top().fElse = true;
+ fSuppressStack.top().fSuppress ^= true;
+ // flip last do / don't do consideration from last '?'
+ break;
+ case kLogicalAnd:
+ case kLogicalOr: {
+ if (fTypeStack.top() != kInt) {
+ fError = kExpectedBooleanExpression;
+ return -1;
+ }
+ int32_t topInt = fOperandStack.top().fS32;
+ if (fOpStack.top() != kLogicalAnd)
+ *fOpStack.push() = kLogicalAnd; // really means 'to bool', and is appropriate for 'or'
+ if (match == kLogicalOr ? topInt != 0 : topInt == 0) {
+ suppress.fSuppress = true;
+ suppress.fOperator = match;
+ suppress.fOpStackDepth = fOpStack.count();
+ suppress.fElse = false;
+ fSuppressStack.push(suppress);
+ } else {
+ fTypeStack.pop();
+ fOperandStack.pop();
+ }
+ } break;
+ default:
+ SkASSERT(0);
+ }
+goHome:
+ return advance;
+}
+
+SkScriptEngine::Error SkScriptEngine::opError() {
+ int opCount = fOpStack.count();
+ int operandCount = fOperandStack.count();
+ if (opCount == 0) {
+ if (operandCount != 1)
+ return kExpectedOperator;
+ return kNoError;
+ }
+ SkOp op = (SkOp) (fOpStack.top() & ~kArtificialOp);
+ const SkOperatorAttributes* attributes = &gOpAttributes[op];
+ if (attributes->fLeftType != kNoType && operandCount < 2)
+ return kExpectedValue;
+ if (attributes->fLeftType == kNoType && operandCount < 1)
+ return kExpectedValue;
+ return kNoError;
+}
+
+bool SkScriptEngine::processOp() {
+ SkOp op;
+ fOpStack.pop(&op);
+ op = (SkOp) (op & ~kArtificialOp);
+ const SkOperatorAttributes* attributes = &gOpAttributes[op];
+ SkOpType type2;
+ fTypeStack.pop(&type2);
+ SkOpType type1 = type2;
+ SkOperand operand2;
+ fOperandStack.pop(&operand2);
+ SkOperand operand1 = operand2; // !!! not really needed, suppresses warning
+ if (attributes->fLeftType != kNoType) {
+ fTypeStack.pop(&type1);
+ fOperandStack.pop(&operand1);
+ if (op == kFlipOps) {
+ SkTSwap(type1, type2);
+ SkTSwap(operand1, operand2);
+ fOpStack.pop(&op);
+ op = (SkOp) (op & ~kArtificialOp);
+ attributes = &gOpAttributes[op];
+ }
+ if (type1 == kObject && (type1 & attributes->fLeftType) == 0) {
+ SkScriptValue val;
+ val.fType = ToDisplayType(type1);
+ val.fOperand = operand1;
+ bool success = handleUnbox(&val);
+ if (success == false)
+ return false;
+ type1 = ToOpType(val.fType);
+ operand1 = val.fOperand;
+ }
+ }
+ if (type2 == kObject && (type2 & attributes->fLeftType) == 0) {
+ SkScriptValue val;
+ val.fType = ToDisplayType(type2);
+ val.fOperand = operand2;
+ bool success = handleUnbox(&val);
+ if (success == false)
+ return false;
+ type2 = ToOpType(val.fType);
+ operand2 = val.fOperand;
+ }
+ if (attributes->fLeftType != kNoType) {
+ if (type1 != type2) {
+ if ((attributes->fLeftType & kString) && attributes->fBias & kTowardsString && ((type1 | type2) & kString)) {
+ if (type1 == kInt || type1 == kScalar) {
+ convertToString(operand1, type1 == kInt ? SkType_Int : SkType_Float);
+ type1 = kString;
+ }
+ if (type2 == kInt || type2 == kScalar) {
+ convertToString(operand2, type2 == kInt ? SkType_Int : SkType_Float);
+ type2 = kString;
+ }
+ } else if (attributes->fLeftType & kScalar && ((type1 | type2) & kScalar)) {
+ if (type1 == kInt) {
+ operand1.fScalar = IntToScalar(operand1.fS32);
+ type1 = kScalar;
+ }
+ if (type2 == kInt) {
+ operand2.fScalar = IntToScalar(operand2.fS32);
+ type2 = kScalar;
+ }
+ }
+ }
+ if ((type1 & attributes->fLeftType) == 0 || type1 != type2) {
+ if (type1 == kString) {
+ const char* result = SkParse::FindScalar(operand1.fString->c_str(), &operand1.fScalar);
+ if (result == NULL) {
+ fError = kExpectedNumber;
+ return false;
+ }
+ type1 = kScalar;
+ }
+ if (type1 == kScalar && (attributes->fLeftType == kInt || type2 == kInt)) {
+ operand1.fS32 = SkScalarFloor(operand1.fScalar);
+ type1 = kInt;
+ }
+ }
+ }
+ if ((type2 & attributes->fRightType) == 0 || type1 != type2) {
+ if (type2 == kString) {
+ const char* result = SkParse::FindScalar(operand2.fString->c_str(), &operand2.fScalar);
+ if (result == NULL) {
+ fError = kExpectedNumber;
+ return false;
+ }
+ type2 = kScalar;
+ }
+ if (type2 == kScalar && (attributes->fRightType == kInt || type1 == kInt)) {
+ operand2.fS32 = SkScalarFloor(operand2.fScalar);
+ type2 = kInt;
+ }
+ }
+ if (type2 == kScalar)
+ op = (SkOp) (op + 1);
+ else if (type2 == kString)
+ op = (SkOp) (op + 2);
+ switch(op) {
+ case kAddInt:
+ operand2.fS32 += operand1.fS32;
+ break;
+ case kAddScalar:
+ operand2.fScalar += operand1.fScalar;
+ break;
+ case kAddString:
+ if (fTrackString.find(operand1.fString) < 0) {
+ operand1.fString = SkNEW_ARGS(SkString, (*operand1.fString));
+ track(operand1.fString);
+ }
+ operand1.fString->append(*operand2.fString);
+ operand2 = operand1;
+ break;
+ case kBitAnd:
+ operand2.fS32 &= operand1.fS32;
+ break;
+ case kBitNot:
+ operand2.fS32 = ~operand2.fS32;
+ break;
+ case kBitOr:
+ operand2.fS32 |= operand1.fS32;
+ break;
+ case kDivideInt:
+ if (operand2.fS32 == 0) {
+ operand2.fS32 = operand1.fS32 == 0 ? SK_NaN32 : operand1.fS32 > 0 ? SK_MaxS32 : -SK_MaxS32;
+ break;
+ } else {
+ int32_t original = operand2.fS32;
+ operand2.fS32 = operand1.fS32 / operand2.fS32;
+ if (original * operand2.fS32 == operand1.fS32)
+ break; // integer divide was good enough
+ operand2.fS32 = original;
+ type2 = kScalar;
+ }
+ case kDivideScalar:
+ if (operand2.fScalar == 0)
+ operand2.fScalar = operand1.fScalar == 0 ? SK_ScalarNaN : operand1.fScalar > 0 ? SK_ScalarMax : -SK_ScalarMax;
+ else
+ operand2.fScalar = SkScalarDiv(operand1.fScalar, operand2.fScalar);
+ break;
+ case kEqualInt:
+ operand2.fS32 = operand1.fS32 == operand2.fS32;
+ break;
+ case kEqualScalar:
+ operand2.fS32 = operand1.fScalar == operand2.fScalar;
+ type2 = kInt;
+ break;
+ case kEqualString:
+ operand2.fS32 = *operand1.fString == *operand2.fString;
+ type2 = kInt;
+ break;
+ case kGreaterEqualInt:
+ operand2.fS32 = operand1.fS32 >= operand2.fS32;
+ break;
+ case kGreaterEqualScalar:
+ operand2.fS32 = operand1.fScalar >= operand2.fScalar;
+ type2 = kInt;
+ break;
+ case kGreaterEqualString:
+ operand2.fS32 = strcmp(operand1.fString->c_str(), operand2.fString->c_str()) >= 0;
+ type2 = kInt;
+ break;
+ case kLogicalAnd:
+ operand2.fS32 = !! operand2.fS32; // really, ToBool
+ break;
+ case kLogicalNot:
+ operand2.fS32 = ! operand2.fS32;
+ break;
+ case kLogicalOr:
+ SkASSERT(0); // should have already been processed
+ break;
+ case kMinusInt:
+ operand2.fS32 = -operand2.fS32;
+ break;
+ case kMinusScalar:
+ operand2.fScalar = -operand2.fScalar;
+ break;
+ case kModuloInt:
+ operand2.fS32 = operand1.fS32 % operand2.fS32;
+ break;
+ case kModuloScalar:
+ operand2.fScalar = SkScalarMod(operand1.fScalar, operand2.fScalar);
+ break;
+ case kMultiplyInt:
+ operand2.fS32 *= operand1.fS32;
+ break;
+ case kMultiplyScalar:
+ operand2.fScalar = SkScalarMul(operand1.fScalar, operand2.fScalar);
+ break;
+ case kShiftLeft:
+ operand2.fS32 = operand1.fS32 << operand2.fS32;
+ break;
+ case kShiftRight:
+ operand2.fS32 = operand1.fS32 >> operand2.fS32;
+ break;
+ case kSubtractInt:
+ operand2.fS32 = operand1.fS32 - operand2.fS32;
+ break;
+ case kSubtractScalar:
+ operand2.fScalar = operand1.fScalar - operand2.fScalar;
+ break;
+ case kXor:
+ operand2.fS32 ^= operand1.fS32;
+ break;
+ default:
+ SkASSERT(0);
+ }
+ fTypeStack.push(type2);
+ fOperandStack.push(operand2);
+ return true;
+}
+
+void SkScriptEngine::propertyCallBack(_propertyCallBack prop, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fPropertyCallBack = prop;
+ commonCallBack(kProperty, callBack, userStorage);
+}
+
+void SkScriptEngine::track(SkTypedArray* array) {
+ SkASSERT(fTrackArray.find(array) < 0);
+ *(fTrackArray.end() - 1) = array;
+ fTrackArray.appendClear();
+}
+
+void SkScriptEngine::track(SkString* string) {
+ SkASSERT(fTrackString.find(string) < 0);
+ *(fTrackString.end() - 1) = string;
+ fTrackString.appendClear();
+}
+
+void SkScriptEngine::unboxCallBack(_unboxCallBack func, void* userStorage) {
+ UserCallBack callBack;
+ callBack.fUnboxCallBack = func;
+ commonCallBack(kUnbox, callBack, userStorage);
+}
+
+bool SkScriptEngine::ConvertTo(SkScriptEngine* engine, SkDisplayTypes toType, SkScriptValue* value ) {
+ SkASSERT(value);
+ if (SkDisplayType::IsEnum(NULL /* fMaker */, toType))
+ toType = SkType_Int;
+ if (toType == SkType_Point || toType == SkType_3D_Point)
+ toType = SkType_Float;
+ if (toType == SkType_Drawable)
+ toType = SkType_Displayable;
+ SkDisplayTypes type = value->fType;
+ if (type == toType)
+ return true;
+ SkOperand& operand = value->fOperand;
+ bool success = true;
+ switch (toType) {
+ case SkType_Int:
+ if (type == SkType_Boolean)
+ break;
+ if (type == SkType_Float)
+ operand.fS32 = SkScalarFloor(operand.fScalar);
+ else {
+ if (type != SkType_String) {
+ success = false;
+ break; // error
+ }
+ success = SkParse::FindS32(operand.fString->c_str(), &operand.fS32) != NULL;
+ }
+ break;
+ case SkType_Float:
+ if (type == SkType_Int) {
+ if (operand.fS32 == SK_NaN32)
+ operand.fScalar = SK_ScalarNaN;
+ else if (SkAbs32(operand.fS32) == SK_MaxS32)
+ operand.fScalar = SkSign32(operand.fS32) * SK_ScalarMax;
+ else
+ operand.fScalar = SkIntToScalar(operand.fS32);
+ } else {
+ if (type != SkType_String) {
+ success = false;
+ break; // error
+ }
+ success = SkParse::FindScalar(operand.fString->c_str(), &operand.fScalar) != NULL;
+ }
+ break;
+ case SkType_String: {
+ SkString* strPtr = new SkString();
+ SkASSERT(engine);
+ engine->track(strPtr);
+ if (type == SkType_Int)
+ strPtr->appendS32(operand.fS32);
+ else if (type == SkType_Displayable)
+ SkASSERT(0); // must call through instance version instead of static version
+ else {
+ if (type != SkType_Float) {
+ success = false;
+ break;
+ }
+ strPtr->appendScalar(operand.fScalar);
+ }
+ operand.fString = strPtr;
+ } break;
+ case SkType_Array: {
+ SkTypedArray* array = new SkTypedArray(type);
+ *array->append() = operand;
+ engine->track(array);
+ operand.fArray = array;
+ } break;
+ default:
+ SkASSERT(0);
+ }
+ value->fType = toType;
+ if (success == false)
+ engine->fError = kTypeConversionFailed;
+ return success;
+}
+
+SkScalar SkScriptEngine::IntToScalar(int32_t s32) {
+ SkScalar scalar;
+ if (s32 == SK_NaN32)
+ scalar = SK_ScalarNaN;
+ else if (SkAbs32(s32) == SK_MaxS32)
+ scalar = SkSign32(s32) * SK_ScalarMax;
+ else
+ scalar = SkIntToScalar(s32);
+ return scalar;
+}
+
+SkDisplayTypes SkScriptEngine::ToDisplayType(SkOpType type) {
+ int val = type;
+ switch (val) {
+ case kNoType:
+ return SkType_Unknown;
+ case kInt:
+ return SkType_Int;
+ case kScalar:
+ return SkType_Float;
+ case kString:
+ return SkType_String;
+ case kArray:
+ return SkType_Array;
+ case kObject:
+ return SkType_Displayable;
+// case kStruct:
+// return SkType_Structure;
+ default:
+ SkASSERT(0);
+ return SkType_Unknown;
+ }
+}
+
+SkScriptEngine::SkOpType SkScriptEngine::ToOpType(SkDisplayTypes type) {
+ if (SkDisplayType::IsDisplayable(NULL /* fMaker */, type))
+ return (SkOpType) kObject;
+ if (SkDisplayType::IsEnum(NULL /* fMaker */, type))
+ return kInt;
+ switch (type) {
+ case SkType_ARGB:
+ case SkType_MSec:
+ case SkType_Int:
+ return kInt;
+ case SkType_Float:
+ case SkType_Point:
+ case SkType_3D_Point:
+ return kScalar;
+ case SkType_Base64:
+ case SkType_DynamicString:
+ case SkType_String:
+ return kString;
+ case SkType_Array:
+ return (SkOpType) kArray;
+ case SkType_Unknown:
+ return kNoType;
+ default:
+ SkASSERT(0);
+ return kNoType;
+ }
+}
+
+bool SkScriptEngine::ValueToString(SkScriptValue value, SkString* string) {
+ switch (value.fType) {
+ case kInt:
+ string->reset();
+ string->appendS32(value.fOperand.fS32);
+ break;
+ case kScalar:
+ string->reset();
+ string->appendScalar(value.fOperand.fScalar);
+ break;
+ case kString:
+ string->set(*value.fOperand.fString);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true; // no error
+}
+
+#ifdef SK_SUPPORT_UNITTEST
+
+#include "SkFloatingPoint.h"
+
+#define DEF_SCALAR_ANSWER 0
+#define DEF_STRING_ANSWER NULL
+
+#define testInt(expression) { #expression, SkType_Int, expression, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
+#ifdef SK_SCALAR_IS_FLOAT
+ #define testScalar(expression) { #expression, SkType_Float, 0, (float) expression, DEF_STRING_ANSWER }
+ #define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, sk_float_mod(exp1, exp2), DEF_STRING_ANSWER }
+#else
+ #define testScalar(expression) { #expression, SkType_Float, 0, (int) ((expression) * 65536.0f), DEF_STRING_ANSWER }
+ #define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, (int) (sk_float_mod(exp1, exp2) * 65536.0f), DEF_STRING_ANSWER }
+#endif
+#define testTrue(expression) { #expression, SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
+#define testFalse(expression) { #expression, SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
+
+static const SkScriptNAnswer scriptTests[] = {
+ testInt(1>1/2),
+ testInt((6+7)*8),
+ testInt(0&&1?2:3),
+ testInt(3*(4+5)),
+ testScalar(1.0+2.0),
+ testScalar(1.0+5),
+ testScalar(3.0-1.0),
+ testScalar(6-1.0),
+ testScalar(- -5.5- -1.5),
+ testScalar(2.5*6.),
+ testScalar(0.5*4),
+ testScalar(4.5/.5),
+ testScalar(9.5/19),
+ testRemainder(9.5, 0.5),
+ testRemainder(9.,2),
+ testRemainder(9,2.5),
+ testRemainder(-9,2.5),
+ testTrue(-9==-9.0),
+ testTrue(-9.==-4.0-5),
+ testTrue(-9.*1==-4-5),
+ testFalse(-9!=-9.0),
+ testFalse(-9.!=-4.0-5),
+ testFalse(-9.*1!=-4-5),
+ testInt(0x123),
+ testInt(0XABC),
+ testInt(0xdeadBEEF),
+ { "'123'+\"456\"", SkType_String, 0, 0, "123456" },
+ { "123+\"456\"", SkType_String, 0, 0, "123456" },
+ { "'123'+456", SkType_String, 0, 0, "123456" },
+ { "'123'|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ { "123|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ { "'123'|456", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ { "'2'<11", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ { "2<'11'", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ { "'2'<'11'", SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
+ testInt(123),
+ testInt(-345),
+ testInt(+678),
+ testInt(1+2+3),
+ testInt(3*4+5),
+ testInt(6+7*8),
+ testInt(-1-2-8/4),
+ testInt(-9%4),
+ testInt(9%-4),
+ testInt(-9%-4),
+ testInt(123|978),
+ testInt(123&978),
+ testInt(123^978),
+ testInt(2<<4),
+ testInt(99>>3),
+ testInt(~55),
+ testInt(~~55),
+ testInt(!55),
+ testInt(!!55),
+ // both int
+ testInt(2<2),
+ testInt(2<11),
+ testInt(20<11),
+ testInt(2<=2),
+ testInt(2<=11),
+ testInt(20<=11),
+ testInt(2>2),
+ testInt(2>11),
+ testInt(20>11),
+ testInt(2>=2),
+ testInt(2>=11),
+ testInt(20>=11),
+ testInt(2==2),
+ testInt(2==11),
+ testInt(20==11),
+ testInt(2!=2),
+ testInt(2!=11),
+ testInt(20!=11),
+ // left int, right scalar
+ testInt(2<2.),
+ testInt(2<11.),
+ testInt(20<11.),
+ testInt(2<=2.),
+ testInt(2<=11.),
+ testInt(20<=11.),
+ testInt(2>2.),
+ testInt(2>11.),
+ testInt(20>11.),
+ testInt(2>=2.),
+ testInt(2>=11.),
+ testInt(20>=11.),
+ testInt(2==2.),
+ testInt(2==11.),
+ testInt(20==11.),
+ testInt(2!=2.),
+ testInt(2!=11.),
+ testInt(20!=11.),
+ // left scalar, right int
+ testInt(2.<2),
+ testInt(2.<11),
+ testInt(20.<11),
+ testInt(2.<=2),
+ testInt(2.<=11),
+ testInt(20.<=11),
+ testInt(2.>2),
+ testInt(2.>11),
+ testInt(20.>11),
+ testInt(2.>=2),
+ testInt(2.>=11),
+ testInt(20.>=11),
+ testInt(2.==2),
+ testInt(2.==11),
+ testInt(20.==11),
+ testInt(2.!=2),
+ testInt(2.!=11),
+ testInt(20.!=11),
+ // both scalar
+ testInt(2.<11.),
+ testInt(20.<11.),
+ testInt(2.<=2.),
+ testInt(2.<=11.),
+ testInt(20.<=11.),
+ testInt(2.>2.),
+ testInt(2.>11.),
+ testInt(20.>11.),
+ testInt(2.>=2.),
+ testInt(2.>=11.),
+ testInt(20.>=11.),
+ testInt(2.==2.),
+ testInt(2.==11.),
+ testInt(20.==11.),
+ testInt(2.!=2.),
+ testInt(2.!=11.),
+ testInt(20.!=11.),
+ // int, string (string is int)
+ testFalse(2<'2'),
+ testTrue(2<'11'),
+ testFalse(20<'11'),
+ testTrue(2<='2'),
+ testTrue(2<='11'),
+ testFalse(20<='11'),
+ testFalse(2>'2'),
+ testFalse(2>'11'),
+ testTrue(20>'11'),
+ testTrue(2>='2'),
+ testFalse(2>='11'),
+ testTrue(20>='11'),
+ testTrue(2=='2'),
+ testFalse(2=='11'),
+ testFalse(2!='2'),
+ testTrue(2!='11'),
+ // int, string (string is scalar)
+ testFalse(2<'2.'),
+ testTrue(2<'11.'),
+ testFalse(20<'11.'),
+ testTrue(2=='2.'),
+ testFalse(2=='11.'),
+ // scalar, string
+ testFalse(2.<'2.'),
+ testTrue(2.<'11.'),
+ testFalse(20.<'11.'),
+ testTrue(2.=='2.'),
+ testFalse(2.=='11.'),
+ // string, int
+ testFalse('2'<2),
+ testTrue('2'<11),
+ testFalse('20'<11),
+ testTrue('2'==2),
+ testFalse('2'==11),
+ // string, scalar
+ testFalse('2'<2.),
+ testTrue('2'<11.),
+ testFalse('20'<11.),
+ testTrue('2'==2.),
+ testFalse('2'==11.),
+ // string, string
+ testFalse('2'<'2'),
+ testFalse('2'<'11'),
+ testFalse('20'<'11'),
+ testTrue('2'=='2'),
+ testFalse('2'=='11'),
+ // logic
+ testInt(1?2:3),
+ testInt(0?2:3),
+ testInt(1&&2||3),
+ testInt(1&&0||3),
+ testInt(1&&0||0),
+ testInt(1||0&&3),
+ testInt(0||0&&3),
+ testInt(0||1&&3),
+ testInt(1?(2?3:4):5),
+ testInt(0?(2?3:4):5),
+ testInt(1?(0?3:4):5),
+ testInt(0?(0?3:4):5),
+ testInt(1?2?3:4:5),
+ testInt(0?2?3:4:5),
+ testInt(1?0?3:4:5),
+ testInt(0?0?3:4:5),
+
+ testInt(1?2:(3?4:5)),
+ testInt(0?2:(3?4:5)),
+ testInt(1?0:(3?4:5)),
+ testInt(0?0:(3?4:5)),
+ testInt(1?2:3?4:5),
+ testInt(0?2:3?4:5),
+ testInt(1?0:3?4:5),
+ testInt(0?0:3?4:5)
+ , { "123.5", SkType_Float, 0, SkIntToScalar(123) + SK_Scalar1/2, DEF_STRING_ANSWER }
+};
+
+#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
+
+void SkScriptEngine::UnitTest() {
+ for (unsigned index = 0; index < SkScriptNAnswer_testCount; index++) {
+ SkScriptEngine engine(SkScriptEngine::ToOpType(scriptTests[index].fType));
+ SkScriptValue value;
+ const char* script = scriptTests[index].fScript;
+ SkASSERT(engine.evaluateScript(&script, &value) == true);
+ SkASSERT(value.fType == scriptTests[index].fType);
+ SkScalar error;
+ switch (value.fType) {
+ case SkType_Int:
+ SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
+ break;
+ case SkType_Float:
+ error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
+ SkASSERT(error < SK_Scalar1 / 10000);
+ break;
+ case SkType_String:
+ SkASSERT(strcmp(value.fOperand.fString->c_str(), scriptTests[index].fStringAnswer) == 0);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+}
+#endif
diff --git a/animator/SkScript.h b/animator/SkScript.h
new file mode 100644
index 00000000..aa8d9a30
--- /dev/null
+++ b/animator/SkScript.h
@@ -0,0 +1,266 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkScript_DEFINED
+#define SkScript_DEFINED
+
+#include "SkOperand.h"
+#include "SkIntArray.h"
+#include "SkTDict.h"
+#include "SkTDStack.h"
+
+class SkAnimateMaker;
+
+class SkScriptEngine {
+public:
+ enum Error {
+ kNoError,
+ kArrayIndexOutOfBounds,
+ kCouldNotFindReferencedID,
+ kDotOperatorExpectsObject,
+ kErrorInArrrayIndex,
+ kErrorInFunctionParameters,
+ kExpectedArray,
+ kExpectedBooleanExpression,
+ kExpectedFieldName,
+ kExpectedHex,
+ kExpectedIntForConditionOperator,
+ kExpectedNumber,
+ kExpectedNumberForArrayIndex,
+ kExpectedOperator,
+ kExpectedToken,
+ kExpectedTokenBeforeDotOperator,
+ kExpectedValue,
+ kHandleMemberFailed,
+ kHandleMemberFunctionFailed,
+ kHandleUnboxFailed,
+ kIndexOutOfRange,
+ kMismatchedArrayBrace,
+ kMismatchedBrackets,
+ kNoFunctionHandlerFound,
+ kPrematureEnd,
+ kTooManyParameters,
+ kTypeConversionFailed,
+ kUnterminatedString
+ };
+
+ enum SkOpType {
+ kNoType,
+ kInt = 1,
+ kScalar = 2,
+ kString = 4,
+ kArray = 8,
+ kObject = 16
+// kStruct = 32
+ };
+
+ typedef bool (*_boxCallBack)(void* userStorage, SkScriptValue* result);
+ typedef bool (*_functionCallBack)(const char* func, size_t len, SkTDArray<SkScriptValue>& params,
+ void* userStorage, SkScriptValue* result);
+ typedef bool (*_memberCallBack)(const char* member, size_t len, void* object,
+ void* userStorage, SkScriptValue* result);
+ typedef bool (*_memberFunctionCallBack)(const char* member, size_t len, void* object,
+ SkTDArray<SkScriptValue>& params, void* userStorage, SkScriptValue* result);
+// typedef bool (*_objectToStringCallBack)(void* object, void* userStorage, SkScriptValue* result);
+ typedef bool (*_propertyCallBack)(const char* prop, size_t len, void* userStorage, SkScriptValue* result);
+ typedef bool (*_unboxCallBack)(void* userStorage, SkScriptValue* result);
+ SkScriptEngine(SkOpType returnType);
+ ~SkScriptEngine();
+ void boxCallBack(_boxCallBack func, void* userStorage);
+ bool convertTo(SkDisplayTypes , SkScriptValue* );
+ bool evaluateScript(const char** script, SkScriptValue* value);
+ void forget(SkTypedArray* array);
+ void functionCallBack(_functionCallBack func, void* userStorage);
+ Error getError() const { return fError; }
+#ifdef SK_DEBUG
+ bool getErrorString(SkString* err) const;
+#endif
+ void memberCallBack(_memberCallBack , void* userStorage);
+ void memberFunctionCallBack(_memberFunctionCallBack , void* userStorage);
+// void objectToStringCallBack(_objectToStringCallBack , void* userStorage);
+ void propertyCallBack(_propertyCallBack prop, void* userStorage);
+ void track(SkTypedArray* array);
+ void track(SkString* string);
+ void unboxCallBack(_unboxCallBack func, void* userStorage);
+ static bool ConvertTo(SkScriptEngine* , SkDisplayTypes toType, SkScriptValue* value);
+ static SkScalar IntToScalar(int32_t );
+ static SkDisplayTypes ToDisplayType(SkOpType type);
+ static SkOpType ToOpType(SkDisplayTypes type);
+ static bool ValueToString(SkScriptValue value, SkString* string);
+
+ enum CallBackType {
+ kBox,
+ kFunction,
+ kMember,
+ kMemberFunction,
+ // kObjectToString,
+ kProperty,
+ kUnbox
+ };
+
+ struct UserCallBack {
+ CallBackType fCallBackType;
+ void* fUserStorage;
+ union {
+ _boxCallBack fBoxCallBack;
+ _functionCallBack fFunctionCallBack;
+ _memberCallBack fMemberCallBack;
+ _memberFunctionCallBack fMemberFunctionCallBack;
+ // _objectToStringCallBack fObjectToStringCallBack;
+ _propertyCallBack fPropertyCallBack;
+ _unboxCallBack fUnboxCallBack;
+ };
+ };
+
+ enum SkOp {
+ kUnassigned,
+ kAdd,
+ kAddInt = kAdd,
+ kAddScalar,
+ kAddString, // string concat
+ kArrayOp,
+ kBitAnd,
+ kBitNot,
+ kBitOr,
+ kDivide,
+ kDivideInt = kDivide,
+ kDivideScalar,
+ kElse,
+ kEqual,
+ kEqualInt = kEqual,
+ kEqualScalar,
+ kEqualString,
+ kFlipOps,
+ kGreaterEqual,
+ kGreaterEqualInt = kGreaterEqual,
+ kGreaterEqualScalar,
+ kGreaterEqualString,
+ kIf,
+ kLogicalAnd,
+ kLogicalNot,
+ kLogicalOr,
+ kMinus,
+ kMinusInt = kMinus,
+ kMinusScalar,
+ kModulo,
+ kModuloInt = kModulo,
+ kModuloScalar,
+ kMultiply,
+ kMultiplyInt = kMultiply,
+ kMultiplyScalar,
+ kParen,
+ kShiftLeft,
+ kShiftRight, // signed
+ kSubtract,
+ kSubtractInt = kSubtract,
+ kSubtractScalar,
+ kXor,
+ kArtificialOp = 0x40
+ };
+
+ enum SkOpBias {
+ kNoBias,
+ kTowardsNumber = 0,
+ kTowardsString
+ };
+
+protected:
+
+ struct SkOperatorAttributes {
+ unsigned int fLeftType : 3; // SkOpType, but only lower values
+ unsigned int fRightType : 3; // SkOpType, but only lower values
+ SkOpBias fBias : 1;
+ };
+
+ struct SkSuppress { // !!! could be compressed to a long
+ SkOp fOperator; // operand which enabled suppression
+ int fOpStackDepth; // depth when suppression operator was found
+ SkBool8 fSuppress; // set if suppression happens now, as opposed to later
+ SkBool8 fElse; // set on the : half of ? :
+ };
+
+ static const SkOperatorAttributes gOpAttributes[];
+ static const signed char gPrecedence[];
+ int arithmeticOp(char ch, char nextChar, bool lastPush);
+ void commonCallBack(CallBackType type, UserCallBack& callBack, void* userStorage);
+ bool convertParams(SkTDArray<SkScriptValue>&, const SkFunctionParamType* ,
+ int paramTypeCount);
+ void convertToString(SkOperand& operand, SkDisplayTypes type) {
+ SkScriptValue scriptValue;
+ scriptValue.fOperand = operand;
+ scriptValue.fType = type;
+ convertTo(SkType_String, &scriptValue);
+ operand = scriptValue.fOperand;
+ }
+ bool evaluateDot(const char*& script, bool suppressed);
+ bool evaluateDotParam(const char*& script, bool suppressed, const char* field, size_t fieldLength);
+ bool functionParams(const char** scriptPtr, SkTDArray<SkScriptValue>& params);
+ bool handleArrayIndexer(const char** scriptPtr, bool suppressed);
+ bool handleBox(SkScriptValue* value);
+ bool handleFunction(const char** scriptPtr, bool suppressed);
+ bool handleMember(const char* field, size_t len, void* object);
+ bool handleMemberFunction(const char* field, size_t len, void* object, SkTDArray<SkScriptValue>& params);
+// bool handleObjectToString(void* object);
+ bool handleProperty(bool suppressed);
+ bool handleUnbox(SkScriptValue* scriptValue);
+ bool innerScript(const char** scriptPtr, SkScriptValue* value);
+ int logicalOp(char ch, char nextChar);
+ Error opError();
+ bool processOp();
+ void setAnimateMaker(SkAnimateMaker* maker) { fMaker = maker; }
+ bool setError(Error , const char* pos);
+ enum SkBraceStyle {
+ // kStructBrace,
+ kArrayBrace,
+ kFunctionBrace
+ };
+
+#if 0
+ SkIntArray(SkBraceStyle) fBraceStack; // curly, square, function paren
+ SkIntArray(SkOp) fOpStack;
+ SkIntArray(SkOpType) fTypeStack;
+ SkTDOperandArray fOperandStack;
+ SkTDArray<SkSuppress> fSuppressStack;
+#else
+ SkTDStack<SkBraceStyle> fBraceStack; // curly, square, function paren
+ SkTDStack<SkOp> fOpStack;
+ SkTDStack<SkOpType> fTypeStack;
+ SkTDStack<SkOperand> fOperandStack;
+ SkTDStack<SkSuppress> fSuppressStack;
+#endif
+ SkAnimateMaker* fMaker;
+ SkTDTypedArrayArray fTrackArray;
+ SkTDStringArray fTrackString;
+ const char* fToken; // one-deep stack
+ size_t fTokenLength;
+ SkTDArray<UserCallBack> fUserCallBacks;
+ SkOpType fReturnType;
+ Error fError;
+ int fErrorPosition;
+private:
+ friend class SkTypedArray;
+#ifdef SK_SUPPORT_UNITTEST
+public:
+ static void UnitTest();
+#endif
+};
+
+#ifdef SK_SUPPORT_UNITTEST
+
+struct SkScriptNAnswer {
+ const char* fScript;
+ SkDisplayTypes fType;
+ int32_t fIntAnswer;
+ SkScalar fScalarAnswer;
+ const char* fStringAnswer;
+};
+
+#endif
+
+#endif // SkScript_DEFINED
diff --git a/animator/SkScript2.h b/animator/SkScript2.h
new file mode 100644
index 00000000..33e2af7f
--- /dev/null
+++ b/animator/SkScript2.h
@@ -0,0 +1,291 @@
+
+/*
+ * 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 SkScript2_DEFINED
+#define SkScript2_DEFINED
+
+#include "SkOperand2.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTDArray_Experimental.h"
+#include "SkTDict.h"
+#include "SkTDStack.h"
+
+typedef SkLongArray(SkString*) SkTDStringArray;
+
+class SkAnimateMaker;
+class SkScriptCallBack;
+
+class SkScriptEngine2 {
+public:
+ enum Error {
+ kNoError,
+ kArrayIndexOutOfBounds,
+ kCouldNotFindReferencedID,
+ kFunctionCallFailed,
+ kMemberOpFailed,
+ kPropertyOpFailed
+ };
+
+ enum Attrs {
+ kConstant,
+ kVariable
+ };
+
+ SkScriptEngine2(SkOperand2::OpType returnType);
+ ~SkScriptEngine2();
+ bool convertTo(SkOperand2::OpType , SkScriptValue2* );
+ bool evaluateScript(const char** script, SkScriptValue2* value);
+ void forget(SkOpArray* array);
+ Error getError() { return fError; }
+ SkOperand2::OpType getReturnType() { return fReturnType; }
+ void track(SkOpArray* array) {
+ SkASSERT(fTrackArray.find(array) < 0);
+ *fTrackArray.append() = array; }
+ void track(SkString* string) {
+ SkASSERT(fTrackString.find(string) < 0);
+ *fTrackString.append() = string;
+ }
+ static bool ConvertTo(SkScriptEngine2* , SkOperand2::OpType toType, SkScriptValue2* value);
+ static SkScalar IntToScalar(int32_t );
+ static bool ValueToString(const SkScriptValue2& value, SkString* string);
+
+ enum Op { // used by tokenizer attribute table
+ kUnassigned,
+ kAdd,
+ kBitAnd,
+ kBitNot,
+ kBitOr,
+ kDivide,
+ kEqual,
+ kFlipOps,
+ kGreaterEqual,
+ kLogicalAnd,
+ kLogicalNot,
+ kLogicalOr,
+ kMinus,
+ kModulo,
+ kMultiply,
+ kShiftLeft,
+ kShiftRight, // signed
+ kSubtract,
+ kXor,
+// following not in attribute table
+ kArrayOp,
+ kElse,
+ kIf,
+ kParen,
+ kLastLogicalOp,
+ kArtificialOp = 0x20
+ };
+
+ enum TypeOp { // generated by tokenizer
+ kNop, // should never get generated
+ kAccumulatorPop,
+ kAccumulatorPush,
+ kAddInt,
+ kAddScalar,
+ kAddString, // string concat
+ kArrayIndex,
+ kArrayParam,
+ kArrayToken,
+ kBitAndInt,
+ kBitNotInt,
+ kBitOrInt,
+ kBoxToken,
+ kCallback,
+ kDivideInt,
+ kDivideScalar,
+ kDotOperator,
+ kElseOp,
+ kEnd,
+ kEqualInt,
+ kEqualScalar,
+ kEqualString,
+ kFunctionCall,
+ kFlipOpsOp,
+ kFunctionToken,
+ kGreaterEqualInt,
+ kGreaterEqualScalar,
+ kGreaterEqualString,
+ kIfOp,
+ kIntToScalar,
+ kIntToScalar2,
+ kIntToString,
+ kIntToString2,
+ kIntegerAccumulator,
+ kIntegerOperand,
+ kLogicalAndInt,
+ kLogicalNotInt,
+ kLogicalOrInt,
+ kMemberOp,
+ kMinusInt,
+ kMinusScalar,
+ kModuloInt,
+ kModuloScalar,
+ kMultiplyInt,
+ kMultiplyScalar,
+ kPropertyOp,
+ kScalarAccumulator,
+ kScalarOperand,
+ kScalarToInt,
+ kScalarToInt2,
+ kScalarToString,
+ kScalarToString2,
+ kShiftLeftInt,
+ kShiftRightInt, // signed
+ kStringAccumulator,
+ kStringOperand,
+ kStringToInt,
+ kStringToScalar,
+ kStringToScalar2,
+ kStringTrack,
+ kSubtractInt,
+ kSubtractScalar,
+ kToBool,
+ kUnboxToken,
+ kUnboxToken2,
+ kXorInt,
+ kLastTypeOp
+ };
+
+ enum OpBias {
+ kNoBias,
+ kTowardsNumber = 0,
+ kTowardsString
+ };
+
+protected:
+
+ enum BraceStyle {
+ // kStructBrace,
+ kArrayBrace,
+ kFunctionBrace
+ };
+
+ enum AddTokenRegister {
+ kAccumulator,
+ kOperand
+ };
+
+ enum ResultIsBoolean {
+ kResultIsNotBoolean,
+ kResultIsBoolean
+ };
+
+ struct OperatorAttributes {
+ unsigned int fLeftType : 3; // SkOpType union, but only lower values
+ unsigned int fRightType : 3; // SkOpType union, but only lower values
+ OpBias fBias : 1;
+ ResultIsBoolean fResultIsBoolean : 1;
+ };
+
+ struct Branch {
+ Branch() {
+ }
+
+ Branch(Op op, int depth, unsigned offset) : fOffset(offset), fOpStackDepth(depth), fOperator(op),
+ fPrimed(kIsNotPrimed), fDone(kIsNotDone) {
+ }
+
+ enum Primed {
+ kIsNotPrimed,
+ kIsPrimed
+ };
+
+ enum Done {
+ kIsNotDone,
+ kIsDone,
+ };
+
+ unsigned fOffset : 16; // offset in generated stream where branch needs to go
+ int fOpStackDepth : 7; // depth when operator was found
+ Op fOperator : 6; // operand which generated branch
+ mutable Primed fPrimed : 1; // mark when next instruction generates branch
+ Done fDone : 1; // mark when branch is complete
+ void prime() { fPrimed = kIsPrimed; }
+ void resolve(SkDynamicMemoryWStream* , size_t offset);
+ };
+
+ static const OperatorAttributes gOpAttributes[];
+ static const signed char gPrecedence[];
+ static const TypeOp gTokens[];
+ void addToken(TypeOp );
+ void addTokenConst(SkScriptValue2* , AddTokenRegister , SkOperand2::OpType , TypeOp );
+ void addTokenInt(int );
+ void addTokenScalar(SkScalar );
+ void addTokenString(const SkString& );
+ void addTokenValue(const SkScriptValue2& , AddTokenRegister );
+ int arithmeticOp(char ch, char nextChar, bool lastPush);
+ bool convertParams(SkTDArray<SkScriptValue2>* ,
+ const SkOperand2::OpType* paramTypes, int paramTypeCount);
+ void convertToString(SkOperand2* operand, SkOperand2::OpType type) {
+ SkScriptValue2 scriptValue;
+ scriptValue.fOperand = *operand;
+ scriptValue.fType = type;
+ convertTo(SkOperand2::kString, &scriptValue);
+ *operand = scriptValue.fOperand;
+ }
+ bool evaluateDot(const char*& script);
+ bool evaluateDotParam(const char*& script, const char* field, size_t fieldLength);
+ bool functionParams(const char** scriptPtr, SkTDArray<SkScriptValue2>* params);
+ size_t getTokenOffset();
+ SkOperand2::OpType getUnboxType(SkOperand2 scriptValue);
+ bool handleArrayIndexer(const char** scriptPtr);
+ bool handleFunction(const char** scriptPtr);
+ bool handleMember(const char* field, size_t len, void* object);
+ bool handleMemberFunction(const char* field, size_t len, void* object,
+ SkTDArray<SkScriptValue2>* params);
+ bool handleProperty();
+ bool handleUnbox(SkScriptValue2* scriptValue);
+ bool innerScript(const char** scriptPtr, SkScriptValue2* value);
+ int logicalOp(char ch, char nextChar);
+ void processLogicalOp(Op op);
+ bool processOp();
+ void resolveBranch(Branch& );
+// void setAnimateMaker(SkAnimateMaker* maker) { fMaker = maker; }
+ SkDynamicMemoryWStream fStream;
+ SkDynamicMemoryWStream* fActiveStream;
+ SkTDStack<BraceStyle> fBraceStack; // curly, square, function paren
+ SkTDStack<Branch> fBranchStack; // logical operators, slot to store forward branch
+ SkLongArray(SkScriptCallBack*) fCallBackArray;
+ SkTDStack<Op> fOpStack;
+ SkTDStack<SkScriptValue2> fValueStack;
+// SkAnimateMaker* fMaker;
+ SkLongArray(SkOpArray*) fTrackArray;
+ SkTDStringArray fTrackString;
+ const char* fToken; // one-deep stack
+ size_t fTokenLength;
+ SkOperand2::OpType fReturnType;
+ Error fError;
+ SkOperand2::OpType fAccumulatorType; // tracking for code generation
+ SkBool fBranchPopAllowed;
+ SkBool fConstExpression;
+ SkBool fOperandInUse;
+private:
+#ifdef SK_DEBUG
+public:
+ void decompile(const unsigned char* , size_t );
+ static void UnitTest();
+ static void ValidateDecompileTable();
+#endif
+};
+
+#ifdef SK_DEBUG
+
+struct SkScriptNAnswer2 {
+ const char* fScript;
+ SkOperand2::OpType fType;
+ int32_t fIntAnswer;
+ SkScalar fScalarAnswer;
+ const char* fStringAnswer;
+};
+
+#endif
+
+
+#endif // SkScript2_DEFINED
diff --git a/animator/SkScriptCallBack.h b/animator/SkScriptCallBack.h
new file mode 100644
index 00000000..dcbaf118
--- /dev/null
+++ b/animator/SkScriptCallBack.h
@@ -0,0 +1,65 @@
+
+/*
+ * 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 SkScriptCallBack_DEFINED
+#define SkScriptCallBack_DEFINED
+
+#include "SkOperand2.h"
+#include "SkTDArray_Experimental.h"
+
+class SkScriptCallBack {
+public:
+ enum Type {
+ kBox,
+ kFunction,
+ kMember,
+ kMemberFunction,
+ kProperty,
+ kUnbox
+ };
+
+ virtual bool getReference(const char* , size_t len, SkScriptValue2* result) { return false; }
+ virtual SkOperand2::OpType getReturnType(size_t ref, SkOperand2*) {
+ return SkOperand2::kS32; }
+ virtual Type getType() const = 0;
+};
+
+class SkScriptCallBackConvert : public SkScriptCallBack {
+public:
+ virtual bool convert(SkOperand2::OpType type, SkOperand2* operand) = 0;
+};
+
+class SkScriptCallBackFunction : public SkScriptCallBack {
+public:
+ virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) = 0;
+ virtual Type getType() const { return kFunction; }
+ virtual bool invoke(size_t ref, SkOpArray* params, SkOperand2* value) = 0;
+};
+
+class SkScriptCallBackMember: public SkScriptCallBack {
+public:
+ bool getMemberReference(const char* , size_t len, void* object, SkScriptValue2* ref);
+ virtual Type getType() const { return kMember; }
+ virtual bool invoke(size_t ref, void* object, SkOperand2* value) = 0;
+};
+
+class SkScriptCallBackMemberFunction : public SkScriptCallBack {
+public:
+ bool getMemberReference(const char* , size_t len, void* object, SkScriptValue2* ref);
+ virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) = 0;
+ virtual Type getType() const { return kMemberFunction; }
+ virtual bool invoke(size_t ref, void* object, SkOpArray* params, SkOperand2* value) = 0;
+};
+
+class SkScriptCallBackProperty : public SkScriptCallBack {
+public:
+ virtual bool getConstValue(const char* name, size_t len, SkOperand2* value) { return false; }
+ virtual bool getResult(size_t ref, SkOperand2* answer) { return false; }
+ virtual Type getType() const { return kProperty; }
+};
+
+#endif // SkScriptCallBack_DEFINED
diff --git a/animator/SkScriptDecompile.cpp b/animator/SkScriptDecompile.cpp
new file mode 100644
index 00000000..995da874
--- /dev/null
+++ b/animator/SkScriptDecompile.cpp
@@ -0,0 +1,211 @@
+
+/*
+ * 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 "SkScript2.h"
+
+#ifdef SK_DEBUG
+
+#define TypeOpName(op) {SkScriptEngine2::op, #op }
+
+static const struct OpName {
+ SkScriptEngine2::TypeOp fOp;
+ const char* fName;
+} gOpNames[] = {
+ TypeOpName(kNop), // should never get generated
+ TypeOpName(kAccumulatorPop),
+ TypeOpName(kAccumulatorPush),
+ TypeOpName(kAddInt),
+ TypeOpName(kAddScalar),
+ TypeOpName(kAddString), // string concat
+ TypeOpName(kArrayIndex),
+ TypeOpName(kArrayParam),
+ TypeOpName(kArrayToken),
+ TypeOpName(kBitAndInt),
+ TypeOpName(kBitNotInt),
+ TypeOpName(kBitOrInt),
+ TypeOpName(kBoxToken),
+ TypeOpName(kCallback),
+ TypeOpName(kDivideInt),
+ TypeOpName(kDivideScalar),
+ TypeOpName(kDotOperator),
+ TypeOpName(kElseOp),
+ TypeOpName(kEnd),
+ TypeOpName(kEqualInt),
+ TypeOpName(kEqualScalar),
+ TypeOpName(kEqualString),
+ TypeOpName(kFunctionCall),
+ TypeOpName(kFlipOpsOp),
+ TypeOpName(kFunctionToken),
+ TypeOpName(kGreaterEqualInt),
+ TypeOpName(kGreaterEqualScalar),
+ TypeOpName(kGreaterEqualString),
+ TypeOpName(kIfOp),
+ TypeOpName(kIntToScalar),
+ TypeOpName(kIntToScalar2),
+ TypeOpName(kIntToString),
+ TypeOpName(kIntToString2),
+ TypeOpName(kIntegerAccumulator),
+ TypeOpName(kIntegerOperand),
+ TypeOpName(kLogicalAndInt),
+ TypeOpName(kLogicalNotInt),
+ TypeOpName(kLogicalOrInt),
+ TypeOpName(kMemberOp),
+ TypeOpName(kMinusInt),
+ TypeOpName(kMinusScalar),
+ TypeOpName(kModuloInt),
+ TypeOpName(kModuloScalar),
+ TypeOpName(kMultiplyInt),
+ TypeOpName(kMultiplyScalar),
+ TypeOpName(kPropertyOp),
+ TypeOpName(kScalarAccumulator),
+ TypeOpName(kScalarOperand),
+ TypeOpName(kScalarToInt),
+ TypeOpName(kScalarToInt2),
+ TypeOpName(kScalarToString),
+ TypeOpName(kScalarToString2),
+ TypeOpName(kShiftLeftInt),
+ TypeOpName(kShiftRightInt), // signed
+ TypeOpName(kStringAccumulator),
+ TypeOpName(kStringOperand),
+ TypeOpName(kStringToInt),
+ TypeOpName(kStringToScalar),
+ TypeOpName(kStringToScalar2),
+ TypeOpName(kStringTrack),
+ TypeOpName(kSubtractInt),
+ TypeOpName(kSubtractScalar),
+ TypeOpName(kToBool),
+ TypeOpName(kUnboxToken),
+ TypeOpName(kUnboxToken2),
+ TypeOpName(kXorInt)
+};
+
+static size_t gOpNamesSize = sizeof(gOpNames) / sizeof(gOpNames[0]);
+
+#define OperandName(op) {SkOperand2::op, #op }
+
+static const struct OperName {
+ SkOperand2::OpType fType;
+ const char* fName;
+} gOperandNames[] = {
+ OperandName(kNoType),
+ OperandName(kS32),
+ OperandName(kScalar),
+ OperandName(kString),
+ OperandName(kArray),
+ OperandName(kObject)
+};
+
+static size_t gOperandNamesSize = sizeof(gOperandNames) / sizeof(gOperandNames[0]);
+
+// check to see that there are no missing or duplicate entries
+void SkScriptEngine2::ValidateDecompileTable() {
+ SkScriptEngine2::TypeOp op = SkScriptEngine2::kNop;
+ size_t index;
+ for (index = 0; index < gOpNamesSize; index++) {
+ SkASSERT(gOpNames[index].fOp == op);
+ op = (SkScriptEngine2::TypeOp) (op + 1);
+ }
+ index = 0;
+ SkOperand2::OpType type = SkOperand2::kNoType;
+ SkASSERT(gOperandNames[index].fType == type);
+ for (; index < gOperandNamesSize - 1; ) {
+ type = (SkOperand2::OpType) (1 << index);
+ SkASSERT(gOperandNames[++index].fType == type);
+ }
+}
+
+void SkScriptEngine2::decompile(const unsigned char* start, size_t length) {
+ SkASSERT(length > 0);
+ const unsigned char* opCode = start;
+ do {
+ SkASSERT((size_t)(opCode - start) < length);
+ SkScriptEngine2::TypeOp op = (SkScriptEngine2::TypeOp) *opCode++;
+ SkASSERT((size_t)op < gOpNamesSize);
+ SkDebugf("%d: %s", opCode - start - 1, gOpNames[op].fName);
+ switch (op) {
+ case SkScriptEngine2::kCallback: {
+ int index;
+ memcpy(&index, opCode, sizeof(index));
+ opCode += sizeof(index);
+ SkDebugf(" index: %d", index);
+ } break;
+ case SkScriptEngine2::kFunctionCall:
+ case SkScriptEngine2::kMemberOp:
+ case SkScriptEngine2::kPropertyOp: {
+ size_t ref;
+ memcpy(&ref, opCode, sizeof(ref));
+ opCode += sizeof(ref);
+ SkDebugf(" ref: %d", ref);
+ } break;
+ case SkScriptEngine2::kIntegerAccumulator:
+ case SkScriptEngine2::kIntegerOperand: {
+ int32_t integer;
+ memcpy(&integer, opCode, sizeof(integer));
+ opCode += sizeof(int32_t);
+ SkDebugf(" integer: %d", integer);
+ } break;
+ case SkScriptEngine2::kScalarAccumulator:
+ case SkScriptEngine2::kScalarOperand: {
+ SkScalar scalar;
+ memcpy(&scalar, opCode, sizeof(scalar));
+ opCode += sizeof(SkScalar);
+ SkDebugf(" scalar: %g", SkScalarToFloat(scalar));
+ } break;
+ case SkScriptEngine2::kStringAccumulator:
+ case SkScriptEngine2::kStringOperand: {
+ int size;
+ SkString* strPtr = new SkString();
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ strPtr->set((char*) opCode, size);
+ opCode += size;
+ SkDebugf(" string: %s", strPtr->c_str());
+ delete strPtr;
+ } break;
+ case SkScriptEngine2::kBoxToken: {
+ SkOperand2::OpType type;
+ memcpy(&type, opCode, sizeof(type));
+ opCode += sizeof(type);
+ size_t index = 0;
+ if (type == 0)
+ SkDebugf(" type: %s", gOperandNames[index].fName);
+ else {
+ while (type != 0) {
+ SkASSERT(index + 1 < gOperandNamesSize);
+ if (type & (1 << index)) {
+ type = (SkOperand2::OpType) (type & ~(1 << index));
+ SkDebugf(" type: %s", gOperandNames[index + 1].fName);
+ }
+ index++;
+ }
+ }
+ } break;
+ case SkScriptEngine2::kIfOp:
+ case SkScriptEngine2::kLogicalAndInt:
+ case SkScriptEngine2::kElseOp:
+ case SkScriptEngine2::kLogicalOrInt: {
+ int size;
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ SkDebugf(" offset (address): %d (%d)", size, opCode - start + size);
+ } break;
+ case SkScriptEngine2::kEnd:
+ goto done;
+ case SkScriptEngine2::kNop:
+ SkASSERT(0);
+ default:
+ break;
+ }
+ SkDebugf("\n");
+ } while (true);
+done:
+ SkDebugf("\n");
+}
+
+#endif
diff --git a/animator/SkScriptRuntime.cpp b/animator/SkScriptRuntime.cpp
new file mode 100644
index 00000000..061847ec
--- /dev/null
+++ b/animator/SkScriptRuntime.cpp
@@ -0,0 +1,351 @@
+
+/*
+ * 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 "SkScriptRuntime.h"
+#include "SkScript2.h"
+#include "SkMath.h"
+#include "SkParse.h"
+#include "SkScriptCallBack.h"
+#include "SkString.h"
+#include "SkOpArray.h"
+
+// script tokenizer
+
+// turn text into token string
+// turn number literals into inline UTF8-style values
+// process operators to turn standard notation into stack notation
+
+// defer processing until the tokens can all be resolved
+// then, turn token strings into indices into the appropriate tables / dictionaries
+
+// consider: const evaluation?
+
+// replace script string with script tokens preceeded by special value
+
+// need second version of script plugins that return private index of found value?
+ // then would need in script index of plugin, private index
+
+// encode brace stack push/pop as opcodes
+
+// should token script enocde type where possible?
+
+// current flow:
+ // strip whitespace
+ // if in array brace [ recurse, continue
+ // if token, handle function, or array, or property (continue)
+ // parse number, continue
+ // parse token, continue
+ // parse string literal, continue
+ // if dot operator, handle dot, continue
+ // if [ , handle array literal or accessor, continue
+ // if ), pop (if function, break)
+ // if ], pop ; if ',' break
+ // handle logical ops
+ // or, handle arithmetic ops
+ // loop
+
+// !!! things to do
+ // add separate processing loop to advance while suppressed
+ // or, include jump offset to skip suppressed code?
+
+SkScriptRuntime::~SkScriptRuntime() {
+ for (SkString** stringPtr = fTrackString.begin(); stringPtr < fTrackString.end(); stringPtr++)
+ delete *stringPtr;
+ for (SkOpArray** arrayPtr = fTrackArray.begin(); arrayPtr < fTrackArray.end(); arrayPtr++)
+ delete *arrayPtr;
+}
+
+bool SkScriptRuntime::executeTokens(unsigned char* opCode) {
+ SkOperand2 operand[2]; // 1=accumulator and 2=operand
+ SkScriptEngine2::TypeOp op;
+ size_t ref;
+ int index, size;
+ int registerLoad;
+ SkScriptCallBack* callBack SK_INIT_TO_AVOID_WARNING;
+ do {
+ switch ((op = (SkScriptEngine2::TypeOp) *opCode++)) {
+ case SkScriptEngine2::kArrayToken: // create an array
+ operand[0].fArray = new SkOpArray(SkOperand2::kNoType /*fReturnType*/);
+ break;
+ case SkScriptEngine2::kArrayIndex: // array accessor
+ index = operand[1].fS32;
+ if (index >= operand[0].fArray->count()) {
+ fError = kArrayIndexOutOfBounds;
+ return false;
+ }
+ operand[0] = operand[0].fArray->begin()[index];
+ break;
+ case SkScriptEngine2::kArrayParam: // array initializer, or function param
+ *operand[0].fArray->append() = operand[1];
+ break;
+ case SkScriptEngine2::kCallback:
+ memcpy(&index, opCode, sizeof(index));
+ opCode += sizeof(index);
+ callBack = fCallBackArray[index];
+ break;
+ case SkScriptEngine2::kFunctionCall: {
+ memcpy(&ref, opCode, sizeof(ref));
+ opCode += sizeof(ref);
+ SkScriptCallBackFunction* callBackFunction = (SkScriptCallBackFunction*) callBack;
+ if (callBackFunction->invoke(ref, operand[0].fArray, /* params */
+ &operand[0] /* result */) == false) {
+ fError = kFunctionCallFailed;
+ return false;
+ }
+ } break;
+ case SkScriptEngine2::kMemberOp: {
+ memcpy(&ref, opCode, sizeof(ref));
+ opCode += sizeof(ref);
+ SkScriptCallBackMember* callBackMember = (SkScriptCallBackMember*) callBack;
+ if (callBackMember->invoke(ref, operand[0].fObject, &operand[0]) == false) {
+ fError = kMemberOpFailed;
+ return false;
+ }
+ } break;
+ case SkScriptEngine2::kPropertyOp: {
+ memcpy(&ref, opCode, sizeof(ref));
+ opCode += sizeof(ref);
+ SkScriptCallBackProperty* callBackProperty = (SkScriptCallBackProperty*) callBack;
+ if (callBackProperty->getResult(ref, &operand[0])== false) {
+ fError = kPropertyOpFailed;
+ return false;
+ }
+ } break;
+ case SkScriptEngine2::kAccumulatorPop:
+ fRunStack.pop(&operand[0]);
+ break;
+ case SkScriptEngine2::kAccumulatorPush:
+ *fRunStack.push() = operand[0];
+ break;
+ case SkScriptEngine2::kIntegerAccumulator:
+ case SkScriptEngine2::kIntegerOperand:
+ registerLoad = op - SkScriptEngine2::kIntegerAccumulator;
+ memcpy(&operand[registerLoad].fS32, opCode, sizeof(int32_t));
+ opCode += sizeof(int32_t);
+ break;
+ case SkScriptEngine2::kScalarAccumulator:
+ case SkScriptEngine2::kScalarOperand:
+ registerLoad = op - SkScriptEngine2::kScalarAccumulator;
+ memcpy(&operand[registerLoad].fScalar, opCode, sizeof(SkScalar));
+ opCode += sizeof(SkScalar);
+ break;
+ case SkScriptEngine2::kStringAccumulator:
+ case SkScriptEngine2::kStringOperand: {
+ SkString* strPtr = new SkString();
+ track(strPtr);
+ registerLoad = op - SkScriptEngine2::kStringAccumulator;
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ strPtr->set((char*) opCode, size);
+ opCode += size;
+ operand[registerLoad].fString = strPtr;
+ } break;
+ case SkScriptEngine2::kStringTrack: // call after kObjectToValue
+ track(operand[0].fString);
+ break;
+ case SkScriptEngine2::kBoxToken: {
+ SkOperand2::OpType type;
+ memcpy(&type, opCode, sizeof(type));
+ opCode += sizeof(type);
+ SkScriptCallBackConvert* callBackBox = (SkScriptCallBackConvert*) callBack;
+ if (callBackBox->convert(type, &operand[0]) == false)
+ return false;
+ } break;
+ case SkScriptEngine2::kUnboxToken:
+ case SkScriptEngine2::kUnboxToken2: {
+ SkScriptCallBackConvert* callBackUnbox = (SkScriptCallBackConvert*) callBack;
+ if (callBackUnbox->convert(SkOperand2::kObject, &operand[0]) == false)
+ return false;
+ } break;
+ case SkScriptEngine2::kIfOp:
+ case SkScriptEngine2::kLogicalAndInt:
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ if (operand[0].fS32 == 0)
+ opCode += size; // skip to else (or end of if predicate)
+ break;
+ case SkScriptEngine2::kElseOp:
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ opCode += size; // if true: after predicate, always skip to end of else
+ break;
+ case SkScriptEngine2::kLogicalOrInt:
+ memcpy(&size, opCode, sizeof(size));
+ opCode += sizeof(size);
+ if (operand[0].fS32 != 0)
+ opCode += size; // skip to kToBool opcode after || predicate
+ break;
+ // arithmetic conversion ops
+ case SkScriptEngine2::kFlipOpsOp:
+ SkTSwap(operand[0], operand[1]);
+ break;
+ case SkScriptEngine2::kIntToString:
+ case SkScriptEngine2::kIntToString2:
+ case SkScriptEngine2::kScalarToString:
+ case SkScriptEngine2::kScalarToString2:{
+ SkString* strPtr = new SkString();
+ track(strPtr);
+ if (op == SkScriptEngine2::kIntToString || op == SkScriptEngine2::kIntToString2)
+ strPtr->appendS32(operand[op - SkScriptEngine2::kIntToString].fS32);
+ else
+ strPtr->appendScalar(operand[op - SkScriptEngine2::kScalarToString].fScalar);
+ operand[0].fString = strPtr;
+ } break;
+ case SkScriptEngine2::kIntToScalar:
+ case SkScriptEngine2::kIntToScalar2:
+ operand[0].fScalar = SkScriptEngine2::IntToScalar(operand[op - SkScriptEngine2::kIntToScalar].fS32);
+ break;
+ case SkScriptEngine2::kStringToInt:
+ if (SkParse::FindS32(operand[0].fString->c_str(), &operand[0].fS32) == NULL)
+ return false;
+ break;
+ case SkScriptEngine2::kStringToScalar:
+ case SkScriptEngine2::kStringToScalar2:
+ if (SkParse::FindScalar(operand[0].fString->c_str(),
+ &operand[op - SkScriptEngine2::kStringToScalar].fScalar) == NULL)
+ return false;
+ break;
+ case SkScriptEngine2::kScalarToInt:
+ operand[0].fS32 = SkScalarFloor(operand[0].fScalar);
+ break;
+ // arithmetic ops
+ case SkScriptEngine2::kAddInt:
+ operand[0].fS32 += operand[1].fS32;
+ break;
+ case SkScriptEngine2::kAddScalar:
+ operand[0].fScalar += operand[1].fScalar;
+ break;
+ case SkScriptEngine2::kAddString:
+// if (fTrackString.find(operand[1].fString) < 0) {
+// operand[1].fString = SkNEW_ARGS(SkString, (*operand[1].fString));
+// track(operand[1].fString);
+// }
+ operand[0].fString->append(*operand[1].fString);
+ break;
+ case SkScriptEngine2::kBitAndInt:
+ operand[0].fS32 &= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kBitNotInt:
+ operand[0].fS32 = ~operand[0].fS32;
+ break;
+ case SkScriptEngine2::kBitOrInt:
+ operand[0].fS32 |= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kDivideInt:
+ SkASSERT(operand[1].fS32 != 0);
+ if (operand[1].fS32 == 0)
+ operand[0].fS32 = operand[0].fS32 == 0 ? SK_NaN32 :
+ operand[0].fS32 > 0 ? SK_MaxS32 : -SK_MaxS32;
+ else
+ if (operand[1].fS32 != 0) // throw error on divide by zero?
+ operand[0].fS32 /= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kDivideScalar:
+ if (operand[1].fScalar == 0)
+ operand[0].fScalar = operand[0].fScalar == 0 ? SK_ScalarNaN :
+ operand[0].fScalar > 0 ? SK_ScalarMax : -SK_ScalarMax;
+ else
+ operand[0].fScalar = SkScalarDiv(operand[0].fScalar, operand[1].fScalar);
+ break;
+ case SkScriptEngine2::kEqualInt:
+ operand[0].fS32 = operand[0].fS32 == operand[1].fS32;
+ break;
+ case SkScriptEngine2::kEqualScalar:
+ operand[0].fS32 = operand[0].fScalar == operand[1].fScalar;
+ break;
+ case SkScriptEngine2::kEqualString:
+ operand[0].fS32 = *operand[0].fString == *operand[1].fString;
+ break;
+ case SkScriptEngine2::kGreaterEqualInt:
+ operand[0].fS32 = operand[0].fS32 >= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kGreaterEqualScalar:
+ operand[0].fS32 = operand[0].fScalar >= operand[1].fScalar;
+ break;
+ case SkScriptEngine2::kGreaterEqualString:
+ operand[0].fS32 = strcmp(operand[0].fString->c_str(), operand[1].fString->c_str()) >= 0;
+ break;
+ case SkScriptEngine2::kToBool:
+ operand[0].fS32 = !! operand[0].fS32;
+ break;
+ case SkScriptEngine2::kLogicalNotInt:
+ operand[0].fS32 = ! operand[0].fS32;
+ break;
+ case SkScriptEngine2::kMinusInt:
+ operand[0].fS32 = -operand[0].fS32;
+ break;
+ case SkScriptEngine2::kMinusScalar:
+ operand[0].fScalar = -operand[0].fScalar;
+ break;
+ case SkScriptEngine2::kModuloInt:
+ operand[0].fS32 %= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kModuloScalar:
+ operand[0].fScalar = SkScalarMod(operand[0].fScalar, operand[1].fScalar);
+ break;
+ case SkScriptEngine2::kMultiplyInt:
+ operand[0].fS32 *= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kMultiplyScalar:
+ operand[0].fScalar = SkScalarMul(operand[0].fScalar, operand[1].fScalar);
+ break;
+ case SkScriptEngine2::kShiftLeftInt:
+ operand[0].fS32 <<= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kShiftRightInt:
+ operand[0].fS32 >>= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kSubtractInt:
+ operand[0].fS32 -= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kSubtractScalar:
+ operand[0].fScalar -= operand[1].fScalar;
+ break;
+ case SkScriptEngine2::kXorInt:
+ operand[0].fS32 ^= operand[1].fS32;
+ break;
+ case SkScriptEngine2::kEnd:
+ goto done;
+ case SkScriptEngine2::kNop:
+ SkASSERT(0);
+ default:
+ break;
+ }
+ } while (true);
+done:
+ fRunStack.push(operand[0]);
+ return true;
+}
+
+bool SkScriptRuntime::getResult(SkOperand2* result) {
+ if (fRunStack.count() == 0)
+ return false;
+ fRunStack.pop(result);
+ return true;
+}
+
+void SkScriptRuntime::track(SkOpArray* array) {
+ SkASSERT(fTrackArray.find(array) < 0);
+ *fTrackArray.append() = array;
+}
+
+void SkScriptRuntime::track(SkString* string) {
+ SkASSERT(fTrackString.find(string) < 0);
+ *fTrackString.append() = string;
+}
+
+void SkScriptRuntime::untrack(SkOpArray* array) {
+ int index = fTrackArray.find(array);
+ SkASSERT(index >= 0);
+ fTrackArray.begin()[index] = NULL;
+}
+
+void SkScriptRuntime::untrack(SkString* string) {
+ int index = fTrackString.find(string);
+ SkASSERT(index >= 0);
+ fTrackString.begin()[index] = NULL;
+}
diff --git a/animator/SkScriptRuntime.h b/animator/SkScriptRuntime.h
new file mode 100644
index 00000000..3e738011
--- /dev/null
+++ b/animator/SkScriptRuntime.h
@@ -0,0 +1,50 @@
+
+/*
+ * 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 SkScriptRuntime_DEFINED
+#define SkScriptRuntime_DEFINED
+
+#include "SkOperand2.h"
+#include "SkTDArray_Experimental.h"
+#include "SkTDStack.h"
+
+class SkScriptCallBack;
+
+typedef SkLongArray(SkString*) SkTDStringArray;
+typedef SkLongArray(SkScriptCallBack*) SkTDScriptCallBackArray;
+
+class SkScriptRuntime {
+public:
+ enum SkError {
+ kNoError,
+ kArrayIndexOutOfBounds,
+ kCouldNotFindReferencedID,
+ kFunctionCallFailed,
+ kMemberOpFailed,
+ kPropertyOpFailed
+ };
+
+ SkScriptRuntime(SkTDScriptCallBackArray& callBackArray) : fCallBackArray(callBackArray)
+ { }
+ ~SkScriptRuntime();
+ bool executeTokens(unsigned char* opCode);
+ bool getResult(SkOperand2* result);
+ void untrack(SkOpArray* array);
+ void untrack(SkString* string);
+private:
+ void track(SkOpArray* array);
+ void track(SkString* string);
+ SkTDScriptCallBackArray& fCallBackArray;
+ SkError fError;
+ SkTDStack<SkOperand2> fRunStack;
+ SkLongArray(SkOpArray*) fTrackArray;
+ SkTDStringArray fTrackString;
+ // illegal
+ SkScriptRuntime& operator=(const SkScriptRuntime&);
+};
+
+#endif // SkScriptRuntime_DEFINED
diff --git a/animator/SkScriptTokenizer.cpp b/animator/SkScriptTokenizer.cpp
new file mode 100644
index 00000000..f789d388
--- /dev/null
+++ b/animator/SkScriptTokenizer.cpp
@@ -0,0 +1,1511 @@
+
+/*
+ * 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 "SkScript2.h"
+#include "SkData.h"
+#include "SkFloatingPoint.h"
+#include "SkMath.h"
+#include "SkParse.h"
+#include "SkScriptCallBack.h"
+#include "SkScriptRuntime.h"
+#include "SkString.h"
+#include "SkOpArray.h"
+
+const SkScriptEngine2::OperatorAttributes SkScriptEngine2::gOpAttributes[] = {
+{ SkOperand2::kNoType, SkOperand2::kNoType, kNoBias, kResultIsNotBoolean },
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString), kTowardsString, kResultIsNotBoolean }, // kAdd
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitAnd
+{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitNot
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitOr
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kDivide
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar |SkOperand2:: kString), kTowardsNumber,
+ kResultIsBoolean }, // kEqual
+{ SkOperand2::kS32, SkOperand2::kNoType, kNoBias, kResultIsNotBoolean }, // kFlipOps
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString), kTowardsNumber,
+ kResultIsBoolean }, // kGreaterEqual
+{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalAnd (really, ToBool)
+{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalNot
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalOr
+{ SkOperand2::kNoType, SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kMinus
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
+ SkOperand2::OpType(SkOperand2::kS32 |SkOperand2:: kScalar), kNoBias, kResultIsNotBoolean }, // kModulo
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kMultiply
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kShiftLeft
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kShiftRight
+{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
+ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kSubtract
+{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean } // kXor
+};
+
+#define kBracketPrecedence 16
+#define kIfElsePrecedence 15
+
+const signed char SkScriptEngine2::gPrecedence[] = {
+ 17, // kUnassigned,
+ 6, // kAdd,
+ 10, // kBitAnd,
+ 4, // kBitNot,
+ 12, // kBitOr,
+ 5, // kDivide,
+ 9, // kEqual,
+ -1, // kFlipOps,
+ 8, // kGreaterEqual,
+ 13, // kLogicalAnd,
+ 4, // kLogicalNot,
+ 14, // kLogicalOr,
+ 4, // kMinus,
+ 5, // kModulo,
+ 5, // kMultiply,
+ 7, // kShiftLeft,
+ 7, // kShiftRight, // signed
+ 6, // kSubtract,
+ 11, // kXor
+ kBracketPrecedence, // kArrayOp
+ kIfElsePrecedence, // kElse
+ kIfElsePrecedence, // kIf
+ kBracketPrecedence, // kParen
+};
+
+const SkScriptEngine2::TypeOp SkScriptEngine2::gTokens[] = {
+ kNop, // unassigned
+ kAddInt, // kAdd,
+ kBitAndInt, // kBitAnd,
+ kBitNotInt, // kBitNot,
+ kBitOrInt, // kBitOr,
+ kDivideInt, // kDivide,
+ kEqualInt, // kEqual,
+ kFlipOpsOp, // kFlipOps,
+ kGreaterEqualInt, // kGreaterEqual,
+ kLogicalAndInt, // kLogicalAnd,
+ kLogicalNotInt, // kLogicalNot,
+ kLogicalOrInt, // kLogicalOr,
+ kMinusInt, // kMinus,
+ kModuloInt, // kModulo,
+ kMultiplyInt, // kMultiply,
+ kShiftLeftInt, // kShiftLeft,
+ kShiftRightInt, // kShiftRight, // signed
+ kSubtractInt, // kSubtract,
+ kXorInt // kXor
+};
+
+static inline bool is_between(int c, int min, int max)
+{
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_ws(int c)
+{
+ return is_between(c, 1, 32);
+}
+
+static int token_length(const char* start) {
+ char ch = start[0];
+ if (! is_between(ch, 'a' , 'z') && ! is_between(ch, 'A', 'Z') && ch != '_' && ch != '$')
+ return -1;
+ int length = 0;
+ do
+ ch = start[++length];
+ while (is_between(ch, 'a' , 'z') || is_between(ch, 'A', 'Z') || is_between(ch, '0', '9') ||
+ ch == '_' || ch == '$');
+ return length;
+}
+
+SkScriptEngine2::SkScriptEngine2(SkOperand2::OpType returnType) : fActiveStream(&fStream),
+fTokenLength(0), fReturnType(returnType), fError(kNoError),
+fAccumulatorType(SkOperand2::kNoType),
+fBranchPopAllowed(true), fConstExpression(true), fOperandInUse(false)
+{
+ Branch branch(kUnassigned, 0, 0);
+ fBranchStack.push(branch);
+ *fOpStack.push() = (Op) kParen;
+}
+
+SkScriptEngine2::~SkScriptEngine2() {
+ for (SkString** stringPtr = fTrackString.begin(); stringPtr < fTrackString.end(); stringPtr++)
+ delete *stringPtr;
+ for (SkOpArray** arrayPtr = fTrackArray.begin(); arrayPtr < fTrackArray.end(); arrayPtr++)
+ delete *arrayPtr;
+}
+
+void SkScriptEngine2::addToken(SkScriptEngine2::TypeOp op) {
+ int limit = fBranchStack.count() - 1;
+ for (int index = 0; index < limit; index++) {
+ Branch& branch = fBranchStack.index(index);
+ if (branch.fPrimed == Branch::kIsPrimed)
+ resolveBranch(branch);
+ }
+ if (fBranchPopAllowed) {
+ while (fBranchStack.top().fDone == Branch::kIsDone)
+ fBranchStack.pop();
+ }
+ unsigned char charOp = (unsigned char) op;
+ fActiveStream->write(&charOp, sizeof(charOp));
+}
+
+void SkScriptEngine2::addTokenConst(SkScriptValue2* value, AddTokenRegister reg,
+ SkOperand2::OpType toType, SkScriptEngine2::TypeOp op) {
+ if (value->fIsConstant == SkScriptValue2::kConstant && convertTo(toType, value))
+ return;
+ addTokenValue(*value, reg);
+ addToken(op);
+ value->fIsWritten = SkScriptValue2::kWritten;
+ value->fType = toType;
+}
+
+void SkScriptEngine2::addTokenInt(int integer) {
+ fActiveStream->write(&integer, sizeof(integer));
+}
+
+void SkScriptEngine2::addTokenScalar(SkScalar scalar) {
+ fActiveStream->write(&scalar, sizeof(scalar));
+}
+
+void SkScriptEngine2::addTokenString(const SkString& string) {
+ int size = string.size();
+ addTokenInt(size);
+ fActiveStream->write(string.c_str(), size);
+}
+
+void SkScriptEngine2::addTokenValue(const SkScriptValue2& value, AddTokenRegister reg) {
+ if (value.isConstant() == false) {
+ if (reg == kAccumulator) {
+ if (fAccumulatorType == SkOperand2::kNoType)
+ addToken(kAccumulatorPop);
+ } else {
+ ; // !!! incomplete?
+ }
+ return;
+ }
+ if (reg == kAccumulator && fAccumulatorType != SkOperand2::kNoType)
+ addToken(kAccumulatorPush);
+ switch (value.fType) {
+ case SkOperand2::kS32:
+ addToken(reg == kAccumulator ? kIntegerAccumulator : kIntegerOperand);
+ addTokenInt(value.fOperand.fS32);
+ if (reg == kAccumulator)
+ fAccumulatorType = SkOperand2::kS32;
+ else
+ fOperandInUse = true;
+ break;
+ case SkOperand2::kScalar:
+ addToken(reg == kAccumulator ? kScalarAccumulator : kScalarOperand);
+ addTokenScalar(value.fOperand.fScalar);
+ if (reg == kAccumulator)
+ fAccumulatorType = SkOperand2::kScalar;
+ else
+ fOperandInUse = true;
+ break;
+ case SkOperand2::kString:
+ addToken(reg == kAccumulator ? kStringAccumulator : kStringOperand);
+ addTokenString(*value.fOperand.fString);
+ if (reg == kAccumulator)
+ fAccumulatorType = SkOperand2::kString;
+ else
+ fOperandInUse = true;
+ break;
+ default:
+ SkASSERT(0); //!!! not implemented yet
+ }
+}
+
+int SkScriptEngine2::arithmeticOp(char ch, char nextChar, bool lastPush) {
+ Op op = kUnassigned;
+ bool reverseOperands = false;
+ bool negateResult = false;
+ int advance = 1;
+ switch (ch) {
+ case '+':
+ // !!! ignoring unary plus as implemented here has the side effect of
+ // suppressing errors like +"hi"
+ if (lastPush == false) // unary plus, don't push an operator
+ return advance;
+ op = kAdd;
+ break;
+ case '-':
+ op = lastPush ? kSubtract : kMinus;
+ break;
+ case '*':
+ op = kMultiply;
+ break;
+ case '/':
+ op = kDivide;
+ break;
+ case '>':
+ if (nextChar == '>') {
+ op = kShiftRight;
+ goto twoChar;
+ }
+ op = kGreaterEqual;
+ if (nextChar == '=')
+ goto twoChar;
+ reverseOperands = negateResult = true;
+ break;
+ case '<':
+ if (nextChar == '<') {
+ op = kShiftLeft;
+ goto twoChar;
+ }
+ op = kGreaterEqual;
+ reverseOperands = nextChar == '=';
+ negateResult = ! reverseOperands;
+ advance += reverseOperands;
+ break;
+ case '=':
+ if (nextChar == '=') {
+ op = kEqual;
+ goto twoChar;
+ }
+ break;
+ case '!':
+ if (nextChar == '=') {
+ op = kEqual;
+ negateResult = true;
+twoChar:
+ advance++;
+ break;
+ }
+ op = kLogicalNot;
+ break;
+ case '?':
+ op =(Op) kIf;
+ break;
+ case ':':
+ op = (Op) kElse;
+ break;
+ case '^':
+ op = kXor;
+ break;
+ case '(':
+ *fOpStack.push() = (Op) kParen;
+ return advance;
+ case '&':
+ SkASSERT(nextChar != '&');
+ op = kBitAnd;
+ break;
+ case '|':
+ SkASSERT(nextChar != '|');
+ op = kBitOr;
+ break;
+ case '%':
+ op = kModulo;
+ break;
+ case '~':
+ op = kBitNot;
+ break;
+ }
+ if (op == kUnassigned)
+ return 0;
+ signed char precedence = gPrecedence[op];
+ do {
+ int idx = 0;
+ Op compare;
+ do {
+ compare = fOpStack.index(idx);
+ if ((compare & kArtificialOp) == 0)
+ break;
+ idx++;
+ } while (true);
+ signed char topPrecedence = gPrecedence[compare];
+ SkASSERT(topPrecedence != -1);
+ if (topPrecedence > precedence || (topPrecedence == precedence &&
+ gOpAttributes[op].fLeftType == SkOperand2::kNoType)) {
+ break;
+ }
+ processOp();
+ } while (true);
+ if (negateResult)
+ *fOpStack.push() = (Op) (kLogicalNot | kArtificialOp);
+ fOpStack.push(op);
+ if (reverseOperands)
+ *fOpStack.push() = (Op) (kFlipOps | kArtificialOp);
+
+ return advance;
+}
+
+bool SkScriptEngine2::convertParams(SkTDArray<SkScriptValue2>* params,
+ const SkOperand2::OpType* paramTypes, int paramCount) {
+ int count = params->count();
+ if (count > paramCount) {
+ SkASSERT(0);
+ return false; // too many parameters passed
+ }
+ for (int index = 0; index < count; index++)
+ convertTo(paramTypes[index], &(*params)[index]);
+ return true;
+}
+
+bool SkScriptEngine2::convertTo(SkOperand2::OpType toType, SkScriptValue2* value ) {
+ SkOperand2::OpType type = value->fType;
+ if (type == toType)
+ return true;
+ if (type == SkOperand2::kObject) {
+ if (handleUnbox(value) == false)
+ return false;
+ return convertTo(toType, value);
+ }
+ return ConvertTo(this, toType, value);
+}
+
+bool SkScriptEngine2::evaluateDot(const char*& script) {
+ size_t fieldLength = token_length(++script); // skip dot
+ SkASSERT(fieldLength > 0); // !!! add error handling
+ const char* field = script;
+ script += fieldLength;
+ bool success = handleProperty();
+ if (success == false) {
+ fError = kCouldNotFindReferencedID;
+ goto error;
+ }
+ return evaluateDotParam(script, field, fieldLength);
+error:
+ return false;
+}
+
+bool SkScriptEngine2::evaluateDotParam(const char*& script, const char* field, size_t fieldLength) {
+ SkScriptValue2& top = fValueStack.top();
+ if (top.fType != SkOperand2::kObject)
+ return false;
+ void* object = top.fOperand.fObject;
+ fValueStack.pop();
+ char ch; // see if it is a simple member or a function
+ while (is_ws(ch = script[0]))
+ script++;
+ bool success = true;
+ if (ch != '(')
+ success = handleMember(field, fieldLength, object);
+ else {
+ SkTDArray<SkScriptValue2> params;
+ *fBraceStack.push() = kFunctionBrace;
+ success = functionParams(&script, &params);
+ if (success)
+ success = handleMemberFunction(field, fieldLength, object, &params);
+ }
+ return success;
+}
+
+bool SkScriptEngine2::evaluateScript(const char** scriptPtr, SkScriptValue2* value) {
+ // fArrayOffset = 0; // no support for structures for now
+ bool success;
+ const char* inner;
+ if (strncmp(*scriptPtr, "#script:", sizeof("#script:") - 1) == 0) {
+ *scriptPtr += sizeof("#script:") - 1;
+ if (fReturnType == SkOperand2::kNoType || fReturnType == SkOperand2::kString) {
+ success = innerScript(scriptPtr, value);
+ SkASSERT(success);
+ inner = value->fOperand.fString->c_str();
+ scriptPtr = &inner;
+ }
+ }
+ success = innerScript(scriptPtr, value);
+ const char* script = *scriptPtr;
+ char ch;
+ while (is_ws(ch = script[0]))
+ script++;
+ if (ch != '\0') {
+ // error may trigger on scripts like "50,0" that were intended to be written as "[50, 0]"
+ return false;
+ }
+ return success;
+}
+
+void SkScriptEngine2::forget(SkOpArray* array) {
+ if (array->getType() == SkOperand2::kString) {
+ for (int index = 0; index < array->count(); index++) {
+ SkString* string = (*array)[index].fString;
+ int found = fTrackString.find(string);
+ if (found >= 0)
+ fTrackString.remove(found);
+ }
+ return;
+ }
+ if (array->getType() == SkOperand2::kArray) {
+ for (int index = 0; index < array->count(); index++) {
+ SkOpArray* child = (*array)[index].fArray;
+ forget(child); // forgets children of child
+ int found = fTrackArray.find(child);
+ if (found >= 0)
+ fTrackArray.remove(found);
+ }
+ }
+}
+
+bool SkScriptEngine2::functionParams(const char** scriptPtr, SkTDArray<SkScriptValue2>* params) {
+ (*scriptPtr)++; // skip open paren
+ *fOpStack.push() = (Op) kParen;
+ *fBraceStack.push() = kFunctionBrace;
+ do {
+ SkScriptValue2 value;
+ bool success = innerScript(scriptPtr, &value);
+ SkASSERT(success);
+ if (success == false)
+ return false;
+ *params->append() = value;
+ } while ((*scriptPtr)[-1] == ',');
+ fBraceStack.pop();
+ fOpStack.pop(); // pop paren
+ (*scriptPtr)++; // advance beyond close paren
+ return true;
+}
+
+size_t SkScriptEngine2::getTokenOffset() {
+ return fActiveStream->getOffset();
+}
+
+SkOperand2::OpType SkScriptEngine2::getUnboxType(SkOperand2 scriptValue) {
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kUnbox)
+ continue;
+ return (*callBack)->getReturnType(0, &scriptValue);
+ }
+ return SkOperand2::kObject;
+}
+
+bool SkScriptEngine2::innerScript(const char** scriptPtr, SkScriptValue2* value) {
+ const char* script = *scriptPtr;
+ char ch;
+ bool lastPush = false;
+ bool success = true;
+ int opBalance = fOpStack.count();
+ int baseBrace = fBraceStack.count();
+ int branchBalance = fBranchStack.count();
+ while ((ch = script[0]) != '\0') {
+ if (is_ws(ch)) {
+ script++;
+ continue;
+ }
+ SkScriptValue2 operand;
+ const char* dotCheck;
+ if (fBraceStack.count() > baseBrace) {
+ if (fBraceStack.top() == kArrayBrace) {
+ SkScriptValue2 tokenValue;
+ success = innerScript(&script, &tokenValue); // terminate and return on comma, close brace
+ SkASSERT(success);
+ {
+ SkOperand2::OpType type = fReturnType;
+ if (fReturnType == SkOperand2::kNoType) {
+ // !!! short sighted; in the future, allow each returned array component to carry
+ // its own type, and let caller do any needed conversions
+ if (value->fOperand.fArray->count() == 0)
+ value->fOperand.fArray->setType(type = tokenValue.fType);
+ else
+ type = value->fOperand.fArray->getType();
+ }
+ if (tokenValue.fType != type)
+ convertTo(type, &tokenValue);
+ *value->fOperand.fArray->append() = tokenValue.fOperand;
+ }
+ lastPush = false;
+ continue;
+ } else {
+ SkASSERT(token_length(script) > 0);
+ }
+ }
+ if (lastPush != false && fTokenLength > 0) {
+ if (ch == '(') {
+ *fBraceStack.push() = kFunctionBrace;
+ SkString functionName(fToken, fTokenLength);
+
+ if (handleFunction(&script) == false)
+ return false;
+ lastPush = true;
+ continue;
+ } else if (ch == '[') {
+ if (handleProperty() == false) {
+ SkASSERT(0);
+ return false;
+ }
+ if (handleArrayIndexer(&script) == false)
+ return false;
+ lastPush = true;
+ continue;
+ } else if (ch != '.') {
+ if (handleProperty() == false) {
+ SkASSERT(0);
+ return false;
+ }
+ lastPush = true;
+ continue;
+ }
+ }
+ if (ch == '0' && (script[1] & ~0x20) == 'X') {
+ SkASSERT(lastPush == false);
+ script += 2;
+ script = SkParse::FindHex(script, (uint32_t*) &operand.fOperand.fS32);
+ SkASSERT(script);
+ goto intCommon;
+ }
+ if (lastPush == false && ch == '.')
+ goto scalarCommon;
+ if (ch >= '0' && ch <= '9') {
+ SkASSERT(lastPush == false);
+ dotCheck = SkParse::FindS32(script, &operand.fOperand.fS32);
+ if (dotCheck[0] != '.') {
+ script = dotCheck;
+intCommon:
+ operand.fType = SkOperand2::kS32;
+ } else {
+scalarCommon:
+ script = SkParse::FindScalar(script, &operand.fOperand.fScalar);
+ operand.fType = SkOperand2::kScalar;
+ }
+ operand.fIsConstant = SkScriptValue2::kConstant;
+ fValueStack.push(operand);
+ lastPush = true;
+ continue;
+ }
+ int length = token_length(script);
+ if (length > 0) {
+ SkASSERT(lastPush == false);
+ fToken = script;
+ fTokenLength = length;
+ script += length;
+ lastPush = true;
+ continue;
+ }
+ char startQuote = ch;
+ if (startQuote == '\'' || startQuote == '\"') {
+ SkASSERT(lastPush == false);
+ operand.fOperand.fString = new SkString();
+ ++script;
+ const char* stringStart = script;
+ do { // measure string
+ if (script[0] == '\\')
+ ++script;
+ ++script;
+ SkASSERT(script[0]); // !!! throw an error
+ } while (script[0] != startQuote);
+ operand.fOperand.fString->set(stringStart, script - stringStart);
+ script = stringStart;
+ char* stringWrite = operand.fOperand.fString->writable_str();
+ do { // copy string
+ if (script[0] == '\\')
+ ++script;
+ *stringWrite++ = script[0];
+ ++script;
+ SkASSERT(script[0]); // !!! throw an error
+ } while (script[0] != startQuote);
+ ++script;
+ track(operand.fOperand.fString);
+ operand.fType = SkOperand2::kString;
+ operand.fIsConstant = SkScriptValue2::kConstant;
+ fValueStack.push(operand);
+ lastPush = true;
+ continue;
+ }
+ if (ch == '.') {
+ if (fTokenLength == 0) {
+ int tokenLength = token_length(++script);
+ const char* token = script;
+ script += tokenLength;
+ SkASSERT(fValueStack.count() > 0); // !!! add error handling
+ SkScriptValue2 top;
+ fValueStack.pop(&top);
+
+ addTokenInt(top.fType);
+ addToken(kBoxToken);
+ top.fType = SkOperand2::kObject;
+ top.fIsConstant = SkScriptValue2::kVariable;
+ fConstExpression = false;
+ fValueStack.push(top);
+ success = evaluateDotParam(script, token, tokenLength);
+ SkASSERT(success);
+ lastPush = true;
+ continue;
+ }
+ // get next token, and evaluate immediately
+ success = evaluateDot(script);
+ if (success == false) {
+ // SkASSERT(0);
+ return false;
+ }
+ lastPush = true;
+ continue;
+ }
+ if (ch == '[') {
+ if (lastPush == false) {
+ script++;
+ *fBraceStack.push() = kArrayBrace;
+ operand.fOperand.fArray = value->fOperand.fArray = new SkOpArray(fReturnType);
+ track(value->fOperand.fArray);
+
+ operand.fType = SkOperand2::kArray;
+ operand.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(operand);
+ continue;
+ }
+ if (handleArrayIndexer(&script) == false)
+ return false;
+ lastPush = true;
+ continue;
+ }
+#if 0 // structs not supported for now
+ if (ch == '{') {
+ if (lastPush == false) {
+ script++;
+ *fBraceStack.push() = kStructBrace;
+ operand.fS32 = 0;
+ *fTypeStack.push() = (SkOpType) kStruct;
+ fOperandStack.push(operand);
+ continue;
+ }
+ SkASSERT(0); // braces in other contexts aren't supported yet
+ }
+#endif
+ if (ch == ')' && fBraceStack.count() > 0) {
+ BraceStyle braceStyle = fBraceStack.top();
+ if (braceStyle == kFunctionBrace) {
+ fBraceStack.pop();
+ break;
+ }
+ }
+ if (ch == ',' || ch == ']') {
+ if (ch != ',') {
+ BraceStyle match;
+ fBraceStack.pop(&match);
+ SkASSERT(match == kArrayBrace);
+ }
+ script++;
+ // !!! see if brace or bracket is correct closer
+ break;
+ }
+ char nextChar = script[1];
+ int advance = logicalOp(ch, nextChar);
+ if (advance == 0)
+ advance = arithmeticOp(ch, nextChar, lastPush);
+ if (advance == 0) // unknown token
+ return false;
+ if (advance > 0)
+ script += advance;
+ lastPush = ch == ']' || ch == ')';
+ }
+ if (fTokenLength > 0) {
+ success = handleProperty();
+ SkASSERT(success);
+ }
+ int branchIndex = 0;
+ branchBalance = fBranchStack.count() - branchBalance;
+ fBranchPopAllowed = false;
+ while (branchIndex < branchBalance) {
+ Branch& branch = fBranchStack.index(branchIndex++);
+ if (branch.fPrimed == Branch::kIsPrimed)
+ break;
+ Op branchOp = branch.fOperator;
+ SkOperand2::OpType lastType = fValueStack.top().fType;
+ addTokenValue(fValueStack.top(), kAccumulator);
+ fValueStack.pop();
+ if (branchOp == kLogicalAnd || branchOp == kLogicalOr) {
+ if (branch.fOperator == kLogicalAnd)
+ branch.prime();
+ addToken(kToBool);
+ } else {
+ resolveBranch(branch);
+ SkScriptValue2 operand;
+ operand.fType = lastType;
+ // !!! note that many branching expressions could be constant
+ // today, we always evaluate branches as returning variables
+ operand.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(operand);
+ }
+ if (branch.fDone == Branch::kIsNotDone)
+ branch.prime();
+ }
+ fBranchPopAllowed = true;
+ while (fBranchStack.top().fDone == Branch::kIsDone)
+ fBranchStack.pop();
+ while (fOpStack.count() > opBalance) { // leave open paren
+ if (processOp() == false)
+ return false;
+ }
+ SkOperand2::OpType topType = fValueStack.count() > 0 ? fValueStack.top().fType : SkOperand2::kNoType;
+ if (topType != fReturnType &&
+ topType == SkOperand2::kString && fReturnType != SkOperand2::kNoType) { // if result is a string, give handle property a chance to convert it to the property value
+ SkString* string = fValueStack.top().fOperand.fString;
+ fToken = string->c_str();
+ fTokenLength = string->size();
+ fValueStack.pop();
+ success = handleProperty();
+ if (success == false) { // if it couldn't convert, return string (error?)
+ SkScriptValue2 operand;
+ operand.fType = SkOperand2::kString;
+ operand.fOperand.fString = string;
+ operand.fIsConstant = SkScriptValue2::kVariable; // !!! ?
+ fValueStack.push(operand);
+ }
+ }
+ if (fStream.getOffset() > 0) {
+ addToken(kEnd);
+ SkAutoDataUnref data(fStream.copyToData());
+#ifdef SK_DEBUG
+ decompile(data->bytes(), data->size());
+#endif
+ SkScriptRuntime runtime(fCallBackArray);
+ runtime.executeTokens((unsigned char*) data->bytes());
+ SkScriptValue2 value1;
+ runtime.getResult(&value1.fOperand);
+ value1.fType = fReturnType;
+ fValueStack.push(value1);
+ }
+ if (value) {
+ if (fValueStack.count() == 0)
+ return false;
+ fValueStack.pop(value);
+ if (value->fType != fReturnType && value->fType == SkOperand2::kObject &&
+ fReturnType != SkOperand2::kNoType)
+ convertTo(fReturnType, value);
+ }
+ // if (fBranchStack.top().fOpStackDepth > fOpStack.count())
+ // resolveBranch();
+ *scriptPtr = script;
+ return true; // no error
+}
+
+bool SkScriptEngine2::handleArrayIndexer(const char** scriptPtr) {
+ SkScriptValue2 scriptValue;
+ (*scriptPtr)++;
+ *fOpStack.push() = (Op) kParen;
+ *fBraceStack.push() = kArrayBrace;
+ SkOperand2::OpType saveType = fReturnType;
+ fReturnType = SkOperand2::kS32;
+ bool success = innerScript(scriptPtr, &scriptValue);
+ fReturnType = saveType;
+ SkASSERT(success);
+ success = convertTo(SkOperand2::kS32, &scriptValue);
+ SkASSERT(success);
+ int index = scriptValue.fOperand.fS32;
+ fValueStack.pop(&scriptValue);
+ if (scriptValue.fType == SkOperand2::kObject) {
+ success = handleUnbox(&scriptValue);
+ SkASSERT(success);
+ SkASSERT(scriptValue.fType == SkOperand2::kArray);
+ }
+ scriptValue.fType = scriptValue.fOperand.fArray->getType();
+ // SkASSERT(index >= 0);
+ if ((unsigned) index >= (unsigned) scriptValue.fOperand.fArray->count()) {
+ fError = kArrayIndexOutOfBounds;
+ return false;
+ }
+ scriptValue.fOperand = scriptValue.fOperand.fArray->begin()[index];
+ scriptValue.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(scriptValue);
+ fOpStack.pop(); // pop paren
+ return success;
+}
+
+bool SkScriptEngine2::handleFunction(const char** scriptPtr) {
+ const char* functionName = fToken;
+ size_t functionNameLen = fTokenLength;
+ fTokenLength = 0;
+ SkTDArray<SkScriptValue2> params;
+ bool success = functionParams(scriptPtr, &params);
+ if (success == false)
+ goto done;
+ {
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kFunction)
+ continue;
+ SkScriptValue2 callbackResult;
+ success = (*callBack)->getReference(functionName, functionNameLen, &callbackResult);
+ if (success) {
+ callbackResult.fType = (*callBack)->getReturnType(callbackResult.fOperand.fReference, NULL);
+ callbackResult.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(callbackResult);
+ goto done;
+ }
+ }
+ }
+ return false;
+done:
+ fOpStack.pop();
+ return success;
+}
+
+bool SkScriptEngine2::handleMember(const char* field, size_t len, void* object) {
+ bool success = true;
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kMember)
+ continue;
+ SkScriptValue2 callbackResult;
+ success = (*callBack)->getReference(field, len, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkOperand2::kString)
+ track(callbackResult.fOperand.fString);
+ callbackResult.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(callbackResult);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+bool SkScriptEngine2::handleMemberFunction(const char* field, size_t len, void* object,
+ SkTDArray<SkScriptValue2>* params) {
+ bool success = true;
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kMemberFunction)
+ continue;
+ SkScriptValue2 callbackResult;
+ success = (*callBack)->getReference(field, len, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkOperand2::kString)
+ track(callbackResult.fOperand.fString);
+ callbackResult.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(callbackResult);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+bool SkScriptEngine2::handleProperty() {
+ bool success = true;
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kProperty)
+ continue;
+ SkScriptValue2 callbackResult;
+ success = (*callBack)->getReference(fToken, fTokenLength, &callbackResult);
+ if (success) {
+ if (callbackResult.fType == SkOperand2::kString && callbackResult.fOperand.fString == NULL) {
+ callbackResult.fOperand.fString = new SkString(fToken, fTokenLength);
+ track(callbackResult.fOperand.fString);
+ }
+ callbackResult.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(callbackResult);
+ goto done;
+ }
+ }
+done:
+ fTokenLength = 0;
+ return success;
+}
+
+bool SkScriptEngine2::handleUnbox(SkScriptValue2* scriptValue) {
+ bool success = true;
+ for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
+ if ((*callBack)->getType() != SkScriptCallBack::kUnbox)
+ continue;
+ SkScriptCallBackConvert* callBackConvert = (SkScriptCallBackConvert*) *callBack;
+ success = callBackConvert->convert(scriptValue->fType, &scriptValue->fOperand);
+ if (success) {
+ if (scriptValue->fType == SkOperand2::kString)
+ track(scriptValue->fOperand.fString);
+ goto done;
+ }
+ }
+ return false;
+done:
+ return success;
+}
+
+// note that entire expression is treated as if it were enclosed in parens
+// an open paren is always the first thing in the op stack
+
+int SkScriptEngine2::logicalOp(char ch, char nextChar) {
+ int advance = 1;
+ Op op;
+ signed char precedence;
+ switch (ch) {
+ case ')':
+ op = (Op) kParen;
+ break;
+ case ']':
+ op = (Op) kArrayOp;
+ break;
+ case '?':
+ op = (Op) kIf;
+ break;
+ case ':':
+ op = (Op) kElse;
+ break;
+ case '&':
+ if (nextChar != '&')
+ goto noMatch;
+ op = kLogicalAnd;
+ advance = 2;
+ break;
+ case '|':
+ if (nextChar != '|')
+ goto noMatch;
+ op = kLogicalOr;
+ advance = 2;
+ break;
+ default:
+ noMatch:
+ return 0;
+ }
+ precedence = gPrecedence[op];
+ int branchIndex = 0;
+ fBranchPopAllowed = false;
+ do {
+ while (gPrecedence[fOpStack.top() & ~kArtificialOp] < precedence)
+ processOp();
+ Branch& branch = fBranchStack.index(branchIndex++);
+ Op branchOp = branch.fOperator;
+ if (gPrecedence[branchOp] >= precedence)
+ break;
+ addTokenValue(fValueStack.top(), kAccumulator);
+ fValueStack.pop();
+ if (branchOp == kLogicalAnd || branchOp == kLogicalOr) {
+ if (branch.fOperator == kLogicalAnd)
+ branch.prime();
+ addToken(kToBool);
+ } else
+ resolveBranch(branch);
+ if (branch.fDone == Branch::kIsNotDone)
+ branch.prime();
+ } while (true);
+ fBranchPopAllowed = true;
+ while (fBranchStack.top().fDone == Branch::kIsDone)
+ fBranchStack.pop();
+ processLogicalOp(op);
+ return advance;
+}
+
+void SkScriptEngine2::processLogicalOp(Op op) {
+ switch (op) {
+ case kParen:
+ case kArrayOp:
+ SkASSERT(fOpStack.count() > 1 && fOpStack.top() == op); // !!! add error handling
+ if (op == kParen)
+ fOpStack.pop();
+ else {
+ SkScriptValue2 value;
+ fValueStack.pop(&value);
+ SkASSERT(value.fType == SkOperand2::kS32 || value.fType == SkOperand2::kScalar); // !!! add error handling (although, could permit strings eventually)
+ int index = value.fType == SkOperand2::kScalar ? SkScalarFloor(value.fOperand.fScalar) :
+ value.fOperand.fS32;
+ SkScriptValue2 arrayValue;
+ fValueStack.pop(&arrayValue);
+ SkASSERT(arrayValue.fType == SkOperand2::kArray); // !!! add error handling
+ SkOpArray* array = arrayValue.fOperand.fArray;
+ SkOperand2 operand;
+ SkDEBUGCODE(bool success = ) array->getIndex(index, &operand);
+ SkASSERT(success); // !!! add error handling
+ SkScriptValue2 resultValue;
+ resultValue.fType = array->getType();
+ resultValue.fOperand = operand;
+ resultValue.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(resultValue);
+ }
+ break;
+ case kIf: {
+ if (fAccumulatorType == SkOperand2::kNoType) {
+ addTokenValue(fValueStack.top(), kAccumulator);
+ fValueStack.pop();
+ }
+ SkASSERT(fAccumulatorType != SkOperand2::kString); // !!! add error handling
+ addToken(kIfOp);
+ Branch branch(op, fOpStack.count(), getTokenOffset());
+ *fBranchStack.push() = branch;
+ addTokenInt(0); // placeholder for future branch
+ fAccumulatorType = SkOperand2::kNoType;
+ } break;
+ case kElse: {
+ addTokenValue(fValueStack.top(), kAccumulator);
+ fValueStack.pop();
+ addToken(kElseOp);
+ size_t newOffset = getTokenOffset();
+ addTokenInt(0); // placeholder for future branch
+ Branch& branch = fBranchStack.top();
+ resolveBranch(branch);
+ branch.fOperator = op;
+ branch.fDone = Branch::kIsNotDone;
+ SkASSERT(branch.fOpStackDepth == fOpStack.count());
+ branch.fOffset = newOffset;
+ fAccumulatorType = SkOperand2::kNoType;
+ } break;
+ case kLogicalAnd:
+ case kLogicalOr: {
+ Branch& oldTop = fBranchStack.top();
+ Branch::Primed wasPrime = oldTop.fPrimed;
+ Branch::Done wasDone = oldTop.fDone;
+ oldTop.fPrimed = Branch::kIsNotPrimed;
+ oldTop.fDone = Branch::kIsNotDone;
+ if (fAccumulatorType == SkOperand2::kNoType) {
+ SkASSERT(fValueStack.top().fType == SkOperand2::kS32); // !!! add error handling, and conversion to int?
+ addTokenValue(fValueStack.top(), kAccumulator);
+ fValueStack.pop();
+ } else {
+ SkASSERT(fAccumulatorType == SkOperand2::kS32);
+ }
+ // if 'and', write beq goto opcode after end of predicate (after to bool)
+ // if 'or', write bne goto to bool
+ addToken(op == kLogicalAnd ? kLogicalAndInt : kLogicalOrInt);
+ Branch branch(op, fOpStack.count(), getTokenOffset());
+ addTokenInt(0); // placeholder for future branch
+ oldTop.fPrimed = wasPrime;
+ oldTop.fDone = wasDone;
+ *fBranchStack.push() = branch;
+ fAccumulatorType = SkOperand2::kNoType;
+ } break;
+ default:
+ SkASSERT(0);
+ }
+}
+
+bool SkScriptEngine2::processOp() {
+ Op op;
+ fOpStack.pop(&op);
+ op = (Op) (op & ~kArtificialOp);
+ const OperatorAttributes* attributes = &gOpAttributes[op];
+ SkScriptValue2 value1;
+ memset(&value1, 0, sizeof(SkScriptValue2));
+ SkScriptValue2 value2;
+ fValueStack.pop(&value2);
+ value2.fIsWritten = SkScriptValue2::kUnwritten;
+ // SkScriptEngine2::SkTypeOp convert1[3];
+ // SkScriptEngine2::SkTypeOp convert2[3];
+ // SkScriptEngine2::SkTypeOp* convert2Ptr = convert2;
+ bool constantOperands = value2.fIsConstant == SkScriptValue2::kConstant;
+ if (attributes->fLeftType != SkOperand2::kNoType) {
+ fValueStack.pop(&value1);
+ constantOperands &= value1.fIsConstant == SkScriptValue2::kConstant;
+ value1.fIsWritten = SkScriptValue2::kUnwritten;
+ if (op == kFlipOps) {
+ SkTSwap(value1, value2);
+ fOpStack.pop(&op);
+ op = (Op) (op & ~kArtificialOp);
+ attributes = &gOpAttributes[op];
+ if (constantOperands == false)
+ addToken(kFlipOpsOp);
+ }
+ if (value1.fType == SkOperand2::kObject && (value1.fType & attributes->fLeftType) == 0) {
+ value1.fType = getUnboxType(value1.fOperand);
+ addToken(kUnboxToken);
+ }
+ }
+ if (value2.fType == SkOperand2::kObject && (value2.fType & attributes->fLeftType) == 0) {
+ value1.fType = getUnboxType(value2.fOperand);
+ addToken(kUnboxToken2);
+ }
+ if (attributes->fLeftType != SkOperand2::kNoType) {
+ if (value1.fType != value2.fType) {
+ if ((attributes->fLeftType & SkOperand2::kString) && attributes->fBias & kTowardsString &&
+ ((value1.fType | value2.fType) & SkOperand2::kString)) {
+ if (value1.fType == SkOperand2::kS32 || value1.fType == SkOperand2::kScalar) {
+ addTokenConst(&value1, kAccumulator, SkOperand2::kString,
+ value1.fType == SkOperand2::kS32 ? kIntToString : kScalarToString);
+ }
+ if (value2.fType == SkOperand2::kS32 || value2.fType == SkOperand2::kScalar) {
+ addTokenConst(&value2, kOperand, SkOperand2::kString,
+ value2.fType == SkOperand2::kS32 ? kIntToString2 : kScalarToString2);
+ }
+ } else if (attributes->fLeftType & SkOperand2::kScalar && ((value1.fType | value2.fType) &
+ SkOperand2::kScalar)) {
+ if (value1.fType == SkOperand2::kS32)
+ addTokenConst(&value1, kAccumulator, SkOperand2::kScalar, kIntToScalar);
+ if (value2.fType == SkOperand2::kS32)
+ addTokenConst(&value2, kOperand, SkOperand2::kScalar, kIntToScalar2);
+ }
+ }
+ if ((value1.fType & attributes->fLeftType) == 0 || value1.fType != value2.fType) {
+ if (value1.fType == SkOperand2::kString)
+ addTokenConst(&value1, kAccumulator, SkOperand2::kScalar, kStringToScalar);
+ if (value1.fType == SkOperand2::kScalar && (attributes->fLeftType == SkOperand2::kS32 ||
+ value2.fType == SkOperand2::kS32))
+ addTokenConst(&value1, kAccumulator, SkOperand2::kS32, kScalarToInt);
+ }
+ }
+ AddTokenRegister rhRegister = attributes->fLeftType != SkOperand2::kNoType ?
+ kOperand : kAccumulator;
+ if ((value2.fType & attributes->fRightType) == 0 || value1.fType != value2.fType) {
+ if (value2.fType == SkOperand2::kString)
+ addTokenConst(&value2, rhRegister, SkOperand2::kScalar, kStringToScalar2);
+ if (value2.fType == SkOperand2::kScalar && (attributes->fRightType == SkOperand2::kS32 ||
+ value1.fType == SkOperand2::kS32))
+ addTokenConst(&value2, rhRegister, SkOperand2::kS32, kScalarToInt2);
+ }
+ TypeOp typeOp = gTokens[op];
+ if (value2.fType == SkOperand2::kScalar)
+ typeOp = (TypeOp) (typeOp + 1);
+ else if (value2.fType == SkOperand2::kString)
+ typeOp = (TypeOp) (typeOp + 2);
+ SkDynamicMemoryWStream stream;
+ SkOperand2::OpType saveType = SkOperand2::kNoType;
+ SkBool saveOperand = false;
+ if (constantOperands) {
+ fActiveStream = &stream;
+ saveType = fAccumulatorType;
+ saveOperand = fOperandInUse;
+ fAccumulatorType = SkOperand2::kNoType;
+ fOperandInUse = false;
+ }
+ if (attributes->fLeftType != SkOperand2::kNoType) { // two operands
+ if (value1.fIsWritten == SkScriptValue2::kUnwritten)
+ addTokenValue(value1, kAccumulator);
+ }
+ if (value2.fIsWritten == SkScriptValue2::kUnwritten)
+ addTokenValue(value2, rhRegister);
+ addToken(typeOp);
+ if (constantOperands) {
+ addToken(kEnd);
+ SkAutoDataUnref data(fStream.copyToData());
+#ifdef SK_DEBUG
+ decompile(data->bytes(), data->size());
+#endif
+ SkScriptRuntime runtime(fCallBackArray);
+ runtime.executeTokens((unsigned char*)data->bytes());
+ runtime.getResult(&value1.fOperand);
+ if (attributes->fResultIsBoolean == kResultIsBoolean)
+ value1.fType = SkOperand2::kS32;
+ else if (attributes->fLeftType == SkOperand2::kNoType) // unary operand
+ value1.fType = value2.fType;
+ fValueStack.push(value1);
+ if (value1.fType == SkOperand2::kString)
+ runtime.untrack(value1.fOperand.fString);
+ else if (value1.fType == SkOperand2::kArray)
+ runtime.untrack(value1.fOperand.fArray);
+ fActiveStream = &fStream;
+ fAccumulatorType = saveType;
+ fOperandInUse = saveOperand;
+ return true;
+ }
+ value2.fIsConstant = SkScriptValue2::kVariable;
+ fValueStack.push(value2);
+ return true;
+}
+
+void SkScriptEngine2::Branch::resolve(SkDynamicMemoryWStream* stream, size_t off) {
+ SkASSERT(fDone == kIsNotDone);
+ fPrimed = kIsNotPrimed;
+ fDone = kIsDone;
+ SkASSERT(off > fOffset + sizeof(size_t));
+ size_t offset = off - fOffset - sizeof(offset);
+ stream->write(&offset, fOffset, sizeof(offset));
+}
+
+void SkScriptEngine2::resolveBranch(SkScriptEngine2::Branch& branch) {
+ branch.resolve(fActiveStream, getTokenOffset());
+}
+
+bool SkScriptEngine2::ConvertTo(SkScriptEngine2* engine, SkOperand2::OpType toType, SkScriptValue2* value ) {
+ SkASSERT(value);
+ SkOperand2::OpType type = value->fType;
+ if (type == toType)
+ return true;
+ SkOperand2& operand = value->fOperand;
+ bool success = true;
+ switch (toType) {
+ case SkOperand2::kS32:
+ if (type == SkOperand2::kScalar)
+ operand.fS32 = SkScalarFloor(operand.fScalar);
+ else {
+ SkASSERT(type == SkOperand2::kString);
+ success = SkParse::FindS32(operand.fString->c_str(), &operand.fS32) != NULL;
+ }
+ break;
+ case SkOperand2::kScalar:
+ if (type == SkOperand2::kS32)
+ operand.fScalar = IntToScalar(operand.fS32);
+ else {
+ SkASSERT(type == SkOperand2::kString);
+ success = SkParse::FindScalar(operand.fString->c_str(), &operand.fScalar) != NULL;
+ }
+ break;
+ case SkOperand2::kString: {
+ SkString* strPtr = new SkString();
+ SkASSERT(engine);
+ engine->track(strPtr);
+ if (type == SkOperand2::kS32)
+ strPtr->appendS32(operand.fS32);
+ else {
+ SkASSERT(type == SkOperand2::kScalar);
+ strPtr->appendScalar(operand.fScalar);
+ }
+ operand.fString = strPtr;
+ } break;
+ case SkOperand2::kArray: {
+ SkOpArray* array = new SkOpArray(type);
+ *array->append() = operand;
+ engine->track(array);
+ operand.fArray = array;
+ } break;
+ default:
+ SkASSERT(0);
+ }
+ value->fType = toType;
+ return success;
+}
+
+SkScalar SkScriptEngine2::IntToScalar(int32_t s32) {
+ SkScalar scalar;
+ if (s32 == (int32_t) SK_NaN32)
+ scalar = SK_ScalarNaN;
+ else if (SkAbs32(s32) == SK_MaxS32)
+ scalar = SkSign32(s32) * SK_ScalarMax;
+ else
+ scalar = SkIntToScalar(s32);
+ return scalar;
+}
+
+bool SkScriptEngine2::ValueToString(const SkScriptValue2& value, SkString* string) {
+ switch (value.fType) {
+ case SkOperand2::kS32:
+ string->reset();
+ string->appendS32(value.fOperand.fS32);
+ break;
+ case SkOperand2::kScalar:
+ string->reset();
+ string->appendScalar(value.fOperand.fScalar);
+ break;
+ case SkOperand2::kString:
+ string->set(*value.fOperand.fString);
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ return true; // no error
+}
+
+#ifdef SK_DEBUG
+#if defined(SK_SUPPORT_UNITTEST)
+
+#define testInt(expression) { #expression, SkOperand2::kS32, expression, 0, NULL }
+#ifdef SK_SCALAR_IS_FLOAT
+#define testScalar(expression) { #expression, SkOperand2::kScalar, 0, (float) (expression), NULL }
+#define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkOperand2::kScalar, 0, fmodf((float) exp1, (float) exp2), NULL }
+#else
+#define testScalar(expression) { #expression, SkOperand2::kScalar, 0, (int) ((expression) * 65536.0f), NULL }
+#define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkOperand2::kScalar, 0, (int) (fmod(exp1, exp2) * 65536.0f), NULL }
+#endif
+#define testTrue(expression) { #expression, SkOperand2::kS32, 1, 0, NULL }
+#define testFalse(expression) { #expression, SkOperand2::kS32, 0, 0, NULL }
+
+static const SkScriptNAnswer2 scriptTests[] = {
+ testInt(1||(0&&3)),
+ testScalar(- -5.5- -1.5),
+ testScalar(1.0+5),
+ testInt((6+7)*8),
+ testInt(3*(4+5)),
+ testScalar(1.0+2.0),
+ testScalar(3.0-1.0),
+ testScalar(6-1.0),
+ testScalar(2.5*6.),
+ testScalar(0.5*4),
+ testScalar(4.5/.5),
+ testScalar(9.5/19),
+ testRemainder(9.5, 0.5),
+ testRemainder(9.,2),
+ testRemainder(9,2.5),
+ testRemainder(-9,2.5),
+ testTrue(-9==-9.0),
+ testTrue(-9.==-4.0-5),
+ testTrue(-9.*1==-4-5),
+ testFalse(-9!=-9.0),
+ testFalse(-9.!=-4.0-5),
+ testFalse(-9.*1!=-4-5),
+ testInt(0x123),
+ testInt(0XABC),
+ testInt(0xdeadBEEF),
+ { "'123'+\"456\"", SkOperand2::kString, 0, 0, "123456" },
+ { "123+\"456\"", SkOperand2::kString, 0, 0, "123456" },
+ { "'123'+456", SkOperand2::kString, 0, 0, "123456" },
+ { "'123'|\"456\"", SkOperand2::kS32, 123|456, 0, NULL },
+ { "123|\"456\"", SkOperand2::kS32, 123|456, 0, NULL },
+ { "'123'|456", SkOperand2::kS32, 123|456, 0, NULL },
+ { "'2'<11", SkOperand2::kS32, 1, 0, NULL },
+ { "2<'11'", SkOperand2::kS32, 1, 0, NULL },
+ { "'2'<'11'", SkOperand2::kS32, 0, 0, NULL },
+ testInt(123),
+ testInt(-345),
+ testInt(+678),
+ testInt(1+2+3),
+ testInt(3*4+5),
+ testInt(6+7*8),
+ testInt(-1-2-8/4),
+ testInt(-9%4),
+ testInt(9%-4),
+ testInt(-9%-4),
+ testInt(123|978),
+ testInt(123&978),
+ testInt(123^978),
+ testInt(2<<4),
+ testInt(99>>3),
+ testInt(~55),
+ testInt(~~55),
+ testInt(!55),
+ testInt(!!55),
+ // both int
+ testInt(2<2),
+ testInt(2<11),
+ testInt(20<11),
+ testInt(2<=2),
+ testInt(2<=11),
+ testInt(20<=11),
+ testInt(2>2),
+ testInt(2>11),
+ testInt(20>11),
+ testInt(2>=2),
+ testInt(2>=11),
+ testInt(20>=11),
+ testInt(2==2),
+ testInt(2==11),
+ testInt(20==11),
+ testInt(2!=2),
+ testInt(2!=11),
+ testInt(20!=11),
+ // left int, right scalar
+ testInt(2<2.),
+ testInt(2<11.),
+ testInt(20<11.),
+ testInt(2<=2.),
+ testInt(2<=11.),
+ testInt(20<=11.),
+ testInt(2>2.),
+ testInt(2>11.),
+ testInt(20>11.),
+ testInt(2>=2.),
+ testInt(2>=11.),
+ testInt(20>=11.),
+ testInt(2==2.),
+ testInt(2==11.),
+ testInt(20==11.),
+ testInt(2!=2.),
+ testInt(2!=11.),
+ testInt(20!=11.),
+ // left scalar, right int
+ testInt(2.<2),
+ testInt(2.<11),
+ testInt(20.<11),
+ testInt(2.<=2),
+ testInt(2.<=11),
+ testInt(20.<=11),
+ testInt(2.>2),
+ testInt(2.>11),
+ testInt(20.>11),
+ testInt(2.>=2),
+ testInt(2.>=11),
+ testInt(20.>=11),
+ testInt(2.==2),
+ testInt(2.==11),
+ testInt(20.==11),
+ testInt(2.!=2),
+ testInt(2.!=11),
+ testInt(20.!=11),
+ // both scalar
+ testInt(2.<11.),
+ testInt(20.<11.),
+ testInt(2.<=2.),
+ testInt(2.<=11.),
+ testInt(20.<=11.),
+ testInt(2.>2.),
+ testInt(2.>11.),
+ testInt(20.>11.),
+ testInt(2.>=2.),
+ testInt(2.>=11.),
+ testInt(20.>=11.),
+ testInt(2.==2.),
+ testInt(2.==11.),
+ testInt(20.==11.),
+ testInt(2.!=2.),
+ testInt(2.!=11.),
+ testInt(20.!=11.),
+ // int, string (string is int)
+ testFalse(2<'2'),
+ testTrue(2<'11'),
+ testFalse(20<'11'),
+ testTrue(2<='2'),
+ testTrue(2<='11'),
+ testFalse(20<='11'),
+ testFalse(2>'2'),
+ testFalse(2>'11'),
+ testTrue(20>'11'),
+ testTrue(2>='2'),
+ testFalse(2>='11'),
+ testTrue(20>='11'),
+ testTrue(2=='2'),
+ testFalse(2=='11'),
+ testFalse(2!='2'),
+ testTrue(2!='11'),
+ // int, string (string is scalar)
+ testFalse(2<'2.'),
+ testTrue(2<'11.'),
+ testFalse(20<'11.'),
+ testTrue(2=='2.'),
+ testFalse(2=='11.'),
+ // scalar, string
+ testFalse(2.<'2.'),
+ testTrue(2.<'11.'),
+ testFalse(20.<'11.'),
+ testTrue(2.=='2.'),
+ testFalse(2.=='11.'),
+ // string, int
+ testFalse('2'<2),
+ testTrue('2'<11),
+ testFalse('20'<11),
+ testTrue('2'==2),
+ testFalse('2'==11),
+ // string, scalar
+ testFalse('2'<2.),
+ testTrue('2'<11.),
+ testFalse('20'<11.),
+ testTrue('2'==2.),
+ testFalse('2'==11.),
+ // string, string
+ testFalse('2'<'2'),
+ testFalse('2'<'11'),
+ testFalse('20'<'11'),
+ testTrue('2'=='2'),
+ testFalse('2'=='11'),
+ // logic
+ testInt(1?2:3),
+ testInt(0?2:3),
+ testInt((1&&2)||3),
+ testInt((1&&0)||3),
+ testInt((1&&0)||0),
+ testInt(1||(0&&3)),
+ testInt(0||(0&&3)),
+ testInt(0||(1&&3)),
+ testInt(0&&1?2:3)
+ , { "123.5", SkOperand2::kScalar, 0, SkIntToScalar(123) + SK_Scalar1/2, NULL }
+};
+
+#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
+#endif // SK_SUPPORT_UNITTEST
+
+void SkScriptEngine2::UnitTest() {
+#if defined(SK_SUPPORT_UNITTEST)
+ ValidateDecompileTable();
+ for (int index = 0; index < SkScriptNAnswer_testCount; index++) {
+ SkScriptEngine2 engine(scriptTests[index].fType);
+ SkScriptValue2 value;
+ const char* script = scriptTests[index].fScript;
+ const char* scriptPtr = script;
+ SkASSERT(engine.evaluateScript(&scriptPtr, &value) == true);
+ SkASSERT(value.fType == scriptTests[index].fType);
+ SkScalar error;
+ switch (value.fType) {
+ case SkOperand2::kS32:
+ if (value.fOperand.fS32 != scriptTests[index].fIntAnswer)
+ SkDEBUGF(("script '%s' == value %d != expected answer %d\n", script, value.fOperand.fS32, scriptTests[index].fIntAnswer));
+ SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
+ break;
+ case SkOperand2::kScalar:
+ error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
+ if (error >= SK_Scalar1 / 10000)
+ SkDEBUGF(("script '%s' == value %g != expected answer %g\n", script, value.fOperand.fScalar / (1.0f * SK_Scalar1), scriptTests[index].fScalarAnswer / (1.0f * SK_Scalar1)));
+ SkASSERT(error < SK_Scalar1 / 10000);
+ break;
+ case SkOperand2::kString:
+ SkASSERT(value.fOperand.fString->equals(scriptTests[index].fStringAnswer));
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+#endif // SK_SUPPORT_UNITTEST
+}
+#endif // SK_DEBUG
diff --git a/animator/SkSnapshot.cpp b/animator/SkSnapshot.cpp
new file mode 100644
index 00000000..493ce2b3
--- /dev/null
+++ b/animator/SkSnapshot.cpp
@@ -0,0 +1,67 @@
+
+/*
+ * 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 "SkTypes.h"
+
+#include "SkSnapshot.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkImageEncoder.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkSnapshot::fInfo[] = {
+ SK_MEMBER(filename, String),
+ SK_MEMBER(quality, Float),
+ SK_MEMBER(sequence, Boolean),
+ SK_MEMBER(type, BitmapEncoding)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkSnapshot);
+
+SkSnapshot::SkSnapshot()
+{
+ quality = 100 * SK_Scalar1;
+ type = (SkImageEncoder::Type) -1;
+ sequence = false;
+ fSeqVal = 0;
+}
+
+#include "SkDevice.h"
+
+bool SkSnapshot::draw(SkAnimateMaker& maker) {
+ SkASSERT(type >= 0);
+ SkASSERT(filename.size() > 0);
+ SkImageEncoder* encoder = SkImageEncoder::Create((SkImageEncoder::Type) type);
+ if (!encoder) {
+ return false;
+ }
+ SkAutoTDelete<SkImageEncoder> ad(encoder);
+
+ SkString name(filename);
+ if (sequence) {
+ char num[4] = "000";
+ num[0] = (char) (num[0] + fSeqVal / 100);
+ num[1] = (char) (num[1] + fSeqVal / 10 % 10);
+ num[2] = (char) (num[2] + fSeqVal % 10);
+ name.append(num);
+ if (++fSeqVal > 999)
+ sequence = false;
+ }
+ if (type == SkImageEncoder::kJPEG_Type)
+ name.append(".jpg");
+ else if (type == SkImageEncoder::kPNG_Type)
+ name.append(".png");
+ encoder->encodeFile(name.c_str(),
+ maker.fCanvas->getDevice()->accessBitmap(false),
+ SkScalarFloor(quality));
+ return false;
+}
diff --git a/animator/SkSnapshot.h b/animator/SkSnapshot.h
new file mode 100644
index 00000000..5ae6917a
--- /dev/null
+++ b/animator/SkSnapshot.h
@@ -0,0 +1,30 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSnapShot_DEFINED
+#define SkSnapShot_DEFINED
+
+#include "SkDrawable.h"
+#include "SkImageDecoder.h"
+#include "SkMemberInfo.h"
+#include "SkString.h"
+
+class SkSnapshot: public SkDrawable {
+ DECLARE_MEMBER_INFO(Snapshot);
+ SkSnapshot();
+ virtual bool draw(SkAnimateMaker& );
+ private:
+ SkString filename;
+ SkScalar quality;
+ SkBool sequence;
+ int /*SkImageEncoder::Type*/ type;
+ int fSeqVal;
+};
+
+#endif // SkSnapShot_DEFINED
diff --git a/animator/SkTDArray_Experimental.h b/animator/SkTDArray_Experimental.h
new file mode 100644
index 00000000..4b3970fd
--- /dev/null
+++ b/animator/SkTDArray_Experimental.h
@@ -0,0 +1,142 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTDArray_Experimental_DEFINED
+#define SkTDArray_Experimental_DEFINED
+
+#include "SkTypes.h"
+
+#ifdef SK_BUILD_FOR_UNIX
+#define SK_BUILD_FOR_ADS_12
+#endif
+
+#if !defined(SK_BUILD_FOR_ADS_12) && !defined(__x86_64__)
+#define SK_SMALLER_ARRAY_TEMPLATE_EXPERIMENT 1
+#else
+#define SK_SMALLER_ARRAY_TEMPLATE_EXPERIMENT 0
+#endif
+
+#if SK_SMALLER_ARRAY_TEMPLATE_EXPERIMENT == 0
+#include "SkTDArray.h"
+#define SkIntArray(type) SkTDArray<type>
+#define SkLongArray(type) SkTDArray<type>
+#else
+
+class SkDS32Array {
+protected:
+ SkDS32Array();
+ SkDS32Array(const SkDS32Array& src);
+ SkDS32Array(const int32_t src[], U16CPU count);
+ SkDS32Array& operator=(const SkDS32Array& src);
+ friend int operator==(const SkDS32Array& a, const SkDS32Array& b);
+ int32_t* append() { return this->append(1, NULL); }
+ int32_t* append(U16CPU count, const int32_t* src = NULL);
+
+ int32_t* appendClear()
+ {
+ int32_t* result = this->append();
+ *result = 0;
+ return result;
+ }
+
+ int find(const int32_t& elem) const;
+ int32_t* insert(U16CPU index, U16CPU count, const int32_t* src);
+ int rfind(const int32_t& elem) const;
+ void swap(SkDS32Array& other);
+public:
+ bool isEmpty() const { return fCount == 0; }
+ int count() const { return fCount; }
+
+ void remove(U16CPU index, U16CPU count = 1)
+ {
+ SkASSERT(index + count <= fCount);
+ fCount = SkToU16(fCount - count);
+ memmove(fArray + index, fArray + index + count, sizeof(int32_t) * (fCount - index));
+ }
+
+ void reset()
+ {
+ if (fArray)
+ {
+ sk_free(fArray);
+ fArray = NULL;
+#ifdef SK_DEBUG
+ fData = NULL;
+#endif
+ fReserve = fCount = 0;
+ }
+ else
+ {
+ SkASSERT(fReserve == 0 && fCount == 0);
+ }
+ }
+
+ void setCount(U16CPU count)
+ {
+ if (count > fReserve)
+ this->growBy(count - fCount);
+ else
+ fCount = SkToU16(count);
+ }
+protected:
+#ifdef SK_DEBUG
+ enum {
+ kDebugArraySize = 24
+ };
+ int32_t(* fData)[kDebugArraySize];
+#endif
+ int32_t* fArray;
+ uint16_t fReserve, fCount;
+ void growBy(U16CPU extra);
+};
+
+#ifdef SK_DEBUG
+ #define SYNC() fTData = (T (*)[kDebugArraySize]) fArray
+#else
+ #define SYNC()
+#endif
+
+template <typename T> class SkTDS32Array : public SkDS32Array {
+public:
+ SkTDS32Array() { SkDEBUGCODE(fTData=NULL); SkASSERT(sizeof(T) == sizeof(int32_t)); }
+ SkTDS32Array(const SkTDS32Array<T>& src) : SkDS32Array(src) {}
+ ~SkTDS32Array() { sk_free(fArray); }
+ T& operator[](int index) const { SYNC(); SkASSERT((unsigned)index < fCount); return ((T*) fArray)[index]; }
+ SkTDS32Array<T>& operator=(const SkTDS32Array<T>& src) {
+ return (SkTDS32Array<T>&) SkDS32Array::operator=(src); }
+ friend int operator==(const SkTDS32Array<T>& a, const SkTDS32Array<T>& b) {
+ return operator==((const SkDS32Array&) a, (const SkDS32Array&) b); }
+ T* append() { return (T*) SkDS32Array::append(); }
+ T* appendClear() { return (T*) SkDS32Array::appendClear(); }
+ T* append(U16CPU count, const T* src = NULL) { return (T*) SkDS32Array::append(count, (const int32_t*) src); }
+ T* begin() const { SYNC(); return (T*) fArray; }
+ T* end() const { return (T*) (fArray ? fArray + fCount : NULL); }
+ int find(const T& elem) const { return SkDS32Array::find((const int32_t&) elem); }
+ T* insert(U16CPU index) { return this->insert(index, 1, NULL); }
+ T* insert(U16CPU index, U16CPU count, const T* src = NULL) {
+ return (T*) SkDS32Array::insert(index, count, (const int32_t*) src); }
+ int rfind(const T& elem) const { return SkDS32Array::rfind((const int32_t&) elem); }
+ T* push() { return this->append(); }
+ void push(T& elem) { *this->append() = elem; }
+ const T& top() const { return (*this)[fCount - 1]; }
+ T& top() { return (*this)[fCount - 1]; }
+ void pop(T* elem) { if (elem) *elem = (*this)[fCount - 1]; --fCount; }
+ void pop() { --fCount; }
+private:
+#ifdef SK_DEBUG
+ mutable T(* fTData)[kDebugArraySize];
+#endif
+};
+
+#define SkIntArray(type) SkTDS32Array<type> // holds 32 bit data types
+#define SkLongArray(type) SkTDS32Array<type>
+
+#endif // SK_SMALLER_ARRAY_TEMPLATE_EXPERIMENT
+
+#endif // SkTDArray_Experimental_DEFINED
diff --git a/animator/SkTextOnPath.cpp b/animator/SkTextOnPath.cpp
new file mode 100644
index 00000000..7bdb7fd9
--- /dev/null
+++ b/animator/SkTextOnPath.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * 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 "SkTextOnPath.h"
+#include "SkAnimateMaker.h"
+#include "SkCanvas.h"
+#include "SkDrawPath.h"
+#include "SkDrawText.h"
+#include "SkPaint.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkTextOnPath::fInfo[] = {
+ SK_MEMBER(offset, Float),
+ SK_MEMBER(path, Path),
+ SK_MEMBER(text, Text)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkTextOnPath);
+
+SkTextOnPath::SkTextOnPath() : offset(0), path(NULL), text(NULL) {
+}
+
+bool SkTextOnPath::draw(SkAnimateMaker& maker) {
+ SkASSERT(text);
+ SkASSERT(path);
+ SkBoundableAuto boundable(this, maker);
+ maker.fCanvas->drawTextOnPathHV(text->getText(), text->getSize(),
+ path->getPath(), offset, 0, *maker.fPaint);
+ return false;
+}
diff --git a/animator/SkTextOnPath.h b/animator/SkTextOnPath.h
new file mode 100644
index 00000000..b0ce234d
--- /dev/null
+++ b/animator/SkTextOnPath.h
@@ -0,0 +1,30 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTextOnPath_DEFINED
+#define SkTextOnPath_DEFINED
+
+#include "SkBoundable.h"
+#include "SkMemberInfo.h"
+
+class SkDrawPath;
+class SkText;
+
+class SkTextOnPath : public SkBoundable {
+ DECLARE_MEMBER_INFO(TextOnPath);
+ SkTextOnPath();
+ virtual bool draw(SkAnimateMaker& );
+private:
+ SkScalar offset;
+ SkDrawPath* path;
+ SkText* text;
+ typedef SkBoundable INHERITED;
+};
+
+#endif // SkTextOnPath_DEFINED
diff --git a/animator/SkTextToPath.cpp b/animator/SkTextToPath.cpp
new file mode 100644
index 00000000..0c1b0e42
--- /dev/null
+++ b/animator/SkTextToPath.cpp
@@ -0,0 +1,47 @@
+
+/*
+ * 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 "SkTextToPath.h"
+#include "SkAnimateMaker.h"
+#include "SkDrawPaint.h"
+#include "SkDrawPath.h"
+#include "SkDrawText.h"
+#include "SkPaint.h"
+
+#if SK_USE_CONDENSED_INFO == 0
+
+const SkMemberInfo SkTextToPath::fInfo[] = {
+ SK_MEMBER(paint, Paint),
+ SK_MEMBER(path, Path),
+ SK_MEMBER(text, Text)
+};
+
+#endif
+
+DEFINE_GET_MEMBER(SkTextToPath);
+
+SkTextToPath::SkTextToPath() : paint(NULL), path(NULL), text(NULL) {
+}
+
+bool SkTextToPath::draw(SkAnimateMaker& maker) {
+ path->draw(maker);
+ return false;
+}
+
+void SkTextToPath::onEndElement(SkAnimateMaker& maker) {
+ if (paint == NULL || path == NULL || text == NULL) {
+ // !!! add error message here
+ maker.setErrorCode(SkDisplayXMLParserError::kErrorInAttributeValue);
+ return;
+ }
+ SkPaint realPaint;
+ paint->setupPaint(&realPaint);
+ realPaint.getTextPath(text->getText(), text->getSize(), text->x,
+ text->y, &path->getPath());
+}
diff --git a/animator/SkTextToPath.h b/animator/SkTextToPath.h
new file mode 100644
index 00000000..ac44ad70
--- /dev/null
+++ b/animator/SkTextToPath.h
@@ -0,0 +1,31 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTextToPath_DEFINED
+#define SkTextToPath_DEFINED
+
+#include "SkDrawPath.h"
+#include "SkMemberInfo.h"
+
+class SkDrawPaint;
+class SkDrawPath;
+class SkText;
+
+class SkTextToPath : public SkDrawable {
+ DECLARE_MEMBER_INFO(TextToPath);
+ SkTextToPath();
+ virtual bool draw(SkAnimateMaker& );
+ virtual void onEndElement(SkAnimateMaker& );
+private:
+ SkDrawPaint* paint;
+ SkDrawPath* path;
+ SkText* text;
+};
+
+#endif // SkTextToPath_DEFINED
diff --git a/animator/SkTime.cpp b/animator/SkTime.cpp
new file mode 100644
index 00000000..ffd6f38d
--- /dev/null
+++ b/animator/SkTime.cpp
@@ -0,0 +1,80 @@
+
+/*
+ * 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 "SkTime.h"
+
+#ifdef SK_BUILD_FOR_WIN
+
+#ifdef SK_DEBUG
+SkMSec gForceTickCount = (SkMSec) -1;
+#endif
+
+void SkTime::GetDateTime(DateTime* t)
+{
+ if (t)
+ {
+ SYSTEMTIME syst;
+
+ ::GetLocalTime(&syst);
+ t->fYear = SkToU16(syst.wYear);
+ t->fMonth = SkToU8(syst.wMonth);
+ t->fDayOfWeek = SkToU8(syst.wDayOfWeek);
+ t->fDay = SkToU8(syst.wDay);
+ t->fHour = SkToU8(syst.wHour);
+ t->fMinute = SkToU8(syst.wMinute);
+ t->fSecond = SkToU8(syst.wSecond);
+ }
+}
+
+SkMSec SkTime::GetMSecs()
+{
+#ifdef SK_DEBUG
+ if (gForceTickCount != (SkMSec) -1)
+ return gForceTickCount;
+#endif
+ return ::GetTickCount();
+}
+
+#elif defined(xSK_BUILD_FOR_MAC)
+
+#include <time.h>
+
+void SkTime::GetDateTime(DateTime* t)
+{
+ if (t)
+ {
+ tm syst;
+ time_t tm;
+
+ time(&tm);
+ localtime_r(&tm, &syst);
+ t->fYear = SkToU16(syst.tm_year);
+ t->fMonth = SkToU8(syst.tm_mon + 1);
+ t->fDayOfWeek = SkToU8(syst.tm_wday);
+ t->fDay = SkToU8(syst.tm_mday);
+ t->fHour = SkToU8(syst.tm_hour);
+ t->fMinute = SkToU8(syst.tm_min);
+ t->fSecond = SkToU8(syst.tm_sec);
+ }
+}
+
+#include "Sk64.h"
+
+SkMSec SkTime::GetMSecs()
+{
+ UnsignedWide wide;
+ Sk64 s;
+
+ ::Microseconds(&wide);
+ s.set(wide.hi, wide.lo);
+ s.div(1000, Sk64::kRound_DivOption);
+ return s.get32();
+}
+
+#endif
diff --git a/animator/SkTypedArray.cpp b/animator/SkTypedArray.cpp
new file mode 100644
index 00000000..e94e57dc
--- /dev/null
+++ b/animator/SkTypedArray.cpp
@@ -0,0 +1,179 @@
+
+/*
+ * 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 "SkTypedArray.h"
+
+SkTypedArray::SkTypedArray() : fType(SkType_Unknown) {
+}
+
+SkTypedArray::SkTypedArray(SkDisplayTypes type) : fType(type) {
+}
+
+bool SkTypedArray::getIndex(int index, SkOperand* operand) {
+ if (index >= count()) {
+ SkASSERT(0);
+ return false;
+ }
+ *operand = begin()[index];
+ return true;
+}
+
+
+#if SK_SMALLER_ARRAY_TEMPLATE_EXPERIMENT == 1
+SkDS32Array::SkDS32Array()
+{
+ fReserve = fCount = 0;
+ fArray = NULL;
+#ifdef SK_DEBUG
+ fData = NULL;
+#endif
+}
+
+SkDS32Array::SkDS32Array(const SkDS32Array& src)
+{
+ fReserve = fCount = 0;
+ fArray = NULL;
+#ifdef SK_DEBUG
+ fData = NULL;
+#endif
+ SkDS32Array tmp(src.fArray, src.fCount);
+ this->swap(tmp);
+}
+
+SkDS32Array::SkDS32Array(const int32_t src[], U16CPU count)
+{
+ SkASSERT(src || count == 0);
+
+ fReserve = fCount = 0;
+ fArray = NULL;
+#ifdef SK_DEBUG
+ fData = NULL;
+#endif
+ if (count)
+ {
+ fArray = (int32_t*)sk_malloc_throw(count * sizeof(int32_t));
+#ifdef SK_DEBUG
+ fData = (int32_t (*)[kDebugArraySize]) fArray;
+#endif
+ memcpy(fArray, src, sizeof(int32_t) * count);
+ fReserve = fCount = SkToU16(count);
+ }
+}
+
+SkDS32Array& SkDS32Array::operator=(const SkDS32Array& src)
+{
+ if (this != &src)
+ {
+ if (src.fCount > fReserve)
+ {
+ SkDS32Array tmp(src.fArray, src.fCount);
+ this->swap(tmp);
+ }
+ else
+ {
+ memcpy(fArray, src.fArray, sizeof(int32_t) * src.fCount);
+ fCount = src.fCount;
+ }
+ }
+ return *this;
+}
+
+int operator==(const SkDS32Array& a, const SkDS32Array& b)
+{
+ return a.fCount == b.fCount &&
+ (a.fCount == 0 || !memcmp(a.fArray, b.fArray, a.fCount * sizeof(int32_t)));
+}
+
+void SkDS32Array::swap(SkDS32Array& other)
+{
+ SkTSwap(fArray, other.fArray);
+#ifdef SK_DEBUG
+ SkTSwap(fData, other.fData);
+#endif
+ SkTSwap(fReserve, other.fReserve);
+ SkTSwap(fCount, other.fCount);
+}
+
+int32_t* SkDS32Array::append(U16CPU count, const int32_t* src)
+{
+ unsigned oldCount = fCount;
+ if (count)
+ {
+ SkASSERT(src == NULL || fArray == NULL ||
+ src + count <= fArray || fArray + count <= src);
+
+ this->growBy(count);
+ if (src)
+ memcpy(fArray + oldCount, src, sizeof(int32_t) * count);
+ }
+ return fArray + oldCount;
+}
+
+int SkDS32Array::find(const int32_t& elem) const
+{
+ const int32_t* iter = fArray;
+ const int32_t* stop = fArray + fCount;
+
+ for (; iter < stop; iter++)
+ {
+ if (*iter == elem)
+ return (int) (iter - fArray);
+ }
+ return -1;
+}
+
+void SkDS32Array::growBy(U16CPU extra)
+{
+ SkASSERT(extra);
+ SkASSERT(fCount + extra <= 0xFFFF);
+
+ if (fCount + extra > fReserve)
+ {
+ size_t size = fCount + extra + 4;
+ size += size >> 2;
+ int32_t* array = (int32_t*)sk_malloc_throw(size * sizeof(int32_t));
+ memcpy(array, fArray, fCount * sizeof(int32_t));
+
+ sk_free(fArray);
+ fArray = array;
+#ifdef SK_DEBUG
+ fData = (int32_t (*)[kDebugArraySize]) fArray;
+#endif
+ fReserve = SkToU16((U16CPU)size);
+ }
+ fCount = SkToU16(fCount + extra);
+}
+
+int32_t* SkDS32Array::insert(U16CPU index, U16CPU count, const int32_t* src)
+{
+ SkASSERT(count);
+ int oldCount = fCount;
+ this->growBy(count);
+ int32_t* dst = fArray + index;
+ memmove(dst + count, dst, sizeof(int32_t) * (oldCount - index));
+ if (src)
+ memcpy(dst, src, sizeof(int32_t) * count);
+ return dst;
+}
+
+
+ int SkDS32Array::rfind(const int32_t& elem) const
+ {
+ const int32_t* iter = fArray + fCount;
+ const int32_t* stop = fArray;
+
+ while (iter > stop)
+ {
+ if (*--iter == elem)
+ return (int) (iter - stop);
+ }
+ return -1;
+ }
+
+#endif
diff --git a/animator/SkTypedArray.h b/animator/SkTypedArray.h
new file mode 100644
index 00000000..e93b1067
--- /dev/null
+++ b/animator/SkTypedArray.h
@@ -0,0 +1,31 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTypedArray_DEFINED
+#define SkTypedArray_DEFINED
+
+#include "SkScript.h"
+#include "SkTDArray_Experimental.h"
+
+class SkTypedArray : public SkTDOperandArray {
+public:
+ SkTypedArray();
+ SkTypedArray(SkDisplayTypes type);
+ bool getIndex(int index, SkOperand* operand);
+ SkDisplayTypes getType() { return fType; }
+ SkScriptEngine::SkOpType getOpType() { return SkScriptEngine::ToOpType(fType); }
+ void setType(SkDisplayTypes type) {
+ // SkASSERT(count() == 0);
+ fType = type;
+ }
+protected:
+ SkDisplayTypes fType;
+};
+
+#endif // SkTypedArray_DEFINED
diff --git a/animator/SkXMLAnimatorWriter.cpp b/animator/SkXMLAnimatorWriter.cpp
new file mode 100644
index 00000000..58f20f13
--- /dev/null
+++ b/animator/SkXMLAnimatorWriter.cpp
@@ -0,0 +1,82 @@
+
+/*
+ * 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 "SkXMLAnimatorWriter.h"
+#include "SkAnimator.h"
+#include "SkAnimateMaker.h"
+#include "SkDisplayXMLParser.h"
+
+SkXMLAnimatorWriter::SkXMLAnimatorWriter(SkAnimator* animator) : fAnimator(animator)
+{
+ fParser = new SkDisplayXMLParser(*fAnimator->fMaker);
+}
+
+SkXMLAnimatorWriter::~SkXMLAnimatorWriter() {
+ delete fParser;
+}
+
+void SkXMLAnimatorWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
+{
+ fParser->onAddAttributeLen(name, value, length);
+}
+
+void SkXMLAnimatorWriter::onEndElement()
+{
+ Elem* elem = getEnd();
+ fParser->onEndElement(elem->fName.c_str());
+ doEnd(elem);
+}
+
+void SkXMLAnimatorWriter::onStartElementLen(const char name[], size_t length)
+{
+ doStart(name, length);
+ fParser->onStartElementLen(name, length);
+}
+
+void SkXMLAnimatorWriter::writeHeader()
+{
+}
+
+#ifdef SK_DEBUG
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+void SkXMLAnimatorWriter::UnitTest(SkCanvas* canvas)
+{
+ SkAnimator s;
+ SkXMLAnimatorWriter w(&s);
+ w.startElement("screenplay");
+ w.startElement("animateField");
+ w.addAttribute("field", "x1");
+ w.addAttribute("id", "to100");
+ w.addAttribute("from", "0");
+ w.addAttribute("to", "100");
+ w.addAttribute("dur", "1");
+ w.endElement();
+ w.startElement("event");
+ w.addAttribute("kind", "onLoad");
+ w.startElement("line");
+ w.addAttribute("id", "line");
+ w.addAttribute("x1", "-1");
+ w.addAttribute("y1", "20");
+ w.addAttribute("x2", "150");
+ w.addAttribute("y2", "40");
+ w.endElement();
+ w.startElement("apply");
+ w.addAttribute("animator", "to100");
+ w.addAttribute("scope", "line");
+ w.endElement();
+ w.endElement();
+ w.endElement();
+ SkPaint paint;
+ canvas->drawColor(SK_ColorWHITE);
+ s.draw(canvas, &paint, 0);
+}
+
+#endif
diff --git a/animator/SkXMLAnimatorWriter.h b/animator/SkXMLAnimatorWriter.h
new file mode 100644
index 00000000..1b17f6ff
--- /dev/null
+++ b/animator/SkXMLAnimatorWriter.h
@@ -0,0 +1,33 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkXMLAnimatorWriter_DEFINED
+#define SkXMLAnimatorWriter_DEFINED
+
+#include "SkXMLWriter.h"
+
+class SkAnimator;
+class SkDisplayXMLParser;
+
+class SkXMLAnimatorWriter : public SkXMLWriter {
+public:
+ SkXMLAnimatorWriter(SkAnimator*);
+ virtual ~SkXMLAnimatorWriter();
+ virtual void writeHeader();
+ SkDEBUGCODE(static void UnitTest(class SkCanvas* canvas);)
+protected:
+ virtual void onAddAttributeLen(const char name[], const char value[], size_t length);
+ virtual void onEndElement();
+ virtual void onStartElementLen(const char elem[], size_t length);
+private:
+ SkAnimator* fAnimator;
+ SkDisplayXMLParser* fParser;
+};
+
+#endif // SkXMLAnimatorWriter_DEFINED
diff --git a/animator/thingstodo.txt b/animator/thingstodo.txt
new file mode 100644
index 00000000..8d0d47a0
--- /dev/null
+++ b/animator/thingstodo.txt
@@ -0,0 +1,21 @@
+things to do:
+ figure out where endless or very deep recursion is possible
+ at these points, generate an error if actual physical stack gets too large
+ candidates are scripts
+ eval(eval(eval... user callouts
+ ((((( operator precedence or similar making stack deep
+ groups within groups
+ very large apply create or apply immediate steps
+
+ write tests for math functions
+ looks like random takes a parameter when it should take zero parameters
+
+ add Math, Number files to perforce for docs
+ alphabetize attributes in docs
+
+ manually modified tools/screenplayDocs/xmlToJPEG.cpp
+
+ fix docs where lines are stitched together (insert space)
+
+ naked <data> outside of <post> asserts on name
+ handle errors for all element not contained by correct parents \ No newline at end of file
diff --git a/core/ARGB32_Clamp_Bilinear_BitmapShader.h b/core/ARGB32_Clamp_Bilinear_BitmapShader.h
new file mode 100644
index 00000000..87121cfd
--- /dev/null
+++ b/core/ARGB32_Clamp_Bilinear_BitmapShader.h
@@ -0,0 +1,177 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+class ARGB32_Clamp_Bilinear_BitmapShader : public SkBitmapShader {
+public:
+ ARGB32_Clamp_Bilinear_BitmapShader(const SkBitmap& src)
+ : SkBitmapShader(src, true,
+ SkShader::kClamp_TileMode, SkShader::kClamp_TileMode)
+ {}
+
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
+};
+
+SkPMColor sample_bilerp(SkFixed fx, SkFixed fy, unsigned srcMaxX, unsigned srcMaxY,
+ const SkPMColor* srcPixels, int srcRB, const SkFilterPtrProc* proc_table);
+SkPMColor sample_bilerp(SkFixed fx, SkFixed fy, unsigned srcMaxX, unsigned srcMaxY,
+ const SkPMColor* srcPixels, int srcRB, const SkFilterPtrProc* proc_table)
+{
+ int ix = fx >> 16;
+ int iy = fy >> 16;
+
+ const SkPMColor *p00, *p01, *p10, *p11;
+
+ p00 = p01 = ((const SkPMColor*)((const char*)srcPixels
+ + SkClampMax(iy, srcMaxY) * srcRB))
+ + SkClampMax(ix, srcMaxX);
+
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+ p10 = p00;
+ p11 = p01;
+ if ((unsigned)iy < srcMaxY)
+ {
+ p10 = (const SkPMColor*)((const char*)p10 + srcRB);
+ p11 = (const SkPMColor*)((const char*)p11 + srcRB);
+ }
+
+ SkFilterPtrProc proc = SkGetBilinearFilterPtrProc(proc_table, fx, fy);
+ return proc(p00, p01, p10, p11);
+}
+
+static inline SkPMColor sample_bilerpx(SkFixed fx, unsigned srcMaxX, const SkPMColor* srcPixels,
+ int srcRB, const SkFilterPtrProc* proc_table)
+{
+ int ix = fx >> 16;
+
+ const SkPMColor *p00, *p01, *p10, *p11;
+
+ p00 = p01 = srcPixels + SkClampMax(ix, srcMaxX);
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+
+ p10 = (const SkPMColor*)((const char*)p00 + srcRB);
+ p11 = (const SkPMColor*)((const char*)p01 + srcRB);
+
+ SkFilterPtrProc proc = SkGetBilinearFilterPtrXProc(proc_table, fx);
+ return proc(p00, p01, p10, p11);
+}
+
+void ARGB32_Clamp_Bilinear_BitmapShader::shadeSpan(int x, int y, SkPMColor dstC[], int count)
+{
+ SkASSERT(count > 0);
+
+ unsigned srcScale = SkAlpha255To256(this->getPaintAlpha());
+
+ const SkMatrix& inv = this->getTotalInverse();
+ const SkBitmap& srcBitmap = this->getSrcBitmap();
+ unsigned srcMaxX = srcBitmap.width() - 1;
+ unsigned srcMaxY = srcBitmap.height() - 1;
+ unsigned srcRB = srcBitmap.rowBytes();
+
+ const SkFilterPtrProc* proc_table = SkGetBilinearFilterPtrProcTable();
+ const SkPMColor* srcPixels = (const SkPMColor*)srcBitmap.getPixels();
+
+ if (this->getInverseClass() == kPerspective_MatrixClass)
+ {
+ SkPerspIter iter(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ if (256 == srcScale)
+ {
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+ while (--count >= 0)
+ {
+ SkFixed fx = *srcXY++ - SK_FixedHalf;
+ SkFixed fy = *srcXY++ - SK_FixedHalf;
+ *dstC++ = sample_bilerp(fx, fy, srcMaxX, srcMaxY, srcPixels, srcRB, proc_table);
+ }
+ }
+ }
+ else // scale by srcScale
+ {
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+ while (--count >= 0)
+ {
+ SkFixed fx = *srcXY++ - SK_FixedHalf;
+ SkFixed fy = *srcXY++ - SK_FixedHalf;
+ SkPMColor c = sample_bilerp(fx, fy, srcMaxX, srcMaxY, srcPixels, srcRB, proc_table);
+ *dstC++ = SkAlphaMulQ(c, srcScale);
+ }
+ }
+ }
+ }
+ else // linear case
+ {
+ SkFixed fx, fy, dx, dy;
+
+ // now init fx, fy, dx, dy
+ {
+ SkPoint srcPt;
+ this->getInverseMapPtProc()(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf,
+ &srcPt);
+
+ fx = SkScalarToFixed(srcPt.fX) - SK_FixedHalf;
+ fy = SkScalarToFixed(srcPt.fY) - SK_FixedHalf;
+
+ if (this->getInverseClass() == kFixedStepInX_MatrixClass)
+ (void)inv.fixedStepInX(SkIntToScalar(y), &dx, &dy);
+ else
+ {
+ dx = SkScalarToFixed(inv.getScaleX());
+ dy = SkScalarToFixed(inv.getSkewY());
+ }
+ }
+
+ if (dy == 0 && (unsigned)(fy >> 16) < srcMaxY)
+ {
+ srcPixels = (const SkPMColor*)((const char*)srcPixels + (fy >> 16) * srcRB);
+ proc_table = SkGetBilinearFilterPtrProcYTable(proc_table, fy);
+ if (256 == srcScale)
+ {
+ do {
+ *dstC++ = sample_bilerpx(fx, srcMaxX, srcPixels, srcRB, proc_table);
+ fx += dx;
+ } while (--count != 0);
+ }
+ else
+ {
+ do {
+ SkPMColor c = sample_bilerpx(fx, srcMaxX, srcPixels, srcRB, proc_table);
+ *dstC++ = SkAlphaMulQ(c, srcScale);
+ fx += dx;
+ } while (--count != 0);
+ }
+ }
+ else // dy is != 0
+ {
+ if (256 == srcScale)
+ {
+ do {
+ *dstC++ = sample_bilerp(fx, fy, srcMaxX, srcMaxY, srcPixels, srcRB, proc_table);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ else
+ {
+ do {
+ SkPMColor c = sample_bilerp(fx, fy, srcMaxX, srcMaxY, srcPixels, srcRB, proc_table);
+ *dstC++ = SkAlphaMulQ(c, srcScale);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ }
+ }
+}
diff --git a/core/Sk64.cpp b/core/Sk64.cpp
new file mode 100644
index 00000000..7c195ce4
--- /dev/null
+++ b/core/Sk64.cpp
@@ -0,0 +1,354 @@
+/*
+ * 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 "Sk64.h"
+#include "SkMathPriv.h"
+
+#define shift_left(hi, lo) \
+ hi = (hi << 1) | (lo >> 31); \
+ lo <<= 1
+
+#define shift_left_bits(hi, lo, bits) \
+ SkASSERT((unsigned)(bits) < 31); \
+ hi = (hi << (bits)) | (lo >> (32 - (bits))); \
+ lo <<= (bits)
+
+//////////////////////////////////////////////////////////////////////
+
+int Sk64::getClzAbs() const
+{
+ int32_t hi = fHi;
+ uint32_t lo = fLo;
+
+ // get abs
+ if (hi < 0)
+ {
+ hi = -hi - Sk32ToBool(lo);
+ lo = 0 - lo;
+ }
+ return hi ? SkCLZ(hi) : SkCLZ(lo) + 32;
+}
+
+void Sk64::shiftLeft(unsigned bits)
+{
+ SkASSERT(bits <= 63);
+ if (bits == 0)
+ return;
+
+ if (bits >= 32)
+ {
+ fHi = fLo << (bits - 32);
+ fLo = 0;
+ }
+ else
+ {
+ fHi = (fHi << bits) | (fLo >> (32 - bits));
+ fLo <<= bits;
+ }
+}
+
+int32_t Sk64::getShiftRight(unsigned bits) const
+{
+ SkASSERT(bits <= 63);
+
+ if (bits == 0)
+ return fLo;
+
+ if (bits >= 32)
+ return fHi >> (bits - 32);
+ else
+ {
+#ifdef SK_DEBUG
+ int32_t tmp = fHi >> bits;
+ SkASSERT(tmp == 0 || tmp == -1);
+#endif
+ return (fHi << (32 - bits)) | (fLo >> bits);
+ }
+}
+
+void Sk64::shiftRight(unsigned bits)
+{
+ SkASSERT(bits <= 63);
+ if (bits == 0)
+ return;
+
+ if (bits >= 32)
+ {
+ fLo = fHi >> (bits - 32);
+ fHi >>= 31;
+ }
+ else
+ {
+ fLo = (fHi << (32 - bits)) | (fLo >> bits);
+ fHi >>= bits;
+ }
+}
+
+void Sk64::roundRight(unsigned bits)
+{
+ SkASSERT(bits <= 63);
+ if (bits)
+ {
+ Sk64 one;
+ one.set(1);
+ one.shiftLeft(bits - 1);
+ this->add(one);
+ this->shiftRight(bits);
+ }
+}
+
+int Sk64::shiftToMake32() const
+{
+ int32_t hi = fHi;
+ uint32_t lo = fLo;
+
+ if (hi < 0) // make it positive
+ {
+ hi = -hi - Sk32ToBool(lo);
+ lo = 0 - lo;
+ }
+
+ if (hi == 0)
+ return lo >> 31;
+ else
+ return 33 - SkCLZ(hi);
+}
+
+void Sk64::negate()
+{
+ fHi = -fHi - Sk32ToBool(fLo);
+ fLo = 0 - fLo;
+}
+
+void Sk64::abs()
+{
+ if (fHi < 0)
+ {
+ fHi = -fHi - Sk32ToBool(fLo);
+ fLo = 0 - fLo;
+ }
+}
+
+////////////////////////////////////////////////////////////////
+
+static inline int32_t round_right_16(int32_t hi, uint32_t lo)
+{
+ uint32_t sum = lo + (1 << 15);
+ hi += (sum < lo);
+ return (hi << 16) | (sum >> 16);
+}
+
+SkBool Sk64::isFixed() const
+{
+ Sk64 tmp = *this;
+ tmp.roundRight(16);
+ return tmp.is32();
+}
+
+SkFract Sk64::getFract() const
+{
+ Sk64 tmp = *this;
+ tmp.roundRight(30);
+ return tmp.get32();
+}
+
+void Sk64::sub(const Sk64& a)
+{
+ fHi = fHi - a.fHi - (fLo < a.fLo);
+ fLo = fLo - a.fLo;
+}
+
+void Sk64::rsub(const Sk64& a)
+{
+ fHi = a.fHi - fHi - (a.fLo < fLo);
+ fLo = a.fLo - fLo;
+}
+
+void Sk64::setMul(int32_t a, int32_t b)
+{
+ int sa = a >> 31;
+ int sb = b >> 31;
+ // now make them positive
+ a = (a ^ sa) - sa;
+ b = (b ^ sb) - sb;
+
+ uint32_t ah = a >> 16;
+ uint32_t al = a & 0xFFFF;
+ uint32_t bh = b >> 16;
+ uint32_t bl = b & 0xFFFF;
+
+ uint32_t A = ah * bh;
+ uint32_t B = ah * bl + al * bh;
+ uint32_t C = al * bl;
+
+ /* [ A ]
+ [ B ]
+ [ C ]
+ */
+ fLo = C + (B << 16);
+ fHi = A + (B >>16) + (fLo < C);
+
+ if (sa != sb)
+ this->negate();
+}
+
+void Sk64::div(int32_t denom, DivOptions option)
+{
+ SkASSERT(denom);
+
+ int32_t hi = fHi;
+ uint32_t lo = fLo;
+ int sign = denom ^ hi;
+
+ denom = SkAbs32(denom);
+ if (hi < 0)
+ {
+ hi = -hi - Sk32ToBool(lo);
+ lo = 0 - lo;
+ }
+
+ if (option == kRound_DivOption) // add denom/2
+ {
+ uint32_t newLo = lo + (denom >> 1);
+ hi += (newLo < lo);
+ lo = newLo;
+ }
+
+ if (hi == 0) // fast-case
+ {
+ if (lo < (uint32_t)denom)
+ this->set(0, 0);
+ else
+ {
+ this->set(0, lo / denom);
+ if (sign < 0)
+ this->negate();
+ }
+ return;
+ }
+
+ int bits;
+
+ {
+ int dbits = SkCLZ(denom);
+ int nbits = SkCLZ(hi);
+
+ bits = 32 + dbits - nbits;
+ SkASSERT(bits <= 63);
+ if (bits <= 0)
+ {
+ this->set(0, 0);
+ return;
+ }
+ denom <<= (dbits - 1);
+ shift_left_bits(hi, lo, nbits - 1);
+ }
+
+ int32_t rhi = 0;
+ uint32_t rlo = 0;
+
+ do {
+ shift_left(rhi, rlo);
+ if ((uint32_t)denom <= (uint32_t)hi)
+ {
+ hi -= denom;
+ rlo |= 1;
+ }
+ shift_left(hi, lo);
+ } while (--bits >= 0);
+ SkASSERT(rhi >= 0);
+
+ fHi = rhi;
+ fLo = rlo;
+ if (sign < 0)
+ this->negate();
+}
+
+#define shift_left_2(a, b, c) \
+ a = (a << 2) | (b >> 30); \
+ b = (b << 2) | (c >> 30); \
+ c <<= 2
+
+int32_t Sk64::getSqrt() const
+{
+ SkASSERT(!this->isNeg());
+
+ uint32_t hi = fHi;
+ uint32_t lo = fLo;
+ uint32_t sqr = 0;
+ uint32_t root = 0;
+ int count = 31;
+
+ do {
+ root <<= 1;
+ shift_left_2(sqr, hi, lo);
+
+ uint32_t testDiv = (root << 1) + 1;
+ if (sqr >= testDiv)
+ {
+ sqr -= testDiv;
+ root++;
+ }
+ } while (--count >= 0);
+ SkASSERT((int32_t)root >= 0);
+
+ return root;
+}
+
+#ifdef SkLONGLONG
+ SkLONGLONG Sk64::getLongLong() const
+ {
+ SkLONGLONG value = fHi;
+ value <<= 32;
+ return value | fLo;
+ }
+#endif
+
+SkFixed Sk64::getFixedDiv(const Sk64& denom) const
+{
+ Sk64 N = *this;
+ Sk64 D = denom;
+ int32_t sign = SkExtractSign(N.fHi ^ D.fHi);
+ SkFixed result;
+
+ N.abs();
+ D.abs();
+
+ // need to knock D down to just 31 bits
+ // either by rounding it to the right, or shifting N to the left
+ // then we can just call 64/32 div
+
+ int nclz = N.fHi ? SkCLZ(N.fHi) : 32;
+ int dclz = D.fHi ? SkCLZ(D.fHi) : (33 - (D.fLo >> 31));
+
+ int shiftN = nclz - 1;
+ SkASSERT(shiftN >= 0);
+ int shiftD = 33 - dclz;
+ SkASSERT(shiftD >= 0);
+
+ if (shiftD + shiftN < 16)
+ shiftD = 16 - shiftN;
+ else
+ shiftN = 16 - shiftD;
+
+ D.roundRight(shiftD);
+ if (D.isZero())
+ result = SK_MaxS32;
+ else
+ {
+ if (shiftN >= 0)
+ N.shiftLeft(shiftN);
+ else
+ N.roundRight(-shiftN);
+ N.div(D.get32(), Sk64::kTrunc_DivOption);
+ if (N.is32())
+ result = N.get32();
+ else
+ result = SK_MaxS32;
+ }
+ return SkApplySign(result, sign);
+}
diff --git a/core/SkAAClip.cpp b/core/SkAAClip.cpp
new file mode 100644
index 00000000..6ab1c89d
--- /dev/null
+++ b/core/SkAAClip.cpp
@@ -0,0 +1,2178 @@
+
+/*
+ * 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 "SkAAClip.h"
+#include "SkBlitter.h"
+#include "SkColorPriv.h"
+#include "SkPath.h"
+#include "SkScan.h"
+#include "SkThread.h"
+#include "SkUtils.h"
+
+class AutoAAClipValidate {
+public:
+ AutoAAClipValidate(const SkAAClip& clip) : fClip(clip) {
+ fClip.validate();
+ }
+ ~AutoAAClipValidate() {
+ fClip.validate();
+ }
+private:
+ const SkAAClip& fClip;
+};
+
+#ifdef SK_DEBUG
+ #define AUTO_AACLIP_VALIDATE(clip) AutoAAClipValidate acv(clip)
+#else
+ #define AUTO_AACLIP_VALIDATE(clip)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define kMaxInt32 0x7FFFFFFF
+
+static inline bool x_in_rect(int x, const SkIRect& rect) {
+ return (unsigned)(x - rect.fLeft) < (unsigned)rect.width();
+}
+
+static inline bool y_in_rect(int y, const SkIRect& rect) {
+ return (unsigned)(y - rect.fTop) < (unsigned)rect.height();
+}
+
+/*
+ * Data runs are packed [count, alpha]
+ */
+
+struct SkAAClip::YOffset {
+ int32_t fY;
+ uint32_t fOffset;
+};
+
+struct SkAAClip::RunHead {
+ int32_t fRefCnt;
+ int32_t fRowCount;
+ size_t fDataSize;
+
+ YOffset* yoffsets() {
+ return (YOffset*)((char*)this + sizeof(RunHead));
+ }
+ const YOffset* yoffsets() const {
+ return (const YOffset*)((const char*)this + sizeof(RunHead));
+ }
+ uint8_t* data() {
+ return (uint8_t*)(this->yoffsets() + fRowCount);
+ }
+ const uint8_t* data() const {
+ return (const uint8_t*)(this->yoffsets() + fRowCount);
+ }
+
+ static RunHead* Alloc(int rowCount, size_t dataSize) {
+ size_t size = sizeof(RunHead) + rowCount * sizeof(YOffset) + dataSize;
+ RunHead* head = (RunHead*)sk_malloc_throw(size);
+ head->fRefCnt = 1;
+ head->fRowCount = rowCount;
+ head->fDataSize = dataSize;
+ return head;
+ }
+
+ static int ComputeRowSizeForWidth(int width) {
+ // 2 bytes per segment, where each segment can store up to 255 for count
+ int segments = 0;
+ while (width > 0) {
+ segments += 1;
+ int n = SkMin32(width, 255);
+ width -= n;
+ }
+ return segments * 2; // each segment is row[0] + row[1] (n + alpha)
+ }
+
+ static RunHead* AllocRect(const SkIRect& bounds) {
+ SkASSERT(!bounds.isEmpty());
+ int width = bounds.width();
+ size_t rowSize = ComputeRowSizeForWidth(width);
+ RunHead* head = RunHead::Alloc(1, rowSize);
+ YOffset* yoff = head->yoffsets();
+ yoff->fY = bounds.height() - 1;
+ yoff->fOffset = 0;
+ uint8_t* row = head->data();
+ while (width > 0) {
+ int n = SkMin32(width, 255);
+ row[0] = n;
+ row[1] = 0xFF;
+ width -= n;
+ row += 2;
+ }
+ return head;
+ }
+};
+
+class SkAAClip::Iter {
+public:
+ Iter(const SkAAClip&);
+
+ bool done() const { return fDone; }
+ int top() const { return fTop; }
+ int bottom() const { return fBottom; }
+ const uint8_t* data() const { return fData; }
+ void next();
+
+private:
+ const YOffset* fCurrYOff;
+ const YOffset* fStopYOff;
+ const uint8_t* fData;
+
+ int fTop, fBottom;
+ bool fDone;
+};
+
+SkAAClip::Iter::Iter(const SkAAClip& clip) {
+ if (clip.isEmpty()) {
+ fDone = true;
+ fTop = fBottom = clip.fBounds.fBottom;
+ fData = NULL;
+ fCurrYOff = NULL;
+ fStopYOff = NULL;
+ return;
+ }
+
+ const RunHead* head = clip.fRunHead;
+ fCurrYOff = head->yoffsets();
+ fStopYOff = fCurrYOff + head->fRowCount;
+ fData = head->data() + fCurrYOff->fOffset;
+
+ // setup first value
+ fTop = clip.fBounds.fTop;
+ fBottom = clip.fBounds.fTop + fCurrYOff->fY + 1;
+ fDone = false;
+}
+
+void SkAAClip::Iter::next() {
+ if (!fDone) {
+ const YOffset* prev = fCurrYOff;
+ const YOffset* curr = prev + 1;
+ SkASSERT(curr <= fStopYOff);
+
+ fTop = fBottom;
+ if (curr >= fStopYOff) {
+ fDone = true;
+ fBottom = kMaxInt32;
+ fData = NULL;
+ } else {
+ fBottom += curr->fY - prev->fY;
+ fData += curr->fOffset - prev->fOffset;
+ fCurrYOff = curr;
+ }
+ }
+}
+
+#ifdef SK_DEBUG
+// assert we're exactly width-wide, and then return the number of bytes used
+static size_t compute_row_length(const uint8_t row[], int width) {
+ const uint8_t* origRow = row;
+ while (width > 0) {
+ int n = row[0];
+ SkASSERT(n > 0);
+ SkASSERT(n <= width);
+ row += 2;
+ width -= n;
+ }
+ SkASSERT(0 == width);
+ return row - origRow;
+}
+
+void SkAAClip::validate() const {
+ if (NULL == fRunHead) {
+ SkASSERT(fBounds.isEmpty());
+ return;
+ }
+
+ const RunHead* head = fRunHead;
+ SkASSERT(head->fRefCnt > 0);
+ SkASSERT(head->fRowCount > 0);
+
+ const YOffset* yoff = head->yoffsets();
+ const YOffset* ystop = yoff + head->fRowCount;
+ const int lastY = fBounds.height() - 1;
+
+ // Y and offset must be monotonic
+ int prevY = -1;
+ int32_t prevOffset = -1;
+ while (yoff < ystop) {
+ SkASSERT(prevY < yoff->fY);
+ SkASSERT(yoff->fY <= lastY);
+ prevY = yoff->fY;
+ SkASSERT(prevOffset < (int32_t)yoff->fOffset);
+ prevOffset = yoff->fOffset;
+ const uint8_t* row = head->data() + yoff->fOffset;
+ size_t rowLength = compute_row_length(row, fBounds.width());
+ SkASSERT(yoff->fOffset + rowLength <= head->fDataSize);
+ yoff += 1;
+ }
+ // check the last entry;
+ --yoff;
+ SkASSERT(yoff->fY == lastY);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Count the number of zeros on the left and right edges of the passed in
+// RLE row. If 'row' is all zeros return 'width' in both variables.
+static void count_left_right_zeros(const uint8_t* row, int width,
+ int* leftZ, int* riteZ) {
+ int zeros = 0;
+ do {
+ if (row[1]) {
+ break;
+ }
+ int n = row[0];
+ SkASSERT(n > 0);
+ SkASSERT(n <= width);
+ zeros += n;
+ row += 2;
+ width -= n;
+ } while (width > 0);
+ *leftZ = zeros;
+
+ if (0 == width) {
+ // this line is completely empty return 'width' in both variables
+ *riteZ = *leftZ;
+ return;
+ }
+
+ zeros = 0;
+ while (width > 0) {
+ int n = row[0];
+ SkASSERT(n > 0);
+ if (0 == row[1]) {
+ zeros += n;
+ } else {
+ zeros = 0;
+ }
+ row += 2;
+ width -= n;
+ }
+ *riteZ = zeros;
+}
+
+#ifdef SK_DEBUG
+static void test_count_left_right_zeros() {
+ static bool gOnce;
+ if (gOnce) {
+ return;
+ }
+ gOnce = true;
+
+ const uint8_t data0[] = { 0, 0, 10, 0xFF };
+ const uint8_t data1[] = { 0, 0, 5, 0xFF, 2, 0, 3, 0xFF };
+ const uint8_t data2[] = { 7, 0, 5, 0, 2, 0, 3, 0xFF };
+ const uint8_t data3[] = { 0, 5, 5, 0xFF, 2, 0, 3, 0 };
+ const uint8_t data4[] = { 2, 3, 2, 0, 5, 0xFF, 3, 0 };
+ const uint8_t data5[] = { 10, 10, 10, 0 };
+ const uint8_t data6[] = { 2, 2, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+
+ const uint8_t* array[] = {
+ data0, data1, data2, data3, data4, data5, data6
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(array); ++i) {
+ const uint8_t* data = array[i];
+ const int expectedL = *data++;
+ const int expectedR = *data++;
+ int L = 12345, R = 12345;
+ count_left_right_zeros(data, 10, &L, &R);
+ SkASSERT(expectedL == L);
+ SkASSERT(expectedR == R);
+ }
+}
+#endif
+
+// modify row in place, trimming off (zeros) from the left and right sides.
+// return the number of bytes that were completely eliminated from the left
+static int trim_row_left_right(uint8_t* row, int width, int leftZ, int riteZ) {
+ int trim = 0;
+ while (leftZ > 0) {
+ SkASSERT(0 == row[1]);
+ int n = row[0];
+ SkASSERT(n > 0);
+ SkASSERT(n <= width);
+ width -= n;
+ row += 2;
+ if (n > leftZ) {
+ row[-2] = n - leftZ;
+ break;
+ }
+ trim += 2;
+ leftZ -= n;
+ SkASSERT(leftZ >= 0);
+ }
+
+ if (riteZ) {
+ // walk row to the end, and then we'll back up to trim riteZ
+ while (width > 0) {
+ int n = row[0];
+ SkASSERT(n <= width);
+ width -= n;
+ row += 2;
+ }
+ // now skip whole runs of zeros
+ do {
+ row -= 2;
+ SkASSERT(0 == row[1]);
+ int n = row[0];
+ SkASSERT(n > 0);
+ if (n > riteZ) {
+ row[0] = n - riteZ;
+ break;
+ }
+ riteZ -= n;
+ SkASSERT(riteZ >= 0);
+ } while (riteZ > 0);
+ }
+
+ return trim;
+}
+
+#ifdef SK_DEBUG
+// assert that this row is exactly this width
+static void assert_row_width(const uint8_t* row, int width) {
+ while (width > 0) {
+ int n = row[0];
+ SkASSERT(n > 0);
+ SkASSERT(n <= width);
+ width -= n;
+ row += 2;
+ }
+ SkASSERT(0 == width);
+}
+
+static void test_trim_row_left_right() {
+ static bool gOnce;
+ if (gOnce) {
+ return;
+ }
+ gOnce = true;
+
+ uint8_t data0[] = { 0, 0, 0, 10, 10, 0xFF };
+ uint8_t data1[] = { 2, 0, 0, 10, 5, 0, 2, 0, 3, 0xFF };
+ uint8_t data2[] = { 5, 0, 2, 10, 5, 0, 2, 0, 3, 0xFF };
+ uint8_t data3[] = { 6, 0, 2, 10, 5, 0, 2, 0, 3, 0xFF };
+ uint8_t data4[] = { 0, 0, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data5[] = { 1, 0, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data6[] = { 0, 1, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data7[] = { 1, 1, 0, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data8[] = { 2, 2, 2, 10, 2, 0, 2, 0xFF, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data9[] = { 5, 2, 4, 10, 2, 0, 2, 0, 2, 0, 2, 0xFF, 2, 0 };
+ uint8_t data10[] ={ 74, 0, 4, 150, 9, 0, 65, 0, 76, 0xFF };
+
+ uint8_t* array[] = {
+ data0, data1, data2, data3, data4,
+ data5, data6, data7, data8, data9,
+ data10
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(array); ++i) {
+ uint8_t* data = array[i];
+ const int trimL = *data++;
+ const int trimR = *data++;
+ const int expectedSkip = *data++;
+ const int origWidth = *data++;
+ assert_row_width(data, origWidth);
+ int skip = trim_row_left_right(data, origWidth, trimL, trimR);
+ SkASSERT(expectedSkip == skip);
+ int expectedWidth = origWidth - trimL - trimR;
+ assert_row_width(data + skip, expectedWidth);
+ }
+}
+#endif
+
+bool SkAAClip::trimLeftRight() {
+ SkDEBUGCODE(test_trim_row_left_right();)
+
+ if (this->isEmpty()) {
+ return false;
+ }
+
+ AUTO_AACLIP_VALIDATE(*this);
+
+ const int width = fBounds.width();
+ RunHead* head = fRunHead;
+ YOffset* yoff = head->yoffsets();
+ YOffset* stop = yoff + head->fRowCount;
+ uint8_t* base = head->data();
+
+ // After this loop, 'leftZeros' & 'rightZeros' will contain the minimum
+ // number of zeros on the left and right of the clip. This information
+ // can be used to shrink the bounding box.
+ int leftZeros = width;
+ int riteZeros = width;
+ while (yoff < stop) {
+ int L, R;
+ count_left_right_zeros(base + yoff->fOffset, width, &L, &R);
+ SkASSERT(L + R < width || (L == width && R == width));
+ if (L < leftZeros) {
+ leftZeros = L;
+ }
+ if (R < riteZeros) {
+ riteZeros = R;
+ }
+ if (0 == (leftZeros | riteZeros)) {
+ // no trimming to do
+ return true;
+ }
+ yoff += 1;
+ }
+
+ SkASSERT(leftZeros || riteZeros);
+ if (width == leftZeros) {
+ SkASSERT(width == riteZeros);
+ return this->setEmpty();
+ }
+
+ this->validate();
+
+ fBounds.fLeft += leftZeros;
+ fBounds.fRight -= riteZeros;
+ SkASSERT(!fBounds.isEmpty());
+
+ // For now we don't realloc the storage (for time), we just shrink in place
+ // This means we don't have to do any memmoves either, since we can just
+ // play tricks with the yoff->fOffset for each row
+ yoff = head->yoffsets();
+ while (yoff < stop) {
+ uint8_t* row = base + yoff->fOffset;
+ SkDEBUGCODE((void)compute_row_length(row, width);)
+ yoff->fOffset += trim_row_left_right(row, width, leftZeros, riteZeros);
+ SkDEBUGCODE((void)compute_row_length(base + yoff->fOffset, width - leftZeros - riteZeros);)
+ yoff += 1;
+ }
+ return true;
+}
+
+static bool row_is_all_zeros(const uint8_t* row, int width) {
+ SkASSERT(width > 0);
+ do {
+ if (row[1]) {
+ return false;
+ }
+ int n = row[0];
+ SkASSERT(n <= width);
+ width -= n;
+ row += 2;
+ } while (width > 0);
+ SkASSERT(0 == width);
+ return true;
+}
+
+bool SkAAClip::trimTopBottom() {
+ if (this->isEmpty()) {
+ return false;
+ }
+
+ this->validate();
+
+ const int width = fBounds.width();
+ RunHead* head = fRunHead;
+ YOffset* yoff = head->yoffsets();
+ YOffset* stop = yoff + head->fRowCount;
+ const uint8_t* base = head->data();
+
+ // Look to trim away empty rows from the top.
+ //
+ int skip = 0;
+ while (yoff < stop) {
+ const uint8_t* data = base + yoff->fOffset;
+ if (!row_is_all_zeros(data, width)) {
+ break;
+ }
+ skip += 1;
+ yoff += 1;
+ }
+ SkASSERT(skip <= head->fRowCount);
+ if (skip == head->fRowCount) {
+ return this->setEmpty();
+ }
+ if (skip > 0) {
+ // adjust fRowCount and fBounds.fTop, and slide all the data up
+ // as we remove [skip] number of YOffset entries
+ yoff = head->yoffsets();
+ int dy = yoff[skip - 1].fY + 1;
+ for (int i = skip; i < head->fRowCount; ++i) {
+ SkASSERT(yoff[i].fY >= dy);
+ yoff[i].fY -= dy;
+ }
+ YOffset* dst = head->yoffsets();
+ size_t size = head->fRowCount * sizeof(YOffset) + head->fDataSize;
+ memmove(dst, dst + skip, size - skip * sizeof(YOffset));
+
+ fBounds.fTop += dy;
+ SkASSERT(!fBounds.isEmpty());
+ head->fRowCount -= skip;
+ SkASSERT(head->fRowCount > 0);
+
+ this->validate();
+ // need to reset this after the memmove
+ base = head->data();
+ }
+
+ // Look to trim away empty rows from the bottom.
+ // We know that we have at least one non-zero row, so we can just walk
+ // backwards without checking for running past the start.
+ //
+ stop = yoff = head->yoffsets() + head->fRowCount;
+ do {
+ yoff -= 1;
+ } while (row_is_all_zeros(base + yoff->fOffset, width));
+ skip = stop - yoff - 1;
+ SkASSERT(skip >= 0 && skip < head->fRowCount);
+ if (skip > 0) {
+ // removing from the bottom is easier than from the top, as we don't
+ // have to adjust any of the Y values, we just have to trim the array
+ memmove(stop - skip, stop, head->fDataSize);
+
+ fBounds.fBottom = fBounds.fTop + yoff->fY + 1;
+ SkASSERT(!fBounds.isEmpty());
+ head->fRowCount -= skip;
+ SkASSERT(head->fRowCount > 0);
+ }
+ this->validate();
+
+ return true;
+}
+
+// can't validate before we're done, since trimming is part of the process of
+// making us valid after the Builder. Since we build from top to bottom, its
+// possible our fBounds.fBottom is bigger than our last scanline of data, so
+// we trim fBounds.fBottom back up.
+//
+// TODO: check for duplicates in X and Y to further compress our data
+//
+bool SkAAClip::trimBounds() {
+ if (this->isEmpty()) {
+ return false;
+ }
+
+ const RunHead* head = fRunHead;
+ const YOffset* yoff = head->yoffsets();
+
+ SkASSERT(head->fRowCount > 0);
+ const YOffset& lastY = yoff[head->fRowCount - 1];
+ SkASSERT(lastY.fY + 1 <= fBounds.height());
+ fBounds.fBottom = fBounds.fTop + lastY.fY + 1;
+ SkASSERT(lastY.fY + 1 == fBounds.height());
+ SkASSERT(!fBounds.isEmpty());
+
+ return this->trimTopBottom() && this->trimLeftRight();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkAAClip::freeRuns() {
+ if (fRunHead) {
+ SkASSERT(fRunHead->fRefCnt >= 1);
+ if (1 == sk_atomic_dec(&fRunHead->fRefCnt)) {
+ sk_free(fRunHead);
+ }
+ }
+}
+
+SkAAClip::SkAAClip() {
+ fBounds.setEmpty();
+ fRunHead = NULL;
+}
+
+SkAAClip::SkAAClip(const SkAAClip& src) {
+ SkDEBUGCODE(fBounds.setEmpty();) // need this for validate
+ fRunHead = NULL;
+ *this = src;
+}
+
+SkAAClip::~SkAAClip() {
+ this->freeRuns();
+}
+
+SkAAClip& SkAAClip::operator=(const SkAAClip& src) {
+ AUTO_AACLIP_VALIDATE(*this);
+ src.validate();
+
+ if (this != &src) {
+ this->freeRuns();
+ fBounds = src.fBounds;
+ fRunHead = src.fRunHead;
+ if (fRunHead) {
+ sk_atomic_inc(&fRunHead->fRefCnt);
+ }
+ }
+ return *this;
+}
+
+bool operator==(const SkAAClip& a, const SkAAClip& b) {
+ a.validate();
+ b.validate();
+
+ if (&a == &b) {
+ return true;
+ }
+ if (a.fBounds != b.fBounds) {
+ return false;
+ }
+
+ const SkAAClip::RunHead* ah = a.fRunHead;
+ const SkAAClip::RunHead* bh = b.fRunHead;
+
+ // this catches empties and rects being equal
+ if (ah == bh) {
+ return true;
+ }
+
+ // now we insist that both are complex (but different ptrs)
+ if (!a.fRunHead || !b.fRunHead) {
+ return false;
+ }
+
+ return ah->fRowCount == bh->fRowCount &&
+ ah->fDataSize == bh->fDataSize &&
+ !memcmp(ah->data(), bh->data(), ah->fDataSize);
+}
+
+void SkAAClip::swap(SkAAClip& other) {
+ AUTO_AACLIP_VALIDATE(*this);
+ other.validate();
+
+ SkTSwap(fBounds, other.fBounds);
+ SkTSwap(fRunHead, other.fRunHead);
+}
+
+bool SkAAClip::set(const SkAAClip& src) {
+ *this = src;
+ return !this->isEmpty();
+}
+
+bool SkAAClip::setEmpty() {
+ this->freeRuns();
+ fBounds.setEmpty();
+ fRunHead = NULL;
+ return false;
+}
+
+bool SkAAClip::setRect(const SkIRect& bounds) {
+ if (bounds.isEmpty()) {
+ return this->setEmpty();
+ }
+
+ AUTO_AACLIP_VALIDATE(*this);
+
+#if 0
+ SkRect r;
+ r.set(bounds);
+ SkPath path;
+ path.addRect(r);
+ return this->setPath(path);
+#else
+ this->freeRuns();
+ fBounds = bounds;
+ fRunHead = RunHead::AllocRect(bounds);
+ SkASSERT(!this->isEmpty());
+ return true;
+#endif
+}
+
+bool SkAAClip::setRect(const SkRect& r, bool doAA) {
+ if (r.isEmpty()) {
+ return this->setEmpty();
+ }
+
+ AUTO_AACLIP_VALIDATE(*this);
+
+ // TODO: special case this
+
+ SkPath path;
+ path.addRect(r);
+ return this->setPath(path, NULL, doAA);
+}
+
+static void append_run(SkTDArray<uint8_t>& array, uint8_t value, int count) {
+ SkASSERT(count >= 0);
+ while (count > 0) {
+ int n = count;
+ if (n > 255) {
+ n = 255;
+ }
+ uint8_t* data = array.append(2);
+ data[0] = n;
+ data[1] = value;
+ count -= n;
+ }
+}
+
+bool SkAAClip::setRegion(const SkRegion& rgn) {
+ if (rgn.isEmpty()) {
+ return this->setEmpty();
+ }
+ if (rgn.isRect()) {
+ return this->setRect(rgn.getBounds());
+ }
+
+#if 0
+ SkAAClip clip;
+ SkRegion::Iterator iter(rgn);
+ for (; !iter.done(); iter.next()) {
+ clip.op(iter.rect(), SkRegion::kUnion_Op);
+ }
+ this->swap(clip);
+ return !this->isEmpty();
+#else
+ const SkIRect& bounds = rgn.getBounds();
+ const int offsetX = bounds.fLeft;
+ const int offsetY = bounds.fTop;
+
+ SkTDArray<YOffset> yArray;
+ SkTDArray<uint8_t> xArray;
+
+ yArray.setReserve(SkMin32(bounds.height(), 1024));
+ xArray.setReserve(SkMin32(bounds.width() * 128, 64 * 1024));
+
+ SkRegion::Iterator iter(rgn);
+ int prevRight = 0;
+ int prevBot = 0;
+ YOffset* currY = NULL;
+
+ for (; !iter.done(); iter.next()) {
+ const SkIRect& r = iter.rect();
+ SkASSERT(bounds.contains(r));
+
+ int bot = r.fBottom - offsetY;
+ SkASSERT(bot >= prevBot);
+ if (bot > prevBot) {
+ if (currY) {
+ // flush current row
+ append_run(xArray, 0, bounds.width() - prevRight);
+ }
+ // did we introduce an empty-gap from the prev row?
+ int top = r.fTop - offsetY;
+ if (top > prevBot) {
+ currY = yArray.append();
+ currY->fY = top - 1;
+ currY->fOffset = xArray.count();
+ append_run(xArray, 0, bounds.width());
+ }
+ // create a new record for this Y value
+ currY = yArray.append();
+ currY->fY = bot - 1;
+ currY->fOffset = xArray.count();
+ prevRight = 0;
+ prevBot = bot;
+ }
+
+ int x = r.fLeft - offsetX;
+ append_run(xArray, 0, x - prevRight);
+
+ int w = r.fRight - r.fLeft;
+ append_run(xArray, 0xFF, w);
+ prevRight = x + w;
+ SkASSERT(prevRight <= bounds.width());
+ }
+ // flush last row
+ append_run(xArray, 0, bounds.width() - prevRight);
+
+ // now pack everything into a RunHead
+ RunHead* head = RunHead::Alloc(yArray.count(), xArray.bytes());
+ memcpy(head->yoffsets(), yArray.begin(), yArray.bytes());
+ memcpy(head->data(), xArray.begin(), xArray.bytes());
+
+ this->setEmpty();
+ fBounds = bounds;
+ fRunHead = head;
+ this->validate();
+ return true;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const uint8_t* SkAAClip::findRow(int y, int* lastYForRow) const {
+ SkASSERT(fRunHead);
+
+ if (!y_in_rect(y, fBounds)) {
+ return NULL;
+ }
+ y -= fBounds.y(); // our yoffs values are relative to the top
+
+ const YOffset* yoff = fRunHead->yoffsets();
+ while (yoff->fY < y) {
+ yoff += 1;
+ SkASSERT(yoff - fRunHead->yoffsets() < fRunHead->fRowCount);
+ }
+
+ if (lastYForRow) {
+ *lastYForRow = fBounds.y() + yoff->fY;
+ }
+ return fRunHead->data() + yoff->fOffset;
+}
+
+const uint8_t* SkAAClip::findX(const uint8_t data[], int x, int* initialCount) const {
+ SkASSERT(x_in_rect(x, fBounds));
+ x -= fBounds.x();
+
+ // first skip up to X
+ for (;;) {
+ int n = data[0];
+ if (x < n) {
+ if (initialCount) {
+ *initialCount = n - x;
+ }
+ break;
+ }
+ data += 2;
+ x -= n;
+ }
+ return data;
+}
+
+bool SkAAClip::quickContains(int left, int top, int right, int bottom) const {
+ if (this->isEmpty()) {
+ return false;
+ }
+ if (!fBounds.contains(left, top, right, bottom)) {
+ return false;
+ }
+#if 0
+ if (this->isRect()) {
+ return true;
+ }
+#endif
+
+ int lastY SK_INIT_TO_AVOID_WARNING;
+ const uint8_t* row = this->findRow(top, &lastY);
+ if (lastY < bottom) {
+ return false;
+ }
+ // now just need to check in X
+ int count;
+ row = this->findX(row, left, &count);
+#if 0
+ return count >= (right - left) && 0xFF == row[1];
+#else
+ int rectWidth = right - left;
+ while (0xFF == row[1]) {
+ if (count >= rectWidth) {
+ return true;
+ }
+ rectWidth -= count;
+ row += 2;
+ count = row[0];
+ }
+ return false;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkAAClip::Builder {
+ SkIRect fBounds;
+ struct Row {
+ int fY;
+ int fWidth;
+ SkTDArray<uint8_t>* fData;
+ };
+ SkTDArray<Row> fRows;
+ Row* fCurrRow;
+ int fPrevY;
+ int fWidth;
+ int fMinY;
+
+public:
+ Builder(const SkIRect& bounds) : fBounds(bounds) {
+ fPrevY = -1;
+ fWidth = bounds.width();
+ fCurrRow = NULL;
+ fMinY = bounds.fTop;
+ }
+
+ ~Builder() {
+ Row* row = fRows.begin();
+ Row* stop = fRows.end();
+ while (row < stop) {
+ delete row->fData;
+ row += 1;
+ }
+ }
+
+ const SkIRect& getBounds() const { return fBounds; }
+
+ void addRun(int x, int y, U8CPU alpha, int count) {
+ SkASSERT(count > 0);
+ SkASSERT(fBounds.contains(x, y));
+ SkASSERT(fBounds.contains(x + count - 1, y));
+
+ x -= fBounds.left();
+ y -= fBounds.top();
+
+ Row* row = fCurrRow;
+ if (y != fPrevY) {
+ SkASSERT(y > fPrevY);
+ fPrevY = y;
+ row = this->flushRow(true);
+ row->fY = y;
+ row->fWidth = 0;
+ SkASSERT(row->fData);
+ SkASSERT(0 == row->fData->count());
+ fCurrRow = row;
+ }
+
+ SkASSERT(row->fWidth <= x);
+ SkASSERT(row->fWidth < fBounds.width());
+
+ SkTDArray<uint8_t>& data = *row->fData;
+
+ int gap = x - row->fWidth;
+ if (gap) {
+ AppendRun(data, 0, gap);
+ row->fWidth += gap;
+ SkASSERT(row->fWidth < fBounds.width());
+ }
+
+ AppendRun(data, alpha, count);
+ row->fWidth += count;
+ SkASSERT(row->fWidth <= fBounds.width());
+ }
+
+ void addColumn(int x, int y, U8CPU alpha, int height) {
+ SkASSERT(fBounds.contains(x, y + height - 1));
+
+ this->addRun(x, y, alpha, 1);
+ this->flushRowH(fCurrRow);
+ y -= fBounds.fTop;
+ SkASSERT(y == fCurrRow->fY);
+ fCurrRow->fY = y + height - 1;
+ }
+
+ void addRectRun(int x, int y, int width, int height) {
+ SkASSERT(fBounds.contains(x + width - 1, y + height - 1));
+ this->addRun(x, y, 0xFF, width);
+
+ // we assum the rect must be all we'll see for these scanlines
+ // so we ensure our row goes all the way to our right
+ this->flushRowH(fCurrRow);
+
+ y -= fBounds.fTop;
+ SkASSERT(y == fCurrRow->fY);
+ fCurrRow->fY = y + height - 1;
+ }
+
+ void addAntiRectRun(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) {
+ SkASSERT(fBounds.contains(x + width - 1 +
+ (leftAlpha > 0 ? 1 : 0) + (rightAlpha > 0 ? 1 : 0),
+ y + height - 1));
+ SkASSERT(width >= 0);
+
+ // Conceptually we're always adding 3 runs, but we should
+ // merge or omit them if possible.
+ if (leftAlpha == 0xFF) {
+ width++;
+ } else if (leftAlpha > 0) {
+ this->addRun(x++, y, leftAlpha, 1);
+ }
+ if (rightAlpha == 0xFF) {
+ width++;
+ }
+ if (width > 0) {
+ this->addRun(x, y, 0xFF, width);
+ }
+ if (rightAlpha > 0 && rightAlpha < 255) {
+ this->addRun(x + width, y, rightAlpha, 1);
+ }
+
+ // we assume the rect must be all we'll see for these scanlines
+ // so we ensure our row goes all the way to our right
+ this->flushRowH(fCurrRow);
+
+ y -= fBounds.fTop;
+ SkASSERT(y == fCurrRow->fY);
+ fCurrRow->fY = y + height - 1;
+ }
+
+ bool finish(SkAAClip* target) {
+ this->flushRow(false);
+
+ const Row* row = fRows.begin();
+ const Row* stop = fRows.end();
+
+ size_t dataSize = 0;
+ while (row < stop) {
+ dataSize += row->fData->count();
+ row += 1;
+ }
+
+ if (0 == dataSize) {
+ return target->setEmpty();
+ }
+
+ SkASSERT(fMinY >= fBounds.fTop);
+ SkASSERT(fMinY < fBounds.fBottom);
+ int adjustY = fMinY - fBounds.fTop;
+ fBounds.fTop = fMinY;
+
+ RunHead* head = RunHead::Alloc(fRows.count(), dataSize);
+ YOffset* yoffset = head->yoffsets();
+ uint8_t* data = head->data();
+ uint8_t* baseData = data;
+
+ row = fRows.begin();
+ SkDEBUGCODE(int prevY = row->fY - 1;)
+ while (row < stop) {
+ SkASSERT(prevY < row->fY); // must be monotonic
+ SkDEBUGCODE(prevY = row->fY);
+
+ yoffset->fY = row->fY - adjustY;
+ yoffset->fOffset = data - baseData;
+ yoffset += 1;
+
+ size_t n = row->fData->count();
+ memcpy(data, row->fData->begin(), n);
+#ifdef SK_DEBUG
+ size_t bytesNeeded = compute_row_length(data, fBounds.width());
+ SkASSERT(bytesNeeded == n);
+#endif
+ data += n;
+
+ row += 1;
+ }
+
+ target->freeRuns();
+ target->fBounds = fBounds;
+ target->fRunHead = head;
+ return target->trimBounds();
+ }
+
+ void dump() {
+ this->validate();
+ int y;
+ for (y = 0; y < fRows.count(); ++y) {
+ const Row& row = fRows[y];
+ SkDebugf("Y:%3d W:%3d", row.fY, row.fWidth);
+ const SkTDArray<uint8_t>& data = *row.fData;
+ int count = data.count();
+ SkASSERT(!(count & 1));
+ const uint8_t* ptr = data.begin();
+ for (int x = 0; x < count; x += 2) {
+ SkDebugf(" [%3d:%02X]", ptr[0], ptr[1]);
+ ptr += 2;
+ }
+ SkDebugf("\n");
+ }
+ }
+
+ void validate() {
+#ifdef SK_DEBUG
+ if (false) { // avoid bit rot, suppress warning
+ test_count_left_right_zeros();
+ }
+ int prevY = -1;
+ for (int i = 0; i < fRows.count(); ++i) {
+ const Row& row = fRows[i];
+ SkASSERT(prevY < row.fY);
+ SkASSERT(fWidth == row.fWidth);
+ int count = row.fData->count();
+ const uint8_t* ptr = row.fData->begin();
+ SkASSERT(!(count & 1));
+ int w = 0;
+ for (int x = 0; x < count; x += 2) {
+ int n = ptr[0];
+ SkASSERT(n > 0);
+ w += n;
+ SkASSERT(w <= fWidth);
+ ptr += 2;
+ }
+ SkASSERT(w == fWidth);
+ prevY = row.fY;
+ }
+#endif
+ }
+
+ // only called by BuilderBlitter
+ void setMinY(int y) {
+ fMinY = y;
+ }
+
+private:
+ void flushRowH(Row* row) {
+ // flush current row if needed
+ if (row->fWidth < fWidth) {
+ AppendRun(*row->fData, 0, fWidth - row->fWidth);
+ row->fWidth = fWidth;
+ }
+ }
+
+ Row* flushRow(bool readyForAnother) {
+ Row* next = NULL;
+ int count = fRows.count();
+ if (count > 0) {
+ this->flushRowH(&fRows[count - 1]);
+ }
+ if (count > 1) {
+ // are our last two runs the same?
+ Row* prev = &fRows[count - 2];
+ Row* curr = &fRows[count - 1];
+ SkASSERT(prev->fWidth == fWidth);
+ SkASSERT(curr->fWidth == fWidth);
+ if (*prev->fData == *curr->fData) {
+ prev->fY = curr->fY;
+ if (readyForAnother) {
+ curr->fData->rewind();
+ next = curr;
+ } else {
+ delete curr->fData;
+ fRows.removeShuffle(count - 1);
+ }
+ } else {
+ if (readyForAnother) {
+ next = fRows.append();
+ next->fData = new SkTDArray<uint8_t>;
+ }
+ }
+ } else {
+ if (readyForAnother) {
+ next = fRows.append();
+ next->fData = new SkTDArray<uint8_t>;
+ }
+ }
+ return next;
+ }
+
+ static void AppendRun(SkTDArray<uint8_t>& data, U8CPU alpha, int count) {
+ do {
+ int n = count;
+ if (n > 255) {
+ n = 255;
+ }
+ uint8_t* ptr = data.append(2);
+ ptr[0] = n;
+ ptr[1] = alpha;
+ count -= n;
+ } while (count > 0);
+ }
+};
+
+class SkAAClip::BuilderBlitter : public SkBlitter {
+ int fLastY;
+
+ /*
+ If we see a gap of 1 or more empty scanlines while building in Y-order,
+ we inject an explicit empty scanline (alpha==0)
+
+ See AAClipTest.cpp : test_path_with_hole()
+ */
+ void checkForYGap(int y) {
+ SkASSERT(y >= fLastY);
+ if (fLastY > -SK_MaxS32) {
+ int gap = y - fLastY;
+ if (gap > 1) {
+ fBuilder->addRun(fLeft, y - 1, 0, fRight - fLeft);
+ }
+ }
+ fLastY = y;
+ }
+
+public:
+
+ BuilderBlitter(Builder* builder) {
+ fBuilder = builder;
+ fLeft = builder->getBounds().fLeft;
+ fRight = builder->getBounds().fRight;
+ fMinY = SK_MaxS32;
+ fLastY = -SK_MaxS32; // sentinel
+ }
+
+ void finish() {
+ if (fMinY < SK_MaxS32) {
+ fBuilder->setMinY(fMinY);
+ }
+ }
+
+ /**
+ Must evaluate clips in scan-line order, so don't want to allow blitV(),
+ but an AAClip can be clipped down to a single pixel wide, so we
+ must support it (given AntiRect semantics: minimum width is 2).
+ Instead we'll rely on the runtime asserts to guarantee Y monotonicity;
+ any failure cases that misses may have minor artifacts.
+ */
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) SK_OVERRIDE {
+ this->recordMinY(y);
+ fBuilder->addColumn(x, y, alpha, height);
+ fLastY = y + height - 1;
+ }
+
+ virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE {
+ this->recordMinY(y);
+ this->checkForYGap(y);
+ fBuilder->addRectRun(x, y, width, height);
+ fLastY = y + height - 1;
+ }
+
+ virtual void blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) SK_OVERRIDE {
+ this->recordMinY(y);
+ this->checkForYGap(y);
+ fBuilder->addAntiRectRun(x, y, width, height, leftAlpha, rightAlpha);
+ fLastY = y + height - 1;
+ }
+
+ virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE
+ { unexpected(); }
+
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t*) SK_OVERRIDE {
+ return NULL;
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE {
+ this->recordMinY(y);
+ this->checkForYGap(y);
+ fBuilder->addRun(x, y, 0xFF, width);
+ }
+
+ virtual void blitAntiH(int x, int y, const SkAlpha alpha[],
+ const int16_t runs[]) SK_OVERRIDE {
+ this->recordMinY(y);
+ this->checkForYGap(y);
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ return;
+ }
+
+ // The supersampler's buffer can be the width of the device, so
+ // we may have to trim the run to our bounds. If so, we assert that
+ // the extra spans are always alpha==0
+ int localX = x;
+ int localCount = count;
+ if (x < fLeft) {
+ SkASSERT(0 == *alpha);
+ int gap = fLeft - x;
+ SkASSERT(gap <= count);
+ localX += gap;
+ localCount -= gap;
+ }
+ int right = x + count;
+ if (right > fRight) {
+ SkASSERT(0 == *alpha);
+ localCount -= right - fRight;
+ SkASSERT(localCount >= 0);
+ }
+
+ if (localCount) {
+ fBuilder->addRun(localX, y, *alpha, localCount);
+ }
+ // Next run
+ runs += count;
+ alpha += count;
+ x += count;
+ }
+ }
+
+private:
+ Builder* fBuilder;
+ int fLeft; // cache of builder's bounds' left edge
+ int fRight;
+ int fMinY;
+
+ /*
+ * We track this, in case the scan converter skipped some number of
+ * scanlines at the (relative to the bounds it was given). This allows
+ * the builder, during its finish, to trip its bounds down to the "real"
+ * top.
+ */
+ void recordMinY(int y) {
+ if (y < fMinY) {
+ fMinY = y;
+ }
+ }
+
+ void unexpected() {
+ SkDebugf("---- did not expect to get called here");
+ sk_throw();
+ }
+};
+
+bool SkAAClip::setPath(const SkPath& path, const SkRegion* clip, bool doAA) {
+ AUTO_AACLIP_VALIDATE(*this);
+
+ if (clip && clip->isEmpty()) {
+ return this->setEmpty();
+ }
+
+ SkIRect ibounds;
+ path.getBounds().roundOut(&ibounds);
+
+ SkRegion tmpClip;
+ if (NULL == clip) {
+ tmpClip.setRect(ibounds);
+ clip = &tmpClip;
+ }
+
+ if (path.isInverseFillType()) {
+ ibounds = clip->getBounds();
+ } else {
+ if (ibounds.isEmpty() || !ibounds.intersect(clip->getBounds())) {
+ return this->setEmpty();
+ }
+ }
+
+ Builder builder(ibounds);
+ BuilderBlitter blitter(&builder);
+
+ if (doAA) {
+ SkScan::AntiFillPath(path, *clip, &blitter, true);
+ } else {
+ SkScan::FillPath(path, *clip, &blitter);
+ }
+
+ blitter.finish();
+ return builder.finish(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef void (*RowProc)(SkAAClip::Builder&, int bottom,
+ const uint8_t* rowA, const SkIRect& rectA,
+ const uint8_t* rowB, const SkIRect& rectB);
+
+typedef U8CPU (*AlphaProc)(U8CPU alphaA, U8CPU alphaB);
+
+static U8CPU sectAlphaProc(U8CPU alphaA, U8CPU alphaB) {
+ // Multiply
+ return SkMulDiv255Round(alphaA, alphaB);
+}
+
+static U8CPU unionAlphaProc(U8CPU alphaA, U8CPU alphaB) {
+ // SrcOver
+ return alphaA + alphaB - SkMulDiv255Round(alphaA, alphaB);
+}
+
+static U8CPU diffAlphaProc(U8CPU alphaA, U8CPU alphaB) {
+ // SrcOut
+ return SkMulDiv255Round(alphaA, 0xFF - alphaB);
+}
+
+static U8CPU xorAlphaProc(U8CPU alphaA, U8CPU alphaB) {
+ // XOR
+ return alphaA + alphaB - 2 * SkMulDiv255Round(alphaA, alphaB);
+}
+
+static AlphaProc find_alpha_proc(SkRegion::Op op) {
+ switch (op) {
+ case SkRegion::kIntersect_Op:
+ return sectAlphaProc;
+ case SkRegion::kDifference_Op:
+ return diffAlphaProc;
+ case SkRegion::kUnion_Op:
+ return unionAlphaProc;
+ case SkRegion::kXOR_Op:
+ return xorAlphaProc;
+ default:
+ SkDEBUGFAIL("unexpected region op");
+ return sectAlphaProc;
+ }
+}
+
+static const uint8_t gEmptyRow[] = {
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+ 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0,
+};
+
+class RowIter {
+public:
+ RowIter(const uint8_t* row, const SkIRect& bounds) {
+ fRow = row;
+ fLeft = bounds.fLeft;
+ fBoundsRight = bounds.fRight;
+ if (row) {
+ fRight = bounds.fLeft + row[0];
+ SkASSERT(fRight <= fBoundsRight);
+ fAlpha = row[1];
+ fDone = false;
+ } else {
+ fDone = true;
+ fRight = kMaxInt32;
+ fAlpha = 0;
+ }
+ }
+
+ bool done() const { return fDone; }
+ int left() const { return fLeft; }
+ int right() const { return fRight; }
+ U8CPU alpha() const { return fAlpha; }
+ void next() {
+ if (!fDone) {
+ fLeft = fRight;
+ if (fRight == fBoundsRight) {
+ fDone = true;
+ fRight = kMaxInt32;
+ fAlpha = 0;
+ } else {
+ fRow += 2;
+ fRight += fRow[0];
+ fAlpha = fRow[1];
+ SkASSERT(fRight <= fBoundsRight);
+ }
+ }
+ }
+
+private:
+ const uint8_t* fRow;
+ int fLeft;
+ int fRight;
+ int fBoundsRight;
+ bool fDone;
+ uint8_t fAlpha;
+};
+
+static void adjust_row(RowIter& iter, int& leftA, int& riteA, int rite) {
+ if (rite == riteA) {
+ iter.next();
+ leftA = iter.left();
+ riteA = iter.right();
+ }
+}
+
+#if 0 // UNUSED
+static bool intersect(int& min, int& max, int boundsMin, int boundsMax) {
+ SkASSERT(min < max);
+ SkASSERT(boundsMin < boundsMax);
+ if (min >= boundsMax || max <= boundsMin) {
+ return false;
+ }
+ if (min < boundsMin) {
+ min = boundsMin;
+ }
+ if (max > boundsMax) {
+ max = boundsMax;
+ }
+ return true;
+}
+#endif
+
+static void operatorX(SkAAClip::Builder& builder, int lastY,
+ RowIter& iterA, RowIter& iterB,
+ AlphaProc proc, const SkIRect& bounds) {
+ int leftA = iterA.left();
+ int riteA = iterA.right();
+ int leftB = iterB.left();
+ int riteB = iterB.right();
+
+ int prevRite = bounds.fLeft;
+
+ do {
+ U8CPU alphaA = 0;
+ U8CPU alphaB = 0;
+ int left, rite;
+
+ if (leftA < leftB) {
+ left = leftA;
+ alphaA = iterA.alpha();
+ if (riteA <= leftB) {
+ rite = riteA;
+ } else {
+ rite = leftA = leftB;
+ }
+ } else if (leftB < leftA) {
+ left = leftB;
+ alphaB = iterB.alpha();
+ if (riteB <= leftA) {
+ rite = riteB;
+ } else {
+ rite = leftB = leftA;
+ }
+ } else {
+ left = leftA; // or leftB, since leftA == leftB
+ rite = leftA = leftB = SkMin32(riteA, riteB);
+ alphaA = iterA.alpha();
+ alphaB = iterB.alpha();
+ }
+
+ if (left >= bounds.fRight) {
+ break;
+ }
+ if (rite > bounds.fRight) {
+ rite = bounds.fRight;
+ }
+
+ if (left >= bounds.fLeft) {
+ SkASSERT(rite > left);
+ builder.addRun(left, lastY, proc(alphaA, alphaB), rite - left);
+ prevRite = rite;
+ }
+
+ adjust_row(iterA, leftA, riteA, rite);
+ adjust_row(iterB, leftB, riteB, rite);
+ } while (!iterA.done() || !iterB.done());
+
+ if (prevRite < bounds.fRight) {
+ builder.addRun(prevRite, lastY, 0, bounds.fRight - prevRite);
+ }
+}
+
+static void adjust_iter(SkAAClip::Iter& iter, int& topA, int& botA, int bot) {
+ if (bot == botA) {
+ iter.next();
+ topA = botA;
+ SkASSERT(botA == iter.top());
+ botA = iter.bottom();
+ }
+}
+
+static void operateY(SkAAClip::Builder& builder, const SkAAClip& A,
+ const SkAAClip& B, SkRegion::Op op) {
+ AlphaProc proc = find_alpha_proc(op);
+ const SkIRect& bounds = builder.getBounds();
+
+ SkAAClip::Iter iterA(A);
+ SkAAClip::Iter iterB(B);
+
+ SkASSERT(!iterA.done());
+ int topA = iterA.top();
+ int botA = iterA.bottom();
+ SkASSERT(!iterB.done());
+ int topB = iterB.top();
+ int botB = iterB.bottom();
+
+ do {
+ const uint8_t* rowA = NULL;
+ const uint8_t* rowB = NULL;
+ int top, bot;
+
+ if (topA < topB) {
+ top = topA;
+ rowA = iterA.data();
+ if (botA <= topB) {
+ bot = botA;
+ } else {
+ bot = topA = topB;
+ }
+
+ } else if (topB < topA) {
+ top = topB;
+ rowB = iterB.data();
+ if (botB <= topA) {
+ bot = botB;
+ } else {
+ bot = topB = topA;
+ }
+ } else {
+ top = topA; // or topB, since topA == topB
+ bot = topA = topB = SkMin32(botA, botB);
+ rowA = iterA.data();
+ rowB = iterB.data();
+ }
+
+ if (top >= bounds.fBottom) {
+ break;
+ }
+
+ if (bot > bounds.fBottom) {
+ bot = bounds.fBottom;
+ }
+ SkASSERT(top < bot);
+
+ if (!rowA && !rowB) {
+ builder.addRun(bounds.fLeft, bot - 1, 0, bounds.width());
+ } else if (top >= bounds.fTop) {
+ SkASSERT(bot <= bounds.fBottom);
+ RowIter rowIterA(rowA, rowA ? A.getBounds() : bounds);
+ RowIter rowIterB(rowB, rowB ? B.getBounds() : bounds);
+ operatorX(builder, bot - 1, rowIterA, rowIterB, proc, bounds);
+ }
+
+ adjust_iter(iterA, topA, botA, bot);
+ adjust_iter(iterB, topB, botB, bot);
+ } while (!iterA.done() || !iterB.done());
+}
+
+bool SkAAClip::op(const SkAAClip& clipAOrig, const SkAAClip& clipBOrig,
+ SkRegion::Op op) {
+ AUTO_AACLIP_VALIDATE(*this);
+
+ if (SkRegion::kReplace_Op == op) {
+ return this->set(clipBOrig);
+ }
+
+ const SkAAClip* clipA = &clipAOrig;
+ const SkAAClip* clipB = &clipBOrig;
+
+ if (SkRegion::kReverseDifference_Op == op) {
+ SkTSwap(clipA, clipB);
+ op = SkRegion::kDifference_Op;
+ }
+
+ bool a_empty = clipA->isEmpty();
+ bool b_empty = clipB->isEmpty();
+
+ SkIRect bounds;
+ switch (op) {
+ case SkRegion::kDifference_Op:
+ if (a_empty) {
+ return this->setEmpty();
+ }
+ if (b_empty || !SkIRect::Intersects(clipA->fBounds, clipB->fBounds)) {
+ return this->set(*clipA);
+ }
+ bounds = clipA->fBounds;
+ break;
+
+ case SkRegion::kIntersect_Op:
+ if ((a_empty | b_empty) || !bounds.intersect(clipA->fBounds,
+ clipB->fBounds)) {
+ return this->setEmpty();
+ }
+ break;
+
+ case SkRegion::kUnion_Op:
+ case SkRegion::kXOR_Op:
+ if (a_empty) {
+ return this->set(*clipB);
+ }
+ if (b_empty) {
+ return this->set(*clipA);
+ }
+ bounds = clipA->fBounds;
+ bounds.join(clipB->fBounds);
+ break;
+
+ default:
+ SkDEBUGFAIL("unknown region op");
+ return !this->isEmpty();
+ }
+
+ SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds));
+ SkASSERT(SkIRect::Intersects(bounds, clipB->fBounds));
+
+ Builder builder(bounds);
+ operateY(builder, *clipA, *clipB, op);
+
+ return builder.finish(this);
+}
+
+/*
+ * It can be expensive to build a local aaclip before applying the op, so
+ * we first see if we can restrict the bounds of new rect to our current
+ * bounds, or note that the new rect subsumes our current clip.
+ */
+
+bool SkAAClip::op(const SkIRect& rOrig, SkRegion::Op op) {
+ SkIRect rStorage;
+ const SkIRect* r = &rOrig;
+
+ switch (op) {
+ case SkRegion::kIntersect_Op:
+ if (!rStorage.intersect(rOrig, fBounds)) {
+ // no overlap, so we're empty
+ return this->setEmpty();
+ }
+ if (rStorage == fBounds) {
+ // we were wholly inside the rect, no change
+ return !this->isEmpty();
+ }
+ if (this->quickContains(rStorage)) {
+ // the intersection is wholly inside us, we're a rect
+ return this->setRect(rStorage);
+ }
+ r = &rStorage; // use the intersected bounds
+ break;
+ case SkRegion::kDifference_Op:
+ break;
+ case SkRegion::kUnion_Op:
+ if (rOrig.contains(fBounds)) {
+ return this->setRect(rOrig);
+ }
+ break;
+ default:
+ break;
+ }
+
+ SkAAClip clip;
+ clip.setRect(*r);
+ return this->op(*this, clip, op);
+}
+
+bool SkAAClip::op(const SkRect& rOrig, SkRegion::Op op, bool doAA) {
+ SkRect rStorage, boundsStorage;
+ const SkRect* r = &rOrig;
+
+ boundsStorage.set(fBounds);
+ switch (op) {
+ case SkRegion::kIntersect_Op:
+ case SkRegion::kDifference_Op:
+ if (!rStorage.intersect(rOrig, boundsStorage)) {
+ if (SkRegion::kIntersect_Op == op) {
+ return this->setEmpty();
+ } else { // kDifference
+ return !this->isEmpty();
+ }
+ }
+ r = &rStorage; // use the intersected bounds
+ break;
+ case SkRegion::kUnion_Op:
+ if (rOrig.contains(boundsStorage)) {
+ return this->setRect(rOrig);
+ }
+ break;
+ default:
+ break;
+ }
+
+ SkAAClip clip;
+ clip.setRect(*r, doAA);
+ return this->op(*this, clip, op);
+}
+
+bool SkAAClip::op(const SkAAClip& clip, SkRegion::Op op) {
+ return this->op(*this, clip, op);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkAAClip::translate(int dx, int dy, SkAAClip* dst) const {
+ if (NULL == dst) {
+ return !this->isEmpty();
+ }
+
+ if (this->isEmpty()) {
+ return dst->setEmpty();
+ }
+
+ if (this != dst) {
+ sk_atomic_inc(&fRunHead->fRefCnt);
+ dst->freeRuns();
+ dst->fRunHead = fRunHead;
+ dst->fBounds = fBounds;
+ }
+ dst->fBounds.offset(dx, dy);
+ return true;
+}
+
+static void expand_row_to_mask(uint8_t* SK_RESTRICT mask,
+ const uint8_t* SK_RESTRICT row,
+ int width) {
+ while (width > 0) {
+ int n = row[0];
+ SkASSERT(width >= n);
+ memset(mask, row[1], n);
+ mask += n;
+ row += 2;
+ width -= n;
+ }
+ SkASSERT(0 == width);
+}
+
+void SkAAClip::copyToMask(SkMask* mask) const {
+ mask->fFormat = SkMask::kA8_Format;
+ if (this->isEmpty()) {
+ mask->fBounds.setEmpty();
+ mask->fImage = NULL;
+ mask->fRowBytes = 0;
+ return;
+ }
+
+ mask->fBounds = fBounds;
+ mask->fRowBytes = fBounds.width();
+ size_t size = mask->computeImageSize();
+ mask->fImage = SkMask::AllocImage(size);
+
+ Iter iter(*this);
+ uint8_t* dst = mask->fImage;
+ const int width = fBounds.width();
+
+ int y = fBounds.fTop;
+ while (!iter.done()) {
+ do {
+ expand_row_to_mask(dst, iter.data(), width);
+ dst += mask->fRowBytes;
+ } while (++y < iter.bottom());
+ iter.next();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void expandToRuns(const uint8_t* SK_RESTRICT data, int initialCount, int width,
+ int16_t* SK_RESTRICT runs, SkAlpha* SK_RESTRICT aa) {
+ // we don't read our initial n from data, since the caller may have had to
+ // clip it, hence the initialCount parameter.
+ int n = initialCount;
+ for (;;) {
+ if (n > width) {
+ n = width;
+ }
+ SkASSERT(n > 0);
+ runs[0] = n;
+ runs += n;
+
+ aa[0] = data[1];
+ aa += n;
+
+ data += 2;
+ width -= n;
+ if (0 == width) {
+ break;
+ }
+ // load the next count
+ n = data[0];
+ }
+ runs[0] = 0; // sentinel
+}
+
+SkAAClipBlitter::~SkAAClipBlitter() {
+ sk_free(fScanlineScratch);
+}
+
+void SkAAClipBlitter::ensureRunsAndAA() {
+ if (NULL == fScanlineScratch) {
+ // add 1 so we can store the terminating run count of 0
+ int count = fAAClipBounds.width() + 1;
+ // we use this either for fRuns + fAA, or a scaline of a mask
+ // which may be as deep as 32bits
+ fScanlineScratch = sk_malloc_throw(count * sizeof(SkPMColor));
+ fRuns = (int16_t*)fScanlineScratch;
+ fAA = (SkAlpha*)(fRuns + count);
+ }
+}
+
+void SkAAClipBlitter::blitH(int x, int y, int width) {
+ SkASSERT(width > 0);
+ SkASSERT(fAAClipBounds.contains(x, y));
+ SkASSERT(fAAClipBounds.contains(x + width - 1, y));
+
+ const uint8_t* row = fAAClip->findRow(y);
+ int initialCount;
+ row = fAAClip->findX(row, x, &initialCount);
+
+ if (initialCount >= width) {
+ SkAlpha alpha = row[1];
+ if (0 == alpha) {
+ return;
+ }
+ if (0xFF == alpha) {
+ fBlitter->blitH(x, y, width);
+ return;
+ }
+ }
+
+ this->ensureRunsAndAA();
+ expandToRuns(row, initialCount, width, fRuns, fAA);
+
+ fBlitter->blitAntiH(x, y, fAA, fRuns);
+}
+
+static void merge(const uint8_t* SK_RESTRICT row, int rowN,
+ const SkAlpha* SK_RESTRICT srcAA,
+ const int16_t* SK_RESTRICT srcRuns,
+ SkAlpha* SK_RESTRICT dstAA,
+ int16_t* SK_RESTRICT dstRuns,
+ int width) {
+ SkDEBUGCODE(int accumulated = 0;)
+ int srcN = srcRuns[0];
+ // do we need this check?
+ if (0 == srcN) {
+ return;
+ }
+
+ for (;;) {
+ SkASSERT(rowN > 0);
+ SkASSERT(srcN > 0);
+
+ unsigned newAlpha = SkMulDiv255Round(srcAA[0], row[1]);
+ int minN = SkMin32(srcN, rowN);
+ dstRuns[0] = minN;
+ dstRuns += minN;
+ dstAA[0] = newAlpha;
+ dstAA += minN;
+
+ if (0 == (srcN -= minN)) {
+ srcN = srcRuns[0]; // refresh
+ srcRuns += srcN;
+ srcAA += srcN;
+ srcN = srcRuns[0]; // reload
+ if (0 == srcN) {
+ break;
+ }
+ }
+ if (0 == (rowN -= minN)) {
+ row += 2;
+ rowN = row[0]; // reload
+ }
+
+ SkDEBUGCODE(accumulated += minN;)
+ SkASSERT(accumulated <= width);
+ }
+ dstRuns[0] = 0;
+}
+
+void SkAAClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[],
+ const int16_t runs[]) {
+
+ const uint8_t* row = fAAClip->findRow(y);
+ int initialCount;
+ row = fAAClip->findX(row, x, &initialCount);
+
+ this->ensureRunsAndAA();
+
+ merge(row, initialCount, aa, runs, fAA, fRuns, fAAClipBounds.width());
+ fBlitter->blitAntiH(x, y, fAA, fRuns);
+}
+
+void SkAAClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ if (fAAClip->quickContains(x, y, x + 1, y + height)) {
+ fBlitter->blitV(x, y, height, alpha);
+ return;
+ }
+
+ for (;;) {
+ int lastY SK_INIT_TO_AVOID_WARNING;
+ const uint8_t* row = fAAClip->findRow(y, &lastY);
+ int dy = lastY - y + 1;
+ if (dy > height) {
+ dy = height;
+ }
+ height -= dy;
+
+ row = fAAClip->findX(row, x);
+ SkAlpha newAlpha = SkMulDiv255Round(alpha, row[1]);
+ if (newAlpha) {
+ fBlitter->blitV(x, y, dy, newAlpha);
+ }
+ SkASSERT(height >= 0);
+ if (height <= 0) {
+ break;
+ }
+ y = lastY + 1;
+ }
+}
+
+void SkAAClipBlitter::blitRect(int x, int y, int width, int height) {
+ if (fAAClip->quickContains(x, y, x + width, y + height)) {
+ fBlitter->blitRect(x, y, width, height);
+ return;
+ }
+
+ while (--height >= 0) {
+ this->blitH(x, y, width);
+ y += 1;
+ }
+}
+
+typedef void (*MergeAAProc)(const void* src, int width, const uint8_t* row,
+ int initialRowCount, void* dst);
+
+static void small_memcpy(void* dst, const void* src, size_t n) {
+ memcpy(dst, src, n);
+}
+
+static void small_bzero(void* dst, size_t n) {
+ sk_bzero(dst, n);
+}
+
+static inline uint8_t mergeOne(uint8_t value, unsigned alpha) {
+ return SkMulDiv255Round(value, alpha);
+}
+static inline uint16_t mergeOne(uint16_t value, unsigned alpha) {
+ unsigned r = SkGetPackedR16(value);
+ unsigned g = SkGetPackedG16(value);
+ unsigned b = SkGetPackedB16(value);
+ return SkPackRGB16(SkMulDiv255Round(r, alpha),
+ SkMulDiv255Round(g, alpha),
+ SkMulDiv255Round(b, alpha));
+}
+static inline SkPMColor mergeOne(SkPMColor value, unsigned alpha) {
+ unsigned a = SkGetPackedA32(value);
+ unsigned r = SkGetPackedR32(value);
+ unsigned g = SkGetPackedG32(value);
+ unsigned b = SkGetPackedB32(value);
+ return SkPackARGB32(SkMulDiv255Round(a, alpha),
+ SkMulDiv255Round(r, alpha),
+ SkMulDiv255Round(g, alpha),
+ SkMulDiv255Round(b, alpha));
+}
+
+template <typename T> void mergeT(const T* SK_RESTRICT src, int srcN,
+ const uint8_t* SK_RESTRICT row, int rowN,
+ T* SK_RESTRICT dst) {
+ for (;;) {
+ SkASSERT(rowN > 0);
+ SkASSERT(srcN > 0);
+
+ int n = SkMin32(rowN, srcN);
+ unsigned rowA = row[1];
+ if (0xFF == rowA) {
+ small_memcpy(dst, src, n * sizeof(T));
+ } else if (0 == rowA) {
+ small_bzero(dst, n * sizeof(T));
+ } else {
+ for (int i = 0; i < n; ++i) {
+ dst[i] = mergeOne(src[i], rowA);
+ }
+ }
+
+ if (0 == (srcN -= n)) {
+ break;
+ }
+
+ src += n;
+ dst += n;
+
+ SkASSERT(rowN == n);
+ row += 2;
+ rowN = row[0];
+ }
+}
+
+static MergeAAProc find_merge_aa_proc(SkMask::Format format) {
+ switch (format) {
+ case SkMask::kBW_Format:
+ SkDEBUGFAIL("unsupported");
+ return NULL;
+ case SkMask::kA8_Format:
+ case SkMask::k3D_Format: {
+ void (*proc8)(const uint8_t*, int, const uint8_t*, int, uint8_t*) = mergeT;
+ return (MergeAAProc)proc8;
+ }
+ case SkMask::kLCD16_Format: {
+ void (*proc16)(const uint16_t*, int, const uint8_t*, int, uint16_t*) = mergeT;
+ return (MergeAAProc)proc16;
+ }
+ case SkMask::kLCD32_Format: {
+ void (*proc32)(const SkPMColor*, int, const uint8_t*, int, SkPMColor*) = mergeT;
+ return (MergeAAProc)proc32;
+ }
+ default:
+ SkDEBUGFAIL("unsupported");
+ return NULL;
+ }
+}
+
+static U8CPU bit2byte(int bitInAByte) {
+ SkASSERT(bitInAByte <= 0xFF);
+ // negation turns any non-zero into 0xFFFFFF??, so we just shift down
+ // some value >= 8 to get a full FF value
+ return -bitInAByte >> 8;
+}
+
+static void upscaleBW2A8(SkMask* dstMask, const SkMask& srcMask) {
+ SkASSERT(SkMask::kBW_Format == srcMask.fFormat);
+ SkASSERT(SkMask::kA8_Format == dstMask->fFormat);
+
+ const int width = srcMask.fBounds.width();
+ const int height = srcMask.fBounds.height();
+
+ const uint8_t* SK_RESTRICT src = (const uint8_t*)srcMask.fImage;
+ const size_t srcRB = srcMask.fRowBytes;
+ uint8_t* SK_RESTRICT dst = (uint8_t*)dstMask->fImage;
+ const size_t dstRB = dstMask->fRowBytes;
+
+ const int wholeBytes = width >> 3;
+ const int leftOverBits = width & 7;
+
+ for (int y = 0; y < height; ++y) {
+ uint8_t* SK_RESTRICT d = dst;
+ for (int i = 0; i < wholeBytes; ++i) {
+ int srcByte = src[i];
+ d[0] = bit2byte(srcByte & (1 << 7));
+ d[1] = bit2byte(srcByte & (1 << 6));
+ d[2] = bit2byte(srcByte & (1 << 5));
+ d[3] = bit2byte(srcByte & (1 << 4));
+ d[4] = bit2byte(srcByte & (1 << 3));
+ d[5] = bit2byte(srcByte & (1 << 2));
+ d[6] = bit2byte(srcByte & (1 << 1));
+ d[7] = bit2byte(srcByte & (1 << 0));
+ d += 8;
+ }
+ if (leftOverBits) {
+ int srcByte = src[wholeBytes];
+ for (int x = 0; x < leftOverBits; ++x) {
+ *d++ = bit2byte(srcByte & 0x80);
+ srcByte <<= 1;
+ }
+ }
+ src += srcRB;
+ dst += dstRB;
+ }
+}
+
+void SkAAClipBlitter::blitMask(const SkMask& origMask, const SkIRect& clip) {
+ SkASSERT(fAAClip->getBounds().contains(clip));
+
+ if (fAAClip->quickContains(clip)) {
+ fBlitter->blitMask(origMask, clip);
+ return;
+ }
+
+ const SkMask* mask = &origMask;
+
+ // if we're BW, we need to upscale to A8 (ugh)
+ SkMask grayMask;
+ grayMask.fImage = NULL;
+ if (SkMask::kBW_Format == origMask.fFormat) {
+ grayMask.fFormat = SkMask::kA8_Format;
+ grayMask.fBounds = origMask.fBounds;
+ grayMask.fRowBytes = origMask.fBounds.width();
+ size_t size = grayMask.computeImageSize();
+ grayMask.fImage = (uint8_t*)fGrayMaskScratch.reset(size,
+ SkAutoMalloc::kReuse_OnShrink);
+
+ upscaleBW2A8(&grayMask, origMask);
+ mask = &grayMask;
+ }
+
+ this->ensureRunsAndAA();
+
+ // HACK -- we are devolving 3D into A8, need to copy the rest of the 3D
+ // data into a temp block to support it better (ugh)
+
+ const void* src = mask->getAddr(clip.fLeft, clip.fTop);
+ const size_t srcRB = mask->fRowBytes;
+ const int width = clip.width();
+ MergeAAProc mergeProc = find_merge_aa_proc(mask->fFormat);
+
+ SkMask rowMask;
+ rowMask.fFormat = SkMask::k3D_Format == mask->fFormat ? SkMask::kA8_Format : mask->fFormat;
+ rowMask.fBounds.fLeft = clip.fLeft;
+ rowMask.fBounds.fRight = clip.fRight;
+ rowMask.fRowBytes = mask->fRowBytes; // doesn't matter, since our height==1
+ rowMask.fImage = (uint8_t*)fScanlineScratch;
+
+ int y = clip.fTop;
+ const int stopY = y + clip.height();
+
+ do {
+ int localStopY SK_INIT_TO_AVOID_WARNING;
+ const uint8_t* row = fAAClip->findRow(y, &localStopY);
+ // findRow returns last Y, not stop, so we add 1
+ localStopY = SkMin32(localStopY + 1, stopY);
+
+ int initialCount;
+ row = fAAClip->findX(row, clip.fLeft, &initialCount);
+ do {
+ mergeProc(src, width, row, initialCount, rowMask.fImage);
+ rowMask.fBounds.fTop = y;
+ rowMask.fBounds.fBottom = y + 1;
+ fBlitter->blitMask(rowMask, rowMask.fBounds);
+ src = (const void*)((const char*)src + srcRB);
+ } while (++y < localStopY);
+ } while (y < stopY);
+}
+
+const SkBitmap* SkAAClipBlitter::justAnOpaqueColor(uint32_t* value) {
+ return NULL;
+}
diff --git a/core/SkAAClip.h b/core/SkAAClip.h
new file mode 100644
index 00000000..f2cde62d
--- /dev/null
+++ b/core/SkAAClip.h
@@ -0,0 +1,132 @@
+
+/*
+ * 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 SkAAClip_DEFINED
+#define SkAAClip_DEFINED
+
+#include "SkBlitter.h"
+#include "SkRegion.h"
+
+class SkAAClip {
+public:
+ SkAAClip();
+ SkAAClip(const SkAAClip&);
+ ~SkAAClip();
+
+ SkAAClip& operator=(const SkAAClip&);
+ friend bool operator==(const SkAAClip&, const SkAAClip&);
+ friend bool operator!=(const SkAAClip& a, const SkAAClip& b) {
+ return !(a == b);
+ }
+
+ void swap(SkAAClip&);
+
+ bool isEmpty() const { return NULL == fRunHead; }
+ const SkIRect& getBounds() const { return fBounds; }
+
+ bool setEmpty();
+ bool setRect(const SkIRect&);
+ bool setRect(const SkRect&, bool doAA = true);
+ bool setPath(const SkPath&, const SkRegion* clip = NULL, bool doAA = true);
+ bool setRegion(const SkRegion&);
+ bool set(const SkAAClip&);
+
+ bool op(const SkAAClip&, const SkAAClip&, SkRegion::Op);
+
+ // Helpers for op()
+ bool op(const SkIRect&, SkRegion::Op);
+ bool op(const SkRect&, SkRegion::Op, bool doAA);
+ bool op(const SkAAClip&, SkRegion::Op);
+
+ bool translate(int dx, int dy, SkAAClip* dst) const;
+ bool translate(int dx, int dy) {
+ return this->translate(dx, dy, this);
+ }
+
+ /**
+ * Allocates a mask the size of the aaclip, and expands its data into
+ * the mask, using kA8_Format
+ */
+ void copyToMask(SkMask*) const;
+
+ // called internally
+
+ bool quickContains(int left, int top, int right, int bottom) const;
+ bool quickContains(const SkIRect& r) const {
+ return this->quickContains(r.fLeft, r.fTop, r.fRight, r.fBottom);
+ }
+
+ const uint8_t* findRow(int y, int* lastYForRow = NULL) const;
+ const uint8_t* findX(const uint8_t data[], int x, int* initialCount = NULL) const;
+
+ class Iter;
+ struct RunHead;
+ struct YOffset;
+ class Builder;
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+private:
+ SkIRect fBounds;
+ RunHead* fRunHead;
+
+ void freeRuns();
+ bool trimBounds();
+ bool trimTopBottom();
+ bool trimLeftRight();
+
+ friend class Builder;
+ class BuilderBlitter;
+ friend class BuilderBlitter;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkAAClipBlitter : public SkBlitter {
+public:
+ SkAAClipBlitter() : fScanlineScratch(NULL) {}
+ virtual ~SkAAClipBlitter();
+
+ void init(SkBlitter* blitter, const SkAAClip* aaclip) {
+ SkASSERT(aaclip && !aaclip->isEmpty());
+ fBlitter = blitter;
+ fAAClip = aaclip;
+ fAAClipBounds = aaclip->getBounds();
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitAntiH(int x, int y, const SkAlpha[],
+ const int16_t runs[]) 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 blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE;
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) SK_OVERRIDE;
+
+private:
+ SkBlitter* fBlitter;
+ const SkAAClip* fAAClip;
+ SkIRect fAAClipBounds;
+
+ // point into fScanlineScratch
+ int16_t* fRuns;
+ SkAlpha* fAA;
+
+ enum {
+ kSize = 32 * 32
+ };
+ SkAutoSMalloc<kSize> fGrayMaskScratch; // used for blitMask
+ void* fScanlineScratch; // enough for a mask at 32bit, or runs+aa
+
+ void ensureRunsAndAA();
+};
+
+#endif
diff --git a/core/SkAdvancedTypefaceMetrics.cpp b/core/SkAdvancedTypefaceMetrics.cpp
new file mode 100644
index 00000000..defe68c8
--- /dev/null
+++ b/core/SkAdvancedTypefaceMetrics.cpp
@@ -0,0 +1,317 @@
+
+/*
+ * 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 "SkAdvancedTypefaceMetrics.h"
+#include "SkTypes.h"
+
+SK_DEFINE_INST_COUNT(SkAdvancedTypefaceMetrics)
+
+#if defined(SK_BUILD_FOR_WIN)
+#include <dwrite.h>
+#endif
+
+#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID)
+// forward declare structs needed for getAdvanceData() template for freetype
+struct FT_FaceRec;
+typedef struct FT_FaceRec_* FT_Face;
+#endif
+
+#ifdef SK_BUILD_FOR_MAC
+#import <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef SK_BUILD_FOR_IOS
+#include <CoreText/CoreText.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+namespace skia_advanced_typeface_metrics_utils {
+
+const int16_t kInvalidAdvance = SK_MinS16;
+const int16_t kDontCareAdvance = SK_MinS16 + 1;
+
+template <typename Data>
+void stripUninterestingTrailingAdvancesFromRange(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* range) {
+ SkASSERT(false);
+}
+
+template <>
+void stripUninterestingTrailingAdvancesFromRange<int16_t>(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<int16_t>* range) {
+ SkASSERT(range);
+
+ int expectedAdvanceCount = range->fEndId - range->fStartId + 1;
+ if (range->fAdvance.count() < expectedAdvanceCount) {
+ return;
+ }
+
+ for (int i = expectedAdvanceCount - 1; i >= 0; --i) {
+ if (range->fAdvance[i] != kDontCareAdvance &&
+ range->fAdvance[i] != kInvalidAdvance &&
+ range->fAdvance[i] != 0) {
+ range->fEndId = range->fStartId + i;
+ break;
+ }
+ }
+}
+
+template <typename Data>
+void resetRange(SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* range,
+ int startId) {
+ range->fStartId = startId;
+ range->fAdvance.setCount(0);
+}
+
+template <typename Data>
+SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* appendRange(
+ SkTScopedPtr<SkAdvancedTypefaceMetrics::AdvanceMetric<Data> >* nextSlot,
+ int startId) {
+ nextSlot->reset(new SkAdvancedTypefaceMetrics::AdvanceMetric<Data>);
+ resetRange(nextSlot->get(), startId);
+ return nextSlot->get();
+}
+
+template <typename Data>
+void zeroWildcardsInRange(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* range) {
+ SkASSERT(false);
+}
+
+template <>
+void zeroWildcardsInRange<int16_t>(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<int16_t>* range) {
+ SkASSERT(range);
+ if (range->fType != SkAdvancedTypefaceMetrics::WidthRange::kRange) {
+ return;
+ }
+ SkASSERT(range->fAdvance.count() == range->fEndId - range->fStartId + 1);
+
+ // Zero out wildcards.
+ for (int i = 0; i < range->fAdvance.count(); ++i) {
+ if (range->fAdvance[i] == kDontCareAdvance) {
+ range->fAdvance[i] = 0;
+ }
+ }
+}
+
+template <typename Data>
+void finishRange(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* range,
+ int endId,
+ typename SkAdvancedTypefaceMetrics::AdvanceMetric<Data>::MetricType
+ type) {
+ range->fEndId = endId;
+ range->fType = type;
+ stripUninterestingTrailingAdvancesFromRange(range);
+ int newLength;
+ if (type == SkAdvancedTypefaceMetrics::AdvanceMetric<Data>::kRange) {
+ newLength = range->fEndId - range->fStartId + 1;
+ } else {
+ if (range->fEndId == range->fStartId) {
+ range->fType =
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>::kRange;
+ }
+ newLength = 1;
+ }
+ SkASSERT(range->fAdvance.count() >= newLength);
+ range->fAdvance.setCount(newLength);
+ zeroWildcardsInRange(range);
+}
+
+template <typename Data, typename FontHandle>
+SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* getAdvanceData(
+ FontHandle fontHandle,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(FontHandle fontHandle, int gId, Data* data)) {
+ // Assuming that on average, the ASCII representation of an advance plus
+ // a space is 8 characters and the ASCII representation of a glyph id is 3
+ // characters, then the following cut offs for using different range types
+ // apply:
+ // The cost of stopping and starting the range is 7 characers
+ // a. Removing 4 0's or don't care's is a win
+ // The cost of stopping and starting the range plus a run is 22
+ // characters
+ // b. Removing 3 repeating advances is a win
+ // c. Removing 2 repeating advances and 3 don't cares is a win
+ // When not currently in a range the cost of a run over a range is 16
+ // characaters, so:
+ // d. Removing a leading 0/don't cares is a win because it is omitted
+ // e. Removing 2 repeating advances is a win
+
+ SkTScopedPtr<SkAdvancedTypefaceMetrics::AdvanceMetric<Data> > result;
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* curRange;
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* prevRange = NULL;
+ Data lastAdvance = kInvalidAdvance;
+ int repeatedAdvances = 0;
+ int wildCardsInRun = 0;
+ int trailingWildCards = 0;
+ uint32_t subsetIndex = 0;
+
+ // Limit the loop count to glyph id ranges provided.
+ int firstIndex = 0;
+ int lastIndex = num_glyphs;
+ if (subsetGlyphIDs) {
+ firstIndex = static_cast<int>(subsetGlyphIDs[0]);
+ lastIndex =
+ static_cast<int>(subsetGlyphIDs[subsetGlyphIDsLength - 1]) + 1;
+ }
+ curRange = appendRange(&result, firstIndex);
+
+ for (int gId = firstIndex; gId <= lastIndex; gId++) {
+ Data advance = kInvalidAdvance;
+ if (gId < lastIndex) {
+ // Get glyph id only when subset is NULL, or the id is in subset.
+ if (!subsetGlyphIDs ||
+ (subsetIndex < subsetGlyphIDsLength &&
+ static_cast<uint32_t>(gId) == subsetGlyphIDs[subsetIndex])) {
+ SkAssertResult(getAdvance(fontHandle, gId, &advance));
+ ++subsetIndex;
+ } else {
+ advance = kDontCareAdvance;
+ }
+ }
+ if (advance == lastAdvance) {
+ repeatedAdvances++;
+ trailingWildCards = 0;
+ } else if (advance == kDontCareAdvance) {
+ wildCardsInRun++;
+ trailingWildCards++;
+ } else if (curRange->fAdvance.count() ==
+ repeatedAdvances + 1 + wildCardsInRun) { // All in run.
+ if (lastAdvance == 0) {
+ resetRange(curRange, gId);
+ trailingWildCards = 0;
+ } else if (repeatedAdvances + 1 >= 2 || trailingWildCards >= 4) {
+ finishRange(curRange, gId - 1,
+ SkAdvancedTypefaceMetrics::WidthRange::kRun);
+ prevRange = curRange;
+ curRange = appendRange(&curRange->fNext, gId);
+ trailingWildCards = 0;
+ }
+ repeatedAdvances = 0;
+ wildCardsInRun = trailingWildCards;
+ trailingWildCards = 0;
+ } else {
+ if (lastAdvance == 0 &&
+ repeatedAdvances + 1 + wildCardsInRun >= 4) {
+ finishRange(curRange,
+ gId - repeatedAdvances - wildCardsInRun - 2,
+ SkAdvancedTypefaceMetrics::WidthRange::kRange);
+ prevRange = curRange;
+ curRange = appendRange(&curRange->fNext, gId);
+ trailingWildCards = 0;
+ } else if (trailingWildCards >= 4 && repeatedAdvances + 1 < 2) {
+ finishRange(curRange,
+ gId - trailingWildCards - 1,
+ SkAdvancedTypefaceMetrics::WidthRange::kRange);
+ prevRange = curRange;
+ curRange = appendRange(&curRange->fNext, gId);
+ trailingWildCards = 0;
+ } else if (lastAdvance != 0 &&
+ (repeatedAdvances + 1 >= 3 ||
+ (repeatedAdvances + 1 >= 2 && wildCardsInRun >= 3))) {
+ finishRange(curRange,
+ gId - repeatedAdvances - wildCardsInRun - 2,
+ SkAdvancedTypefaceMetrics::WidthRange::kRange);
+ curRange =
+ appendRange(&curRange->fNext,
+ gId - repeatedAdvances - wildCardsInRun - 1);
+ curRange->fAdvance.append(1, &lastAdvance);
+ finishRange(curRange, gId - 1,
+ SkAdvancedTypefaceMetrics::WidthRange::kRun);
+ prevRange = curRange;
+ curRange = appendRange(&curRange->fNext, gId);
+ trailingWildCards = 0;
+ }
+ repeatedAdvances = 0;
+ wildCardsInRun = trailingWildCards;
+ trailingWildCards = 0;
+ }
+ curRange->fAdvance.append(1, &advance);
+ if (advance != kDontCareAdvance) {
+ lastAdvance = advance;
+ }
+ }
+ if (curRange->fStartId == lastIndex) {
+ SkASSERT(prevRange);
+ SkASSERT(prevRange->fNext->fStartId == lastIndex);
+ prevRange->fNext.reset();
+ } else {
+ finishRange(curRange, lastIndex - 1,
+ SkAdvancedTypefaceMetrics::WidthRange::kRange);
+ }
+ return result.release();
+}
+
+// Make AdvanceMetric template functions available for linking with typename
+// WidthRange and VerticalAdvanceRange.
+#if defined(SK_BUILD_FOR_WIN)
+template SkAdvancedTypefaceMetrics::WidthRange* getAdvanceData(
+ HDC hdc,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(HDC hdc, int gId, int16_t* data));
+template SkAdvancedTypefaceMetrics::WidthRange* getAdvanceData(
+ IDWriteFontFace* fontFace,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(IDWriteFontFace* fontFace, int gId, int16_t* data));
+#elif defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID)
+template SkAdvancedTypefaceMetrics::WidthRange* getAdvanceData(
+ FT_Face face,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(FT_Face face, int gId, int16_t* data));
+#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
+template SkAdvancedTypefaceMetrics::WidthRange* getAdvanceData(
+ CTFontRef ctFont,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(CTFontRef ctFont, int gId, int16_t* data));
+#endif
+template void resetRange(
+ SkAdvancedTypefaceMetrics::WidthRange* range,
+ int startId);
+template SkAdvancedTypefaceMetrics::WidthRange* appendRange(
+ SkTScopedPtr<SkAdvancedTypefaceMetrics::WidthRange >* nextSlot,
+ int startId);
+template void finishRange<int16_t>(
+ SkAdvancedTypefaceMetrics::WidthRange* range,
+ int endId,
+ SkAdvancedTypefaceMetrics::WidthRange::MetricType type);
+
+template void resetRange(
+ SkAdvancedTypefaceMetrics::VerticalAdvanceRange* range,
+ int startId);
+template SkAdvancedTypefaceMetrics::VerticalAdvanceRange* appendRange(
+ SkTScopedPtr<SkAdvancedTypefaceMetrics::VerticalAdvanceRange >*
+ nextSlot,
+ int startId);
+template void finishRange<SkAdvancedTypefaceMetrics::VerticalMetric>(
+ SkAdvancedTypefaceMetrics::VerticalAdvanceRange* range,
+ int endId,
+ SkAdvancedTypefaceMetrics::VerticalAdvanceRange::MetricType type);
+
+// additional declaration needed for testing with a face of an unknown type
+template SkAdvancedTypefaceMetrics::WidthRange* getAdvanceData(
+ void* fontData,
+ int num_glyphs,
+ const uint32_t* subsetGlyphIDs,
+ uint32_t subsetGlyphIDsLength,
+ bool (*getAdvance)(void* fontData, int gId, int16_t* data));
+
+} // namespace skia_advanced_typeface_metrics_utils
diff --git a/core/SkAlphaRuns.cpp b/core/SkAlphaRuns.cpp
new file mode 100644
index 00000000..1b041581
--- /dev/null
+++ b/core/SkAlphaRuns.cpp
@@ -0,0 +1,178 @@
+
+/*
+ * 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 "SkAntiRun.h"
+#include "SkUtils.h"
+
+void SkAlphaRuns::reset(int width) {
+ SkASSERT(width > 0);
+
+#ifdef SK_DEBUG
+ sk_memset16((uint16_t*)fRuns, (uint16_t)(-42), width);
+#endif
+ fRuns[0] = SkToS16(width);
+ fRuns[width] = 0;
+ fAlpha[0] = 0;
+
+ SkDEBUGCODE(fWidth = width;)
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkAlphaRuns::Break(int16_t runs[], uint8_t alpha[], int x, int count) {
+ SkASSERT(count > 0 && x >= 0);
+
+// SkAlphaRuns::BreakAt(runs, alpha, x);
+// SkAlphaRuns::BreakAt(&runs[x], &alpha[x], count);
+
+ int16_t* next_runs = runs + x;
+ uint8_t* next_alpha = alpha + x;
+
+ while (x > 0) {
+ int n = runs[0];
+ SkASSERT(n > 0);
+
+ if (x < n) {
+ alpha[x] = alpha[0];
+ runs[0] = SkToS16(x);
+ runs[x] = SkToS16(n - x);
+ break;
+ }
+ runs += n;
+ alpha += n;
+ x -= n;
+ }
+
+ runs = next_runs;
+ alpha = next_alpha;
+ x = count;
+
+ for (;;) {
+ int n = runs[0];
+ SkASSERT(n > 0);
+
+ if (x < n) {
+ alpha[x] = alpha[0];
+ runs[0] = SkToS16(x);
+ runs[x] = SkToS16(n - x);
+ break;
+ }
+ x -= n;
+ if (x <= 0) {
+ break;
+ }
+ runs += n;
+ alpha += n;
+ }
+}
+
+int SkAlphaRuns::add(int x, U8CPU startAlpha, int middleCount, U8CPU stopAlpha,
+ U8CPU maxValue, int offsetX) {
+ SkASSERT(middleCount >= 0);
+ SkASSERT(x >= 0 && x + (startAlpha != 0) + middleCount + (stopAlpha != 0) <= fWidth);
+
+ SkASSERT(fRuns[offsetX] >= 0);
+
+ int16_t* runs = fRuns + offsetX;
+ uint8_t* alpha = fAlpha + offsetX;
+ uint8_t* lastAlpha = alpha;
+ x -= offsetX;
+
+ if (startAlpha) {
+ SkAlphaRuns::Break(runs, alpha, x, 1);
+ /* I should be able to just add alpha[x] + startAlpha.
+ However, if the trailing edge of the previous span and the leading
+ edge of the current span round to the same super-sampled x value,
+ I might overflow to 256 with this add, hence the funny subtract (crud).
+ */
+ unsigned tmp = alpha[x] + startAlpha;
+ SkASSERT(tmp <= 256);
+ alpha[x] = SkToU8(tmp - (tmp >> 8)); // was (tmp >> 7), but that seems wrong if we're trying to catch 256
+
+ runs += x + 1;
+ alpha += x + 1;
+ x = 0;
+ lastAlpha += x; // we don't want the +1
+ SkDEBUGCODE(this->validate();)
+ }
+
+ if (middleCount) {
+ SkAlphaRuns::Break(runs, alpha, x, middleCount);
+ alpha += x;
+ runs += x;
+ x = 0;
+ do {
+ alpha[0] = SkToU8(alpha[0] + maxValue);
+ int n = runs[0];
+ SkASSERT(n <= middleCount);
+ alpha += n;
+ runs += n;
+ middleCount -= n;
+ } while (middleCount > 0);
+ SkDEBUGCODE(this->validate();)
+ lastAlpha = alpha;
+ }
+
+ if (stopAlpha) {
+ SkAlphaRuns::Break(runs, alpha, x, 1);
+ alpha += x;
+ alpha[0] = SkToU8(alpha[0] + stopAlpha);
+ SkDEBUGCODE(this->validate();)
+ lastAlpha = alpha;
+ }
+
+ return SkToS32(lastAlpha - fAlpha); // new offsetX
+}
+
+#ifdef SK_DEBUG
+ void SkAlphaRuns::assertValid(int y, int maxStep) const {
+ int max = (y + 1) * maxStep - (y == maxStep - 1);
+
+ const int16_t* runs = fRuns;
+ const uint8_t* alpha = fAlpha;
+
+ while (*runs) {
+ SkASSERT(*alpha <= max);
+ alpha += *runs;
+ runs += *runs;
+ }
+ }
+
+ void SkAlphaRuns::dump() const {
+ const int16_t* runs = fRuns;
+ const uint8_t* alpha = fAlpha;
+
+ SkDebugf("Runs");
+ while (*runs) {
+ int n = *runs;
+
+ SkDebugf(" %02x", *alpha);
+ if (n > 1) {
+ SkDebugf(",%d", n);
+ }
+ alpha += n;
+ runs += n;
+ }
+ SkDebugf("\n");
+ }
+
+ void SkAlphaRuns::validate() const {
+ SkASSERT(fWidth > 0);
+
+ int count = 0;
+ const int16_t* runs = fRuns;
+
+ while (*runs) {
+ SkASSERT(*runs > 0);
+ count += *runs;
+ SkASSERT(count <= fWidth);
+ runs += *runs;
+ }
+ SkASSERT(count == fWidth);
+ }
+#endif
diff --git a/core/SkAnnotation.cpp b/core/SkAnnotation.cpp
new file mode 100644
index 00000000..52fa9b79
--- /dev/null
+++ b/core/SkAnnotation.cpp
@@ -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.
+ */
+
+#include "SkAnnotation.h"
+#include "SkDataSet.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPoint.h"
+#include "SkStream.h"
+
+SkAnnotation::SkAnnotation(SkDataSet* data, uint32_t flags) {
+ if (NULL == data) {
+ data = SkDataSet::NewEmpty();
+ } else {
+ data->ref();
+ }
+ fDataSet = data;
+ fFlags = flags;
+}
+
+SkAnnotation::~SkAnnotation() {
+ fDataSet->unref();
+}
+
+SkData* SkAnnotation::find(const char name[]) const {
+ return fDataSet->find(name);
+}
+
+SkAnnotation::SkAnnotation(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fFlags = buffer.readUInt();
+ fDataSet = buffer.readFlattenableT<SkDataSet>();
+}
+
+void SkAnnotation::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeUInt(fFlags);
+ buffer.writeFlattenable(fDataSet);
+}
+
+const char* SkAnnotationKeys::URL_Key() {
+ return "SkAnnotationKey_URL";
+};
+
+const char* SkAnnotationKeys::Define_Named_Dest_Key() {
+ return "SkAnnotationKey_Define_Named_Dest";
+};
+
+const char* SkAnnotationKeys::Link_Named_Dest_Key() {
+ return "SkAnnotationKey_Link_Named_Dest";
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkCanvas.h"
+
+static void annotate_paint(SkPaint& paint, const char* key, SkData* value) {
+ SkAutoTUnref<SkDataSet> dataset(SkNEW_ARGS(SkDataSet, (key, value)));
+ SkAnnotation* ann = SkNEW_ARGS(SkAnnotation, (dataset,
+ SkAnnotation::kNoDraw_Flag));
+
+ paint.setAnnotation(ann)->unref();
+ SkASSERT(paint.isNoDrawAnnotation());
+}
+
+void SkAnnotateRectWithURL(SkCanvas* canvas, const SkRect& rect, SkData* value) {
+ if (NULL == value) {
+ return;
+ }
+ SkPaint paint;
+ annotate_paint(paint, SkAnnotationKeys::URL_Key(), value);
+ canvas->drawRect(rect, paint);
+}
+
+void SkAnnotateNamedDestination(SkCanvas* canvas, const SkPoint& point, SkData* name) {
+ if (NULL == name) {
+ return;
+ }
+ SkPaint paint;
+ annotate_paint(paint, SkAnnotationKeys::Define_Named_Dest_Key(), name);
+ canvas->drawPoint(point.x(), point.y(), paint);
+}
+
+void SkAnnotateLinkToDestination(SkCanvas* canvas, const SkRect& rect, SkData* name) {
+ if (NULL == name) {
+ return;
+ }
+ SkPaint paint;
+ annotate_paint(paint, SkAnnotationKeys::Link_Named_Dest_Key(), name);
+ canvas->drawRect(rect, paint);
+}
diff --git a/core/SkAntiRun.h b/core/SkAntiRun.h
new file mode 100644
index 00000000..12397269
--- /dev/null
+++ b/core/SkAntiRun.h
@@ -0,0 +1,92 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAntiRun_DEFINED
+#define SkAntiRun_DEFINED
+
+#include "SkBlitter.h"
+
+/** Sparse array of run-length-encoded alpha (supersampling coverage) values.
+ Sparseness allows us to independently compose several paths into the
+ same SkAlphaRuns buffer.
+*/
+
+class SkAlphaRuns {
+public:
+ int16_t* fRuns;
+ uint8_t* fAlpha;
+
+ /// Returns true if the scanline contains only a single run,
+ /// of alpha value 0.
+ bool empty() const {
+ SkASSERT(fRuns[0] > 0);
+ return fAlpha[0] == 0 && fRuns[fRuns[0]] == 0;
+ }
+
+ /// Reinitialize for a new scanline.
+ void reset(int width);
+
+ /**
+ * Insert into the buffer a run starting at (x-offsetX):
+ * if startAlpha > 0
+ * one pixel with value += startAlpha,
+ * max 255
+ * if middleCount > 0
+ * middleCount pixels with value += maxValue
+ * if stopAlpha > 0
+ * one pixel with value += stopAlpha
+ * Returns the offsetX value that should be passed on the next call,
+ * assuming we're on the same scanline. If the caller is switching
+ * scanlines, then offsetX should be 0 when this is called.
+ */
+ int add(int x, U8CPU startAlpha, int middleCount, U8CPU stopAlpha,
+ U8CPU maxValue, int offsetX);
+
+ SkDEBUGCODE(void assertValid(int y, int maxStep) const;)
+ SkDEBUGCODE(void dump() const;)
+
+ /**
+ * Break the runs in the buffer at offsets x and x+count, properly
+ * updating the runs to the right and left.
+ * i.e. from the state AAAABBBB, run-length encoded as A4B4,
+ * Break(..., 2, 5) would produce AAAABBBB rle as A2A2B3B1.
+ * Allows add() to sum another run to some of the new sub-runs.
+ * i.e. adding ..CCCCC. would produce AADDEEEB, rle as A2D2E3B1.
+ */
+ static void Break(int16_t runs[], uint8_t alpha[], int x, int count);
+
+ /**
+ * Cut (at offset x in the buffer) a run into two shorter runs with
+ * matching alpha values.
+ * Used by the RectClipBlitter to trim a RLE encoding to match the
+ * clipping rectangle.
+ */
+ static void BreakAt(int16_t runs[], uint8_t alpha[], int x) {
+ while (x > 0) {
+ int n = runs[0];
+ SkASSERT(n > 0);
+
+ if (x < n) {
+ alpha[x] = alpha[0];
+ runs[0] = SkToS16(x);
+ runs[x] = SkToS16(n - x);
+ break;
+ }
+ runs += n;
+ alpha += n;
+ x -= n;
+ }
+ }
+
+private:
+ SkDEBUGCODE(int fWidth;)
+ SkDEBUGCODE(void validate() const;)
+};
+
+#endif
diff --git a/core/SkAutoKern.h b/core/SkAutoKern.h
new file mode 100644
index 00000000..0b22e564
--- /dev/null
+++ b/core/SkAutoKern.h
@@ -0,0 +1,53 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkAutoKern_DEFINED
+#define SkAutoKern_DEFINED
+
+#include "SkGlyph.h"
+
+#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+#define SkAutoKern_AdjustS(prev, next) SkIntToScalar(((next) - (prev) + 32) >> 6)
+
+/* this is a helper class to perform auto-kerning
+ * the adjust() method returns a SkFixed corresponding
+ * to a +1/0/-1 pixel adjustment
+ */
+
+class SkAutoKern {
+public:
+ SkAutoKern() : fPrevRsbDelta(0) {}
+
+ SkFixed adjust(const SkGlyph& glyph)
+ {
+// if (SkAbs32(glyph.fLsbDelta) > 47 || SkAbs32(glyph.fRsbDelta) > 47)
+// printf("------- %d> L %d R %d\n", glyph.f_GlyphID, glyph.fLsbDelta, glyph.fRsbDelta);
+
+#if 0
+ int distort = fPrevRsbDelta - glyph.fLsbDelta;
+
+ fPrevRsbDelta = glyph.fRsbDelta;
+
+ if (distort >= 32)
+ return -SK_Fixed1;
+ else if (distort < -32)
+ return +SK_Fixed1;
+ else
+ return 0;
+#else
+ SkFixed adjust = SkAutoKern_AdjustF(fPrevRsbDelta, glyph.fLsbDelta);
+ fPrevRsbDelta = glyph.fRsbDelta;
+ return adjust;
+#endif
+ }
+private:
+ int fPrevRsbDelta;
+};
+
+#endif
diff --git a/core/SkBBoxHierarchy.cpp b/core/SkBBoxHierarchy.cpp
new file mode 100644
index 00000000..5232fb7c
--- /dev/null
+++ b/core/SkBBoxHierarchy.cpp
@@ -0,0 +1,11 @@
+
+/*
+ * 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 "SkBBoxHierarchy.h"
+
+SK_DEFINE_INST_COUNT(SkBBoxHierarchy)
diff --git a/core/SkBBoxHierarchy.h b/core/SkBBoxHierarchy.h
new file mode 100644
index 00000000..62b22d80
--- /dev/null
+++ b/core/SkBBoxHierarchy.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 SkBBoxHierarchy_DEFINED
+#define SkBBoxHierarchy_DEFINED
+
+#include "SkRect.h"
+#include "SkTDArray.h"
+#include "SkRefCnt.h"
+
+/**
+ * Interface for a client class that implements utility methods needed
+ * by SkBBoxHierarchy that require intrinsic knowledge of the data
+ * object type that is stored in the bounding box hierarchy.
+ */
+class SkBBoxHierarchyClient {
+public:
+ virtual ~SkBBoxHierarchyClient() {}
+
+ /**
+ * Implements a rewind stop condition used by rewindInserts
+ * Must returns true if 'data' points to an object that should be re-wound
+ * by rewinfInserts.
+ */
+ virtual bool shouldRewind(void* data) = 0;
+};
+
+/**
+ * Interface for a spatial data structure that associates user data pointers with axis-aligned
+ * bounding boxes, and allows efficient retrieval of intersections with query rectangles.
+ */
+class SkBBoxHierarchy : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkBBoxHierarchy)
+
+ SkBBoxHierarchy() : fClient(NULL) {}
+
+ /**
+ * 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 Whether or not it is acceptable to delay insertion of this element (building up
+ * an entire spatial data structure at once is often faster and produces better
+ * structures than repeated inserts) until flushDeferredInserts is called or the first
+ * search.
+ */
+ virtual void insert(void* data, const SkIRect& bounds, bool defer = false) = 0;
+
+ /**
+ * If any insertions have been deferred, this forces them to be inserted
+ */
+ virtual void flushDeferredInserts() = 0;
+
+ /**
+ * Populate 'results' with data pointers corresponding to bounding boxes that intersect 'query'
+ */
+ virtual void search(const SkIRect& query, SkTDArray<void*>* results) = 0;
+
+ virtual void clear() = 0;
+
+ /**
+ * Gets the number of insertions
+ */
+ virtual int getCount() const = 0;
+
+ /**
+ * Rewinds all the most recently inserted data elements until an element
+ * is encountered for which client->shouldRewind(data) returns false. May
+ * not rewind elements that were inserted prior to the last call to
+ * flushDeferredInserts.
+ */
+ virtual void rewindInserts() = 0;
+
+ void setClient(SkBBoxHierarchyClient* client) { fClient = client; }
+
+protected:
+ SkBBoxHierarchyClient* fClient;
+
+private:
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkBBoxHierarchyRecord.cpp b/core/SkBBoxHierarchyRecord.cpp
new file mode 100644
index 00000000..9c02468a
--- /dev/null
+++ b/core/SkBBoxHierarchyRecord.cpp
@@ -0,0 +1,115 @@
+
+/*
+ * 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 "SkBBoxHierarchyRecord.h"
+#include "SkPictureStateTree.h"
+
+SkBBoxHierarchyRecord::SkBBoxHierarchyRecord(uint32_t recordFlags,
+ SkBBoxHierarchy* h,
+ SkDevice* device)
+ : INHERITED(recordFlags, device) {
+ fStateTree = SkNEW(SkPictureStateTree);
+ fBoundingHierarchy = h;
+ fBoundingHierarchy->ref();
+ fBoundingHierarchy->setClient(this);
+}
+
+void SkBBoxHierarchyRecord::handleBBox(const SkRect& bounds) {
+ SkIRect r;
+ bounds.roundOut(&r);
+ SkPictureStateTree::Draw* draw = fStateTree->appendDraw(this->writeStream().size());
+ fBoundingHierarchy->insert(draw, r, true);
+}
+
+int SkBBoxHierarchyRecord::save(SaveFlags flags) {
+ fStateTree->appendSave();
+ return INHERITED::save(flags);
+}
+
+int SkBBoxHierarchyRecord::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ fStateTree->appendSaveLayer(this->writeStream().size());
+ return INHERITED::saveLayer(bounds, paint, flags);
+}
+
+void SkBBoxHierarchyRecord::restore() {
+ fStateTree->appendRestore();
+ INHERITED::restore();
+}
+
+bool SkBBoxHierarchyRecord::translate(SkScalar dx, SkScalar dy) {
+ bool result = INHERITED::translate(dx, dy);
+ fStateTree->appendTransform(getTotalMatrix());
+ return result;
+}
+
+bool SkBBoxHierarchyRecord::scale(SkScalar sx, SkScalar sy) {
+ bool result = INHERITED::scale(sx, sy);
+ fStateTree->appendTransform(getTotalMatrix());
+ return result;
+}
+
+bool SkBBoxHierarchyRecord::rotate(SkScalar degrees) {
+ bool result = INHERITED::rotate(degrees);
+ fStateTree->appendTransform(getTotalMatrix());
+ return result;
+}
+
+bool SkBBoxHierarchyRecord::skew(SkScalar sx, SkScalar sy) {
+ bool result = INHERITED::skew(sx, sy);
+ fStateTree->appendTransform(getTotalMatrix());
+ return result;
+}
+
+bool SkBBoxHierarchyRecord::concat(const SkMatrix& matrix) {
+ bool result = INHERITED::concat(matrix);
+ fStateTree->appendTransform(getTotalMatrix());
+ return result;
+}
+
+void SkBBoxHierarchyRecord::setMatrix(const SkMatrix& matrix) {
+ INHERITED::setMatrix(matrix);
+ fStateTree->appendTransform(getTotalMatrix());
+}
+
+bool SkBBoxHierarchyRecord::clipRect(const SkRect& rect,
+ SkRegion::Op op,
+ bool doAntiAlias) {
+ fStateTree->appendClip(this->writeStream().size());
+ return INHERITED::clipRect(rect, op, doAntiAlias);
+}
+
+bool SkBBoxHierarchyRecord::clipRegion(const SkRegion& region,
+ SkRegion::Op op) {
+ fStateTree->appendClip(this->writeStream().size());
+ return INHERITED::clipRegion(region, op);
+}
+
+bool SkBBoxHierarchyRecord::clipPath(const SkPath& path,
+ SkRegion::Op op,
+ bool doAntiAlias) {
+ fStateTree->appendClip(this->writeStream().size());
+ 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);
+}
+
+bool SkBBoxHierarchyRecord::shouldRewind(void* data) {
+ // SkBBoxHierarchy::rewindInserts is called by SkPicture after the
+ // SkPicture has rewound its command stream. To match that rewind in the
+ // BBH, we rewind all draws that reference commands that were recorded
+ // past the point to which the SkPicture has rewound, which is given by
+ // writeStream().size().
+ SkPictureStateTree::Draw* draw = static_cast<SkPictureStateTree::Draw*>(data);
+ return draw->fOffset >= writeStream().size();
+}
diff --git a/core/SkBBoxHierarchyRecord.h b/core/SkBBoxHierarchyRecord.h
new file mode 100644
index 00000000..27da3c91
--- /dev/null
+++ b/core/SkBBoxHierarchyRecord.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.
+ */
+
+#ifndef SkRTreeCanvas_DEFINED
+#define SkRTreeCanvas_DEFINED
+
+#include "SkBBoxHierarchy.h"
+#include "SkBBoxRecord.h"
+
+/**
+ * This records bounding box information into an SkBBoxHierarchy, and clip/transform information
+ * into an SkPictureStateTree to allow for efficient culling and correct playback of draws.
+ */
+class SkBBoxHierarchyRecord : public SkBBoxRecord, public SkBBoxHierarchyClient {
+public:
+ /** This will take a ref of h */
+ SkBBoxHierarchyRecord(uint32_t recordFlags, SkBBoxHierarchy* h,
+ SkDevice*);
+
+ virtual void handleBBox(const SkRect& bounds) SK_OVERRIDE;
+
+ virtual int save(SaveFlags flags = kMatrixClip_SaveFlag) SK_OVERRIDE;
+ virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags = kARGB_ClipLayer_SaveFlag) SK_OVERRIDE;
+ virtual void restore() SK_OVERRIDE;
+
+ virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE;
+ virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE;
+ virtual bool rotate(SkScalar degrees) SK_OVERRIDE;
+ 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 = SkRegion::kIntersect_Op,
+ bool doAntiAlias = false) SK_OVERRIDE;
+ virtual bool clipRegion(const SkRegion& region,
+ SkRegion::Op op = SkRegion::kIntersect_Op) SK_OVERRIDE;
+ 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;
+
+ // Implementation of the SkBBoxHierarchyClient interface
+ virtual bool shouldRewind(void* data) SK_OVERRIDE;
+
+private:
+ typedef SkBBoxRecord INHERITED;
+};
+
+#endif
diff --git a/core/SkBBoxRecord.cpp b/core/SkBBoxRecord.cpp
new file mode 100644
index 00000000..52d599f5
--- /dev/null
+++ b/core/SkBBoxRecord.cpp
@@ -0,0 +1,284 @@
+
+/*
+ * 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 "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);
+ }
+}
+
+void SkBBoxRecord::drawPath(const SkPath& path, const SkPaint& 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);
+ }
+}
+
+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);
+ }
+}
+
+void SkBBoxRecord::drawPaint(const SkPaint& paint) {
+ SkRect bbox;
+ if (this->getClipBounds(&bbox)) {
+ if (this->transformBounds(bbox, &paint)) {
+ INHERITED::drawPaint(paint);
+ }
+ }
+}
+
+void SkBBoxRecord::clear(SkColor color) {
+ SkISize size = this->getDeviceSize();
+ SkRect bbox = {0, 0, SkIntToScalar(size.width()), SkIntToScalar(size.height())};
+ this->handleBBox(bbox);
+ INHERITED::clear(color);
+}
+
+void SkBBoxRecord::drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+ const SkPaint& paint) {
+ SkRect bbox;
+ paint.measureText(text, byteLength, &bbox);
+ SkPaint::FontMetrics metrics;
+ paint.getFontMetrics(&metrics);
+
+ // Vertical and aligned text need to be offset
+ if (paint.isVerticalText()) {
+ SkScalar h = bbox.fBottom - bbox.fTop;
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ bbox.fTop -= h / 2;
+ bbox.fBottom -= h / 2;
+ }
+ // Pad top and bottom with max extents from FontMetrics
+ bbox.fBottom += metrics.fBottom;
+ bbox.fTop += metrics.fTop;
+ } else {
+ SkScalar w = bbox.fRight - bbox.fLeft;
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ bbox.fLeft -= w / 2;
+ bbox.fRight -= w / 2;
+ } else if (paint.getTextAlign() == SkPaint::kRight_Align) {
+ bbox.fLeft -= w;
+ bbox.fRight -= w;
+ }
+ // Set vertical bounds to max extents from font metrics
+ bbox.fTop = metrics.fTop;
+ bbox.fBottom = metrics.fBottom;
+ }
+
+ // Pad horizontal bounds on each side by half of max vertical extents (this is sort of
+ // arbitrary, but seems to produce reasonable results, if there were a way of getting max
+ // glyph X-extents to pad by, that may be better here, but FontMetrics fXMin and fXMax seem
+ // incorrect on most platforms (too small in Linux, never even set in Windows).
+ SkScalar pad = (metrics.fBottom - metrics.fTop) / 2;
+ bbox.fLeft -= pad;
+ bbox.fRight += pad;
+
+ bbox.fLeft += x;
+ bbox.fRight += x;
+ bbox.fTop += y;
+ bbox.fBottom += y;
+ if (this->transformBounds(bbox, &paint)) {
+ INHERITED::drawText(text, byteLength, x, y, paint);
+ }
+}
+
+void SkBBoxRecord::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+ const SkPaint* paint) {
+ SkRect bbox = {left, top, left + bitmap.width(), top + bitmap.height()};
+ if (this->transformBounds(bbox, paint)) {
+ INHERITED::drawBitmap(bitmap, left, top, paint);
+ }
+}
+
+void SkBBoxRecord::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ if (this->transformBounds(dst, paint)) {
+ INHERITED::drawBitmapRectToRect(bitmap, src, dst, paint);
+ }
+}
+
+void SkBBoxRecord::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& mat,
+ const SkPaint* paint) {
+ SkMatrix m = mat;
+ SkRect bbox = {0, 0, SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height())};
+ m.mapRect(&bbox);
+ if (this->transformBounds(bbox, paint)) {
+ INHERITED::drawBitmapMatrix(bitmap, mat, paint);
+ }
+}
+
+void SkBBoxRecord::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) {
+ if (this->transformBounds(dst, paint)) {
+ INHERITED::drawBitmapNine(bitmap, center, dst, paint);
+ }
+}
+
+void SkBBoxRecord::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ SkRect bbox;
+ bbox.set(pos, paint.countText(text, byteLength));
+ SkPaint::FontMetrics metrics;
+ paint.getFontMetrics(&metrics);
+ bbox.fTop += metrics.fTop;
+ bbox.fBottom += metrics.fBottom;
+
+ // pad on left and right by half of max vertical glyph extents
+ SkScalar pad = (metrics.fTop - metrics.fBottom) / 2;
+ bbox.fLeft += pad;
+ bbox.fRight -= pad;
+
+ if (this->transformBounds(bbox, &paint)) {
+ INHERITED::drawPosText(text, byteLength, pos, paint);
+ }
+}
+
+void SkBBoxRecord::drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
+ SkScalar constY, const SkPaint& paint) {
+ SkRect bbox;
+ size_t numChars = paint.countText(text, byteLength);
+ if (numChars > 0) {
+ bbox.fLeft = xpos[0];
+ bbox.fRight = xpos[numChars - 1];
+ // if we had a guarantee that these will be monotonically increasing, this could be sped up
+ for (size_t i = 1; i < numChars; ++i) {
+ if (xpos[i] < bbox.fLeft) {
+ bbox.fLeft = xpos[i];
+ }
+ if (xpos[i] > bbox.fRight) {
+ bbox.fRight = xpos[i];
+ }
+ }
+ SkPaint::FontMetrics metrics;
+ paint.getFontMetrics(&metrics);
+
+ // pad horizontally by max glyph height
+ SkScalar pad = (metrics.fTop - metrics.fBottom);
+ bbox.fLeft += pad;
+ bbox.fRight -= pad;
+
+ bbox.fTop = metrics.fTop + constY;
+ bbox.fBottom = metrics.fBottom + constY;
+ if (!this->transformBounds(bbox, &paint)) {
+ return;
+ }
+ }
+ INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint);
+}
+
+void SkBBoxRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint) {
+ SkRect bbox;
+ bbox.set(SkIRect::MakeXYWH(left, top, bitmap.width(), bitmap.height()));
+ this->handleBBox(bbox); // directly call handleBBox, matrix is ignored
+ INHERITED::drawSprite(bitmap, left, top, paint);
+}
+
+void SkBBoxRecord::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ SkRect bbox = path.getBounds();
+ SkPaint::FontMetrics metrics;
+ paint.getFontMetrics(&metrics);
+
+ // pad out all sides by the max glyph height above baseline
+ SkScalar pad = metrics.fTop;
+ bbox.fLeft += pad;
+ bbox.fRight -= pad;
+ bbox.fTop += pad;
+ bbox.fBottom -= pad;
+
+ if (this->transformBounds(bbox, &paint)) {
+ INHERITED::drawTextOnPath(text, byteLength, path, matrix, paint);
+ }
+}
+
+void SkBBoxRecord::drawVertices(VertexMode mode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xfer,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ SkRect bbox;
+ bbox.set(vertices, vertexCount);
+ if (this->transformBounds(bbox, &paint)) {
+ INHERITED::drawVertices(mode, vertexCount, vertices, texs,
+ colors, xfer, indices, indexCount, paint);
+ }
+}
+
+void SkBBoxRecord::drawPicture(SkPicture& picture) {
+ if (picture.width() > 0 && picture.height() > 0 &&
+ this->transformBounds(SkRect::MakeWH(picture.width(), picture.height()), NULL)) {
+ INHERITED::drawPicture(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(outBounds, &temp);
+ } else {
+ // set bounds to current clip
+ if (!this->getClipBounds(&outBounds)) {
+ // current clip is empty
+ return false;
+ }
+ }
+ }
+
+ if (!outBounds.isEmpty() && !this->quickReject(outBounds)) {
+ this->getTotalMatrix().mapRect(&outBounds);
+ this->handleBBox(outBounds);
+ return true;
+ }
+
+ return false;
+}
diff --git a/core/SkBBoxRecord.h b/core/SkBBoxRecord.h
new file mode 100644
index 00000000..9f796717
--- /dev/null
+++ b/core/SkBBoxRecord.h
@@ -0,0 +1,78 @@
+
+/*
+ * 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 SkBBoxRecord_DEFINED
+#define SkBBoxRecord_DEFINED
+
+#include "SkPictureRecord.h"
+
+/**
+ * This is an abstract SkPictureRecord subclass that intercepts draw calls and computes an
+ * axis-aligned bounding box for each draw that it sees, subclasses implement handleBBox()
+ * which will be called every time we get a new bounding box.
+ */
+class SkBBoxRecord : public SkPictureRecord {
+public:
+
+ SkBBoxRecord(uint32_t recordFlags, SkDevice* device)
+ : INHERITED(recordFlags, device) { }
+ virtual ~SkBBoxRecord() { }
+
+ /**
+ * This is called each time we get a bounding box, it will be axis-aligned,
+ * in device coordinates, and expanded to include stroking, shadows, etc.
+ */
+ 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[],
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
+ virtual void clear(SkColor) SK_OVERRIDE;
+ virtual void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+ const SkPaint* paint = NULL) SK_OVERRIDE;
+ virtual void drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) SK_OVERRIDE;
+ virtual void drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& mat,
+ const SkPaint* paint) SK_OVERRIDE;
+ virtual void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) SK_OVERRIDE;
+ virtual void drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint) SK_OVERRIDE;
+ virtual void drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawVertices(VertexMode mode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xfer,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) SK_OVERRIDE;
+ virtual void drawPicture(SkPicture& picture) SK_OVERRIDE;
+
+private:
+ /**
+ * Takes a bounding box in current canvas view space, accounts for stroking and effects, and
+ * computes an axis-aligned bounding box in device coordinates, then passes it to handleBBox()
+ * returns false if the draw is completely clipped out, and may safely be ignored.
+ **/
+ bool transformBounds(const SkRect& bounds, const SkPaint* paint);
+
+ typedef SkPictureRecord INHERITED;
+};
+
+#endif
diff --git a/core/SkBitmap.cpp b/core/SkBitmap.cpp
new file mode 100644
index 00000000..d3bbecd7
--- /dev/null
+++ b/core/SkBitmap.cpp
@@ -0,0 +1,1709 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkFlattenable.h"
+#include "SkMallocPixelRef.h"
+#include "SkMask.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPixelRef.h"
+#include "SkThread.h"
+#include "SkUnPreMultiply.h"
+#include "SkUtils.h"
+#include "SkPackBits.h"
+#include <new>
+
+SK_DEFINE_INST_COUNT(SkBitmap::Allocator)
+
+static bool isPos32Bits(const Sk64& value) {
+ return !value.isNeg() && value.is32();
+}
+
+struct MipLevel {
+ void* fPixels;
+ uint32_t fRowBytes;
+ uint32_t fWidth, fHeight;
+};
+
+struct SkBitmap::MipMap : SkNoncopyable {
+ int32_t fRefCnt;
+ int fLevelCount;
+// MipLevel fLevel[fLevelCount];
+// Pixels[]
+
+ static MipMap* Alloc(int levelCount, size_t pixelSize) {
+ if (levelCount < 0) {
+ return NULL;
+ }
+ Sk64 size;
+ size.setMul(levelCount + 1, sizeof(MipLevel));
+ size.add(sizeof(MipMap));
+ size.add(SkToS32(pixelSize));
+ if (!isPos32Bits(size)) {
+ return NULL;
+ }
+ MipMap* mm = (MipMap*)sk_malloc_throw(size.get32());
+ mm->fRefCnt = 1;
+ mm->fLevelCount = levelCount;
+ return mm;
+ }
+
+ const MipLevel* levels() const { return (const MipLevel*)(this + 1); }
+ MipLevel* levels() { return (MipLevel*)(this + 1); }
+
+ const void* pixels() const { return levels() + fLevelCount; }
+ void* pixels() { return levels() + fLevelCount; }
+
+ void ref() {
+ if (SK_MaxS32 == sk_atomic_inc(&fRefCnt)) {
+ sk_throw();
+ }
+ }
+ void unref() {
+ SkASSERT(fRefCnt > 0);
+ if (sk_atomic_dec(&fRefCnt) == 1) {
+ sk_free(this);
+ }
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+SkBitmap::SkBitmap() {
+ sk_bzero(this, sizeof(*this));
+}
+
+SkBitmap::SkBitmap(const SkBitmap& src) {
+ SkDEBUGCODE(src.validate();)
+ sk_bzero(this, sizeof(*this));
+ *this = src;
+ SkDEBUGCODE(this->validate();)
+}
+
+SkBitmap::~SkBitmap() {
+ SkDEBUGCODE(this->validate();)
+ this->freePixels();
+}
+
+SkBitmap& SkBitmap::operator=(const SkBitmap& src) {
+ if (this != &src) {
+ this->freePixels();
+ memcpy(this, &src, sizeof(src));
+
+ // inc src reference counts
+ SkSafeRef(src.fPixelRef);
+ SkSafeRef(src.fMipMap);
+
+ // we reset our locks if we get blown away
+ fPixelLockCount = 0;
+
+ /* The src could be in 3 states
+ 1. no pixelref, in which case we just copy/ref the pixels/ctable
+ 2. unlocked pixelref, pixels/ctable should be null
+ 3. locked pixelref, we should lock the ref again ourselves
+ */
+ if (NULL == fPixelRef) {
+ // leave fPixels as it is
+ SkSafeRef(fColorTable); // ref the user's ctable if present
+ } else { // we have a pixelref, so pixels/ctable reflect it
+ // ignore the values from the memcpy
+ fPixels = NULL;
+ fColorTable = NULL;
+ // Note that what to for genID is somewhat arbitrary. We have no
+ // way to track changes to raw pixels across multiple SkBitmaps.
+ // Would benefit from an SkRawPixelRef type created by
+ // setPixels.
+ // Just leave the memcpy'ed one but they'll get out of sync
+ // as soon either is modified.
+ }
+ }
+
+ SkDEBUGCODE(this->validate();)
+ return *this;
+}
+
+void SkBitmap::swap(SkBitmap& other) {
+ SkTSwap(fColorTable, other.fColorTable);
+ SkTSwap(fPixelRef, other.fPixelRef);
+ SkTSwap(fPixelRefOffset, other.fPixelRefOffset);
+ SkTSwap(fPixelLockCount, other.fPixelLockCount);
+ SkTSwap(fMipMap, other.fMipMap);
+ SkTSwap(fPixels, other.fPixels);
+ SkTSwap(fRowBytes, other.fRowBytes);
+ SkTSwap(fWidth, other.fWidth);
+ SkTSwap(fHeight, other.fHeight);
+ SkTSwap(fConfig, other.fConfig);
+ SkTSwap(fFlags, other.fFlags);
+ SkTSwap(fBytesPerPixel, other.fBytesPerPixel);
+
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkBitmap::reset() {
+ this->freePixels();
+ sk_bzero(this, sizeof(*this));
+}
+
+int SkBitmap::ComputeBytesPerPixel(SkBitmap::Config config) {
+ int bpp;
+ switch (config) {
+ case kNo_Config:
+ case kA1_Config:
+ bpp = 0; // not applicable
+ break;
+ case kA8_Config:
+ case kIndex8_Config:
+ bpp = 1;
+ break;
+ case kRGB_565_Config:
+ case kARGB_4444_Config:
+ bpp = 2;
+ break;
+ case kARGB_8888_Config:
+ bpp = 4;
+ break;
+ default:
+ SkDEBUGFAIL("unknown config");
+ bpp = 0; // error
+ break;
+ }
+ return bpp;
+}
+
+size_t SkBitmap::ComputeRowBytes(Config c, int width) {
+ if (width < 0) {
+ return 0;
+ }
+
+ Sk64 rowBytes;
+ rowBytes.setZero();
+
+ switch (c) {
+ case kNo_Config:
+ break;
+ case kA1_Config:
+ rowBytes.set(width);
+ rowBytes.add(7);
+ rowBytes.shiftRight(3);
+ break;
+ case kA8_Config:
+ case kIndex8_Config:
+ rowBytes.set(width);
+ break;
+ case kRGB_565_Config:
+ case kARGB_4444_Config:
+ rowBytes.set(width);
+ rowBytes.shiftLeft(1);
+ break;
+ case kARGB_8888_Config:
+ rowBytes.set(width);
+ rowBytes.shiftLeft(2);
+ break;
+ default:
+ SkDEBUGFAIL("unknown config");
+ break;
+ }
+ return isPos32Bits(rowBytes) ? rowBytes.get32() : 0;
+}
+
+Sk64 SkBitmap::ComputeSize64(Config c, int width, int height) {
+ Sk64 size;
+ size.setMul(SkToS32(SkBitmap::ComputeRowBytes(c, width)), height);
+ return size;
+}
+
+size_t SkBitmap::ComputeSize(Config c, int width, int height) {
+ Sk64 size = SkBitmap::ComputeSize64(c, width, height);
+ return isPos32Bits(size) ? size.get32() : 0;
+}
+
+Sk64 SkBitmap::ComputeSafeSize64(Config config,
+ uint32_t width,
+ uint32_t height,
+ size_t rowBytes) {
+ Sk64 safeSize;
+ safeSize.setZero();
+ if (height > 0) {
+ // TODO: Handle the case where the return value from
+ // ComputeRowBytes is more than 31 bits.
+ safeSize.set(SkToS32(ComputeRowBytes(config, width)));
+ Sk64 sizeAllButLastRow;
+ sizeAllButLastRow.setMul(height - 1, SkToS32(rowBytes));
+ safeSize.add(sizeAllButLastRow);
+ }
+ SkASSERT(!safeSize.isNeg());
+ return safeSize;
+}
+
+size_t SkBitmap::ComputeSafeSize(Config config,
+ uint32_t width,
+ uint32_t height,
+ size_t rowBytes) {
+ Sk64 safeSize = ComputeSafeSize64(config, width, height, rowBytes);
+ return (safeSize.is32() ? safeSize.get32() : 0);
+}
+
+void SkBitmap::getBounds(SkRect* bounds) const {
+ SkASSERT(bounds);
+ bounds->set(0, 0,
+ SkIntToScalar(fWidth), SkIntToScalar(fHeight));
+}
+
+void SkBitmap::getBounds(SkIRect* bounds) const {
+ SkASSERT(bounds);
+ bounds->set(0, 0, fWidth, fHeight);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkBitmap::setConfig(Config c, int width, int height, size_t rowBytes) {
+ this->freePixels();
+
+ if ((width | height) < 0) {
+ goto err;
+ }
+
+ if (rowBytes == 0) {
+ rowBytes = SkBitmap::ComputeRowBytes(c, width);
+ if (0 == rowBytes && kNo_Config != c) {
+ goto err;
+ }
+ }
+
+ fConfig = SkToU8(c);
+ fWidth = width;
+ fHeight = height;
+ fRowBytes = SkToU32(rowBytes);
+
+ fBytesPerPixel = (uint8_t)ComputeBytesPerPixel(c);
+
+ SkDEBUGCODE(this->validate();)
+ return;
+
+ // if we got here, we had an error, so we reset the bitmap to empty
+err:
+ this->reset();
+}
+
+void SkBitmap::updatePixelsFromRef() const {
+ if (NULL != fPixelRef) {
+ if (fPixelLockCount > 0) {
+ SkASSERT(fPixelRef->isLocked());
+
+ void* p = fPixelRef->pixels();
+ if (NULL != p) {
+ p = (char*)p + fPixelRefOffset;
+ }
+ fPixels = p;
+ SkRefCnt_SafeAssign(fColorTable, fPixelRef->colorTable());
+ } else {
+ SkASSERT(0 == fPixelLockCount);
+ fPixels = NULL;
+ if (fColorTable) {
+ fColorTable->unref();
+ fColorTable = NULL;
+ }
+ }
+ }
+}
+
+SkPixelRef* SkBitmap::setPixelRef(SkPixelRef* pr, size_t offset) {
+ // do this first, we that we never have a non-zero offset with a null ref
+ if (NULL == pr) {
+ offset = 0;
+ }
+
+ if (fPixelRef != pr || fPixelRefOffset != offset) {
+ if (fPixelRef != pr) {
+ this->freePixels();
+ SkASSERT(NULL == fPixelRef);
+
+ SkSafeRef(pr);
+ fPixelRef = pr;
+ }
+ fPixelRefOffset = offset;
+ this->updatePixelsFromRef();
+ }
+
+ SkDEBUGCODE(this->validate();)
+ return pr;
+}
+
+void SkBitmap::lockPixels() const {
+ if (NULL != fPixelRef && 0 == sk_atomic_inc(&fPixelLockCount)) {
+ fPixelRef->lockPixels();
+ this->updatePixelsFromRef();
+ }
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkBitmap::unlockPixels() const {
+ SkASSERT(NULL == fPixelRef || fPixelLockCount > 0);
+
+ if (NULL != fPixelRef && 1 == sk_atomic_dec(&fPixelLockCount)) {
+ fPixelRef->unlockPixels();
+ this->updatePixelsFromRef();
+ }
+ SkDEBUGCODE(this->validate();)
+}
+
+bool SkBitmap::lockPixelsAreWritable() const {
+ return (fPixelRef) ? fPixelRef->lockPixelsAreWritable() : false;
+}
+
+void SkBitmap::setPixels(void* p, SkColorTable* ctable) {
+ if (NULL == p) {
+ this->setPixelRef(NULL, 0);
+ return;
+ }
+
+ Sk64 size = this->getSize64();
+ SkASSERT(!size.isNeg() && size.is32());
+
+ this->setPixelRef(new SkMallocPixelRef(p, size.get32(), ctable, false))->unref();
+ // since we're already allocated, we lockPixels right away
+ this->lockPixels();
+ SkDEBUGCODE(this->validate();)
+}
+
+bool SkBitmap::allocPixels(Allocator* allocator, SkColorTable* ctable) {
+ HeapAllocator stdalloc;
+
+ if (NULL == allocator) {
+ allocator = &stdalloc;
+ }
+ return allocator->allocPixelRef(this, ctable);
+}
+
+void SkBitmap::freePixels() {
+ // if we're gonna free the pixels, we certainly need to free the mipmap
+ this->freeMipMap();
+
+ if (fColorTable) {
+ fColorTable->unref();
+ fColorTable = NULL;
+ }
+
+ if (NULL != fPixelRef) {
+ if (fPixelLockCount > 0) {
+ fPixelRef->unlockPixels();
+ }
+ fPixelRef->unref();
+ fPixelRef = NULL;
+ fPixelRefOffset = 0;
+ }
+ fPixelLockCount = 0;
+ fPixels = NULL;
+}
+
+void SkBitmap::freeMipMap() {
+ if (fMipMap) {
+ fMipMap->unref();
+ fMipMap = NULL;
+ }
+}
+
+uint32_t SkBitmap::getGenerationID() const {
+ return (fPixelRef) ? fPixelRef->getGenerationID() : 0;
+}
+
+void SkBitmap::notifyPixelsChanged() const {
+ SkASSERT(!this->isImmutable());
+ if (fPixelRef) {
+ fPixelRef->notifyPixelsChanged();
+ }
+}
+
+GrTexture* SkBitmap::getTexture() const {
+ return fPixelRef ? fPixelRef->getTexture() : NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** We explicitly use the same allocator for our pixels that SkMask does,
+ so that we can freely assign memory allocated by one class to the other.
+ */
+bool SkBitmap::HeapAllocator::allocPixelRef(SkBitmap* dst,
+ SkColorTable* ctable) {
+ Sk64 size = dst->getSize64();
+ if (size.isNeg() || !size.is32()) {
+ return false;
+ }
+
+ void* addr = sk_malloc_flags(size.get32(), 0); // returns NULL on failure
+ if (NULL == addr) {
+ return false;
+ }
+
+ dst->setPixelRef(new SkMallocPixelRef(addr, size.get32(), ctable))->unref();
+ // since we're already allocated, we lockPixels right away
+ dst->lockPixels();
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+size_t SkBitmap::getSafeSize() const {
+ // This is intended to be a size_t version of ComputeSafeSize64(), just
+ // faster. The computation is meant to be identical.
+ return (fHeight ? ((fHeight - 1) * fRowBytes) +
+ ComputeRowBytes(getConfig(), fWidth): 0);
+}
+
+Sk64 SkBitmap::getSafeSize64() const {
+ return ComputeSafeSize64(getConfig(), fWidth, fHeight, fRowBytes);
+}
+
+bool SkBitmap::copyPixelsTo(void* const dst, size_t dstSize,
+ size_t dstRowBytes, bool preserveDstPad) const {
+
+ if (0 == dstRowBytes) {
+ dstRowBytes = fRowBytes;
+ }
+
+ if (dstRowBytes < ComputeRowBytes(getConfig(), fWidth) ||
+ dst == NULL || (getPixels() == NULL && pixelRef() == NULL))
+ return false;
+
+ if (!preserveDstPad && static_cast<uint32_t>(dstRowBytes) == fRowBytes) {
+ size_t safeSize = getSafeSize();
+ if (safeSize > dstSize || safeSize == 0)
+ return false;
+ else {
+ SkAutoLockPixels lock(*this);
+ // This implementation will write bytes beyond the end of each row,
+ // excluding the last row, if the bitmap's stride is greater than
+ // strictly required by the current config.
+ memcpy(dst, getPixels(), safeSize);
+
+ return true;
+ }
+ } else {
+ // If destination has different stride than us, then copy line by line.
+ if (ComputeSafeSize(getConfig(), fWidth, fHeight, dstRowBytes) >
+ dstSize)
+ return false;
+ else {
+ // Just copy what we need on each line.
+ size_t rowBytes = ComputeRowBytes(getConfig(), fWidth);
+ SkAutoLockPixels lock(*this);
+ const uint8_t* srcP = reinterpret_cast<const uint8_t*>(getPixels());
+ uint8_t* dstP = reinterpret_cast<uint8_t*>(dst);
+ for (uint32_t row = 0; row < fHeight;
+ row++, srcP += fRowBytes, dstP += dstRowBytes) {
+ memcpy(dstP, srcP, rowBytes);
+ }
+
+ return true;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkBitmap::isImmutable() const {
+ return fPixelRef ? fPixelRef->isImmutable() :
+ fFlags & kImageIsImmutable_Flag;
+}
+
+void SkBitmap::setImmutable() {
+ if (fPixelRef) {
+ fPixelRef->setImmutable();
+ } else {
+ fFlags |= kImageIsImmutable_Flag;
+ }
+}
+
+bool SkBitmap::isOpaque() const {
+ switch (fConfig) {
+ case kNo_Config:
+ return true;
+
+ case kA1_Config:
+ case kA8_Config:
+ case kARGB_4444_Config:
+ case kARGB_8888_Config:
+ return (fFlags & kImageIsOpaque_Flag) != 0;
+
+ case kIndex8_Config: {
+ uint32_t flags = 0;
+
+ this->lockPixels();
+ // if lockPixels failed, we may not have a ctable ptr
+ if (fColorTable) {
+ flags = fColorTable->getFlags();
+ }
+ this->unlockPixels();
+
+ return (flags & SkColorTable::kColorsAreOpaque_Flag) != 0;
+ }
+
+ case kRGB_565_Config:
+ return true;
+
+ default:
+ SkDEBUGFAIL("unknown bitmap config pased to isOpaque");
+ return false;
+ }
+}
+
+void SkBitmap::setIsOpaque(bool isOpaque) {
+ /* we record this regardless of fConfig, though it is ignored in
+ isOpaque() for configs that can't support per-pixel alpha.
+ */
+ if (isOpaque) {
+ fFlags |= kImageIsOpaque_Flag;
+ } else {
+ fFlags &= ~kImageIsOpaque_Flag;
+ }
+}
+
+bool SkBitmap::isVolatile() const {
+ return (fFlags & kImageIsVolatile_Flag) != 0;
+}
+
+void SkBitmap::setIsVolatile(bool isVolatile) {
+ if (isVolatile) {
+ fFlags |= kImageIsVolatile_Flag;
+ } else {
+ fFlags &= ~kImageIsVolatile_Flag;
+ }
+}
+
+void* SkBitmap::getAddr(int x, int y) const {
+ SkASSERT((unsigned)x < (unsigned)this->width());
+ SkASSERT((unsigned)y < (unsigned)this->height());
+
+ char* base = (char*)this->getPixels();
+ if (base) {
+ base += y * this->rowBytes();
+ switch (this->config()) {
+ case SkBitmap::kARGB_8888_Config:
+ base += x << 2;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ case SkBitmap::kRGB_565_Config:
+ base += x << 1;
+ break;
+ case SkBitmap::kA8_Config:
+ case SkBitmap::kIndex8_Config:
+ base += x;
+ break;
+ case SkBitmap::kA1_Config:
+ base += x >> 3;
+ break;
+ default:
+ SkDEBUGFAIL("Can't return addr for config");
+ base = NULL;
+ break;
+ }
+ }
+ return base;
+}
+
+SkColor SkBitmap::getColor(int x, int y) const {
+ SkASSERT((unsigned)x < (unsigned)this->width());
+ SkASSERT((unsigned)y < (unsigned)this->height());
+
+ switch (this->config()) {
+ case SkBitmap::kA1_Config: {
+ uint8_t* addr = this->getAddr1(x, y);
+ uint8_t mask = 1 << (7 - (x % 8));
+ if (addr[0] & mask) {
+ return SK_ColorBLACK;
+ } else {
+ return 0;
+ }
+ }
+ case SkBitmap::kA8_Config: {
+ uint8_t* addr = this->getAddr8(x, y);
+ return SkColorSetA(0, addr[0]);
+ }
+ case SkBitmap::kIndex8_Config: {
+ SkPMColor c = this->getIndex8Color(x, y);
+ return SkUnPreMultiply::PMColorToColor(c);
+ }
+ case SkBitmap::kRGB_565_Config: {
+ uint16_t* addr = this->getAddr16(x, y);
+ return SkPixel16ToColor(addr[0]);
+ }
+ case SkBitmap::kARGB_4444_Config: {
+ uint16_t* addr = this->getAddr16(x, y);
+ SkPMColor c = SkPixel4444ToPixel32(addr[0]);
+ return SkUnPreMultiply::PMColorToColor(c);
+ }
+ case SkBitmap::kARGB_8888_Config: {
+ uint32_t* addr = this->getAddr32(x, y);
+ return SkUnPreMultiply::PMColorToColor(addr[0]);
+ }
+ case kNo_Config:
+ SkASSERT(false);
+ return 0;
+ }
+ SkASSERT(false); // Not reached.
+ 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 SkBitmap::kIndex8_Config: {
+ SkAutoLockColors alc(bm);
+ const SkPMColor* table = alc.colors();
+ if (!table) {
+ return false;
+ }
+ SkPMColor c = (SkPMColor)~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 = (SkPMColor)~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;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static uint16_t pack_8888_to_4444(unsigned a, unsigned r, unsigned g, unsigned b) {
+ unsigned pixel = (SkA32To4444(a) << SK_A4444_SHIFT) |
+ (SkR32To4444(r) << SK_R4444_SHIFT) |
+ (SkG32To4444(g) << SK_G4444_SHIFT) |
+ (SkB32To4444(b) << SK_B4444_SHIFT);
+ return SkToU16(pixel);
+}
+
+void SkBitmap::internalErase(const SkIRect& area,
+ U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
+#ifdef SK_DEBUG
+ SkDEBUGCODE(this->validate();)
+ SkASSERT(!area.isEmpty());
+ {
+ SkIRect total = { 0, 0, this->width(), this->height() };
+ SkASSERT(total.contains(area));
+ }
+#endif
+
+ if (kNo_Config == fConfig || kIndex8_Config == fConfig) {
+ return;
+ }
+
+ SkAutoLockPixels alp(*this);
+ // perform this check after the lock call
+ if (!this->readyToDraw()) {
+ return;
+ }
+
+ int height = area.height();
+ const int width = area.width();
+ const int rowBytes = fRowBytes;
+
+ // make rgb premultiplied
+ if (255 != a) {
+ r = SkAlphaMul(r, a);
+ g = SkAlphaMul(g, a);
+ b = SkAlphaMul(b, a);
+ }
+
+ switch (fConfig) {
+ case kA1_Config: {
+ uint8_t* p = this->getAddr1(area.fLeft, area.fTop);
+ const int left = area.fLeft >> 3;
+ const int right = area.fRight >> 3;
+
+ int middle = right - left - 1;
+
+ uint8_t leftMask = 0xFF >> (area.fLeft & 7);
+ uint8_t rightMask = ~(0xFF >> (area.fRight & 7));
+ if (left == right) {
+ leftMask &= rightMask;
+ rightMask = 0;
+ }
+
+ a = (a >> 7) ? 0xFF : 0;
+ while (--height >= 0) {
+ uint8_t* startP = p;
+
+ *p = (*p & ~leftMask) | (a & leftMask);
+ p++;
+ if (middle > 0) {
+ memset(p, a, middle);
+ p += middle;
+ }
+ if (rightMask) {
+ *p = (*p & ~rightMask) | (a & rightMask);
+ }
+
+ p = startP + rowBytes;
+ }
+ break;
+ }
+ case kA8_Config: {
+ uint8_t* p = this->getAddr8(area.fLeft, area.fTop);
+ while (--height >= 0) {
+ memset(p, a, width);
+ p += rowBytes;
+ }
+ break;
+ }
+ case kARGB_4444_Config:
+ case kRGB_565_Config: {
+ uint16_t* p = this->getAddr16(area.fLeft, area.fTop);;
+ uint16_t v;
+
+ if (kARGB_4444_Config == fConfig) {
+ v = pack_8888_to_4444(a, r, g, b);
+ } else {
+ v = SkPackRGB16(r >> (8 - SK_R16_BITS),
+ g >> (8 - SK_G16_BITS),
+ b >> (8 - SK_B16_BITS));
+ }
+ while (--height >= 0) {
+ sk_memset16(p, v, width);
+ p = (uint16_t*)((char*)p + rowBytes);
+ }
+ break;
+ }
+ case kARGB_8888_Config: {
+ uint32_t* p = this->getAddr32(area.fLeft, area.fTop);
+ uint32_t v = SkPackARGB32(a, r, g, b);
+
+ while (--height >= 0) {
+ sk_memset32(p, v, width);
+ p = (uint32_t*)((char*)p + rowBytes);
+ }
+ break;
+ }
+ }
+
+ this->notifyPixelsChanged();
+}
+
+void SkBitmap::eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const {
+ SkIRect area = { 0, 0, this->width(), this->height() };
+ if (!area.isEmpty()) {
+ this->internalErase(area, a, r, g, b);
+ }
+}
+
+void SkBitmap::eraseArea(const SkIRect& rect, SkColor c) const {
+ SkIRect area = { 0, 0, this->width(), this->height() };
+ if (area.intersect(rect)) {
+ this->internalErase(area, SkColorGetA(c), SkColorGetR(c),
+ SkColorGetG(c), SkColorGetB(c));
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////
+
+#define SUB_OFFSET_FAILURE ((size_t)-1)
+
+/**
+ * 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.
+ */
+static size_t get_sub_offset(const SkBitmap& bm, int x, int y) {
+ switch (bm.getConfig()) {
+ case SkBitmap::kA8_Config:
+ case SkBitmap:: kIndex8_Config:
+ // x is fine as is for the calculation
+ break;
+
+ case SkBitmap::kRGB_565_Config:
+ case SkBitmap::kARGB_4444_Config:
+ x <<= 1;
+ break;
+
+ case SkBitmap::kARGB_8888_Config:
+ x <<= 2;
+ break;
+
+ case SkBitmap::kNo_Config:
+ case SkBitmap::kA1_Config:
+ default:
+ return SUB_OFFSET_FAILURE;
+ }
+ 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 get_upper_left_from_offset(SkBitmap::Config config, size_t offset, size_t rowBytes,
+ int32_t* x, int32_t* y);
+bool get_upper_left_from_offset(SkBitmap::Config config, size_t offset, size_t rowBytes,
+ int32_t* x, int32_t* y) {
+ SkASSERT(x != NULL && y != NULL);
+ if (0 == offset) {
+ *x = *y = 0;
+ return true;
+ }
+ // Use integer division to find the correct y position.
+ *y = SkToS32(offset / rowBytes);
+ // The remainder will be the x position, after we reverse get_sub_offset.
+ *x = SkToS32(offset % rowBytes);
+ switch (config) {
+ 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;
+}
+
+static bool get_upper_left_from_offset(const SkBitmap& bm, int32_t* x, int32_t* y) {
+ return get_upper_left_from_offset(bm.config(), bm.pixelRefOffset(), bm.rowBytes(), x, y);
+}
+
+bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (NULL == result || NULL == fPixelRef) {
+ return false; // no src pixels
+ }
+
+ SkIRect srcRect, r;
+ srcRect.set(0, 0, this->width(), this->height());
+ if (!r.intersect(srcRect, subset)) {
+ 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 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 = get_sub_offset(*this, r.fLeft, r.fTop);
+ if (SUB_OFFSET_FAILURE == offset) {
+ return false; // config not supported
+ }
+
+ SkBitmap dst;
+ dst.setConfig(this->config(), r.width(), r.height(), this->rowBytes());
+ dst.setIsVolatile(this->isVolatile());
+ dst.setIsOpaque(this->isOpaque());
+
+ if (fPixelRef) {
+ // share the pixelref with a custom offset
+ dst.setPixelRef(fPixelRef, fPixelRefOffset + offset);
+ }
+ SkDEBUGCODE(dst.validate();)
+
+ // we know we're good, so commit to result
+ result->swap(dst);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+bool SkBitmap::canCopyTo(Config dstConfig) const {
+ if (this->getConfig() == kNo_Config) {
+ return false;
+ }
+
+ bool sameConfigs = (this->config() == dstConfig);
+ switch (dstConfig) {
+ case kA8_Config:
+ case kRGB_565_Config:
+ case kARGB_8888_Config:
+ break;
+ case kA1_Config:
+ case kIndex8_Config:
+ if (!sameConfigs) {
+ return false;
+ }
+ break;
+ case kARGB_4444_Config:
+ return sameConfigs || kARGB_8888_Config == this->config();
+ default:
+ return false;
+ }
+
+ // do not copy src if srcConfig == kA1_Config while dstConfig != kA1_Config
+ if (this->getConfig() == kA1_Config && !sameConfigs) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SkBitmap::copyTo(SkBitmap* dst, Config dstConfig, Allocator* alloc) const {
+ if (!this->canCopyTo(dstConfig)) {
+ return false;
+ }
+
+ // if we have a texture, first get those pixels
+ SkBitmap tmpSrc;
+ const SkBitmap* src = this;
+
+ if (fPixelRef) {
+ SkIRect subset;
+ if (get_upper_left_from_offset(*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;
+ }
+
+ // fall through to the raster case
+ src = &tmpSrc;
+ }
+ }
+ }
+
+ // we lock this now, since we may need its colortable
+ SkAutoLockPixels srclock(*src);
+ if (!src->readyToDraw()) {
+ return false;
+ }
+
+ SkBitmap tmpDst;
+ tmpDst.setConfig(dstConfig, src->width(), src->height());
+
+ // allocate colortable if srcConfig == kIndex8_Config
+ SkColorTable* ctable = (dstConfig == kIndex8_Config) ?
+ new SkColorTable(*src->getColorTable()) : NULL;
+ SkAutoUnref au(ctable);
+ if (!tmpDst.allocPixels(alloc, ctable)) {
+ return false;
+ }
+
+ if (!tmpDst.readyToDraw()) {
+ // allocator/lock failed
+ return false;
+ }
+
+ /* do memcpy for the same configs cases, else use drawing
+ */
+ if (src->config() == dstConfig) {
+ if (tmpDst.getSize() == src->getSize()) {
+ memcpy(tmpDst.getPixels(), src->getPixels(), src->getSafeSize());
+ SkPixelRef* pixelRef = tmpDst.pixelRef();
+ if (pixelRef != NULL) {
+ pixelRef->fGenerationID = this->getGenerationID();
+ }
+ } else {
+ const char* srcP = reinterpret_cast<const char*>(src->getPixels());
+ char* dstP = reinterpret_cast<char*>(tmpDst.getPixels());
+ // to be sure we don't read too much, only copy our logical pixels
+ size_t bytesToCopy = tmpDst.width() * tmpDst.bytesPerPixel();
+ for (int y = 0; y < tmpDst.height(); y++) {
+ memcpy(dstP, srcP, bytesToCopy);
+ srcP += src->rowBytes();
+ dstP += tmpDst.rowBytes();
+ }
+ }
+ } else if (SkBitmap::kARGB_4444_Config == dstConfig
+ && SkBitmap::kARGB_8888_Config == src->config()) {
+ SkASSERT(src->height() == tmpDst.height());
+ SkASSERT(src->width() == tmpDst.width());
+ for (int y = 0; y < src->height(); ++y) {
+ SkPMColor16* SK_RESTRICT dstRow = (SkPMColor16*) tmpDst.getAddr16(0, y);
+ SkPMColor* SK_RESTRICT srcRow = (SkPMColor*) src->getAddr32(0, y);
+ DITHER_4444_SCAN(y);
+ for (int x = 0; x < src->width(); ++x) {
+ dstRow[x] = SkDitherARGB32To4444(srcRow[x],
+ DITHER_VALUE(x));
+ }
+ }
+ } else {
+ // if the src has alpha, we have to clear the dst first
+ if (!src->isOpaque()) {
+ tmpDst.eraseColor(SK_ColorTRANSPARENT);
+ }
+
+ SkCanvas canvas(tmpDst);
+ SkPaint paint;
+
+ paint.setDither(true);
+ canvas.drawBitmap(*src, 0, 0, &paint);
+ }
+
+ tmpDst.setIsOpaque(src->isOpaque());
+
+ dst->swap(tmpDst);
+ return true;
+}
+
+bool SkBitmap::deepCopyTo(SkBitmap* dst, Config dstConfig) const {
+ if (!this->canCopyTo(dstConfig)) {
+ return false;
+ }
+
+ // If we have a PixelRef, and it supports deep copy, use it.
+ // Currently supported only by texture-backed bitmaps.
+ 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.
+ int32_t x, y;
+ if (!get_upper_left_from_offset(*this, &x, &y)) {
+ return false;
+ }
+ pixelRefOffset = get_sub_offset(*dst, x, y);
+ if (SUB_OFFSET_FAILURE == pixelRefOffset) {
+ return false;
+ }
+ }
+ dst->setPixelRef(pixelRef, pixelRefOffset)->unref();
+ return true;
+ }
+ }
+
+ if (this->getTexture()) {
+ return false;
+ } else {
+ return this->copyTo(dst, dstConfig, NULL);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void downsampleby2_proc32(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const SkPMColor* p = src.getAddr32(x, y);
+ const SkPMColor* baseP = p;
+ SkPMColor c, ag, rb;
+
+ c = *p; ag = (c >> 8) & 0xFF00FF; rb = c & 0xFF00FF;
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 2;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+
+ *dst->getAddr32(x >> 1, y >> 1) =
+ ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00);
+}
+
+static inline uint32_t expand16(U16CPU c) {
+ return (c & ~SK_G16_MASK_IN_PLACE) | ((c & SK_G16_MASK_IN_PLACE) << 16);
+}
+
+// returns dirt in the top 16bits, but we don't care, since we only
+// store the low 16bits.
+static inline U16CPU pack16(uint32_t c) {
+ return (c & ~SK_G16_MASK_IN_PLACE) | ((c >> 16) & SK_G16_MASK_IN_PLACE);
+}
+
+static void downsampleby2_proc16(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const uint16_t* p = src.getAddr16(x, y);
+ const uint16_t* baseP = p;
+ SkPMColor c;
+
+ c = expand16(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand16(*p);
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 1;
+ }
+ c += expand16(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand16(*p);
+
+ *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)pack16(c >> 2);
+}
+
+static uint32_t expand4444(U16CPU c) {
+ return (c & 0xF0F) | ((c & ~0xF0F) << 12);
+}
+
+static U16CPU collaps4444(uint32_t c) {
+ return (c & 0xF0F) | ((c >> 12) & ~0xF0F);
+}
+
+static void downsampleby2_proc4444(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const uint16_t* p = src.getAddr16(x, y);
+ const uint16_t* baseP = p;
+ uint32_t c;
+
+ c = expand4444(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand4444(*p);
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 1;
+ }
+ c += expand4444(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand4444(*p);
+
+ *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)collaps4444(c >> 2);
+}
+
+void SkBitmap::buildMipMap(bool forceRebuild) {
+ if (forceRebuild)
+ this->freeMipMap();
+ else if (fMipMap)
+ return; // we're already built
+
+ SkASSERT(NULL == fMipMap);
+
+ void (*proc)(SkBitmap* dst, int x, int y, const SkBitmap& src);
+
+ const SkBitmap::Config config = this->getConfig();
+
+ switch (config) {
+ case kARGB_8888_Config:
+ proc = downsampleby2_proc32;
+ break;
+ case kRGB_565_Config:
+ proc = downsampleby2_proc16;
+ break;
+ case kARGB_4444_Config:
+ proc = downsampleby2_proc4444;
+ break;
+ case kIndex8_Config:
+ case kA8_Config:
+ default:
+ return; // don't build mipmaps for these configs
+ }
+
+ SkAutoLockPixels alp(*this);
+ if (!this->readyToDraw()) {
+ return;
+ }
+
+ // whip through our loop to compute the exact size needed
+ size_t size = 0;
+ int maxLevels = 0;
+ {
+ int width = this->width();
+ int height = this->height();
+ for (;;) {
+ width >>= 1;
+ height >>= 1;
+ if (0 == width || 0 == height) {
+ break;
+ }
+ size += ComputeRowBytes(config, width) * height;
+ maxLevels += 1;
+ }
+ }
+
+ // nothing to build
+ if (0 == maxLevels) {
+ return;
+ }
+
+ SkBitmap srcBM(*this);
+ srcBM.lockPixels();
+ if (!srcBM.readyToDraw()) {
+ return;
+ }
+
+ MipMap* mm = MipMap::Alloc(maxLevels, size);
+ if (NULL == mm) {
+ return;
+ }
+
+ MipLevel* level = mm->levels();
+ uint8_t* addr = (uint8_t*)mm->pixels();
+ int width = this->width();
+ int height = this->height();
+ uint32_t rowBytes;
+ SkBitmap dstBM;
+
+ for (int i = 0; i < maxLevels; i++) {
+ width >>= 1;
+ height >>= 1;
+ rowBytes = SkToU32(ComputeRowBytes(config, width));
+
+ level[i].fPixels = addr;
+ level[i].fWidth = width;
+ level[i].fHeight = height;
+ level[i].fRowBytes = rowBytes;
+
+ dstBM.setConfig(config, width, height, rowBytes);
+ dstBM.setPixels(addr);
+
+ srcBM.lockPixels();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ proc(&dstBM, x, y, srcBM);
+ }
+ }
+ srcBM.unlockPixels();
+
+ srcBM = dstBM;
+ addr += height * rowBytes;
+ }
+ SkASSERT(addr == (uint8_t*)mm->pixels() + size);
+ fMipMap = mm;
+}
+
+bool SkBitmap::hasMipMap() const {
+ return fMipMap != NULL;
+}
+
+int SkBitmap::extractMipLevel(SkBitmap* dst, SkFixed sx, SkFixed sy) {
+ if (NULL == fMipMap) {
+ return 0;
+ }
+
+ int level = ComputeMipLevel(sx, sy) >> 16;
+ SkASSERT(level >= 0);
+ if (level <= 0) {
+ return 0;
+ }
+
+ if (level >= fMipMap->fLevelCount) {
+ level = fMipMap->fLevelCount - 1;
+ }
+ if (dst) {
+ const MipLevel& mip = fMipMap->levels()[level - 1];
+ dst->setConfig((SkBitmap::Config)this->config(),
+ mip.fWidth, mip.fHeight, mip.fRowBytes);
+ dst->setPixels(mip.fPixels);
+ }
+ return level;
+}
+
+SkFixed SkBitmap::ComputeMipLevel(SkFixed sx, SkFixed sy) {
+ sx = SkAbs32(sx);
+ sy = SkAbs32(sy);
+ if (sx < sy) {
+ sx = sy;
+ }
+ if (sx < SK_Fixed1) {
+ return 0;
+ }
+ int clz = SkCLZ(sx);
+ SkASSERT(clz >= 1 && clz <= 15);
+ return SkIntToFixed(15 - clz) + ((unsigned)(sx << (clz + 1)) >> 16);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool GetBitmapAlpha(const SkBitmap& src, uint8_t* SK_RESTRICT alpha,
+ int alphaRowBytes) {
+ SkASSERT(alpha != NULL);
+ SkASSERT(alphaRowBytes >= src.width());
+
+ SkBitmap::Config config = src.getConfig();
+ int w = src.width();
+ int h = src.height();
+ size_t rb = src.rowBytes();
+
+ SkAutoLockPixels alp(src);
+ if (!src.readyToDraw()) {
+ // zero out the alpha buffer and return
+ while (--h >= 0) {
+ memset(alpha, 0, w);
+ alpha += alphaRowBytes;
+ }
+ return false;
+ }
+
+ if (SkBitmap::kA8_Config == config && !src.isOpaque()) {
+ const uint8_t* s = src.getAddr8(0, 0);
+ while (--h >= 0) {
+ memcpy(alpha, s, w);
+ s += rb;
+ alpha += alphaRowBytes;
+ }
+ } else if (SkBitmap::kARGB_8888_Config == config && !src.isOpaque()) {
+ const SkPMColor* SK_RESTRICT s = src.getAddr32(0, 0);
+ while (--h >= 0) {
+ for (int x = 0; x < w; x++) {
+ alpha[x] = SkGetPackedA32(s[x]);
+ }
+ s = (const SkPMColor*)((const char*)s + rb);
+ alpha += alphaRowBytes;
+ }
+ } else if (SkBitmap::kARGB_4444_Config == config && !src.isOpaque()) {
+ const SkPMColor16* SK_RESTRICT s = src.getAddr16(0, 0);
+ while (--h >= 0) {
+ for (int x = 0; x < w; x++) {
+ alpha[x] = SkPacked4444ToA32(s[x]);
+ }
+ s = (const SkPMColor16*)((const char*)s + rb);
+ alpha += alphaRowBytes;
+ }
+ } else if (SkBitmap::kIndex8_Config == config && !src.isOpaque()) {
+ SkColorTable* ct = src.getColorTable();
+ if (ct) {
+ const SkPMColor* SK_RESTRICT table = ct->lockColors();
+ const uint8_t* SK_RESTRICT s = src.getAddr8(0, 0);
+ while (--h >= 0) {
+ for (int x = 0; x < w; x++) {
+ alpha[x] = SkGetPackedA32(table[s[x]]);
+ }
+ s += rb;
+ alpha += alphaRowBytes;
+ }
+ ct->unlockColors(false);
+ }
+ } else { // src is opaque, so just fill alpha[] with 0xFF
+ memset(alpha, 0xFF, h * alphaRowBytes);
+ }
+ return true;
+}
+
+#include "SkPaint.h"
+#include "SkMaskFilter.h"
+#include "SkMatrix.h"
+
+bool SkBitmap::extractAlpha(SkBitmap* dst, const SkPaint* paint,
+ Allocator *allocator, SkIPoint* offset) const {
+ SkDEBUGCODE(this->validate();)
+
+ SkBitmap tmpBitmap;
+ SkMatrix identity;
+ SkMask srcM, dstM;
+
+ srcM.fBounds.set(0, 0, this->width(), this->height());
+ srcM.fRowBytes = SkAlign4(this->width());
+ srcM.fFormat = SkMask::kA8_Format;
+
+ SkMaskFilter* filter = paint ? paint->getMaskFilter() : NULL;
+
+ // compute our (larger?) dst bounds if we have a filter
+ if (NULL != filter) {
+ identity.reset();
+ srcM.fImage = NULL;
+ if (!filter->filterMask(&dstM, srcM, identity, NULL)) {
+ goto NO_FILTER_CASE;
+ }
+ dstM.fRowBytes = SkAlign4(dstM.fBounds.width());
+ } else {
+ NO_FILTER_CASE:
+ tmpBitmap.setConfig(SkBitmap::kA8_Config, this->width(), this->height(),
+ srcM.fRowBytes);
+ if (!tmpBitmap.allocPixels(allocator, NULL)) {
+ // Allocation of pixels for alpha bitmap failed.
+ SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n",
+ tmpBitmap.width(), tmpBitmap.height());
+ return false;
+ }
+ GetBitmapAlpha(*this, tmpBitmap.getAddr8(0, 0), srcM.fRowBytes);
+ if (offset) {
+ offset->set(0, 0);
+ }
+ tmpBitmap.swap(*dst);
+ return true;
+ }
+ srcM.fImage = SkMask::AllocImage(srcM.computeImageSize());
+ SkAutoMaskFreeImage srcCleanup(srcM.fImage);
+
+ GetBitmapAlpha(*this, srcM.fImage, srcM.fRowBytes);
+ if (!filter->filterMask(&dstM, srcM, identity, NULL)) {
+ goto NO_FILTER_CASE;
+ }
+ SkAutoMaskFreeImage dstCleanup(dstM.fImage);
+
+ tmpBitmap.setConfig(SkBitmap::kA8_Config, dstM.fBounds.width(),
+ dstM.fBounds.height(), dstM.fRowBytes);
+ if (!tmpBitmap.allocPixels(allocator, NULL)) {
+ // Allocation of pixels for alpha bitmap failed.
+ SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n",
+ tmpBitmap.width(), tmpBitmap.height());
+ return false;
+ }
+ memcpy(tmpBitmap.getPixels(), dstM.fImage, dstM.computeImageSize());
+ if (offset) {
+ offset->set(dstM.fBounds.fLeft, dstM.fBounds.fTop);
+ }
+ SkDEBUGCODE(tmpBitmap.validate();)
+
+ tmpBitmap.swap(*dst);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum {
+ SERIALIZE_PIXELTYPE_NONE,
+ SERIALIZE_PIXELTYPE_REF_DATA
+};
+
+void SkBitmap::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeInt(fWidth);
+ buffer.writeInt(fHeight);
+ buffer.writeInt(fRowBytes);
+ buffer.writeInt(fConfig);
+ buffer.writeBool(this->isOpaque());
+
+ if (fPixelRef) {
+ if (fPixelRef->getFactory()) {
+ buffer.writeInt(SERIALIZE_PIXELTYPE_REF_DATA);
+ buffer.writeUInt(SkToU32(fPixelRefOffset));
+ buffer.writeFlattenable(fPixelRef);
+ return;
+ }
+ // if we get here, we can't record the pixels
+ buffer.writeInt(SERIALIZE_PIXELTYPE_NONE);
+ } else {
+ buffer.writeInt(SERIALIZE_PIXELTYPE_NONE);
+ }
+}
+
+void SkBitmap::unflatten(SkFlattenableReadBuffer& buffer) {
+ this->reset();
+
+ int width = buffer.readInt();
+ int height = buffer.readInt();
+ int rowBytes = buffer.readInt();
+ int config = buffer.readInt();
+
+ this->setConfig((Config)config, width, height, rowBytes);
+ this->setIsOpaque(buffer.readBool());
+
+ int reftype = buffer.readInt();
+ switch (reftype) {
+ case SERIALIZE_PIXELTYPE_REF_DATA: {
+ size_t offset = buffer.readUInt();
+ SkPixelRef* pr = buffer.readFlattenableT<SkPixelRef>();
+ SkSafeUnref(this->setPixelRef(pr, offset));
+ break;
+ }
+ case SERIALIZE_PIXELTYPE_NONE:
+ break;
+ default:
+ SkDEBUGFAIL("unrecognized pixeltype in serialized data");
+ sk_throw();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBitmap::RLEPixels::RLEPixels(int width, int height) {
+ fHeight = height;
+ fYPtrs = (uint8_t**)sk_malloc_throw(height * sizeof(uint8_t*));
+ sk_bzero(fYPtrs, height * sizeof(uint8_t*));
+}
+
+SkBitmap::RLEPixels::~RLEPixels() {
+ sk_free(fYPtrs);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+void SkBitmap::validate() const {
+ SkASSERT(fConfig < kConfigCount);
+ SkASSERT(fRowBytes >= (unsigned)ComputeRowBytes((Config)fConfig, fWidth));
+ uint8_t allFlags = kImageIsOpaque_Flag | kImageIsVolatile_Flag | kImageIsImmutable_Flag;
+#ifdef SK_BUILD_FOR_ANDROID
+ allFlags |= kHasHardwareMipMap_Flag;
+#endif
+ SkASSERT(fFlags <= allFlags);
+ SkASSERT(fPixelLockCount >= 0);
+ SkASSERT(NULL == fColorTable || (unsigned)fColorTable->getRefCnt() < 10000);
+ SkASSERT((uint8_t)ComputeBytesPerPixel((Config)fConfig) == fBytesPerPixel);
+
+#if 0 // these asserts are not thread-correct, so disable for now
+ if (fPixelRef) {
+ if (fPixelLockCount > 0) {
+ SkASSERT(fPixelRef->isLocked());
+ } else {
+ SkASSERT(NULL == fPixels);
+ SkASSERT(NULL == fColorTable);
+ }
+ }
+#endif
+}
+#endif
+
+#ifdef SK_DEVELOPER
+void SkBitmap::toString(SkString* str) const {
+
+ static const char* gConfigNames[kConfigCount] = {
+ "NONE", "A1", "A8", "INDEX8", "565", "4444", "8888"
+ };
+
+ str->appendf("bitmap: ((%d, %d) %s", this->width(), this->height(),
+ gConfigNames[this->config()]);
+
+ str->append(" (");
+ if (this->isOpaque()) {
+ str->append("opaque");
+ } else {
+ str->append("transparent");
+ }
+ if (this->isImmutable()) {
+ str->append(", immutable");
+ } else {
+ str->append(", not-immutable");
+ }
+ str->append(")");
+
+ SkPixelRef* pr = this->pixelRef();
+ if (NULL == pr) {
+ // show null or the explicit pixel address (rare)
+ str->appendf(" pixels:%p", this->getPixels());
+ } else {
+ const char* uri = pr->getURI();
+ if (NULL != uri) {
+ str->appendf(" uri:\"%s\"", uri);
+ } else {
+ str->appendf(" pixelref:%p", pr);
+ }
+ }
+
+ str->append(")");
+}
+#endif
diff --git a/core/SkBitmapFilter.cpp b/core/SkBitmapFilter.cpp
new file mode 100644
index 00000000..c2f68d48
--- /dev/null
+++ b/core/SkBitmapFilter.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkErrorInternals.h"
+#include "SkConvolver.h"
+#include "SkBitmapProcState.h"
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkConvolver.h"
+#include "SkUnPreMultiply.h"
+#include "SkShader.h"
+#include "SkRTConf.h"
+#include "SkMath.h"
+
+// These are the per-scanline callbacks that are used when we must resort to
+// resampling an image as it is blitted. Typically these are used only when
+// the image is rotated or has some other complex transformation applied.
+// Scaled images will usually be rescaled directly before rasterization.
+
+void highQualityFilter(const SkBitmapProcState& s, int x, int y,
+ SkPMColor* SK_RESTRICT colors, int count) {
+
+ const int maxX = s.fBitmap->width() - 1;
+ const int maxY = s.fBitmap->height() - 1;
+
+ while (count-- > 0) {
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix, SkFloatToScalar(x + 0.5f),
+ SkFloatToScalar(y + 0.5f), &srcPt);
+ srcPt.fX -= SK_ScalarHalf;
+ srcPt.fY -= SK_ScalarHalf;
+
+ SkScalar weight = 0;
+ SkScalar fr = 0, fg = 0, fb = 0, fa = 0;
+
+ int y0 = SkClampMax(SkScalarCeilToInt(srcPt.fY-s.getBitmapFilter()->width()), maxY);
+ int y1 = SkClampMax(SkScalarFloorToInt(srcPt.fY+s.getBitmapFilter()->width()), maxY);
+ int x0 = SkClampMax(SkScalarCeilToInt(srcPt.fX-s.getBitmapFilter()->width()), maxX);
+ int x1 = SkClampMax(SkScalarFloorToInt(srcPt.fX+s.getBitmapFilter()->width()), maxX);
+
+ for (int srcY = y0; srcY <= y1; srcY++) {
+ SkScalar yWeight = s.getBitmapFilter()->lookupScalar((srcPt.fY - srcY));
+
+ for (int srcX = x0; srcX <= x1 ; srcX++) {
+ SkScalar xWeight = s.getBitmapFilter()->lookupScalar((srcPt.fX - srcX));
+
+ SkScalar combined_weight = SkScalarMul(xWeight, yWeight);
+
+ SkPMColor c = *s.fBitmap->getAddr32(srcX, srcY);
+ fr += combined_weight * SkGetPackedR32(c);
+ fg += combined_weight * SkGetPackedG32(c);
+ fb += combined_weight * SkGetPackedB32(c);
+ fa += combined_weight * SkGetPackedA32(c);
+ weight += combined_weight;
+ }
+ }
+
+ fr = SkScalarDiv(fr, weight);
+ fg = SkScalarDiv(fg, weight);
+ fb = SkScalarDiv(fb, weight);
+ fa = SkScalarDiv(fa, weight);
+
+ int a = SkClampMax(SkScalarRoundToInt(fa), 255);
+ int r = SkClampMax(SkScalarRoundToInt(fr), a);
+ int g = SkClampMax(SkScalarRoundToInt(fg), a);
+ int b = SkClampMax(SkScalarRoundToInt(fb), a);
+
+ *colors++ = SkPackARGB32(a, r, g, b);
+
+ x++;
+ }
+}
+
+SK_CONF_DECLARE(const char *, c_bitmapFilter, "bitmap.filter", "mitchell", "Which scanline bitmap filter to use [mitchell, lanczos, hamming, gaussian, triangle, box]");
+
+SkBitmapFilter *SkBitmapFilter::Allocate() {
+ if (!strcmp(c_bitmapFilter, "mitchell")) {
+ return SkNEW_ARGS(SkMitchellFilter,(1.f/3.f,1.f/3.f));
+ } else if (!strcmp(c_bitmapFilter, "lanczos")) {
+ return SkNEW(SkLanczosFilter);
+ } else if (!strcmp(c_bitmapFilter, "hamming")) {
+ return SkNEW(SkHammingFilter);
+ } else if (!strcmp(c_bitmapFilter, "gaussian")) {
+ return SkNEW_ARGS(SkGaussianFilter,(2));
+ } else if (!strcmp(c_bitmapFilter, "triangle")) {
+ return SkNEW(SkTriangleFilter);
+ } else if (!strcmp(c_bitmapFilter, "box")) {
+ return SkNEW(SkBoxFilter);
+ } else {
+ SkASSERT(!!!"Unknown filter type");
+ }
+
+ return NULL;
+}
+
+SkBitmapProcState::ShaderProc32
+SkBitmapProcState::chooseBitmapFilterProc() {
+
+ if (fFilterLevel != SkPaint::kHigh_FilterLevel) {
+ return NULL;
+ }
+
+ if (fAlphaScale != 256) {
+ return NULL;
+ }
+
+ // TODO: consider supporting other configs (e.g. 565, A8)
+ if (fBitmap->config() != SkBitmap::kARGB_8888_Config) {
+ return NULL;
+ }
+
+ // TODO: consider supporting repeat and mirror
+ if (SkShader::kClamp_TileMode != fTileModeX || SkShader::kClamp_TileMode != fTileModeY) {
+ return NULL;
+ }
+
+ if (fInvType & (SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask)) {
+ fBitmapFilter = SkBitmapFilter::Allocate();
+ }
+
+ if (fInvType & SkMatrix::kScale_Mask) {
+ return highQualityFilter;
+ } else {
+ return NULL;
+ }
+}
diff --git a/core/SkBitmapFilter.h b/core/SkBitmapFilter.h
new file mode 100644
index 00000000..0b8483a1
--- /dev/null
+++ b/core/SkBitmapFilter.h
@@ -0,0 +1,175 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkBitmapFilter_DEFINED
+#define SkBitmapFilter_DEFINED
+
+#include "SkMath.h"
+
+// size of the precomputed bitmap filter tables for high quality filtering.
+// Used to precompute the shape of the filter kernel.
+// Table size chosen from experiments to see where I could start to see a difference.
+
+#define SKBITMAP_FILTER_TABLE_SIZE 128
+
+class SkBitmapFilter {
+ public:
+ SkBitmapFilter(float width)
+ : fWidth(width), fInvWidth(1.f/width) {
+ fPrecomputed = false;
+ fLookupMultiplier = this->invWidth() * (SKBITMAP_FILTER_TABLE_SIZE-1);
+ }
+
+ SkFixed lookup(float x) const {
+ if (!fPrecomputed) {
+ precomputeTable();
+ }
+ int filter_idx = int(sk_float_abs(x * fLookupMultiplier));
+ SkASSERT(filter_idx < SKBITMAP_FILTER_TABLE_SIZE);
+ return fFilterTable[filter_idx];
+ }
+
+ SkScalar lookupScalar(float x) const {
+ if (!fPrecomputed) {
+ precomputeTable();
+ }
+ int filter_idx = int(sk_float_abs(x * fLookupMultiplier));
+ SkASSERT(filter_idx < SKBITMAP_FILTER_TABLE_SIZE);
+ return fFilterTableScalar[filter_idx];
+ }
+
+ float width() const { return fWidth; }
+ float invWidth() const { return fInvWidth; }
+ virtual float evaluate(float x) const = 0;
+ virtual ~SkBitmapFilter() {}
+
+ static SkBitmapFilter* Allocate();
+ protected:
+ float fWidth;
+ float fInvWidth;
+
+ float fLookupMultiplier;
+
+ mutable bool fPrecomputed;
+ mutable SkFixed fFilterTable[SKBITMAP_FILTER_TABLE_SIZE];
+ mutable SkScalar fFilterTableScalar[SKBITMAP_FILTER_TABLE_SIZE];
+ private:
+ void precomputeTable() const {
+ fPrecomputed = true;
+ SkFixed *ftp = fFilterTable;
+ SkScalar *ftpScalar = fFilterTableScalar;
+ for (int x = 0; x < SKBITMAP_FILTER_TABLE_SIZE; ++x) {
+ float fx = ((float)x + .5f) * this->width() / SKBITMAP_FILTER_TABLE_SIZE;
+ float filter_value = evaluate(fx);
+ *ftpScalar++ = SkFloatToScalar(filter_value);
+ *ftp++ = SkFloatToFixed(filter_value);
+ }
+ }
+};
+
+class SkMitchellFilter: public SkBitmapFilter {
+ public:
+ SkMitchellFilter(float b, float c, float width=2.0f)
+ : SkBitmapFilter(width), B(b), C(c) {
+ }
+
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ x = fabsf(x);
+ if (x > 2.f) {
+ return 0;
+ } else if (x > 1.f) {
+ return ((-B - 6*C) * x*x*x + (6*B + 30*C) * x*x +
+ (-12*B - 48*C) * x + (8*B + 24*C)) * (1.f/6.f);
+ } else {
+ return ((12 - 9*B - 6*C) * x*x*x +
+ (-18 + 12*B + 6*C) * x*x +
+ (6 - 2*B)) * (1.f/6.f);
+ }
+ }
+ protected:
+ float B, C;
+};
+
+class SkGaussianFilter: public SkBitmapFilter {
+ public:
+ SkGaussianFilter(float a, float width=2.0f)
+ : SkBitmapFilter(width), alpha(a), expWidth(expf(-alpha * width * width)) {
+ }
+
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ return SkTMax(0.f, float(expf(-alpha*x*x) - expWidth));
+ }
+ protected:
+ float alpha, expWidth;
+};
+
+class SkTriangleFilter: public SkBitmapFilter {
+ public:
+ SkTriangleFilter(float width=1)
+ : SkBitmapFilter(width) {
+ }
+
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ return SkTMax(0.f, fWidth - fabsf(x));
+ }
+ protected:
+};
+
+class SkBoxFilter: public SkBitmapFilter {
+ public:
+ SkBoxFilter(float width=0.5f)
+ : SkBitmapFilter(width) {
+ }
+
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ return (x >= -fWidth && x < fWidth) ? 1.0f : 0.0f;
+ }
+ protected:
+};
+
+class SkHammingFilter: public SkBitmapFilter {
+public:
+ SkHammingFilter(float width=1.f)
+ : SkBitmapFilter(width) {
+ }
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ if (x <= -fWidth || x >= fWidth) {
+ return 0.0f; // Outside of the window.
+ }
+ if (x > -FLT_EPSILON && x < FLT_EPSILON) {
+ return 1.0f; // Special case the sinc discontinuity at the origin.
+ }
+ const float xpi = x * static_cast<float>(SK_ScalarPI);
+
+ return ((sk_float_sin(xpi) / xpi) * // sinc(x)
+ (0.54f + 0.46f * sk_float_cos(xpi / fWidth))); // hamming(x)
+ }
+};
+
+class SkLanczosFilter: public SkBitmapFilter {
+ public:
+ SkLanczosFilter(float width=3.f)
+ : SkBitmapFilter(width) {
+ }
+
+ virtual float evaluate(float x) const SK_OVERRIDE {
+ if (x <= -fWidth || x >= fWidth) {
+ return 0.0f; // Outside of the window.
+ }
+ if (x > -FLT_EPSILON && x < FLT_EPSILON) {
+ return 1.0f; // Special case the discontinuity at the origin.
+ }
+ float xpi = x * static_cast<float>(SK_ScalarPI);
+ return (sk_float_sin(xpi) / xpi) * // sinc(x)
+ sk_float_sin(xpi / fWidth) / (xpi / fWidth); // sinc(x/fWidth)
+ }
+};
+
+
+#endif
diff --git a/core/SkBitmapHeap.cpp b/core/SkBitmapHeap.cpp
new file mode 100644
index 00000000..f3428db6
--- /dev/null
+++ b/core/SkBitmapHeap.cpp
@@ -0,0 +1,402 @@
+
+/*
+ * 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 "SkBitmapHeap.h"
+
+#include "SkBitmap.h"
+#include "SkFlattenableBuffers.h"
+#include "SkTSearch.h"
+
+SK_DEFINE_INST_COUNT(SkBitmapHeapReader)
+SK_DEFINE_INST_COUNT(SkBitmapHeap::ExternalStorage)
+
+SkBitmapHeapEntry::SkBitmapHeapEntry()
+ : fSlot(-1)
+ , fRefCount(0)
+ , fBytesAllocated(0) {
+}
+
+SkBitmapHeapEntry::~SkBitmapHeapEntry() {
+ SkASSERT(0 == fRefCount);
+}
+
+void SkBitmapHeapEntry::addReferences(int count) {
+ if (0 == fRefCount) {
+ // If there are no current owners then the heap manager
+ // will be the only one able to modify it, so it does not
+ // need to be an atomic operation.
+ fRefCount = count;
+ } else {
+ sk_atomic_add(&fRefCount, count);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkBitmapHeap::LookupEntry::Less(const SkBitmapHeap::LookupEntry& a,
+ const SkBitmapHeap::LookupEntry& b) {
+ if (a.fGenerationId < b.fGenerationId) {
+ return true;
+ } else if (a.fGenerationId > b.fGenerationId) {
+ return false;
+ } else if (a.fPixelOffset < b.fPixelOffset) {
+ return true;
+ } else if (a.fPixelOffset > b.fPixelOffset) {
+ return false;
+ } else if (a.fWidth < b.fWidth) {
+ return true;
+ } else if (a.fWidth > b.fWidth) {
+ return false;
+ } else if (a.fHeight < b.fHeight) {
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBitmapHeap::SkBitmapHeap(int32_t preferredSize, int32_t ownerCount)
+ : INHERITED()
+ , fExternalStorage(NULL)
+ , fMostRecentlyUsed(NULL)
+ , fLeastRecentlyUsed(NULL)
+ , fPreferredCount(preferredSize)
+ , fOwnerCount(ownerCount)
+ , fBytesAllocated(0)
+ , fDeferAddingOwners(false) {
+}
+
+SkBitmapHeap::SkBitmapHeap(ExternalStorage* storage, int32_t preferredSize)
+ : INHERITED()
+ , fExternalStorage(storage)
+ , fMostRecentlyUsed(NULL)
+ , fLeastRecentlyUsed(NULL)
+ , fPreferredCount(preferredSize)
+ , fOwnerCount(IGNORE_OWNERS)
+ , fBytesAllocated(0)
+ , fDeferAddingOwners(false) {
+ SkSafeRef(storage);
+}
+
+SkBitmapHeap::~SkBitmapHeap() {
+ SkDEBUGCODE(
+ for (int i = 0; i < fStorage.count(); i++) {
+ bool unused = false;
+ for (int j = 0; j < fUnusedSlots.count(); j++) {
+ if (fUnusedSlots[j] == fStorage[i]->fSlot) {
+ unused = true;
+ break;
+ }
+ }
+ if (!unused) {
+ fBytesAllocated -= fStorage[i]->fBytesAllocated;
+ }
+ }
+ fBytesAllocated -= (fStorage.count() * sizeof(SkBitmapHeapEntry));
+ )
+ SkASSERT(0 == fBytesAllocated);
+ fStorage.deleteAll();
+ SkSafeUnref(fExternalStorage);
+ fLookupTable.deleteAll();
+}
+
+SkTRefArray<SkBitmap>* SkBitmapHeap::extractBitmaps() const {
+ const int size = fStorage.count();
+ SkTRefArray<SkBitmap>* array = NULL;
+ if (size > 0) {
+ array = SkTRefArray<SkBitmap>::Create(size);
+ for (int i = 0; i < size; i++) {
+ // make a shallow copy of the bitmap
+ array->writableAt(i) = fStorage[i]->fBitmap;
+ }
+ }
+ return array;
+}
+
+void SkBitmapHeap::removeFromLRU(SkBitmapHeap::LookupEntry* entry) {
+ if (fMostRecentlyUsed == entry) {
+ fMostRecentlyUsed = entry->fLessRecentlyUsed;
+ if (NULL == fMostRecentlyUsed) {
+ SkASSERT(fLeastRecentlyUsed == entry);
+ fLeastRecentlyUsed = NULL;
+ } else {
+ fMostRecentlyUsed->fMoreRecentlyUsed = NULL;
+ }
+ } else {
+ // Remove entry from its prior place, and make sure to cover the hole.
+ if (fLeastRecentlyUsed == entry) {
+ SkASSERT(entry->fMoreRecentlyUsed != NULL);
+ fLeastRecentlyUsed = entry->fMoreRecentlyUsed;
+ }
+ // Since we have already considered the case where entry is the most recently used, it must
+ // have a more recently used at this point.
+ SkASSERT(entry->fMoreRecentlyUsed != NULL);
+ entry->fMoreRecentlyUsed->fLessRecentlyUsed = entry->fLessRecentlyUsed;
+
+ if (entry->fLessRecentlyUsed != NULL) {
+ SkASSERT(fLeastRecentlyUsed != entry);
+ entry->fLessRecentlyUsed->fMoreRecentlyUsed = entry->fMoreRecentlyUsed;
+ }
+ }
+ entry->fMoreRecentlyUsed = NULL;
+}
+
+void SkBitmapHeap::appendToLRU(SkBitmapHeap::LookupEntry* entry) {
+ if (fMostRecentlyUsed != NULL) {
+ SkASSERT(NULL == fMostRecentlyUsed->fMoreRecentlyUsed);
+ fMostRecentlyUsed->fMoreRecentlyUsed = entry;
+ entry->fLessRecentlyUsed = fMostRecentlyUsed;
+ }
+ fMostRecentlyUsed = entry;
+ if (NULL == fLeastRecentlyUsed) {
+ fLeastRecentlyUsed = entry;
+ }
+}
+
+// iterate through our LRU cache and try to find an entry to evict
+SkBitmapHeap::LookupEntry* SkBitmapHeap::findEntryToReplace(const SkBitmap& replacement) {
+ SkASSERT(fPreferredCount != UNLIMITED_SIZE);
+ SkASSERT(fStorage.count() >= fPreferredCount);
+
+ SkBitmapHeap::LookupEntry* iter = fLeastRecentlyUsed;
+ while (iter != NULL) {
+ SkBitmapHeapEntry* heapEntry = fStorage[iter->fStorageSlot];
+ if (heapEntry->fRefCount > 0) {
+ // If the least recently used bitmap has not been unreferenced
+ // by its owner, then according to our LRU specifications a more
+ // recently used one can not have used all its references yet either.
+ return NULL;
+ }
+ if (replacement.getGenerationID() == iter->fGenerationId) {
+ // Do not replace a bitmap with a new one using the same
+ // pixel ref. Instead look for a different one that will
+ // potentially free up more space.
+ iter = iter->fMoreRecentlyUsed;
+ } else {
+ return iter;
+ }
+ }
+ return NULL;
+}
+
+size_t SkBitmapHeap::freeMemoryIfPossible(size_t bytesToFree) {
+ if (UNLIMITED_SIZE == fPreferredCount) {
+ return 0;
+ }
+ LookupEntry* iter = fLeastRecentlyUsed;
+ size_t origBytesAllocated = fBytesAllocated;
+ // Purge starting from LRU until a non-evictable bitmap is found or until
+ // everything is evicted.
+ while (iter != NULL) {
+ SkBitmapHeapEntry* heapEntry = fStorage[iter->fStorageSlot];
+ if (heapEntry->fRefCount > 0) {
+ break;
+ }
+ LookupEntry* next = iter->fMoreRecentlyUsed;
+ this->removeEntryFromLookupTable(iter);
+ // Free the pixel memory. removeEntryFromLookupTable already reduced
+ // fBytesAllocated properly.
+ heapEntry->fBitmap.reset();
+ // Add to list of unused slots which can be reused in the future.
+ fUnusedSlots.push(heapEntry->fSlot);
+ iter = next;
+ if (origBytesAllocated - fBytesAllocated >= bytesToFree) {
+ break;
+ }
+ }
+
+ if (fLeastRecentlyUsed != iter) {
+ // There was at least one eviction.
+ fLeastRecentlyUsed = iter;
+ if (NULL == fLeastRecentlyUsed) {
+ // Everything was evicted
+ fMostRecentlyUsed = NULL;
+ fBytesAllocated -= (fStorage.count() * sizeof(SkBitmapHeapEntry));
+ fStorage.deleteAll();
+ fUnusedSlots.reset();
+ SkASSERT(0 == fBytesAllocated);
+ } else {
+ fLeastRecentlyUsed->fLessRecentlyUsed = NULL;
+ }
+ }
+
+ return origBytesAllocated - fBytesAllocated;
+}
+
+int SkBitmapHeap::findInLookupTable(const LookupEntry& indexEntry, SkBitmapHeapEntry** entry) {
+ int index = SkTSearch<const LookupEntry, LookupEntry::Less>(
+ (const LookupEntry**)fLookupTable.begin(),
+ fLookupTable.count(),
+ &indexEntry, sizeof(void*));
+
+ if (index < 0) {
+ // insert ourselves into the bitmapIndex
+ index = ~index;
+ *fLookupTable.insert(index) = SkNEW_ARGS(LookupEntry, (indexEntry));
+ } else if (entry != NULL) {
+ // populate the entry if needed
+ *entry = fStorage[fLookupTable[index]->fStorageSlot];
+ }
+
+ return index;
+}
+
+bool SkBitmapHeap::copyBitmap(const SkBitmap& originalBitmap, SkBitmap& copiedBitmap) {
+ SkASSERT(!fExternalStorage);
+
+ // If the bitmap is mutable, we need to do a deep copy, since the
+ // caller may modify it afterwards.
+ if (originalBitmap.isImmutable()) {
+ copiedBitmap = originalBitmap;
+// TODO if we have the pixel ref in the heap we could pass it here to avoid a potential deep copy
+// else if (sharedPixelRef != NULL) {
+// copiedBitmap = orig;
+// copiedBitmap.setPixelRef(sharedPixelRef, originalBitmap.pixelRefOffset());
+ } else if (originalBitmap.empty()) {
+ copiedBitmap.reset();
+ } else if (!originalBitmap.deepCopyTo(&copiedBitmap, originalBitmap.getConfig())) {
+ return false;
+ }
+ copiedBitmap.setImmutable();
+ return true;
+}
+
+int SkBitmapHeap::removeEntryFromLookupTable(LookupEntry* entry) {
+ // remove the bitmap index for the deleted entry
+ SkDEBUGCODE(int count = fLookupTable.count();)
+ int index = this->findInLookupTable(*entry, NULL);
+ // Verify that findInLookupTable found an existing entry rather than adding
+ // a new entry to the lookup table.
+ SkASSERT(count == fLookupTable.count());
+ fBytesAllocated -= fStorage[entry->fStorageSlot]->fBytesAllocated;
+ SkDELETE(fLookupTable[index]);
+ fLookupTable.remove(index);
+ return index;
+}
+
+int32_t SkBitmapHeap::insert(const SkBitmap& originalBitmap) {
+ SkBitmapHeapEntry* entry = NULL;
+ int searchIndex = this->findInLookupTable(LookupEntry(originalBitmap), &entry);
+
+ if (entry) {
+ // Already had a copy of the bitmap in the heap.
+ if (fOwnerCount != IGNORE_OWNERS) {
+ if (fDeferAddingOwners) {
+ *fDeferredEntries.append() = entry->fSlot;
+ } else {
+ entry->addReferences(fOwnerCount);
+ }
+ }
+ if (fPreferredCount != UNLIMITED_SIZE) {
+ LookupEntry* lookupEntry = fLookupTable[searchIndex];
+ if (lookupEntry != fMostRecentlyUsed) {
+ this->removeFromLRU(lookupEntry);
+ this->appendToLRU(lookupEntry);
+ }
+ }
+ return entry->fSlot;
+ }
+
+ // decide if we need to evict an existing heap entry or create a new one
+ if (fPreferredCount != UNLIMITED_SIZE && fStorage.count() >= fPreferredCount) {
+ // iterate through our LRU cache and try to find an entry to evict
+ LookupEntry* lookupEntry = this->findEntryToReplace(originalBitmap);
+ if (lookupEntry != NULL) {
+ // we found an entry to evict
+ entry = fStorage[lookupEntry->fStorageSlot];
+ // Remove it from the LRU. The new entry will be added to the LRU later.
+ this->removeFromLRU(lookupEntry);
+ int index = this->removeEntryFromLookupTable(lookupEntry);
+
+ // update the current search index now that we have removed one
+ if (index < searchIndex) {
+ searchIndex--;
+ }
+ }
+ }
+
+ // if we didn't have an entry yet we need to create one
+ if (!entry) {
+ if (fPreferredCount != UNLIMITED_SIZE && fUnusedSlots.count() > 0) {
+ int slot;
+ fUnusedSlots.pop(&slot);
+ entry = fStorage[slot];
+ } else {
+ entry = SkNEW(SkBitmapHeapEntry);
+ fStorage.append(1, &entry);
+ entry->fSlot = fStorage.count() - 1;
+ fBytesAllocated += sizeof(SkBitmapHeapEntry);
+ }
+ }
+
+ // create a copy of the bitmap
+ bool copySucceeded;
+ if (fExternalStorage) {
+ copySucceeded = fExternalStorage->insert(originalBitmap, entry->fSlot);
+ } else {
+ copySucceeded = copyBitmap(originalBitmap, entry->fBitmap);
+ }
+
+ // if the copy failed then we must abort
+ if (!copySucceeded) {
+ // delete the index
+ SkDELETE(fLookupTable[searchIndex]);
+ fLookupTable.remove(searchIndex);
+ // If entry is the last slot in storage, it is safe to delete it.
+ if (fStorage.count() - 1 == entry->fSlot) {
+ // free the slot
+ fStorage.remove(entry->fSlot);
+ fBytesAllocated -= sizeof(SkBitmapHeapEntry);
+ SkDELETE(entry);
+ } else {
+ fUnusedSlots.push(entry->fSlot);
+ }
+ return INVALID_SLOT;
+ }
+
+ // update the index with the appropriate slot in the heap
+ fLookupTable[searchIndex]->fStorageSlot = entry->fSlot;
+
+ // compute the space taken by this entry
+ // 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();
+
+ // add the bytes from this entry to the total count
+ fBytesAllocated += entry->fBytesAllocated;
+
+ if (fOwnerCount != IGNORE_OWNERS) {
+ if (fDeferAddingOwners) {
+ *fDeferredEntries.append() = entry->fSlot;
+ } else {
+ entry->addReferences(fOwnerCount);
+ }
+ }
+ if (fPreferredCount != UNLIMITED_SIZE) {
+ this->appendToLRU(fLookupTable[searchIndex]);
+ }
+ return entry->fSlot;
+}
+
+void SkBitmapHeap::deferAddingOwners() {
+ fDeferAddingOwners = true;
+}
+
+void SkBitmapHeap::endAddingOwnersDeferral(bool add) {
+ if (add) {
+ for (int i = 0; i < fDeferredEntries.count(); i++) {
+ SkASSERT(fOwnerCount != IGNORE_OWNERS);
+ SkBitmapHeapEntry* heapEntry = this->getEntry(fDeferredEntries[i]);
+ SkASSERT(heapEntry != NULL);
+ heapEntry->addReferences(fOwnerCount);
+ }
+ }
+ fDeferAddingOwners = false;
+ fDeferredEntries.reset();
+}
diff --git a/core/SkBitmapHeap.h b/core/SkBitmapHeap.h
new file mode 100644
index 00000000..2547eeec
--- /dev/null
+++ b/core/SkBitmapHeap.h
@@ -0,0 +1,307 @@
+
+/*
+ * 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 SkBitmapHeap_DEFINED
+#define SkBitmapHeap_DEFINED
+
+#include "SkBitmap.h"
+#include "SkFlattenable.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+#include "SkThread.h"
+#include "SkTRefArray.h"
+
+/**
+ * SkBitmapHeapEntry provides users of SkBitmapHeap (using internal storage) with a means to...
+ * (1) get access a bitmap in the heap
+ * (2) indicate they are done with bitmap by releasing their reference (if they were an owner).
+ */
+class SkBitmapHeapEntry : SkNoncopyable {
+public:
+ ~SkBitmapHeapEntry();
+
+ int32_t getSlot() { return fSlot; }
+
+ SkBitmap* getBitmap() { return &fBitmap; }
+
+ void releaseRef() {
+ sk_atomic_dec(&fRefCount);
+ }
+
+private:
+ SkBitmapHeapEntry();
+
+ void addReferences(int count);
+
+ int32_t fSlot;
+ int32_t fRefCount;
+
+ SkBitmap fBitmap;
+ // Keep track of the bytes allocated for this bitmap. When replacing the
+ // bitmap or removing this HeapEntry we know how much memory has been
+ // reclaimed.
+ size_t fBytesAllocated;
+
+ friend class SkBitmapHeap;
+ friend class SkBitmapHeapTester;
+};
+
+
+class SkBitmapHeapReader : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkBitmapHeapReader)
+
+ SkBitmapHeapReader() : INHERITED() {}
+ virtual SkBitmap* getBitmap(int32_t slot) const = 0;
+ virtual void releaseRef(int32_t slot) = 0;
+private:
+ typedef SkRefCnt INHERITED;
+};
+
+
+/**
+ * TODO: stores immutable bitmaps into a heap
+ */
+class SkBitmapHeap : public SkBitmapHeapReader {
+public:
+ class ExternalStorage : public SkRefCnt {
+ public:
+ SK_DECLARE_INST_COUNT(ExternalStorage)
+
+ virtual bool insert(const SkBitmap& bitmap, int32_t slot) = 0;
+
+ private:
+ typedef SkRefCnt INHERITED;
+ };
+
+ static const int32_t UNLIMITED_SIZE = -1;
+ static const int32_t IGNORE_OWNERS = -1;
+ static const int32_t INVALID_SLOT = -1;
+
+ /**
+ * Constructs a heap that is responsible for allocating and managing its own storage. In the
+ * case where we choose to allow the heap to grow indefinitely (i.e. UNLIMITED_SIZE) we
+ * guarantee that once allocated in the heap a bitmap's index in the heap is immutable.
+ * Otherwise we guarantee the bitmaps placement in the heap until its owner count goes to zero.
+ *
+ * @param preferredSize Specifies the preferred maximum number of bitmaps to store. This is
+ * not a hard limit as it can grow larger if the number of bitmaps in the heap with active
+ * owners exceeds this limit.
+ * @param ownerCount The number of owners to assign to each inserted bitmap. NOTE: while a
+ * bitmap in the heap has a least one owner it can't be removed.
+ */
+ SkBitmapHeap(int32_t preferredSize = UNLIMITED_SIZE, int32_t ownerCount = IGNORE_OWNERS);
+
+ /**
+ * Constructs a heap that defers the responsibility of storing the bitmaps to an external
+ * function. This is especially useful if the bitmaps will be used in a separate process as the
+ * external storage can ensure the data is properly shuttled to the appropriate processes.
+ *
+ * Our LRU implementation assumes that inserts into the external storage are consumed in the
+ * order that they are inserted (i.e. SkPipe). This ensures that we don't need to query the
+ * external storage to see if a slot in the heap is eligible to be overwritten.
+ *
+ * @param externalStorage The class responsible for storing the bitmaps inserted into the heap
+ * @param heapSize The maximum size of the heap. Because of the sequential limitation imposed
+ * by our LRU implementation we can guarantee that the heap will never grow beyond this size.
+ */
+ SkBitmapHeap(ExternalStorage* externalStorage, int32_t heapSize = UNLIMITED_SIZE);
+
+ ~SkBitmapHeap();
+
+ /**
+ * Makes a shallow copy of all bitmaps currently in the heap and returns them as an array. The
+ * array indices match their position in the heap.
+ *
+ * @return a ptr to an array of bitmaps or NULL if external storage is being used.
+ */
+ SkTRefArray<SkBitmap>* extractBitmaps() const;
+
+ /**
+ * Retrieves the bitmap from the specified slot in the heap
+ *
+ * @return The bitmap located at that slot or NULL if external storage is being used.
+ */
+ virtual SkBitmap* getBitmap(int32_t slot) const SK_OVERRIDE {
+ SkASSERT(fExternalStorage == NULL);
+ SkBitmapHeapEntry* entry = getEntry(slot);
+ if (entry) {
+ return &entry->fBitmap;
+ }
+ return NULL;
+ }
+
+ /**
+ * Retrieves the bitmap from the specified slot in the heap
+ *
+ * @return The bitmap located at that slot or NULL if external storage is being used.
+ */
+ virtual void releaseRef(int32_t slot) SK_OVERRIDE {
+ SkASSERT(fExternalStorage == NULL);
+ if (fOwnerCount != IGNORE_OWNERS) {
+ SkBitmapHeapEntry* entry = getEntry(slot);
+ if (entry) {
+ entry->releaseRef();
+ }
+ }
+ }
+
+ /**
+ * Inserts a bitmap into the heap. The stored version of bitmap is guaranteed to be immutable
+ * and is not dependent on the lifecycle of the provided bitmap.
+ *
+ * @param bitmap the bitmap to be inserted into the heap
+ * @return the slot in the heap where the bitmap is stored or INVALID_SLOT if the bitmap could
+ * not be added to the heap. If it was added the slot will remain valid...
+ * (1) indefinitely if no owner count has been specified.
+ * (2) until all owners have called releaseRef on the appropriate SkBitmapHeapEntry*
+ */
+ int32_t insert(const SkBitmap& bitmap);
+
+ /**
+ * Retrieves an entry from the heap at a given slot.
+ *
+ * @param slot the slot in the heap where a bitmap was stored.
+ * @return a SkBitmapHeapEntry that wraps the bitmap or NULL if external storage is used.
+ */
+ SkBitmapHeapEntry* getEntry(int32_t slot) const {
+ SkASSERT(slot <= fStorage.count());
+ if (fExternalStorage != NULL) {
+ return NULL;
+ }
+ return fStorage[slot];
+ }
+
+ /**
+ * Returns a count of the number of items currently in the heap
+ */
+ int count() const {
+ SkASSERT(fExternalStorage != NULL ||
+ fStorage.count() - fUnusedSlots.count() == fLookupTable.count());
+ return fLookupTable.count();
+ }
+
+ /**
+ * Returns the total number of bytes allocated by the bitmaps in the heap
+ */
+ size_t bytesAllocated() const {
+ return fBytesAllocated;
+ }
+
+ /**
+ * Attempt to reduce the storage allocated.
+ * @param bytesToFree minimum number of bytes that should be attempted to
+ * be freed.
+ * @return number of bytes actually freed.
+ */
+ size_t freeMemoryIfPossible(size_t bytesToFree);
+
+ /**
+ * Defer any increments of owner counts until endAddingOwnersDeferral is called. So if an
+ * existing SkBitmap is inserted into the SkBitmapHeap, its corresponding SkBitmapHeapEntry will
+ * not have addReferences called on it, and the client does not need to make a corresponding
+ * call to releaseRef. Only meaningful if this SkBitmapHeap was created with an owner count not
+ * equal to IGNORE_OWNERS.
+ */
+ void deferAddingOwners();
+
+ /**
+ * Resume adding references when duplicate SkBitmaps are inserted.
+ * @param add If true, add references to the SkBitmapHeapEntrys whose SkBitmaps were re-inserted
+ * while deferring.
+ */
+ void endAddingOwnersDeferral(bool add);
+
+private:
+ struct LookupEntry {
+ LookupEntry(const SkBitmap& bm)
+ : fGenerationId(bm.getGenerationID())
+ , fPixelOffset(bm.pixelRefOffset())
+ , fWidth(bm.width())
+ , fHeight(bm.height())
+ , fMoreRecentlyUsed(NULL)
+ , fLessRecentlyUsed(NULL){}
+
+ const uint32_t fGenerationId; // SkPixelRef GenerationID.
+ const size_t fPixelOffset;
+ const uint32_t fWidth;
+ const uint32_t fHeight;
+
+ // TODO: Generalize the LRU caching mechanism
+ LookupEntry* fMoreRecentlyUsed;
+ LookupEntry* fLessRecentlyUsed;
+
+ uint32_t fStorageSlot; // slot of corresponding bitmap in fStorage.
+
+ /**
+ * Compare two LookupEntry pointers for sorting and searching.
+ */
+ static bool Less(const LookupEntry& a, const LookupEntry& b);
+ };
+
+ /**
+ * Remove the entry from the lookup table. Also deletes the entry pointed
+ * to by the table. Therefore, if a pointer to that one was passed in, the
+ * pointer should no longer be used, since the object to which it points has
+ * been deleted.
+ * @return The index in the lookup table of the entry before removal.
+ */
+ int removeEntryFromLookupTable(LookupEntry*);
+
+ /**
+ * Searches for the bitmap in the lookup table and returns the bitmaps index within the table.
+ * If the bitmap was not already in the table it is added.
+ *
+ * @param key The key to search the lookup table, created from a bitmap.
+ * @param entry A pointer to a SkBitmapHeapEntry* that if non-null AND the bitmap is found
+ * in the lookup table is populated with the entry from the heap storage.
+ */
+ int findInLookupTable(const LookupEntry& key, SkBitmapHeapEntry** entry);
+
+ LookupEntry* findEntryToReplace(const SkBitmap& replacement);
+ bool copyBitmap(const SkBitmap& originalBitmap, SkBitmap& copiedBitmap);
+
+ /**
+ * Remove a LookupEntry from the LRU, in preparation for either deleting or appending as most
+ * recent. Points the LookupEntry's old neighbors at each other, and sets fLeastRecentlyUsed
+ * (if there is still an entry left). Sets LookupEntry's fMoreRecentlyUsed to NULL and leaves
+ * its fLessRecentlyUsed unmodified.
+ */
+ void removeFromLRU(LookupEntry* entry);
+
+ /**
+ * Append a LookupEntry to the end of the LRU cache, marking it as the most
+ * recently used. Assumes that the LookupEntry is already in fLookupTable,
+ * but is not in the LRU cache. If it is in the cache, removeFromLRU should
+ * be called first.
+ */
+ void appendToLRU(LookupEntry*);
+
+ // searchable index that maps to entries in the heap
+ SkTDArray<LookupEntry*> fLookupTable;
+
+ // heap storage
+ SkTDArray<SkBitmapHeapEntry*> fStorage;
+ // Used to mark slots in fStorage as deleted without actually deleting
+ // the slot so as not to mess up the numbering.
+ SkTDArray<int> fUnusedSlots;
+ ExternalStorage* fExternalStorage;
+
+ LookupEntry* fMostRecentlyUsed;
+ LookupEntry* fLeastRecentlyUsed;
+
+ const int32_t fPreferredCount;
+ const int32_t fOwnerCount;
+ size_t fBytesAllocated;
+
+ bool fDeferAddingOwners;
+ SkTDArray<int> fDeferredEntries;
+
+ typedef SkBitmapHeapReader INHERITED;
+};
+
+#endif // SkBitmapHeap_DEFINED
diff --git a/core/SkBitmapProcShader.cpp b/core/SkBitmapProcShader.cpp
new file mode 100644
index 00000000..0e7ac6a2
--- /dev/null
+++ b/core/SkBitmapProcShader.cpp
@@ -0,0 +1,397 @@
+
+/*
+ * 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 "SkBitmapProcShader.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPixelRef.h"
+#include "SkErrorInternals.h"
+
+bool SkBitmapProcShader::CanDo(const SkBitmap& bm, TileMode tx, TileMode ty) {
+ switch (bm.config()) {
+ case SkBitmap::kA8_Config:
+ case SkBitmap::kRGB_565_Config:
+ case SkBitmap::kIndex8_Config:
+ case SkBitmap::kARGB_8888_Config:
+ // if (tx == ty && (kClamp_TileMode == tx || kRepeat_TileMode == tx))
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+SkBitmapProcShader::SkBitmapProcShader(const SkBitmap& src,
+ TileMode tmx, TileMode tmy) {
+ fRawBitmap = src;
+ fState.fTileModeX = (uint8_t)tmx;
+ fState.fTileModeY = (uint8_t)tmy;
+ fFlags = 0; // computed in setContext
+}
+
+SkBitmapProcShader::SkBitmapProcShader(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ buffer.readBitmap(&fRawBitmap);
+ fRawBitmap.setImmutable();
+ fState.fTileModeX = buffer.readUInt();
+ fState.fTileModeY = buffer.readUInt();
+ fFlags = 0; // computed in setContext
+}
+
+SkShader::BitmapType SkBitmapProcShader::asABitmap(SkBitmap* texture,
+ SkMatrix* texM,
+ TileMode xy[]) const {
+ if (texture) {
+ *texture = fRawBitmap;
+ }
+ if (texM) {
+ texM->reset();
+ }
+ if (xy) {
+ xy[0] = (TileMode)fState.fTileModeX;
+ xy[1] = (TileMode)fState.fTileModeY;
+ }
+ return kDefault_BitmapType;
+}
+
+void SkBitmapProcShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeBitmap(fRawBitmap);
+ buffer.writeUInt(fState.fTileModeX);
+ buffer.writeUInt(fState.fTileModeY);
+}
+
+static bool only_scale_and_translate(const SkMatrix& matrix) {
+ unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
+ return (matrix.getType() & ~mask) == 0;
+}
+
+bool SkBitmapProcShader::isOpaque() const {
+ return fRawBitmap.isOpaque();
+}
+
+bool SkBitmapProcShader::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ // do this first, so we have a correct inverse matrix
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ fState.fOrigBitmap = fRawBitmap;
+ fState.fOrigBitmap.lockPixels();
+ if (!fState.fOrigBitmap.getTexture() && !fState.fOrigBitmap.readyToDraw()) {
+ fState.fOrigBitmap.unlockPixels();
+ this->INHERITED::endContext();
+ return false;
+ }
+
+ if (!fState.chooseProcs(this->getTotalInverse(), paint)) {
+ fState.fOrigBitmap.unlockPixels();
+ this->INHERITED::endContext();
+ return false;
+ }
+
+ const SkBitmap& bitmap = *fState.fBitmap;
+ bool bitmapIsOpaque = bitmap.isOpaque();
+
+ // update fFlags
+ uint32_t flags = 0;
+ if (bitmapIsOpaque && (255 == this->getPaintAlpha())) {
+ flags |= kOpaqueAlpha_Flag;
+ }
+
+ switch (bitmap.config()) {
+ case SkBitmap::kRGB_565_Config:
+ flags |= (kHasSpan16_Flag | kIntrinsicly16_Flag);
+ break;
+ case SkBitmap::kIndex8_Config:
+ case SkBitmap::kARGB_8888_Config:
+ if (bitmapIsOpaque) {
+ flags |= kHasSpan16_Flag;
+ }
+ break;
+ case SkBitmap::kA8_Config:
+ break; // never set kHasSpan16_Flag
+ default:
+ break;
+ }
+
+ if (paint.isDither() && bitmap.config() != SkBitmap::kRGB_565_Config) {
+ // gradients can auto-dither in their 16bit sampler, but we don't so
+ // we clear the flag here.
+ flags &= ~kHasSpan16_Flag;
+ }
+
+ // if we're only 1-pixel high, and we don't rotate, then we can claim this
+ if (1 == bitmap.height() &&
+ only_scale_and_translate(this->getTotalInverse())) {
+ flags |= kConstInY32_Flag;
+ if (flags & kHasSpan16_Flag) {
+ flags |= kConstInY16_Flag;
+ }
+ }
+
+ fFlags = flags;
+ return true;
+}
+
+void SkBitmapProcShader::endContext() {
+ fState.fOrigBitmap.unlockPixels();
+ fState.endContext();
+ this->INHERITED::endContext();
+}
+
+#define BUF_MAX 128
+
+#define TEST_BUFFER_OVERRITEx
+
+#ifdef TEST_BUFFER_OVERRITE
+ #define TEST_BUFFER_EXTRA 32
+ #define TEST_PATTERN 0x88888888
+#else
+ #define TEST_BUFFER_EXTRA 0
+#endif
+
+void SkBitmapProcShader::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
+ const SkBitmapProcState& state = fState;
+ if (state.getShaderProc32()) {
+ state.getShaderProc32()(state, x, y, dstC, count);
+ return;
+ }
+
+ uint32_t buffer[BUF_MAX + TEST_BUFFER_EXTRA];
+ SkBitmapProcState::MatrixProc mproc = state.getMatrixProc();
+ SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32();
+ int max = fState.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX);
+
+ SkASSERT(state.fBitmap->getPixels());
+ SkASSERT(state.fBitmap->pixelRef() == NULL ||
+ state.fBitmap->pixelRef()->isLocked());
+
+ for (;;) {
+ int n = count;
+ if (n > max) {
+ n = max;
+ }
+ SkASSERT(n > 0 && n < BUF_MAX*2);
+#ifdef TEST_BUFFER_OVERRITE
+ for (int i = 0; i < TEST_BUFFER_EXTRA; i++) {
+ buffer[BUF_MAX + i] = TEST_PATTERN;
+ }
+#endif
+ mproc(state, buffer, n, x, y);
+#ifdef TEST_BUFFER_OVERRITE
+ for (int j = 0; j < TEST_BUFFER_EXTRA; j++) {
+ SkASSERT(buffer[BUF_MAX + j] == TEST_PATTERN);
+ }
+#endif
+ sproc(state, buffer, n, dstC);
+
+ if ((count -= n) == 0) {
+ break;
+ }
+ SkASSERT(count > 0);
+ x += n;
+ dstC += n;
+ }
+}
+
+SkShader::ShadeProc SkBitmapProcShader::asAShadeProc(void** ctx) {
+ if (fState.getShaderProc32()) {
+ *ctx = &fState;
+ return (ShadeProc)fState.getShaderProc32();
+ }
+ return NULL;
+}
+
+void SkBitmapProcShader::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
+ const SkBitmapProcState& state = fState;
+ if (state.getShaderProc16()) {
+ state.getShaderProc16()(state, x, y, dstC, count);
+ return;
+ }
+
+ uint32_t buffer[BUF_MAX];
+ SkBitmapProcState::MatrixProc mproc = state.getMatrixProc();
+ SkBitmapProcState::SampleProc16 sproc = state.getSampleProc16();
+ int max = fState.maxCountForBufferSize(sizeof(buffer));
+
+ SkASSERT(state.fBitmap->getPixels());
+ SkASSERT(state.fBitmap->pixelRef() == NULL ||
+ state.fBitmap->pixelRef()->isLocked());
+
+ for (;;) {
+ int n = count;
+ if (n > max) {
+ n = max;
+ }
+ mproc(state, buffer, n, x, y);
+ sproc(state, buffer, n, dstC);
+
+ if ((count -= n) == 0) {
+ break;
+ }
+ x += n;
+ dstC += n;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkUnPreMultiply.h"
+#include "SkColorShader.h"
+#include "SkEmptyShader.h"
+
+// returns true and set color if the bitmap can be drawn as a single color
+// (for efficiency)
+static bool canUseColorShader(const SkBitmap& bm, SkColor* color) {
+ if (1 != bm.width() || 1 != bm.height()) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(bm);
+ if (!bm.readyToDraw()) {
+ return false;
+ }
+
+ switch (bm.config()) {
+ case SkBitmap::kARGB_8888_Config:
+ *color = SkUnPreMultiply::PMColorToColor(*bm.getAddr32(0, 0));
+ return true;
+ case SkBitmap::kRGB_565_Config:
+ *color = SkPixel16ToColor(*bm.getAddr16(0, 0));
+ return true;
+ case SkBitmap::kIndex8_Config:
+ *color = SkUnPreMultiply::PMColorToColor(bm.getIndex8Color(0, 0));
+ return true;
+ default: // just skip the other configs for now
+ break;
+ }
+ return false;
+}
+
+#include "SkTemplatesPriv.h"
+
+static bool bitmapIsTooBig(const SkBitmap& bm) {
+ // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
+ // communicates between its matrix-proc and its sampler-proc. Until we can
+ // widen that, we have to reject bitmaps that are larger.
+ //
+ const int maxSize = 65535;
+
+ return bm.width() > maxSize || bm.height() > maxSize;
+}
+
+SkShader* SkShader::CreateBitmapShader(const SkBitmap& src,
+ TileMode tmx, TileMode tmy,
+ void* storage, size_t storageSize) {
+ SkShader* shader;
+ SkColor color;
+ if (src.isNull() || bitmapIsTooBig(src)) {
+ SK_PLACEMENT_NEW(shader, SkEmptyShader, storage, storageSize);
+ }
+ else if (canUseColorShader(src, &color)) {
+ SK_PLACEMENT_NEW_ARGS(shader, SkColorShader, storage, storageSize,
+ (color));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(shader, SkBitmapProcShader, storage,
+ storageSize, (src, tmx, tmy));
+ }
+ return shader;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEVELOPER
+void SkBitmapProcShader::toString(SkString* str) const {
+ static const char* gTileModeName[SkShader::kTileModeCount] = {
+ "clamp", "repeat", "mirror"
+ };
+
+ str->append("BitmapShader: (");
+
+ str->appendf("(%s, %s)",
+ gTileModeName[fState.fTileModeX],
+ gTileModeName[fState.fTileModeY]);
+
+ str->append(" ");
+ fRawBitmap.toString(str);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTextureAccess.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGr.h"
+
+GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint& paint) const {
+ SkMatrix matrix;
+ matrix.setIDiv(fRawBitmap.width(), fRawBitmap.height());
+
+ if (this->hasLocalMatrix()) {
+ SkMatrix inverse;
+ if (!this->getLocalMatrix().invert(&inverse)) {
+ return NULL;
+ }
+ matrix.preConcat(inverse);
+ }
+ SkShader::TileMode tm[] = {
+ (TileMode)fState.fTileModeX,
+ (TileMode)fState.fTileModeY,
+ };
+
+ // Must set wrap and filter on the sampler before requesting a texture.
+ SkPaint::FilterLevel paintFilterLevel = paint.getFilterLevel();
+ GrTextureParams::FilterMode textureFilterMode;
+ switch(paintFilterLevel) {
+ case SkPaint::kNone_FilterLevel:
+ textureFilterMode = GrTextureParams::kNone_FilterMode;
+ break;
+ case SkPaint::kLow_FilterLevel:
+ textureFilterMode = GrTextureParams::kBilerp_FilterMode;
+ break;
+ case SkPaint::kMedium_FilterLevel:
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+ case SkPaint::kHigh_FilterLevel:
+ SkErrorInternals::SetError( kInvalidPaint_SkError,
+ "Sorry, I don't yet support high quality "
+ "filtering on the GPU; falling back to "
+ "MIPMaps.");
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+ default:
+ SkErrorInternals::SetError( kInvalidPaint_SkError,
+ "Sorry, I don't understand the filtering "
+ "mode you asked for. Falling back to "
+ "MIPMaps.");
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+
+ }
+ GrTextureParams params(tm, textureFilterMode);
+ GrTexture* texture = GrLockAndRefCachedBitmapTexture(context, fRawBitmap, &params);
+
+ if (NULL == texture) {
+ SkDebugf("Couldn't convert bitmap to texture.\n");
+ return NULL;
+ }
+
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+ GrUnlockAndUnrefCachedBitmapTexture(texture);
+ return effect;
+}
+#endif
diff --git a/core/SkBitmapProcShader.h b/core/SkBitmapProcShader.h
new file mode 100644
index 00000000..5a599c3b
--- /dev/null
+++ b/core/SkBitmapProcShader.h
@@ -0,0 +1,51 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBitmapProcShader_DEFINED
+#define SkBitmapProcShader_DEFINED
+
+#include "SkShader.h"
+#include "SkBitmapProcState.h"
+
+class SkBitmapProcShader : public SkShader {
+public:
+ SkBitmapProcShader(const SkBitmap& src, TileMode tx, TileMode ty);
+
+ // overrides from SkShader
+ virtual bool isOpaque() const SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
+ virtual void endContext() SK_OVERRIDE;
+ virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+ virtual ShadeProc asAShadeProc(void** ctx) SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
+ virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*) const SK_OVERRIDE;
+
+ static bool CanDo(const SkBitmap&, TileMode tx, TileMode ty);
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBitmapProcShader)
+
+#if SK_SUPPORT_GPU
+ GrEffectRef* asNewEffect(GrContext*, const SkPaint&) const SK_OVERRIDE;
+#endif
+
+protected:
+ SkBitmapProcShader(SkFlattenableReadBuffer& );
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+ SkBitmap fRawBitmap; // experimental for RLE encoding
+ SkBitmapProcState fState;
+ uint32_t fFlags;
+
+private:
+ typedef SkShader INHERITED;
+};
+
+#endif
diff --git a/core/SkBitmapProcState.cpp b/core/SkBitmapProcState.cpp
new file mode 100644
index 00000000..b48f8384
--- /dev/null
+++ b/core/SkBitmapProcState.cpp
@@ -0,0 +1,958 @@
+
+/*
+ * 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 "SkBitmapProcState.h"
+#include "SkColorPriv.h"
+#include "SkFilterProc.h"
+#include "SkPaint.h"
+#include "SkShader.h" // for tilemodes
+#include "SkUtilsArm.h"
+#include "SkBitmapScaler.h"
+#include "SkMipMap.h"
+#include "SkScaledImageCache.h"
+
+#if !SK_ARM_NEON_IS_NONE
+// These are defined in src/opts/SkBitmapProcState_arm_neon.cpp
+extern const SkBitmapProcState::SampleProc16 gSkBitmapProcStateSample16_neon[];
+extern const SkBitmapProcState::SampleProc32 gSkBitmapProcStateSample32_neon[];
+extern void S16_D16_filter_DX_neon(const SkBitmapProcState&, const uint32_t*, int, uint16_t*);
+extern void Clamp_S16_D16_filter_DX_shaderproc_neon(const SkBitmapProcState&, int, int, uint16_t*, int);
+extern void Repeat_S16_D16_filter_DX_shaderproc_neon(const SkBitmapProcState&, int, int, uint16_t*, int);
+extern void SI8_opaque_D32_filter_DX_neon(const SkBitmapProcState&, const uint32_t*, int, SkPMColor*);
+extern void SI8_opaque_D32_filter_DX_shaderproc_neon(const SkBitmapProcState&, int, int, uint32_t*, int);
+extern void Clamp_SI8_opaque_D32_filter_DX_shaderproc_neon(const SkBitmapProcState&, int, int, uint32_t*, int);
+#endif
+
+#define NAME_WRAP(x) x
+#include "SkBitmapProcState_filter.h"
+#include "SkBitmapProcState_procs.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+// true iff the matrix contains, at most, scale and translate elements
+static bool matrix_only_scale_translate(const SkMatrix& m) {
+ return m.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask);
+}
+
+/**
+ * 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) {
+ SkASSERT(matrix_only_scale_translate(matrix));
+
+ if (matrix.getType() & SkMatrix::kScale_Mask) {
+ SkRect src, dst;
+ bitmap.getBounds(&src);
+
+ // Can't call mapRect(), since that will fix up inverted rectangles,
+ // e.g. when scale is negative, and we don't want to return true for
+ // those.
+ matrix.mapPoints(SkTCast<SkPoint*>(&dst),
+ SkTCast<const SkPoint*>(&src),
+ 2);
+
+ // Now round all 4 edges to device space, and then compare the device
+ // width/height to the original. Note: we must map all 4 and subtract
+ // rather than map the "width" and compare, since we care about the
+ // phase (in pixel space) that any translate in the matrix might impart.
+ SkIRect idst;
+ dst.round(&idst);
+ return idst.width() == bitmap.width() && idst.height() == bitmap.height();
+ }
+ // if we got here, we're either kTranslate_Mask or identity
+ return true;
+}
+
+static bool just_trans_general(const SkMatrix& matrix) {
+ SkASSERT(matrix_only_scale_translate(matrix));
+
+ if (matrix.getType() & 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
+ return (dimension & ~0x3FFF) == 0;
+}
+
+static SkScalar effective_matrix_scale_sqrd(const SkMatrix& mat) {
+ SkPoint v1, v2;
+
+ v1.fX = mat.getScaleX();
+ v1.fY = mat.getSkewY();
+
+ v2.fX = mat.getSkewX();
+ v2.fY = mat.getScaleY();
+
+ return SkMaxScalar(v1.lengthSqd(), v2.lengthSqd());
+}
+
+// TODO -- we may want to pass the clip into this function so we only scale
+// the portion of the image that we're going to need. This will complicate
+// the interface to the cache, but might be well worth it.
+
+void SkBitmapProcState::possiblyScaleImage() {
+
+ if (fFilterLevel <= SkPaint::kLow_FilterLevel) {
+ // none or low (bilerp) does not need to look any further
+ return;
+ }
+
+ // see if our platform has any specialized convolution code.
+
+
+ // Set up a pointer to a local (instead of storing the structure in the
+ // proc state) to avoid introducing a header dependency; this makes
+ // recompiles a lot less painful.
+
+ SkConvolutionProcs simd;
+ fConvolutionProcs = &simd;
+
+ fConvolutionProcs->fExtraHorizontalReads = 0;
+ fConvolutionProcs->fConvolveVertically = NULL;
+ fConvolutionProcs->fConvolve4RowsHorizontally = NULL;
+ fConvolutionProcs->fConvolveHorizontally = NULL;
+ fConvolutionProcs->fApplySIMDPadding = NULL;
+
+ this->platformConvolutionProcs();
+
+ // STEP 1: Highest quality direct scale?
+
+ // Check to see if the transformation matrix is simple, and if we're
+ // doing high quality scaling. If so, do the bitmap scale here and
+ // remove the scaling component from the matrix.
+
+ if (SkPaint::kHigh_FilterLevel == fFilterLevel &&
+ fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) &&
+ fOrigBitmap.config() == SkBitmap::kARGB_8888_Config) {
+
+ SkScalar invScaleX = fInvMatrix.getScaleX();
+ SkScalar invScaleY = fInvMatrix.getScaleY();
+
+ SkASSERT(NULL == fScaledCacheID);
+ fScaledCacheID = SkScaledImageCache::FindAndLock(fOrigBitmap,
+ invScaleX, invScaleY,
+ &fScaledBitmap);
+ if (NULL == fScaledCacheID) {
+ int dest_width = SkScalarCeilToInt(fOrigBitmap.width() / invScaleX);
+ int dest_height = SkScalarCeilToInt(fOrigBitmap.height() / invScaleY);
+
+ // All the criteria are met; let's make a new bitmap.
+
+ if (!SkBitmapScaler::Resize(&fScaledBitmap,
+ fOrigBitmap,
+ SkBitmapScaler::RESIZE_BEST,
+ dest_width,
+ dest_height,
+ fConvolutionProcs)) {
+ // we failed to create fScaledBitmap, so just return and let
+ // the scanline proc handle it.
+ return;
+
+ }
+ fScaledCacheID = SkScaledImageCache::AddAndLock(fOrigBitmap,
+ invScaleX,
+ invScaleY,
+ fScaledBitmap);
+ }
+ fScaledBitmap.lockPixels();
+
+ fBitmap = &fScaledBitmap;
+
+ // set the inv matrix type to translate-only;
+
+ fInvMatrix.setTranslate( 1/fInvMatrix.getScaleX() * fInvMatrix.getTranslateX(),
+ 1/fInvMatrix.getScaleY() * fInvMatrix.getTranslateY() );
+
+ // no need for any further filtering; we just did it!
+
+ fFilterLevel = SkPaint::kNone_FilterLevel;
+
+ return;
+ }
+
+ /*
+ * If we get here, the caller has requested either Med or High filter-level
+ *
+ * If High, then our special-case for scale-only did not take, and so we
+ * have to make a choice:
+ * 1. fall back on mipmaps + bilerp
+ * 2. fall back on scanline bicubic filter
+ * For now, we compute the "scale" value from the matrix, and have a
+ * threshold to decide when bicubic is better, and when mips are better.
+ * No doubt a fancier decision tree could be used uere.
+ *
+ * If Medium, then we just try to build a mipmap and select a level,
+ * setting the filter-level to kLow to signal that we just need bilerp
+ * to process the selected level.
+ */
+
+ SkScalar scaleSqd = effective_matrix_scale_sqrd(fInvMatrix);
+
+ if (SkPaint::kHigh_FilterLevel == fFilterLevel) {
+ // Set the limit at 0.25 for the CTM... if the CTM is scaling smaller
+ // than this, then the mipmaps quality may be greater (certainly faster)
+ // so we only keep High quality if the scale is greater than this.
+ //
+ // Since we're dealing with the inverse, we compare against its inverse.
+ const SkScalar bicubicLimit = SkFloatToScalar(4.0f);
+ const SkScalar bicubicLimitSqd = bicubicLimit * bicubicLimit;
+ if (scaleSqd < bicubicLimitSqd) { // use bicubic scanline
+ return;
+ }
+
+ // else set the filter-level to Medium, since we're scaling down and
+ // want to reqeust mipmaps
+ fFilterLevel = SkPaint::kMedium_FilterLevel;
+ }
+
+ SkASSERT(SkPaint::kMedium_FilterLevel == fFilterLevel);
+
+ /**
+ * Medium quality means use a mipmap for down-scaling, and just bilper
+ * for upscaling. Since we're examining the inverse matrix, we look for
+ * a scale > 1 to indicate down scaling by the CTM.
+ */
+ if (scaleSqd > SK_Scalar1) {
+ const SkMipMap* mip = NULL;
+
+ SkASSERT(NULL == fScaledCacheID);
+ fScaledCacheID = SkScaledImageCache::FindAndLockMip(fOrigBitmap, &mip);
+ if (!fScaledCacheID) {
+ SkASSERT(NULL == mip);
+ mip = SkMipMap::Build(fOrigBitmap);
+ if (mip) {
+ fScaledCacheID = SkScaledImageCache::AddAndLockMip(fOrigBitmap,
+ mip);
+ mip->unref(); // the cache took a ref
+ SkASSERT(fScaledCacheID);
+ }
+ } else {
+ SkASSERT(mip);
+ }
+
+ if (mip) {
+ SkScalar levelScale = SkScalarInvert(SkScalarSqrt(scaleSqd));
+ SkMipMap::Level level;
+ if (mip->extractLevel(levelScale, &level)) {
+ SkScalar invScaleFixup = level.fScale;
+ fInvMatrix.postScale(invScaleFixup, invScaleFixup);
+
+ fScaledBitmap.setConfig(fOrigBitmap.config(),
+ level.fWidth, level.fHeight,
+ level.fRowBytes);
+ fScaledBitmap.setPixels(level.fPixels);
+ fBitmap = &fScaledBitmap;
+ }
+ }
+ }
+
+ /*
+ * At this point, we may or may not have built a mipmap. Regardless, we
+ * now fall back on Low so will bilerp whatever fBitmap now points at.
+ */
+ fFilterLevel = SkPaint::kLow_FilterLevel;
+}
+
+void SkBitmapProcState::endContext() {
+ SkDELETE(fBitmapFilter);
+ fBitmapFilter = NULL;
+ fScaledBitmap.reset();
+
+ if (fScaledCacheID) {
+ SkScaledImageCache::Unlock(fScaledCacheID);
+ fScaledCacheID = NULL;
+ }
+}
+
+SkBitmapProcState::~SkBitmapProcState() {
+ if (fScaledCacheID) {
+ SkScaledImageCache::Unlock(fScaledCacheID);
+ }
+ SkDELETE(fBitmapFilter);
+}
+
+bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) {
+ if (fOrigBitmap.width() == 0 || fOrigBitmap.height() == 0) {
+ return false;
+ }
+
+ bool trivialMatrix = (inv.getType() & ~SkMatrix::kTranslate_Mask) == 0;
+ bool clampClamp = SkShader::kClamp_TileMode == fTileModeX &&
+ SkShader::kClamp_TileMode == fTileModeY;
+
+ fInvMatrix = inv;
+ if (!(clampClamp || trivialMatrix)) {
+ fInvMatrix.postIDiv(fOrigBitmap.width(), fOrigBitmap.height());
+ }
+
+ fBitmap = &fOrigBitmap;
+
+ // initialize our filter quality to the one requested by the caller.
+ // We may downgrade it later if we determine that we either don't need
+ // or can't provide as high a quality filtering as the user requested.
+
+ fFilterLevel = paint.getFilterLevel();
+
+#ifndef SK_IGNORE_IMAGE_PRESCALE
+ // possiblyScaleImage will look to see if it can rescale the image as a
+ // preprocess; either by scaling up to the target size, or by selecting
+ // a nearby mipmap level. If it does, it will adjust the working
+ // matrix as well as the working bitmap. It may also adjust the filter
+ // quality to avoid re-filtering an already perfectly scaled image.
+
+ this->possiblyScaleImage();
+#endif
+
+ // Now that all possible changes to the matrix have taken place, check
+ // to see if we're really close to a no-scale matrix. If so, explicitly
+ // set it to be so. Subsequent code may inspect this matrix to choose
+ // a faster path in this case.
+
+ // This code will only execute if the matrix has some scale component;
+ // if it's already pure translate then we won't do this inversion.
+
+ if (matrix_only_scale_translate(fInvMatrix)) {
+ SkMatrix forward;
+ if (fInvMatrix.invert(&forward)) {
+ if (clampClamp ? just_trans_clamp(forward, *fBitmap)
+ : just_trans_general(forward)) {
+ SkScalar tx = -SkScalarRoundToScalar(forward.getTranslateX());
+ SkScalar ty = -SkScalarRoundToScalar(forward.getTranslateY());
+ fInvMatrix.setTranslate(tx, ty);
+
+ }
+ }
+ }
+
+ fInvProc = fInvMatrix.getMapXYProc();
+ fInvType = fInvMatrix.getType();
+ fInvSx = SkScalarToFixed(fInvMatrix.getScaleX());
+ fInvSxFractionalInt = SkScalarToFractionalInt(fInvMatrix.getScaleX());
+ fInvKy = SkScalarToFixed(fInvMatrix.getSkewY());
+ fInvKyFractionalInt = SkScalarToFractionalInt(fInvMatrix.getSkewY());
+
+ fAlphaScale = SkAlpha255To256(paint.getAlpha());
+
+ fShaderProc32 = NULL;
+ fShaderProc16 = NULL;
+ fSampleProc32 = NULL;
+ fSampleProc16 = NULL;
+
+ // recompute the triviality of the matrix here because we may have
+ // changed it!
+
+ trivialMatrix = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0;
+
+ if (SkPaint::kHigh_FilterLevel == fFilterLevel) {
+ // If this is still set, that means we wanted HQ sampling
+ // but couldn't do it as a preprocess. Let's try to install
+ // the scanline version of the HQ sampler. If that process fails,
+ // downgrade to bilerp.
+
+ // NOTE: Might need to be careful here in the future when we want
+ // to have the platform proc have a shot at this; it's possible that
+ // the chooseBitmapFilterProc will fail to install a shader but a
+ // platform-specific one might succeed, so it might be premature here
+ // to fall back to bilerp. This needs thought.
+
+ SkASSERT(fInvType > SkMatrix::kTranslate_Mask);
+
+ fShaderProc32 = this->chooseBitmapFilterProc();
+ if (!fShaderProc32) {
+ fFilterLevel = SkPaint::kLow_FilterLevel;
+ }
+ }
+
+ if (SkPaint::kLow_FilterLevel == fFilterLevel) {
+ // Only try bilerp if the matrix is "interesting" and
+ // the image has a suitable size.
+
+ if (fInvType <= SkMatrix::kTranslate_Mask ||
+ !valid_for_filtering(fBitmap->width() | fBitmap->height())) {
+ fFilterLevel = SkPaint::kNone_FilterLevel;
+ }
+ }
+
+ // At this point, we know exactly what kind of sampling the per-scanline
+ // shader will perform.
+
+ fMatrixProc = this->chooseMatrixProc(trivialMatrix);
+ if (NULL == fMatrixProc) {
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ // No need to do this if we're doing HQ sampling; if filter quality is
+ // still set to HQ by the time we get here, then we must have installed
+ // the shader proc above and can skip all this.
+
+ if (fFilterLevel < SkPaint::kHigh_FilterLevel) {
+
+ int index = 0;
+ if (fAlphaScale < 256) { // note: this distinction is not used for D16
+ index |= 1;
+ }
+ if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) {
+ index |= 2;
+ }
+ if (fFilterLevel > SkPaint::kNone_FilterLevel) {
+ index |= 4;
+ }
+ // bits 3,4,5 encoding the source bitmap format
+ switch (fBitmap->config()) {
+ case SkBitmap::kARGB_8888_Config:
+ index |= 0;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ index |= 8;
+ break;
+ case SkBitmap::kIndex8_Config:
+ index |= 16;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ index |= 24;
+ break;
+ case SkBitmap::kA8_Config:
+ index |= 32;
+ fPaintPMColor = SkPreMultiplyColor(paint.getColor());
+ break;
+ default:
+ return false;
+ }
+
+ #if !SK_ARM_NEON_IS_ALWAYS
+ static const SampleProc32 gSkBitmapProcStateSample32[] = {
+ S32_opaque_D32_nofilter_DXDY,
+ S32_alpha_D32_nofilter_DXDY,
+ S32_opaque_D32_nofilter_DX,
+ S32_alpha_D32_nofilter_DX,
+ S32_opaque_D32_filter_DXDY,
+ S32_alpha_D32_filter_DXDY,
+ S32_opaque_D32_filter_DX,
+ S32_alpha_D32_filter_DX,
+
+ S16_opaque_D32_nofilter_DXDY,
+ S16_alpha_D32_nofilter_DXDY,
+ S16_opaque_D32_nofilter_DX,
+ S16_alpha_D32_nofilter_DX,
+ S16_opaque_D32_filter_DXDY,
+ S16_alpha_D32_filter_DXDY,
+ S16_opaque_D32_filter_DX,
+ S16_alpha_D32_filter_DX,
+
+ SI8_opaque_D32_nofilter_DXDY,
+ SI8_alpha_D32_nofilter_DXDY,
+ SI8_opaque_D32_nofilter_DX,
+ SI8_alpha_D32_nofilter_DX,
+ SI8_opaque_D32_filter_DXDY,
+ SI8_alpha_D32_filter_DXDY,
+ SI8_opaque_D32_filter_DX,
+ SI8_alpha_D32_filter_DX,
+
+ S4444_opaque_D32_nofilter_DXDY,
+ S4444_alpha_D32_nofilter_DXDY,
+ S4444_opaque_D32_nofilter_DX,
+ S4444_alpha_D32_nofilter_DX,
+ S4444_opaque_D32_filter_DXDY,
+ S4444_alpha_D32_filter_DXDY,
+ S4444_opaque_D32_filter_DX,
+ S4444_alpha_D32_filter_DX,
+
+ // A8 treats alpha/opaque the same (equally efficient)
+ SA8_alpha_D32_nofilter_DXDY,
+ SA8_alpha_D32_nofilter_DXDY,
+ SA8_alpha_D32_nofilter_DX,
+ SA8_alpha_D32_nofilter_DX,
+ SA8_alpha_D32_filter_DXDY,
+ SA8_alpha_D32_filter_DXDY,
+ SA8_alpha_D32_filter_DX,
+ SA8_alpha_D32_filter_DX
+ };
+
+ static const SampleProc16 gSkBitmapProcStateSample16[] = {
+ S32_D16_nofilter_DXDY,
+ S32_D16_nofilter_DX,
+ S32_D16_filter_DXDY,
+ S32_D16_filter_DX,
+
+ S16_D16_nofilter_DXDY,
+ S16_D16_nofilter_DX,
+ S16_D16_filter_DXDY,
+ S16_D16_filter_DX,
+
+ SI8_D16_nofilter_DXDY,
+ SI8_D16_nofilter_DX,
+ SI8_D16_filter_DXDY,
+ SI8_D16_filter_DX,
+
+ // Don't support 4444 -> 565
+ NULL, NULL, NULL, NULL,
+ // Don't support A8 -> 565
+ NULL, NULL, NULL, NULL
+ };
+ #endif
+
+ fSampleProc32 = SK_ARM_NEON_WRAP(gSkBitmapProcStateSample32)[index];
+ index >>= 1; // shift away any opaque/alpha distinction
+ fSampleProc16 = SK_ARM_NEON_WRAP(gSkBitmapProcStateSample16)[index];
+
+ // our special-case shaderprocs
+ if (SK_ARM_NEON_WRAP(S16_D16_filter_DX) == fSampleProc16) {
+ if (clampClamp) {
+ fShaderProc16 = SK_ARM_NEON_WRAP(Clamp_S16_D16_filter_DX_shaderproc);
+ } else if (SkShader::kRepeat_TileMode == fTileModeX &&
+ SkShader::kRepeat_TileMode == fTileModeY) {
+ fShaderProc16 = SK_ARM_NEON_WRAP(Repeat_S16_D16_filter_DX_shaderproc);
+ }
+ } else if (SK_ARM_NEON_WRAP(SI8_opaque_D32_filter_DX) == fSampleProc32 && clampClamp) {
+ fShaderProc32 = SK_ARM_NEON_WRAP(Clamp_SI8_opaque_D32_filter_DX_shaderproc);
+ }
+
+ if (NULL == fShaderProc32) {
+ fShaderProc32 = this->chooseShaderProc32();
+ }
+ }
+
+ // see if our platform has any accelerated overrides
+ this->platformProcs();
+
+ return true;
+}
+
+static void Clamp_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(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+
+ const int maxX = s.fBitmap->width() - 1;
+ const int maxY = s.fBitmap->height() - 1;
+ int ix = s.fFilterOneX + x;
+ int iy = SkClampMax(s.fFilterOneY + y, maxY);
+#ifdef SK_DEBUG
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ int iy2 = SkClampMax(SkScalarFloorToInt(pt.fY), maxY);
+ int ix2 = SkScalarFloorToInt(pt.fX);
+
+ SkASSERT(iy == iy2);
+ SkASSERT(ix == ix2);
+ }
+#endif
+ const SkPMColor* row = s.fBitmap->getAddr32(0, iy);
+
+ // clamp to the left
+ if (ix < 0) {
+ int n = SkMin32(-ix, count);
+ sk_memset32(colors, row[0], n);
+ count -= n;
+ if (0 == count) {
+ return;
+ }
+ colors += n;
+ SkASSERT(-ix == n);
+ ix = 0;
+ }
+ // copy the middle
+ if (ix <= maxX) {
+ int n = SkMin32(maxX - ix + 1, count);
+ memcpy(colors, row + ix, n * sizeof(SkPMColor));
+ count -= n;
+ if (0 == count) {
+ return;
+ }
+ colors += n;
+ }
+ SkASSERT(count > 0);
+ // clamp to the right
+ 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 inline int sk_int_mirror(int x, int n) {
+ x = sk_int_mod(x, 2 * n);
+ if (x >= n) {
+ x = n + ~(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(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+
+ 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 S32_D32_constX_shaderproc(const SkBitmapProcState& s,
+ int x, int y,
+ SkPMColor* SK_RESTRICT colors,
+ int count) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(1 == s.fBitmap->width());
+
+ int iY0;
+ int iY1 SK_INIT_TO_AVOID_WARNING;
+ int iSubY SK_INIT_TO_AVOID_WARNING;
+
+ if (SkPaint::kNone_FilterLevel != s.fFilterLevel) {
+ SkBitmapProcState::MatrixProc mproc = s.getMatrixProc();
+ uint32_t xy[2];
+
+ mproc(s, xy, 1, x, y);
+
+ iY0 = xy[0] >> 18;
+ iY1 = xy[0] & 0x3FFF;
+ iSubY = (xy[0] >> 14) & 0xF;
+ } else {
+ int yTemp;
+
+ if (s.fInvType > SkMatrix::kTranslate_Mask) {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf,
+ &pt);
+ // When the matrix has a scale component the setup code in
+ // chooseProcs multiples the inverse matrix by the inverse of the
+ // bitmap's width and height. Since this method is going to do
+ // its own tiling and sampling we need to undo that here.
+ if (SkShader::kClamp_TileMode != s.fTileModeX ||
+ SkShader::kClamp_TileMode != s.fTileModeY) {
+ yTemp = SkScalarFloorToInt(pt.fY * s.fBitmap->height());
+ } else {
+ yTemp = SkScalarFloorToInt(pt.fY);
+ }
+ } else {
+ yTemp = s.fFilterOneY + y;
+ }
+
+ const int stopY = s.fBitmap->height();
+ switch (s.fTileModeY) {
+ case SkShader::kClamp_TileMode:
+ iY0 = SkClampMax(yTemp, stopY-1);
+ break;
+ case SkShader::kRepeat_TileMode:
+ iY0 = sk_int_mod(yTemp, stopY);
+ break;
+ case SkShader::kMirror_TileMode:
+ default:
+ iY0 = sk_int_mirror(yTemp, stopY);
+ break;
+ }
+
+#ifdef SK_DEBUG
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf,
+ &pt);
+ if (s.fInvType > SkMatrix::kTranslate_Mask &&
+ (SkShader::kClamp_TileMode != s.fTileModeX ||
+ SkShader::kClamp_TileMode != s.fTileModeY)) {
+ pt.fY *= s.fBitmap->height();
+ }
+ int iY2;
+
+ switch (s.fTileModeY) {
+ case SkShader::kClamp_TileMode:
+ iY2 = SkClampMax(SkScalarFloorToInt(pt.fY), stopY-1);
+ break;
+ case SkShader::kRepeat_TileMode:
+ iY2 = sk_int_mod(SkScalarFloorToInt(pt.fY), stopY);
+ break;
+ case SkShader::kMirror_TileMode:
+ default:
+ iY2 = sk_int_mirror(SkScalarFloorToInt(pt.fY), stopY);
+ break;
+ }
+
+ SkASSERT(iY0 == iY2);
+ }
+#endif
+ }
+
+ const SkPMColor* row0 = s.fBitmap->getAddr32(0, iY0);
+ SkPMColor color;
+
+ if (SkPaint::kNone_FilterLevel != s.fFilterLevel) {
+ const SkPMColor* row1 = s.fBitmap->getAddr32(0, iY1);
+
+ if (s.fAlphaScale < 256) {
+ Filter_32_alpha(iSubY, *row0, *row1, &color, s.fAlphaScale);
+ } else {
+ Filter_32_opaque(iSubY, *row0, *row1, &color);
+ }
+ } else {
+ if (s.fAlphaScale < 256) {
+ color = SkAlphaMulQ(*row0, s.fAlphaScale);
+ } else {
+ color = *row0;
+ }
+ }
+
+ sk_memset32(colors, color, count);
+}
+
+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 (SkBitmap::kARGB_8888_Config != fBitmap->config()) {
+ return NULL;
+ }
+
+ static const unsigned kMask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
+
+ if (1 == fBitmap->width() && 0 == (fInvType & ~kMask)) {
+ if (SkPaint::kNone_FilterLevel == fFilterLevel &&
+ fInvType <= SkMatrix::kTranslate_Mask &&
+ !this->setupForTranslate()) {
+ return DoNothing_shaderproc;
+ }
+ return S32_D32_constX_shaderproc;
+ }
+
+ if (fAlphaScale < 256) {
+ return NULL;
+ }
+ if (fInvType > SkMatrix::kTranslate_Mask) {
+ return NULL;
+ }
+ if (SkPaint::kNone_FilterLevel != fFilterLevel) {
+ return NULL;
+ }
+
+ 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;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+static void check_scale_nofilter(uint32_t bitmapXY[], int count,
+ unsigned mx, unsigned my) {
+ unsigned y = *bitmapXY++;
+ SkASSERT(y < my);
+
+ const uint16_t* xptr = reinterpret_cast<const uint16_t*>(bitmapXY);
+ for (int i = 0; i < count; ++i) {
+ SkASSERT(xptr[i] < mx);
+ }
+}
+
+static void check_scale_filter(uint32_t bitmapXY[], int count,
+ unsigned mx, unsigned my) {
+ uint32_t YY = *bitmapXY++;
+ unsigned y0 = YY >> 18;
+ unsigned y1 = YY & 0x3FFF;
+ SkASSERT(y0 < my);
+ SkASSERT(y1 < my);
+
+ for (int i = 0; i < count; ++i) {
+ uint32_t XX = bitmapXY[i];
+ unsigned x0 = XX >> 18;
+ unsigned x1 = XX & 0x3FFF;
+ SkASSERT(x0 < mx);
+ SkASSERT(x1 < mx);
+ }
+}
+
+static void check_affine_nofilter(uint32_t bitmapXY[], int count,
+ unsigned mx, unsigned my) {
+ for (int i = 0; i < count; ++i) {
+ uint32_t XY = bitmapXY[i];
+ unsigned x = XY & 0xFFFF;
+ unsigned y = XY >> 16;
+ SkASSERT(x < mx);
+ SkASSERT(y < my);
+ }
+}
+
+static void check_affine_filter(uint32_t bitmapXY[], int count,
+ unsigned mx, unsigned my) {
+ for (int i = 0; i < count; ++i) {
+ uint32_t YY = *bitmapXY++;
+ unsigned y0 = YY >> 18;
+ unsigned y1 = YY & 0x3FFF;
+ SkASSERT(y0 < my);
+ SkASSERT(y1 < my);
+
+ uint32_t XX = *bitmapXY++;
+ unsigned x0 = XX >> 18;
+ unsigned x1 = XX & 0x3FFF;
+ SkASSERT(x0 < mx);
+ SkASSERT(x1 < mx);
+ }
+}
+
+void SkBitmapProcState::DebugMatrixProc(const SkBitmapProcState& state,
+ uint32_t bitmapXY[], int count,
+ int x, int y) {
+ SkASSERT(bitmapXY);
+ SkASSERT(count > 0);
+
+ state.fMatrixProc(state, bitmapXY, count, x, y);
+
+ void (*proc)(uint32_t bitmapXY[], int count, unsigned mx, unsigned my);
+
+ // There are four formats possible:
+ // scale -vs- affine
+ // filter -vs- nofilter
+ if (state.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) {
+ proc = state.fFilterLevel != SkPaint::kNone_FilterLevel ? check_scale_filter : check_scale_nofilter;
+ } else {
+ proc = state.fFilterLevel != SkPaint::kNone_FilterLevel ? check_affine_filter : check_affine_nofilter;
+ }
+ proc(bitmapXY, count, state.fBitmap->width(), state.fBitmap->height());
+}
+
+SkBitmapProcState::MatrixProc SkBitmapProcState::getMatrixProc() const {
+ return DebugMatrixProc;
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ The storage requirements for the different matrix procs are as follows,
+ where each X or Y is 2 bytes, and N is the number of pixels/elements:
+
+ scale/translate nofilter Y(4bytes) + N * X
+ affine/perspective nofilter N * (X Y)
+ scale/translate filter Y Y + N * (X X)
+ affine/perspective filter N * (Y Y X X)
+ */
+int SkBitmapProcState::maxCountForBufferSize(size_t bufferSize) const {
+ int32_t size = static_cast<int32_t>(bufferSize);
+
+ size &= ~3; // only care about 4-byte aligned chunks
+ if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) {
+ size -= 4; // the shared Y (or YY) coordinate
+ if (size < 0) {
+ size = 0;
+ }
+ size >>= 1;
+ } else {
+ size >>= 2;
+ }
+
+ if (fFilterLevel != SkPaint::kNone_FilterLevel) {
+ size >>= 1;
+ }
+
+ return size;
+}
diff --git a/core/SkBitmapProcState.h b/core/SkBitmapProcState.h
new file mode 100644
index 00000000..e138ed26
--- /dev/null
+++ b/core/SkBitmapProcState.h
@@ -0,0 +1,229 @@
+
+/*
+ * Copyright 2007 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 SkBitmapProcState_DEFINED
+#define SkBitmapProcState_DEFINED
+
+#include "SkBitmap.h"
+#include "SkBitmapFilter.h"
+#include "SkMatrix.h"
+#include "SkScaledImageCache.h"
+
+#define FractionalInt_IS_64BIT
+
+#ifdef FractionalInt_IS_64BIT
+ typedef SkFixed48 SkFractionalInt;
+ #define SkScalarToFractionalInt(x) SkScalarToFixed48(x)
+ #define SkFractionalIntToFixed(x) SkFixed48ToFixed(x)
+ #define SkFixedToFractionalInt(x) SkFixedToFixed48(x)
+ #define SkFractionalIntToInt(x) SkFixed48ToInt(x)
+#else
+ typedef SkFixed SkFractionalInt;
+ #define SkScalarToFractionalInt(x) SkScalarToFixed(x)
+ #define SkFractionalIntToFixed(x) (x)
+ #define SkFixedToFractionalInt(x) (x)
+ #define SkFractionalIntToInt(x) ((x) >> 16)
+#endif
+
+class SkPaint;
+struct SkConvolutionProcs;
+
+struct SkBitmapProcState {
+
+ SkBitmapProcState(): fScaledCacheID(NULL), fBitmapFilter(NULL) {}
+ ~SkBitmapProcState();
+
+ typedef void (*ShaderProc32)(const SkBitmapProcState&, int x, int y,
+ SkPMColor[], int count);
+
+ typedef void (*ShaderProc16)(const SkBitmapProcState&, int x, int y,
+ uint16_t[], int count);
+
+ typedef void (*MatrixProc)(const SkBitmapProcState&,
+ uint32_t bitmapXY[],
+ int count,
+ int x, int y);
+
+ typedef void (*SampleProc32)(const SkBitmapProcState&,
+ const uint32_t[],
+ int count,
+ SkPMColor colors[]);
+
+ typedef void (*SampleProc16)(const SkBitmapProcState&,
+ const uint32_t[],
+ int count,
+ uint16_t colors[]);
+
+ typedef U16CPU (*FixedTileProc)(SkFixed); // returns 0..0xFFFF
+ typedef U16CPU (*FixedTileLowBitsProc)(SkFixed, int); // returns 0..0xF
+ typedef U16CPU (*IntTileProc)(int value, int count); // returns 0..count-1
+
+ const SkBitmap* fBitmap; // chooseProcs - orig or scaled
+ SkMatrix fInvMatrix; // chooseProcs
+ SkMatrix::MapXYProc fInvProc; // chooseProcs
+
+ SkFractionalInt fInvSxFractionalInt;
+ SkFractionalInt fInvKyFractionalInt;
+
+ FixedTileProc fTileProcX; // chooseProcs
+ FixedTileProc fTileProcY; // chooseProcs
+ FixedTileLowBitsProc fTileLowBitsProcX; // chooseProcs
+ FixedTileLowBitsProc fTileLowBitsProcY; // chooseProcs
+ IntTileProc fIntTileProcY; // chooseProcs
+ SkFixed fFilterOneX;
+ SkFixed fFilterOneY;
+
+ SkConvolutionProcs* fConvolutionProcs; // possiblyScaleImage
+
+ SkPMColor fPaintPMColor; // chooseProcs - A8 config
+ SkFixed fInvSx; // chooseProcs
+ SkFixed fInvKy; // chooseProcs
+ uint16_t fAlphaScale; // chooseProcs
+ uint8_t fInvType; // chooseProcs
+ uint8_t fTileModeX; // CONSTRUCTOR
+ uint8_t fTileModeY; // CONSTRUCTOR
+ uint8_t fFilterLevel; // chooseProcs
+
+ /** The shader will let us know when we can release some of our resources
+ * like scaled bitmaps.
+ */
+
+ void endContext();
+
+ /** Platforms implement this, and can optionally overwrite only the
+ following fields:
+
+ fShaderProc32
+ fShaderProc16
+ fMatrixProc
+ fSampleProc32
+ fSampleProc32
+
+ They will already have valid function pointers, so a platform that does
+ not have an accelerated version can just leave that field as is. A valid
+ implementation can do nothing (see SkBitmapProcState_opts_none.cpp)
+ */
+ void platformProcs();
+
+ /** Platforms can also optionally overwrite the convolution functions
+ if we have SIMD versions of them.
+ */
+
+ void platformConvolutionProcs();
+
+ /** Given the byte size of the index buffer to be passed to the matrix proc,
+ return the maximum number of resulting pixels that can be computed
+ (i.e. the number of SkPMColor values to be written by the sample proc).
+ This routine takes into account that filtering and scale-vs-affine
+ affect the amount of buffer space needed.
+
+ Only valid to call after chooseProcs (setContext) has been called. It is
+ safe to call this inside the shader's shadeSpan() method.
+ */
+ int maxCountForBufferSize(size_t bufferSize) const;
+
+ // If a shader proc is present, then the corresponding matrix/sample procs
+ // are ignored
+ ShaderProc32 getShaderProc32() const { return fShaderProc32; }
+ ShaderProc16 getShaderProc16() const { return fShaderProc16; }
+
+ SkBitmapFilter* getBitmapFilter() const { return fBitmapFilter; }
+
+#ifdef SK_DEBUG
+ MatrixProc getMatrixProc() const;
+#else
+ MatrixProc getMatrixProc() const { return fMatrixProc; }
+#endif
+ SampleProc32 getSampleProc32() const { return fSampleProc32; }
+ SampleProc16 getSampleProc16() const { return fSampleProc16; }
+
+private:
+ friend class SkBitmapProcShader;
+
+ ShaderProc32 fShaderProc32; // chooseProcs
+ ShaderProc16 fShaderProc16; // chooseProcs
+ // These are used if the shaderproc is NULL
+ MatrixProc fMatrixProc; // chooseProcs
+ SampleProc32 fSampleProc32; // chooseProcs
+ SampleProc16 fSampleProc16; // chooseProcs
+
+ SkBitmap fOrigBitmap; // CONSTRUCTOR
+ SkBitmap fScaledBitmap; // chooseProcs
+
+ SkScaledImageCache::ID* fScaledCacheID;
+
+ MatrixProc chooseMatrixProc(bool trivial_matrix);
+ bool chooseProcs(const SkMatrix& inv, const SkPaint&);
+ ShaderProc32 chooseShaderProc32();
+
+ void possiblyScaleImage();
+
+ SkBitmapFilter* fBitmapFilter;
+
+ ShaderProc32 chooseBitmapFilterProc();
+
+ // 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);
+#endif
+};
+
+/* Macros for packing and unpacking pairs of 16bit values in a 32bit uint.
+ Used to allow access to a stream of uint16_t either one at a time, or
+ 2 at a time by unpacking a uint32_t
+ */
+#ifdef SK_CPU_BENDIAN
+ #define PACK_TWO_SHORTS(pri, sec) ((pri) << 16 | (sec))
+ #define UNPACK_PRIMARY_SHORT(packed) ((uint32_t)(packed) >> 16)
+ #define UNPACK_SECONDARY_SHORT(packed) ((packed) & 0xFFFF)
+#else
+ #define PACK_TWO_SHORTS(pri, sec) ((pri) | ((sec) << 16))
+ #define UNPACK_PRIMARY_SHORT(packed) ((packed) & 0xFFFF)
+ #define UNPACK_SECONDARY_SHORT(packed) ((uint32_t)(packed) >> 16)
+#endif
+
+#ifdef SK_DEBUG
+ static inline uint32_t pack_two_shorts(U16CPU pri, U16CPU sec) {
+ SkASSERT((uint16_t)pri == pri);
+ SkASSERT((uint16_t)sec == sec);
+ return PACK_TWO_SHORTS(pri, sec);
+ }
+#else
+ #define pack_two_shorts(pri, sec) PACK_TWO_SHORTS(pri, sec)
+#endif
+
+// These functions are generated via macros, but are exposed here so that
+// platformProcs may test for them by name.
+void S32_opaque_D32_filter_DX(const SkBitmapProcState& s, const uint32_t xy[],
+ int count, SkPMColor colors[]);
+void S32_alpha_D32_filter_DX(const SkBitmapProcState& s, const uint32_t xy[],
+ int count, SkPMColor colors[]);
+void S32_opaque_D32_filter_DXDY(const SkBitmapProcState& s,
+ const uint32_t xy[], int count, SkPMColor colors[]);
+void S32_alpha_D32_filter_DXDY(const SkBitmapProcState& s,
+ const uint32_t xy[], int count, SkPMColor colors[]);
+void ClampX_ClampY_filter_scale(const SkBitmapProcState& s, uint32_t xy[],
+ int count, int x, int y);
+void ClampX_ClampY_nofilter_scale(const SkBitmapProcState& s, uint32_t xy[],
+ int count, int x, int y);
+void ClampX_ClampY_filter_affine(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void ClampX_ClampY_nofilter_affine(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void S32_D16_filter_DX(const SkBitmapProcState& s,
+ const uint32_t* xy, int count, uint16_t* colors);
+
+void highQualityFilter(const SkBitmapProcState &s, int x, int y,
+ SkPMColor *SK_RESTRICT colors, int count);
+
+
+#endif
diff --git a/core/SkBitmapProcState_filter.h b/core/SkBitmapProcState_filter.h
new file mode 100644
index 00000000..12606656
--- /dev/null
+++ b/core/SkBitmapProcState_filter.h
@@ -0,0 +1,125 @@
+
+/*
+ * Copyright 2009 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 "SkColorPriv.h"
+
+/*
+ Filter_32_opaque
+
+ There is no hard-n-fast rule that the filtering must produce
+ exact results for the color components, but if the 4 incoming colors are
+ all opaque, then the output color must also be opaque. Subsequent parts of
+ the drawing pipeline may rely on this (e.g. which blitrow proc to use).
+ */
+
+static inline void Filter_32_opaque(unsigned x, unsigned y,
+ SkPMColor a00, SkPMColor a01,
+ SkPMColor a10, SkPMColor a11,
+ SkPMColor* dstColor) {
+ SkASSERT((unsigned)x <= 0xF);
+ SkASSERT((unsigned)y <= 0xF);
+
+ int xy = x * y;
+ static const uint32_t mask = gMask_00FF00FF; //0xFF00FF;
+
+ int scale = 256 - 16*y - 16*x + xy;
+ uint32_t lo = (a00 & mask) * scale;
+ uint32_t hi = ((a00 >> 8) & mask) * scale;
+
+ scale = 16*x - xy;
+ lo += (a01 & mask) * scale;
+ hi += ((a01 >> 8) & mask) * scale;
+
+ scale = 16*y - xy;
+ lo += (a10 & mask) * scale;
+ hi += ((a10 >> 8) & mask) * scale;
+
+ lo += (a11 & mask) * xy;
+ hi += ((a11 >> 8) & mask) * xy;
+
+ *dstColor = ((lo >> 8) & mask) | (hi & ~mask);
+}
+
+static inline void Filter_32_alpha(unsigned x, unsigned y,
+ SkPMColor a00, SkPMColor a01,
+ SkPMColor a10, SkPMColor a11,
+ SkPMColor* dstColor,
+ unsigned alphaScale) {
+ SkASSERT((unsigned)x <= 0xF);
+ SkASSERT((unsigned)y <= 0xF);
+ SkASSERT(alphaScale <= 256);
+
+ int xy = x * y;
+ static const uint32_t mask = gMask_00FF00FF; //0xFF00FF;
+
+ int scale = 256 - 16*y - 16*x + xy;
+ uint32_t lo = (a00 & mask) * scale;
+ uint32_t hi = ((a00 >> 8) & mask) * scale;
+
+ scale = 16*x - xy;
+ lo += (a01 & mask) * scale;
+ hi += ((a01 >> 8) & mask) * scale;
+
+ scale = 16*y - xy;
+ lo += (a10 & mask) * scale;
+ hi += ((a10 >> 8) & mask) * scale;
+
+ lo += (a11 & mask) * xy;
+ hi += ((a11 >> 8) & mask) * xy;
+
+ lo = ((lo >> 8) & mask) * alphaScale;
+ hi = ((hi >> 8) & mask) * alphaScale;
+
+ *dstColor = ((lo >> 8) & mask) | (hi & ~mask);
+}
+
+// Two color version, where we filter only along 1 axis
+static inline void Filter_32_opaque(unsigned t,
+ SkPMColor color0,
+ SkPMColor color1,
+ SkPMColor* dstColor) {
+ SkASSERT((unsigned)t <= 0xF);
+
+ static const uint32_t mask = gMask_00FF00FF; //0x00FF00FF;
+
+ int scale = 256 - 16*t;
+ uint32_t lo = (color0 & mask) * scale;
+ uint32_t hi = ((color0 >> 8) & mask) * scale;
+
+ scale = 16*t;
+ lo += (color1 & mask) * scale;
+ hi += ((color1 >> 8) & mask) * scale;
+
+ *dstColor = ((lo >> 8) & mask) | (hi & ~mask);
+}
+
+// Two color version, where we filter only along 1 axis
+static inline void Filter_32_alpha(unsigned t,
+ SkPMColor color0,
+ SkPMColor color1,
+ SkPMColor* dstColor,
+ unsigned alphaScale) {
+ SkASSERT((unsigned)t <= 0xF);
+ SkASSERT(alphaScale <= 256);
+
+ static const uint32_t mask = gMask_00FF00FF; //0x00FF00FF;
+
+ int scale = 256 - 16*t;
+ uint32_t lo = (color0 & mask) * scale;
+ uint32_t hi = ((color0 >> 8) & mask) * scale;
+
+ scale = 16*t;
+ lo += (color1 & mask) * scale;
+ hi += ((color1 >> 8) & mask) * scale;
+
+ lo = ((lo >> 8) & mask) * alphaScale;
+ hi = ((hi >> 8) & mask) * alphaScale;
+
+ *dstColor = ((lo >> 8) & mask) | (hi & ~mask);
+}
diff --git a/core/SkBitmapProcState_matrix.h b/core/SkBitmapProcState_matrix.h
new file mode 100644
index 00000000..d796d0b0
--- /dev/null
+++ b/core/SkBitmapProcState_matrix.h
@@ -0,0 +1,303 @@
+
+/*
+ * 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 "SkMath.h"
+#include "SkMathPriv.h"
+
+#define SCALE_NOFILTER_NAME MAKENAME(_nofilter_scale)
+#define SCALE_FILTER_NAME MAKENAME(_filter_scale)
+#define AFFINE_NOFILTER_NAME MAKENAME(_nofilter_affine)
+#define AFFINE_FILTER_NAME MAKENAME(_filter_affine)
+#define PERSP_NOFILTER_NAME MAKENAME(_nofilter_persp)
+#define PERSP_FILTER_NAME MAKENAME(_filter_persp)
+
+#define PACK_FILTER_X_NAME MAKENAME(_pack_filter_x)
+#define PACK_FILTER_Y_NAME MAKENAME(_pack_filter_y)
+
+#ifndef PREAMBLE
+ #define PREAMBLE(state)
+ #define PREAMBLE_PARAM_X
+ #define PREAMBLE_PARAM_Y
+ #define PREAMBLE_ARG_X
+ #define PREAMBLE_ARG_Y
+#endif
+
+// declare functions externally to suppress warnings.
+void SCALE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void AFFINE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void PERSP_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy,
+ int count, int x, int y);
+void SCALE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void AFFINE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void PERSP_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy, int count,
+ int x, int y);
+
+void SCALE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+
+ PREAMBLE(s);
+ // we store y, x, x, x, x, x
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ SkFractionalInt fx;
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ fx = SkScalarToFractionalInt(pt.fY);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ *xy++ = TILEY_PROCF(SkFractionalIntToFixed(fx), maxY);
+ fx = SkScalarToFractionalInt(pt.fX);
+ }
+
+ if (0 == maxX) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ const SkFractionalInt dx = s.fInvSxFractionalInt;
+
+#ifdef CHECK_FOR_DECAL
+ if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
+ decal_nofilter_scale(xy, SkFractionalIntToFixed(fx),
+ SkFractionalIntToFixed(dx), count);
+ } else
+#endif
+ {
+ int i;
+ for (i = (count >> 2); i > 0; --i) {
+ unsigned a, b;
+ a = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX); fx += dx;
+ b = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX); fx += dx;
+#ifdef SK_CPU_BENDIAN
+ *xy++ = (a << 16) | b;
+#else
+ *xy++ = (b << 16) | a;
+#endif
+ a = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX); fx += dx;
+ b = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX); fx += dx;
+#ifdef SK_CPU_BENDIAN
+ *xy++ = (a << 16) | b;
+#else
+ *xy++ = (b << 16) | a;
+#endif
+ }
+ uint16_t* xx = (uint16_t*)xy;
+ for (i = (count & 3); i > 0; --i) {
+ *xx++ = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX); fx += dx;
+ }
+ }
+}
+
+// note: we could special-case on a matrix which is skewed in X but not Y.
+// this would require a more general setup thatn SCALE does, but could use
+// SCALE's inner loop that only looks at dx
+
+void AFFINE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFractionalInt fx = SkScalarToFractionalInt(srcPt.fX);
+ SkFractionalInt fy = SkScalarToFractionalInt(srcPt.fY);
+ SkFractionalInt dx = s.fInvSxFractionalInt;
+ SkFractionalInt dy = s.fInvKyFractionalInt;
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ for (int i = count; i > 0; --i) {
+ *xy++ = (TILEY_PROCF(SkFractionalIntToFixed(fy), maxY) << 16) |
+ TILEX_PROCF(SkFractionalIntToFixed(fx), maxX);
+ fx += dx; fy += dy;
+ }
+}
+
+void PERSP_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy,
+ int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ PREAMBLE(s);
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+ while (--count >= 0) {
+ *xy++ = (TILEY_PROCF(srcXY[1], maxY) << 16) |
+ TILEX_PROCF(srcXY[0], maxX);
+ srcXY += 2;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static inline uint32_t PACK_FILTER_Y_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_Y) {
+ unsigned i = TILEY_PROCF(f, max);
+ i = (i << 4) | TILEY_LOW_BITS(f, max);
+ return (i << 14) | (TILEY_PROCF((f + one), max));
+}
+
+static inline uint32_t PACK_FILTER_X_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_X) {
+ unsigned i = TILEX_PROCF(f, max);
+ i = (i << 4) | TILEX_LOW_BITS(f, max);
+ return (i << 14) | (TILEX_PROCF((f + one), max));
+}
+
+void SCALE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+
+ PREAMBLE(s);
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ const SkFixed one = s.fFilterOneX;
+ const SkFractionalInt dx = s.fInvSxFractionalInt;
+ SkFractionalInt fx;
+
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ const SkFixed fy = SkScalarToFixed(pt.fY) - (s.fFilterOneY >> 1);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ // compute our two Y values up front
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, s.fFilterOneY PREAMBLE_ARG_Y);
+ // now initialize fx
+ fx = SkScalarToFractionalInt(pt.fX) - (SkFixedToFractionalInt(one) >> 1);
+ }
+
+#ifdef CHECK_FOR_DECAL
+ if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
+ decal_filter_scale(xy, SkFractionalIntToFixed(fx),
+ SkFractionalIntToFixed(dx), count);
+ } else
+#endif
+ {
+ do {
+ SkFixed fixedFx = SkFractionalIntToFixed(fx);
+ *xy++ = PACK_FILTER_X_NAME(fixedFx, maxX, one PREAMBLE_ARG_X);
+ fx += dx;
+ } while (--count != 0);
+ }
+}
+
+void AFFINE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+ SkFixed fx = SkScalarToFixed(srcPt.fX) - (oneX >> 1);
+ SkFixed fy = SkScalarToFixed(srcPt.fY) - (oneY >> 1);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+
+ do {
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, oneY PREAMBLE_ARG_Y);
+ fy += dy;
+ *xy++ = PACK_FILTER_X_NAME(fx, maxX, oneX PREAMBLE_ARG_X);
+ fx += dx;
+ } while (--count != 0);
+}
+
+void PERSP_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy, int count,
+ int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ PREAMBLE(s);
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+ do {
+ *xy++ = PACK_FILTER_Y_NAME(srcXY[1] - (oneY >> 1), maxY,
+ oneY PREAMBLE_ARG_Y);
+ *xy++ = PACK_FILTER_X_NAME(srcXY[0] - (oneX >> 1), maxX,
+ oneX PREAMBLE_ARG_X);
+ srcXY += 2;
+ } while (--count != 0);
+ }
+}
+
+static SkBitmapProcState::MatrixProc MAKENAME(_Procs)[] = {
+ SCALE_NOFILTER_NAME,
+ SCALE_FILTER_NAME,
+ AFFINE_NOFILTER_NAME,
+ AFFINE_FILTER_NAME,
+ PERSP_NOFILTER_NAME,
+ PERSP_FILTER_NAME
+};
+
+#undef MAKENAME
+#undef TILEX_PROCF
+#undef TILEY_PROCF
+#ifdef CHECK_FOR_DECAL
+ #undef CHECK_FOR_DECAL
+#endif
+
+#undef SCALE_NOFILTER_NAME
+#undef SCALE_FILTER_NAME
+#undef AFFINE_NOFILTER_NAME
+#undef AFFINE_FILTER_NAME
+#undef PERSP_NOFILTER_NAME
+#undef PERSP_FILTER_NAME
+
+#undef PREAMBLE
+#undef PREAMBLE_PARAM_X
+#undef PREAMBLE_PARAM_Y
+#undef PREAMBLE_ARG_X
+#undef PREAMBLE_ARG_Y
+
+#undef TILEX_LOW_BITS
+#undef TILEY_LOW_BITS
diff --git a/core/SkBitmapProcState_matrixProcs.cpp b/core/SkBitmapProcState_matrixProcs.cpp
new file mode 100644
index 00000000..70d367b8
--- /dev/null
+++ b/core/SkBitmapProcState_matrixProcs.cpp
@@ -0,0 +1,501 @@
+/* NEON optimized code (C) COPYRIGHT 2009 Motorola
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapProcState.h"
+#include "SkPerspIter.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkUtilsArm.h"
+
+// Helper to ensure that when we shift down, we do it w/o sign-extension
+// so the caller doesn't have to manually mask off the top 16 bits
+//
+static unsigned SK_USHIFT16(unsigned x) {
+ return x >> 16;
+}
+
+/* returns 0...(n-1) given any x (positive or negative).
+
+ As an example, if n (which is always positive) is 5...
+
+ x: -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8
+ returns: 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3
+ */
+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;
+}
+
+/*
+ * The decal_ functions require that
+ * 1. dx > 0
+ * 2. [fx, fx+dx, fx+2dx, fx+3dx, ... fx+(count-1)dx] are all <= maxX
+ *
+ * In addition, we use SkFractionalInt to keep more fractional precision than
+ * just SkFixed, so we will abort the decal_ call if dx is very small, since
+ * the decal_ function just operates on SkFixed. If that were changed, we could
+ * skip the very_small test here.
+ */
+static inline bool can_truncate_to_fixed_for_decal(SkFractionalInt frX,
+ SkFractionalInt frDx,
+ int count, unsigned max) {
+ SkFixed dx = SkFractionalIntToFixed(frDx);
+
+ // if decal_ kept SkFractionalInt precision, this would just be dx <= 0
+ // I just made up the 1/256. Just don't want to perceive accumulated error
+ // if we truncate frDx and lose its low bits.
+ if (dx <= SK_Fixed1 / 256) {
+ return false;
+ }
+
+ // We cast to unsigned so we don't have to check for negative values, which
+ // will now appear as very large positive values, and thus fail our test!
+ SkFixed fx = SkFractionalIntToFixed(frX);
+ return (unsigned)SkFixedFloorToInt(fx) <= max &&
+ (unsigned)SkFixedFloorToInt(fx + dx * (count - 1)) < max;
+}
+
+void decal_nofilter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count);
+void decal_filter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count);
+
+// Compile neon code paths if needed
+#if !SK_ARM_NEON_IS_NONE
+
+// These are defined in src/opts/SkBitmapProcState_matrixProcs_neon.cpp
+extern const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs_neon[];
+extern const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs_neon[];
+
+#endif // !SK_ARM_NEON_IS_NONE
+
+// Compile non-neon code path if needed
+#if !SK_ARM_NEON_IS_ALWAYS
+#define MAKENAME(suffix) ClampX_ClampY ## suffix
+#define TILEX_PROCF(fx, max) SkClampMax((fx) >> 16, max)
+#define TILEY_PROCF(fy, max) SkClampMax((fy) >> 16, max)
+#define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF)
+#define CHECK_FOR_DECAL
+#include "SkBitmapProcState_matrix.h"
+
+#define MAKENAME(suffix) RepeatX_RepeatY ## suffix
+#define TILEX_PROCF(fx, max) SK_USHIFT16(((fx) & 0xFFFF) * ((max) + 1))
+#define TILEY_PROCF(fy, max) SK_USHIFT16(((fy) & 0xFFFF) * ((max) + 1))
+#define TILEX_LOW_BITS(fx, max) ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) ((((fy) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+#include "SkBitmapProcState_matrix.h"
+#endif
+
+#define MAKENAME(suffix) GeneralXY ## suffix
+#define PREAMBLE(state) SkBitmapProcState::FixedTileProc tileProcX = (state).fTileProcX; (void) tileProcX; \
+ SkBitmapProcState::FixedTileProc tileProcY = (state).fTileProcY; (void) tileProcY; \
+ SkBitmapProcState::FixedTileLowBitsProc tileLowBitsProcX = (state).fTileLowBitsProcX; (void) tileLowBitsProcX; \
+ SkBitmapProcState::FixedTileLowBitsProc tileLowBitsProcY = (state).fTileLowBitsProcY; (void) tileLowBitsProcY
+#define PREAMBLE_PARAM_X , SkBitmapProcState::FixedTileProc tileProcX, SkBitmapProcState::FixedTileLowBitsProc tileLowBitsProcX
+#define PREAMBLE_PARAM_Y , SkBitmapProcState::FixedTileProc tileProcY, SkBitmapProcState::FixedTileLowBitsProc tileLowBitsProcY
+#define PREAMBLE_ARG_X , tileProcX, tileLowBitsProcX
+#define PREAMBLE_ARG_Y , tileProcY, tileLowBitsProcY
+#define TILEX_PROCF(fx, max) SK_USHIFT16(tileProcX(fx) * ((max) + 1))
+#define TILEY_PROCF(fy, max) SK_USHIFT16(tileProcY(fy) * ((max) + 1))
+#define TILEX_LOW_BITS(fx, max) tileLowBitsProcX(fx, (max) + 1)
+#define TILEY_LOW_BITS(fy, max) tileLowBitsProcY(fy, (max) + 1)
+#include "SkBitmapProcState_matrix.h"
+
+static inline U16CPU fixed_clamp(SkFixed x)
+{
+ if (x < 0) {
+ x = 0;
+ }
+ if (x >> 16) {
+ x = 0xFFFF;
+ }
+ return x;
+}
+
+static inline U16CPU fixed_repeat(SkFixed x)
+{
+ return x & 0xFFFF;
+}
+
+// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
+// See http://code.google.com/p/skia/issues/detail?id=472
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", off)
+#endif
+
+static inline U16CPU fixed_mirror(SkFixed x)
+{
+ SkFixed s = x << 15 >> 31;
+ // s is FFFFFFFF if we're on an odd interval, or 0 if an even interval
+ return (x ^ s) & 0xFFFF;
+}
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", on)
+#endif
+
+static SkBitmapProcState::FixedTileProc choose_tile_proc(unsigned m)
+{
+ if (SkShader::kClamp_TileMode == m)
+ return fixed_clamp;
+ if (SkShader::kRepeat_TileMode == m)
+ return fixed_repeat;
+ SkASSERT(SkShader::kMirror_TileMode == m);
+ return fixed_mirror;
+}
+
+static inline U16CPU fixed_clamp_lowbits(SkFixed x, int) {
+ return (x >> 12) & 0xF;
+}
+
+static inline U16CPU fixed_repeat_or_mirrow_lowbits(SkFixed x, int scale) {
+ return ((x * scale) >> 12) & 0xF;
+}
+
+static SkBitmapProcState::FixedTileLowBitsProc choose_tile_lowbits_proc(unsigned m) {
+ if (SkShader::kClamp_TileMode == m) {
+ return fixed_clamp_lowbits;
+ } else {
+ SkASSERT(SkShader::kMirror_TileMode == m ||
+ SkShader::kRepeat_TileMode == m);
+ // mirror and repeat have the same behavior for the low bits.
+ return fixed_repeat_or_mirrow_lowbits;
+ }
+}
+
+static inline U16CPU int_clamp(int x, int n) {
+ if (x >= n) {
+ x = n - 1;
+ }
+ if (x < 0) {
+ x = 0;
+ }
+ return x;
+}
+
+static inline U16CPU int_repeat(int x, int n) {
+ return sk_int_mod(x, n);
+}
+
+static inline U16CPU int_mirror(int x, int n) {
+ x = sk_int_mod(x, 2 * n);
+ if (x >= n) {
+ x = n + ~(x - n);
+ }
+ return x;
+}
+
+#if 0
+static void test_int_tileprocs() {
+ for (int i = -8; i <= 8; i++) {
+ SkDebugf(" int_mirror(%2d, 3) = %d\n", i, int_mirror(i, 3));
+ }
+}
+#endif
+
+static SkBitmapProcState::IntTileProc choose_int_tile_proc(unsigned tm) {
+ if (SkShader::kClamp_TileMode == tm)
+ return int_clamp;
+ if (SkShader::kRepeat_TileMode == tm)
+ return int_repeat;
+ SkASSERT(SkShader::kMirror_TileMode == tm);
+ return int_mirror;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void decal_nofilter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count)
+{
+ int i;
+
+ for (i = (count >> 2); i > 0; --i)
+ {
+ *dst++ = pack_two_shorts(fx >> 16, (fx + dx) >> 16);
+ fx += dx+dx;
+ *dst++ = pack_two_shorts(fx >> 16, (fx + dx) >> 16);
+ fx += dx+dx;
+ }
+ count &= 3;
+
+ uint16_t* xx = (uint16_t*)dst;
+ for (i = count; i > 0; --i) {
+ *xx++ = SkToU16(fx >> 16); fx += dx;
+ }
+}
+
+void decal_filter_scale(uint32_t dst[], SkFixed fx, SkFixed dx, int count)
+{
+
+
+ if (count & 1)
+ {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ }
+ while ((count -= 2) >= 0)
+ {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// stores the same as SCALE, but is cheaper to compute. Also since there is no
+// scale, we don't need/have a FILTER version
+
+static void fill_sequential(uint16_t xptr[], int start, int count) {
+#if 1
+ if (reinterpret_cast<intptr_t>(xptr) & 0x2) {
+ *xptr++ = start++;
+ count -= 1;
+ }
+ if (count > 3) {
+ uint32_t* xxptr = reinterpret_cast<uint32_t*>(xptr);
+ uint32_t pattern0 = PACK_TWO_SHORTS(start + 0, start + 1);
+ uint32_t pattern1 = PACK_TWO_SHORTS(start + 2, start + 3);
+ start += count & ~3;
+ int qcount = count >> 2;
+ do {
+ *xxptr++ = pattern0;
+ pattern0 += 0x40004;
+ *xxptr++ = pattern1;
+ pattern1 += 0x40004;
+ } while (--qcount != 0);
+ xptr = reinterpret_cast<uint16_t*>(xxptr);
+ count &= 3;
+ }
+ while (--count >= 0) {
+ *xptr++ = start++;
+ }
+#else
+ for (int i = 0; i < count; i++) {
+ *xptr++ = start++;
+ }
+#endif
+}
+
+static int nofilter_trans_preamble(const SkBitmapProcState& s, uint32_t** xy,
+ int x, int y) {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ **xy = s.fIntTileProcY(SkScalarToFixed(pt.fY) >> 16,
+ s.fBitmap->height());
+ *xy += 1; // bump the ptr
+ // return our starting X position
+ return SkScalarToFixed(pt.fX) >> 16;
+}
+
+static void clampx_nofilter_trans(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0);
+
+ int xpos = nofilter_trans_preamble(s, &xy, x, y);
+ const int width = s.fBitmap->width();
+ if (1 == width) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
+ int n;
+
+ // fill before 0 as needed
+ if (xpos < 0) {
+ n = -xpos;
+ if (n > count) {
+ n = count;
+ }
+ memset(xptr, 0, n * sizeof(uint16_t));
+ count -= n;
+ if (0 == count) {
+ return;
+ }
+ xptr += n;
+ xpos = 0;
+ }
+
+ // fill in 0..width-1 if needed
+ if (xpos < width) {
+ n = width - xpos;
+ if (n > count) {
+ n = count;
+ }
+ fill_sequential(xptr, xpos, n);
+ count -= n;
+ if (0 == count) {
+ return;
+ }
+ xptr += n;
+ }
+
+ // fill the remaining with the max value
+ sk_memset16(xptr, width - 1, count);
+}
+
+static void repeatx_nofilter_trans(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0);
+
+ int xpos = nofilter_trans_preamble(s, &xy, x, y);
+ const int width = s.fBitmap->width();
+ if (1 == width) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
+ int start = sk_int_mod(xpos, width);
+ int n = width - start;
+ if (n > count) {
+ n = count;
+ }
+ fill_sequential(xptr, start, n);
+ xptr += n;
+ count -= n;
+
+ while (count >= width) {
+ fill_sequential(xptr, 0, width);
+ xptr += width;
+ count -= width;
+ }
+
+ if (count > 0) {
+ fill_sequential(xptr, 0, count);
+ }
+}
+
+static void fill_backwards(uint16_t xptr[], int pos, int count) {
+ for (int i = 0; i < count; i++) {
+ SkASSERT(pos >= 0);
+ xptr[i] = pos--;
+ }
+}
+
+static void mirrorx_nofilter_trans(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0);
+
+ int xpos = nofilter_trans_preamble(s, &xy, x, y);
+ const int width = s.fBitmap->width();
+ if (1 == width) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ uint16_t* xptr = reinterpret_cast<uint16_t*>(xy);
+ // need to know our start, and our initial phase (forward or backward)
+ bool forward;
+ int n;
+ int start = sk_int_mod(xpos, 2 * width);
+ if (start >= width) {
+ start = width + ~(start - width);
+ forward = false;
+ n = start + 1; // [start .. 0]
+ } else {
+ forward = true;
+ n = width - start; // [start .. width)
+ }
+ if (n > count) {
+ n = count;
+ }
+ if (forward) {
+ fill_sequential(xptr, start, n);
+ } else {
+ fill_backwards(xptr, start, n);
+ }
+ forward = !forward;
+ xptr += n;
+ count -= n;
+
+ while (count >= width) {
+ if (forward) {
+ fill_sequential(xptr, 0, width);
+ } else {
+ fill_backwards(xptr, width - 1, width);
+ }
+ forward = !forward;
+ xptr += width;
+ count -= width;
+ }
+
+ if (count > 0) {
+ if (forward) {
+ fill_sequential(xptr, 0, count);
+ } else {
+ fill_backwards(xptr, width - 1, count);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBitmapProcState::MatrixProc
+SkBitmapProcState::chooseMatrixProc(bool trivial_matrix) {
+// test_int_tileprocs();
+ // check for our special case when there is no scale/affine/perspective
+ if (trivial_matrix) {
+ SkASSERT(SkPaint::kNone_FilterLevel == fFilterLevel);
+ fIntTileProcY = choose_int_tile_proc(fTileModeY);
+ switch (fTileModeX) {
+ case SkShader::kClamp_TileMode:
+ return clampx_nofilter_trans;
+ case SkShader::kRepeat_TileMode:
+ return repeatx_nofilter_trans;
+ case SkShader::kMirror_TileMode:
+ return mirrorx_nofilter_trans;
+ }
+ }
+
+ int index = 0;
+ if (fFilterLevel != SkPaint::kNone_FilterLevel) {
+ index = 1;
+ }
+ if (fInvType & SkMatrix::kPerspective_Mask) {
+ index += 4;
+ } else if (fInvType & SkMatrix::kAffine_Mask) {
+ index += 2;
+ }
+
+ if (SkShader::kClamp_TileMode == fTileModeX &&
+ SkShader::kClamp_TileMode == fTileModeY)
+ {
+ // clamp gets special version of filterOne
+ fFilterOneX = SK_Fixed1;
+ fFilterOneY = SK_Fixed1;
+ return SK_ARM_NEON_WRAP(ClampX_ClampY_Procs)[index];
+ }
+
+ // all remaining procs use this form for filterOne
+ fFilterOneX = SK_Fixed1 / fBitmap->width();
+ fFilterOneY = SK_Fixed1 / fBitmap->height();
+
+ if (SkShader::kRepeat_TileMode == fTileModeX &&
+ SkShader::kRepeat_TileMode == fTileModeY)
+ {
+ return SK_ARM_NEON_WRAP(RepeatX_RepeatY_Procs)[index];
+ }
+
+ fTileProcX = choose_tile_proc(fTileModeX);
+ fTileProcY = choose_tile_proc(fTileModeY);
+ fTileLowBitsProcX = choose_tile_lowbits_proc(fTileModeX);
+ fTileLowBitsProcY = choose_tile_lowbits_proc(fTileModeY);
+ return GeneralXY_Procs[index];
+}
diff --git a/core/SkBitmapProcState_procs.h b/core/SkBitmapProcState_procs.h
new file mode 100644
index 00000000..da9ca89b
--- /dev/null
+++ b/core/SkBitmapProcState_procs.h
@@ -0,0 +1,343 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Define NAME_WRAP(x) before including this header to perform name-wrapping
+// E.g. for ARM NEON, defined it as 'x ## _neon' to ensure all important
+// identifiers have a _neon suffix.
+#ifndef NAME_WRAP
+#error "Please define NAME_WRAP() before including this file"
+#endif
+
+// returns expanded * 5bits
+static inline uint32_t Filter_565_Expanded(unsigned x, unsigned y,
+ uint32_t a00, uint32_t a01,
+ uint32_t a10, uint32_t a11) {
+ SkASSERT((unsigned)x <= 0xF);
+ SkASSERT((unsigned)y <= 0xF);
+
+ a00 = SkExpand_rgb_16(a00);
+ a01 = SkExpand_rgb_16(a01);
+ a10 = SkExpand_rgb_16(a10);
+ a11 = SkExpand_rgb_16(a11);
+
+ int xy = x * y >> 3;
+ return a00 * (32 - 2*y - 2*x + xy) +
+ a01 * (2*x - xy) +
+ a10 * (2*y - xy) +
+ a11 * xy;
+}
+
+// turn an expanded 565 * 5bits into SkPMColor
+// g:11 | r:10 | x:1 | b:10
+static inline SkPMColor SkExpanded_565_To_PMColor(uint32_t c) {
+ unsigned r = (c >> 13) & 0xFF;
+ unsigned g = (c >> 24);
+ unsigned b = (c >> 2) & 0xFF;
+ return SkPackARGB32(0xFF, r, g, b);
+}
+
+// returns answer in SkPMColor format
+static inline SkPMColor Filter_4444_D32(unsigned x, unsigned y,
+ uint32_t a00, uint32_t a01,
+ uint32_t a10, uint32_t a11) {
+ SkASSERT((unsigned)x <= 0xF);
+ SkASSERT((unsigned)y <= 0xF);
+
+ a00 = SkExpand_4444(a00);
+ a01 = SkExpand_4444(a01);
+ a10 = SkExpand_4444(a10);
+ a11 = SkExpand_4444(a11);
+
+ int xy = x * y >> 4;
+ uint32_t result = a00 * (16 - y - x + xy) +
+ a01 * (x - xy) +
+ a10 * (y - xy) +
+ a11 * xy;
+
+ return SkCompact_8888(result);
+}
+
+static inline U8CPU Filter_8(unsigned x, unsigned y,
+ U8CPU a00, U8CPU a01,
+ U8CPU a10, U8CPU a11) {
+ SkASSERT((unsigned)x <= 0xF);
+ SkASSERT((unsigned)y <= 0xF);
+
+ int xy = x * y;
+ unsigned result = a00 * (256 - 16*y - 16*x + xy) +
+ a01 * (16*x - xy) +
+ a10 * (16*y - xy) +
+ a11 * xy;
+
+ return result >> 8;
+}
+
+/*****************************************************************************
+ *
+ * D32 functions
+ *
+ */
+
+// SRC == 8888
+
+#define FILTER_PROC(x, y, a, b, c, d, dst) NAME_WRAP(Filter_32_opaque)(x, y, a, b, c, d, dst)
+
+#define MAKENAME(suffix) NAME_WRAP(S32_opaque_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE SkPMColor
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kARGB_8888_Config); \
+ SkASSERT(state.fAlphaScale == 256)
+#define RETURNDST(src) src
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) NAME_WRAP(Filter_32_alpha)(x, y, a, b, c, d, dst, alphaScale)
+
+#define MAKENAME(suffix) NAME_WRAP(S32_alpha_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE SkPMColor
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kARGB_8888_Config); \
+ SkASSERT(state.fAlphaScale < 256)
+#define PREAMBLE(state) unsigned alphaScale = state.fAlphaScale
+#define RETURNDST(src) SkAlphaMulQ(src, alphaScale)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+// SRC == 565
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_565_Expanded(x, y, a, b, c, d); \
+ *(dst) = SkExpanded_565_To_PMColor(tmp); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(S16_opaque_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE uint16_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kRGB_565_Config); \
+ SkASSERT(state.fAlphaScale == 256)
+#define RETURNDST(src) SkPixel16ToPixel32(src)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_565_Expanded(x, y, a, b, c, d); \
+ *(dst) = SkAlphaMulQ(SkExpanded_565_To_PMColor(tmp), alphaScale); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(S16_alpha_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE uint16_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kRGB_565_Config); \
+ SkASSERT(state.fAlphaScale < 256)
+#define PREAMBLE(state) unsigned alphaScale = state.fAlphaScale
+#define RETURNDST(src) SkAlphaMulQ(SkPixel16ToPixel32(src), alphaScale)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+// SRC == Index8
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) NAME_WRAP(Filter_32_opaque)(x, y, a, b, c, d, dst)
+
+#define MAKENAME(suffix) NAME_WRAP(SI8_opaque_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE uint8_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kIndex8_Config); \
+ SkASSERT(state.fAlphaScale == 256)
+#define PREAMBLE(state) const SkPMColor* SK_RESTRICT table = state.fBitmap->getColorTable()->lockColors()
+#define RETURNDST(src) table[src]
+#define SRC_TO_FILTER(src) table[src]
+#define POSTAMBLE(state) state.fBitmap->getColorTable()->unlockColors(false)
+#include "SkBitmapProcState_sample.h"
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) NAME_WRAP(Filter_32_alpha)(x, y, a, b, c, d, dst, alphaScale)
+
+#define MAKENAME(suffix) NAME_WRAP(SI8_alpha_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE uint8_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kIndex8_Config); \
+ SkASSERT(state.fAlphaScale < 256)
+#define PREAMBLE(state) unsigned alphaScale = state.fAlphaScale; \
+ const SkPMColor* SK_RESTRICT table = state.fBitmap->getColorTable()->lockColors()
+#define RETURNDST(src) SkAlphaMulQ(table[src], alphaScale)
+#define SRC_TO_FILTER(src) table[src]
+#define POSTAMBLE(state) state.fBitmap->getColorTable()->unlockColors(false)
+#include "SkBitmapProcState_sample.h"
+
+// SRC == 4444
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) *(dst) = Filter_4444_D32(x, y, a, b, c, d)
+
+#define MAKENAME(suffix) NAME_WRAP(S4444_opaque_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE SkPMColor16
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kARGB_4444_Config); \
+ SkASSERT(state.fAlphaScale == 256)
+#define RETURNDST(src) SkPixel4444ToPixel32(src)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_4444_D32(x, y, a, b, c, d); \
+ *(dst) = SkAlphaMulQ(tmp, alphaScale); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(S4444_alpha_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE SkPMColor16
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kARGB_4444_Config); \
+ SkASSERT(state.fAlphaScale < 256)
+#define PREAMBLE(state) unsigned alphaScale = state.fAlphaScale
+#define RETURNDST(src) SkAlphaMulQ(SkPixel4444ToPixel32(src), alphaScale)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+// SRC == A8
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ unsigned tmp = Filter_8(x, y, a, b, c, d); \
+ *(dst) = SkAlphaMulQ(pmColor, SkAlpha255To256(tmp)); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(SA8_alpha_D32 ## suffix)
+#define DSTSIZE 32
+#define SRCTYPE uint8_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kA8_Config);
+#define PREAMBLE(state) const SkPMColor pmColor = state.fPaintPMColor;
+#define RETURNDST(src) SkAlphaMulQ(pmColor, SkAlpha255To256(src))
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+/*****************************************************************************
+ *
+ * D16 functions
+ *
+ */
+
+// SRC == 8888
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ SkPMColor dstColor; \
+ NAME_WRAP(Filter_32_opaque)(x, y, a, b, c, d, &dstColor); \
+ (*dst) = SkPixel32ToPixel16(dstColor); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(S32_D16 ## suffix)
+#define DSTSIZE 16
+#define SRCTYPE SkPMColor
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kARGB_8888_Config); \
+ SkASSERT(state.fBitmap->isOpaque())
+#define RETURNDST(src) SkPixel32ToPixel16(src)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+// SRC == 565
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_565_Expanded(x, y, a, b, c, d); \
+ *(dst) = SkCompact_rgb_16((tmp) >> 5); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(S16_D16 ## suffix)
+#define DSTSIZE 16
+#define SRCTYPE uint16_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kRGB_565_Config)
+#define RETURNDST(src) src
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_sample.h"
+
+// SRC == Index8
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_565_Expanded(x, y, a, b, c, d); \
+ *(dst) = SkCompact_rgb_16((tmp) >> 5); \
+ } while (0)
+
+#define MAKENAME(suffix) NAME_WRAP(SI8_D16 ## suffix)
+#define DSTSIZE 16
+#define SRCTYPE uint8_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kIndex8_Config); \
+ SkASSERT(state.fBitmap->isOpaque())
+#define PREAMBLE(state) const uint16_t* SK_RESTRICT table = state.fBitmap->getColorTable()->lock16BitCache()
+#define RETURNDST(src) table[src]
+#define SRC_TO_FILTER(src) table[src]
+#define POSTAMBLE(state) state.fBitmap->getColorTable()->unlock16BitCache()
+#include "SkBitmapProcState_sample.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) \
+ do { \
+ uint32_t tmp = Filter_565_Expanded(x, y, a, b, c, d); \
+ *(dst) = SkCompact_rgb_16((tmp) >> 5); \
+ } while (0)
+
+
+// clamp
+
+#define TILEX_PROCF(fx, max) SkClampMax((fx) >> 16, max)
+#define TILEY_PROCF(fy, max) SkClampMax((fy) >> 16, max)
+#define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF)
+
+#define MAKENAME(suffix) NAME_WRAP(Clamp_S16_D16 ## suffix)
+#define SRCTYPE uint16_t
+#define DSTTYPE uint16_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kRGB_565_Config)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_shaderproc.h"
+
+
+#define TILEX_PROCF(fx, max) (((fx) & 0xFFFF) * ((max) + 1) >> 16)
+#define TILEY_PROCF(fy, max) (((fy) & 0xFFFF) * ((max) + 1) >> 16)
+#define TILEX_LOW_BITS(fx, max) ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) ((((fy) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+
+#define MAKENAME(suffix) NAME_WRAP(Repeat_S16_D16 ## suffix)
+#define SRCTYPE uint16_t
+#define DSTTYPE uint16_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kRGB_565_Config)
+#define SRC_TO_FILTER(src) src
+#include "SkBitmapProcState_shaderproc.h"
+
+
+#define TILEX_PROCF(fx, max) SkClampMax((fx) >> 16, max)
+#define TILEY_PROCF(fy, max) SkClampMax((fy) >> 16, max)
+#define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF)
+
+#undef FILTER_PROC
+#define FILTER_PROC(x, y, a, b, c, d, dst) NAME_WRAP(Filter_32_opaque)(x, y, a, b, c, d, dst)
+#define MAKENAME(suffix) NAME_WRAP(Clamp_SI8_opaque_D32 ## suffix)
+#define SRCTYPE uint8_t
+#define DSTTYPE uint32_t
+#define CHECKSTATE(state) SkASSERT(state.fBitmap->config() == SkBitmap::kIndex8_Config)
+#define PREAMBLE(state) const SkPMColor* SK_RESTRICT table = state.fBitmap->getColorTable()->lockColors()
+#define SRC_TO_FILTER(src) table[src]
+#define POSTAMBLE(state) state.fBitmap->getColorTable()->unlockColors(false)
+#include "SkBitmapProcState_shaderproc.h"
+
+#undef NAME_WRAP
diff --git a/core/SkBitmapProcState_sample.h b/core/SkBitmapProcState_sample.h
new file mode 100644
index 00000000..5c5f199f
--- /dev/null
+++ b/core/SkBitmapProcState_sample.h
@@ -0,0 +1,248 @@
+
+/*
+ * 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 "SkUtils.h"
+
+#if DSTSIZE==32
+ #define DSTTYPE SkPMColor
+#elif DSTSIZE==16
+ #define DSTTYPE uint16_t
+#else
+ #error "need DSTSIZE to be 32 or 16"
+#endif
+
+#if (DSTSIZE == 32)
+ #define BITMAPPROC_MEMSET(ptr, value, n) sk_memset32(ptr, value, n)
+#elif (DSTSIZE == 16)
+ #define BITMAPPROC_MEMSET(ptr, value, n) sk_memset16(ptr, value, n)
+#else
+ #error "unsupported DSTSIZE"
+#endif
+
+
+// declare functions externally to suppress warnings.
+void MAKENAME(_nofilter_DXDY)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors);
+void MAKENAME(_nofilter_DX)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors);
+void MAKENAME(_filter_DX)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors);
+void MAKENAME(_filter_DXDY)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors);
+
+void MAKENAME(_nofilter_DXDY)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+ SkDEBUGCODE(CHECKSTATE(s);)
+
+#ifdef PREAMBLE
+ PREAMBLE(s);
+#endif
+ const char* SK_RESTRICT srcAddr = (const char*)s.fBitmap->getPixels();
+ size_t rb = s.fBitmap->rowBytes();
+
+ uint32_t XY;
+ SRCTYPE src;
+
+ for (int i = (count >> 1); i > 0; --i) {
+ XY = *xy++;
+ SkASSERT((XY >> 16) < (unsigned)s.fBitmap->height() &&
+ (XY & 0xFFFF) < (unsigned)s.fBitmap->width());
+ src = ((const SRCTYPE*)(srcAddr + (XY >> 16) * rb))[XY & 0xFFFF];
+ *colors++ = RETURNDST(src);
+
+ XY = *xy++;
+ SkASSERT((XY >> 16) < (unsigned)s.fBitmap->height() &&
+ (XY & 0xFFFF) < (unsigned)s.fBitmap->width());
+ src = ((const SRCTYPE*)(srcAddr + (XY >> 16) * rb))[XY & 0xFFFF];
+ *colors++ = RETURNDST(src);
+ }
+ if (count & 1) {
+ XY = *xy++;
+ SkASSERT((XY >> 16) < (unsigned)s.fBitmap->height() &&
+ (XY & 0xFFFF) < (unsigned)s.fBitmap->width());
+ src = ((const SRCTYPE*)(srcAddr + (XY >> 16) * rb))[XY & 0xFFFF];
+ *colors++ = RETURNDST(src);
+ }
+
+#ifdef POSTAMBLE
+ POSTAMBLE(s);
+#endif
+}
+
+void MAKENAME(_nofilter_DX)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask));
+ SkASSERT(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+ SkDEBUGCODE(CHECKSTATE(s);)
+
+#ifdef PREAMBLE
+ PREAMBLE(s);
+#endif
+ const SRCTYPE* SK_RESTRICT srcAddr = (const SRCTYPE*)s.fBitmap->getPixels();
+
+ // buffer is y32, x16, x16, x16, x16, x16
+ // bump srcAddr to the proper row, since we're told Y never changes
+ SkASSERT((unsigned)xy[0] < (unsigned)s.fBitmap->height());
+ srcAddr = (const SRCTYPE*)((const char*)srcAddr +
+ xy[0] * s.fBitmap->rowBytes());
+ xy += 1;
+
+ SRCTYPE src;
+
+ if (1 == s.fBitmap->width()) {
+ src = srcAddr[0];
+ DSTTYPE dstValue = RETURNDST(src);
+ BITMAPPROC_MEMSET(colors, dstValue, count);
+ } else {
+ int i;
+ for (i = (count >> 2); i > 0; --i) {
+ uint32_t xx0 = *xy++;
+ uint32_t xx1 = *xy++;
+ SRCTYPE x0 = srcAddr[UNPACK_PRIMARY_SHORT(xx0)];
+ SRCTYPE x1 = srcAddr[UNPACK_SECONDARY_SHORT(xx0)];
+ SRCTYPE x2 = srcAddr[UNPACK_PRIMARY_SHORT(xx1)];
+ SRCTYPE x3 = srcAddr[UNPACK_SECONDARY_SHORT(xx1)];
+
+ *colors++ = RETURNDST(x0);
+ *colors++ = RETURNDST(x1);
+ *colors++ = RETURNDST(x2);
+ *colors++ = RETURNDST(x3);
+ }
+ const uint16_t* SK_RESTRICT xx = (const uint16_t*)(xy);
+ for (i = (count & 3); i > 0; --i) {
+ SkASSERT(*xx < (unsigned)s.fBitmap->width());
+ src = srcAddr[*xx++]; *colors++ = RETURNDST(src);
+ }
+ }
+
+#ifdef POSTAMBLE
+ POSTAMBLE(s);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MAKENAME(_filter_DX)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkDEBUGCODE(CHECKSTATE(s);)
+
+#ifdef PREAMBLE
+ PREAMBLE(s);
+#endif
+ const char* SK_RESTRICT srcAddr = (const char*)s.fBitmap->getPixels();
+ size_t rb = s.fBitmap->rowBytes();
+ unsigned subY;
+ const SRCTYPE* SK_RESTRICT row0;
+ const SRCTYPE* SK_RESTRICT row1;
+
+ // setup row ptrs and update proc_table
+ {
+ uint32_t XY = *xy++;
+ unsigned y0 = XY >> 14;
+ row0 = (const SRCTYPE*)(srcAddr + (y0 >> 4) * rb);
+ row1 = (const SRCTYPE*)(srcAddr + (XY & 0x3FFF) * rb);
+ subY = y0 & 0xF;
+ }
+
+ do {
+ uint32_t XX = *xy++; // x0:14 | 4 | x1:14
+ unsigned x0 = XX >> 14;
+ unsigned x1 = XX & 0x3FFF;
+ unsigned subX = x0 & 0xF;
+ x0 >>= 4;
+
+ FILTER_PROC(subX, subY,
+ SRC_TO_FILTER(row0[x0]),
+ SRC_TO_FILTER(row0[x1]),
+ SRC_TO_FILTER(row1[x0]),
+ SRC_TO_FILTER(row1[x1]),
+ colors);
+ colors += 1;
+
+ } while (--count != 0);
+
+#ifdef POSTAMBLE
+ POSTAMBLE(s);
+#endif
+}
+void MAKENAME(_filter_DXDY)(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, DSTTYPE* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkDEBUGCODE(CHECKSTATE(s);)
+
+#ifdef PREAMBLE
+ PREAMBLE(s);
+#endif
+ const char* SK_RESTRICT srcAddr = (const char*)s.fBitmap->getPixels();
+ size_t rb = s.fBitmap->rowBytes();
+
+ do {
+ uint32_t data = *xy++;
+ unsigned y0 = data >> 14;
+ unsigned y1 = data & 0x3FFF;
+ unsigned subY = y0 & 0xF;
+ y0 >>= 4;
+
+ data = *xy++;
+ unsigned x0 = data >> 14;
+ unsigned x1 = data & 0x3FFF;
+ unsigned subX = x0 & 0xF;
+ x0 >>= 4;
+
+ const SRCTYPE* SK_RESTRICT row0 = (const SRCTYPE*)(srcAddr + y0 * rb);
+ const SRCTYPE* SK_RESTRICT row1 = (const SRCTYPE*)(srcAddr + y1 * rb);
+
+ FILTER_PROC(subX, subY,
+ SRC_TO_FILTER(row0[x0]),
+ SRC_TO_FILTER(row0[x1]),
+ SRC_TO_FILTER(row1[x0]),
+ SRC_TO_FILTER(row1[x1]),
+ colors);
+ colors += 1;
+ } while (--count != 0);
+
+#ifdef POSTAMBLE
+ POSTAMBLE(s);
+#endif
+}
+
+#undef MAKENAME
+#undef DSTSIZE
+#undef DSTTYPE
+#undef SRCTYPE
+#undef CHECKSTATE
+#undef RETURNDST
+#undef SRC_TO_FILTER
+#undef FILTER_TO_DST
+
+#ifdef PREAMBLE
+ #undef PREAMBLE
+#endif
+#ifdef POSTAMBLE
+ #undef POSTAMBLE
+#endif
+
+#undef FILTER_PROC_TYPE
+#undef GET_FILTER_TABLE
+#undef GET_FILTER_ROW
+#undef GET_FILTER_ROW_PROC
+#undef GET_FILTER_PROC
+#undef BITMAPPROC_MEMSET
diff --git a/core/SkBitmapProcState_shaderproc.h b/core/SkBitmapProcState_shaderproc.h
new file mode 100644
index 00000000..0014b4a5
--- /dev/null
+++ b/core/SkBitmapProcState_shaderproc.h
@@ -0,0 +1,94 @@
+
+/*
+ * 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 "SkMathPriv.h"
+
+#define SCALE_FILTER_NAME MAKENAME(_filter_DX_shaderproc)
+
+// Can't be static in the general case because some of these implementations
+// will be defined and referenced in different object files.
+void SCALE_FILTER_NAME(const SkBitmapProcState& s, int x, int y,
+ DSTTYPE* SK_RESTRICT colors, int count);
+
+void SCALE_FILTER_NAME(const SkBitmapProcState& s, int x, int y,
+ DSTTYPE* SK_RESTRICT colors, int count) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkDEBUGCODE(CHECKSTATE(s);)
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ const SkFixed oneX = s.fFilterOneX;
+ const SkFixed dx = s.fInvSx;
+ SkFixed fx;
+ const SRCTYPE* SK_RESTRICT row0;
+ const SRCTYPE* SK_RESTRICT row1;
+ unsigned subY;
+
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ SkFixed fy = SkScalarToFixed(pt.fY) - (s.fFilterOneY >> 1);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ // compute our two Y values up front
+ subY = TILEY_LOW_BITS(fy, maxY);
+ int y0 = TILEY_PROCF(fy, maxY);
+ int y1 = TILEY_PROCF((fy + s.fFilterOneY), maxY);
+
+ const char* SK_RESTRICT srcAddr = (const char*)s.fBitmap->getPixels();
+ size_t rb = s.fBitmap->rowBytes();
+ row0 = (const SRCTYPE*)(srcAddr + y0 * rb);
+ row1 = (const SRCTYPE*)(srcAddr + y1 * rb);
+ // now initialize fx
+ fx = SkScalarToFixed(pt.fX) - (oneX >> 1);
+ }
+
+#ifdef PREAMBLE
+ PREAMBLE(s);
+#endif
+
+ do {
+ unsigned subX = TILEX_LOW_BITS(fx, maxX);
+ unsigned x0 = TILEX_PROCF(fx, maxX);
+ unsigned x1 = TILEX_PROCF((fx + oneX), maxX);
+
+ FILTER_PROC(subX, subY,
+ SRC_TO_FILTER(row0[x0]),
+ SRC_TO_FILTER(row0[x1]),
+ SRC_TO_FILTER(row1[x0]),
+ SRC_TO_FILTER(row1[x1]),
+ colors);
+ colors += 1;
+
+ fx += dx;
+ } while (--count != 0);
+
+#ifdef POSTAMBLE
+ POSTAMBLE(s);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TILEX_PROCF
+#undef TILEY_PROCF
+#undef TILEX_LOW_BITS
+#undef TILEY_LOW_BITS
+#undef MAKENAME
+#undef SRCTYPE
+#undef DSTTYPE
+#undef CHECKSTATE
+#undef SRC_TO_FILTER
+#undef FILTER_TO_DST
+#undef PREAMBLE
+#undef POSTAMBLE
+
+#undef SCALE_FILTER_NAME
diff --git a/core/SkBitmapScaler.cpp b/core/SkBitmapScaler.cpp
new file mode 100644
index 00000000..80769627
--- /dev/null
+++ b/core/SkBitmapScaler.cpp
@@ -0,0 +1,318 @@
+#include "SkBitmapScaler.h"
+#include "SkBitmapFilter.h"
+#include "SkRect.h"
+#include "SkTArray.h"
+#include "SkErrorInternals.h"
+#include "SkConvolver.h"
+
+// SkResizeFilter ----------------------------------------------------------------
+
+// Encapsulates computation and storage of the filters required for one complete
+// resize operation.
+class SkResizeFilter {
+public:
+ SkResizeFilter(SkBitmapScaler::ResizeMethod method,
+ int srcFullWidth, int srcFullHeight,
+ int destWidth, int destHeight,
+ const SkIRect& destSubset,
+ SkConvolutionProcs* convolveProcs);
+ ~SkResizeFilter() {
+ SkDELETE( fBitmapFilter );
+ }
+
+ // Returns the filled filter values.
+ const SkConvolutionFilter1D& xFilter() { return fXFilter; }
+ const SkConvolutionFilter1D& yFilter() { return fYFilter; }
+
+private:
+
+ SkBitmapFilter* fBitmapFilter;
+
+ // Computes one set of filters either horizontally or vertically. The caller
+ // will specify the "min" and "max" rather than the bottom/top and
+ // right/bottom so that the same code can be re-used in each dimension.
+ //
+ // |srcDependLo| and |srcDependSize| gives the range for the source
+ // depend rectangle (horizontally or vertically at the caller's discretion
+ // -- see above for what this means).
+ //
+ // Likewise, the range of destination values to compute and the scale factor
+ // for the transform is also specified.
+
+ void computeFilters(int srcSize,
+ int destSubsetLo, int destSubsetSize,
+ float scale,
+ SkConvolutionFilter1D* output,
+ SkConvolutionProcs* convolveProcs);
+
+ SkConvolutionFilter1D fXFilter;
+ SkConvolutionFilter1D fYFilter;
+};
+
+SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method,
+ int srcFullWidth, int srcFullHeight,
+ int destWidth, int destHeight,
+ const SkIRect& destSubset,
+ SkConvolutionProcs* convolveProcs) {
+
+ // method will only ever refer to an "algorithm method".
+ SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
+ (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
+
+ switch(method) {
+ case SkBitmapScaler::RESIZE_BOX:
+ fBitmapFilter = SkNEW(SkBoxFilter);
+ break;
+ case SkBitmapScaler::RESIZE_TRIANGLE:
+ fBitmapFilter = SkNEW(SkTriangleFilter);
+ break;
+ case SkBitmapScaler::RESIZE_MITCHELL:
+ fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
+ break;
+ case SkBitmapScaler::RESIZE_HAMMING:
+ fBitmapFilter = SkNEW(SkHammingFilter);
+ break;
+ case SkBitmapScaler::RESIZE_LANCZOS3:
+ fBitmapFilter = SkNEW(SkLanczosFilter);
+ break;
+ default:
+ // NOTREACHED:
+ fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
+ break;
+ }
+
+
+ float scaleX = static_cast<float>(destWidth) /
+ static_cast<float>(srcFullWidth);
+ float scaleY = static_cast<float>(destHeight) /
+ static_cast<float>(srcFullHeight);
+
+ this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(),
+ scaleX, &fXFilter, convolveProcs);
+ this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(),
+ scaleY, &fYFilter, convolveProcs);
+}
+
+// TODO(egouriou): Take advantage of periods in the convolution.
+// Practical resizing filters are periodic outside of the border area.
+// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the
+// source become p pixels in the destination) will have a period of p.
+// A nice consequence is a period of 1 when downscaling by an integral
+// factor. Downscaling from typical display resolutions is also bound
+// to produce interesting periods as those are chosen to have multiple
+// small factors.
+// Small periods reduce computational load and improve cache usage if
+// the coefficients can be shared. For periods of 1 we can consider
+// loading the factors only once outside the borders.
+void SkResizeFilter::computeFilters(int srcSize,
+ int destSubsetLo, int destSubsetSize,
+ float scale,
+ SkConvolutionFilter1D* output,
+ SkConvolutionProcs* convolveProcs) {
+ int destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi)
+
+ // When we're doing a magnification, the scale will be larger than one. This
+ // means the destination pixels are much smaller than the source pixels, and
+ // that the range covered by the filter won't necessarily cover any source
+ // pixel boundaries. Therefore, we use these clamped values (max of 1) for
+ // some computations.
+ float clampedScale = SkTMin(1.0f, scale);
+
+ // This is how many source pixels from the center we need to count
+ // to support the filtering function.
+ float srcSupport = fBitmapFilter->width() / clampedScale;
+
+ // Speed up the divisions below by turning them into multiplies.
+ float invScale = 1.0f / scale;
+
+ SkTArray<float> filterValues(64);
+ SkTArray<short> fixedFilterValues(64);
+
+ // Loop over all pixels in the output range. We will generate one set of
+ // filter values for each one. Those values will tell us how to blend the
+ // source pixels to compute the destination pixel.
+ for (int destSubsetI = destSubsetLo; destSubsetI < destSubsetHi;
+ destSubsetI++) {
+ // Reset the arrays. We don't declare them inside so they can re-use the
+ // same malloc-ed buffer.
+ filterValues.reset();
+ fixedFilterValues.reset();
+
+ // This is the pixel in the source directly under the pixel in the dest.
+ // Note that we base computations on the "center" of the pixels. To see
+ // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
+ // downscale should "cover" the pixels around the pixel with *its center*
+ // at coordinates (2.5, 2.5) in the source, not those around (0, 0).
+ // Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
+ float srcPixel = (static_cast<float>(destSubsetI) + 0.5f) * invScale;
+
+ // Compute the (inclusive) range of source pixels the filter covers.
+ int srcBegin = SkTMax(0, SkScalarFloorToInt(srcPixel - srcSupport));
+ int srcEnd = SkTMin(srcSize - 1, SkScalarCeilToInt(srcPixel + srcSupport));
+
+ // Compute the unnormalized filter value at each location of the source
+ // it covers.
+ float filterSum = 0.0f; // Sub of the filter values for normalizing.
+ for (int curFilterPixel = srcBegin; curFilterPixel <= srcEnd;
+ curFilterPixel++) {
+ // Distance from the center of the filter, this is the filter coordinate
+ // in source space. We also need to consider the center of the pixel
+ // when comparing distance against 'srcPixel'. In the 5x downscale
+ // example used above the distance from the center of the filter to
+ // the pixel with coordinates (2, 2) should be 0, because its center
+ // is at (2.5, 2.5).
+ float srcFilterDist =
+ ((static_cast<float>(curFilterPixel) + 0.5f) - srcPixel);
+
+ // Since the filter really exists in dest space, map it there.
+ float destFilterDist = srcFilterDist * clampedScale;
+
+ // Compute the filter value at that location.
+ float filterValue = fBitmapFilter->evaluate(destFilterDist);
+ filterValues.push_back(filterValue);
+
+ filterSum += filterValue;
+ }
+ SkASSERT(!filterValues.empty());
+
+ // The filter must be normalized so that we don't affect the brightness of
+ // the image. Convert to normalized fixed point.
+ short fixedSum = 0;
+ for (int i = 0; i < filterValues.count(); i++) {
+ short curFixed = output->FloatToFixed(filterValues[i] / filterSum);
+ fixedSum += curFixed;
+ fixedFilterValues.push_back(curFixed);
+ }
+
+ // The conversion to fixed point will leave some rounding errors, which
+ // we add back in to avoid affecting the brightness of the image. We
+ // arbitrarily add this to the center of the filter array (this won't always
+ // be the center of the filter function since it could get clipped on the
+ // edges, but it doesn't matter enough to worry about that case).
+ short leftovers = output->FloatToFixed(1.0f) - fixedSum;
+ fixedFilterValues[fixedFilterValues.count() / 2] += leftovers;
+
+ // Now it's ready to go.
+ output->AddFilter(srcBegin, &fixedFilterValues[0],
+ static_cast<int>(fixedFilterValues.count()));
+ }
+
+ if (convolveProcs->fApplySIMDPadding) {
+ convolveProcs->fApplySIMDPadding( output );
+ }
+}
+
+static SkBitmapScaler::ResizeMethod ResizeMethodToAlgorithmMethod(
+ SkBitmapScaler::ResizeMethod method) {
+ // Convert any "Quality Method" into an "Algorithm Method"
+ if (method >= SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD &&
+ method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD) {
+ return method;
+ }
+ // The call to SkBitmapScalerGtv::Resize() above took care of
+ // GPU-acceleration in the cases where it is possible. So now we just
+ // pick the appropriate software method for each resize quality.
+ switch (method) {
+ // Users of RESIZE_GOOD are willing to trade a lot of quality to
+ // get speed, allowing the use of linear resampling to get hardware
+ // acceleration (SRB). Hence any of our "good" software filters
+ // will be acceptable, so we use a triangle.
+ case SkBitmapScaler::RESIZE_GOOD:
+ return SkBitmapScaler::RESIZE_TRIANGLE;
+ // Users of RESIZE_BETTER are willing to trade some quality in order
+ // to improve performance, but are guaranteed not to devolve to a linear
+ // resampling. In visual tests we see that Hamming-1 is not as good as
+ // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is
+ // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed
+ // an acceptable trade-off between quality and speed.
+ case SkBitmapScaler::RESIZE_BETTER:
+ return SkBitmapScaler::RESIZE_HAMMING;
+ default:
+ return SkBitmapScaler::RESIZE_MITCHELL;
+ }
+}
+
+// static
+bool SkBitmapScaler::Resize(SkBitmap* resultPtr,
+ const SkBitmap& source,
+ ResizeMethod method,
+ int destWidth, int destHeight,
+ const SkIRect& destSubset,
+ SkConvolutionProcs* convolveProcs,
+ SkBitmap::Allocator* allocator) {
+ // Ensure that the ResizeMethod enumeration is sound.
+ SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) &&
+ (method <= RESIZE_LAST_QUALITY_METHOD)) ||
+ ((RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
+ (method <= RESIZE_LAST_ALGORITHM_METHOD)));
+
+ SkIRect dest = { 0, 0, destWidth, destHeight };
+ if (!dest.contains(destSubset)) {
+ SkErrorInternals::SetError( kInvalidArgument_SkError,
+ "Sorry, you passed me a bitmap resize "
+ " method I have never heard of: %d",
+ method );
+ }
+
+ // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
+ // return empty.
+ if (source.width() < 1 || source.height() < 1 ||
+ destWidth < 1 || destHeight < 1) {
+ // todo: seems like we could handle negative dstWidth/Height, since that
+ // is just a negative scale (flip)
+ return false;
+ }
+
+ method = ResizeMethodToAlgorithmMethod(method);
+
+ // Check that we deal with an "algorithm methods" from this point onward.
+ SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
+ (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
+
+ SkAutoLockPixels locker(source);
+ if (!source.readyToDraw() ||
+ source.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkResizeFilter filter(method, source.width(), source.height(),
+ destWidth, destHeight, destSubset, convolveProcs);
+
+ // Get a source bitmap encompassing this touched area. We construct the
+ // offsets and row strides such that it looks like a new bitmap, while
+ // referring to the old data.
+ const unsigned char* sourceSubset =
+ reinterpret_cast<const unsigned char*>(source.getPixels());
+
+ // Convolve into the result.
+ SkBitmap result;
+ result.setConfig(SkBitmap::kARGB_8888_Config,
+ destSubset.width(), destSubset.height());
+ result.allocPixels(allocator, NULL);
+ if (!result.readyToDraw()) {
+ return false;
+ }
+
+ BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()),
+ !source.isOpaque(), filter.xFilter(), filter.yFilter(),
+ static_cast<int>(result.rowBytes()),
+ static_cast<unsigned char*>(result.getPixels()),
+ convolveProcs, true);
+
+ // Preserve the "opaque" flag for use as an optimization later.
+ result.setIsOpaque(source.isOpaque());
+ *resultPtr = result;
+ return true;
+}
+
+// static
+bool SkBitmapScaler::Resize(SkBitmap* resultPtr,
+ const SkBitmap& source,
+ ResizeMethod method,
+ int destWidth, int destHeight,
+ SkConvolutionProcs* convolveProcs,
+ SkBitmap::Allocator* allocator) {
+ SkIRect destSubset = { 0, 0, destWidth, destHeight };
+ return Resize(resultPtr, source, method, destWidth, destHeight, destSubset,
+ convolveProcs, allocator);
+}
diff --git a/core/SkBitmapScaler.h b/core/SkBitmapScaler.h
new file mode 100644
index 00000000..b88fb9e7
--- /dev/null
+++ b/core/SkBitmapScaler.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapScaler_DEFINED
+#define SkBitmapScaler_DEFINED
+
+#include "SkBitmap.h"
+#include "SkConvolver.h"
+
+/** \class SkBitmapScaler
+
+ Provides the interface for high quality image resampling.
+ */
+
+class SK_API SkBitmapScaler {
+public:
+ enum ResizeMethod {
+ // Quality Methods
+ //
+ // Those enumeration values express a desired quality/speed tradeoff.
+ // They are translated into an algorithm-specific method that depends
+ // on the capabilities (CPU, GPU) of the underlying platform.
+ // It is possible for all three methods to be mapped to the same
+ // algorithm on a given platform.
+
+ // Good quality resizing. Fastest resizing with acceptable visual quality.
+ // This is typically intended for use during interactive layouts
+ // where slower platforms may want to trade image quality for large
+ // increase in resizing performance.
+ //
+ // For example the resizing implementation may devolve to linear
+ // filtering if this enables GPU acceleration to be used.
+ //
+ // Note that the underlying resizing method may be determined
+ // on the fly based on the parameters for a given resize call.
+ // For example an implementation using a GPU-based linear filter
+ // in the common case may still use a higher-quality software-based
+ // filter in cases where using the GPU would actually be slower - due
+ // to too much latency - or impossible - due to image format or size
+ // constraints.
+ RESIZE_GOOD,
+
+ // Medium quality resizing. Close to high quality resizing (better
+ // than linear interpolation) with potentially some quality being
+ // traded-off for additional speed compared to RESIZE_BEST.
+ //
+ // This is intended, for example, for generation of large thumbnails
+ // (hundreds of pixels in each dimension) from large sources, where
+ // a linear filter would produce too many artifacts but where
+ // a RESIZE_HIGH might be too costly time-wise.
+ RESIZE_BETTER,
+
+ // High quality resizing. The algorithm is picked to favor image quality.
+ RESIZE_BEST,
+
+ //
+ // Algorithm-specific enumerations
+ //
+
+ // Box filter. This is a weighted average of all of the pixels touching
+ // the destination pixel. For enlargement, this is nearest neighbor.
+ //
+ // You probably don't want this, it is here for testing since it is easy to
+ // compute. Use RESIZE_LANCZOS3 instead.
+ RESIZE_BOX,
+ RESIZE_TRIANGLE,
+ RESIZE_LANCZOS3,
+ RESIZE_HAMMING,
+ RESIZE_MITCHELL,
+
+ // enum aliases for first and last methods by algorithm or by quality.
+ RESIZE_FIRST_QUALITY_METHOD = RESIZE_GOOD,
+ RESIZE_LAST_QUALITY_METHOD = RESIZE_BEST,
+ RESIZE_FIRST_ALGORITHM_METHOD = RESIZE_BOX,
+ RESIZE_LAST_ALGORITHM_METHOD = RESIZE_MITCHELL,
+ };
+
+ // Resizes the given source bitmap using the specified resize method, so that
+ // the entire image is (dest_size) big. The dest_subset is the rectangle in
+ // this destination image that should actually be returned.
+ //
+ // The output image will be (dest_subset.width(), dest_subset.height()). This
+ // will save work if you do not need the entire bitmap.
+ //
+ // The destination subset must be smaller than the destination image.
+ static bool Resize(SkBitmap* result,
+ const SkBitmap& source,
+ ResizeMethod method,
+ int dest_width, int dest_height,
+ const SkIRect& dest_subset,
+ SkConvolutionProcs *convolveProcs = NULL,
+ SkBitmap::Allocator* allocator = NULL);
+
+ // Alternate version for resizing and returning the entire bitmap rather than
+ // a subset.
+ static bool Resize(SkBitmap* result,
+ const SkBitmap& source,
+ ResizeMethod method,
+ int dest_width, int dest_height,
+ SkConvolutionProcs *convolveProcs = NULL,
+ SkBitmap::Allocator* allocator = NULL);
+};
+
+#endif
diff --git a/core/SkBitmapShader16BilerpTemplate.h b/core/SkBitmapShader16BilerpTemplate.h
new file mode 100644
index 00000000..435b806b
--- /dev/null
+++ b/core/SkBitmapShader16BilerpTemplate.h
@@ -0,0 +1,245 @@
+
+/*
+ * 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 "SkFilterProc.h"
+
+class BILERP_BITMAP16_SHADER_CLASS : public HasSpan16_Sampler_BitmapShader {
+public:
+ BILERP_BITMAP16_SHADER_CLASS(const SkBitmap& src)
+ : HasSpan16_Sampler_BitmapShader(src, true,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode)
+ {
+ }
+
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
+ {
+ SkASSERT(count > 0);
+
+ U8CPU alpha = this->getPaintAlpha();
+
+ const SkMatrix& inv = this->getTotalInverse();
+ const SkBitmap& srcBitmap = this->getSrcBitmap();
+ unsigned srcMaxX = srcBitmap.width() - 1;
+ unsigned srcMaxY = srcBitmap.height() - 1;
+ unsigned srcRB = srcBitmap.rowBytes();
+
+ BILERP_BITMAP16_SHADER_PREAMBLE(srcBitmap);
+
+ const SkFilterProc* proc_table = SkGetBilinearFilterProcTable();
+ const BILERP_BITMAP16_SHADER_TYPE* srcPixels = (const BILERP_BITMAP16_SHADER_TYPE*)srcBitmap.getPixels();
+
+ if (this->getInverseClass() == kPerspective_MatrixClass)
+ {
+ SkPerspIter iter(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+ while (--count >= 0)
+ {
+ SkFixed fx = *srcXY++ - SK_FixedHalf;
+ SkFixed fy = *srcXY++ - SK_FixedHalf;
+ int ix = fx >> 16;
+ int iy = fy >> 16;
+ int x = SkClampMax(ix, srcMaxX);
+ int y = SkClampMax(iy, srcMaxY);
+
+ const BILERP_BITMAP16_SHADER_TYPE *p00, *p01, *p10, *p11;
+
+ p00 = p01 = ((const BILERP_BITMAP16_SHADER_TYPE*)((const char*)srcPixels + y * srcRB)) + x;
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+ p10 = p00;
+ p11 = p01;
+ if ((unsigned)iy < srcMaxY)
+ {
+ p10 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p10 + srcRB);
+ p11 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p11 + srcRB);
+ }
+
+ SkFilterProc proc = SkGetBilinearFilterProc(proc_table, fx, fy);
+ uint32_t c = proc(SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p00)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p01)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p10)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p11)));
+
+ *dstC++ = expanded_rgb16_to_8888(c, alpha);
+ }
+ }
+ }
+ else // linear case
+ {
+ SkFixed fx, fy, dx, dy;
+
+ // now init fx, fy, dx, dy
+ {
+ SkPoint srcPt;
+ this->getInverseMapPtProc()(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ fx = SkScalarToFixed(srcPt.fX) - SK_FixedHalf;
+ fy = SkScalarToFixed(srcPt.fY) - SK_FixedHalf;
+
+ if (this->getInverseClass() == kFixedStepInX_MatrixClass)
+ (void)inv.fixedStepInX(SkIntToScalar(y), &dx, &dy);
+ else
+ {
+ dx = SkScalarToFixed(inv.getScaleX());
+ dy = SkScalarToFixed(inv.getSkewY());
+ }
+ }
+
+ do {
+ int ix = fx >> 16;
+ int iy = fy >> 16;
+
+ const BILERP_BITMAP16_SHADER_TYPE *p00, *p01, *p10, *p11;
+
+ p00 = p01 = ((const BILERP_BITMAP16_SHADER_TYPE*)((const char*)srcPixels +
+ SkClampMax(iy, srcMaxY) * srcRB)) +
+ SkClampMax(ix, srcMaxX);
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+ p10 = p00;
+ p11 = p01;
+ if ((unsigned)iy < srcMaxY)
+ {
+ p10 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p10 + srcRB);
+ p11 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p11 + srcRB);
+ }
+
+ SkFilterProc proc = SkGetBilinearFilterProc(proc_table, fx, fy);
+ uint32_t c = proc(SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p00)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p01)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p10)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p11)));
+ *dstC++ = expanded_rgb16_to_8888(c, alpha);
+
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ BILERP_BITMAP16_SHADER_POSTAMBLE(srcBitmap);
+ }
+
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count)
+ {
+ SkASSERT(count > 0);
+
+ const SkMatrix& inv = this->getTotalInverse();
+ const SkBitmap& srcBitmap = this->getSrcBitmap();
+ unsigned srcMaxX = srcBitmap.width() - 1;
+ unsigned srcMaxY = srcBitmap.height() - 1;
+ unsigned srcRB = srcBitmap.rowBytes();
+
+ BILERP_BITMAP16_SHADER_PREAMBLE(srcBitmap);
+
+ const SkFilterProc* proc_table = SkGetBilinearFilterProcTable();
+ const BILERP_BITMAP16_SHADER_TYPE* srcPixels = (const BILERP_BITMAP16_SHADER_TYPE*)srcBitmap.getPixels();
+
+ if (this->getInverseClass() == kPerspective_MatrixClass)
+ {
+ SkPerspIter iter(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+ while (--count >= 0)
+ {
+ SkFixed fx = *srcXY++ - SK_FixedHalf;
+ SkFixed fy = *srcXY++ - SK_FixedHalf;
+ int ix = fx >> 16;
+ int iy = fy >> 16;
+
+ const BILERP_BITMAP16_SHADER_TYPE *p00, *p01, *p10, *p11;
+
+ p00 = p01 = ((const BILERP_BITMAP16_SHADER_TYPE*)((const char*)srcPixels +
+ SkClampMax(iy, srcMaxY) * srcRB)) +
+ SkClampMax(ix, srcMaxX);
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+ p10 = p00;
+ p11 = p01;
+ if ((unsigned)iy < srcMaxY)
+ {
+ p10 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p10 + srcRB);
+ p11 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p11 + srcRB);
+ }
+
+ SkFilterProc proc = SkGetBilinearFilterProc(proc_table, fx, fy);
+ uint32_t c = proc(SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p00)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p01)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p10)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p11)));
+ *dstC++ = SkCompact_rgb_16(c);
+ }
+ }
+ }
+ else // linear case
+ {
+ SkFixed fx, fy, dx, dy;
+
+ // now init fx, fy, dx, dy
+ {
+ SkPoint srcPt;
+ this->getInverseMapPtProc()(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ fx = SkScalarToFixed(srcPt.fX) - SK_FixedHalf;
+ fy = SkScalarToFixed(srcPt.fY) - SK_FixedHalf;
+
+ if (this->getInverseClass() == kFixedStepInX_MatrixClass)
+ (void)inv.fixedStepInX(SkIntToScalar(y), &dx, &dy);
+ else
+ {
+ dx = SkScalarToFixed(inv.getScaleX());
+ dy = SkScalarToFixed(inv.getSkewY());
+ }
+ }
+
+ do {
+ int ix = fx >> 16;
+ int iy = fy >> 16;
+
+ const BILERP_BITMAP16_SHADER_TYPE *p00, *p01, *p10, *p11;
+
+ p00 = p01 = ((const BILERP_BITMAP16_SHADER_TYPE*)((const char*)srcPixels +
+ SkClampMax(iy, srcMaxY) * srcRB)) +
+ SkClampMax(ix, srcMaxX);
+ if ((unsigned)ix < srcMaxX)
+ p01 += 1;
+ p10 = p00;
+ p11 = p01;
+ if ((unsigned)iy < srcMaxY)
+ {
+ p10 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p10 + srcRB);
+ p11 = (const BILERP_BITMAP16_SHADER_TYPE*)((const char*)p11 + srcRB);
+ }
+
+ SkFilterProc proc = SkGetBilinearFilterProc(proc_table, fx, fy);
+ uint32_t c = proc(SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p00)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p01)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p10)),
+ SkExpand_rgb_16(BILERP_BITMAP16_SHADER_PIXEL(*p11)));
+ *dstC++ = SkCompact_rgb_16(c);
+
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ BILERP_BITMAP16_SHADER_POSTAMBLE(srcBitmap);
+ }
+};
+
+#undef BILERP_BITMAP16_SHADER_CLASS
+#undef BILERP_BITMAP16_SHADER_TYPE
+#undef BILERP_BITMAP16_SHADER_PREAMBLE
+#undef BILERP_BITMAP16_SHADER_PIXEL
+#undef BILERP_BITMAP16_SHADER_POSTAMBLE
diff --git a/core/SkBitmapShaderTemplate.h b/core/SkBitmapShaderTemplate.h
new file mode 100644
index 00000000..20e55188
--- /dev/null
+++ b/core/SkBitmapShaderTemplate.h
@@ -0,0 +1,306 @@
+
+/*
+ * 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.
+ */
+
+
+
+#ifndef NOFILTER_BITMAP_SHADER_PREAMBLE
+ #define NOFILTER_BITMAP_SHADER_PREAMBLE(bitmap, rb)
+#endif
+#ifndef NOFILTER_BITMAP_SHADER_POSTAMBLE
+ #define NOFILTER_BITMAP_SHADER_POSTAMBLE(bitmap)
+#endif
+#ifndef NOFILTER_BITMAP_SHADER_PREAMBLE16
+ #define NOFILTER_BITMAP_SHADER_PREAMBLE16(bitmap, rb)
+#endif
+#ifndef NOFILTER_BITMAP_SHADER_POSTAMBLE16
+ #define NOFILTER_BITMAP_SHADER_POSTAMBLE16(bitmap)
+#endif
+
+class NOFILTER_BITMAP_SHADER_CLASS : public HasSpan16_Sampler_BitmapShader {
+public:
+ NOFILTER_BITMAP_SHADER_CLASS(const SkBitmap& src)
+ : HasSpan16_Sampler_BitmapShader(src, false,
+ NOFILTER_BITMAP_SHADER_TILEMODE,
+ NOFILTER_BITMAP_SHADER_TILEMODE)
+ {
+ }
+
+ virtual bool setContext(const SkBitmap& device, const SkPaint& paint, const SkMatrix& matrix)
+ {
+ if (!this->INHERITED::setContext(device, paint, matrix))
+ return false;
+
+#ifdef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ this->computeUnitInverse();
+#endif
+ return true;
+ }
+
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
+ {
+ SkASSERT(count > 0);
+
+#ifdef NOFILTER_BITMAP_SHADER_SPRITEPROC32
+ if ((this->getTotalInverse().getType() & ~SkMatrix::kTranslate_Mask) == 0)
+ {
+ NOFILTER_BITMAP_SHADER_SPRITEPROC32(this, x, y, dstC, count);
+ return;
+ }
+#endif
+
+ unsigned scale = SkAlpha255To256(this->getPaintAlpha());
+#ifdef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ const SkMatrix& inv = this->getUnitInverse();
+ SkMatrix::MapPtProc invProc = this->getUnitInverseProc();
+#else
+ const SkMatrix& inv = this->getTotalInverse();
+ SkMatrix::MapPtProc invProc = this->getInverseMapPtProc();
+#endif
+ const SkBitmap& srcBitmap = this->getSrcBitmap();
+ unsigned srcMaxX = srcBitmap.width() - 1;
+ unsigned srcMaxY = srcBitmap.height() - 1;
+ unsigned srcRB = srcBitmap.rowBytes();
+ SkFixed fx, fy, dx, dy;
+
+ const NOFILTER_BITMAP_SHADER_TYPE* srcPixels = (const NOFILTER_BITMAP_SHADER_TYPE*)srcBitmap.getPixels();
+ NOFILTER_BITMAP_SHADER_PREAMBLE(srcBitmap, srcRB);
+
+ if (this->getInverseClass() == kPerspective_MatrixClass)
+ {
+ SkPerspIter iter(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+
+/* Do I need this?
+#ifndef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ fx >>= level;
+ fy >>= level;
+#endif
+*/
+ if (256 == scale)
+ {
+ while (--count >= 0)
+ {
+ fx = *srcXY++;
+ fy = *srcXY++;
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_XY(srcPixels, x, y, srcRB);
+ }
+ }
+ else
+ {
+ while (--count >= 0)
+ {
+ fx = *srcXY++;
+ fy = *srcXY++;
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ uint32_t c = NOFILTER_BITMAP_SHADER_SAMPLE_XY(srcPixels, x, y, srcRB);
+ *dstC++ = SkAlphaMulQ(c, scale);
+ }
+ }
+ }
+ return;
+ }
+
+ // now init fx, fy, dx, dy
+ {
+ SkPoint srcPt;
+ invProc(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ fx = SkScalarToFixed(srcPt.fX);
+ fy = SkScalarToFixed(srcPt.fY);
+
+ if (this->getInverseClass() == kFixedStepInX_MatrixClass)
+ (void)inv.fixedStepInX(SkIntToScalar(y), &dx, &dy);
+ else
+ {
+ dx = SkScalarToFixed(inv.getScaleX());
+ dy = SkScalarToFixed(inv.getSkewY());
+ }
+ }
+
+#ifndef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ { int level = this->getMipLevel() >> 16;
+ fx >>= level;
+ fy >>= level;
+ dx >>= level;
+ dy >>= level;
+ }
+#endif
+
+ if (dy == 0)
+ {
+ int y_index = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+// SkDEBUGF(("fy = %g, srcMaxY = %d, y_index = %d\n", SkFixedToFloat(fy), srcMaxY, y_index));
+ srcPixels = (const NOFILTER_BITMAP_SHADER_TYPE*)((const char*)srcPixels + y_index * srcRB);
+ if (scale == 256)
+ while (--count >= 0)
+ {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ fx += dx;
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_X(srcPixels, x);
+ }
+ else
+ while (--count >= 0)
+ {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ SkPMColor c = NOFILTER_BITMAP_SHADER_SAMPLE_X(srcPixels, x);
+ fx += dx;
+ *dstC++ = SkAlphaMulQ(c, scale);
+ }
+ }
+ else // dy != 0
+ {
+ if (scale == 256)
+ while (--count >= 0)
+ {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ fx += dx;
+ fy += dy;
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_XY(srcPixels, x, y, srcRB);
+ }
+ else
+ while (--count >= 0)
+ {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ SkPMColor c = NOFILTER_BITMAP_SHADER_SAMPLE_XY(srcPixels, x, y, srcRB);
+ fx += dx;
+ fy += dy;
+ *dstC++ = SkAlphaMulQ(c, scale);
+ }
+ }
+
+ NOFILTER_BITMAP_SHADER_POSTAMBLE(srcBitmap);
+ }
+
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count)
+ {
+ SkASSERT(count > 0);
+ SkASSERT(this->getFlags() & SkShader::kHasSpan16_Flag);
+
+#ifdef NOFILTER_BITMAP_SHADER_SPRITEPROC16
+ if ((this->getTotalInverse().getType() & ~SkMatrix::kTranslate_Mask) == 0)
+ {
+ NOFILTER_BITMAP_SHADER_SPRITEPROC16(this, x, y, dstC, count);
+ return;
+ }
+#endif
+
+#ifdef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ const SkMatrix& inv = this->getUnitInverse();
+ SkMatrix::MapPtProc invProc = this->getUnitInverseProc();
+#else
+ const SkMatrix& inv = this->getTotalInverse();
+ SkMatrix::MapPtProc invProc = this->getInverseMapPtProc();
+#endif
+ const SkBitmap& srcBitmap = this->getSrcBitmap();
+ unsigned srcMaxX = srcBitmap.width() - 1;
+ unsigned srcMaxY = srcBitmap.height() - 1;
+ unsigned srcRB = srcBitmap.rowBytes();
+ SkFixed fx, fy, dx, dy;
+
+ const NOFILTER_BITMAP_SHADER_TYPE* srcPixels = (const NOFILTER_BITMAP_SHADER_TYPE*)srcBitmap.getPixels();
+ NOFILTER_BITMAP_SHADER_PREAMBLE16(srcBitmap, srcRB);
+
+ if (this->getInverseClass() == kPerspective_MatrixClass)
+ {
+ SkPerspIter iter(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+ while ((count = iter.next()) != 0)
+ {
+ const SkFixed* srcXY = iter.getXY();
+
+ while (--count >= 0)
+ {
+ fx = *srcXY++;
+ fy = *srcXY++;
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_XY16(srcPixels, x, y, srcRB);
+ }
+ }
+ return;
+ }
+
+ // now init fx, fy, dx, dy
+ {
+ SkPoint srcPt;
+ invProc(inv, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ fx = SkScalarToFixed(srcPt.fX);
+ fy = SkScalarToFixed(srcPt.fY);
+
+ if (this->getInverseClass() == kFixedStepInX_MatrixClass)
+ (void)inv.fixedStepInX(SkIntToScalar(y), &dx, &dy);
+ else
+ {
+ dx = SkScalarToFixed(inv.getScaleX());
+ dy = SkScalarToFixed(inv.getSkewY());
+ }
+ }
+
+#ifndef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+ { int level = this->getMipLevel() >> 16;
+ fx >>= level;
+ fy >>= level;
+ dx >>= level;
+ dy >>= level;
+ }
+#endif
+
+ if (dy == 0)
+ {
+ srcPixels = (const NOFILTER_BITMAP_SHADER_TYPE*)((const char*)srcPixels + NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY) * srcRB);
+ do {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ fx += dx;
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_X16(srcPixels, x);
+ } while (--count != 0);
+ }
+ else // dy != 0
+ {
+ do {
+ unsigned x = NOFILTER_BITMAP_SHADER_TILEPROC(fx, srcMaxX);
+ unsigned y = NOFILTER_BITMAP_SHADER_TILEPROC(fy, srcMaxY);
+ fx += dx;
+ fy += dy;
+ *dstC++ = NOFILTER_BITMAP_SHADER_SAMPLE_XY16(srcPixels, x, y, srcRB);
+ } while (--count != 0);
+ }
+
+ NOFILTER_BITMAP_SHADER_POSTAMBLE16(srcBitmap);
+ }
+private:
+ typedef HasSpan16_Sampler_BitmapShader INHERITED;
+};
+
+#undef NOFILTER_BITMAP_SHADER_CLASS
+#undef NOFILTER_BITMAP_SHADER_TYPE
+#undef NOFILTER_BITMAP_SHADER_PREAMBLE
+#undef NOFILTER_BITMAP_SHADER_POSTAMBLE
+#undef NOFILTER_BITMAP_SHADER_SAMPLE_X //(x)
+#undef NOFILTER_BITMAP_SHADER_SAMPLE_XY //(x, y, rowBytes)
+#undef NOFILTER_BITMAP_SHADER_TILEMODE
+#undef NOFILTER_BITMAP_SHADER_TILEPROC
+
+#undef NOFILTER_BITMAP_SHADER_PREAMBLE16
+#undef NOFILTER_BITMAP_SHADER_POSTAMBLE16
+#undef NOFILTER_BITMAP_SHADER_SAMPLE_X16 //(x)
+#undef NOFILTER_BITMAP_SHADER_SAMPLE_XY16 //(x, y, rowBytes)
+
+#undef NOFILTER_BITMAP_SHADER_USE_UNITINVERSE
+#undef NOFILTER_BITMAP_SHADER_SPRITEPROC16
+#undef NOFILTER_BITMAP_SHADER_SPRITEPROC32
diff --git a/core/SkBitmap_scroll.cpp b/core/SkBitmap_scroll.cpp
new file mode 100644
index 00000000..e9c886f4
--- /dev/null
+++ b/core/SkBitmap_scroll.cpp
@@ -0,0 +1,123 @@
+
+/*
+ * 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 "SkBitmap.h"
+#include "SkRegion.h"
+
+bool SkBitmap::scrollRect(const SkIRect* subset, int dx, int dy,
+ SkRegion* inval) const
+{
+ if (this->isImmutable()) {
+ return false;
+ }
+
+ if (NULL != subset) {
+ SkBitmap tmp;
+
+ return this->extractSubset(&tmp, *subset) &&
+ // now call again with no rectangle
+ tmp.scrollRect(NULL, dx, dy, inval);
+ }
+
+ int shift;
+
+ switch (this->config()) {
+ case kIndex8_Config:
+ case kA8_Config:
+ shift = 0;
+ break;
+ case kARGB_4444_Config:
+ case kRGB_565_Config:
+ shift = 1;
+ break;
+ case kARGB_8888_Config:
+ shift = 2;
+ break;
+ default:
+ // can't scroll this config
+ return false;
+ }
+
+ int width = this->width();
+ int height = this->height();
+
+ // check if there's nothing to do
+ if ((dx | dy) == 0 || width <= 0 || height <= 0) {
+ if (NULL != inval) {
+ inval->setEmpty();
+ }
+ return true;
+ }
+
+ // compute the inval region now, before we see if there are any pixels
+ if (NULL != inval) {
+ SkIRect r;
+
+ r.set(0, 0, width, height);
+ // initial the region with the entire bounds
+ inval->setRect(r);
+ // do the "scroll"
+ r.offset(dx, dy);
+
+ // check if we scrolled completely away
+ if (!SkIRect::Intersects(r, inval->getBounds())) {
+ // inval has already been updated...
+ return true;
+ }
+
+ // compute the dirty area
+ inval->op(r, SkRegion::kDifference_Op);
+ }
+
+ SkAutoLockPixels alp(*this);
+ // if we have no pixels, just return (inval is already updated)
+ // don't call readyToDraw(), since we don't require a colortable per se
+ if (this->getPixels() == NULL) {
+ return true;
+ }
+
+ char* dst = (char*)this->getPixels();
+ const char* src = dst;
+ int rowBytes = (int)this->rowBytes(); // need rowBytes to be signed
+
+ if (dy <= 0) {
+ src -= dy * rowBytes;
+ height += dy;
+ } else {
+ dst += dy * rowBytes;
+ height -= dy;
+ // now jump src/dst to the last scanline
+ src += (height - 1) * rowBytes;
+ dst += (height - 1) * rowBytes;
+ // now invert rowbytes so we copy backwards in the loop
+ rowBytes = -rowBytes;
+ }
+
+ if (dx <= 0) {
+ src -= dx << shift;
+ width += dx;
+ } else {
+ dst += dx << shift;
+ width -= dx;
+ }
+
+ // If the X-translation would push it completely beyond the region,
+ // then there's nothing to draw.
+ if (width <= 0) {
+ return true;
+ }
+
+ width <<= shift; // now width is the number of bytes to move per line
+ while (--height >= 0) {
+ memmove(dst, src, width);
+ dst += rowBytes;
+ src += rowBytes;
+ }
+
+ this->notifyPixelsChanged();
+ return true;
+}
diff --git a/core/SkBlitBWMaskTemplate.h b/core/SkBlitBWMaskTemplate.h
new file mode 100644
index 00000000..798e44aa
--- /dev/null
+++ b/core/SkBlitBWMaskTemplate.h
@@ -0,0 +1,128 @@
+
+/*
+ * 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 "SkBitmap.h"
+#include "SkMask.h"
+
+#ifndef ClearLow3Bits_DEFINED
+#define ClearLow3Bits_DEFINED
+ #define ClearLow3Bits(x) ((unsigned)(x) >> 3 << 3)
+#endif
+
+/*
+ SK_BLITBWMASK_NAME name of function(const SkBitmap& bitmap, const SkMask& mask, const SkIRect& clip, SK_BLITBWMASK_ARGS)
+ SK_BLITBWMASK_ARGS list of additional arguments to SK_BLITBWMASK_NAME, beginning with a comma
+ SK_BLITBWMASK_BLIT8 name of function(U8CPU byteMask, SK_BLITBWMASK_DEVTYPE* dst, int x, int y)
+ SK_BLITBWMASK_GETADDR either getAddr32 or getAddr16 or getAddr8
+ SK_BLITBWMASK_DEVTYPE either U32 or U16 or U8
+*/
+
+static void SK_BLITBWMASK_NAME(const SkBitmap& bitmap, const SkMask& srcMask, const SkIRect& clip SK_BLITBWMASK_ARGS)
+{
+ SkASSERT(clip.fRight <= srcMask.fBounds.fRight);
+
+ int cx = clip.fLeft;
+ int cy = clip.fTop;
+ int maskLeft = srcMask.fBounds.fLeft;
+ unsigned mask_rowBytes = srcMask.fRowBytes;
+ size_t bitmap_rowBytes = bitmap.rowBytes();
+ unsigned height = clip.height();
+
+ SkASSERT(mask_rowBytes != 0);
+ SkASSERT(bitmap_rowBytes != 0);
+ SkASSERT(height != 0);
+
+ const uint8_t* bits = srcMask.getAddr1(cx, cy);
+ SK_BLITBWMASK_DEVTYPE* device = bitmap.SK_BLITBWMASK_GETADDR(cx, cy);
+
+ if (cx == maskLeft && clip.fRight == srcMask.fBounds.fRight)
+ {
+ do {
+ SK_BLITBWMASK_DEVTYPE* dst = device;
+ unsigned rb = mask_rowBytes;
+ do {
+ U8CPU mask = *bits++;
+ SK_BLITBWMASK_BLIT8(mask, dst);
+ dst += 8;
+ } while (--rb != 0);
+ device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes);
+ } while (--height != 0);
+ }
+ else
+ {
+ int left_edge = cx - maskLeft;
+ SkASSERT(left_edge >= 0);
+ int rite_edge = clip.fRight - maskLeft;
+ SkASSERT(rite_edge > left_edge);
+
+ int left_mask = 0xFF >> (left_edge & 7);
+ int rite_mask = 0xFF << (8 - (rite_edge & 7));
+ rite_mask &= 0xFF; // only want low-8 bits of mask
+ int full_runs = (rite_edge >> 3) - ((left_edge + 7) >> 3);
+
+ // check for empty right mask, so we don't read off the end (or go slower than we need to)
+ if (rite_mask == 0)
+ {
+ SkASSERT(full_runs >= 0);
+ full_runs -= 1;
+ rite_mask = 0xFF;
+ }
+ if (left_mask == 0xFF)
+ full_runs -= 1;
+
+ // back up manually so we can keep in sync with our byte-aligned src
+ // and not trigger an assert from the getAddr## function
+ device -= left_edge & 7;
+
+ if (full_runs < 0)
+ {
+ left_mask &= rite_mask;
+ SkASSERT(left_mask != 0);
+ do {
+ U8CPU mask = *bits & left_mask;
+ SK_BLITBWMASK_BLIT8(mask, device);
+ bits += mask_rowBytes;
+ device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes);
+ } while (--height != 0);
+ }
+ else
+ {
+ do {
+ int runs = full_runs;
+ SK_BLITBWMASK_DEVTYPE* dst = device;
+ const uint8_t* b = bits;
+ U8CPU mask;
+
+ mask = *b++ & left_mask;
+ SK_BLITBWMASK_BLIT8(mask, dst);
+ dst += 8;
+
+ while (--runs >= 0)
+ {
+ mask = *b++;
+ SK_BLITBWMASK_BLIT8(mask, dst);
+ dst += 8;
+ }
+
+ mask = *b & rite_mask;
+ SK_BLITBWMASK_BLIT8(mask, dst);
+
+ bits += mask_rowBytes;
+ device = (SK_BLITBWMASK_DEVTYPE*)((char*)device + bitmap_rowBytes);
+ } while (--height != 0);
+ }
+ }
+}
+
+#undef SK_BLITBWMASK_NAME
+#undef SK_BLITBWMASK_ARGS
+#undef SK_BLITBWMASK_BLIT8
+#undef SK_BLITBWMASK_GETADDR
+#undef SK_BLITBWMASK_DEVTYPE
+#undef SK_BLITBWMASK_DOROWSETUP
diff --git a/core/SkBlitMask.h b/core/SkBlitMask.h
new file mode 100644
index 00000000..ea1da257
--- /dev/null
+++ b/core/SkBlitMask.h
@@ -0,0 +1,90 @@
+/*
+ * 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 SkBlitMask_DEFINED
+#define SkBlitMask_DEFINED
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkMask.h"
+
+class SkBlitMask {
+public:
+ /**
+ * Returns true if the device config and mask format were supported.
+ * else return false (nothing was drawn)
+ */
+ static bool BlitColor(const SkBitmap& device, const SkMask& mask,
+ const SkIRect& clip, SkColor color);
+
+ /**
+ * Function pointer that blits the mask into a device (dst) colorized
+ * by color. The number of pixels to blit is specified by width and height,
+ * but each scanline is offset by dstRB (rowbytes) and srcRB respectively.
+ */
+ typedef void (*ColorProc)(void* dst, size_t dstRB,
+ const void* mask, size_t maskRB,
+ SkColor color, int width, int height);
+
+ /**
+ * Function pointer that blits a row of mask(lcd16) into a row of dst
+ * colorized by a single color. The number of pixels to blit is specified
+ * by width.
+ */
+ typedef void (*BlitLCD16RowProc)(SkPMColor dst[], const uint16_t src[],
+ SkColor color, int width,
+ SkPMColor opaqueDst);
+
+ /**
+ * Function pointer that blits a row of src colors through a row of a mask
+ * onto a row of dst colors. The RowFactory that returns this function ptr
+ * will have been told the formats for the mask and the dst.
+ */
+ typedef void (*RowProc)(void* dst, const void* mask,
+ const SkPMColor* src, int width);
+
+ /**
+ * Public entry-point to return a blitmask ColorProc.
+ * May return NULL if config or format are not supported.
+ */
+ static ColorProc ColorFactory(SkBitmap::Config, SkMask::Format, SkColor);
+
+ /**
+ * Return either platform specific optimized blitmask ColorProc,
+ * or NULL if no optimized routine is available.
+ */
+ static ColorProc PlatformColorProcs(SkBitmap::Config, SkMask::Format, SkColor);
+
+ /**
+ * Public entry-point to return a blitcolor BlitLCD16RowProc.
+ */
+ static BlitLCD16RowProc BlitLCD16RowFactory(bool isOpaque);
+
+ /**
+ * Return either platform specific optimized blitcolor BlitLCD16RowProc,
+ * or NULL if no optimized routine is available.
+ */
+ static BlitLCD16RowProc PlatformBlitRowProcs16(bool isOpaque);
+
+ enum RowFlags {
+ kSrcIsOpaque_RowFlag = 1 << 0
+ };
+
+ /**
+ * Public entry-point to return a blitmask RowProc.
+ * May return NULL if config or format are not supported.
+ */
+ static RowProc RowFactory(SkBitmap::Config, SkMask::Format, RowFlags);
+
+ /**
+ * Return either platform specific optimized blitmask RowProc,
+ * or NULL if no optimized routine is available.
+ */
+ static RowProc PlatformRowProcs(SkBitmap::Config, SkMask::Format, RowFlags);
+};
+
+#endif
diff --git a/core/SkBlitMask_D32.cpp b/core/SkBlitMask_D32.cpp
new file mode 100644
index 00000000..1f16d775
--- /dev/null
+++ b/core/SkBlitMask_D32.cpp
@@ -0,0 +1,592 @@
+#include "SkBlitMask.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+
+static void D32_A8_Color(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT maskPtr, size_t maskRB,
+ SkColor color, int width, int height) {
+ SkPMColor pmc = SkPreMultiplyColor(color);
+ size_t dstOffset = dstRB - (width << 2);
+ size_t maskOffset = maskRB - width;
+ SkPMColor* SK_RESTRICT device = (SkPMColor *)dst;
+ const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;
+
+ do {
+ int w = width;
+ do {
+ unsigned aa = *mask++;
+ *device = SkBlendARGB32(pmc, *device, aa);
+ device += 1;
+ } while (--w != 0);
+ device = (uint32_t*)((char*)device + dstOffset);
+ mask += maskOffset;
+ } while (--height != 0);
+}
+
+static void D32_A8_Opaque(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT maskPtr, size_t maskRB,
+ SkColor color, int width, int height) {
+ SkPMColor pmc = SkPreMultiplyColor(color);
+ SkPMColor* SK_RESTRICT device = (SkPMColor*)dst;
+ const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;
+
+ maskRB -= width;
+ dstRB -= (width << 2);
+ do {
+ int w = width;
+ do {
+ unsigned aa = *mask++;
+ *device = SkAlphaMulQ(pmc, SkAlpha255To256(aa)) + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
+ device += 1;
+ } while (--w != 0);
+ device = (uint32_t*)((char*)device + dstRB);
+ mask += maskRB;
+ } while (--height != 0);
+}
+
+static void D32_A8_Black(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT maskPtr, size_t maskRB,
+ SkColor, int width, int height) {
+ SkPMColor* SK_RESTRICT device = (SkPMColor*)dst;
+ const uint8_t* SK_RESTRICT mask = (const uint8_t*)maskPtr;
+
+ maskRB -= width;
+ dstRB -= (width << 2);
+ do {
+ int w = width;
+ do {
+ unsigned aa = *mask++;
+ *device = (aa << SK_A32_SHIFT) + SkAlphaMulQ(*device, SkAlpha255To256(255 - aa));
+ device += 1;
+ } while (--w != 0);
+ device = (uint32_t*)((char*)device + dstRB);
+ mask += maskRB;
+ } while (--height != 0);
+}
+
+SkBlitMask::BlitLCD16RowProc SkBlitMask::BlitLCD16RowFactory(bool isOpaque) {
+ BlitLCD16RowProc proc = PlatformBlitRowProcs16(isOpaque);
+ if (proc) {
+ return proc;
+ }
+
+ if (isOpaque) {
+ return SkBlitLCD16OpaqueRow;
+ } else {
+ return SkBlitLCD16Row;
+ }
+}
+
+static void D32_LCD16_Proc(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT mask, size_t maskRB,
+ SkColor color, int width, int height) {
+
+ SkPMColor* dstRow = (SkPMColor*)dst;
+ const uint16_t* srcRow = (const uint16_t*)mask;
+ SkPMColor opaqueDst;
+
+ SkBlitMask::BlitLCD16RowProc proc = NULL;
+ bool isOpaque = (0xFF == SkColorGetA(color));
+ proc = SkBlitMask::BlitLCD16RowFactory(isOpaque);
+ SkASSERT(proc != NULL);
+
+ if (isOpaque) {
+ opaqueDst = SkPreMultiplyColor(color);
+ } else {
+ opaqueDst = 0; // ignored
+ }
+
+ do {
+ proc(dstRow, srcRow, color, width, opaqueDst);
+ dstRow = (SkPMColor*)((char*)dstRow + dstRB);
+ srcRow = (const uint16_t*)((const char*)srcRow + maskRB);
+ } while (--height != 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void blit_lcd32_opaque_row(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ SkColor color, int width) {
+ int srcR = SkColorGetR(color);
+ int srcG = SkColorGetG(color);
+ int srcB = SkColorGetB(color);
+
+ for (int i = 0; i < width; i++) {
+ SkPMColor mask = src[i];
+ if (0 == mask) {
+ continue;
+ }
+
+ SkPMColor d = dst[i];
+
+ int maskR = SkGetPackedR32(mask);
+ int maskG = SkGetPackedG32(mask);
+ int maskB = SkGetPackedB32(mask);
+
+ // Now upscale them to 0..256, so we can use SkAlphaBlend
+ maskR = SkAlpha255To256(maskR);
+ maskG = SkAlpha255To256(maskG);
+ maskB = SkAlpha255To256(maskB);
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ SkAlphaBlend(srcR, dstR, maskR),
+ SkAlphaBlend(srcG, dstG, maskG),
+ SkAlphaBlend(srcB, dstB, maskB));
+ }
+}
+
+static void blit_lcd32_row(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ SkColor color, int width) {
+ int srcA = SkColorGetA(color);
+ int srcR = SkColorGetR(color);
+ int srcG = SkColorGetG(color);
+ int srcB = SkColorGetB(color);
+
+ srcA = SkAlpha255To256(srcA);
+
+ for (int i = 0; i < width; i++) {
+ SkPMColor mask = src[i];
+ if (0 == mask) {
+ continue;
+ }
+
+ SkPMColor d = dst[i];
+
+ int maskR = SkGetPackedR32(mask);
+ int maskG = SkGetPackedG32(mask);
+ int maskB = SkGetPackedB32(mask);
+
+ // Now upscale them to 0..256, so we can use SkAlphaBlend
+ maskR = SkAlpha255To256(maskR);
+ maskG = SkAlpha255To256(maskG);
+ maskB = SkAlpha255To256(maskB);
+
+ maskR = maskR * srcA >> 8;
+ maskG = maskG * srcA >> 8;
+ maskB = maskB * srcA >> 8;
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ SkAlphaBlend(srcR, dstR, maskR),
+ SkAlphaBlend(srcG, dstG, maskG),
+ SkAlphaBlend(srcB, dstB, maskB));
+ }
+}
+
+static void D32_LCD32_Blend(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT mask, size_t maskRB,
+ SkColor color, int width, int height) {
+ SkASSERT(height > 0);
+ SkPMColor* SK_RESTRICT dstRow = (SkPMColor*)dst;
+ const SkPMColor* SK_RESTRICT srcRow = (const SkPMColor*)mask;
+
+ do {
+ blit_lcd32_row(dstRow, srcRow, color, width);
+ dstRow = (SkPMColor*)((char*)dstRow + dstRB);
+ srcRow = (const SkPMColor*)((const char*)srcRow + maskRB);
+ } while (--height != 0);
+}
+
+static void D32_LCD32_Opaque(void* SK_RESTRICT dst, size_t dstRB,
+ const void* SK_RESTRICT mask, size_t maskRB,
+ SkColor color, int width, int height) {
+ SkASSERT(height > 0);
+ SkPMColor* SK_RESTRICT dstRow = (SkPMColor*)dst;
+ const SkPMColor* SK_RESTRICT srcRow = (const SkPMColor*)mask;
+
+ do {
+ blit_lcd32_opaque_row(dstRow, srcRow, color, width);
+ dstRow = (SkPMColor*)((char*)dstRow + dstRB);
+ srcRow = (const SkPMColor*)((const char*)srcRow + maskRB);
+ } while (--height != 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkBlitMask::ColorProc D32_A8_Factory(SkColor color) {
+ if (SK_ColorBLACK == color) {
+ return D32_A8_Black;
+ } else if (0xFF == SkColorGetA(color)) {
+ return D32_A8_Opaque;
+ } else {
+ return D32_A8_Color;
+ }
+}
+
+static SkBlitMask::ColorProc D32_LCD32_Factory(SkColor color) {
+ return (0xFF == SkColorGetA(color)) ? D32_LCD32_Opaque : D32_LCD32_Blend;
+}
+
+SkBlitMask::ColorProc SkBlitMask::ColorFactory(SkBitmap::Config config,
+ SkMask::Format format,
+ SkColor color) {
+ ColorProc proc = PlatformColorProcs(config, format, color);
+ if (proc) {
+ return proc;
+ }
+
+ switch (config) {
+ case SkBitmap::kARGB_8888_Config:
+ switch (format) {
+ case SkMask::kA8_Format:
+ return D32_A8_Factory(color);
+ case SkMask::kLCD16_Format:
+ return D32_LCD16_Proc;
+ case SkMask::kLCD32_Format:
+ return D32_LCD32_Factory(color);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+bool SkBlitMask::BlitColor(const SkBitmap& device, const SkMask& mask,
+ const SkIRect& clip, SkColor color) {
+ ColorProc proc = ColorFactory(device.config(), mask.fFormat, color);
+ if (proc) {
+ int x = clip.fLeft;
+ int y = clip.fTop;
+ proc(device.getAddr32(x, y), device.rowBytes(), mask.getAddr(x, y),
+ mask.fRowBytes, color, clip.width(), clip.height());
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void BW_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ int i, octuple = (count + 7) >> 3;
+ for (i = 0; i < octuple; ++i) {
+ int m = *mask++;
+ if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
+ if (m & 0x40) { dst[1] = SkPMSrcOver(src[1], dst[1]); }
+ if (m & 0x20) { dst[2] = SkPMSrcOver(src[2], dst[2]); }
+ if (m & 0x10) { dst[3] = SkPMSrcOver(src[3], dst[3]); }
+ if (m & 0x08) { dst[4] = SkPMSrcOver(src[4], dst[4]); }
+ if (m & 0x04) { dst[5] = SkPMSrcOver(src[5], dst[5]); }
+ if (m & 0x02) { dst[6] = SkPMSrcOver(src[6], dst[6]); }
+ if (m & 0x01) { dst[7] = SkPMSrcOver(src[7], dst[7]); }
+ src += 8;
+ dst += 8;
+ }
+ count &= 7;
+ if (count > 0) {
+ int m = *mask;
+ do {
+ if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
+ m <<= 1;
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+ }
+}
+
+static void BW_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ int i, octuple = (count + 7) >> 3;
+ for (i = 0; i < octuple; ++i) {
+ int m = *mask++;
+ if (m & 0x80) { dst[0] = src[0]; }
+ if (m & 0x40) { dst[1] = src[1]; }
+ if (m & 0x20) { dst[2] = src[2]; }
+ if (m & 0x10) { dst[3] = src[3]; }
+ if (m & 0x08) { dst[4] = src[4]; }
+ if (m & 0x04) { dst[5] = src[5]; }
+ if (m & 0x02) { dst[6] = src[6]; }
+ if (m & 0x01) { dst[7] = src[7]; }
+ src += 8;
+ dst += 8;
+ }
+ count &= 7;
+ if (count > 0) {
+ int m = *mask;
+ do {
+ if (m & 0x80) { dst[0] = SkPMSrcOver(src[0], dst[0]); }
+ m <<= 1;
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+ }
+}
+
+static void A8_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ for (int i = 0; i < count; ++i) {
+ if (mask[i]) {
+ dst[i] = SkBlendARGB32(src[i], dst[i], mask[i]);
+ }
+ }
+}
+
+// expand the steps that SkAlphaMulQ performs, but this way we can
+// exand.. add.. combine
+// instead of
+// expand..combine add expand..combine
+//
+#define EXPAND0(v, m, s) ((v) & (m)) * (s)
+#define EXPAND1(v, m, s) (((v) >> 8) & (m)) * (s)
+#define COMBINE(e0, e1, m) ((((e0) >> 8) & (m)) | ((e1) & ~(m)))
+
+static void A8_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+#if 0 // suppress warning
+ const uint32_t rbmask = gMask_00FF00FF;
+#endif
+ for (int i = 0; i < count; ++i) {
+ int m = mask[i];
+ if (m) {
+ m += (m >> 7);
+#if 1
+ // this is slightly slower than the expand/combine version, but it
+ // is much closer to the old results, so we use it for now to reduce
+ // rebaselining.
+ dst[i] = SkAlphaMulQ(src[i], m) + SkAlphaMulQ(dst[i], 256 - m);
+#else
+ uint32_t v = src[i];
+ uint32_t s0 = EXPAND0(v, rbmask, m);
+ uint32_t s1 = EXPAND1(v, rbmask, m);
+ v = dst[i];
+ uint32_t d0 = EXPAND0(v, rbmask, m);
+ uint32_t d1 = EXPAND1(v, rbmask, m);
+ dst[i] = COMBINE(s0 + d0, s1 + d1, rbmask);
+#endif
+ }
+ }
+}
+
+static int upscale31To255(int value) {
+ value = (value << 3) | (value >> 2);
+ return value;
+}
+
+static int src_alpha_blend(int src, int dst, int srcA, int mask) {
+
+ return dst + SkAlphaMul(src - SkAlphaMul(srcA, dst), mask);
+}
+
+static void LCD16_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
+ const uint16_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ for (int i = 0; i < count; ++i) {
+ uint16_t m = mask[i];
+ if (0 == m) {
+ continue;
+ }
+
+ SkPMColor s = src[i];
+ SkPMColor d = dst[i];
+
+ int srcA = SkGetPackedA32(s);
+ int srcR = SkGetPackedR32(s);
+ int srcG = SkGetPackedG32(s);
+ int srcB = SkGetPackedB32(s);
+
+ srcA += srcA >> 7;
+
+ /* We want all of these in 5bits, hence the shifts in case one of them
+ * (green) is 6bits.
+ */
+ int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5);
+ int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5);
+ int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5);
+
+ maskR = upscale31To255(maskR);
+ maskG = upscale31To255(maskG);
+ maskB = upscale31To255(maskB);
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ src_alpha_blend(srcR, dstR, srcA, maskR),
+ src_alpha_blend(srcG, dstG, srcA, maskG),
+ src_alpha_blend(srcB, dstB, srcA, maskB));
+ }
+}
+
+static void LCD16_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
+ const uint16_t* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ for (int i = 0; i < count; ++i) {
+ uint16_t m = mask[i];
+ if (0 == m) {
+ continue;
+ }
+
+ SkPMColor s = src[i];
+ SkPMColor d = dst[i];
+
+ int srcR = SkGetPackedR32(s);
+ int srcG = SkGetPackedG32(s);
+ int srcB = SkGetPackedB32(s);
+
+ /* We want all of these in 5bits, hence the shifts in case one of them
+ * (green) is 6bits.
+ */
+ int maskR = SkGetPackedR16(m) >> (SK_R16_BITS - 5);
+ int maskG = SkGetPackedG16(m) >> (SK_G16_BITS - 5);
+ int maskB = SkGetPackedB16(m) >> (SK_B16_BITS - 5);
+
+ // Now upscale them to 0..32, so we can use blend32
+ maskR = SkUpscale31To32(maskR);
+ maskG = SkUpscale31To32(maskG);
+ maskB = SkUpscale31To32(maskB);
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ SkBlend32(srcR, dstR, maskR),
+ SkBlend32(srcG, dstG, maskG),
+ SkBlend32(srcB, dstB, maskB));
+ }
+}
+
+static void LCD32_RowProc_Blend(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ for (int i = 0; i < count; ++i) {
+ SkPMColor m = mask[i];
+ if (0 == m) {
+ continue;
+ }
+
+ SkPMColor s = src[i];
+ int srcA = SkGetPackedA32(s);
+ int srcR = SkGetPackedR32(s);
+ int srcG = SkGetPackedG32(s);
+ int srcB = SkGetPackedB32(s);
+
+ srcA = SkAlpha255To256(srcA);
+
+ SkPMColor d = dst[i];
+
+ int maskR = SkGetPackedR32(m);
+ int maskG = SkGetPackedG32(m);
+ int maskB = SkGetPackedB32(m);
+
+ // Now upscale them to 0..256
+ maskR = SkAlpha255To256(maskR);
+ maskG = SkAlpha255To256(maskG);
+ maskB = SkAlpha255To256(maskB);
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ src_alpha_blend(srcR, dstR, srcA, maskR),
+ src_alpha_blend(srcG, dstG, srcA, maskG),
+ src_alpha_blend(srcB, dstB, srcA, maskB));
+ }
+}
+
+static void LCD32_RowProc_Opaque(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT mask,
+ const SkPMColor* SK_RESTRICT src, int count) {
+ for (int i = 0; i < count; ++i) {
+ SkPMColor m = mask[i];
+ if (0 == m) {
+ continue;
+ }
+
+ SkPMColor s = src[i];
+ SkPMColor d = dst[i];
+
+ int maskR = SkGetPackedR32(m);
+ int maskG = SkGetPackedG32(m);
+ int maskB = SkGetPackedB32(m);
+
+ int srcR = SkGetPackedR32(s);
+ int srcG = SkGetPackedG32(s);
+ int srcB = SkGetPackedB32(s);
+
+ int dstR = SkGetPackedR32(d);
+ int dstG = SkGetPackedG32(d);
+ int dstB = SkGetPackedB32(d);
+
+ // Now upscale them to 0..256, so we can use SkAlphaBlend
+ maskR = SkAlpha255To256(maskR);
+ maskG = SkAlpha255To256(maskG);
+ maskB = SkAlpha255To256(maskB);
+
+ // LCD blitting is only supported if the dst is known/required
+ // to be opaque
+ dst[i] = SkPackARGB32(0xFF,
+ SkAlphaBlend(srcR, dstR, maskR),
+ SkAlphaBlend(srcG, dstG, maskG),
+ SkAlphaBlend(srcB, dstB, maskB));
+ }
+}
+
+SkBlitMask::RowProc SkBlitMask::RowFactory(SkBitmap::Config config,
+ SkMask::Format format,
+ RowFlags flags) {
+// make this opt-in until chrome can rebaseline
+ RowProc proc = PlatformRowProcs(config, format, flags);
+ if (proc) {
+ return proc;
+ }
+
+ static const RowProc gProcs[] = {
+ // need X coordinate to handle BW
+ false ? (RowProc)BW_RowProc_Blend : NULL, // suppress unused warning
+ false ? (RowProc)BW_RowProc_Opaque : NULL, // suppress unused warning
+ (RowProc)A8_RowProc_Blend, (RowProc)A8_RowProc_Opaque,
+ (RowProc)LCD16_RowProc_Blend, (RowProc)LCD16_RowProc_Opaque,
+ (RowProc)LCD32_RowProc_Blend, (RowProc)LCD32_RowProc_Opaque,
+ };
+
+ int index;
+ switch (config) {
+ case SkBitmap::kARGB_8888_Config:
+ switch (format) {
+ case SkMask::kBW_Format: index = 0; break;
+ case SkMask::kA8_Format: index = 2; break;
+ case SkMask::kLCD16_Format: index = 4; break;
+ case SkMask::kLCD32_Format: index = 6; break;
+ default:
+ return NULL;
+ }
+ if (flags & kSrcIsOpaque_RowFlag) {
+ index |= 1;
+ }
+ SkASSERT((size_t)index < SK_ARRAY_COUNT(gProcs));
+ return gProcs[index];
+ default:
+ break;
+ }
+ return NULL;
+}
diff --git a/core/SkBlitRow_D16.cpp b/core/SkBlitRow_D16.cpp
new file mode 100644
index 00000000..e2447239
--- /dev/null
+++ b/core/SkBlitRow_D16.cpp
@@ -0,0 +1,245 @@
+/*
+ * 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 "SkBlitRow.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkMathPriv.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void S32_D565_Opaque(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 == alpha);
+
+ if (count > 0) {
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ *dst++ = SkPixel32ToPixel16_ToU16(c);
+ } while (--count != 0);
+ }
+}
+
+static void S32_D565_Blend(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 > alpha);
+
+ if (count > 0) {
+ int scale = SkAlpha255To256(alpha);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ uint16_t d = *dst;
+ *dst++ = SkPackRGB16(
+ SkAlphaBlend(SkPacked32ToR16(c), SkGetPackedR16(d), scale),
+ SkAlphaBlend(SkPacked32ToG16(c), SkGetPackedG16(d), scale),
+ SkAlphaBlend(SkPacked32ToB16(c), SkGetPackedB16(d), scale));
+ } while (--count != 0);
+ }
+}
+
+static void S32A_D565_Opaque(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 == alpha);
+
+ if (count > 0) {
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+// if (__builtin_expect(c!=0, 1))
+ if (c) {
+ *dst = SkSrcOver32To16(c, *dst);
+ }
+ dst += 1;
+ } while (--count != 0);
+ }
+}
+
+static void S32A_D565_Blend(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 > alpha);
+
+ if (count > 0) {
+ do {
+ SkPMColor sc = *src++;
+ SkPMColorAssert(sc);
+ if (sc) {
+ uint16_t dc = *dst;
+ unsigned dst_scale = 255 - SkMulDiv255Round(SkGetPackedA32(sc), alpha);
+ unsigned dr = SkMulS16(SkPacked32ToR16(sc), alpha) + SkMulS16(SkGetPackedR16(dc), dst_scale);
+ unsigned dg = SkMulS16(SkPacked32ToG16(sc), alpha) + SkMulS16(SkGetPackedG16(dc), dst_scale);
+ unsigned db = SkMulS16(SkPacked32ToB16(sc), alpha) + SkMulS16(SkGetPackedB16(dc), dst_scale);
+ *dst = SkPackRGB16(SkDiv255Round(dr), SkDiv255Round(dg), SkDiv255Round(db));
+ }
+ dst += 1;
+ } while (--count != 0);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void S32_D565_Opaque_Dither(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 == alpha);
+
+ if (count > 0) {
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+
+ unsigned dither = DITHER_VALUE(x);
+ *dst++ = SkDitherRGB32To565(c, dither);
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+static void S32_D565_Blend_Dither(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 > alpha);
+
+ if (count > 0) {
+ int scale = SkAlpha255To256(alpha);
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+
+ int dither = DITHER_VALUE(x);
+ int sr = SkGetPackedR32(c);
+ int sg = SkGetPackedG32(c);
+ int sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32To565(sr, dither);
+ sg = SkDITHER_G32To565(sg, dither);
+ sb = SkDITHER_B32To565(sb, dither);
+
+ uint16_t d = *dst;
+ *dst++ = SkPackRGB16(SkAlphaBlend(sr, SkGetPackedR16(d), scale),
+ SkAlphaBlend(sg, SkGetPackedG16(d), scale),
+ SkAlphaBlend(sb, SkGetPackedB16(d), scale));
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+static void S32A_D565_Opaque_Dither(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 == alpha);
+
+ if (count > 0) {
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+
+ int d = SkAlphaMul(DITHER_VALUE(x), SkAlpha255To256(a));
+
+ unsigned sr = SkGetPackedR32(c);
+ unsigned sg = SkGetPackedG32(c);
+ unsigned sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32_FOR_565(sr, d);
+ sg = SkDITHER_G32_FOR_565(sg, d);
+ sb = SkDITHER_B32_FOR_565(sb, d);
+
+ uint32_t src_expanded = (sg << 24) | (sr << 13) | (sb << 2);
+ uint32_t dst_expanded = SkExpand_rgb_16(*dst);
+ dst_expanded = dst_expanded * (SkAlpha255To256(255 - a) >> 3);
+ // now src and dst expanded are in g:11 r:10 x:1 b:10
+ *dst = SkCompact_rgb_16((src_expanded + dst_expanded) >> 5);
+ }
+ dst += 1;
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+static void S32A_D565_Blend_Dither(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 > alpha);
+
+ if (count > 0) {
+ int src_scale = SkAlpha255To256(alpha);
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ if (c)
+ {
+ unsigned d = *dst;
+ int sa = SkGetPackedA32(c);
+ int dst_scale = SkAlpha255To256(255 - SkAlphaMul(sa, src_scale));
+ int dither = DITHER_VALUE(x);
+
+ int sr = SkGetPackedR32(c);
+ int sg = SkGetPackedG32(c);
+ int sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32To565(sr, dither);
+ sg = SkDITHER_G32To565(sg, dither);
+ sb = SkDITHER_B32To565(sb, dither);
+
+ int dr = (sr * src_scale + SkGetPackedR16(d) * dst_scale) >> 8;
+ int dg = (sg * src_scale + SkGetPackedG16(d) * dst_scale) >> 8;
+ int db = (sb * src_scale + SkGetPackedB16(d) * dst_scale) >> 8;
+
+ *dst = SkPackRGB16(dr, dg, db);
+ }
+ dst += 1;
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkBlitRow::Proc gDefault_565_Procs[] = {
+ // no dither
+ S32_D565_Opaque,
+ S32_D565_Blend,
+
+ S32A_D565_Opaque,
+ S32A_D565_Blend,
+
+ // dither
+ S32_D565_Opaque_Dither,
+ S32_D565_Blend_Dither,
+
+ S32A_D565_Opaque_Dither,
+ S32A_D565_Blend_Dither
+};
+
+SkBlitRow::Proc SkBlitRow::Factory(unsigned flags, SkBitmap::Config config) {
+ SkASSERT(flags < SK_ARRAY_COUNT(gDefault_565_Procs));
+ // just so we don't crash
+ flags &= kFlags16_Mask;
+
+ SkBlitRow::Proc proc = NULL;
+
+ switch (config) {
+ case SkBitmap::kRGB_565_Config:
+ proc = PlatformProcs565(flags);
+ if (NULL == proc) {
+ proc = gDefault_565_Procs[flags];
+ }
+ break;
+ default:
+ break;
+ }
+ return proc;
+}
diff --git a/core/SkBlitRow_D32.cpp b/core/SkBlitRow_D32.cpp
new file mode 100644
index 00000000..c858af63
--- /dev/null
+++ b/core/SkBlitRow_D32.cpp
@@ -0,0 +1,249 @@
+/*
+ * 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 "SkBlitRow.h"
+#include "SkBlitMask.h"
+#include "SkColorPriv.h"
+#include "SkUtils.h"
+
+#define UNROLL
+
+SkBlitRow::ColorRectProc PlatformColorRectProcFactory();
+
+static void S32_Opaque_BlitRow32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(255 == alpha);
+ memcpy(dst, src, count * sizeof(SkPMColor));
+}
+
+static void S32_Blend_BlitRow32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha <= 255);
+ if (count > 0) {
+ unsigned src_scale = SkAlpha255To256(alpha);
+ unsigned dst_scale = 256 - src_scale;
+
+#ifdef UNROLL
+ if (count & 1) {
+ *dst = SkAlphaMulQ(*(src++), src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ dst += 1;
+ count -= 1;
+ }
+
+ const SkPMColor* SK_RESTRICT srcEnd = src + count;
+ while (src != srcEnd) {
+ *dst = SkAlphaMulQ(*(src++), src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ dst += 1;
+ *dst = SkAlphaMulQ(*(src++), src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ dst += 1;
+ }
+#else
+ do {
+ *dst = SkAlphaMulQ(*src, src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+#endif
+ }
+}
+
+static void S32A_Opaque_BlitRow32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(255 == alpha);
+ if (count > 0) {
+#ifdef UNROLL
+ if (count & 1) {
+ *dst = SkPMSrcOver(*(src++), *dst);
+ dst += 1;
+ count -= 1;
+ }
+
+ const SkPMColor* SK_RESTRICT srcEnd = src + count;
+ while (src != srcEnd) {
+ *dst = SkPMSrcOver(*(src++), *dst);
+ dst += 1;
+ *dst = SkPMSrcOver(*(src++), *dst);
+ dst += 1;
+ }
+#else
+ do {
+ *dst = SkPMSrcOver(*src, *dst);
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+#endif
+ }
+}
+
+static void S32A_Blend_BlitRow32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha <= 255);
+ if (count > 0) {
+#ifdef UNROLL
+ if (count & 1) {
+ *dst = SkBlendARGB32(*(src++), *dst, alpha);
+ dst += 1;
+ count -= 1;
+ }
+
+ const SkPMColor* SK_RESTRICT srcEnd = src + count;
+ while (src != srcEnd) {
+ *dst = SkBlendARGB32(*(src++), *dst, alpha);
+ dst += 1;
+ *dst = SkBlendARGB32(*(src++), *dst, alpha);
+ dst += 1;
+ }
+#else
+ do {
+ *dst = SkBlendARGB32(*src, *dst, alpha);
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+#endif
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkBlitRow::Proc32 gDefault_Procs32[] = {
+ S32_Opaque_BlitRow32,
+ S32_Blend_BlitRow32,
+ S32A_Opaque_BlitRow32,
+ S32A_Blend_BlitRow32
+};
+
+SkBlitRow::Proc32 SkBlitRow::Factory32(unsigned flags) {
+ SkASSERT(flags < SK_ARRAY_COUNT(gDefault_Procs32));
+ // just so we don't crash
+ flags &= kFlags32_Mask;
+
+ SkBlitRow::Proc32 proc = PlatformProcs32(flags);
+ if (NULL == proc) {
+ proc = gDefault_Procs32[flags];
+ }
+ SkASSERT(proc);
+ return proc;
+}
+
+SkBlitRow::Proc32 SkBlitRow::ColorProcFactory() {
+ SkBlitRow::ColorProc proc = PlatformColorProc();
+ if (NULL == proc) {
+ proc = Color32;
+ }
+ SkASSERT(proc);
+ return proc;
+}
+
+void SkBlitRow::Color32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, SkPMColor color) {
+ if (count > 0) {
+ if (0 == color) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(SkPMColor));
+ }
+ return;
+ }
+ unsigned colorA = SkGetPackedA32(color);
+ if (255 == colorA) {
+ sk_memset32(dst, color, count);
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(colorA);
+ do {
+ *dst = color + SkAlphaMulQ(*src, scale);
+ src += 1;
+ dst += 1;
+ } while (--count);
+ }
+ }
+}
+
+template <size_t N> void assignLoop(SkPMColor* dst, SkPMColor color) {
+ for (size_t i = 0; i < N; ++i) {
+ *dst++ = color;
+ }
+}
+
+static inline void assignLoop(SkPMColor dst[], SkPMColor color, int count) {
+ while (count >= 4) {
+ *dst++ = color;
+ *dst++ = color;
+ *dst++ = color;
+ *dst++ = color;
+ count -= 4;
+ }
+ if (count >= 2) {
+ *dst++ = color;
+ *dst++ = color;
+ count -= 2;
+ }
+ if (count > 0) {
+ *dst++ = color;
+ }
+}
+
+void SkBlitRow::ColorRect32(SkPMColor* dst, int width, int height,
+ size_t rowBytes, SkPMColor color) {
+ if (width <= 0 || height <= 0 || 0 == color) {
+ return;
+ }
+
+ // Just made up this value, since I saw it once in a SSE2 file.
+ // We should consider writing some tests to find the optimimal break-point
+ // (or query the Platform proc?)
+ static const int MIN_WIDTH_FOR_SCANLINE_PROC = 32;
+
+ bool isOpaque = (0xFF == SkGetPackedA32(color));
+
+ if (!isOpaque || width >= MIN_WIDTH_FOR_SCANLINE_PROC) {
+ SkBlitRow::ColorProc proc = SkBlitRow::ColorProcFactory();
+ while (--height >= 0) {
+ (*proc)(dst, dst, width, color);
+ dst = (SkPMColor*) ((char*)dst + rowBytes);
+ }
+ } else {
+ switch (width) {
+ case 1:
+ while (--height >= 0) {
+ assignLoop<1>(dst, color);
+ dst = (SkPMColor*) ((char*)dst + rowBytes);
+ }
+ break;
+ case 2:
+ while (--height >= 0) {
+ assignLoop<2>(dst, color);
+ dst = (SkPMColor*) ((char*)dst + rowBytes);
+ }
+ break;
+ case 3:
+ while (--height >= 0) {
+ assignLoop<3>(dst, color);
+ dst = (SkPMColor*) ((char*)dst + rowBytes);
+ }
+ break;
+ default:
+ while (--height >= 0) {
+ assignLoop(dst, color, width);
+ dst = (SkPMColor*) ((char*)dst + rowBytes);
+ }
+ break;
+ }
+ }
+}
+
+SkBlitRow::ColorRectProc SkBlitRow::ColorRectProcFactory() {
+ SkBlitRow::ColorRectProc proc = PlatformColorRectProcFactory();
+ if (NULL == proc) {
+ proc = ColorRect32;
+ }
+ SkASSERT(proc);
+ return proc;
+}
diff --git a/core/SkBlitter.cpp b/core/SkBlitter.cpp
new file mode 100644
index 00000000..162fdbcd
--- /dev/null
+++ b/core/SkBlitter.cpp
@@ -0,0 +1,1016 @@
+
+/*
+ * 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 "SkBlitter.h"
+#include "SkAntiRun.h"
+#include "SkColor.h"
+#include "SkColorFilter.h"
+#include "SkFilterShader.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMask.h"
+#include "SkMaskFilter.h"
+#include "SkTemplatesPriv.h"
+#include "SkTLazy.h"
+#include "SkUtils.h"
+#include "SkXfermode.h"
+#include "SkString.h"
+
+SkBlitter::~SkBlitter() {}
+
+bool SkBlitter::isNullBlitter() const { return false; }
+
+const SkBitmap* SkBlitter::justAnOpaqueColor(uint32_t* value) {
+ return NULL;
+}
+
+void SkBlitter::blitH(int x, int y, int width) {
+ SkDEBUGFAIL("unimplemented");
+}
+
+void SkBlitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ SkDEBUGFAIL("unimplemented");
+}
+
+void SkBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ if (alpha == 255) {
+ this->blitRect(x, y, 1, height);
+ } else {
+ int16_t runs[2];
+ runs[0] = 1;
+ runs[1] = 0;
+
+ while (--height >= 0) {
+ this->blitAntiH(x, y++, &alpha, runs);
+ }
+ }
+}
+
+void SkBlitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0);
+ while (--height >= 0) {
+ this->blitH(x, y++, width);
+ }
+}
+
+/// Default implementation doesn't check for any easy optimizations
+/// such as alpha == 0 or 255; also uses blitV(), which some subclasses
+/// may not support.
+void SkBlitter::blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) {
+ this->blitV(x++, y, height, leftAlpha);
+ if (width > 0) {
+ this->blitRect(x, y, width, height);
+ x += width;
+ }
+ this->blitV(x, y, height, rightAlpha);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static inline void bits_to_runs(SkBlitter* blitter, int x, int y,
+ const uint8_t bits[],
+ U8CPU left_mask, int rowBytes,
+ U8CPU right_mask) {
+ int inFill = 0;
+ int pos = 0;
+
+ while (--rowBytes >= 0) {
+ unsigned b = *bits++ & left_mask;
+ if (rowBytes == 0) {
+ b &= right_mask;
+ }
+
+ for (unsigned test = 0x80; test != 0; test >>= 1) {
+ if (b & test) {
+ if (!inFill) {
+ pos = x;
+ inFill = true;
+ }
+ } else {
+ if (inFill) {
+ blitter->blitH(pos, y, x - pos);
+ inFill = false;
+ }
+ }
+ x += 1;
+ }
+ left_mask = 0xFF;
+ }
+
+ // final cleanup
+ if (inFill) {
+ blitter->blitH(pos, y, x - pos);
+ }
+}
+
+void SkBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ SkASSERT(mask.fBounds.contains(clip));
+
+ if (mask.fFormat == SkMask::kBW_Format) {
+ int cx = clip.fLeft;
+ int cy = clip.fTop;
+ int maskLeft = mask.fBounds.fLeft;
+ int mask_rowBytes = mask.fRowBytes;
+ int height = clip.height();
+
+ const uint8_t* bits = mask.getAddr1(cx, cy);
+
+ if (cx == maskLeft && clip.fRight == mask.fBounds.fRight) {
+ while (--height >= 0) {
+ bits_to_runs(this, cx, cy, bits, 0xFF, mask_rowBytes, 0xFF);
+ bits += mask_rowBytes;
+ cy += 1;
+ }
+ } else {
+ int left_edge = cx - maskLeft;
+ SkASSERT(left_edge >= 0);
+ int rite_edge = clip.fRight - maskLeft;
+ SkASSERT(rite_edge > left_edge);
+
+ int left_mask = 0xFF >> (left_edge & 7);
+ int rite_mask = 0xFF << (8 - (rite_edge & 7));
+ int full_runs = (rite_edge >> 3) - ((left_edge + 7) >> 3);
+
+ // check for empty right mask, so we don't read off the end (or go slower than we need to)
+ if (rite_mask == 0) {
+ SkASSERT(full_runs >= 0);
+ full_runs -= 1;
+ rite_mask = 0xFF;
+ }
+ if (left_mask == 0xFF) {
+ full_runs -= 1;
+ }
+
+ // back up manually so we can keep in sync with our byte-aligned src
+ // have cx reflect our actual starting x-coord
+ cx -= left_edge & 7;
+
+ if (full_runs < 0) {
+ SkASSERT((left_mask & rite_mask) != 0);
+ while (--height >= 0) {
+ bits_to_runs(this, cx, cy, bits, left_mask, 1, rite_mask);
+ bits += mask_rowBytes;
+ cy += 1;
+ }
+ } else {
+ while (--height >= 0) {
+ bits_to_runs(this, cx, cy, bits, left_mask, full_runs + 2, rite_mask);
+ bits += mask_rowBytes;
+ cy += 1;
+ }
+ }
+ }
+ } else {
+ int width = clip.width();
+ SkAutoSTMalloc<64, int16_t> runStorage(width + 1);
+ int16_t* runs = runStorage.get();
+ const uint8_t* aa = mask.getAddr8(clip.fLeft, clip.fTop);
+
+ sk_memset16((uint16_t*)runs, 1, width);
+ runs[width] = 0;
+
+ int height = clip.height();
+ int y = clip.fTop;
+ while (--height >= 0) {
+ this->blitAntiH(clip.fLeft, y, aa, runs);
+ aa += mask.fRowBytes;
+ y += 1;
+ }
+ }
+}
+
+/////////////////////// these guys are not virtual, just a helpers
+
+void SkBlitter::blitMaskRegion(const SkMask& mask, const SkRegion& clip) {
+ if (clip.quickReject(mask.fBounds)) {
+ return;
+ }
+
+ SkRegion::Cliperator clipper(clip, mask.fBounds);
+
+ while (!clipper.done()) {
+ const SkIRect& cr = clipper.rect();
+ this->blitMask(mask, cr);
+ clipper.next();
+ }
+}
+
+void SkBlitter::blitRectRegion(const SkIRect& rect, const SkRegion& clip) {
+ SkRegion::Cliperator clipper(clip, rect);
+
+ while (!clipper.done()) {
+ const SkIRect& cr = clipper.rect();
+ this->blitRect(cr.fLeft, cr.fTop, cr.width(), cr.height());
+ clipper.next();
+ }
+}
+
+void SkBlitter::blitRegion(const SkRegion& clip) {
+ SkRegion::Iterator iter(clip);
+
+ while (!iter.done()) {
+ const SkIRect& cr = iter.rect();
+ this->blitRect(cr.fLeft, cr.fTop, cr.width(), cr.height());
+ iter.next();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkNullBlitter::blitH(int x, int y, int width) {}
+
+void SkNullBlitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {}
+
+void SkNullBlitter::blitV(int x, int y, int height, SkAlpha alpha) {}
+
+void SkNullBlitter::blitRect(int x, int y, int width, int height) {}
+
+void SkNullBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {}
+
+const SkBitmap* SkNullBlitter::justAnOpaqueColor(uint32_t* value) {
+ return NULL;
+}
+
+bool SkNullBlitter::isNullBlitter() const { return true; }
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int compute_anti_width(const int16_t runs[]) {
+ int width = 0;
+
+ for (;;) {
+ int count = runs[0];
+
+ SkASSERT(count >= 0);
+ if (count == 0) {
+ break;
+ }
+ width += count;
+ runs += count;
+ }
+ return width;
+}
+
+static inline bool y_in_rect(int y, const SkIRect& rect) {
+ return (unsigned)(y - rect.fTop) < (unsigned)rect.height();
+}
+
+static inline bool x_in_rect(int x, const SkIRect& rect) {
+ return (unsigned)(x - rect.fLeft) < (unsigned)rect.width();
+}
+
+void SkRectClipBlitter::blitH(int left, int y, int width) {
+ SkASSERT(width > 0);
+
+ if (!y_in_rect(y, fClipRect)) {
+ return;
+ }
+
+ int right = left + width;
+
+ if (left < fClipRect.fLeft) {
+ left = fClipRect.fLeft;
+ }
+ if (right > fClipRect.fRight) {
+ right = fClipRect.fRight;
+ }
+
+ width = right - left;
+ if (width > 0) {
+ fBlitter->blitH(left, y, width);
+ }
+}
+
+void SkRectClipBlitter::blitAntiH(int left, int y, const SkAlpha aa[],
+ const int16_t runs[]) {
+ if (!y_in_rect(y, fClipRect) || left >= fClipRect.fRight) {
+ return;
+ }
+
+ int x0 = left;
+ int x1 = left + compute_anti_width(runs);
+
+ if (x1 <= fClipRect.fLeft) {
+ return;
+ }
+
+ SkASSERT(x0 < x1);
+ if (x0 < fClipRect.fLeft) {
+ int dx = fClipRect.fLeft - x0;
+ SkAlphaRuns::BreakAt((int16_t*)runs, (uint8_t*)aa, dx);
+ runs += dx;
+ aa += dx;
+ x0 = fClipRect.fLeft;
+ }
+
+ SkASSERT(x0 < x1 && runs[x1 - x0] == 0);
+ if (x1 > fClipRect.fRight) {
+ x1 = fClipRect.fRight;
+ SkAlphaRuns::BreakAt((int16_t*)runs, (uint8_t*)aa, x1 - x0);
+ ((int16_t*)runs)[x1 - x0] = 0;
+ }
+
+ SkASSERT(x0 < x1 && runs[x1 - x0] == 0);
+ SkASSERT(compute_anti_width(runs) == x1 - x0);
+
+ fBlitter->blitAntiH(x0, y, aa, runs);
+}
+
+void SkRectClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ SkASSERT(height > 0);
+
+ if (!x_in_rect(x, fClipRect)) {
+ return;
+ }
+
+ int y0 = y;
+ int y1 = y + height;
+
+ if (y0 < fClipRect.fTop) {
+ y0 = fClipRect.fTop;
+ }
+ if (y1 > fClipRect.fBottom) {
+ y1 = fClipRect.fBottom;
+ }
+
+ if (y0 < y1) {
+ fBlitter->blitV(x, y0, y1 - y0, alpha);
+ }
+}
+
+void SkRectClipBlitter::blitRect(int left, int y, int width, int height) {
+ SkIRect r;
+
+ r.set(left, y, left + width, y + height);
+ if (r.intersect(fClipRect)) {
+ fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
+ }
+}
+
+void SkRectClipBlitter::blitAntiRect(int left, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) {
+ SkIRect r;
+
+ // The *true* width of the rectangle blitted is width+2:
+ r.set(left, y, left + width + 2, y + height);
+ if (r.intersect(fClipRect)) {
+ if (r.fLeft != left) {
+ SkASSERT(r.fLeft > left);
+ leftAlpha = 255;
+ }
+ if (r.fRight != left + width + 2) {
+ SkASSERT(r.fRight < left + width + 2);
+ rightAlpha = 255;
+ }
+ if (255 == leftAlpha && 255 == rightAlpha) {
+ fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
+ } else if (1 == r.width()) {
+ if (r.fLeft == left) {
+ fBlitter->blitV(r.fLeft, r.fTop, r.height(), leftAlpha);
+ } else {
+ SkASSERT(r.fLeft == left + width + 1);
+ fBlitter->blitV(r.fLeft, r.fTop, r.height(), rightAlpha);
+ }
+ } else {
+ fBlitter->blitAntiRect(r.fLeft, r.fTop, r.width() - 2, r.height(),
+ leftAlpha, rightAlpha);
+ }
+ }
+}
+
+void SkRectClipBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ SkASSERT(mask.fBounds.contains(clip));
+
+ SkIRect r = clip;
+
+ if (r.intersect(fClipRect)) {
+ fBlitter->blitMask(mask, r);
+ }
+}
+
+const SkBitmap* SkRectClipBlitter::justAnOpaqueColor(uint32_t* value) {
+ return fBlitter->justAnOpaqueColor(value);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkRgnClipBlitter::blitH(int x, int y, int width) {
+ SkRegion::Spanerator span(*fRgn, y, x, x + width);
+ int left, right;
+
+ while (span.next(&left, &right)) {
+ SkASSERT(left < right);
+ fBlitter->blitH(left, y, right - left);
+ }
+}
+
+void SkRgnClipBlitter::blitAntiH(int x, int y, const SkAlpha aa[],
+ const int16_t runs[]) {
+ int width = compute_anti_width(runs);
+ SkRegion::Spanerator span(*fRgn, y, x, x + width);
+ int left, right;
+ SkDEBUGCODE(const SkIRect& bounds = fRgn->getBounds();)
+
+ int prevRite = x;
+ while (span.next(&left, &right)) {
+ SkASSERT(x <= left);
+ SkASSERT(left < right);
+ SkASSERT(left >= bounds.fLeft && right <= bounds.fRight);
+
+ SkAlphaRuns::Break((int16_t*)runs, (uint8_t*)aa, left - x, right - left);
+
+ // now zero before left
+ if (left > prevRite) {
+ int index = prevRite - x;
+ ((uint8_t*)aa)[index] = 0; // skip runs after right
+ ((int16_t*)runs)[index] = SkToS16(left - prevRite);
+ }
+
+ prevRite = right;
+ }
+
+ if (prevRite > x) {
+ ((int16_t*)runs)[prevRite - x] = 0;
+
+ if (x < 0) {
+ int skip = runs[0];
+ SkASSERT(skip >= -x);
+ aa += skip;
+ runs += skip;
+ x += skip;
+ }
+ fBlitter->blitAntiH(x, y, aa, runs);
+ }
+}
+
+void SkRgnClipBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ SkIRect bounds;
+ bounds.set(x, y, x + 1, y + height);
+
+ SkRegion::Cliperator iter(*fRgn, bounds);
+
+ while (!iter.done()) {
+ const SkIRect& r = iter.rect();
+ SkASSERT(bounds.contains(r));
+
+ fBlitter->blitV(x, r.fTop, r.height(), alpha);
+ iter.next();
+ }
+}
+
+void SkRgnClipBlitter::blitRect(int x, int y, int width, int height) {
+ SkIRect bounds;
+ bounds.set(x, y, x + width, y + height);
+
+ SkRegion::Cliperator iter(*fRgn, bounds);
+
+ while (!iter.done()) {
+ const SkIRect& r = iter.rect();
+ SkASSERT(bounds.contains(r));
+
+ fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
+ iter.next();
+ }
+}
+
+void SkRgnClipBlitter::blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) {
+ // The *true* width of the rectangle to blit is width + 2
+ SkIRect bounds;
+ bounds.set(x, y, x + width + 2, y + height);
+
+ SkRegion::Cliperator iter(*fRgn, bounds);
+
+ while (!iter.done()) {
+ const SkIRect& r = iter.rect();
+ SkASSERT(bounds.contains(r));
+ SkASSERT(r.fLeft >= x);
+ SkASSERT(r.fRight <= x + width + 2);
+
+ SkAlpha effectiveLeftAlpha = (r.fLeft == x) ? leftAlpha : 255;
+ SkAlpha effectiveRightAlpha = (r.fRight == x + width + 2) ?
+ rightAlpha : 255;
+
+ if (255 == effectiveLeftAlpha && 255 == effectiveRightAlpha) {
+ fBlitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
+ } else if (1 == r.width()) {
+ if (r.fLeft == x) {
+ fBlitter->blitV(r.fLeft, r.fTop, r.height(),
+ effectiveLeftAlpha);
+ } else {
+ SkASSERT(r.fLeft == x + width + 1);
+ fBlitter->blitV(r.fLeft, r.fTop, r.height(),
+ effectiveRightAlpha);
+ }
+ } else {
+ fBlitter->blitAntiRect(r.fLeft, r.fTop, r.width() - 2, r.height(),
+ effectiveLeftAlpha, effectiveRightAlpha);
+ }
+ iter.next();
+ }
+}
+
+
+void SkRgnClipBlitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ SkASSERT(mask.fBounds.contains(clip));
+
+ SkRegion::Cliperator iter(*fRgn, clip);
+ const SkIRect& r = iter.rect();
+ SkBlitter* blitter = fBlitter;
+
+ while (!iter.done()) {
+ blitter->blitMask(mask, r);
+ iter.next();
+ }
+}
+
+const SkBitmap* SkRgnClipBlitter::justAnOpaqueColor(uint32_t* value) {
+ return fBlitter->justAnOpaqueColor(value);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlitter* SkBlitterClipper::apply(SkBlitter* blitter, const SkRegion* clip,
+ const SkIRect* ir) {
+ if (clip) {
+ const SkIRect& clipR = clip->getBounds();
+
+ if (clip->isEmpty() || (ir && !SkIRect::Intersects(clipR, *ir))) {
+ blitter = &fNullBlitter;
+ } else if (clip->isRect()) {
+ if (ir == NULL || !clipR.contains(*ir)) {
+ fRectBlitter.init(blitter, clipR);
+ blitter = &fRectBlitter;
+ }
+ } else {
+ fRgnBlitter.init(blitter, clip);
+ blitter = &fRgnBlitter;
+ }
+ }
+ return blitter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorShader.h"
+#include "SkColorPriv.h"
+
+class Sk3DShader : public SkShader {
+public:
+ Sk3DShader(SkShader* proxy) : fProxy(proxy) {
+ SkSafeRef(proxy);
+ fMask = NULL;
+ }
+
+ virtual ~Sk3DShader() {
+ SkSafeUnref(fProxy);
+ }
+
+ void setMask(const SkMask* mask) { fMask = mask; }
+
+ virtual bool setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) SK_OVERRIDE {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+ if (fProxy) {
+ 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 true;
+ }
+
+ virtual void endContext() SK_OVERRIDE {
+ if (fProxy) {
+ fProxy->endContext();
+ }
+ this->INHERITED::endContext();
+ }
+
+ virtual void shadeSpan(int x, int y, SkPMColor span[], int count) SK_OVERRIDE {
+ if (fProxy) {
+ fProxy->shadeSpan(x, y, span, count);
+ }
+
+ if (fMask == NULL) {
+ if (fProxy == NULL) {
+ sk_memset32(span, fPMColor, count);
+ }
+ return;
+ }
+
+ SkASSERT(fMask->fBounds.contains(x, y));
+ SkASSERT(fMask->fBounds.contains(x + count - 1, y));
+
+ size_t size = fMask->computeImageSize();
+ const uint8_t* alpha = fMask->getAddr8(x, y);
+ const uint8_t* mulp = alpha + size;
+ const uint8_t* addp = mulp + size;
+
+ if (fProxy) {
+ for (int i = 0; i < count; i++) {
+ if (alpha[i]) {
+ SkPMColor c = span[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+
+ unsigned mul = SkAlpha255To256(mulp[i]);
+ unsigned add = addp[i];
+
+ r = SkFastMin32(SkAlphaMul(r, mul) + add, a);
+ g = SkFastMin32(SkAlphaMul(g, mul) + add, a);
+ b = SkFastMin32(SkAlphaMul(b, mul) + add, a);
+
+ span[i] = SkPackARGB32(a, r, g, b);
+ }
+ } else {
+ span[i] = 0;
+ }
+ }
+ } else { // color
+ unsigned a = SkGetPackedA32(fPMColor);
+ unsigned r = SkGetPackedR32(fPMColor);
+ unsigned g = SkGetPackedG32(fPMColor);
+ unsigned b = SkGetPackedB32(fPMColor);
+ for (int i = 0; i < count; i++) {
+ if (alpha[i]) {
+ unsigned mul = SkAlpha255To256(mulp[i]);
+ unsigned add = addp[i];
+
+ span[i] = SkPackARGB32( a,
+ SkFastMin32(SkAlphaMul(r, mul) + add, a),
+ SkFastMin32(SkAlphaMul(g, mul) + add, a),
+ SkFastMin32(SkAlphaMul(b, mul) + add, a));
+ } else {
+ span[i] = 0;
+ }
+ }
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("Sk3DShader: (");
+
+ if (NULL != fProxy) {
+ str->append("Proxy: ");
+ fProxy->toString(str);
+ }
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sk3DShader)
+
+protected:
+ Sk3DShader(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fProxy = buffer.readFlattenableT<SkShader>();
+ fPMColor = buffer.readColor();
+ fMask = NULL;
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fProxy);
+ buffer.writeColor(fPMColor);
+ }
+
+private:
+ SkShader* fProxy;
+ SkPMColor fPMColor;
+ const SkMask* fMask;
+
+ typedef SkShader INHERITED;
+};
+
+class Sk3DBlitter : public SkBlitter {
+public:
+ Sk3DBlitter(SkBlitter* proxy, Sk3DShader* shader, void (*killProc)(void*))
+ : fProxy(proxy), f3DShader(shader), fKillProc(killProc) {
+ shader->ref();
+ }
+
+ virtual ~Sk3DBlitter() {
+ f3DShader->unref();
+ fKillProc(fProxy);
+ }
+
+ virtual void blitH(int x, int y, int width) {
+ fProxy->blitH(x, y, width);
+ }
+
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ fProxy->blitAntiH(x, y, antialias, runs);
+ }
+
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) {
+ fProxy->blitV(x, y, height, alpha);
+ }
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ fProxy->blitRect(x, y, width, height);
+ }
+
+ virtual void blitMask(const SkMask& mask, const SkIRect& clip) {
+ if (mask.fFormat == SkMask::k3D_Format) {
+ f3DShader->setMask(&mask);
+
+ ((SkMask*)&mask)->fFormat = SkMask::kA8_Format;
+ fProxy->blitMask(mask, clip);
+ ((SkMask*)&mask)->fFormat = SkMask::k3D_Format;
+
+ f3DShader->setMask(NULL);
+ } else {
+ fProxy->blitMask(mask, clip);
+ }
+ }
+
+private:
+ SkBlitter* fProxy;
+ Sk3DShader* f3DShader;
+ void (*fKillProc)(void*);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkCoreBlitters.h"
+
+class SkAutoCallProc {
+public:
+ typedef void (*Proc)(void*);
+
+ SkAutoCallProc(void* obj, Proc proc)
+ : fObj(obj), fProc(proc) {}
+
+ ~SkAutoCallProc() {
+ if (fObj && fProc) {
+ fProc(fObj);
+ }
+ }
+
+ void* get() const { return fObj; }
+
+ void* detach() {
+ void* obj = fObj;
+ fObj = NULL;
+ return obj;
+ }
+
+private:
+ void* fObj;
+ Proc fProc;
+};
+
+static void destroy_blitter(void* blitter) {
+ ((SkBlitter*)blitter)->~SkBlitter();
+}
+
+static void delete_blitter(void* blitter) {
+ SkDELETE((SkBlitter*)blitter);
+}
+
+static bool just_solid_color(const SkPaint& paint) {
+ if (paint.getAlpha() == 0xFF && paint.getColorFilter() == NULL) {
+ SkShader* shader = paint.getShader();
+ if (NULL == shader ||
+ (shader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/** By analyzing the paint (with an xfermode), we may decide we can take
+ special action. This enum lists our possible actions
+ */
+enum XferInterp {
+ kNormal_XferInterp, // no special interpretation, draw normally
+ kSrcOver_XferInterp, // draw as if in srcover mode
+ kSkipDrawing_XferInterp // draw nothing
+};
+
+static XferInterp interpret_xfermode(const SkPaint& paint, SkXfermode* xfer,
+ SkBitmap::Config deviceConfig) {
+ SkXfermode::Mode mode;
+
+ if (SkXfermode::AsMode(xfer, &mode)) {
+ switch (mode) {
+ case SkXfermode::kSrc_Mode:
+ if (just_solid_color(paint)) {
+ return kSrcOver_XferInterp;
+ }
+ break;
+ case SkXfermode::kDst_Mode:
+ return kSkipDrawing_XferInterp;
+ case SkXfermode::kSrcOver_Mode:
+ return kSrcOver_XferInterp;
+ case SkXfermode::kDstOver_Mode:
+ if (SkBitmap::kRGB_565_Config == deviceConfig) {
+ return kSkipDrawing_XferInterp;
+ }
+ break;
+ case SkXfermode::kSrcIn_Mode:
+ if (SkBitmap::kRGB_565_Config == deviceConfig &&
+ just_solid_color(paint)) {
+ return kSrcOver_XferInterp;
+ }
+ break;
+ case SkXfermode::kDstIn_Mode:
+ if (just_solid_color(paint)) {
+ return kSkipDrawing_XferInterp;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return kNormal_XferInterp;
+}
+
+SkBlitter* SkBlitter::Choose(const SkBitmap& device,
+ const SkMatrix& matrix,
+ const SkPaint& origPaint,
+ void* storage, size_t storageSize) {
+ SkASSERT(storageSize == 0 || storage != NULL);
+
+ SkBlitter* blitter = NULL;
+
+ // which check, in case we're being called by a client with a dummy device
+ // (e.g. they have a bounder that always aborts the draw)
+ if (SkBitmap::kNo_Config == device.getConfig()) {
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+ return blitter;
+ }
+
+ SkShader* shader = origPaint.getShader();
+ SkColorFilter* cf = origPaint.getColorFilter();
+ SkXfermode* mode = origPaint.getXfermode();
+ Sk3DShader* shader3D = NULL;
+
+ SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+
+ if (origPaint.getMaskFilter() != NULL &&
+ origPaint.getMaskFilter()->getFormat() == SkMask::k3D_Format) {
+ shader3D = SkNEW_ARGS(Sk3DShader, (shader));
+ // we know we haven't initialized lazyPaint yet, so just do it
+ paint.writable()->setShader(shader3D)->unref();
+ shader = shader3D;
+ }
+
+ if (NULL != mode) {
+ switch (interpret_xfermode(*paint, mode, device.config())) {
+ case kSrcOver_XferInterp:
+ mode = NULL;
+ paint.writable()->setXfermode(NULL);
+ break;
+ case kSkipDrawing_XferInterp:
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+ return blitter;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * 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) {
+ if (mode) {
+ // xfermodes (and filters) require shaders for our current blitters
+ shader = SkNEW(SkColorShader);
+ paint.writable()->setShader(shader)->unref();
+ } else if (cf) {
+ // if no shader && no xfermode, we just apply the colorfilter to
+ // our color and move on.
+ SkPaint* writablePaint = paint.writable();
+ writablePaint->setColor(cf->filterColor(paint->getColor()));
+ writablePaint->setColorFilter(NULL);
+ cf = NULL;
+ }
+ }
+
+ if (cf) {
+ SkASSERT(shader);
+ shader = SkNEW_ARGS(SkFilterShader, (shader, cf));
+ paint.writable()->setShader(shader)->unref();
+ // blitters should ignore the presence/absence of a filter, since
+ // 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)) {
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+ return blitter;
+ }
+
+ switch (device.getConfig()) {
+ case SkBitmap::kA1_Config:
+ SK_PLACEMENT_NEW_ARGS(blitter, SkA1_Blitter,
+ storage, storageSize, (device, *paint));
+ break;
+
+ case SkBitmap::kA8_Config:
+ if (shader) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkA8_Shader_Blitter,
+ storage, storageSize, (device, *paint));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkA8_Blitter,
+ storage, storageSize, (device, *paint));
+ }
+ break;
+
+ case SkBitmap::kRGB_565_Config:
+ blitter = SkBlitter_ChooseD565(device, *paint, storage, storageSize);
+ break;
+
+ case SkBitmap::kARGB_8888_Config:
+ if (shader) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkARGB32_Shader_Blitter,
+ storage, storageSize, (device, *paint));
+ } else if (paint->getColor() == SK_ColorBLACK) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkARGB32_Black_Blitter,
+ storage, storageSize, (device, *paint));
+ } else if (paint->getAlpha() == 0xFF) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkARGB32_Opaque_Blitter,
+ storage, storageSize, (device, *paint));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkARGB32_Blitter,
+ storage, storageSize, (device, *paint));
+ }
+ break;
+
+ default:
+ SkDEBUGFAIL("unsupported device config");
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+ break;
+ }
+
+ if (shader3D) {
+ void (*proc)(void*) = ((void*)storage == (void*)blitter) ? destroy_blitter : delete_blitter;
+ SkAutoCallProc tmp(blitter, proc);
+
+ blitter = SkNEW_ARGS(Sk3DBlitter, (blitter, shader3D, proc));
+ (void)tmp.detach();
+ }
+ return blitter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const uint16_t gMask_0F0F = 0xF0F;
+const uint32_t gMask_00FF00FF = 0xFF00FF;
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkShaderBlitter::SkShaderBlitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device) {
+ fShader = paint.getShader();
+ SkASSERT(fShader);
+ SkASSERT(fShader->setContextHasBeenCalled());
+
+ fShader->ref();
+ fShaderFlags = fShader->getFlags();
+}
+
+SkShaderBlitter::~SkShaderBlitter() {
+ SkASSERT(fShader->setContextHasBeenCalled());
+ fShader->endContext();
+ fShader->unref();
+}
diff --git a/core/SkBlitter.h b/core/SkBlitter.h
new file mode 100644
index 00000000..0a075f89
--- /dev/null
+++ b/core/SkBlitter.h
@@ -0,0 +1,174 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBlitter_DEFINED
+#define SkBlitter_DEFINED
+
+#include "SkBitmap.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+#include "SkRegion.h"
+#include "SkMask.h"
+
+/** SkBlitter and its subclasses are responsible for actually writing pixels
+ into memory. Besides efficiency, they handle clipping and antialiasing.
+*/
+class SkBlitter {
+public:
+ virtual ~SkBlitter();
+
+ /// Blit a horizontal run of one or more pixels.
+ virtual void blitH(int x, int y, int width);
+ /// Blit a horizontal run of antialiased pixels; runs[] is a *sparse*
+ /// zero-terminated run-length encoding of spans of constant alpha values.
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]);
+ /// Blit a vertical run of pixels with a constant alpha value.
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ /// Blit a solid rectangle one or more pixels wide.
+ virtual void blitRect(int x, int y, int width, int height);
+ /** Blit a rectangle with one alpha-blended column on the left,
+ width (zero or more) opaque pixels, and one alpha-blended column
+ on the right.
+ The result will always be at least two pixels wide.
+ */
+ virtual void blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha);
+ /// Blit a pattern of pixels defined by a rectangle-clipped mask;
+ /// typically used for text.
+ virtual void blitMask(const SkMask&, const SkIRect& clip);
+
+ /** If the blitter just sets a single value for each pixel, return the
+ bitmap it draws into, and assign value. If not, return NULL and ignore
+ the value parameter.
+ */
+ 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);
+ void blitRegion(const SkRegion& clip);
+ ///@}
+
+ /** @name Factories
+ Return the correct blitter to use given the specified context.
+ */
+ static SkBlitter* Choose(const SkBitmap& device,
+ const SkMatrix& matrix,
+ const SkPaint& paint) {
+ return Choose(device, matrix, paint, NULL, 0);
+ }
+
+ static SkBlitter* Choose(const SkBitmap& device,
+ const SkMatrix& matrix,
+ const SkPaint& paint,
+ void* storage, size_t storageSize);
+
+ static SkBlitter* ChooseSprite(const SkBitmap& device,
+ const SkPaint&,
+ const SkBitmap& src,
+ int left, int top,
+ void* storage, size_t storageSize);
+ ///@}
+
+private:
+};
+
+/** This blitter silently never draws anything.
+*/
+class SkNullBlitter : public SkBlitter {
+public:
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitAntiH(int x, int y, const SkAlpha[],
+ const int16_t runs[]) 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 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
+ called with coordinates that have been clipped by the specified clipRect.
+ This means the caller need not perform the clipping ahead of time.
+*/
+class SkRectClipBlitter : public SkBlitter {
+public:
+ void init(SkBlitter* blitter, const SkIRect& clipRect) {
+ SkASSERT(!clipRect.isEmpty());
+ fBlitter = blitter;
+ fClipRect = clipRect;
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitAntiH(int x, int y, const SkAlpha[],
+ const int16_t runs[]) 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 blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) SK_OVERRIDE;
+ virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE;
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) SK_OVERRIDE;
+
+private:
+ SkBlitter* fBlitter;
+ SkIRect fClipRect;
+};
+
+/** Wraps another (real) blitter, and ensures that the real blitter is only
+ called with coordinates that have been clipped by the specified clipRgn.
+ This means the caller need not perform the clipping ahead of time.
+*/
+class SkRgnClipBlitter : public SkBlitter {
+public:
+ void init(SkBlitter* blitter, const SkRegion* clipRgn) {
+ SkASSERT(clipRgn && !clipRgn->isEmpty());
+ fBlitter = blitter;
+ fRgn = clipRgn;
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ virtual void blitAntiH(int x, int y, const SkAlpha[],
+ const int16_t runs[]) 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 blitAntiRect(int x, int y, int width, int height,
+ SkAlpha leftAlpha, SkAlpha rightAlpha) SK_OVERRIDE;
+ virtual void blitMask(const SkMask&, const SkIRect& clip) SK_OVERRIDE;
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) SK_OVERRIDE;
+
+private:
+ SkBlitter* fBlitter;
+ const SkRegion* fRgn;
+};
+
+/** Factory to set up the appropriate most-efficient wrapper blitter
+ to apply a clip. Returns a pointer to a member, so lifetime must
+ be managed carefully.
+*/
+class SkBlitterClipper {
+public:
+ SkBlitter* apply(SkBlitter* blitter, const SkRegion* clip,
+ const SkIRect* bounds = NULL);
+
+private:
+ SkNullBlitter fNullBlitter;
+ SkRectClipBlitter fRectBlitter;
+ SkRgnClipBlitter fRgnBlitter;
+};
+
+#endif
diff --git a/core/SkBlitter_A1.cpp b/core/SkBlitter_A1.cpp
new file mode 100644
index 00000000..b64afe2a
--- /dev/null
+++ b/core/SkBlitter_A1.cpp
@@ -0,0 +1,50 @@
+
+/*
+ * 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 "SkCoreBlitters.h"
+
+SkA1_Blitter::SkA1_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device) {
+ fSrcA = paint.getAlpha();
+}
+
+void SkA1_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x >= 0 && y >= 0 &&
+ (unsigned)(x + width) <= (unsigned)fDevice.width());
+
+ if (fSrcA <= 0x7F) {
+ return;
+ }
+ uint8_t* dst = fDevice.getAddr1(x, y);
+ int right = x + width;
+
+ int left_mask = 0xFF >> (x & 7);
+ int rite_mask = 0xFF << (8 - (right & 7));
+ int full_runs = (right >> 3) - ((x + 7) >> 3);
+
+ // check for empty right mask, so we don't read off the end
+ // (or go slower than we need to)
+ if (rite_mask == 0) {
+ SkASSERT(full_runs >= 0);
+ full_runs -= 1;
+ rite_mask = 0xFF;
+ }
+ if (left_mask == 0xFF) {
+ full_runs -= 1;
+ }
+ if (full_runs < 0) {
+ SkASSERT((left_mask & rite_mask) != 0);
+ *dst |= (left_mask & rite_mask);
+ } else {
+ *dst++ |= left_mask;
+ memset(dst, 0xFF, full_runs);
+ dst += full_runs;
+ *dst |= rite_mask;
+ }
+}
diff --git a/core/SkBlitter_A8.cpp b/core/SkBlitter_A8.cpp
new file mode 100644
index 00000000..02554643
--- /dev/null
+++ b/core/SkBlitter_A8.cpp
@@ -0,0 +1,345 @@
+
+/*
+ * 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 "SkCoreBlitters.h"
+#include "SkColorPriv.h"
+#include "SkShader.h"
+#include "SkXfermode.h"
+
+SkA8_Blitter::SkA8_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device) {
+ fSrcA = paint.getAlpha();
+}
+
+const SkBitmap* SkA8_Blitter::justAnOpaqueColor(uint32_t* value) {
+ if (255 == fSrcA) {
+ *value = 255;
+ return &fDevice;
+ }
+ return NULL;
+}
+
+void SkA8_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x >= 0 && y >= 0 &&
+ (unsigned)(x + width) <= (unsigned)fDevice.width());
+
+ if (fSrcA == 0) {
+ return;
+ }
+
+ uint8_t* device = fDevice.getAddr8(x, y);
+
+ if (fSrcA == 255) {
+ memset(device, 0xFF, width);
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(fSrcA);
+ unsigned srcA = fSrcA;
+
+ for (int i = 0; i < width; i++) {
+ device[i] = SkToU8(srcA + SkAlphaMul(device[i], scale));
+ }
+ }
+}
+
+void SkA8_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ if (fSrcA == 0) {
+ return;
+ }
+
+ uint8_t* device = fDevice.getAddr8(x, y);
+ unsigned srcA = fSrcA;
+
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count == 0) {
+ return;
+ }
+ unsigned aa = antialias[0];
+
+ if (aa == 255 && srcA == 255) {
+ memset(device, 0xFF, count);
+ } else {
+ unsigned sa = SkAlphaMul(srcA, SkAlpha255To256(aa));
+ unsigned scale = 256 - sa;
+
+ for (int i = 0; i < count; i++) {
+ device[i] = SkToU8(sa + SkAlphaMul(device[i], scale));
+ }
+ }
+ runs += count;
+ antialias += count;
+ device += count;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+#define solid_8_pixels(mask, dst) \
+ do { \
+ if (mask & 0x80) dst[0] = 0xFF; \
+ if (mask & 0x40) dst[1] = 0xFF; \
+ if (mask & 0x20) dst[2] = 0xFF; \
+ if (mask & 0x10) dst[3] = 0xFF; \
+ if (mask & 0x08) dst[4] = 0xFF; \
+ if (mask & 0x04) dst[5] = 0xFF; \
+ if (mask & 0x02) dst[6] = 0xFF; \
+ if (mask & 0x01) dst[7] = 0xFF; \
+ } while (0)
+
+#define SK_BLITBWMASK_NAME SkA8_BlitBW
+#define SK_BLITBWMASK_ARGS
+#define SK_BLITBWMASK_BLIT8(mask, dst) solid_8_pixels(mask, dst)
+#define SK_BLITBWMASK_GETADDR getAddr8
+#define SK_BLITBWMASK_DEVTYPE uint8_t
+#include "SkBlitBWMaskTemplate.h"
+
+static inline void blend_8_pixels(U8CPU bw, uint8_t dst[], U8CPU sa,
+ unsigned dst_scale) {
+ if (bw & 0x80) dst[0] = SkToU8(sa + SkAlphaMul(dst[0], dst_scale));
+ if (bw & 0x40) dst[1] = SkToU8(sa + SkAlphaMul(dst[1], dst_scale));
+ if (bw & 0x20) dst[2] = SkToU8(sa + SkAlphaMul(dst[2], dst_scale));
+ if (bw & 0x10) dst[3] = SkToU8(sa + SkAlphaMul(dst[3], dst_scale));
+ if (bw & 0x08) dst[4] = SkToU8(sa + SkAlphaMul(dst[4], dst_scale));
+ if (bw & 0x04) dst[5] = SkToU8(sa + SkAlphaMul(dst[5], dst_scale));
+ if (bw & 0x02) dst[6] = SkToU8(sa + SkAlphaMul(dst[6], dst_scale));
+ if (bw & 0x01) dst[7] = SkToU8(sa + SkAlphaMul(dst[7], dst_scale));
+}
+
+#define SK_BLITBWMASK_NAME SkA8_BlendBW
+#define SK_BLITBWMASK_ARGS , U8CPU sa, unsigned dst_scale
+#define SK_BLITBWMASK_BLIT8(mask, dst) blend_8_pixels(mask, dst, sa, dst_scale)
+#define SK_BLITBWMASK_GETADDR getAddr8
+#define SK_BLITBWMASK_DEVTYPE uint8_t
+#include "SkBlitBWMaskTemplate.h"
+
+void SkA8_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ if (fSrcA == 0) {
+ return;
+ }
+
+ if (mask.fFormat == SkMask::kBW_Format) {
+ if (fSrcA == 0xFF) {
+ SkA8_BlitBW(fDevice, mask, clip);
+ } else {
+ SkA8_BlendBW(fDevice, mask, clip, fSrcA,
+ SkAlpha255To256(255 - fSrcA));
+ }
+ return;
+ }
+
+ int x = clip.fLeft;
+ int y = clip.fTop;
+ int width = clip.width();
+ int height = clip.height();
+ uint8_t* device = fDevice.getAddr8(x, y);
+ const uint8_t* alpha = mask.getAddr8(x, y);
+ unsigned srcA = fSrcA;
+
+ while (--height >= 0) {
+ for (int i = width - 1; i >= 0; --i) {
+ unsigned sa;
+ // scale our src by the alpha value
+ {
+ int aa = alpha[i];
+ if (aa == 0) {
+ continue;
+ }
+ if (aa == 255) {
+ if (srcA == 255) {
+ device[i] = 0xFF;
+ continue;
+ }
+ sa = srcA;
+ } else {
+ sa = SkAlphaMul(srcA, SkAlpha255To256(aa));
+ }
+ }
+
+ int scale = 256 - SkAlpha255To256(sa);
+ device[i] = SkToU8(sa + SkAlphaMul(device[i], scale));
+ }
+ device += fDevice.rowBytes();
+ alpha += mask.fRowBytes;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkA8_Blitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ if (fSrcA == 0) {
+ return;
+ }
+
+ unsigned sa = SkAlphaMul(fSrcA, SkAlpha255To256(alpha));
+ uint8_t* device = fDevice.getAddr8(x, y);
+ size_t rowBytes = fDevice.rowBytes();
+
+ if (sa == 0xFF) {
+ for (int i = 0; i < height; i++) {
+ *device = SkToU8(sa);
+ device += rowBytes;
+ }
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(sa);
+
+ for (int i = 0; i < height; i++) {
+ *device = SkToU8(sa + SkAlphaMul(*device, scale));
+ device += rowBytes;
+ }
+ }
+}
+
+void SkA8_Blitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(x >= 0 && y >= 0 &&
+ (unsigned)(x + width) <= (unsigned)fDevice.width() &&
+ (unsigned)(y + height) <= (unsigned)fDevice.height());
+
+ if (fSrcA == 0) {
+ return;
+ }
+
+ uint8_t* device = fDevice.getAddr8(x, y);
+ unsigned srcA = fSrcA;
+
+ if (srcA == 255) {
+ while (--height >= 0) {
+ memset(device, 0xFF, width);
+ device += fDevice.rowBytes();
+ }
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(srcA);
+
+ while (--height >= 0) {
+ for (int i = 0; i < width; i++) {
+ device[i] = SkToU8(srcA + SkAlphaMul(device[i], scale));
+ }
+ device += fDevice.rowBytes();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////
+
+SkA8_Shader_Blitter::SkA8_Shader_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device, paint) {
+ if ((fXfermode = paint.getXfermode()) != NULL) {
+ fXfermode->ref();
+ SkASSERT(fShader);
+ }
+
+ int width = device.width();
+ fBuffer = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * (width + (SkAlign4(width) >> 2)));
+ fAAExpand = (uint8_t*)(fBuffer + width);
+}
+
+SkA8_Shader_Blitter::~SkA8_Shader_Blitter() {
+ if (fXfermode) SkSafeUnref(fXfermode);
+ sk_free(fBuffer);
+}
+
+void SkA8_Shader_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x >= 0 && y >= 0 &&
+ (unsigned)(x + width) <= (unsigned)fDevice.width());
+
+ uint8_t* device = fDevice.getAddr8(x, y);
+
+ if ((fShader->getFlags() & SkShader::kOpaqueAlpha_Flag) && !fXfermode) {
+ memset(device, 0xFF, width);
+ } else {
+ SkPMColor* span = fBuffer;
+
+ fShader->shadeSpan(x, y, span, width);
+ if (fXfermode) {
+ fXfermode->xferA8(device, span, width, NULL);
+ } else {
+ for (int i = width - 1; i >= 0; --i) {
+ unsigned srcA = SkGetPackedA32(span[i]);
+ unsigned scale = 256 - SkAlpha255To256(srcA);
+
+ device[i] = SkToU8(srcA + SkAlphaMul(device[i], scale));
+ }
+ }
+ }
+}
+
+static inline uint8_t aa_blend8(SkPMColor src, U8CPU da, int aa) {
+ SkASSERT((unsigned)aa <= 255);
+
+ int src_scale = SkAlpha255To256(aa);
+ int sa = SkGetPackedA32(src);
+ int dst_scale = 256 - SkAlphaMul(sa, src_scale);
+
+ return SkToU8((sa * src_scale + da * dst_scale) >> 8);
+}
+
+void SkA8_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ SkShader* shader = fShader;
+ SkXfermode* mode = fXfermode;
+ uint8_t* aaExpand = fAAExpand;
+ SkPMColor* span = fBuffer;
+ uint8_t* device = fDevice.getAddr8(x, y);
+ int opaque = fShader->getFlags() & SkShader::kOpaqueAlpha_Flag;
+
+ for (;;) {
+ int count = *runs;
+ if (count == 0) {
+ break;
+ }
+ int aa = *antialias;
+ if (aa) {
+ if (opaque && aa == 255 && mode == NULL) {
+ memset(device, 0xFF, count);
+ } else {
+ shader->shadeSpan(x, y, span, count);
+ if (mode) {
+ memset(aaExpand, aa, count);
+ mode->xferA8(device, span, count, aaExpand);
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ device[i] = aa_blend8(span[i], device[i], aa);
+ }
+ }
+ }
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+}
+
+void SkA8_Shader_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ if (mask.fFormat == SkMask::kBW_Format) {
+ this->INHERITED::blitMask(mask, clip);
+ return;
+ }
+
+ int x = clip.fLeft;
+ int y = clip.fTop;
+ int width = clip.width();
+ int height = clip.height();
+ uint8_t* device = fDevice.getAddr8(x, y);
+ const uint8_t* alpha = mask.getAddr8(x, y);
+
+ SkPMColor* span = fBuffer;
+
+ while (--height >= 0) {
+ fShader->shadeSpan(x, y, span, width);
+ if (fXfermode) {
+ fXfermode->xferA8(device, span, width, alpha);
+ }
+
+ y += 1;
+ device += fDevice.rowBytes();
+ alpha += mask.fRowBytes;
+ }
+}
diff --git a/core/SkBlitter_ARGB32.cpp b/core/SkBlitter_ARGB32.cpp
new file mode 100644
index 00000000..d4bec1bc
--- /dev/null
+++ b/core/SkBlitter_ARGB32.cpp
@@ -0,0 +1,638 @@
+/*
+ * 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 "SkCoreBlitters.h"
+#include "SkColorPriv.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkXfermode.h"
+#include "SkBlitMask.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void SkARGB32_Blit32(const SkBitmap& device, const SkMask& mask,
+ const SkIRect& clip, SkPMColor srcColor) {
+ U8CPU alpha = SkGetPackedA32(srcColor);
+ unsigned flags = SkBlitRow::kSrcPixelAlpha_Flag32;
+ if (alpha != 255) {
+ flags |= SkBlitRow::kGlobalAlpha_Flag32;
+ }
+ SkBlitRow::Proc32 proc = SkBlitRow::Factory32(flags);
+
+ int x = clip.fLeft;
+ int y = clip.fTop;
+ int width = clip.width();
+ int height = clip.height();
+
+ SkPMColor* dstRow = device.getAddr32(x, y);
+ const SkPMColor* srcRow = reinterpret_cast<const SkPMColor*>(mask.getAddr8(x, y));
+
+ do {
+ proc(dstRow, srcRow, width, alpha);
+ dstRow = (SkPMColor*)((char*)dstRow + device.rowBytes());
+ srcRow = (const SkPMColor*)((const char*)srcRow + mask.fRowBytes);
+ } while (--height != 0);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+SkARGB32_Blitter::SkARGB32_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device) {
+ SkColor color = paint.getColor();
+ fColor = color;
+
+ fSrcA = SkColorGetA(color);
+ unsigned scale = SkAlpha255To256(fSrcA);
+ fSrcR = SkAlphaMul(SkColorGetR(color), scale);
+ fSrcG = SkAlphaMul(SkColorGetG(color), scale);
+ fSrcB = SkAlphaMul(SkColorGetB(color), scale);
+
+ fPMColor = SkPackARGB32(fSrcA, fSrcR, fSrcG, fSrcB);
+ fColor32Proc = SkBlitRow::ColorProcFactory();
+ fColorRect32Proc = SkBlitRow::ColorRectProcFactory();
+}
+
+const SkBitmap* SkARGB32_Blitter::justAnOpaqueColor(uint32_t* value) {
+ if (255 == fSrcA) {
+ *value = fPMColor;
+ return &fDevice;
+ }
+ return NULL;
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable warning : local variable used without having been initialized
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+void SkARGB32_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width());
+
+ uint32_t* device = fDevice.getAddr32(x, y);
+ fColor32Proc(device, device, width, fPMColor);
+}
+
+void SkARGB32_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ if (fSrcA == 0) {
+ return;
+ }
+
+ uint32_t color = fPMColor;
+ uint32_t* device = fDevice.getAddr32(x, y);
+ unsigned opaqueMask = fSrcA; // if fSrcA is 0xFF, then we will catch the fast opaque case
+
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count <= 0) {
+ return;
+ }
+ unsigned aa = antialias[0];
+ if (aa) {
+ if ((opaqueMask & aa) == 255) {
+ sk_memset32(device, color, count);
+ } else {
+ uint32_t sc = SkAlphaMulQ(color, SkAlpha255To256(aa));
+ fColor32Proc(device, device, count, sc);
+ }
+ }
+ runs += count;
+ antialias += count;
+ device += count;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+#define solid_8_pixels(mask, dst, color) \
+ do { \
+ if (mask & 0x80) dst[0] = color; \
+ if (mask & 0x40) dst[1] = color; \
+ if (mask & 0x20) dst[2] = color; \
+ if (mask & 0x10) dst[3] = color; \
+ if (mask & 0x08) dst[4] = color; \
+ if (mask & 0x04) dst[5] = color; \
+ if (mask & 0x02) dst[6] = color; \
+ if (mask & 0x01) dst[7] = color; \
+ } while (0)
+
+#define SK_BLITBWMASK_NAME SkARGB32_BlitBW
+#define SK_BLITBWMASK_ARGS , SkPMColor color
+#define SK_BLITBWMASK_BLIT8(mask, dst) solid_8_pixels(mask, dst, color)
+#define SK_BLITBWMASK_GETADDR getAddr32
+#define SK_BLITBWMASK_DEVTYPE uint32_t
+#include "SkBlitBWMaskTemplate.h"
+
+#define blend_8_pixels(mask, dst, sc, dst_scale) \
+ do { \
+ if (mask & 0x80) { dst[0] = sc + SkAlphaMulQ(dst[0], dst_scale); } \
+ if (mask & 0x40) { dst[1] = sc + SkAlphaMulQ(dst[1], dst_scale); } \
+ if (mask & 0x20) { dst[2] = sc + SkAlphaMulQ(dst[2], dst_scale); } \
+ if (mask & 0x10) { dst[3] = sc + SkAlphaMulQ(dst[3], dst_scale); } \
+ if (mask & 0x08) { dst[4] = sc + SkAlphaMulQ(dst[4], dst_scale); } \
+ if (mask & 0x04) { dst[5] = sc + SkAlphaMulQ(dst[5], dst_scale); } \
+ if (mask & 0x02) { dst[6] = sc + SkAlphaMulQ(dst[6], dst_scale); } \
+ if (mask & 0x01) { dst[7] = sc + SkAlphaMulQ(dst[7], dst_scale); } \
+ } while (0)
+
+#define SK_BLITBWMASK_NAME SkARGB32_BlendBW
+#define SK_BLITBWMASK_ARGS , uint32_t sc, unsigned dst_scale
+#define SK_BLITBWMASK_BLIT8(mask, dst) blend_8_pixels(mask, dst, sc, dst_scale)
+#define SK_BLITBWMASK_GETADDR getAddr32
+#define SK_BLITBWMASK_DEVTYPE uint32_t
+#include "SkBlitBWMaskTemplate.h"
+
+void SkARGB32_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ SkASSERT(mask.fBounds.contains(clip));
+ SkASSERT(fSrcA != 0xFF);
+
+ if (fSrcA == 0) {
+ return;
+ }
+
+ if (SkBlitMask::BlitColor(fDevice, mask, clip, fColor)) {
+ return;
+ }
+
+ if (mask.fFormat == SkMask::kBW_Format) {
+ SkARGB32_BlendBW(fDevice, mask, clip, fPMColor, SkAlpha255To256(255 - fSrcA));
+ } else if (SkMask::kARGB32_Format == mask.fFormat) {
+ SkARGB32_Blit32(fDevice, mask, clip, fPMColor);
+ }
+}
+
+void SkARGB32_Opaque_Blitter::blitMask(const SkMask& mask,
+ const SkIRect& clip) {
+ SkASSERT(mask.fBounds.contains(clip));
+
+ if (SkBlitMask::BlitColor(fDevice, mask, clip, fColor)) {
+ return;
+ }
+
+ if (mask.fFormat == SkMask::kBW_Format) {
+ SkARGB32_BlitBW(fDevice, mask, clip, fPMColor);
+ } else if (SkMask::kARGB32_Format == mask.fFormat) {
+ SkARGB32_Blit32(fDevice, mask, clip, fPMColor);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkARGB32_Blitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ if (alpha == 0 || fSrcA == 0) {
+ return;
+ }
+
+ uint32_t* device = fDevice.getAddr32(x, y);
+ uint32_t color = fPMColor;
+
+ if (alpha != 255) {
+ color = SkAlphaMulQ(color, SkAlpha255To256(alpha));
+ }
+
+ unsigned dst_scale = 255 - SkGetPackedA32(color);
+ size_t rowBytes = fDevice.rowBytes();
+ while (--height >= 0) {
+ device[0] = color + SkAlphaMulQ(device[0], dst_scale);
+ device = (uint32_t*)((char*)device + rowBytes);
+ }
+}
+
+void SkARGB32_Blitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width() && y + height <= fDevice.height());
+
+ if (fSrcA == 0) {
+ return;
+ }
+
+ uint32_t* device = fDevice.getAddr32(x, y);
+ uint32_t color = fPMColor;
+ size_t rowBytes = fDevice.rowBytes();
+
+ if (255 == SkGetPackedA32(color)) {
+ fColorRect32Proc(device, width, height, rowBytes, color);
+ } else {
+ while (--height >= 0) {
+ fColor32Proc(device, device, width, color);
+ device = (uint32_t*)((char*)device + rowBytes);
+ }
+ }
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+///////////////////////////////////////////////////////////////////////
+
+void SkARGB32_Black_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ uint32_t* device = fDevice.getAddr32(x, y);
+ SkPMColor black = (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT);
+
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count <= 0) {
+ return;
+ }
+ unsigned aa = antialias[0];
+ if (aa) {
+ if (aa == 255) {
+ sk_memset32(device, black, count);
+ } else {
+ SkPMColor src = aa << SK_A32_SHIFT;
+ unsigned dst_scale = 256 - aa;
+ int n = count;
+ do {
+ --n;
+ device[n] = src + SkAlphaMulQ(device[n], dst_scale);
+ } while (n > 0);
+ }
+ }
+ runs += count;
+ antialias += count;
+ device += count;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// 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)));
+
+ fXfermode = paint.getXfermode();
+ SkSafeRef(fXfermode);
+
+ int flags = 0;
+ if (!(fShader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+ flags |= SkBlitRow::kSrcPixelAlpha_Flag32;
+ }
+ // we call this on the output from the shader
+ 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() {
+ SkSafeUnref(fXfermode);
+ sk_free(fBuffer);
+}
+
+void SkARGB32_Shader_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x >= 0 && y >= 0 && x + width <= fDevice.width());
+
+ uint32_t* device = fDevice.getAddr32(x, y);
+
+ if (fShadeDirectlyIntoDevice) {
+ fShader->shadeSpan(x, y, device, width);
+ } else {
+ SkPMColor* span = fBuffer;
+ fShader->shadeSpan(x, y, span, width);
+ if (fXfermode) {
+ fXfermode->xfer32(device, span, width, NULL);
+ } else {
+ fProc32(device, span, width, 255);
+ }
+ }
+}
+
+void SkARGB32_Shader_Blitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(x >= 0 && y >= 0 &&
+ x + width <= fDevice.width() && y + height <= fDevice.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 (fShadeDirectlyIntoDevice) {
+ void* ctx;
+ SkShader::ShadeProc shadeProc = fShader->asAShadeProc(&ctx);
+ if (shadeProc) {
+ do {
+ shadeProc(ctx, x, y, device, width);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ do {
+ shader->shadeSpan(x, y, device, width);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ } else {
+ SkXfermode* xfer = fXfermode;
+ if (xfer) {
+ do {
+ shader->shadeSpan(x, y, span, width);
+ xfer->xfer32(device, span, width, NULL);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ } else {
+ SkBlitRow::Proc32 proc = fProc32;
+ do {
+ shader->shadeSpan(x, y, span, width);
+ proc(device, span, width, 255);
+ y += 1;
+ device = (uint32_t*)((char*)device + deviceRB);
+ } while (--height > 0);
+ }
+ }
+}
+
+void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ SkPMColor* span = fBuffer;
+ uint32_t* device = fDevice.getAddr32(x, y);
+ SkShader* shader = fShader;
+
+ if (fXfermode && !fShadeDirectlyIntoDevice) {
+ for (;;) {
+ SkXfermode* xfer = fXfermode;
+
+ int count = *runs;
+ if (count <= 0)
+ break;
+ int aa = *antialias;
+ if (aa) {
+ shader->shadeSpan(x, y, span, count);
+ if (aa == 255) {
+ xfer->xfer32(device, span, count, NULL);
+ } else {
+ // count is almost always 1
+ for (int i = count - 1; i >= 0; --i) {
+ xfer->xfer32(&device[i], &span[i], 1, antialias);
+ }
+ }
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+ } else if (fShadeDirectlyIntoDevice ||
+ (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ int aa = *antialias;
+ if (aa) {
+ if (aa == 255) {
+ // cool, have the shader draw right into the device
+ shader->shadeSpan(x, y, device, count);
+ } else {
+ shader->shadeSpan(x, y, span, count);
+ fProc32Blend(device, span, count, aa);
+ }
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+ } else {
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ int aa = *antialias;
+ if (aa) {
+ fShader->shadeSpan(x, y, span, count);
+ if (aa == 255) {
+ fProc32(device, span, count, 255);
+ } else {
+ fProc32Blend(device, span, count, aa);
+ }
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+ }
+}
+
+void SkARGB32_Shader_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) {
+ // we only handle kA8 with an xfermode
+ if (fXfermode && (SkMask::kA8_Format != mask.fFormat)) {
+ this->INHERITED::blitMask(mask, clip);
+ return;
+ }
+
+ SkASSERT(mask.fBounds.contains(clip));
+
+ SkBlitMask::RowProc proc = NULL;
+ if (!fXfermode) {
+ unsigned flags = 0;
+ if (fShader->getFlags() & SkShader::kOpaqueAlpha_Flag) {
+ flags |= SkBlitMask::kSrcIsOpaque_RowFlag;
+ }
+ proc = SkBlitMask::RowFactory(SkBitmap::kARGB_8888_Config, mask.fFormat,
+ (SkBlitMask::RowFlags)flags);
+ if (NULL == proc) {
+ this->INHERITED::blitMask(mask, clip);
+ return;
+ }
+ }
+
+ const int x = clip.fLeft;
+ const int width = clip.width();
+ int y = clip.fTop;
+ int height = clip.height();
+
+ char* dstRow = (char*)fDevice.getAddr32(x, y);
+ const size_t dstRB = fDevice.rowBytes();
+ const uint8_t* maskRow = (const uint8_t*)mask.getAddr(x, y);
+ const size_t maskRB = mask.fRowBytes;
+
+ SkShader* shader = fShader;
+ SkPMColor* span = fBuffer;
+
+ if (fXfermode) {
+ SkASSERT(SkMask::kA8_Format == mask.fFormat);
+ SkXfermode* xfer = fXfermode;
+ do {
+ shader->shadeSpan(x, y, span, width);
+ xfer->xfer32((SkPMColor*)dstRow, span, width, maskRow);
+ dstRow += dstRB;
+ maskRow += maskRB;
+ y += 1;
+ } while (--height > 0);
+ } else {
+ do {
+ shader->shadeSpan(x, y, span, width);
+ proc(dstRow, maskRow, span, width);
+ dstRow += dstRB;
+ maskRow += maskRB;
+ y += 1;
+ } while (--height > 0);
+ }
+}
+
+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/core/SkBlitter_RGB16.cpp b/core/SkBlitter_RGB16.cpp
new file mode 100644
index 00000000..256cbc69
--- /dev/null
+++ b/core/SkBlitter_RGB16.cpp
@@ -0,0 +1,1055 @@
+
+/*
+ * 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 "SkBlitRow.h"
+#include "SkCoreBlitters.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkShader.h"
+#include "SkTemplatesPriv.h"
+#include "SkUtils.h"
+#include "SkXfermode.h"
+
+#if defined(__ARM_HAVE_NEON) && defined(SK_CPU_LENDIAN)
+ #define SK_USE_NEON
+ #include <arm_neon.h>
+#else
+ // if we don't have neon, then our black blitter is worth the extra code
+ #define USE_BLACK_BLITTER
+#endif
+
+void sk_dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
+ int count) {
+ if (count > 0) {
+ // see if we need to write one short before we can cast to an 4byte ptr
+ // (we do this subtract rather than (unsigned)dst so we don't get warnings
+ // on 64bit machines)
+ if (((char*)dst - (char*)0) & 2) {
+ *dst++ = value;
+ count -= 1;
+ SkTSwap(value, other);
+ }
+
+ // fast way to set [value,other] pairs
+#ifdef SK_CPU_BENDIAN
+ sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
+#else
+ sk_memset32((uint32_t*)dst, (other << 16) | value, count >> 1);
+#endif
+
+ if (count & 1) {
+ dst[count - 1] = value;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkRGB16_Blitter : public SkRasterBlitter {
+public:
+ SkRGB16_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ virtual void blitRect(int x, int y, int width, int height);
+ virtual void blitMask(const SkMask&,
+ const SkIRect&);
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t*);
+
+protected:
+ SkPMColor fSrcColor32;
+ uint32_t fExpandedRaw16;
+ unsigned fScale;
+ uint16_t fColor16; // already scaled by fScale
+ uint16_t fRawColor16; // unscaled
+ uint16_t fRawDither16; // unscaled
+ SkBool8 fDoDither;
+
+ // illegal
+ SkRGB16_Blitter& operator=(const SkRGB16_Blitter&);
+
+ typedef SkRasterBlitter INHERITED;
+};
+
+class SkRGB16_Opaque_Blitter : public SkRGB16_Blitter {
+public:
+ SkRGB16_Opaque_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ virtual void blitRect(int x, int y, int width, int height);
+ virtual void blitMask(const SkMask&,
+ const SkIRect&);
+
+private:
+ typedef SkRGB16_Blitter INHERITED;
+};
+
+#ifdef USE_BLACK_BLITTER
+class SkRGB16_Black_Blitter : public SkRGB16_Opaque_Blitter {
+public:
+ SkRGB16_Black_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitMask(const SkMask&, const SkIRect&);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+
+private:
+ typedef SkRGB16_Opaque_Blitter INHERITED;
+};
+#endif
+
+class SkRGB16_Shader_Blitter : public SkShaderBlitter {
+public:
+ SkRGB16_Shader_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual ~SkRGB16_Shader_Blitter();
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+ virtual void blitRect(int x, int y, int width, int height);
+
+protected:
+ SkPMColor* fBuffer;
+ SkBlitRow::Proc fOpaqueProc;
+ SkBlitRow::Proc fAlphaProc;
+
+private:
+ // illegal
+ SkRGB16_Shader_Blitter& operator=(const SkRGB16_Shader_Blitter&);
+
+ typedef SkShaderBlitter INHERITED;
+};
+
+// used only if the shader can perform shadSpan16
+class SkRGB16_Shader16_Blitter : public SkRGB16_Shader_Blitter {
+public:
+ SkRGB16_Shader16_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+ virtual void blitRect(int x, int y, int width, int height);
+
+private:
+ typedef SkRGB16_Shader_Blitter INHERITED;
+};
+
+class SkRGB16_Shader_Xfermode_Blitter : public SkShaderBlitter {
+public:
+ SkRGB16_Shader_Xfermode_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual ~SkRGB16_Shader_Xfermode_Blitter();
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha* antialias,
+ const int16_t* runs);
+
+private:
+ SkXfermode* fXfermode;
+ SkPMColor* fBuffer;
+ uint8_t* fAAExpand;
+
+ // illegal
+ SkRGB16_Shader_Xfermode_Blitter& operator=(const SkRGB16_Shader_Xfermode_Blitter&);
+
+ typedef SkShaderBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef USE_BLACK_BLITTER
+SkRGB16_Black_Blitter::SkRGB16_Black_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device, paint) {
+ SkASSERT(paint.getShader() == NULL);
+ SkASSERT(paint.getColorFilter() == NULL);
+ SkASSERT(paint.getXfermode() == NULL);
+ SkASSERT(paint.getColor() == SK_ColorBLACK);
+}
+
+#if 1
+#define black_8_pixels(mask, dst) \
+ do { \
+ if (mask & 0x80) dst[0] = 0; \
+ if (mask & 0x40) dst[1] = 0; \
+ if (mask & 0x20) dst[2] = 0; \
+ if (mask & 0x10) dst[3] = 0; \
+ if (mask & 0x08) dst[4] = 0; \
+ if (mask & 0x04) dst[5] = 0; \
+ if (mask & 0x02) dst[6] = 0; \
+ if (mask & 0x01) dst[7] = 0; \
+ } while (0)
+#else
+static inline black_8_pixels(U8CPU mask, uint16_t dst[])
+{
+ if (mask & 0x80) dst[0] = 0;
+ if (mask & 0x40) dst[1] = 0;
+ if (mask & 0x20) dst[2] = 0;
+ if (mask & 0x10) dst[3] = 0;
+ if (mask & 0x08) dst[4] = 0;
+ if (mask & 0x04) dst[5] = 0;
+ if (mask & 0x02) dst[6] = 0;
+ if (mask & 0x01) dst[7] = 0;
+}
+#endif
+
+#define SK_BLITBWMASK_NAME SkRGB16_Black_BlitBW
+#define SK_BLITBWMASK_ARGS
+#define SK_BLITBWMASK_BLIT8(mask, dst) black_8_pixels(mask, dst)
+#define SK_BLITBWMASK_GETADDR getAddr16
+#define SK_BLITBWMASK_DEVTYPE uint16_t
+#include "SkBlitBWMaskTemplate.h"
+
+void SkRGB16_Black_Blitter::blitMask(const SkMask& mask,
+ const SkIRect& clip) {
+ if (mask.fFormat == SkMask::kBW_Format) {
+ SkRGB16_Black_BlitBW(fDevice, mask, clip);
+ } else {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(clip.fLeft, clip.fTop);
+ const uint8_t* SK_RESTRICT alpha = mask.getAddr8(clip.fLeft, clip.fTop);
+ unsigned width = clip.width();
+ unsigned height = clip.height();
+ size_t deviceRB = fDevice.rowBytes() - (width << 1);
+ unsigned maskRB = mask.fRowBytes - width;
+
+ SkASSERT((int)height > 0);
+ SkASSERT((int)width > 0);
+ SkASSERT((int)deviceRB >= 0);
+ SkASSERT((int)maskRB >= 0);
+
+ do {
+ unsigned w = width;
+ do {
+ unsigned aa = *alpha++;
+ *device = SkAlphaMulRGB16(*device, SkAlpha255To256(255 - aa));
+ device += 1;
+ } while (--w != 0);
+ device = (uint16_t*)((char*)device + deviceRB);
+ alpha += maskRB;
+ } while (--height != 0);
+ }
+}
+
+void SkRGB16_Black_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count <= 0) {
+ return;
+ }
+ runs += count;
+
+ unsigned aa = antialias[0];
+ antialias += count;
+ if (aa) {
+ if (aa == 255) {
+ memset(device, 0, count << 1);
+ } else {
+ aa = SkAlpha255To256(255 - aa);
+ do {
+ *device = SkAlphaMulRGB16(*device, aa);
+ device += 1;
+ } while (--count != 0);
+ continue;
+ }
+ }
+ device += count;
+ }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+SkRGB16_Opaque_Blitter::SkRGB16_Opaque_Blitter(const SkBitmap& device,
+ const SkPaint& paint)
+: INHERITED(device, paint) {}
+
+void SkRGB16_Opaque_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(width > 0);
+ SkASSERT(x + width <= fDevice.width());
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ uint16_t srcColor = fColor16;
+
+ SkASSERT(fRawColor16 == srcColor);
+ if (fDoDither) {
+ uint16_t ditherColor = fRawDither16;
+ if ((x ^ y) & 1) {
+ SkTSwap(ditherColor, srcColor);
+ }
+ sk_dither_memset16(device, srcColor, ditherColor, width);
+ } else {
+ sk_memset16(device, srcColor, width);
+ }
+}
+
+// return 1 or 0 from a bool
+static inline int Bool2Int(int value) {
+ return !!value;
+}
+
+void SkRGB16_Opaque_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ uint16_t srcColor = fRawColor16;
+ uint32_t srcExpanded = fExpandedRaw16;
+ int ditherInt = Bool2Int(fDoDither);
+ uint16_t ditherColor = fRawDither16;
+ // if we have no dithering, this will always fail
+ if ((x ^ y) & ditherInt) {
+ SkTSwap(ditherColor, srcColor);
+ }
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count <= 0) {
+ return;
+ }
+ runs += count;
+
+ unsigned aa = antialias[0];
+ antialias += count;
+ if (aa) {
+ if (aa == 255) {
+ if (ditherInt) {
+ sk_dither_memset16(device, srcColor,
+ ditherColor, count);
+ } else {
+ sk_memset16(device, srcColor, count);
+ }
+ } else {
+ // TODO: respect fDoDither
+ unsigned scale5 = SkAlpha255To256(aa) >> 3;
+ uint32_t src32 = srcExpanded * scale5;
+ scale5 = 32 - scale5; // now we can use it on the device
+ int n = count;
+ do {
+ uint32_t dst32 = SkExpand_rgb_16(*device) * scale5;
+ *device++ = SkCompact_rgb_16((src32 + dst32) >> 5);
+ } while (--n != 0);
+ goto DONE;
+ }
+ }
+ device += count;
+
+ DONE:
+ // if we have no dithering, this will always fail
+ if (count & ditherInt) {
+ SkTSwap(ditherColor, srcColor);
+ }
+ }
+}
+
+#define solid_8_pixels(mask, dst, color) \
+ do { \
+ if (mask & 0x80) dst[0] = color; \
+ if (mask & 0x40) dst[1] = color; \
+ if (mask & 0x20) dst[2] = color; \
+ if (mask & 0x10) dst[3] = color; \
+ if (mask & 0x08) dst[4] = color; \
+ if (mask & 0x04) dst[5] = color; \
+ if (mask & 0x02) dst[6] = color; \
+ if (mask & 0x01) dst[7] = color; \
+ } while (0)
+
+#define SK_BLITBWMASK_NAME SkRGB16_BlitBW
+#define SK_BLITBWMASK_ARGS , uint16_t color
+#define SK_BLITBWMASK_BLIT8(mask, dst) solid_8_pixels(mask, dst, color)
+#define SK_BLITBWMASK_GETADDR getAddr16
+#define SK_BLITBWMASK_DEVTYPE uint16_t
+#include "SkBlitBWMaskTemplate.h"
+
+static U16CPU blend_compact(uint32_t src32, uint32_t dst32, unsigned scale5) {
+ return SkCompact_rgb_16(dst32 + ((src32 - dst32) * scale5 >> 5));
+}
+
+void SkRGB16_Opaque_Blitter::blitMask(const SkMask& mask,
+ const SkIRect& clip) {
+ if (mask.fFormat == SkMask::kBW_Format) {
+ SkRGB16_BlitBW(fDevice, mask, clip, fColor16);
+ return;
+ }
+
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(clip.fLeft, clip.fTop);
+ const uint8_t* SK_RESTRICT alpha = mask.getAddr8(clip.fLeft, clip.fTop);
+ int width = clip.width();
+ int height = clip.height();
+ size_t deviceRB = fDevice.rowBytes() - (width << 1);
+ unsigned maskRB = mask.fRowBytes - width;
+ uint32_t expanded32 = fExpandedRaw16;
+
+#ifdef SK_USE_NEON
+#define UNROLL 8
+ do {
+ int w = width;
+ if (w >= UNROLL) {
+ uint32x4_t color, dev_lo, dev_hi;
+ uint32x4_t wn1, wn2, tmp;
+ uint32x4_t vmask_g16, vmask_ng16;
+ uint16x8_t valpha, vdev;
+ uint16x4_t odev_lo, odev_hi, valpha_lo, valpha_hi;
+
+ // prepare constants
+ vmask_g16 = vdupq_n_u32(SK_G16_MASK_IN_PLACE);
+ vmask_ng16 = vdupq_n_u32(~SK_G16_MASK_IN_PLACE);
+ color = vdupq_n_u32(expanded32);
+
+ do {
+ // alpha is 8x8, widen and split to get a pair of 16x4
+ valpha = vaddw_u8(vdupq_n_u16(1), vld1_u8(alpha));
+ valpha = vshrq_n_u16(valpha, 3);
+ valpha_lo = vget_low_u16(valpha);
+ valpha_hi = vget_high_u16(valpha);
+
+ // load pixels
+ vdev = vld1q_u16(device);
+ dev_lo = vmovl_u16(vget_low_u16(vdev));
+ dev_hi = vmovl_u16(vget_high_u16(vdev));
+
+ // unpack them in 32 bits
+ dev_lo = (dev_lo & vmask_ng16) | vshlq_n_u32(dev_lo & vmask_g16, 16);
+ dev_hi = (dev_hi & vmask_ng16) | vshlq_n_u32(dev_hi & vmask_g16, 16);
+
+ // blend with color
+ tmp = (color - dev_lo) * vmovl_u16(valpha_lo);
+ tmp = vshrq_n_u32(tmp, 5);
+ dev_lo += tmp;
+
+ tmp = vmulq_u32(color - dev_hi, vmovl_u16(valpha_hi));
+ tmp = vshrq_n_u32(tmp, 5);
+ dev_hi += tmp;
+
+ // re-compact
+ wn1 = dev_lo & vmask_ng16;
+ wn2 = vshrq_n_u32(dev_lo, 16) & vmask_g16;
+ odev_lo = vmovn_u32(wn1 | wn2);
+
+ wn1 = dev_hi & vmask_ng16;
+ wn2 = vshrq_n_u32(dev_hi, 16) & vmask_g16;
+ odev_hi = vmovn_u32(wn1 | wn2);
+
+ // store
+ vst1q_u16(device, vcombine_u16(odev_lo, odev_hi));
+
+ device += UNROLL;
+ alpha += UNROLL;
+ w -= UNROLL;
+ } while (w >= UNROLL);
+ }
+
+ // residuals
+ while (w > 0) {
+ *device = blend_compact(expanded32, SkExpand_rgb_16(*device),
+ SkAlpha255To256(*alpha++) >> 3);
+ device += 1;
+ --w;
+ }
+ device = (uint16_t*)((char*)device + deviceRB);
+ alpha += maskRB;
+ } while (--height != 0);
+#undef UNROLL
+#else // non-neon code
+ do {
+ int w = width;
+ do {
+ *device = blend_compact(expanded32, SkExpand_rgb_16(*device),
+ SkAlpha255To256(*alpha++) >> 3);
+ device += 1;
+ } while (--w != 0);
+ device = (uint16_t*)((char*)device + deviceRB);
+ alpha += maskRB;
+ } while (--height != 0);
+#endif
+}
+
+void SkRGB16_Opaque_Blitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ size_t deviceRB = fDevice.rowBytes();
+
+ // TODO: respect fDoDither
+ unsigned scale5 = SkAlpha255To256(alpha) >> 3;
+ uint32_t src32 = fExpandedRaw16 * scale5;
+ scale5 = 32 - scale5;
+ do {
+ uint32_t dst32 = SkExpand_rgb_16(*device) * scale5;
+ *device = SkCompact_rgb_16((src32 + dst32) >> 5);
+ device = (uint16_t*)((char*)device + deviceRB);
+ } while (--height != 0);
+}
+
+void SkRGB16_Opaque_Blitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(x + width <= fDevice.width() && y + height <= fDevice.height());
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ size_t deviceRB = fDevice.rowBytes();
+ uint16_t color16 = fColor16;
+
+ if (fDoDither) {
+ uint16_t ditherColor = fRawDither16;
+ if ((x ^ y) & 1) {
+ SkTSwap(ditherColor, color16);
+ }
+ while (--height >= 0) {
+ sk_dither_memset16(device, color16, ditherColor, width);
+ SkTSwap(ditherColor, color16);
+ device = (uint16_t*)((char*)device + deviceRB);
+ }
+ } else { // no dither
+ while (--height >= 0) {
+ sk_memset16(device, color16, width);
+ device = (uint16_t*)((char*)device + deviceRB);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRGB16_Blitter::SkRGB16_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device) {
+ SkColor color = paint.getColor();
+
+ fSrcColor32 = SkPreMultiplyColor(color);
+ fScale = SkAlpha255To256(SkColorGetA(color));
+
+ int r = SkColorGetR(color);
+ int g = SkColorGetG(color);
+ int b = SkColorGetB(color);
+
+ fRawColor16 = fRawDither16 = SkPack888ToRGB16(r, g, b);
+ // if we're dithered, use fRawDither16 to hold that.
+ if ((fDoDither = paint.isDither()) != false) {
+ fRawDither16 = SkDitherPack888ToRGB16(r, g, b);
+ }
+
+ fExpandedRaw16 = SkExpand_rgb_16(fRawColor16);
+
+ fColor16 = SkPackRGB16( SkAlphaMul(r, fScale) >> (8 - SK_R16_BITS),
+ SkAlphaMul(g, fScale) >> (8 - SK_G16_BITS),
+ SkAlphaMul(b, fScale) >> (8 - SK_B16_BITS));
+}
+
+const SkBitmap* SkRGB16_Blitter::justAnOpaqueColor(uint32_t* value) {
+ if (!fDoDither && 256 == fScale) {
+ *value = fRawColor16;
+ return &fDevice;
+ }
+ return NULL;
+}
+
+static uint32_t pmcolor_to_expand16(SkPMColor c) {
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+ return (g << 24) | (r << 13) | (b << 2);
+}
+
+static inline void blend32_16_row(SkPMColor src, uint16_t dst[], int count) {
+ SkASSERT(count > 0);
+ uint32_t src_expand = pmcolor_to_expand16(src);
+ unsigned scale = SkAlpha255To256(0xFF - SkGetPackedA32(src)) >> 3;
+ do {
+ uint32_t dst_expand = SkExpand_rgb_16(*dst) * scale;
+ *dst = SkCompact_rgb_16((src_expand + dst_expand) >> 5);
+ dst += 1;
+ } while (--count != 0);
+}
+
+void SkRGB16_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(width > 0);
+ SkASSERT(x + width <= fDevice.width());
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+
+ // TODO: respect fDoDither
+ blend32_16_row(fSrcColor32, device, width);
+}
+
+void SkRGB16_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ uint32_t srcExpanded = fExpandedRaw16;
+ unsigned scale = fScale;
+
+ // TODO: respect fDoDither
+ for (;;) {
+ int count = runs[0];
+ SkASSERT(count >= 0);
+ if (count <= 0) {
+ return;
+ }
+ runs += count;
+
+ unsigned aa = antialias[0];
+ antialias += count;
+ if (aa) {
+ unsigned scale5 = SkAlpha255To256(aa) * scale >> (8 + 3);
+ uint32_t src32 = srcExpanded * scale5;
+ scale5 = 32 - scale5;
+ do {
+ uint32_t dst32 = SkExpand_rgb_16(*device) * scale5;
+ *device++ = SkCompact_rgb_16((src32 + dst32) >> 5);
+ } while (--count != 0);
+ continue;
+ }
+ device += count;
+ }
+}
+
+static inline void blend_8_pixels(U8CPU bw, uint16_t dst[], unsigned dst_scale,
+ U16CPU srcColor) {
+ if (bw & 0x80) dst[0] = srcColor + SkAlphaMulRGB16(dst[0], dst_scale);
+ if (bw & 0x40) dst[1] = srcColor + SkAlphaMulRGB16(dst[1], dst_scale);
+ if (bw & 0x20) dst[2] = srcColor + SkAlphaMulRGB16(dst[2], dst_scale);
+ if (bw & 0x10) dst[3] = srcColor + SkAlphaMulRGB16(dst[3], dst_scale);
+ if (bw & 0x08) dst[4] = srcColor + SkAlphaMulRGB16(dst[4], dst_scale);
+ if (bw & 0x04) dst[5] = srcColor + SkAlphaMulRGB16(dst[5], dst_scale);
+ if (bw & 0x02) dst[6] = srcColor + SkAlphaMulRGB16(dst[6], dst_scale);
+ if (bw & 0x01) dst[7] = srcColor + SkAlphaMulRGB16(dst[7], dst_scale);
+}
+
+#define SK_BLITBWMASK_NAME SkRGB16_BlendBW
+#define SK_BLITBWMASK_ARGS , unsigned dst_scale, U16CPU src_color
+#define SK_BLITBWMASK_BLIT8(mask, dst) blend_8_pixels(mask, dst, dst_scale, src_color)
+#define SK_BLITBWMASK_GETADDR getAddr16
+#define SK_BLITBWMASK_DEVTYPE uint16_t
+#include "SkBlitBWMaskTemplate.h"
+
+void SkRGB16_Blitter::blitMask(const SkMask& mask,
+ const SkIRect& clip) {
+ if (mask.fFormat == SkMask::kBW_Format) {
+ SkRGB16_BlendBW(fDevice, mask, clip, 256 - fScale, fColor16);
+ return;
+ }
+
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(clip.fLeft, clip.fTop);
+ const uint8_t* SK_RESTRICT alpha = mask.getAddr8(clip.fLeft, clip.fTop);
+ int width = clip.width();
+ int height = clip.height();
+ size_t deviceRB = fDevice.rowBytes() - (width << 1);
+ unsigned maskRB = mask.fRowBytes - width;
+ uint32_t color32 = fExpandedRaw16;
+
+ unsigned scale256 = fScale;
+ do {
+ int w = width;
+ do {
+ unsigned aa = *alpha++;
+ unsigned scale = SkAlpha255To256(aa) * scale256 >> (8 + 3);
+ uint32_t src32 = color32 * scale;
+ uint32_t dst32 = SkExpand_rgb_16(*device) * (32 - scale);
+ *device++ = SkCompact_rgb_16((src32 + dst32) >> 5);
+ } while (--w != 0);
+ device = (uint16_t*)((char*)device + deviceRB);
+ alpha += maskRB;
+ } while (--height != 0);
+}
+
+void SkRGB16_Blitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ size_t deviceRB = fDevice.rowBytes();
+
+ // TODO: respect fDoDither
+ unsigned scale5 = SkAlpha255To256(alpha) * fScale >> (8 + 3);
+ uint32_t src32 = fExpandedRaw16 * scale5;
+ scale5 = 32 - scale5;
+ do {
+ uint32_t dst32 = SkExpand_rgb_16(*device) * scale5;
+ *device = SkCompact_rgb_16((src32 + dst32) >> 5);
+ device = (uint16_t*)((char*)device + deviceRB);
+ } while (--height != 0);
+}
+
+void SkRGB16_Blitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(x + width <= fDevice.width() && y + height <= fDevice.height());
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ size_t deviceRB = fDevice.rowBytes();
+ SkPMColor src32 = fSrcColor32;
+
+ while (--height >= 0) {
+ blend32_16_row(src32, device, width);
+ device = (uint16_t*)((char*)device + deviceRB);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRGB16_Shader16_Blitter::SkRGB16_Shader16_Blitter(const SkBitmap& device,
+ const SkPaint& paint)
+ : SkRGB16_Shader_Blitter(device, paint) {
+ SkASSERT(SkShader::CanCallShadeSpan16(fShaderFlags));
+}
+
+void SkRGB16_Shader16_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x + width <= fDevice.width());
+
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+ SkShader* shader = fShader;
+
+ int alpha = shader->getSpan16Alpha();
+ if (0xFF == alpha) {
+ shader->shadeSpan16(x, y, device, width);
+ } else {
+ uint16_t* span16 = (uint16_t*)fBuffer;
+ shader->shadeSpan16(x, y, span16, width);
+ SkBlendRGB16(span16, device, SkAlpha255To256(alpha), width);
+ }
+}
+
+void SkRGB16_Shader16_Blitter::blitRect(int x, int y, int width, int height) {
+ SkShader* shader = fShader;
+ uint16_t* dst = fDevice.getAddr16(x, y);
+ size_t dstRB = fDevice.rowBytes();
+ int alpha = shader->getSpan16Alpha();
+
+ if (0xFF == alpha) {
+ if (fShaderFlags & SkShader::kConstInY16_Flag) {
+ // have the shader blit directly into the device the first time
+ shader->shadeSpan16(x, y, dst, width);
+ // and now just memcpy that line on the subsequent lines
+ if (--height > 0) {
+ const uint16_t* orig = dst;
+ do {
+ dst = (uint16_t*)((char*)dst + dstRB);
+ memcpy(dst, orig, width << 1);
+ } while (--height);
+ }
+ } else { // need to call shadeSpan16 for every line
+ do {
+ shader->shadeSpan16(x, y, dst, width);
+ y += 1;
+ dst = (uint16_t*)((char*)dst + dstRB);
+ } while (--height);
+ }
+ } else {
+ int scale = SkAlpha255To256(alpha);
+ uint16_t* span16 = (uint16_t*)fBuffer;
+ if (fShaderFlags & SkShader::kConstInY16_Flag) {
+ shader->shadeSpan16(x, y, span16, width);
+ do {
+ SkBlendRGB16(span16, dst, scale, width);
+ dst = (uint16_t*)((char*)dst + dstRB);
+ } while (--height);
+ } else {
+ do {
+ shader->shadeSpan16(x, y, span16, width);
+ SkBlendRGB16(span16, dst, scale, width);
+ y += 1;
+ dst = (uint16_t*)((char*)dst + dstRB);
+ } while (--height);
+ }
+ }
+}
+
+void SkRGB16_Shader16_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ SkShader* shader = fShader;
+ SkPMColor* SK_RESTRICT span = fBuffer;
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+
+ int alpha = shader->getSpan16Alpha();
+ uint16_t* span16 = (uint16_t*)span;
+
+ if (0xFF == alpha) {
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ SkASSERT(count <= fDevice.width()); // don't overrun fBuffer
+
+ int aa = *antialias;
+ if (aa == 255) {
+ // go direct to the device!
+ shader->shadeSpan16(x, y, device, count);
+ } else if (aa) {
+ shader->shadeSpan16(x, y, span16, count);
+ SkBlendRGB16(span16, device, SkAlpha255To256(aa), count);
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+ } else { // span alpha is < 255
+ alpha = SkAlpha255To256(alpha);
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ SkASSERT(count <= fDevice.width()); // don't overrun fBuffer
+
+ int aa = SkAlphaMul(*antialias, alpha);
+ if (aa) {
+ shader->shadeSpan16(x, y, span16, count);
+ SkBlendRGB16(span16, device, SkAlpha255To256(aa), count);
+ }
+
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRGB16_Shader_Blitter::SkRGB16_Shader_Blitter(const SkBitmap& device,
+ const SkPaint& paint)
+: INHERITED(device, paint) {
+ SkASSERT(paint.getXfermode() == NULL);
+
+ fBuffer = (SkPMColor*)sk_malloc_throw(device.width() * sizeof(SkPMColor));
+
+ // compute SkBlitRow::Procs
+ unsigned flags = 0;
+
+ uint32_t shaderFlags = fShaderFlags;
+ // shaders take care of global alpha, so we never set it in SkBlitRow
+ if (!(shaderFlags & SkShader::kOpaqueAlpha_Flag)) {
+ flags |= SkBlitRow::kSrcPixelAlpha_Flag;
+ }
+ // don't dither if the shader is really 16bit
+ if (paint.isDither() && !(shaderFlags & SkShader::kIntrinsicly16_Flag)) {
+ flags |= SkBlitRow::kDither_Flag;
+ }
+ // used when we know our global alpha is 0xFF
+ fOpaqueProc = SkBlitRow::Factory(flags, SkBitmap::kRGB_565_Config);
+ // used when we know our global alpha is < 0xFF
+ fAlphaProc = SkBlitRow::Factory(flags | SkBlitRow::kGlobalAlpha_Flag,
+ SkBitmap::kRGB_565_Config);
+}
+
+SkRGB16_Shader_Blitter::~SkRGB16_Shader_Blitter() {
+ sk_free(fBuffer);
+}
+
+void SkRGB16_Shader_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x + width <= fDevice.width());
+
+ fShader->shadeSpan(x, y, fBuffer, width);
+ // shaders take care of global alpha, so we pass 0xFF (should be ignored)
+ fOpaqueProc(fDevice.getAddr16(x, y), fBuffer, width, 0xFF, x, y);
+}
+
+void SkRGB16_Shader_Blitter::blitRect(int x, int y, int width, int height) {
+ SkShader* shader = fShader;
+ SkBlitRow::Proc proc = fOpaqueProc;
+ SkPMColor* buffer = fBuffer;
+ uint16_t* dst = fDevice.getAddr16(x, y);
+ size_t dstRB = fDevice.rowBytes();
+
+ if (fShaderFlags & SkShader::kConstInY32_Flag) {
+ shader->shadeSpan(x, y, buffer, width);
+ do {
+ proc(dst, buffer, width, 0xFF, x, y);
+ y += 1;
+ dst = (uint16_t*)((char*)dst + dstRB);
+ } while (--height);
+ } else {
+ do {
+ shader->shadeSpan(x, y, buffer, width);
+ proc(dst, buffer, width, 0xFF, x, y);
+ y += 1;
+ dst = (uint16_t*)((char*)dst + dstRB);
+ } while (--height);
+ }
+}
+
+static inline int count_nonzero_span(const int16_t runs[], const SkAlpha aa[]) {
+ int count = 0;
+ for (;;) {
+ int n = *runs;
+ if (n == 0 || *aa == 0) {
+ break;
+ }
+ runs += n;
+ aa += n;
+ count += n;
+ }
+ return count;
+}
+
+void SkRGB16_Shader_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ SkShader* shader = fShader;
+ SkPMColor* SK_RESTRICT span = fBuffer;
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ int aa = *antialias;
+ if (0 == aa) {
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ continue;
+ }
+
+ int nonZeroCount = count + count_nonzero_span(runs + count, antialias + count);
+
+ SkASSERT(nonZeroCount <= fDevice.width()); // don't overrun fBuffer
+ shader->shadeSpan(x, y, span, nonZeroCount);
+
+ SkPMColor* localSpan = span;
+ for (;;) {
+ SkBlitRow::Proc proc = (aa == 0xFF) ? fOpaqueProc : fAlphaProc;
+ proc(device, localSpan, count, aa, x, y);
+
+ x += count;
+ device += count;
+ runs += count;
+ antialias += count;
+ nonZeroCount -= count;
+ if (nonZeroCount == 0) {
+ break;
+ }
+ localSpan += count;
+ SkASSERT(nonZeroCount > 0);
+ count = *runs;
+ SkASSERT(count > 0);
+ aa = *antialias;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////
+
+SkRGB16_Shader_Xfermode_Blitter::SkRGB16_Shader_Xfermode_Blitter(
+ const SkBitmap& device, const SkPaint& paint)
+: INHERITED(device, paint) {
+ fXfermode = paint.getXfermode();
+ SkASSERT(fXfermode);
+ fXfermode->ref();
+
+ int width = device.width();
+ fBuffer = (SkPMColor*)sk_malloc_throw((width + (SkAlign4(width) >> 2)) * sizeof(SkPMColor));
+ fAAExpand = (uint8_t*)(fBuffer + width);
+}
+
+SkRGB16_Shader_Xfermode_Blitter::~SkRGB16_Shader_Xfermode_Blitter() {
+ fXfermode->unref();
+ sk_free(fBuffer);
+}
+
+void SkRGB16_Shader_Xfermode_Blitter::blitH(int x, int y, int width) {
+ SkASSERT(x + width <= fDevice.width());
+
+ uint16_t* device = fDevice.getAddr16(x, y);
+ SkPMColor* span = fBuffer;
+
+ fShader->shadeSpan(x, y, span, width);
+ fXfermode->xfer16(device, span, width, NULL);
+}
+
+void SkRGB16_Shader_Xfermode_Blitter::blitAntiH(int x, int y,
+ const SkAlpha* SK_RESTRICT antialias,
+ const int16_t* SK_RESTRICT runs) {
+ SkShader* shader = fShader;
+ SkXfermode* mode = fXfermode;
+ SkPMColor* SK_RESTRICT span = fBuffer;
+ uint8_t* SK_RESTRICT aaExpand = fAAExpand;
+ uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
+
+ for (;;) {
+ int count = *runs;
+ if (count <= 0) {
+ break;
+ }
+ int aa = *antialias;
+ if (0 == aa) {
+ device += count;
+ runs += count;
+ antialias += count;
+ x += count;
+ continue;
+ }
+
+ int nonZeroCount = count + count_nonzero_span(runs + count,
+ antialias + count);
+
+ SkASSERT(nonZeroCount <= fDevice.width()); // don't overrun fBuffer
+ shader->shadeSpan(x, y, span, nonZeroCount);
+
+ x += nonZeroCount;
+ SkPMColor* localSpan = span;
+ for (;;) {
+ if (aa == 0xFF) {
+ mode->xfer16(device, localSpan, count, NULL);
+ } else {
+ SkASSERT(aa);
+ memset(aaExpand, aa, count);
+ mode->xfer16(device, localSpan, count, aaExpand);
+ }
+ device += count;
+ runs += count;
+ antialias += count;
+ nonZeroCount -= count;
+ if (nonZeroCount == 0) {
+ break;
+ }
+ localSpan += count;
+ SkASSERT(nonZeroCount > 0);
+ count = *runs;
+ SkASSERT(count > 0);
+ aa = *antialias;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlitter* SkBlitter_ChooseD565(const SkBitmap& device, const SkPaint& paint,
+ void* storage, size_t storageSize) {
+ SkBlitter* blitter;
+ SkShader* shader = paint.getShader();
+ SkXfermode* mode = paint.getXfermode();
+
+ // we require a shader if there is an xfermode, handled by our caller
+ SkASSERT(NULL == mode || NULL != shader);
+
+ if (shader) {
+ if (mode) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Shader_Xfermode_Blitter,
+ storage, storageSize, (device, paint));
+ } else if (shader->canCallShadeSpan16()) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Shader16_Blitter,
+ storage, storageSize, (device, paint));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Shader_Blitter,
+ storage, storageSize, (device, paint));
+ }
+ } else {
+ // no shader, no xfermode, (and we always ignore colorfilter)
+ SkColor color = paint.getColor();
+ if (0 == SkColorGetA(color)) {
+ SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+#ifdef USE_BLACK_BLITTER
+ } else if (SK_ColorBLACK == color) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Black_Blitter, storage,
+ storageSize, (device, paint));
+#endif
+ } else if (0xFF == SkColorGetA(color)) {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Opaque_Blitter, storage,
+ storageSize, (device, paint));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, SkRGB16_Blitter, storage,
+ storageSize, (device, paint));
+ }
+ }
+
+ return blitter;
+}
diff --git a/core/SkBlitter_Sprite.cpp b/core/SkBlitter_Sprite.cpp
new file mode 100644
index 00000000..db7cc69a
--- /dev/null
+++ b/core/SkBlitter_Sprite.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * 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 "SkSpriteBlitter.h"
+
+SkSpriteBlitter::SkSpriteBlitter(const SkBitmap& source)
+ : fSource(&source) {
+ fSource->lockPixels();
+}
+
+SkSpriteBlitter::~SkSpriteBlitter() {
+ fSource->unlockPixels();
+}
+
+void SkSpriteBlitter::setup(const SkBitmap& device, int left, int top,
+ const SkPaint& paint) {
+ fDevice = &device;
+ fLeft = left;
+ fTop = top;
+ fPaint = &paint;
+}
+
+#ifdef SK_DEBUG
+void SkSpriteBlitter::blitH(int x, int y, int width) {
+ SkDEBUGFAIL("how did we get here?");
+}
+
+void SkSpriteBlitter::blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) {
+ SkDEBUGFAIL("how did we get here?");
+}
+
+void SkSpriteBlitter::blitV(int x, int y, int height, SkAlpha alpha) {
+ SkDEBUGFAIL("how did we get here?");
+}
+
+void SkSpriteBlitter::blitMask(const SkMask&, const SkIRect& clip) {
+ SkDEBUGFAIL("how did we get here?");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// returning null means the caller will call SkBlitter::Choose() and
+// have wrapped the source bitmap inside a shader
+SkBlitter* SkBlitter::ChooseSprite( const SkBitmap& device,
+ const SkPaint& paint,
+ const SkBitmap& source,
+ int left, int top,
+ void* storage, size_t storageSize) {
+ /* We currently ignore antialiasing and filtertype, meaning we will take our
+ special blitters regardless of these settings. Ignoring filtertype seems fine
+ since by definition there is no scale in the matrix. Ignoring antialiasing is
+ a bit of a hack, since we "could" pass in the fractional left/top for the bitmap,
+ and respect that by blending the edges of the bitmap against the device. To support
+ this we could either add more special blitters here, or detect antialiasing in the
+ paint and return null if it is set, forcing the client to take the slow shader case
+ (which does respect soft edges).
+ */
+
+ SkSpriteBlitter* blitter;
+
+ switch (device.getConfig()) {
+ case SkBitmap::kRGB_565_Config:
+ blitter = SkSpriteBlitter::ChooseD16(source, paint, storage,
+ storageSize);
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ blitter = SkSpriteBlitter::ChooseD32(source, paint, storage,
+ storageSize);
+ break;
+ default:
+ blitter = NULL;
+ break;
+ }
+
+ if (blitter) {
+ blitter->setup(device, left, top, paint);
+ }
+ return blitter;
+}
diff --git a/core/SkBuffer.cpp b/core/SkBuffer.cpp
new file mode 100644
index 00000000..915264d9
--- /dev/null
+++ b/core/SkBuffer.cpp
@@ -0,0 +1,129 @@
+
+/*
+ * 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 "SkBuffer.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+void SkRBuffer::readNoSizeCheck(void* buffer, size_t size)
+{
+ SkASSERT((fData != 0 && fStop == 0) || fPos + size <= fStop);
+ if (buffer)
+ memcpy(buffer, fPos, size);
+ fPos += size;
+}
+
+const void* SkRBuffer::skip(size_t size)
+{
+ const void* result = fPos;
+ readNoSizeCheck(NULL, size);
+ return result;
+}
+
+size_t SkRBuffer::skipToAlign4()
+{
+ size_t pos = this->pos();
+ size_t n = SkAlign4(pos) - pos;
+ fPos += n;
+ return n;
+}
+
+void* SkWBuffer::skip(size_t size)
+{
+ void* result = fPos;
+ writeNoSizeCheck(NULL, size);
+ return fData == NULL ? NULL : result;
+}
+
+void SkWBuffer::writeNoSizeCheck(const void* buffer, size_t size)
+{
+ SkASSERT(fData == 0 || fStop == 0 || fPos + size <= fStop);
+ if (fData && buffer)
+ memcpy(fPos, buffer, size);
+ fPos += size;
+}
+
+size_t SkWBuffer::padToAlign4()
+{
+ size_t pos = this->pos();
+ size_t n = SkAlign4(pos) - pos;
+
+ if (n && fData)
+ {
+ char* p = fPos;
+ char* stop = p + n;
+ do {
+ *p++ = 0;
+ } while (p < stop);
+ }
+ fPos += n;
+ return n;
+}
+
+#if 0
+#ifdef SK_DEBUG
+ static void AssertBuffer32(const void* buffer)
+ {
+ SkASSERT(buffer);
+ SkASSERT(((size_t)buffer & 3) == 0);
+ }
+#else
+ #define AssertBuffer32(buffer)
+#endif
+
+void* sk_buffer_write_int32(void* buffer, int32_t value)
+{
+ AssertBuffer32(buffer);
+ *(int32_t*)buffer = value;
+ return (char*)buffer + sizeof(int32_t);
+}
+
+void* sk_buffer_write_int32(void* buffer, const int32_t values[], int count)
+{
+ AssertBuffer32(buffer);
+ SkASSERT(count >= 0);
+
+ memcpy((int32_t*)buffer, values, count * sizeof(int32_t));
+ return (char*)buffer + count * sizeof(int32_t);
+}
+
+const void* sk_buffer_read_int32(const void* buffer, int32_t* value)
+{
+ AssertBuffer32(buffer);
+ if (value)
+ *value = *(const int32_t*)buffer;
+ return (const char*)buffer + sizeof(int32_t);
+}
+
+const void* sk_buffer_read_int32(const void* buffer, int32_t values[], int count)
+{
+ AssertBuffer32(buffer);
+ SkASSERT(count >= 0);
+
+ if (values)
+ memcpy(values, (const int32_t*)buffer, count * sizeof(int32_t));
+ return (const char*)buffer + count * sizeof(int32_t);
+}
+
+void* sk_buffer_write_ptr(void* buffer, void* ptr)
+{
+ AssertBuffer32(buffer);
+ *(void**)buffer = ptr;
+ return (char*)buffer + sizeof(void*);
+}
+
+const void* sk_buffer_read_ptr(const void* buffer, void** ptr)
+{
+ AssertBuffer32(buffer);
+ if (ptr)
+ *ptr = *(void**)buffer;
+ return (const char*)buffer + sizeof(void*);
+}
+
+#endif
diff --git a/core/SkBuffer.h b/core/SkBuffer.h
new file mode 100644
index 00000000..96333899
--- /dev/null
+++ b/core/SkBuffer.h
@@ -0,0 +1,138 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBuffer_DEFINED
+#define SkBuffer_DEFINED
+
+#include "SkScalar.h"
+
+/** \class SkRBuffer
+
+ Light weight class for reading data from a memory block.
+ The RBuffer is given the buffer to read from, with either a specified size
+ or no size (in which case no range checking is performed). It is iillegal
+ to attempt to read a value from an empty RBuffer (data == null).
+*/
+class SkRBuffer : SkNoncopyable {
+public:
+ SkRBuffer() : fData(0), fPos(0), fStop(0) {}
+ /** Initialize RBuffer with a data pointer, but no specified length.
+ This signals the RBuffer to not perform range checks during reading.
+ */
+ SkRBuffer(const void* data) {
+ fData = (const char*)data;
+ fPos = (const char*)data;
+ fStop = 0; // no bounds checking
+ }
+ /** Initialize RBuffer with a data point and length.
+ */
+ SkRBuffer(const void* data, size_t size) {
+ SkASSERT(data != 0 || size == 0);
+ fData = (const char*)data;
+ fPos = (const char*)data;
+ fStop = (const char*)data + size;
+ }
+
+ /** Return the number of bytes that have been read from the beginning
+ of the data pointer.
+ */
+ size_t pos() const { return fPos - fData; }
+ /** Return the total size of the data pointer. Only defined if the length was
+ specified in the constructor or in a call to reset().
+ */
+ size_t size() const { return fStop - fData; }
+ /** Return true if the buffer has read to the end of the data pointer.
+ Only defined if the length was specified in the constructor or in a call
+ to reset(). Always returns true if the length was not specified.
+ */
+ bool eof() const { return fPos >= fStop; }
+
+ /** Read the specified number of bytes from the data pointer. If buffer is not
+ null, copy those bytes into buffer.
+ */
+ void read(void* buffer, size_t size) {
+ if (size) {
+ this->readNoSizeCheck(buffer, size);
+ }
+ }
+
+ const void* skip(size_t size); // return start of skipped data
+ size_t skipToAlign4();
+
+ void* readPtr() { void* ptr; read(&ptr, sizeof(ptr)); return ptr; }
+ SkScalar readScalar() { SkScalar x; read(&x, 4); return x; }
+ uint32_t readU32() { uint32_t x; read(&x, 4); return x; }
+ int32_t readS32() { int32_t x; read(&x, 4); return x; }
+ uint16_t readU16() { uint16_t x; read(&x, 2); return x; }
+ int16_t readS16() { int16_t x; read(&x, 2); return x; }
+ uint8_t readU8() { uint8_t x; read(&x, 1); return x; }
+ bool readBool() { return this->readU8() != 0; }
+
+private:
+ void readNoSizeCheck(void* buffer, size_t size);
+
+ const char* fData;
+ const char* fPos;
+ const char* fStop;
+};
+
+/** \class SkWBuffer
+
+ Light weight class for writing data to a memory block.
+ The WBuffer is given the buffer to write into, with either a specified size
+ or no size, in which case no range checking is performed. An empty WBuffer
+ is legal, in which case no data is ever written, but the relative pos()
+ is updated.
+*/
+class SkWBuffer : SkNoncopyable {
+public:
+ SkWBuffer() : fData(0), fPos(0), fStop(0) {}
+ SkWBuffer(void* data) { reset(data); }
+ SkWBuffer(void* data, size_t size) { reset(data, size); }
+
+ void reset(void* data) {
+ fData = (char*)data;
+ fPos = (char*)data;
+ fStop = 0; // no bounds checking
+ }
+
+ void reset(void* data, size_t size) {
+ SkASSERT(data != 0 || size == 0);
+ fData = (char*)data;
+ fPos = (char*)data;
+ fStop = (char*)data + size;
+ }
+
+ size_t pos() const { return fPos - fData; }
+ void* skip(size_t size); // return start of skipped data
+
+ void write(const void* buffer, size_t size) {
+ if (size) {
+ this->writeNoSizeCheck(buffer, size);
+ }
+ }
+
+ size_t padToAlign4();
+
+ void writePtr(const void* x) { this->writeNoSizeCheck(&x, sizeof(x)); }
+ void writeScalar(SkScalar x) { this->writeNoSizeCheck(&x, 4); }
+ void write32(int32_t x) { this->writeNoSizeCheck(&x, 4); }
+ void write16(int16_t x) { this->writeNoSizeCheck(&x, 2); }
+ void write8(int8_t x) { this->writeNoSizeCheck(&x, 1); }
+ void writeBool(bool x) { this->write8(x); }
+
+private:
+ void writeNoSizeCheck(const void* buffer, size_t size);
+
+ char* fData;
+ char* fPos;
+ char* fStop;
+};
+
+#endif
diff --git a/core/SkCanvas.cpp b/core/SkCanvas.cpp
new file mode 100644
index 00000000..5a9a56b2
--- /dev/null
+++ b/core/SkCanvas.cpp
@@ -0,0 +1,2222 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkCanvas.h"
+#include "SkBounder.h"
+#include "SkDevice.h"
+#include "SkDeviceImageFilterProxy.h"
+#include "SkDraw.h"
+#include "SkDrawFilter.h"
+#include "SkDrawLooper.h"
+#include "SkMetaData.h"
+#include "SkPathOps.h"
+#include "SkPicture.h"
+#include "SkRasterClip.h"
+#include "SkRRect.h"
+#include "SkScalarCompare.h"
+#include "SkSurface_Base.h"
+#include "SkTemplates.h"
+#include "SkTextFormatParams.h"
+#include "SkTLazy.h"
+#include "SkUtils.h"
+
+SK_DEFINE_INST_COUNT(SkBounder)
+SK_DEFINE_INST_COUNT(SkCanvas)
+SK_DEFINE_INST_COUNT(SkDrawFilter)
+
+// experimental for faster tiled drawing...
+//#define SK_ENABLE_CLIP_QUICKREJECT
+
+//#define SK_TRACE_SAVERESTORE
+
+#ifdef SK_TRACE_SAVERESTORE
+ static int gLayerCounter;
+ static void inc_layer() { ++gLayerCounter; printf("----- inc layer %d\n", gLayerCounter); }
+ static void dec_layer() { --gLayerCounter; printf("----- dec layer %d\n", gLayerCounter); }
+
+ static int gRecCounter;
+ static void inc_rec() { ++gRecCounter; printf("----- inc rec %d\n", gRecCounter); }
+ static void dec_rec() { --gRecCounter; printf("----- dec rec %d\n", gRecCounter); }
+
+ static int gCanvasCounter;
+ static void inc_canvas() { ++gCanvasCounter; printf("----- inc canvas %d\n", gCanvasCounter); }
+ static void dec_canvas() { --gCanvasCounter; printf("----- dec canvas %d\n", gCanvasCounter); }
+#else
+ #define inc_layer()
+ #define dec_layer()
+ #define inc_rec()
+ #define dec_rec()
+ #define inc_canvas()
+ #define dec_canvas()
+#endif
+
+#ifdef SK_DEBUG
+#include "SkPixelRef.h"
+
+/*
+ * Some pixelref subclasses can support being "locked" from another thread
+ * during the lock-scope of skia calling them. In these instances, this balance
+ * check will fail, but may not be indicative of a problem, so we allow a build
+ * flag to disable this check.
+ *
+ * Potentially another fix would be to have a (debug-only) virtual or flag on
+ * pixelref, which could tell us at runtime if this check is valid. That would
+ * eliminate the need for this heavy-handed build check.
+ */
+#ifdef SK_DISABLE_PIXELREF_LOCKCOUNT_BALANCE_CHECK
+class AutoCheckLockCountBalance {
+public:
+ AutoCheckLockCountBalance(const SkBitmap&) { /* do nothing */ }
+};
+#else
+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;
+};
+#endif
+
+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() {
+ if (fSurfaceBase) {
+ fSurfaceBase->aboutToDraw(SkSurface::kRetain_ContentChangeMode);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* This is the record we keep for each SkDevice that the user installs.
+ The clip/matrix/proc are fields that reflect the top of the save/restore
+ stack. Whenever the canvas changes, it marks a dirty flag, and then before
+ these are used (assuming we're not on a layer) we rebuild these cache
+ values: they reflect the top of the save stack, but translated and clipped
+ by the device's XY offset and bitmap-bounds.
+*/
+struct DeviceCM {
+ DeviceCM* fNext;
+ SkDevice* fDevice;
+ SkRasterClip fClip;
+ const SkMatrix* fMatrix;
+ SkPaint* fPaint; // may be null (in the future)
+
+ DeviceCM(SkDevice* device, int x, int y, const SkPaint* paint, SkCanvas* canvas)
+ : fNext(NULL) {
+ if (NULL != device) {
+ device->ref();
+ device->onAttachToCanvas(canvas);
+ }
+ fDevice = device;
+ fPaint = paint ? SkNEW_ARGS(SkPaint, (*paint)) : NULL;
+ }
+
+ ~DeviceCM() {
+ if (NULL != fDevice) {
+ fDevice->onDetachFromCanvas();
+ fDevice->unref();
+ }
+ SkDELETE(fPaint);
+ }
+
+ void updateMC(const SkMatrix& totalMatrix, const SkRasterClip& totalClip,
+ const SkClipStack& clipStack, SkRasterClip* updateClip) {
+ int x = fDevice->getOrigin().x();
+ int y = fDevice->getOrigin().y();
+ int width = fDevice->width();
+ int height = fDevice->height();
+
+ if ((x | y) == 0) {
+ fMatrix = &totalMatrix;
+ fClip = totalClip;
+ } else {
+ fMatrixStorage = totalMatrix;
+ fMatrixStorage.postTranslate(SkIntToScalar(-x),
+ SkIntToScalar(-y));
+ fMatrix = &fMatrixStorage;
+
+ totalClip.translate(-x, -y, &fClip);
+ }
+
+ fClip.op(SkIRect::MakeWH(width, height), SkRegion::kIntersect_Op);
+
+ // intersect clip, but don't translate it (yet)
+
+ if (updateClip) {
+ updateClip->op(SkIRect::MakeXYWH(x, y, width, height),
+ SkRegion::kDifference_Op);
+ }
+
+ fDevice->setMatrixClip(*fMatrix, fClip.forceGetBW(), clipStack);
+
+#ifdef SK_DEBUG
+ if (!fClip.isEmpty()) {
+ SkIRect deviceR;
+ deviceR.set(0, 0, width, height);
+ SkASSERT(deviceR.contains(fClip.getBounds()));
+ }
+#endif
+ }
+
+private:
+ SkMatrix fMatrixStorage;
+};
+
+/* This is the record we keep for each save/restore level in the stack.
+ Since a level optionally copies the matrix and/or stack, we have pointers
+ for these fields. If the value is copied for this level, the copy is
+ stored in the ...Storage field, and the pointer points to that. If the
+ value is not copied for this level, we ignore ...Storage, and just point
+ at the corresponding value in the previous level in the stack.
+*/
+class SkCanvas::MCRec {
+public:
+ MCRec* fNext;
+ SkMatrix* fMatrix; // points to either fMatrixStorage or prev MCRec
+ SkRasterClip* fRasterClip; // points to either fRegionStorage or prev MCRec
+ SkDrawFilter* fFilter; // the current filter (or null)
+
+ DeviceCM* fLayer;
+ /* If there are any layers in the stack, this points to the top-most
+ one that is at or below this level in the stack (so we know what
+ bitmap/device to draw into from this level. This value is NOT
+ reference counted, since the real owner is either our fLayer field,
+ or a previous one in a lower level.)
+ */
+ DeviceCM* fTopLayer;
+
+ MCRec(const MCRec* prev, int flags) {
+ if (NULL != prev) {
+ if (flags & SkCanvas::kMatrix_SaveFlag) {
+ fMatrixStorage = *prev->fMatrix;
+ fMatrix = &fMatrixStorage;
+ } else {
+ fMatrix = prev->fMatrix;
+ }
+
+ if (flags & SkCanvas::kClip_SaveFlag) {
+ fRasterClipStorage = *prev->fRasterClip;
+ fRasterClip = &fRasterClipStorage;
+ } else {
+ fRasterClip = prev->fRasterClip;
+ }
+
+ fFilter = prev->fFilter;
+ SkSafeRef(fFilter);
+
+ fTopLayer = prev->fTopLayer;
+ } else { // no prev
+ fMatrixStorage.reset();
+
+ fMatrix = &fMatrixStorage;
+ fRasterClip = &fRasterClipStorage;
+ fFilter = NULL;
+ fTopLayer = NULL;
+ }
+ fLayer = NULL;
+
+ // don't bother initializing fNext
+ inc_rec();
+ }
+ ~MCRec() {
+ SkSafeUnref(fFilter);
+ SkDELETE(fLayer);
+ dec_rec();
+ }
+
+private:
+ SkMatrix fMatrixStorage;
+ SkRasterClip fRasterClipStorage;
+};
+
+class SkDrawIter : public SkDraw {
+public:
+ SkDrawIter(SkCanvas* canvas, bool skipEmptyClips = true) {
+ canvas = canvas->canvasForDrawIter();
+ fCanvas = canvas;
+ canvas->updateDeviceCMCache();
+
+ fClipStack = &canvas->fClipStack;
+ fBounder = canvas->getBounder();
+ fCurrLayer = canvas->fMCRec->fTopLayer;
+ fSkipEmptyClips = skipEmptyClips;
+ }
+
+ bool next() {
+ // skip over recs with empty clips
+ if (fSkipEmptyClips) {
+ while (fCurrLayer && fCurrLayer->fClip.isEmpty()) {
+ fCurrLayer = fCurrLayer->fNext;
+ }
+ }
+
+ const DeviceCM* rec = fCurrLayer;
+ if (rec && rec->fDevice) {
+
+ fMatrix = rec->fMatrix;
+ fClip = &((SkRasterClip*)&rec->fClip)->forceGetBW();
+ fRC = &rec->fClip;
+ fDevice = rec->fDevice;
+ fBitmap = &fDevice->accessBitmap(true);
+ fPaint = rec->fPaint;
+ SkDEBUGCODE(this->validate();)
+
+ fCurrLayer = rec->fNext;
+ if (fBounder) {
+ fBounder->setClip(fClip);
+ }
+ // fCurrLayer may be NULL now
+
+ return true;
+ }
+ return false;
+ }
+
+ SkDevice* getDevice() const { return fDevice; }
+ int getX() const { return fDevice->getOrigin().x(); }
+ int getY() const { return fDevice->getOrigin().y(); }
+ const SkMatrix& getMatrix() const { return *fMatrix; }
+ const SkRegion& getClip() const { return *fClip; }
+ const SkPaint* getPaint() const { return fPaint; }
+
+private:
+ SkCanvas* fCanvas;
+ const DeviceCM* fCurrLayer;
+ const SkPaint* fPaint; // May be null.
+ SkBool8 fSkipEmptyClips;
+
+ typedef SkDraw INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class AutoDrawLooper {
+public:
+ AutoDrawLooper(SkCanvas* canvas, const SkPaint& paint,
+ bool skipLayerForImageFilter = false) : fOrigPaint(paint) {
+ fCanvas = canvas;
+ fLooper = paint.getLooper();
+ fFilter = canvas->getDrawFilter();
+ fPaint = NULL;
+ fSaveCount = canvas->getSaveCount();
+ fDoClearImageFilter = false;
+ fDone = false;
+
+ if (!skipLayerForImageFilter && fOrigPaint.getImageFilter()) {
+ SkPaint tmp;
+ tmp.setImageFilter(fOrigPaint.getImageFilter());
+ // it would be nice if we had a guess at the bounds, instead of null
+ (void)canvas->internalSaveLayer(NULL, &tmp,
+ SkCanvas::kARGB_ClipLayer_SaveFlag, true);
+ // we'll clear the imageFilter for the actual draws in next(), so
+ // it will only be applied during the restore().
+ fDoClearImageFilter = true;
+ }
+
+ if (fLooper) {
+ fLooper->init(canvas);
+ fIsSimple = false;
+ } else {
+ // can we be marked as simple?
+ fIsSimple = !fFilter && !fDoClearImageFilter;
+ }
+ }
+
+ ~AutoDrawLooper() {
+ if (fDoClearImageFilter) {
+ fCanvas->internalRestore();
+ }
+ SkASSERT(fCanvas->getSaveCount() == fSaveCount);
+ }
+
+ const SkPaint& paint() const {
+ SkASSERT(fPaint);
+ return *fPaint;
+ }
+
+ bool next(SkDrawFilter::Type drawType) {
+ if (fDone) {
+ return false;
+ } else if (fIsSimple) {
+ fDone = true;
+ fPaint = &fOrigPaint;
+ return !fPaint->nothingToDraw();
+ } else {
+ return this->doNext(drawType);
+ }
+ }
+
+private:
+ SkLazyPaint fLazyPaint;
+ SkCanvas* fCanvas;
+ const SkPaint& fOrigPaint;
+ SkDrawLooper* fLooper;
+ SkDrawFilter* fFilter;
+ const SkPaint* fPaint;
+ int fSaveCount;
+ bool fDoClearImageFilter;
+ bool fDone;
+ bool fIsSimple;
+
+ bool doNext(SkDrawFilter::Type drawType);
+};
+
+bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
+ fPaint = NULL;
+ SkASSERT(!fIsSimple);
+ SkASSERT(fLooper || fFilter || fDoClearImageFilter);
+
+ SkPaint* paint = fLazyPaint.set(fOrigPaint);
+
+ if (fDoClearImageFilter) {
+ paint->setImageFilter(NULL);
+ }
+
+ if (fLooper && !fLooper->next(fCanvas, paint)) {
+ fDone = true;
+ return false;
+ }
+ if (fFilter) {
+ if (!fFilter->filter(paint, drawType)) {
+ fDone = true;
+ return false;
+ }
+ if (NULL == fLooper) {
+ // no looper means we only draw once
+ fDone = true;
+ }
+ }
+ fPaint = paint;
+
+ // if we only came in here for the imagefilter, mark us as done
+ if (!fLooper && !fFilter) {
+ fDone = true;
+ }
+
+ // call this after any possible paint modifiers
+ if (fPaint->nothingToDraw()) {
+ fPaint = NULL;
+ return false;
+ }
+ return true;
+}
+
+/* Stack helper for managing a SkBounder. In the destructor, if we were
+ given a bounder, we call its commit() method, signifying that we are
+ done accumulating bounds for that draw.
+*/
+class SkAutoBounderCommit {
+public:
+ SkAutoBounderCommit(SkBounder* bounder) : fBounder(bounder) {}
+ ~SkAutoBounderCommit() {
+ if (NULL != fBounder) {
+ fBounder->commit();
+ }
+ }
+private:
+ SkBounder* fBounder;
+};
+
+#include "SkColorPriv.h"
+
+////////// macros to place around the internal draw calls //////////////////
+
+#define LOOPER_BEGIN_DRAWDEVICE(paint, type) \
+ this->predrawNotify(); \
+ AutoDrawLooper looper(this, paint, true); \
+ while (looper.next(type)) { \
+ SkAutoBounderCommit ac(fBounder); \
+ SkDrawIter iter(this);
+
+#define LOOPER_BEGIN(paint, type) \
+ this->predrawNotify(); \
+ AutoDrawLooper looper(this, paint); \
+ while (looper.next(type)) { \
+ SkAutoBounderCommit ac(fBounder); \
+ SkDrawIter iter(this);
+
+#define LOOPER_END }
+
+////////////////////////////////////////////////////////////////////////////
+
+SkDevice* SkCanvas::init(SkDevice* device) {
+ fBounder = NULL;
+ fLocalBoundsCompareType.setEmpty();
+ fLocalBoundsCompareTypeDirty = true;
+ fAllowSoftClip = true;
+ fAllowSimplifyClip = false;
+ fDeviceCMDirty = false;
+ fSaveLayerCount = 0;
+ fMetaData = NULL;
+
+ fMCRec = (MCRec*)fMCStack.push_back();
+ new (fMCRec) MCRec(NULL, 0);
+
+ fMCRec->fLayer = SkNEW_ARGS(DeviceCM, (NULL, 0, 0, NULL, NULL));
+ fMCRec->fTopLayer = fMCRec->fLayer;
+ fMCRec->fNext = NULL;
+
+ fSurfaceBase = NULL;
+
+ return this->setDevice(device);
+}
+
+SkCanvas::SkCanvas()
+: fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) {
+ inc_canvas();
+
+ this->init(NULL);
+}
+
+SkCanvas::SkCanvas(SkDevice* device)
+ : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) {
+ inc_canvas();
+
+ this->init(device);
+}
+
+SkCanvas::SkCanvas(const SkBitmap& bitmap)
+ : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) {
+ inc_canvas();
+
+ this->init(SkNEW_ARGS(SkDevice, (bitmap)))->unref();
+}
+
+SkCanvas::~SkCanvas() {
+ // free up the contents of our deque
+ this->restoreToCount(1); // restore everything but the last
+ SkASSERT(0 == fSaveLayerCount);
+
+ this->internalRestore(); // restore the last, since we're going away
+
+ SkSafeUnref(fBounder);
+ SkDELETE(fMetaData);
+
+ dec_canvas();
+}
+
+SkBounder* SkCanvas::setBounder(SkBounder* bounder) {
+ SkRefCnt_SafeAssign(fBounder, bounder);
+ return bounder;
+}
+
+SkDrawFilter* SkCanvas::getDrawFilter() const {
+ return fMCRec->fFilter;
+}
+
+SkDrawFilter* SkCanvas::setDrawFilter(SkDrawFilter* filter) {
+ SkRefCnt_SafeAssign(fMCRec->fFilter, filter);
+ return filter;
+}
+
+SkMetaData& SkCanvas::getMetaData() {
+ // metadata users are rare, so we lazily allocate it. If that changes we
+ // can decide to just make it a field in the device (rather than a ptr)
+ if (NULL == fMetaData) {
+ fMetaData = new SkMetaData;
+ }
+ return *fMetaData;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::flush() {
+ SkDevice* device = this->getDevice();
+ if (device) {
+ device->flush();
+ }
+}
+
+SkISize SkCanvas::getDeviceSize() const {
+ SkDevice* d = this->getDevice();
+ return d ? SkISize::Make(d->width(), d->height()) : SkISize::Make(0, 0);
+}
+
+SkDevice* SkCanvas::getDevice() const {
+ // return root device
+ MCRec* rec = (MCRec*) fMCStack.front();
+ SkASSERT(rec && rec->fLayer);
+ return rec->fLayer->fDevice;
+}
+
+SkDevice* SkCanvas::getTopDevice(bool updateMatrixClip) const {
+ if (updateMatrixClip) {
+ const_cast<SkCanvas*>(this)->updateDeviceCMCache();
+ }
+ return fMCRec->fTopLayer->fDevice;
+}
+
+SkDevice* SkCanvas::setDevice(SkDevice* device) {
+ // return root device
+ SkDeque::F2BIter iter(fMCStack);
+ MCRec* rec = (MCRec*)iter.next();
+ SkASSERT(rec && rec->fLayer);
+ SkDevice* rootDevice = rec->fLayer->fDevice;
+
+ if (rootDevice == device) {
+ return device;
+ }
+
+ if (device) {
+ device->onAttachToCanvas(this);
+ }
+ if (rootDevice) {
+ rootDevice->onDetachFromCanvas();
+ }
+
+ SkRefCnt_SafeAssign(rec->fLayer->fDevice, device);
+ rootDevice = device;
+
+ fDeviceCMDirty = true;
+
+ /* Now we update our initial region to have the bounds of the new device,
+ and then intersect all of the clips in our stack with these bounds,
+ to ensure that we can't draw outside of the device's bounds (and trash
+ memory).
+
+ NOTE: this is only a partial-fix, since if the new device is larger than
+ the previous one, we don't know how to "enlarge" the clips in our stack,
+ so drawing may be artificially restricted. Without keeping a history of
+ all calls to canvas->clipRect() and canvas->clipPath(), we can't exactly
+ reconstruct the correct clips, so this approximation will have to do.
+ The caller really needs to restore() back to the base if they want to
+ accurately take advantage of the new device bounds.
+ */
+
+ SkIRect bounds;
+ if (device) {
+ bounds.set(0, 0, device->width(), device->height());
+ } else {
+ bounds.setEmpty();
+ }
+ // now jam our 1st clip to be bounds, and intersect the rest with that
+ rec->fRasterClip->setRect(bounds);
+ while ((rec = (MCRec*)iter.next()) != NULL) {
+ (void)rec->fRasterClip->op(bounds, SkRegion::kIntersect_Op);
+ }
+
+ return device;
+}
+
+bool SkCanvas::readPixels(SkBitmap* bitmap,
+ int x, int y,
+ Config8888 config8888) {
+ SkDevice* device = this->getDevice();
+ if (!device) {
+ return false;
+ }
+ return device->readPixels(bitmap, x, y, config8888);
+}
+
+bool SkCanvas::readPixels(const SkIRect& srcRect, SkBitmap* bitmap) {
+ SkDevice* device = this->getDevice();
+ if (!device) {
+ return false;
+ }
+
+ SkIRect bounds;
+ bounds.set(0, 0, device->width(), device->height());
+ if (!bounds.intersect(srcRect)) {
+ return false;
+ }
+
+ SkBitmap tmp;
+ tmp.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(),
+ bounds.height());
+ if (this->readPixels(&tmp, bounds.fLeft, bounds.fTop)) {
+ bitmap->swap(tmp);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void SkCanvas::writePixels(const SkBitmap& bitmap, int x, int y,
+ Config8888 config8888) {
+ SkDevice* device = this->getDevice();
+ if (device) {
+ if (SkIRect::Intersects(SkIRect::MakeSize(this->getDeviceSize()),
+ SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()))) {
+ device->accessBitmap(true);
+ device->writePixels(bitmap, x, y, config8888);
+ }
+ }
+}
+
+SkCanvas* SkCanvas::canvasForDrawIter() {
+ return this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::updateDeviceCMCache() {
+ if (fDeviceCMDirty) {
+ const SkMatrix& totalMatrix = this->getTotalMatrix();
+ const SkRasterClip& totalClip = *fMCRec->fRasterClip;
+ DeviceCM* layer = fMCRec->fTopLayer;
+
+ if (NULL == layer->fNext) { // only one layer
+ layer->updateMC(totalMatrix, totalClip, fClipStack, NULL);
+ } else {
+ SkRasterClip clip(totalClip);
+ do {
+ layer->updateMC(totalMatrix, clip, fClipStack, &clip);
+ } while ((layer = layer->fNext) != NULL);
+ }
+ fDeviceCMDirty = false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkCanvas::internalSave(SaveFlags flags) {
+ int saveCount = this->getSaveCount(); // record this before the actual save
+
+ MCRec* newTop = (MCRec*)fMCStack.push_back();
+ new (newTop) MCRec(fMCRec, flags); // balanced in restore()
+
+ newTop->fNext = fMCRec;
+ fMCRec = newTop;
+
+ fClipStack.save();
+ SkASSERT(fClipStack.getSaveCount() == this->getSaveCount() - 1);
+
+ return saveCount;
+}
+
+int SkCanvas::save(SaveFlags flags) {
+ // call shared impl
+ return this->internalSave(flags);
+}
+
+#define C32MASK (1 << SkBitmap::kARGB_8888_Config)
+#define C16MASK (1 << SkBitmap::kRGB_565_Config)
+#define C8MASK (1 << SkBitmap::kA8_Config)
+
+static SkBitmap::Config resolve_config(SkCanvas* canvas,
+ const SkIRect& bounds,
+ SkCanvas::SaveFlags flags,
+ bool* isOpaque) {
+ *isOpaque = (flags & SkCanvas::kHasAlphaLayer_SaveFlag) == 0;
+
+#if 0
+ // loop through and union all the configs we may draw into
+ uint32_t configMask = 0;
+ for (int i = canvas->countLayerDevices() - 1; i >= 0; --i)
+ {
+ SkDevice* device = canvas->getLayerDevice(i);
+ if (device->intersects(bounds))
+ configMask |= 1 << device->config();
+ }
+
+ // if the caller wants alpha or fullcolor, we can't return 565
+ if (flags & (SkCanvas::kFullColorLayer_SaveFlag |
+ SkCanvas::kHasAlphaLayer_SaveFlag))
+ configMask &= ~C16MASK;
+
+ switch (configMask) {
+ case C8MASK: // if we only have A8, return that
+ return SkBitmap::kA8_Config;
+
+ case C16MASK: // if we only have 565, return that
+ return SkBitmap::kRGB_565_Config;
+
+ default:
+ return SkBitmap::kARGB_8888_Config; // default answer
+ }
+#else
+ return SkBitmap::kARGB_8888_Config; // default answer
+#endif
+}
+
+static bool bounds_affects_clip(SkCanvas::SaveFlags flags) {
+ return (flags & SkCanvas::kClipToLayer_SaveFlag) != 0;
+}
+
+bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveFlags flags,
+ SkIRect* intersection) {
+ SkIRect clipBounds;
+ if (!this->getClipDeviceBounds(&clipBounds)) {
+ return false;
+ }
+ SkIRect ir;
+ if (NULL != bounds) {
+ SkRect r;
+
+ this->getTotalMatrix().mapRect(&r, *bounds);
+ r.roundOut(&ir);
+ // early exit if the layer's bounds are clipped out
+ if (!ir.intersect(clipBounds)) {
+ if (bounds_affects_clip(flags)) {
+ fMCRec->fRasterClip->setEmpty();
+ }
+ return false;
+ }
+ } else { // no user bounds, so just use the clip
+ ir = clipBounds;
+ }
+
+ fClipStack.clipDevRect(ir, SkRegion::kIntersect_Op);
+
+ // early exit if the clip is now empty
+ if (bounds_affects_clip(flags) &&
+ !fMCRec->fRasterClip->op(ir, SkRegion::kIntersect_Op)) {
+ return false;
+ }
+
+ if (intersection) {
+ *intersection = ir;
+ }
+ return true;
+}
+
+int SkCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ return this->internalSaveLayer(bounds, paint, flags, false);
+}
+
+int SkCanvas::internalSaveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags, bool justForImageFilter) {
+ // do this before we create the layer. We don't call the public save() since
+ // that would invoke a possibly overridden virtual
+ int count = this->internalSave(flags);
+
+ fDeviceCMDirty = true;
+
+ SkIRect ir;
+ if (!this->clipRectBounds(bounds, flags, &ir)) {
+ return count;
+ }
+
+ // Kill the imagefilter if our device doesn't allow it
+ SkLazyPaint lazyP;
+ if (paint && paint->getImageFilter()) {
+ if (!this->getTopDevice()->allowImageFilter(paint->getImageFilter())) {
+ if (justForImageFilter) {
+ // early exit if the layer was just for the imageFilter
+ return count;
+ }
+ SkPaint* p = lazyP.set(*paint);
+ p->setImageFilter(NULL);
+ paint = p;
+ }
+ }
+
+ bool isOpaque;
+ SkBitmap::Config config = resolve_config(this, ir, flags, &isOpaque);
+
+ SkDevice* device;
+ if (paint && paint->getImageFilter()) {
+ device = this->createCompatibleDevice(config, ir.width(), ir.height(),
+ isOpaque);
+ } else {
+ device = this->createLayerDevice(config, ir.width(), ir.height(),
+ isOpaque);
+ }
+ if (NULL == device) {
+ SkDebugf("Unable to create device for layer.");
+ return count;
+ }
+
+ device->setOrigin(ir.fLeft, ir.fTop);
+ DeviceCM* layer = SkNEW_ARGS(DeviceCM, (device, ir.fLeft, ir.fTop, paint, this));
+ device->unref();
+
+ layer->fNext = fMCRec->fTopLayer;
+ fMCRec->fLayer = layer;
+ fMCRec->fTopLayer = layer; // this field is NOT an owner of layer
+
+ fSaveLayerCount += 1;
+ return count;
+}
+
+int SkCanvas::saveLayerAlpha(const SkRect* bounds, U8CPU alpha,
+ SaveFlags flags) {
+ if (0xFF == alpha) {
+ return this->saveLayer(bounds, NULL, flags);
+ } else {
+ SkPaint tmpPaint;
+ tmpPaint.setAlpha(alpha);
+ return this->saveLayer(bounds, &tmpPaint, flags);
+ }
+}
+
+void SkCanvas::restore() {
+ // check for underflow
+ if (fMCStack.count() > 1) {
+ this->internalRestore();
+ }
+}
+
+void SkCanvas::internalRestore() {
+ SkASSERT(fMCStack.count() != 0);
+
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+
+ fClipStack.restore();
+ // reserve our layer (if any)
+ DeviceCM* layer = fMCRec->fLayer; // may be null
+ // now detach it from fMCRec so we can pop(). Gets freed after its drawn
+ fMCRec->fLayer = NULL;
+
+ // now do the normal restore()
+ fMCRec->~MCRec(); // balanced in save()
+ fMCStack.pop_back();
+ fMCRec = (MCRec*)fMCStack.back();
+
+ /* Time to draw the layer's offscreen. We can't call the public drawSprite,
+ since if we're being recorded, we don't want to record this (the
+ recorder will have already recorded the restore).
+ */
+ if (NULL != layer) {
+ if (layer->fNext) {
+ const SkIPoint& origin = layer->fDevice->getOrigin();
+ this->internalDrawDevice(layer->fDevice, origin.x(), origin.y(),
+ layer->fPaint);
+ // reset this, since internalDrawDevice will have set it to true
+ fDeviceCMDirty = true;
+
+ SkASSERT(fSaveLayerCount > 0);
+ fSaveLayerCount -= 1;
+ }
+ SkDELETE(layer);
+ }
+
+ SkASSERT(fClipStack.getSaveCount() == this->getSaveCount() - 1);
+}
+
+int SkCanvas::getSaveCount() const {
+ return fMCStack.count();
+}
+
+void SkCanvas::restoreToCount(int count) {
+ // sanity check
+ if (count < 1) {
+ count = 1;
+ }
+
+ int n = this->getSaveCount() - count;
+ for (int i = 0; i < n; ++i) {
+ this->restore();
+ }
+}
+
+bool SkCanvas::isDrawingToLayer() const {
+ return fSaveLayerCount > 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+// can't draw it if its empty, or its too big for a fixed-point width or height
+static bool reject_bitmap(const SkBitmap& bitmap) {
+ return bitmap.width() <= 0 || bitmap.height() <= 0;
+}
+
+void SkCanvas::internalDrawBitmap(const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint* paint) {
+ if (reject_bitmap(bitmap)) {
+ return;
+ }
+
+ SkLazyPaint lazy;
+ if (NULL == paint) {
+ paint = lazy.init();
+ }
+
+ SkDEBUGCODE(bitmap.validate();)
+ CHECK_LOCKCOUNT_BALANCE(bitmap);
+
+ LOOPER_BEGIN(*paint, SkDrawFilter::kBitmap_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawBitmap(iter, bitmap, matrix, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+void SkCanvas::internalDrawDevice(SkDevice* srcDev, int x, int y,
+ const SkPaint* paint) {
+ SkPaint tmp;
+ if (NULL == paint) {
+ tmp.setDither(true);
+ paint = &tmp;
+ }
+
+ LOOPER_BEGIN_DRAWDEVICE(*paint, SkDrawFilter::kBitmap_Type)
+ while (iter.next()) {
+ SkDevice* dstDev = iter.fDevice;
+ paint = &looper.paint();
+ SkImageFilter* filter = paint->getImageFilter();
+ SkIPoint pos = { x - iter.getX(), y - iter.getY() };
+ if (filter && !dstDev->canHandleImageFilter(filter)) {
+ SkDeviceImageFilterProxy proxy(dstDev);
+ SkBitmap dst;
+ const SkBitmap& src = srcDev->accessBitmap(false);
+ if (filter->filterImage(&proxy, src, *iter.fMatrix, &dst, &pos)) {
+ SkPaint tmpUnfiltered(*paint);
+ tmpUnfiltered.setImageFilter(NULL);
+ dstDev->drawSprite(iter, dst, pos.x(), pos.y(), tmpUnfiltered);
+ }
+ } else {
+ dstDev->drawDevice(iter, srcDev, pos.x(), pos.y(), *paint);
+ }
+ }
+ LOOPER_END
+}
+
+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;
+ }
+
+ SkPaint tmp;
+ if (NULL == paint) {
+ paint = &tmp;
+ }
+
+ LOOPER_BEGIN_DRAWDEVICE(*paint, SkDrawFilter::kBitmap_Type)
+
+ while (iter.next()) {
+ paint = &looper.paint();
+ SkImageFilter* filter = paint->getImageFilter();
+ SkIPoint pos = { x - iter.getX(), y - iter.getY() };
+ if (filter && !iter.fDevice->canHandleImageFilter(filter)) {
+ SkDeviceImageFilterProxy proxy(iter.fDevice);
+ SkBitmap dst;
+ if (filter->filterImage(&proxy, bitmap, *iter.fMatrix,
+ &dst, &pos)) {
+ SkPaint tmpUnfiltered(*paint);
+ tmpUnfiltered.setImageFilter(NULL);
+ iter.fDevice->drawSprite(iter, dst, pos.x(), pos.y(),
+ tmpUnfiltered);
+ }
+ } else {
+ iter.fDevice->drawSprite(iter, bitmap, pos.x(), pos.y(), *paint);
+ }
+ }
+ LOOPER_END
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool SkCanvas::translate(SkScalar dx, SkScalar dy) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ return fMCRec->fMatrix->preTranslate(dx, dy);
+}
+
+bool SkCanvas::scale(SkScalar sx, SkScalar sy) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ return fMCRec->fMatrix->preScale(sx, sy);
+}
+
+bool SkCanvas::rotate(SkScalar degrees) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ return fMCRec->fMatrix->preRotate(degrees);
+}
+
+bool SkCanvas::skew(SkScalar sx, SkScalar sy) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ return fMCRec->fMatrix->preSkew(sx, sy);
+}
+
+bool SkCanvas::concat(const SkMatrix& matrix) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ return fMCRec->fMatrix->preConcat(matrix);
+}
+
+void SkCanvas::setMatrix(const SkMatrix& matrix) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ *fMCRec->fMatrix = matrix;
+}
+
+// this is not virtual, so it must call a virtual method so that subclasses
+// will see its action
+void SkCanvas::resetMatrix() {
+ SkMatrix matrix;
+
+ matrix.reset();
+ this->setMatrix(matrix);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool SkCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+#ifdef SK_ENABLE_CLIP_QUICKREJECT
+ if (SkRegion::kIntersect_Op == op) {
+ if (fMCRec->fRasterClip->isEmpty()) {
+ return false;
+ }
+
+ if (this->quickReject(rect)) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+
+ fClipStack.clipEmpty();
+ return fMCRec->fRasterClip->setEmpty();
+ }
+ }
+#endif
+
+ AutoValidateClip avc(this);
+
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ doAA &= fAllowSoftClip;
+
+ if (fMCRec->fMatrix->rectStaysRect()) {
+ // for these simpler matrices, we can stay a rect ever after applying
+ // the matrix. This means we don't have to a) make a path, and b) tell
+ // the region code to scan-convert the path, only to discover that it
+ // is really just a rect.
+ SkRect r;
+
+ fMCRec->fMatrix->mapRect(&r, rect);
+ fClipStack.clipDevRect(r, op, doAA);
+ return fMCRec->fRasterClip->op(r, op, doAA);
+ } else {
+ // since we're rotate or some such thing, we convert the rect to a path
+ // and clip against that, since it can handle any matrix. However, to
+ // avoid recursion in the case where we are subclassed (e.g. Pictures)
+ // we explicitly call "our" version of clipPath.
+ SkPath path;
+
+ path.addRect(rect);
+ return this->SkCanvas::clipPath(path, op, doAA);
+ }
+}
+
+static bool clipPathHelper(const SkCanvas* canvas, SkRasterClip* currClip,
+ const SkPath& devPath, SkRegion::Op op, bool doAA) {
+ // base is used to limit the size (and therefore memory allocation) of the
+ // region that results from scan converting devPath.
+ SkRegion base;
+
+ if (SkRegion::kIntersect_Op == op) {
+ // since we are intersect, we can do better (tighter) with currRgn's
+ // bounds, than just using the device. However, if currRgn is complex,
+ // our region blitter may hork, so we do that case in two steps.
+ if (currClip->isRect()) {
+ // FIXME: we should also be able to do this when currClip->isBW(),
+ // but relaxing the test above triggers GM asserts in
+ // SkRgnBuilder::blitH(). We need to investigate what's going on.
+ return currClip->setPath(devPath, currClip->bwRgn(), doAA);
+ } else {
+ base.setRect(currClip->getBounds());
+ SkRasterClip clip;
+ clip.setPath(devPath, base, doAA);
+ return currClip->op(clip, op);
+ }
+ } else {
+ const SkDevice* device = canvas->getDevice();
+ if (!device) {
+ return currClip->setEmpty();
+ }
+
+ base.setRect(0, 0, device->width(), device->height());
+
+ if (SkRegion::kReplace_Op == op) {
+ return currClip->setPath(devPath, base, doAA);
+ } else {
+ SkRasterClip clip;
+ clip.setPath(devPath, base, doAA);
+ return currClip->op(clip, op);
+ }
+ }
+}
+
+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()) {
+ if (fMCRec->fRasterClip->isEmpty()) {
+ return false;
+ }
+
+ if (this->quickReject(path.getBounds())) {
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+
+ fClipStack.clipEmpty();
+ return fMCRec->fRasterClip->setEmpty();
+ }
+ }
+#endif
+
+ AutoValidateClip avc(this);
+
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+ doAA &= fAllowSoftClip;
+
+ SkPath devPath;
+ path.transform(*fMCRec->fMatrix, &devPath);
+
+ // Check if the transfomation, or the original path itself
+ // made us empty. Note this can also happen if we contained NaN
+ // values. computing the bounds detects this, and will set our
+ // bounds to empty if that is the case. (see SkRect::set(pts, count))
+ if (devPath.getBounds().isEmpty()) {
+ // resetting the path will remove any NaN or other wanky values
+ // that might upset our scan converter.
+ devPath.reset();
+ }
+
+ // if we called path.swap() we could avoid a deep copy of this path
+ fClipStack.clipDevPath(devPath, op, doAA);
+
+ if (fAllowSimplifyClip) {
+ devPath.reset();
+ devPath.setFillType(SkPath::kInverseEvenOdd_FillType);
+ const SkClipStack* clipStack = getClipStack();
+ SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
+ const SkClipStack::Element* element;
+ while ((element = iter.next())) {
+ SkClipStack::Element::Type type = element->getType();
+ if (type == SkClipStack::Element::kEmpty_Type) {
+ continue;
+ }
+ SkPath operand;
+ if (type == SkClipStack::Element::kRect_Type) {
+ operand.addRect(element->getRect());
+ } else if (type == SkClipStack::Element::kPath_Type) {
+ operand = element->getPath();
+ } else {
+ SkDEBUGFAIL("Unexpected type.");
+ }
+ SkRegion::Op elementOp = element->getOp();
+ if (elementOp == SkRegion::kReplace_Op) {
+ devPath = operand;
+ } else {
+ Op(devPath, operand, (SkPathOp) elementOp, &devPath);
+ }
+ // if the prev and curr clips disagree about aa -vs- not, favor the aa request.
+ // perhaps we need an API change to avoid this sort of mixed-signals about
+ // clipping.
+ doAA |= element->isAA();
+ }
+ op = SkRegion::kReplace_Op;
+ }
+
+ return clipPathHelper(this, fMCRec->fRasterClip, devPath, op, doAA);
+}
+
+bool SkCanvas::updateClipConservativelyUsingBounds(const SkRect& bounds, SkRegion::Op op,
+ bool inverseFilled) {
+ // This is for updating the clip conservatively using only bounds
+ // information.
+ // Contract:
+ // The current clip must contain the true clip. The true
+ // clip is the clip that would have normally been computed
+ // by calls to clipPath and clipRRect
+ // Objective:
+ // Keep the current clip as small as possible without
+ // breaking the contract, using only clip bounding rectangles
+ // (for performance).
+
+ // N.B.: This *never* calls back through a virtual on canvas, so subclasses
+ // don't have to worry about getting caught in a loop. Thus anywhere
+ // we call a virtual method, we explicitly prefix it with
+ // SkCanvas:: to be sure to call the base-class.
+
+ if (inverseFilled) {
+ switch (op) {
+ case SkRegion::kIntersect_Op:
+ case SkRegion::kDifference_Op:
+ // These ops can only shrink the current clip. So leaving
+ // the clip unchanges conservatively respects the contract.
+ return this->getClipDeviceBounds(NULL);
+ case SkRegion::kUnion_Op:
+ case SkRegion::kReplace_Op:
+ case SkRegion::kReverseDifference_Op:
+ case SkRegion::kXOR_Op:
+ {
+ // These ops can grow the current clip up to the extents of
+ // the input clip, which is inverse filled, so we just set
+ // the current clip to the device bounds.
+ SkRect deviceBounds;
+ SkIRect deviceIBounds;
+ this->getDevice()->getGlobalBounds(&deviceIBounds);
+ deviceBounds = SkRect::MakeFromIRect(deviceIBounds);
+ this->SkCanvas::save(SkCanvas::kMatrix_SaveFlag);
+ // set the clip in device space
+ this->SkCanvas::setMatrix(SkMatrix::I());
+ bool result = this->SkCanvas::clipRect(deviceBounds,
+ SkRegion::kReplace_Op, false);
+ this->SkCanvas::restore(); //pop the matrix, but keep the clip
+ return result;
+ }
+ default:
+ SkASSERT(0); // unhandled op?
+ }
+ } else {
+ // Not inverse filled
+ switch (op) {
+ case SkRegion::kIntersect_Op:
+ case SkRegion::kUnion_Op:
+ case SkRegion::kReplace_Op:
+ return this->SkCanvas::clipRect(bounds, op, false);
+ case SkRegion::kDifference_Op:
+ // Difference can only shrink the current clip.
+ // Leaving clip unchanged conservatively fullfills the contract.
+ return this->getClipDeviceBounds(NULL);
+ case SkRegion::kReverseDifference_Op:
+ // To reverse, we swap in the bounds with a replace op.
+ // As with difference, leave it unchanged.
+ return this->SkCanvas::clipRect(bounds, SkRegion::kReplace_Op, false);
+ case SkRegion::kXOR_Op:
+ // Be conservative, based on (A XOR B) always included in (A union B),
+ // which is always included in (bounds(A) union bounds(B))
+ return this->SkCanvas::clipRect(bounds, SkRegion::kUnion_Op, false);
+ default:
+ SkASSERT(0); // unhandled op?
+ }
+ }
+ return true;
+}
+
+bool SkCanvas::clipRegion(const SkRegion& rgn, SkRegion::Op op) {
+ AutoValidateClip avc(this);
+
+ fDeviceCMDirty = true;
+ fLocalBoundsCompareTypeDirty = true;
+
+ // todo: signal fClipStack that we have a region, and therefore (I guess)
+ // we have to ignore it, and use the region directly?
+ fClipStack.clipDevRect(rgn.getBounds(), op);
+
+ return fMCRec->fRasterClip->op(rgn, op);
+}
+
+#ifdef SK_DEBUG
+void SkCanvas::validateClip() const {
+ // construct clipRgn from the clipstack
+ const SkDevice* device = this->getDevice();
+ if (!device) {
+ SkASSERT(this->getTotalClip().isEmpty());
+ return;
+ }
+
+ SkIRect ir;
+ ir.set(0, 0, device->width(), device->height());
+ SkRasterClip tmpClip(ir);
+
+ SkClipStack::B2TIter iter(fClipStack);
+ 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;
+ }
+ }
+
+#if 0 // enable this locally for testing
+ // now compare against the current rgn
+ const SkRegion& rgn = this->getTotalClip();
+ SkASSERT(rgn == tmpClip);
+#endif
+}
+#endif
+
+void SkCanvas::replayClips(ClipVisitor* visitor) const {
+ SkClipStack::B2TIter iter(fClipStack);
+ 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;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::computeLocalClipBoundsCompareType() const {
+ SkRect r;
+
+ if (!this->getClipBounds(&r)) {
+ fLocalBoundsCompareType.setEmpty();
+ } else {
+ fLocalBoundsCompareType.set(SkScalarToCompareType(r.fLeft),
+ SkScalarToCompareType(r.fTop),
+ SkScalarToCompareType(r.fRight),
+ SkScalarToCompareType(r.fBottom));
+ }
+}
+
+bool SkCanvas::quickReject(const SkRect& rect) const {
+
+ if (!rect.isFinite())
+ return true;
+
+ if (fMCRec->fRasterClip->isEmpty()) {
+ return true;
+ }
+
+ if (fMCRec->fMatrix->hasPerspective()) {
+ SkRect dst;
+ fMCRec->fMatrix->mapRect(&dst, rect);
+ SkIRect idst;
+ dst.roundOut(&idst);
+ return !SkIRect::Intersects(idst, fMCRec->fRasterClip->getBounds());
+ } else {
+ const SkRectCompareType& clipR = this->getLocalClipBoundsCompareType();
+
+ // for speed, do the most likely reject compares first
+ SkScalarCompareType userT = SkScalarToCompareType(rect.fTop);
+ SkScalarCompareType userB = SkScalarToCompareType(rect.fBottom);
+ if (userT >= clipR.fBottom || userB <= clipR.fTop) {
+ return true;
+ }
+ SkScalarCompareType userL = SkScalarToCompareType(rect.fLeft);
+ SkScalarCompareType userR = SkScalarToCompareType(rect.fRight);
+ if (userL >= clipR.fRight || userR <= clipR.fLeft) {
+ return true;
+ }
+ return false;
+ }
+}
+
+bool SkCanvas::quickReject(const SkPath& path) const {
+ return path.isEmpty() || this->quickReject(path.getBounds());
+}
+
+static inline int pinIntForScalar(int x) {
+#ifdef SK_SCALAR_IS_FIXED
+ if (x < SK_MinS16) {
+ x = SK_MinS16;
+ } else if (x > SK_MaxS16) {
+ x = SK_MaxS16;
+ }
+#endif
+ return x;
+}
+
+bool SkCanvas::getClipBounds(SkRect* bounds) const {
+ SkIRect ibounds;
+ if (!getClipDeviceBounds(&ibounds)) {
+ return false;
+ }
+
+ SkMatrix inverse;
+ // if we can't invert the CTM, we can't return local clip bounds
+ if (!fMCRec->fMatrix->invert(&inverse)) {
+ if (bounds) {
+ bounds->setEmpty();
+ }
+ return false;
+ }
+
+ if (NULL != bounds) {
+ SkRect r;
+ // adjust it outwards in case we are antialiasing
+ const int inset = 1;
+
+ // SkRect::iset() will correctly assert if we pass a value out of range
+ // (when SkScalar==fixed), so we pin to legal values. This does not
+ // really returnt the correct answer, but its the best we can do given
+ // that we've promised to return SkRect (even though we support devices
+ // that can be larger than 32K in width or height).
+ r.iset(pinIntForScalar(ibounds.fLeft - inset),
+ pinIntForScalar(ibounds.fTop - inset),
+ pinIntForScalar(ibounds.fRight + inset),
+ pinIntForScalar(ibounds.fBottom + inset));
+ inverse.mapRect(bounds, r);
+ }
+ return true;
+}
+
+bool SkCanvas::getClipDeviceBounds(SkIRect* bounds) const {
+ const SkRasterClip& clip = *fMCRec->fRasterClip;
+ if (clip.isEmpty()) {
+ if (bounds) {
+ bounds->setEmpty();
+ }
+ return false;
+ }
+
+ if (NULL != bounds) {
+ *bounds = clip.getBounds();
+ }
+ return true;
+}
+
+const SkMatrix& SkCanvas::getTotalMatrix() const {
+ return *fMCRec->fMatrix;
+}
+
+SkCanvas::ClipType SkCanvas::getClipType() const {
+ if (fMCRec->fRasterClip->isEmpty()) return kEmpty_ClipType;
+ if (fMCRec->fRasterClip->isRect()) return kRect_ClipType;
+ return kComplex_ClipType;
+}
+
+const SkRegion& SkCanvas::getTotalClip() const {
+ return fMCRec->fRasterClip->forceGetBW();
+}
+
+SkDevice* SkCanvas::createLayerDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque) {
+ SkDevice* device = this->getTopDevice();
+ if (device) {
+ return device->createCompatibleDeviceForSaveLayer(config, width, height,
+ isOpaque);
+ } else {
+ return NULL;
+ }
+}
+
+SkDevice* SkCanvas::createCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque) {
+ SkDevice* device = this->getDevice();
+ if (device) {
+ return device->createCompatibleDevice(config, width, height, isOpaque);
+ } else {
+ return NULL;
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// These are the virtual drawing methods
+//////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::clear(SkColor color) {
+ SkDrawIter iter(this);
+ this->predrawNotify();
+ while (iter.next()) {
+ iter.fDevice->clear(color);
+ }
+}
+
+void SkCanvas::drawPaint(const SkPaint& paint) {
+ this->internalDrawPaint(paint);
+}
+
+void SkCanvas::internalDrawPaint(const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kPaint_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawPaint(iter, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
+ const SkPaint& paint) {
+ if ((long)count <= 0) {
+ return;
+ }
+
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
+ if (paint.canComputeFastBounds()) {
+ SkRect r;
+ // special-case 2 points (common for drawing a single line)
+ if (2 == count) {
+ r.set(pts[0], pts[1]);
+ } else {
+ r.set(pts, count);
+ }
+ SkRect storage;
+ if (this->quickReject(paint.computeFastStrokeBounds(r, &storage))) {
+ return;
+ }
+ }
+
+ SkASSERT(pts != NULL);
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kPoint_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawPoints(iter, mode, count, pts, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+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))) {
+ return;
+ }
+ }
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kRect_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawRect(iter, r, looper.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;
+ }
+ }
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kOval_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawOval(iter, oval, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+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);
+ return;
+ } else if (rrect.isOval()) {
+ // call the non-virtual version
+ this->SkCanvas::drawOval(rrect.getBounds(), paint);
+ return;
+ }
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kRRect_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawRRect(iter, rrect, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+
+void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ CHECK_SHADER_NOSETCONTEXT(paint);
+
+ if (!path.isFinite()) {
+ return;
+ }
+
+ if (!path.isInverseFillType() && paint.canComputeFastBounds()) {
+ SkRect storage;
+ const SkRect& bounds = path.getBounds();
+ if (this->quickReject(paint.computeFastBounds(bounds, &storage))) {
+ return;
+ }
+ }
+ if (path.isEmpty()) {
+ if (path.isInverseFillType()) {
+ this->internalDrawPaint(paint);
+ }
+ return;
+ }
+
+ LOOPER_BEGIN(paint, SkDrawFilter::kPath_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawPath(iter, path, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+void SkCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ SkDEBUGCODE(bitmap.validate();)
+
+ if (NULL == paint || paint->canComputeFastBounds()) {
+ SkRect bounds = {
+ x, y,
+ x + SkIntToScalar(bitmap.width()),
+ y + SkIntToScalar(bitmap.height())
+ };
+ if (paint) {
+ (void)paint->computeFastBounds(bounds, &bounds);
+ }
+ if (this->quickReject(bounds)) {
+ return;
+ }
+ }
+
+ SkMatrix matrix;
+ matrix.setTranslate(x, y);
+ this->internalDrawBitmap(bitmap, matrix, paint);
+}
+
+// this one is non-virtual, so it can be called safely by other canvas apis
+void SkCanvas::internalDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ if (bitmap.width() == 0 || bitmap.height() == 0 || dst.isEmpty()) {
+ return;
+ }
+
+ CHECK_LOCKCOUNT_BALANCE(bitmap);
+
+ if (NULL == paint || paint->canComputeFastBounds()) {
+ SkRect storage;
+ const SkRect* bounds = &dst;
+ if (paint) {
+ bounds = &paint->computeFastBounds(dst, &storage);
+ }
+ if (this->quickReject(*bounds)) {
+ return;
+ }
+ }
+
+ SkLazyPaint lazy;
+ if (NULL == paint) {
+ paint = lazy.init();
+ }
+
+ LOOPER_BEGIN(*paint, SkDrawFilter::kBitmap_Type)
+
+ while (iter.next()) {
+ iter.fDevice->drawBitmapRect(iter, bitmap, src, dst, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+void SkCanvas::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ SkDEBUGCODE(bitmap.validate();)
+ this->internalDrawBitmapRect(bitmap, src, dst, paint);
+}
+
+void SkCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ SkDEBUGCODE(bitmap.validate();)
+ this->internalDrawBitmap(bitmap, matrix, paint);
+}
+
+void SkCanvas::internalDrawBitmapNine(const SkBitmap& bitmap,
+ const SkIRect& center, const SkRect& dst,
+ const SkPaint* paint) {
+ if (NULL == paint || paint->canComputeFastBounds()) {
+ SkRect storage;
+ const SkRect* bounds = &dst;
+ if (paint) {
+ bounds = &paint->computeFastBounds(dst, &storage);
+ }
+ if (this->quickReject(*bounds)) {
+ return;
+ }
+ }
+
+ const int32_t w = bitmap.width();
+ const int32_t h = bitmap.height();
+
+ SkIRect c = center;
+ // pin center to the bounds of the bitmap
+ c.fLeft = SkMax32(0, center.fLeft);
+ c.fTop = SkMax32(0, center.fTop);
+ c.fRight = SkPin32(center.fRight, c.fLeft, w);
+ c.fBottom = SkPin32(center.fBottom, c.fTop, h);
+
+ const SkScalar srcX[4] = {
+ 0, SkIntToScalar(c.fLeft), SkIntToScalar(c.fRight), SkIntToScalar(w)
+ };
+ const SkScalar srcY[4] = {
+ 0, SkIntToScalar(c.fTop), SkIntToScalar(c.fBottom), SkIntToScalar(h)
+ };
+ SkScalar dstX[4] = {
+ dst.fLeft, dst.fLeft + SkIntToScalar(c.fLeft),
+ dst.fRight - SkIntToScalar(w - c.fRight), dst.fRight
+ };
+ SkScalar dstY[4] = {
+ dst.fTop, dst.fTop + SkIntToScalar(c.fTop),
+ dst.fBottom - SkIntToScalar(h - c.fBottom), dst.fBottom
+ };
+
+ if (dstX[1] > dstX[2]) {
+ dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * c.fLeft / (w - c.width());
+ dstX[2] = dstX[1];
+ }
+
+ if (dstY[1] > dstY[2]) {
+ dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * c.fTop / (h - c.height());
+ dstY[2] = dstY[1];
+ }
+
+ for (int y = 0; y < 3; y++) {
+ SkRect s, d;
+
+ s.fTop = srcY[y];
+ s.fBottom = srcY[y+1];
+ d.fTop = dstY[y];
+ d.fBottom = dstY[y+1];
+ for (int x = 0; x < 3; x++) {
+ s.fLeft = srcX[x];
+ s.fRight = srcX[x+1];
+ d.fLeft = dstX[x];
+ d.fRight = dstX[x+1];
+ this->internalDrawBitmapRect(bitmap, &s, d, paint);
+ }
+ }
+}
+
+void SkCanvas::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) {
+ SkDEBUGCODE(bitmap.validate();)
+
+ // Need a device entry-point, so gpu can use a mesh
+ this->internalDrawBitmapNine(bitmap, center, dst, paint);
+}
+
+class SkDeviceFilteredPaint {
+public:
+ SkDeviceFilteredPaint(SkDevice* device, const SkPaint& paint) {
+ SkDevice::TextFlags flags;
+ if (device->filterTextFlags(paint, &flags)) {
+ SkPaint* newPaint = fLazy.set(paint);
+ newPaint->setFlags(flags.fFlags);
+ newPaint->setHinting(flags.fHinting);
+ fPaint = newPaint;
+ } else {
+ fPaint = &paint;
+ }
+ }
+
+ const SkPaint& paint() const { return *fPaint; }
+
+private:
+ const SkPaint* fPaint;
+ SkLazyPaint fLazy;
+};
+
+void SkCanvas::DrawRect(const SkDraw& draw, const SkPaint& paint,
+ const SkRect& r, SkScalar textSize) {
+ if (paint.getStyle() == SkPaint::kFill_Style) {
+ draw.fDevice->drawRect(draw, r, paint);
+ } else {
+ SkPaint p(paint);
+ p.setStrokeWidth(SkScalarMul(textSize, paint.getStrokeWidth()));
+ draw.fDevice->drawRect(draw, r, p);
+ }
+}
+
+void SkCanvas::DrawTextDecorations(const SkDraw& draw, const SkPaint& paint,
+ const char text[], size_t byteLength,
+ SkScalar x, SkScalar y) {
+ SkASSERT(byteLength == 0 || text != NULL);
+
+ // nothing to draw
+ if (text == NULL || byteLength == 0 ||
+ draw.fClip->isEmpty() ||
+ (paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
+ return;
+ }
+
+ SkScalar width = 0;
+ SkPoint start;
+
+ start.set(0, 0); // to avoid warning
+ if (paint.getFlags() & (SkPaint::kUnderlineText_Flag |
+ SkPaint::kStrikeThruText_Flag)) {
+ width = paint.measureText(text, byteLength);
+
+ SkScalar offsetX = 0;
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ offsetX = SkScalarHalf(width);
+ } else if (paint.getTextAlign() == SkPaint::kRight_Align) {
+ offsetX = width;
+ }
+ start.set(x - offsetX, y);
+ }
+
+ if (0 == width) {
+ return;
+ }
+
+ uint32_t flags = paint.getFlags();
+
+ if (flags & (SkPaint::kUnderlineText_Flag |
+ SkPaint::kStrikeThruText_Flag)) {
+ SkScalar textSize = paint.getTextSize();
+ SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
+ SkRect r;
+
+ r.fLeft = start.fX;
+ r.fRight = start.fX + width;
+
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar offset = SkScalarMulAdd(textSize, kStdUnderline_Offset,
+ start.fY);
+ r.fTop = offset;
+ r.fBottom = offset + height;
+ DrawRect(draw, paint, r, textSize);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar offset = SkScalarMulAdd(textSize, kStdStrikeThru_Offset,
+ start.fY);
+ r.fTop = offset;
+ r.fBottom = offset + height;
+ DrawRect(draw, paint, r, textSize);
+ }
+ }
+}
+
+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()) {
+ SkDeviceFilteredPaint dfp(iter.fDevice, looper.paint());
+ iter.fDevice->drawText(iter, text, byteLength, x, y, dfp.paint());
+ DrawTextDecorations(iter, dfp.paint(),
+ static_cast<const char*>(text), byteLength, x, y);
+ }
+
+ LOOPER_END
+}
+
+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()) {
+ SkDeviceFilteredPaint dfp(iter.fDevice, looper.paint());
+ iter.fDevice->drawPosText(iter, text, byteLength, &pos->fX, 0, 2,
+ dfp.paint());
+ }
+
+ LOOPER_END
+}
+
+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()) {
+ SkDeviceFilteredPaint dfp(iter.fDevice, looper.paint());
+ iter.fDevice->drawPosText(iter, text, byteLength, xpos, constY, 1,
+ dfp.paint());
+ }
+
+ LOOPER_END
+}
+
+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()) {
+ iter.fDevice->drawTextOnPath(iter, text, byteLength, path,
+ matrix, looper.paint());
+ }
+
+ LOOPER_END
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+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()) {
+ iter.fDevice->drawPosTextOnPath(iter, text, byteLength, pos,
+ looper.paint(), path, matrix);
+ }
+
+ LOOPER_END
+}
+#endif
+
+void SkCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint verts[], const SkPoint texs[],
+ 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()) {
+ iter.fDevice->drawVertices(iter, vmode, vertexCount, verts, texs,
+ colors, xmode, indices, indexCount,
+ looper.paint());
+ }
+
+ LOOPER_END
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// These methods are NOT virtual, and therefore must call back into virtual
+// methods, rather than actually drawing themselves.
+//////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::drawARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b,
+ SkXfermode::Mode mode) {
+ SkPaint paint;
+
+ paint.setARGB(a, r, g, b);
+ if (SkXfermode::kSrcOver_Mode != mode) {
+ paint.setXfermodeMode(mode);
+ }
+ this->drawPaint(paint);
+}
+
+void SkCanvas::drawColor(SkColor c, SkXfermode::Mode mode) {
+ SkPaint paint;
+
+ paint.setColor(c);
+ if (SkXfermode::kSrcOver_Mode != mode) {
+ paint.setXfermodeMode(mode);
+ }
+ this->drawPaint(paint);
+}
+
+void SkCanvas::drawPoint(SkScalar x, SkScalar y, const SkPaint& paint) {
+ SkPoint pt;
+
+ pt.set(x, y);
+ this->drawPoints(kPoints_PointMode, 1, &pt, paint);
+}
+
+void SkCanvas::drawPoint(SkScalar x, SkScalar y, SkColor color) {
+ SkPoint pt;
+ SkPaint paint;
+
+ pt.set(x, y);
+ paint.setColor(color);
+ this->drawPoints(kPoints_PointMode, 1, &pt, paint);
+}
+
+void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1,
+ const SkPaint& paint) {
+ SkPoint pts[2];
+
+ pts[0].set(x0, y0);
+ pts[1].set(x1, y1);
+ this->drawPoints(kLines_PointMode, 2, pts, paint);
+}
+
+void SkCanvas::drawRectCoords(SkScalar left, SkScalar top,
+ SkScalar right, SkScalar bottom,
+ const SkPaint& paint) {
+ SkRect r;
+
+ r.set(left, top, right, bottom);
+ this->drawRect(r, paint);
+}
+
+void SkCanvas::drawCircle(SkScalar cx, SkScalar cy, SkScalar radius,
+ const SkPaint& paint) {
+ if (radius < 0) {
+ radius = 0;
+ }
+
+ SkRect r;
+ r.set(cx - radius, cy - radius, cx + radius, cy + radius);
+ this->drawOval(r, paint);
+}
+
+void SkCanvas::drawRoundRect(const SkRect& r, SkScalar rx, SkScalar ry,
+ const SkPaint& paint) {
+ if (rx > 0 && ry > 0) {
+ if (paint.canComputeFastBounds()) {
+ SkRect storage;
+ if (this->quickReject(paint.computeFastBounds(r, &storage))) {
+ return;
+ }
+ }
+ SkRRect rrect;
+ rrect.setRectXY(r, rx, ry);
+ this->drawRRect(rrect, paint);
+ } else {
+ this->drawRect(r, paint);
+ }
+}
+
+void SkCanvas::drawArc(const SkRect& oval, SkScalar startAngle,
+ SkScalar sweepAngle, bool useCenter,
+ const SkPaint& paint) {
+ if (SkScalarAbs(sweepAngle) >= SkIntToScalar(360)) {
+ this->drawOval(oval, paint);
+ } else {
+ SkPath path;
+ if (useCenter) {
+ path.moveTo(oval.centerX(), oval.centerY());
+ }
+ path.arcTo(oval, startAngle, sweepAngle, !useCenter);
+ if (useCenter) {
+ path.close();
+ }
+ this->drawPath(path, paint);
+ }
+}
+
+void SkCanvas::drawTextOnPathHV(const void* text, size_t byteLength,
+ const SkPath& path, SkScalar hOffset,
+ SkScalar vOffset, const SkPaint& paint) {
+ SkMatrix matrix;
+
+ matrix.setTranslate(hOffset, vOffset);
+ this->drawTextOnPath(text, byteLength, path, &matrix, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkCanvas::drawPicture(SkPicture& picture) {
+ picture.draw(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+SkCanvas::LayerIter::LayerIter(SkCanvas* canvas, bool skipEmptyClips) {
+ SK_COMPILE_ASSERT(sizeof(fStorage) >= sizeof(SkDrawIter), fStorage_too_small);
+
+ SkASSERT(canvas);
+
+ fImpl = new (fStorage) SkDrawIter(canvas, skipEmptyClips);
+ fDone = !fImpl->next();
+}
+
+SkCanvas::LayerIter::~LayerIter() {
+ fImpl->~SkDrawIter();
+}
+
+void SkCanvas::LayerIter::next() {
+ fDone = !fImpl->next();
+}
+
+SkDevice* SkCanvas::LayerIter::device() const {
+ return fImpl->getDevice();
+}
+
+const SkMatrix& SkCanvas::LayerIter::matrix() const {
+ return fImpl->getMatrix();
+}
+
+const SkPaint& SkCanvas::LayerIter::paint() const {
+ const SkPaint* paint = fImpl->getPaint();
+ if (NULL == paint) {
+ paint = &fDefaultPaint;
+ }
+ return *paint;
+}
+
+const SkRegion& SkCanvas::LayerIter::clip() const { return fImpl->getClip(); }
+int SkCanvas::LayerIter::x() const { return fImpl->getX(); }
+int SkCanvas::LayerIter::y() const { return fImpl->getY(); }
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkCanvas::ClipVisitor::~ClipVisitor() { }
diff --git a/core/SkChunkAlloc.cpp b/core/SkChunkAlloc.cpp
new file mode 100644
index 00000000..62cdf153
--- /dev/null
+++ b/core/SkChunkAlloc.cpp
@@ -0,0 +1,142 @@
+/*
+ * 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 "SkChunkAlloc.h"
+
+// Don't malloc any chunks smaller than this
+#define MIN_CHUNKALLOC_BLOCK_SIZE 1024
+
+// Return the new min blocksize given the current value
+static size_t increase_next_size(size_t size) {
+ return size + (size >> 1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct SkChunkAlloc::Block {
+ Block* fNext;
+ size_t fFreeSize;
+ char* fFreePtr;
+ // data[] follows
+
+ char* startOfData() {
+ return reinterpret_cast<char*>(this + 1);
+ }
+
+ static void FreeChain(Block* block) {
+ while (block) {
+ Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
+ };
+
+ bool contains(const void* addr) const {
+ const char* ptr = reinterpret_cast<const char*>(addr);
+ return ptr >= (const char*)(this + 1) && ptr < fFreePtr;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkChunkAlloc::SkChunkAlloc(size_t minSize) {
+ if (minSize < MIN_CHUNKALLOC_BLOCK_SIZE) {
+ minSize = MIN_CHUNKALLOC_BLOCK_SIZE;
+ }
+
+ fBlock = NULL;
+ fMinSize = minSize;
+ fChunkSize = fMinSize;
+ fTotalCapacity = 0;
+ fTotalUsed = 0;
+ fBlockCount = 0;
+}
+
+SkChunkAlloc::~SkChunkAlloc() {
+ this->reset();
+}
+
+void SkChunkAlloc::reset() {
+ Block::FreeChain(fBlock);
+ fBlock = NULL;
+ fChunkSize = fMinSize; // reset to our initial minSize
+ fTotalCapacity = 0;
+ fTotalUsed = 0;
+ fBlockCount = 0;
+}
+
+SkChunkAlloc::Block* SkChunkAlloc::newBlock(size_t bytes, AllocFailType ftype) {
+ size_t size = bytes;
+ if (size < fChunkSize) {
+ size = fChunkSize;
+ }
+
+ Block* block = (Block*)sk_malloc_flags(sizeof(Block) + size,
+ ftype == kThrow_AllocFailType ? SK_MALLOC_THROW : 0);
+
+ if (block) {
+ // block->fNext = fBlock;
+ block->fFreeSize = size;
+ block->fFreePtr = block->startOfData();
+
+ fTotalCapacity += size;
+ fBlockCount += 1;
+
+ fChunkSize = increase_next_size(fChunkSize);
+ }
+ return block;
+}
+
+void* SkChunkAlloc::alloc(size_t bytes, AllocFailType ftype) {
+ fTotalUsed += bytes;
+
+ bytes = SkAlign4(bytes);
+
+ Block* block = fBlock;
+
+ if (block == NULL || bytes > block->fFreeSize) {
+ block = this->newBlock(bytes, ftype);
+ if (NULL == block) {
+ return NULL;
+ }
+ block->fNext = fBlock;
+ fBlock = block;
+ }
+
+ SkASSERT(block && bytes <= block->fFreeSize);
+ char* ptr = block->fFreePtr;
+
+ block->fFreeSize -= bytes;
+ block->fFreePtr = ptr + bytes;
+ return ptr;
+}
+
+size_t SkChunkAlloc::unalloc(void* ptr) {
+ size_t bytes = 0;
+ Block* block = fBlock;
+ if (block) {
+ char* cPtr = reinterpret_cast<char*>(ptr);
+ char* start = block->startOfData();
+ if (start <= cPtr && cPtr < block->fFreePtr) {
+ bytes = block->fFreePtr - cPtr;
+ block->fFreeSize += bytes;
+ block->fFreePtr = cPtr;
+ }
+ }
+ return bytes;
+}
+
+bool SkChunkAlloc::contains(const void* addr) const {
+ const Block* block = fBlock;
+ while (block) {
+ if (block->contains(addr)) {
+ return true;
+ }
+ block = block->fNext;
+ }
+ return false;
+}
diff --git a/core/SkClipStack.cpp b/core/SkClipStack.cpp
new file mode 100644
index 00000000..2c0961ab
--- /dev/null
+++ b/core/SkClipStack.cpp
@@ -0,0 +1,784 @@
+
+/*
+ * 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 "SkClipStack.h"
+#include "SkPath.h"
+#include "SkThread.h"
+
+#include <new>
+
+
+// 0-2 are reserved for invalid, empty & wide-open
+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;
+ }
+}
+
+void SkClipStack::Element::checkEmpty() const {
+ SkASSERT(fFiniteBound.isEmpty());
+ SkASSERT(kNormal_BoundsType == fFiniteBoundType);
+ SkASSERT(!fIsIntersectionOfRects);
+ SkASSERT(kEmptyGenID == fGenID);
+ SkASSERT(fPath.isEmpty());
+}
+
+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);
+
+ 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;
+}
+
+// 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;
+ }
+}
+
+void SkClipStack::Element::combineBoundsXOR(int combination, const SkRect& prevFinite) {
+
+ 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 combineBoundsIntersection
+void SkClipStack::Element::combineBoundsUnion(int combination, const SkRect& prevFinite) {
+
+ 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 combineBoundsUnion
+void SkClipStack::Element::combineBoundsIntersection(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();
+ fGenID = kEmptyGenID;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("SkClipStack::Element::combineBoundsIntersection Invalid fill combination");
+ break;
+ }
+}
+
+// a mirror of combineBoundsDiff
+void SkClipStack::Element::combineBoundsRevDiff(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();
+ 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;
+ }
+}
+
+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();
+
+ // 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 (SkRegion::kReplace_Op == fOp ||
+ (SkRegion::kIntersect_Op == fOp && NULL == prior) ||
+ (SkRegion::kIntersect_Op == fOp && prior->fIsIntersectionOfRects &&
+ prior->rectRectIntersectAllowed(fRect, fDoAA))) {
+ fIsIntersectionOfRects = true;
+ }
+
+ } else {
+ SkASSERT(kPath_Type == fType);
+
+ fFiniteBound = fPath.getBounds();
+
+ if (fPath.isInverseFillType()) {
+ fFiniteBoundType = kInsideOut_BoundsType;
+ } else {
+ fFiniteBoundType = kNormal_BoundsType;
+ }
+ }
+
+ 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;
+ }
+
+ FillCombo combination = kPrev_Cur_FillCombo;
+ if (kInsideOut_BoundsType == fFiniteBoundType) {
+ combination = (FillCombo) (combination | 0x01);
+ }
+ if (kInsideOut_BoundsType == prevType) {
+ combination = (FillCombo) (combination | 0x02);
+ }
+
+ 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 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 kDefaultElementAllocCnt = 8;
+
+SkClipStack::SkClipStack()
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
+ , fSaveCount(0) {
+}
+
+SkClipStack::SkClipStack(const SkClipStack& b)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt) {
+ *this = b;
+}
+
+SkClipStack::SkClipStack(const SkRect& r)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
+ , fSaveCount(0) {
+ if (!r.isEmpty()) {
+ this->clipDevRect(r, SkRegion::kReplace_Op, false);
+ }
+}
+
+SkClipStack::SkClipStack(const SkIRect& r)
+ : fDeque(sizeof(Element), kDefaultElementAllocCnt)
+ , fSaveCount(0) {
+ if (!r.isEmpty()) {
+ SkRect temp;
+ temp.set(r);
+ this->clipDevRect(temp, SkRegion::kReplace_Op, false);
+ }
+}
+
+SkClipStack::~SkClipStack() {
+ reset();
+}
+
+SkClipStack& SkClipStack::operator=(const SkClipStack& b) {
+ if (this == &b) {
+ return *this;
+ }
+ reset();
+
+ fSaveCount = b.fSaveCount;
+ SkDeque::F2BIter recIter(b.fDeque);
+ for (const Element* element = (const Element*)recIter.next();
+ element != NULL;
+ element = (const Element*)recIter.next()) {
+ new (fDeque.push_back()) Element(*element);
+ }
+
+ return *this;
+}
+
+bool SkClipStack::operator==(const SkClipStack& b) const {
+ if (fSaveCount != b.fSaveCount ||
+ fDeque.count() != b.fDeque.count()) {
+ return false;
+ }
+ SkDeque::F2BIter myIter(fDeque);
+ SkDeque::F2BIter bIter(b.fDeque);
+ const Element* myElement = (const Element*)myIter.next();
+ const Element* bElement = (const Element*)bIter.next();
+
+ while (myElement != NULL && bElement != NULL) {
+ if (*myElement != *bElement) {
+ return false;
+ }
+ myElement = (const Element*)myIter.next();
+ bElement = (const Element*)bIter.next();
+ }
+ 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()) {
+ Element* element = (Element*)fDeque.back();
+ element->~Element();
+ fDeque.pop_back();
+ }
+
+ fSaveCount = 0;
+}
+
+void SkClipStack::save() {
+ fSaveCount += 1;
+}
+
+void SkClipStack::restore() {
+ fSaveCount -= 1;
+ restoreTo(fSaveCount);
+}
+
+void SkClipStack::restoreTo(int saveCount) {
+ while (!fDeque.empty()) {
+ Element* element = (Element*)fDeque.back();
+ if (element->fSaveCount <= saveCount) {
+ break;
+ }
+ this->purgeClip(element);
+ element->~Element();
+ fDeque.pop_back();
+ }
+}
+
+void SkClipStack::getBounds(SkRect* canvFiniteBound,
+ BoundsType* boundType,
+ bool* isIntersectionOfRects) const {
+ SkASSERT(NULL != canvFiniteBound && NULL != boundType);
+
+ Element* element = (Element*)fDeque.back();
+
+ if (NULL == element) {
+ // the clip is wide open - the infinite plane w/ no pixels un-writeable
+ canvFiniteBound->setEmpty();
+ *boundType = kInsideOut_BoundsType;
+ if (NULL != isIntersectionOfRects) {
+ *isIntersectionOfRects = false;
+ }
+ return;
+ }
+
+ *canvFiniteBound = element->fFiniteBound;
+ *boundType = element->fFiniteBoundType;
+ if (NULL != isIntersectionOfRects) {
+ *isIntersectionOfRects = element->fIsIntersectionOfRects;
+ }
+}
+
+bool SkClipStack::intersectRectWithClip(SkRect* rect) const {
+ SkASSERT(NULL != rect);
+
+ SkRect bounds;
+ SkClipStack::BoundsType bt;
+ this->getBounds(&bounds, &bt);
+ if (bt == SkClipStack::kInsideOut_BoundsType) {
+ if (bounds.contains(*rect)) {
+ return false;
+ } else {
+ // If rect's x values are both within bound's x range we
+ // could clip here. Same for y. But we don't bother to check.
+ return true;
+ }
+ } else {
+ return rect->intersect(bounds);
+ }
+}
+
+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;
+}
+
+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);
+ Element* element = (Element*) iter.prev();
+
+ if (NULL != element) {
+ if (element->canBeIntersectedInPlace(fSaveCount, op)) {
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
+ return;
+ case Element::kRect_Type:
+ if (element->rectRectIntersectAllowed(rect, doAA)) {
+ this->purgeClip(element);
+ if (!element->fRect.intersect(rect)) {
+ element->setEmpty();
+ return;
+ }
+
+ element->fDoAA = doAA;
+ Element* prev = (Element*) iter.prev();
+ element->updateBoundAndGenID(prev);
+ return;
+ }
+ break;
+ case Element::kPath_Type:
+ if (!SkRect::Intersects(element->fPath.getBounds(), rect)) {
+ this->purgeClip(element);
+ element->setEmpty();
+ return;
+ }
+ break;
+ }
+ } else if (SkRegion::kReplace_Op == op) {
+ this->restoreTo(fSaveCount - 1);
+ element = (Element*) fDeque.back();
+ }
+ }
+ new (fDeque.push_back()) Element(fSaveCount, rect, op, doAA);
+ ((Element*) fDeque.back())->updateBoundAndGenID(element);
+
+ 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) && !path.isInverseFillType()) {
+ return this->clipDevRect(alt, op, doAA);
+ }
+
+ Element* element = (Element*)fDeque.back();
+ if (NULL != element) {
+ if (element->canBeIntersectedInPlace(fSaveCount, op)) {
+ const SkRect& pathBounds = path.getBounds();
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
+ return;
+ case Element::kRect_Type:
+ if (!SkRect::Intersects(element->fRect, pathBounds)) {
+ this->purgeClip(element);
+ element->setEmpty();
+ return;
+ }
+ break;
+ case Element::kPath_Type:
+ if (!SkRect::Intersects(element->fPath.getBounds(), pathBounds)) {
+ this->purgeClip(element);
+ element->setEmpty();
+ return;
+ }
+ break;
+ }
+ } else if (SkRegion::kReplace_Op == op) {
+ this->restoreTo(fSaveCount - 1);
+ element = (Element*) fDeque.back();
+ }
+ }
+ new (fDeque.push_back()) Element(fSaveCount, path, op, doAA);
+ ((Element*) fDeque.back())->updateBoundAndGenID(element);
+
+ if (element && element->fSaveCount == fSaveCount) {
+ this->purgeClip(element);
+ }
+}
+
+void SkClipStack::clipEmpty() {
+
+ Element* element = (Element*) fDeque.back();
+
+ if (element && element->canBeIntersectedInPlace(fSaveCount, SkRegion::kIntersect_Op)) {
+ switch (element->fType) {
+ case Element::kEmpty_Type:
+ element->checkEmpty();
+ return;
+ case Element::kRect_Type:
+ case Element::kPath_Type:
+ this->purgeClip(element);
+ element->setEmpty();
+ return;
+ }
+ }
+ new (fDeque.push_back()) Element(fSaveCount);
+
+ if (element && element->fSaveCount == fSaveCount) {
+ this->purgeClip(element);
+ }
+ ((Element*)fDeque.back())->fGenID = kEmptyGenID;
+}
+
+bool SkClipStack::isWideOpen() const {
+ if (0 == fDeque.count()) {
+ return true;
+ }
+
+ const Element* back = (const Element*) fDeque.back();
+ return kWideOpenGenID == back->fGenID ||
+ (kInsideOut_BoundsType == back->fFiniteBoundType && back->fFiniteBound.isEmpty());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkClipStack::Iter::Iter() : fStack(NULL) {
+}
+
+SkClipStack::Iter::Iter(const SkClipStack& stack, IterStart startLoc)
+ : fStack(&stack) {
+ this->reset(stack, startLoc);
+}
+
+const SkClipStack::Element* SkClipStack::Iter::next() {
+ return (const SkClipStack::Element*)fIter.next();
+}
+
+const SkClipStack::Element* SkClipStack::Iter::prev() {
+ return (const SkClipStack::Element*)fIter.prev();
+}
+
+const SkClipStack::Element* SkClipStack::Iter::skipToTopmost(SkRegion::Op op) {
+
+ if (NULL == fStack) {
+ return NULL;
+ }
+
+ fIter.reset(fStack->fDeque, SkDeque::Iter::kBack_IterStart);
+
+ const SkClipStack::Element* element = NULL;
+
+ for (element = (const SkClipStack::Element*) fIter.prev();
+ NULL != element;
+ element = (const SkClipStack::Element*) fIter.prev()) {
+
+ if (op == element->fOp) {
+ // The Deque's iterator is actually one pace ahead of the
+ // 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
+ // step so we get the expected result.
+ if (NULL == fIter.next()) {
+ // The reverse iterator has run off the front of the deque
+ // (i.e., the "op" clip is the first clip) and can't
+ // recover. Reset the iterator to start at the front.
+ fIter.reset(fStack->fDeque, SkDeque::Iter::kFront_IterStart);
+ }
+ break;
+ }
+ }
+
+ if (NULL == element) {
+ // There were no "op" clips
+ fIter.reset(fStack->fDeque, SkDeque::Iter::kFront_IterStart);
+ }
+
+ return this->next();
+}
+
+void SkClipStack::Iter::reset(const SkClipStack& stack, IterStart startLoc) {
+ fStack = &stack;
+ fIter.reset(stack.fDeque, static_cast<SkDeque::Iter::IterStart>(startLoc));
+}
+
+// helper method
+void SkClipStack::getConservativeBounds(int offsetX,
+ int offsetY,
+ int maxWidth,
+ int maxHeight,
+ SkRect* devBounds,
+ bool* isIntersectionOfRects) const {
+ SkASSERT(NULL != devBounds);
+
+ devBounds->setLTRB(0, 0,
+ SkIntToScalar(maxWidth), SkIntToScalar(maxHeight));
+
+ SkRect temp;
+ SkClipStack::BoundsType boundType;
+
+ // temp starts off in canvas space here
+ this->getBounds(&temp, &boundType, isIntersectionOfRects);
+ if (SkClipStack::kInsideOut_BoundsType == boundType) {
+ return;
+ }
+
+ // but is converted to device space here
+ temp.offset(SkIntToScalar(offsetX), SkIntToScalar(offsetY));
+
+ if (!devBounds->intersect(temp)) {
+ devBounds->setEmpty();
+ }
+}
+
+void SkClipStack::addPurgeClipCallback(PFPurgeClipCB callback, void* data) const {
+ ClipCallbackData temp = { callback, data };
+ fCallbackData.append(1, &temp);
+}
+
+void SkClipStack::removePurgeClipCallback(PFPurgeClipCB callback, void* data) const {
+ ClipCallbackData temp = { callback, data };
+ int index = fCallbackData.find(temp);
+ if (index >= 0) {
+ fCallbackData.removeShuffle(index);
+ }
+}
+
+// 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)(element->fGenID, fCallbackData[i].fData);
+ }
+
+ // 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);
+}
+
+int32_t SkClipStack::getTopmostGenID() const {
+
+ if (fDeque.empty()) {
+ return kInvalidGenID;
+ }
+
+ Element* element = (Element*)fDeque.back();
+ return element->fGenID;
+}
diff --git a/core/SkColor.cpp b/core/SkColor.cpp
new file mode 100644
index 00000000..6fa239ff
--- /dev/null
+++ b/core/SkColor.cpp
@@ -0,0 +1,113 @@
+
+/*
+ * 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 "SkColor.h"
+#include "SkColorPriv.h"
+
+SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
+ return SkPremultiplyARGBInline(a, r, g, b);
+}
+
+SkPMColor SkPreMultiplyColor(SkColor c) {
+ return SkPremultiplyARGBInline(SkColorGetA(c), SkColorGetR(c),
+ SkColorGetG(c), SkColorGetB(c));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline SkScalar ByteToScalar(U8CPU x) {
+ SkASSERT(x <= 255);
+ return SkIntToScalar(x) / 255;
+}
+
+static inline SkScalar ByteDivToScalar(int numer, U8CPU denom) {
+ // cast to keep the answer signed
+ return SkIntToScalar(numer) / (int)denom;
+}
+
+void SkRGBToHSV(U8CPU r, U8CPU g, U8CPU b, SkScalar hsv[3]) {
+ SkASSERT(hsv);
+
+ unsigned min = SkMin32(r, SkMin32(g, b));
+ unsigned max = SkMax32(r, SkMax32(g, b));
+ unsigned delta = max - min;
+
+ SkScalar v = ByteToScalar(max);
+ SkASSERT(v >= 0 && v <= SK_Scalar1);
+
+ if (0 == delta) { // we're a shade of gray
+ hsv[0] = 0;
+ hsv[1] = 0;
+ hsv[2] = v;
+ return;
+ }
+
+ SkScalar s = ByteDivToScalar(delta, max);
+ SkASSERT(s >= 0 && s <= SK_Scalar1);
+
+ SkScalar h;
+ if (r == max) {
+ h = ByteDivToScalar(g - b, delta);
+ } else if (g == max) {
+ h = SkIntToScalar(2) + ByteDivToScalar(b - r, delta);
+ } else { // b == max
+ h = SkIntToScalar(4) + ByteDivToScalar(r - g, delta);
+ }
+
+ h *= 60;
+ if (h < 0) {
+ h += SkIntToScalar(360);
+ }
+ SkASSERT(h >= 0 && h < SkIntToScalar(360));
+
+ hsv[0] = h;
+ hsv[1] = s;
+ hsv[2] = v;
+}
+
+static inline U8CPU UnitScalarToByte(SkScalar x) {
+ if (x < 0) {
+ return 0;
+ }
+ if (x >= SK_Scalar1) {
+ return 255;
+ }
+ return SkScalarToFixed(x) >> 8;
+}
+
+SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) {
+ SkASSERT(hsv);
+
+ U8CPU s = UnitScalarToByte(hsv[1]);
+ U8CPU v = UnitScalarToByte(hsv[2]);
+
+ if (0 == s) { // shade of gray
+ return SkColorSetARGB(a, v, v, v);
+ }
+ SkFixed hx = (hsv[0] < 0 || hsv[0] >= SkIntToScalar(360)) ? 0 : SkScalarToFixed(hsv[0]/60);
+ SkFixed f = hx & 0xFFFF;
+
+ unsigned v_scale = SkAlpha255To256(v);
+ unsigned p = SkAlphaMul(255 - s, v_scale);
+ unsigned q = SkAlphaMul(255 - (s * f >> 16), v_scale);
+ unsigned t = SkAlphaMul(255 - (s * (SK_Fixed1 - f) >> 16), v_scale);
+
+ unsigned r, g, b;
+
+ SkASSERT((unsigned)(hx >> 16) < 6);
+ switch (hx >> 16) {
+ case 0: r = v; g = t; b = p; break;
+ case 1: r = q; g = v; b = p; break;
+ case 2: r = p; g = v; b = t; break;
+ case 3: r = p; g = q; b = v; break;
+ case 4: r = t; g = p; b = v; break;
+ default: r = v; g = p; b = q; break;
+ }
+ return SkColorSetARGB(a, r, g, b);
+}
diff --git a/core/SkColorFilter.cpp b/core/SkColorFilter.cpp
new file mode 100644
index 00000000..abf191e1
--- /dev/null
+++ b/core/SkColorFilter.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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 "SkColorFilter.h"
+
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+SK_DEFINE_INST_COUNT(SkColorFilter)
+
+bool SkColorFilter::asColorMode(SkColor* color, SkXfermode::Mode* mode) const {
+ return false;
+}
+
+bool SkColorFilter::asColorMatrix(SkScalar matrix[20]) const {
+ return false;
+}
+
+bool SkColorFilter::asComponentTable(SkBitmap*) const {
+ return false;
+}
+
+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");
+
+ if (d != s) {
+ memcpy(d, s, count * sizeof(uint16_t));
+ }
+}
+
+SkColor SkColorFilter::filterColor(SkColor c) const {
+ SkPMColor dst, src = SkPreMultiplyColor(c);
+ this->filterSpan(&src, 1, &dst);
+ return SkUnPreMultiply::PMColorToColor(dst);
+}
+
+GrEffectRef* SkColorFilter::asNewEffect(GrContext*) const {
+ return NULL;
+}
diff --git a/core/SkColorTable.cpp b/core/SkColorTable.cpp
new file mode 100644
index 00000000..e1ebf920
--- /dev/null
+++ b/core/SkColorTable.cpp
@@ -0,0 +1,159 @@
+
+/*
+ * Copyright 2009 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 "SkColorTable.h"
+#include "SkFlattenableBuffers.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+SK_DEFINE_INST_COUNT(SkColorTable)
+
+SkColorTable::SkColorTable(int count)
+ : f16BitCache(NULL), fFlags(0)
+{
+ if (count < 0)
+ count = 0;
+ else if (count > 256)
+ count = 256;
+
+ fCount = SkToU16(count);
+ fColors = (SkPMColor*)sk_malloc_throw(count * sizeof(SkPMColor));
+ memset(fColors, 0, count * sizeof(SkPMColor));
+
+ SkDEBUGCODE(fColorLockCount = 0;)
+ SkDEBUGCODE(f16BitCacheLockCount = 0;)
+}
+
+// As copy constructor is hidden in the class hierarchy, we need to call
+// default constructor explicitly to suppress a compiler warning.
+SkColorTable::SkColorTable(const SkColorTable& src) : INHERITED() {
+ f16BitCache = NULL;
+ fFlags = src.fFlags;
+ int count = src.count();
+ fCount = SkToU16(count);
+ fColors = reinterpret_cast<SkPMColor*>(
+ sk_malloc_throw(count * sizeof(SkPMColor)));
+ memcpy(fColors, src.fColors, count * sizeof(SkPMColor));
+
+ SkDEBUGCODE(fColorLockCount = 0;)
+ SkDEBUGCODE(f16BitCacheLockCount = 0;)
+}
+
+SkColorTable::SkColorTable(const SkPMColor colors[], int count)
+ : f16BitCache(NULL), fFlags(0)
+{
+ if (count < 0)
+ count = 0;
+ else if (count > 256)
+ count = 256;
+
+ fCount = SkToU16(count);
+ fColors = reinterpret_cast<SkPMColor*>(
+ sk_malloc_throw(count * sizeof(SkPMColor)));
+
+ if (colors)
+ memcpy(fColors, colors, count * sizeof(SkPMColor));
+
+ SkDEBUGCODE(fColorLockCount = 0;)
+ SkDEBUGCODE(f16BitCacheLockCount = 0;)
+}
+
+SkColorTable::~SkColorTable()
+{
+ SkASSERT(fColorLockCount == 0);
+ SkASSERT(f16BitCacheLockCount == 0);
+
+ sk_free(fColors);
+ sk_free(f16BitCache);
+}
+
+void SkColorTable::setFlags(unsigned flags)
+{
+ fFlags = SkToU8(flags);
+}
+
+void SkColorTable::unlockColors(bool changed)
+{
+ SkASSERT(fColorLockCount != 0);
+ SkDEBUGCODE(sk_atomic_dec(&fColorLockCount);)
+ if (changed)
+ this->inval16BitCache();
+}
+
+void SkColorTable::inval16BitCache()
+{
+ SkASSERT(f16BitCacheLockCount == 0);
+ if (f16BitCache)
+ {
+ sk_free(f16BitCache);
+ f16BitCache = NULL;
+ }
+}
+
+#include "SkColorPriv.h"
+
+static inline void build_16bitcache(uint16_t dst[], const SkPMColor src[], int count)
+{
+ while (--count >= 0)
+ *dst++ = SkPixel32ToPixel16_ToU16(*src++);
+}
+
+const uint16_t* SkColorTable::lock16BitCache()
+{
+ if (fFlags & kColorsAreOpaque_Flag)
+ {
+ if (f16BitCache == NULL) // build the cache
+ {
+ f16BitCache = (uint16_t*)sk_malloc_throw(fCount * sizeof(uint16_t));
+ build_16bitcache(f16BitCache, fColors, fCount);
+ }
+ }
+ else // our colors have alpha, so no cache
+ {
+ this->inval16BitCache();
+ if (f16BitCache)
+ {
+ sk_free(f16BitCache);
+ f16BitCache = NULL;
+ }
+ }
+
+ SkDEBUGCODE(f16BitCacheLockCount += 1);
+ return f16BitCache;
+}
+
+void SkColorTable::setIsOpaque(bool isOpaque) {
+ if (isOpaque) {
+ fFlags |= kColorsAreOpaque_Flag;
+ } else {
+ fFlags &= ~kColorsAreOpaque_Flag;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorTable::SkColorTable(SkFlattenableReadBuffer& buffer) {
+ f16BitCache = NULL;
+ SkDEBUGCODE(fColorLockCount = 0;)
+ SkDEBUGCODE(f16BitCacheLockCount = 0;)
+
+ fFlags = buffer.readUInt();
+ fCount = buffer.getArrayCount();
+ fColors = (SkPMColor*)sk_malloc_throw(fCount * sizeof(SkPMColor));
+ SkDEBUGCODE(const uint32_t countRead =) buffer.readColorArray(fColors);
+#ifdef SK_DEBUG
+ SkASSERT((unsigned)fCount <= 256);
+ SkASSERT(countRead == fCount);
+#endif
+}
+
+void SkColorTable::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeUInt(fFlags);
+ buffer.writeColorArray(fColors, fCount);
+}
diff --git a/core/SkComposeShader.cpp b/core/SkComposeShader.cpp
new file mode 100644
index 00000000..de0ffdb2
--- /dev/null
+++ b/core/SkComposeShader.cpp
@@ -0,0 +1,192 @@
+
+/*
+ * 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 "SkComposeShader.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkColorShader.h"
+#include "SkFlattenableBuffers.h"
+#include "SkXfermode.h"
+#include "SkString.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkComposeShader::SkComposeShader(SkShader* sA, SkShader* sB, SkXfermode* mode) {
+ fShaderA = sA; sA->ref();
+ fShaderB = sB; sB->ref();
+ // mode may be null
+ fMode = mode;
+ SkSafeRef(mode);
+}
+
+SkComposeShader::SkComposeShader(SkFlattenableReadBuffer& buffer) :
+ INHERITED(buffer) {
+ fShaderA = buffer.readFlattenableT<SkShader>();
+ if (NULL == fShaderA) {
+ fShaderA = SkNEW_ARGS(SkColorShader, (0));
+ }
+ fShaderB = buffer.readFlattenableT<SkShader>();
+ if (NULL == fShaderB) {
+ fShaderB = SkNEW_ARGS(SkColorShader, (0));
+ }
+ fMode = buffer.readFlattenableT<SkXfermode>();
+}
+
+SkComposeShader::~SkComposeShader() {
+ SkSafeUnref(fMode);
+ fShaderB->unref();
+ fShaderA->unref();
+}
+
+class SkAutoAlphaRestore {
+public:
+ SkAutoAlphaRestore(SkPaint* paint, uint8_t newAlpha) {
+ fAlpha = paint->getAlpha();
+ fPaint = paint;
+ paint->setAlpha(newAlpha);
+ }
+
+ ~SkAutoAlphaRestore() {
+ fPaint->setAlpha(fAlpha);
+ }
+private:
+ SkPaint* fPaint;
+ uint8_t fAlpha;
+};
+
+void SkComposeShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fShaderA);
+ buffer.writeFlattenable(fShaderB);
+ buffer.writeFlattenable(fMode);
+}
+
+/* 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) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ // we preconcat our localMatrix (if any) with the device matrix
+ // before calling our sub-shaders
+
+ SkMatrix tmpM;
+
+ tmpM.setConcat(matrix, this->getLocalMatrix());
+
+ SkAutoAlphaRestore restore(const_cast<SkPaint*>(&paint), 0xFF);
+
+ 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
+// take up too much stack-space (each element is 4 bytes)
+#define TMP_COLOR_COUNT 64
+
+void SkComposeShader::shadeSpan(int x, int y, SkPMColor result[], int count) {
+ SkShader* shaderA = fShaderA;
+ SkShader* shaderB = fShaderB;
+ SkXfermode* mode = fMode;
+ unsigned scale = SkAlpha255To256(this->getPaintAlpha());
+
+ SkPMColor tmp[TMP_COLOR_COUNT];
+
+ if (NULL == mode) { // implied SRC_OVER
+ // TODO: when we have a good test-case, should use SkBlitRow::Proc32
+ // for these loops
+ do {
+ int n = count;
+ if (n > TMP_COLOR_COUNT) {
+ n = TMP_COLOR_COUNT;
+ }
+
+ shaderA->shadeSpan(x, y, result, n);
+ shaderB->shadeSpan(x, y, tmp, n);
+
+ if (256 == scale) {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkPMSrcOver(tmp[i], result[i]);
+ }
+ } else {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkAlphaMulQ(SkPMSrcOver(tmp[i], result[i]),
+ scale);
+ }
+ }
+
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+ } else { // use mode for the composition
+ do {
+ int n = count;
+ if (n > TMP_COLOR_COUNT) {
+ n = TMP_COLOR_COUNT;
+ }
+
+ shaderA->shadeSpan(x, y, result, n);
+ shaderB->shadeSpan(x, y, tmp, n);
+ mode->xfer32(result, tmp, n, NULL);
+
+ if (256 == scale) {
+ for (int i = 0; i < n; i++) {
+ result[i] = SkAlphaMulQ(result[i], scale);
+ }
+ }
+
+ result += n;
+ x += n;
+ count -= n;
+ } while (count > 0);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkComposeShader::toString(SkString* str) const {
+ str->append("SkComposeShader: (");
+
+ str->append("ShaderA: ");
+ fShaderA->toString(str);
+ str->append(" ShaderB: ");
+ fShaderB->toString(str);
+ str->append(" Xfermode: ");
+ fMode->toString(str);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/core/SkConfig8888.cpp b/core/SkConfig8888.cpp
new file mode 100644
index 00000000..dd5cbc47
--- /dev/null
+++ b/core/SkConfig8888.cpp
@@ -0,0 +1,280 @@
+#include "SkConfig8888.h"
+#include "SkMathPriv.h"
+#include "SkUnPreMultiply.h"
+
+namespace {
+
+template <int A_IDX, int R_IDX, int G_IDX, int B_IDX>
+inline uint32_t pack_config8888(uint32_t a, uint32_t r,
+ uint32_t g, uint32_t b) {
+#ifdef SK_CPU_LENDIAN
+ return (a << (A_IDX * 8)) | (r << (R_IDX * 8)) |
+ (g << (G_IDX * 8)) | (b << (B_IDX * 8));
+#else
+ return (a << ((3-A_IDX) * 8)) | (r << ((3-R_IDX) * 8)) |
+ (g << ((3-G_IDX) * 8)) | (b << ((3-B_IDX) * 8));
+#endif
+}
+
+template <int A_IDX, int R_IDX, int G_IDX, int B_IDX>
+inline void unpack_config8888(uint32_t color,
+ uint32_t* a, uint32_t* r,
+ uint32_t* g, uint32_t* b) {
+#ifdef SK_CPU_LENDIAN
+ *a = (color >> (A_IDX * 8)) & 0xff;
+ *r = (color >> (R_IDX * 8)) & 0xff;
+ *g = (color >> (G_IDX * 8)) & 0xff;
+ *b = (color >> (B_IDX * 8)) & 0xff;
+#else
+ *a = (color >> ((3 - A_IDX) * 8)) & 0xff;
+ *r = (color >> ((3 - R_IDX) * 8)) & 0xff;
+ *g = (color >> ((3 - G_IDX) * 8)) & 0xff;
+ *b = (color >> ((3 - B_IDX) * 8)) & 0xff;
+#endif
+}
+
+#ifdef SK_CPU_LENDIAN
+ static const int SK_NATIVE_A_IDX = SK_A32_SHIFT / 8;
+ static const int SK_NATIVE_R_IDX = SK_R32_SHIFT / 8;
+ static const int SK_NATIVE_G_IDX = SK_G32_SHIFT / 8;
+ static const int SK_NATIVE_B_IDX = SK_B32_SHIFT / 8;
+#else
+ static const int SK_NATIVE_A_IDX = 3 - (SK_A32_SHIFT / 8);
+ static const int SK_NATIVE_R_IDX = 3 - (SK_R32_SHIFT / 8);
+ static const int SK_NATIVE_G_IDX = 3 - (SK_G32_SHIFT / 8);
+ static const int SK_NATIVE_B_IDX = 3 - (SK_B32_SHIFT / 8);
+#endif
+
+/**
+ * convert_pixel<OUT_CFG, IN_CFG converts a pixel value from one Config8888 to
+ * another. It is implemented by first expanding OUT_CFG to r, g, b, a indices
+ * and an is_premul bool as params to another template function. Then IN_CFG is
+ * expanded via another function call.
+ */
+
+template <bool OUT_PM, int OUT_A_IDX, int OUT_R_IDX, int OUT_G_IDX, int OUT_B_IDX,
+ bool IN_PM, int IN_A_IDX, int IN_R_IDX, int IN_G_IDX, int IN_B_IDX>
+inline uint32_t convert_pixel(uint32_t pixel) {
+ uint32_t a, r, g, b;
+ unpack_config8888<IN_A_IDX, IN_R_IDX, IN_G_IDX, IN_B_IDX>(pixel, &a, &r, &g, &b);
+ if (IN_PM && !OUT_PM) {
+ // Using SkUnPreMultiply::ApplyScale is faster than (value * 0xff) / a.
+ if (a) {
+ SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(a);
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+ } else {
+ return 0;
+ }
+ } else if (!IN_PM && OUT_PM) {
+ // This matches SkUnPreMultiply conversion which we are replacing.
+ r = SkMulDiv255Round(r, a);
+ g = SkMulDiv255Round(g, a);
+ b = SkMulDiv255Round(b, a);
+ }
+ return pack_config8888<OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX>(a, r, g, b);
+}
+
+template <bool OUT_PM, int OUT_A_IDX, int OUT_R_IDX, int OUT_G_IDX, int OUT_B_IDX, SkCanvas::Config8888 IN_CFG>
+inline uint32_t convert_pixel(uint32_t pixel) {
+ switch(IN_CFG) {
+ case SkCanvas::kNative_Premul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ true, SK_NATIVE_A_IDX, SK_NATIVE_R_IDX, SK_NATIVE_G_IDX, SK_NATIVE_B_IDX>(pixel);
+ break;
+ case SkCanvas::kNative_Unpremul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ false, SK_NATIVE_A_IDX, SK_NATIVE_R_IDX, SK_NATIVE_G_IDX, SK_NATIVE_B_IDX>(pixel);
+ break;
+ case SkCanvas::kBGRA_Premul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ true, 3, 2, 1, 0>(pixel);
+ break;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ false, 3, 2, 1, 0>(pixel);
+ break;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ true, 3, 0, 1, 2>(pixel);
+ break;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ return convert_pixel<OUT_PM, OUT_A_IDX, OUT_R_IDX, OUT_G_IDX, OUT_B_IDX,
+ false, 3, 0, 1, 2>(pixel);
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected config8888");
+ return 0;
+ break;
+ }
+}
+
+template <SkCanvas::Config8888 OUT_CFG, SkCanvas::Config8888 IN_CFG>
+inline uint32_t convert_pixel(uint32_t pixel) {
+ switch(OUT_CFG) {
+ case SkCanvas::kNative_Premul_Config8888:
+ return convert_pixel<true, SK_NATIVE_A_IDX, SK_NATIVE_R_IDX, SK_NATIVE_G_IDX, SK_NATIVE_B_IDX, IN_CFG>(pixel);
+ break;
+ case SkCanvas::kNative_Unpremul_Config8888:
+ return convert_pixel<false, SK_NATIVE_A_IDX, SK_NATIVE_R_IDX, SK_NATIVE_G_IDX, SK_NATIVE_B_IDX, IN_CFG>(pixel);
+ break;
+ case SkCanvas::kBGRA_Premul_Config8888:
+ return convert_pixel<true, 3, 2, 1, 0, IN_CFG>(pixel);
+ break;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ return convert_pixel<false, 3, 2, 1, 0, IN_CFG>(pixel);
+ break;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ return convert_pixel<true, 3, 0, 1, 2, IN_CFG>(pixel);
+ break;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ return convert_pixel<false, 3, 0, 1, 2, IN_CFG>(pixel);
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected config8888");
+ return 0;
+ break;
+ }
+}
+
+/**
+ * SkConvertConfig8888Pixels has 6 * 6 possible combinations of src and dst
+ * configs. Each is implemented as an instantiation templated function. Two
+ * levels of switch statements are used to select the correct instantiation, one
+ * for the src config and one for the dst config.
+ */
+
+template <SkCanvas::Config8888 DST_CFG, SkCanvas::Config8888 SRC_CFG>
+inline void convert_config8888(uint32_t* dstPixels,
+ size_t dstRowBytes,
+ const uint32_t* srcPixels,
+ size_t srcRowBytes,
+ int width,
+ int height) {
+ intptr_t dstPix = reinterpret_cast<intptr_t>(dstPixels);
+ intptr_t srcPix = reinterpret_cast<intptr_t>(srcPixels);
+
+ for (int y = 0; y < height; ++y) {
+ srcPixels = reinterpret_cast<const uint32_t*>(srcPix);
+ dstPixels = reinterpret_cast<uint32_t*>(dstPix);
+ for (int x = 0; x < width; ++x) {
+ dstPixels[x] = convert_pixel<DST_CFG, SRC_CFG>(srcPixels[x]);
+ }
+ dstPix += dstRowBytes;
+ srcPix += srcRowBytes;
+ }
+}
+
+template <SkCanvas::Config8888 SRC_CFG>
+inline void convert_config8888(uint32_t* dstPixels,
+ size_t dstRowBytes,
+ SkCanvas::Config8888 dstConfig,
+ const uint32_t* srcPixels,
+ size_t srcRowBytes,
+ int width,
+ int height) {
+ switch(dstConfig) {
+ case SkCanvas::kNative_Premul_Config8888:
+ convert_config8888<SkCanvas::kNative_Premul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kNative_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kNative_Unpremul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kBGRA_Premul_Config8888:
+ convert_config8888<SkCanvas::kBGRA_Premul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kBGRA_Unpremul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ convert_config8888<SkCanvas::kRGBA_Premul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kRGBA_Unpremul_Config8888, SRC_CFG>(dstPixels, dstRowBytes, srcPixels, srcRowBytes, width, height);
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected config8888");
+ break;
+ }
+}
+
+}
+
+void SkConvertConfig8888Pixels(uint32_t* dstPixels,
+ size_t dstRowBytes,
+ SkCanvas::Config8888 dstConfig,
+ const uint32_t* srcPixels,
+ size_t srcRowBytes,
+ SkCanvas::Config8888 srcConfig,
+ int width,
+ int height) {
+ if (srcConfig == dstConfig) {
+ if (srcPixels == dstPixels) {
+ return;
+ }
+ if (dstRowBytes == srcRowBytes &&
+ 4U * width == srcRowBytes) {
+ memcpy(dstPixels, srcPixels, srcRowBytes * height);
+ return;
+ } else {
+ intptr_t srcPix = reinterpret_cast<intptr_t>(srcPixels);
+ intptr_t dstPix = reinterpret_cast<intptr_t>(dstPixels);
+ for (int y = 0; y < height; ++y) {
+ srcPixels = reinterpret_cast<const uint32_t*>(srcPix);
+ dstPixels = reinterpret_cast<uint32_t*>(dstPix);
+ memcpy(dstPixels, srcPixels, 4 * width);
+ srcPix += srcRowBytes;
+ dstPix += dstRowBytes;
+ }
+ return;
+ }
+ }
+ switch(srcConfig) {
+ case SkCanvas::kNative_Premul_Config8888:
+ convert_config8888<SkCanvas::kNative_Premul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kNative_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kNative_Unpremul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kBGRA_Premul_Config8888:
+ convert_config8888<SkCanvas::kBGRA_Premul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kBGRA_Unpremul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ convert_config8888<SkCanvas::kRGBA_Premul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ convert_config8888<SkCanvas::kRGBA_Unpremul_Config8888>(dstPixels, dstRowBytes, dstConfig, srcPixels, srcRowBytes, width, height);
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected config8888");
+ break;
+ }
+}
+
+uint32_t SkPackConfig8888(SkCanvas::Config8888 config,
+ uint32_t a,
+ uint32_t r,
+ uint32_t g,
+ uint32_t b) {
+ switch (config) {
+ case SkCanvas::kNative_Premul_Config8888:
+ case SkCanvas::kNative_Unpremul_Config8888:
+ return pack_config8888<SK_NATIVE_A_IDX,
+ SK_NATIVE_R_IDX,
+ SK_NATIVE_G_IDX,
+ SK_NATIVE_B_IDX>(a, r, g, b);
+ case SkCanvas::kBGRA_Premul_Config8888:
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ return pack_config8888<3, 2, 1, 0>(a, r, g, b);
+ case SkCanvas::kRGBA_Premul_Config8888:
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ return pack_config8888<3, 0, 1, 2>(a, r, g, b);
+ default:
+ SkDEBUGFAIL("Unexpected config8888");
+ return 0;
+ }
+}
diff --git a/core/SkConfig8888.h b/core/SkConfig8888.h
new file mode 100644
index 00000000..96eaef24
--- /dev/null
+++ b/core/SkConfig8888.h
@@ -0,0 +1,76 @@
+
+/*
+ * 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 "SkCanvas.h"
+#include "SkColorPriv.h"
+
+/**
+ * Converts pixels from one Config8888 to another Config8888
+ */
+void SkConvertConfig8888Pixels(uint32_t* dstPixels,
+ size_t dstRowBytes,
+ SkCanvas::Config8888 dstConfig,
+ const uint32_t* srcPixels,
+ size_t srcRowBytes,
+ SkCanvas::Config8888 srcConfig,
+ int width,
+ int height);
+
+/**
+ * Packs a, r, g, b, values into byte order specified by config.
+ */
+uint32_t SkPackConfig8888(SkCanvas::Config8888 config,
+ uint32_t a,
+ uint32_t r,
+ uint32_t g,
+ uint32_t b);
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementation
+
+namespace {
+
+/**
+ Copies all pixels from a bitmap to a dst ptr with a given rowBytes and
+ Config8888. The bitmap must have kARGB_8888_Config.
+ */
+
+static inline void SkCopyBitmapToConfig8888(uint32_t* dstPixels,
+ size_t dstRowBytes,
+ SkCanvas::Config8888 dstConfig8888,
+ const SkBitmap& srcBmp) {
+ SkASSERT(SkBitmap::kARGB_8888_Config == srcBmp.config());
+ SkAutoLockPixels alp(srcBmp);
+ int w = srcBmp.width();
+ int h = srcBmp.height();
+ size_t srcRowBytes = srcBmp.rowBytes();
+ const uint32_t* srcPixels = reinterpret_cast<uint32_t*>(srcBmp.getPixels());
+
+ SkConvertConfig8888Pixels(dstPixels, dstRowBytes, dstConfig8888, srcPixels, srcRowBytes, SkCanvas::kNative_Premul_Config8888, w, h);
+}
+
+/**
+ Copies over all pixels in a bitmap from a src ptr with a given rowBytes and
+ Config8888. The bitmap must have pixels and be kARGB_8888_Config.
+ */
+static inline void SkCopyConfig8888ToBitmap(const SkBitmap& dstBmp,
+ const uint32_t* srcPixels,
+ size_t srcRowBytes,
+ SkCanvas::Config8888 srcConfig8888) {
+ SkASSERT(SkBitmap::kARGB_8888_Config == dstBmp.config());
+ SkAutoLockPixels alp(dstBmp);
+ int w = dstBmp.width();
+ int h = dstBmp.height();
+ size_t dstRowBytes = dstBmp.rowBytes();
+ uint32_t* dstPixels = reinterpret_cast<uint32_t*>(dstBmp.getPixels());
+
+ SkConvertConfig8888Pixels(dstPixels, dstRowBytes, SkCanvas::kNative_Premul_Config8888, srcPixels, srcRowBytes, srcConfig8888, w, h);
+}
+
+}
diff --git a/core/SkConvolver.cpp b/core/SkConvolver.cpp
new file mode 100644
index 00000000..f426ef00
--- /dev/null
+++ b/core/SkConvolver.cpp
@@ -0,0 +1,461 @@
+// Copyright (c) 2011 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.
+
+#include "SkConvolver.h"
+#include "SkSize.h"
+#include "SkTypes.h"
+
+namespace {
+
+ // Converts the argument to an 8-bit unsigned value by clamping to the range
+ // 0-255.
+ inline unsigned char ClampTo8(int a) {
+ if (static_cast<unsigned>(a) < 256) {
+ return a; // Avoid the extra check in the common case.
+ }
+ if (a < 0) {
+ return 0;
+ }
+ return 255;
+ }
+
+ // Stores a list of rows in a circular buffer. The usage is you write into it
+ // by calling AdvanceRow. It will keep track of which row in the buffer it
+ // should use next, and the total number of rows added.
+ class CircularRowBuffer {
+ public:
+ // The number of pixels in each row is given in |sourceRowPixelWidth|.
+ // The maximum number of rows needed in the buffer is |maxYFilterSize|
+ // (we only need to store enough rows for the biggest filter).
+ //
+ // We use the |firstInputRow| to compute the coordinates of all of the
+ // following rows returned by Advance().
+ CircularRowBuffer(int destRowPixelWidth, int maxYFilterSize,
+ int firstInputRow)
+ : fRowByteWidth(destRowPixelWidth * 4),
+ fNumRows(maxYFilterSize),
+ fNextRow(0),
+ fNextRowCoordinate(firstInputRow) {
+ fBuffer.reset(fRowByteWidth * maxYFilterSize);
+ fRowAddresses.reset(fNumRows);
+ }
+
+ // Moves to the next row in the buffer, returning a pointer to the beginning
+ // of it.
+ unsigned char* advanceRow() {
+ unsigned char* row = &fBuffer[fNextRow * fRowByteWidth];
+ fNextRowCoordinate++;
+
+ // Set the pointer to the next row to use, wrapping around if necessary.
+ fNextRow++;
+ if (fNextRow == fNumRows) {
+ fNextRow = 0;
+ }
+ return row;
+ }
+
+ // Returns a pointer to an "unrolled" array of rows. These rows will start
+ // at the y coordinate placed into |*firstRowIndex| and will continue in
+ // order for the maximum number of rows in this circular buffer.
+ //
+ // The |firstRowIndex_| may be negative. This means the circular buffer
+ // starts before the top of the image (it hasn't been filled yet).
+ unsigned char* const* GetRowAddresses(int* firstRowIndex) {
+ // Example for a 4-element circular buffer holding coords 6-9.
+ // Row 0 Coord 8
+ // Row 1 Coord 9
+ // Row 2 Coord 6 <- fNextRow = 2, fNextRowCoordinate = 10.
+ // Row 3 Coord 7
+ //
+ // The "next" row is also the first (lowest) coordinate. This computation
+ // may yield a negative value, but that's OK, the math will work out
+ // since the user of this buffer will compute the offset relative
+ // to the firstRowIndex and the negative rows will never be used.
+ *firstRowIndex = fNextRowCoordinate - fNumRows;
+
+ int curRow = fNextRow;
+ for (int i = 0; i < fNumRows; i++) {
+ fRowAddresses[i] = &fBuffer[curRow * fRowByteWidth];
+
+ // Advance to the next row, wrapping if necessary.
+ curRow++;
+ if (curRow == fNumRows) {
+ curRow = 0;
+ }
+ }
+ return &fRowAddresses[0];
+ }
+
+ private:
+ // The buffer storing the rows. They are packed, each one fRowByteWidth.
+ SkTArray<unsigned char> fBuffer;
+
+ // Number of bytes per row in the |buffer|.
+ int fRowByteWidth;
+
+ // The number of rows available in the buffer.
+ int fNumRows;
+
+ // The next row index we should write into. This wraps around as the
+ // circular buffer is used.
+ int fNextRow;
+
+ // The y coordinate of the |fNextRow|. This is incremented each time a
+ // new row is appended and does not wrap.
+ int fNextRowCoordinate;
+
+ // Buffer used by GetRowAddresses().
+ SkTArray<unsigned char*> fRowAddresses;
+ };
+
+// Convolves horizontally along a single row. The row data is given in
+// |srcData| and continues for the numValues() of the filter.
+template<bool hasAlpha>
+ void ConvolveHorizontally(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow) {
+ // Loop over each pixel on this row in the output image.
+ int numValues = filter.numValues();
+ for (int outX = 0; outX < numValues; outX++) {
+ // Get the filter that determines the current output pixel.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filter.FilterForValue(outX, &filterOffset, &filterLength);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filterLength| pixels (4 bytes each) after this.
+ const unsigned char* rowToFilter = &srcData[filterOffset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int accum[4] = {0};
+ for (int filterX = 0; filterX < filterLength; filterX++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX];
+ accum[0] += curFilter * rowToFilter[filterX * 4 + 0];
+ accum[1] += curFilter * rowToFilter[filterX * 4 + 1];
+ accum[2] += curFilter * rowToFilter[filterX * 4 + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * rowToFilter[filterX * 4 + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[outX * 4 + 0] = ClampTo8(accum[0]);
+ outRow[outX * 4 + 1] = ClampTo8(accum[1]);
+ outRow[outX * 4 + 2] = ClampTo8(accum[2]);
+ if (hasAlpha) {
+ outRow[outX * 4 + 3] = ClampTo8(accum[3]);
+ }
+ }
+ }
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |sourceDataRows| array, with each row
+// being |pixelWidth| wide.
+//
+// The output must have room for |pixelWidth * 4| bytes.
+template<bool hasAlpha>
+ void ConvolveVertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength,
+ unsigned char* const* sourceDataRows,
+ int pixelWidth,
+ unsigned char* outRow) {
+ // We go through each column in the output and do a vertical convolution,
+ // generating one output pixel each time.
+ for (int outX = 0; outX < pixelWidth; outX++) {
+ // Compute the number of bytes over in each row that the current column
+ // we're convolving starts at. The pixel will cover the next 4 bytes.
+ int byteOffset = outX * 4;
+
+ // Apply the filter to one column of pixels.
+ int accum[4] = {0};
+ for (int filterY = 0; filterY < filterLength; filterY++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY];
+ accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0];
+ accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1];
+ accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of precision.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[byteOffset + 0] = ClampTo8(accum[0]);
+ outRow[byteOffset + 1] = ClampTo8(accum[1]);
+ outRow[byteOffset + 2] = ClampTo8(accum[2]);
+ if (hasAlpha) {
+ unsigned char alpha = ClampTo8(accum[3]);
+
+ // Make sure the alpha channel doesn't come out smaller than any of the
+ // color channels. We use premultipled alpha channels, so this should
+ // never happen, but rounding errors will cause this from time to time.
+ // These "impossible" colors will cause overflows (and hence random pixel
+ // values) when the resulting bitmap is drawn to the screen.
+ //
+ // We only need to do this when generating the final output row (here).
+ int maxColorChannel = SkTMax(outRow[byteOffset + 0],
+ SkTMax(outRow[byteOffset + 1],
+ outRow[byteOffset + 2]));
+ if (alpha < maxColorChannel) {
+ outRow[byteOffset + 3] = maxColorChannel;
+ } else {
+ outRow[byteOffset + 3] = alpha;
+ }
+ } else {
+ // No alpha channel, the image is opaque.
+ outRow[byteOffset + 3] = 0xff;
+ }
+ }
+ }
+
+ void ConvolveVertically(const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength,
+ unsigned char* const* sourceDataRows,
+ int pixelWidth,
+ unsigned char* outRow,
+ bool sourceHasAlpha) {
+ if (sourceHasAlpha) {
+ ConvolveVertically<true>(filterValues, filterLength,
+ sourceDataRows, pixelWidth,
+ outRow);
+ } else {
+ ConvolveVertically<false>(filterValues, filterLength,
+ sourceDataRows, pixelWidth,
+ outRow);
+ }
+ }
+
+} // namespace
+
+// SkConvolutionFilter1D ---------------------------------------------------------
+
+SkConvolutionFilter1D::SkConvolutionFilter1D()
+: fMaxFilter(0) {
+}
+
+SkConvolutionFilter1D::~SkConvolutionFilter1D() {
+}
+
+void SkConvolutionFilter1D::AddFilter(int filterOffset,
+ const float* filterValues,
+ int filterLength) {
+ SkASSERT(filterLength > 0);
+
+ SkTArray<ConvolutionFixed> fixedValues;
+ fixedValues.reset(filterLength);
+
+ for (int i = 0; i < filterLength; ++i) {
+ fixedValues.push_back(FloatToFixed(filterValues[i]));
+ }
+
+ AddFilter(filterOffset, &fixedValues[0], filterLength);
+}
+
+void SkConvolutionFilter1D::AddFilter(int filterOffset,
+ const ConvolutionFixed* filterValues,
+ int filterLength) {
+ // It is common for leading/trailing filter values to be zeros. In such
+ // cases it is beneficial to only store the central factors.
+ // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on
+ // a 1080p image this optimization gives a ~10% speed improvement.
+ int filterSize = filterLength;
+ int firstNonZero = 0;
+ while (firstNonZero < filterLength && filterValues[firstNonZero] == 0) {
+ firstNonZero++;
+ }
+
+ if (firstNonZero < filterLength) {
+ // Here we have at least one non-zero factor.
+ int lastNonZero = filterLength - 1;
+ while (lastNonZero >= 0 && filterValues[lastNonZero] == 0) {
+ lastNonZero--;
+ }
+
+ filterOffset += firstNonZero;
+ filterLength = lastNonZero + 1 - firstNonZero;
+ SkASSERT(filterLength > 0);
+
+ for (int i = firstNonZero; i <= lastNonZero; i++) {
+ fFilterValues.push_back(filterValues[i]);
+ }
+ } else {
+ // Here all the factors were zeroes.
+ filterLength = 0;
+ }
+
+ FilterInstance instance;
+
+ // We pushed filterLength elements onto fFilterValues
+ instance.fDataLocation = (static_cast<int>(fFilterValues.count()) -
+ filterLength);
+ instance.fOffset = filterOffset;
+ instance.fTrimmedLength = filterLength;
+ instance.fLength = filterSize;
+ fFilters.push_back(instance);
+
+ fMaxFilter = SkTMax(fMaxFilter, filterLength);
+}
+
+const SkConvolutionFilter1D::ConvolutionFixed* SkConvolutionFilter1D::GetSingleFilter(
+ int* specifiedFilterlength,
+ int* filterOffset,
+ int* filterLength) const {
+ const FilterInstance& filter = fFilters[0];
+ *filterOffset = filter.fOffset;
+ *filterLength = filter.fTrimmedLength;
+ *specifiedFilterlength = filter.fLength;
+ if (filter.fTrimmedLength == 0) {
+ return NULL;
+ }
+
+ return &fFilterValues[filter.fDataLocation];
+}
+
+void BGRAConvolve2D(const unsigned char* sourceData,
+ int sourceByteRowStride,
+ bool sourceHasAlpha,
+ const SkConvolutionFilter1D& filterX,
+ const SkConvolutionFilter1D& filterY,
+ int outputByteRowStride,
+ unsigned char* output,
+ SkConvolutionProcs* convolveProcs,
+ bool useSimdIfPossible) {
+
+ int maxYFilterSize = filterY.maxFilter();
+
+ // The next row in the input that we will generate a horizontally
+ // convolved row for. If the filter doesn't start at the beginning of the
+ // image (this is the case when we are only resizing a subset), then we
+ // don't want to generate any output rows before that. Compute the starting
+ // row for convolution as the first pixel for the first vertical filter.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filterY.FilterForValue(0, &filterOffset, &filterLength);
+ int nextXRow = filterOffset;
+
+ // We loop over each row in the input doing a horizontal convolution. This
+ // will result in a horizontally convolved image. We write the results into
+ // a circular buffer of convolved rows and do vertical convolution as rows
+ // are available. This prevents us from having to store the entire
+ // intermediate image and helps cache coherency.
+ // We will need four extra rows to allow horizontal convolution could be done
+ // simultaneously. We also pad each row in row buffer to be aligned-up to
+ // 16 bytes.
+ // TODO(jiesun): We do not use aligned load from row buffer in vertical
+ // convolution pass yet. Somehow Windows does not like it.
+ int rowBufferWidth = (filterX.numValues() + 15) & ~0xF;
+ int rowBufferHeight = maxYFilterSize +
+ (convolveProcs->fConvolve4RowsHorizontally ? 4 : 0);
+ CircularRowBuffer rowBuffer(rowBufferWidth,
+ rowBufferHeight,
+ filterOffset);
+
+ // Loop over every possible output row, processing just enough horizontal
+ // convolutions to run each subsequent vertical convolution.
+ SkASSERT(outputByteRowStride >= filterX.numValues() * 4);
+ int numOutputRows = filterY.numValues();
+
+ // We need to check which is the last line to convolve before we advance 4
+ // lines in one iteration.
+ int lastFilterOffset, lastFilterLength;
+
+ // SSE2 can access up to 3 extra pixels past the end of the
+ // buffer. At the bottom of the image, we have to be careful
+ // not to access data past the end of the buffer. Normally
+ // we fall back to the C++ implementation for the last row.
+ // If the last row is less than 3 pixels wide, we may have to fall
+ // back to the C++ version for more rows. Compute how many
+ // rows we need to avoid the SSE implementation for here.
+ filterX.FilterForValue(filterX.numValues() - 1, &lastFilterOffset,
+ &lastFilterLength);
+ int avoidSimdRows = 1 + convolveProcs->fExtraHorizontalReads /
+ (lastFilterOffset + lastFilterLength);
+
+ filterY.FilterForValue(numOutputRows - 1, &lastFilterOffset,
+ &lastFilterLength);
+
+ for (int outY = 0; outY < numOutputRows; outY++) {
+ filterValues = filterY.FilterForValue(outY,
+ &filterOffset, &filterLength);
+
+ // Generate output rows until we have enough to run the current filter.
+ while (nextXRow < filterOffset + filterLength) {
+ if (convolveProcs->fConvolve4RowsHorizontally &&
+ nextXRow + 3 < lastFilterOffset + lastFilterLength -
+ avoidSimdRows) {
+ const unsigned char* src[4];
+ unsigned char* outRow[4];
+ for (int i = 0; i < 4; ++i) {
+ src[i] = &sourceData[(nextXRow + i) * sourceByteRowStride];
+ outRow[i] = rowBuffer.advanceRow();
+ }
+ convolveProcs->fConvolve4RowsHorizontally(src, filterX, outRow);
+ nextXRow += 4;
+ } else {
+ // Check if we need to avoid SSE2 for this row.
+ if (convolveProcs->fConvolveHorizontally &&
+ nextXRow < lastFilterOffset + lastFilterLength -
+ avoidSimdRows) {
+ convolveProcs->fConvolveHorizontally(
+ &sourceData[nextXRow * sourceByteRowStride],
+ filterX, rowBuffer.advanceRow(), sourceHasAlpha);
+ } else {
+ if (sourceHasAlpha) {
+ ConvolveHorizontally<true>(
+ &sourceData[nextXRow * sourceByteRowStride],
+ filterX, rowBuffer.advanceRow());
+ } else {
+ ConvolveHorizontally<false>(
+ &sourceData[nextXRow * sourceByteRowStride],
+ filterX, rowBuffer.advanceRow());
+ }
+ }
+ nextXRow++;
+ }
+ }
+
+ // Compute where in the output image this row of final data will go.
+ unsigned char* curOutputRow = &output[outY * outputByteRowStride];
+
+ // Get the list of rows that the circular buffer has, in order.
+ int firstRowInCircularBuffer;
+ unsigned char* const* rowsToConvolve =
+ rowBuffer.GetRowAddresses(&firstRowInCircularBuffer);
+
+ // Now compute the start of the subset of those rows that the filter
+ // needs.
+ unsigned char* const* firstRowForFilter =
+ &rowsToConvolve[filterOffset - firstRowInCircularBuffer];
+
+ if (convolveProcs->fConvolveVertically) {
+ convolveProcs->fConvolveVertically(filterValues, filterLength,
+ firstRowForFilter,
+ filterX.numValues(), curOutputRow,
+ sourceHasAlpha);
+ } else {
+ ConvolveVertically(filterValues, filterLength,
+ firstRowForFilter,
+ filterX.numValues(), curOutputRow,
+ sourceHasAlpha);
+ }
+ }
+}
diff --git a/core/SkConvolver.h b/core/SkConvolver.h
new file mode 100644
index 00000000..4327afaf
--- /dev/null
+++ b/core/SkConvolver.h
@@ -0,0 +1,203 @@
+// 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.
+
+#ifndef SK_CONVOLVER_H
+#define SK_CONVOLVER_H
+
+#include "SkSize.h"
+#include "SkTypes.h"
+#include "SkTArray.h"
+
+// avoid confusion with Mac OS X's math library (Carbon)
+#if defined(__APPLE__)
+#undef FloatToConvolutionFixed
+#undef ConvolutionFixedToFloat
+#endif
+
+// Represents a filter in one dimension. Each output pixel has one entry in this
+// object for the filter values contributing to it. You build up the filter
+// list by calling AddFilter for each output pixel (in order).
+//
+// We do 2-dimensional convolution by first convolving each row by one
+// SkConvolutionFilter1D, then convolving each column by another one.
+//
+// Entries are stored in ConvolutionFixed point, shifted left by kShiftBits.
+class SkConvolutionFilter1D {
+public:
+ typedef short ConvolutionFixed;
+
+ // The number of bits that ConvolutionFixed point values are shifted by.
+ enum { kShiftBits = 14 };
+
+ SK_API SkConvolutionFilter1D();
+ SK_API ~SkConvolutionFilter1D();
+
+ // Convert between floating point and our ConvolutionFixed point representation.
+ static ConvolutionFixed FloatToFixed(float f) {
+ return static_cast<ConvolutionFixed>(f * (1 << kShiftBits));
+ }
+ static unsigned char FixedToChar(ConvolutionFixed x) {
+ return static_cast<unsigned char>(x >> kShiftBits);
+ }
+ static float FixedToFloat(ConvolutionFixed x) {
+ // The cast relies on ConvolutionFixed being a short, implying that on
+ // the platforms we care about all (16) bits will fit into
+ // the mantissa of a (32-bit) float.
+ SK_COMPILE_ASSERT(sizeof(ConvolutionFixed) == 2, ConvolutionFixed_type_should_fit_in_float_mantissa);
+ float raw = static_cast<float>(x);
+ return ldexpf(raw, -kShiftBits);
+ }
+
+ // Returns the maximum pixel span of a filter.
+ int maxFilter() const { return fMaxFilter; }
+
+ // Returns the number of filters in this filter. This is the dimension of the
+ // output image.
+ int numValues() const { return static_cast<int>(fFilters.count()); }
+
+ // Appends the given list of scaling values for generating a given output
+ // pixel. |filterOffset| is the distance from the edge of the image to where
+ // the scaling factors start. The scaling factors apply to the source pixels
+ // starting from this position, and going for the next |filterLength| pixels.
+ //
+ // You will probably want to make sure your input is normalized (that is,
+ // all entries in |filterValuesg| sub to one) to prevent affecting the overall
+ // brighness of the image.
+ //
+ // The filterLength must be > 0.
+ //
+ // This version will automatically convert your input to ConvolutionFixed point.
+ SK_API void AddFilter(int filterOffset,
+ const float* filterValues,
+ int filterLength);
+
+ // Same as the above version, but the input is already ConvolutionFixed point.
+ void AddFilter(int filterOffset,
+ const ConvolutionFixed* filterValues,
+ int filterLength);
+
+ // Retrieves a filter for the given |valueOffset|, a position in the output
+ // image in the direction we're convolving. The offset and length of the
+ // filter values are put into the corresponding out arguments (see AddFilter
+ // above for what these mean), and a pointer to the first scaling factor is
+ // returned. There will be |filterLength| values in this array.
+ inline const ConvolutionFixed* FilterForValue(int valueOffset,
+ int* filterOffset,
+ int* filterLength) const {
+ const FilterInstance& filter = fFilters[valueOffset];
+ *filterOffset = filter.fOffset;
+ *filterLength = filter.fTrimmedLength;
+ if (filter.fTrimmedLength == 0) {
+ return NULL;
+ }
+ return &fFilterValues[filter.fDataLocation];
+ }
+
+ // Retrieves the filter for the offset 0, presumed to be the one and only.
+ // The offset and length of the filter values are put into the corresponding
+ // out arguments (see AddFilter). Note that |filterLegth| and
+ // |specifiedFilterLength| may be different if leading/trailing zeros of the
+ // original floating point form were clipped.
+ // There will be |filterLength| values in the return array.
+ // Returns NULL if the filter is 0-length (for instance when all floating
+ // point values passed to AddFilter were clipped to 0).
+ SK_API const ConvolutionFixed* GetSingleFilter(int* specifiedFilterLength,
+ int* filterOffset,
+ int* filterLength) const;
+
+ // Add another value to the fFilterValues array -- useful for
+ // SIMD padding which happens outside of this class.
+
+ void addFilterValue( ConvolutionFixed val ) {
+ fFilterValues.push_back( val );
+ }
+private:
+ struct FilterInstance {
+ // Offset within filterValues for this instance of the filter.
+ int fDataLocation;
+
+ // Distance from the left of the filter to the center. IN PIXELS
+ int fOffset;
+
+ // Number of values in this filter instance.
+ int fTrimmedLength;
+
+ // Filter length as specified. Note that this may be different from
+ // 'trimmed_length' if leading/trailing zeros of the original floating
+ // point form were clipped differently on each tail.
+ int fLength;
+ };
+
+ // Stores the information for each filter added to this class.
+ SkTArray<FilterInstance> fFilters;
+
+ // We store all the filter values in this flat list, indexed by
+ // |FilterInstance.data_location| to avoid the mallocs required for storing
+ // each one separately.
+ SkTArray<ConvolutionFixed> fFilterValues;
+
+ // The maximum size of any filter we've added.
+ int fMaxFilter;
+};
+
+typedef void (*SkConvolveVertically_pointer)(
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength,
+ unsigned char* const* sourceDataRows,
+ int pixelWidth,
+ unsigned char* outRow,
+ bool hasAlpha);
+typedef void (*SkConvolve4RowsHorizontally_pointer)(
+ const unsigned char* srcData[4],
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow[4]);
+typedef void (*SkConvolveHorizontally_pointer)(
+ const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow,
+ bool hasAlpha);
+typedef void (*SkConvolveFilterPadding_pointer)(
+ SkConvolutionFilter1D* filter);
+
+struct SkConvolutionProcs {
+ // This is how many extra pixels may be read by the
+ // conolve*horizontally functions.
+ int fExtraHorizontalReads;
+ SkConvolveVertically_pointer fConvolveVertically;
+ SkConvolve4RowsHorizontally_pointer fConvolve4RowsHorizontally;
+ SkConvolveHorizontally_pointer fConvolveHorizontally;
+ SkConvolveFilterPadding_pointer fApplySIMDPadding;
+};
+
+
+
+// Does a two-dimensional convolution on the given source image.
+//
+// It is assumed the source pixel offsets referenced in the input filters
+// reference only valid pixels, so the source image size is not required. Each
+// row of the source image starts |sourceByteRowStride| after the previous
+// one (this allows you to have rows with some padding at the end).
+//
+// The result will be put into the given output buffer. The destination image
+// size will be xfilter.numValues() * yfilter.numValues() pixels. It will be
+// in rows of exactly xfilter.numValues() * 4 bytes.
+//
+// |sourceHasAlpha| is a hint that allows us to avoid doing computations on
+// the alpha channel if the image is opaque. If you don't know, set this to
+// true and it will work properly, but setting this to false will be a few
+// percent faster if you know the image is opaque.
+//
+// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
+// (this is ARGB when loaded into 32-bit words on a little-endian machine).
+SK_API void BGRAConvolve2D(const unsigned char* sourceData,
+ int sourceByteRowStride,
+ bool sourceHasAlpha,
+ const SkConvolutionFilter1D& xfilter,
+ const SkConvolutionFilter1D& yfilter,
+ int outputByteRowStride,
+ unsigned char* output,
+ SkConvolutionProcs* convolveProcs,
+ bool useSimdIfPossible);
+
+#endif // SK_CONVOLVER_H
diff --git a/core/SkCordic.cpp b/core/SkCordic.cpp
new file mode 100644
index 00000000..3adc92fa
--- /dev/null
+++ b/core/SkCordic.cpp
@@ -0,0 +1,289 @@
+/*
+ * 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 "SkCordic.h"
+#include "SkMathPriv.h"
+#include "Sk64.h"
+
+// 0x20000000 equals pi / 4
+const int32_t kATanDegrees[] = { 0x20000000,
+ 0x12E4051D, 0x9FB385B, 0x51111D4, 0x28B0D43, 0x145D7E1, 0xA2F61E, 0x517C55,
+ 0x28BE53, 0x145F2E, 0xA2F98, 0x517CC, 0x28BE6, 0x145F3, 0xA2F9, 0x517C,
+ 0x28BE, 0x145F, 0xA2F, 0x517, 0x28B, 0x145, 0xA2, 0x51, 0x28, 0x14,
+ 0xA, 0x5, 0x2, 0x1 };
+
+const int32_t kFixedInvGain1 = 0x18bde0bb; // 0.607252935
+
+static void SkCircularRotation(int32_t* x0, int32_t* y0, int32_t* z0)
+{
+ int32_t t = 0;
+ int32_t x = *x0;
+ int32_t y = *y0;
+ int32_t z = *z0;
+ const int32_t* tanPtr = kATanDegrees;
+ do {
+ int32_t x1 = y >> t;
+ int32_t y1 = x >> t;
+ int32_t tan = *tanPtr++;
+ if (z >= 0) {
+ x -= x1;
+ y += y1;
+ z -= tan;
+ } else {
+ x += x1;
+ y -= y1;
+ z += tan;
+ }
+ } while (++t < 16); // 30);
+ *x0 = x;
+ *y0 = y;
+ *z0 = z;
+}
+
+SkFixed SkCordicSinCos(SkFixed radians, SkFixed* cosp)
+{
+ int32_t scaledRadians = radians * 0x28be; // scale radians to 65536 / PI()
+ int quadrant = scaledRadians >> 30;
+ quadrant += 1;
+ if (quadrant & 2)
+ scaledRadians = -scaledRadians + 0x80000000;
+ /* |a| <= 90 degrees as a 1.31 number */
+ SkFixed sin = 0;
+ SkFixed cos = kFixedInvGain1;
+ SkCircularRotation(&cos, &sin, &scaledRadians);
+ Sk64 scaled;
+ scaled.setMul(sin, 0x6488d);
+ sin = scaled.fHi;
+ scaled.setMul(cos, 0x6488d);
+ if (quadrant & 2)
+ scaled.fHi = - scaled.fHi;
+ *cosp = scaled.fHi;
+ return sin;
+}
+
+SkFixed SkCordicTan(SkFixed a)
+{
+ int32_t cos;
+ int32_t sin = SkCordicSinCos(a, &cos);
+ return SkFixedDiv(sin, cos);
+}
+
+static int32_t SkCircularVector(int32_t* y0, int32_t* x0, int32_t vecMode)
+{
+ int32_t x = *x0;
+ int32_t y = *y0;
+ int32_t z = 0;
+ int32_t t = 0;
+ const int32_t* tanPtr = kATanDegrees;
+ do {
+ int32_t x1 = y >> t;
+ int32_t y1 = x >> t;
+ int32_t tan = *tanPtr++;
+ if (y < vecMode) {
+ x -= x1;
+ y += y1;
+ z -= tan;
+ } else {
+ x += x1;
+ y -= y1;
+ z += tan;
+ }
+ } while (++t < 16); // 30
+ Sk64 scaled;
+ scaled.setMul(z, 0x6488d); // scale back into the SkScalar space (0x100000000/0x28be)
+ return scaled.fHi;
+}
+
+SkFixed SkCordicASin(SkFixed a) {
+ int32_t sign = SkExtractSign(a);
+ int32_t z = SkFixedAbs(a);
+ if (z >= SK_Fixed1)
+ return SkApplySign(SK_FixedPI>>1, sign);
+ int32_t x = kFixedInvGain1;
+ int32_t y = 0;
+ z *= 0x28be;
+ z = SkCircularVector(&y, &x, z);
+ z = SkApplySign(z, ~sign);
+ return z;
+}
+
+SkFixed SkCordicACos(SkFixed a) {
+ int32_t z = SkCordicASin(a);
+ z = (SK_FixedPI>>1) - z;
+ return z;
+}
+
+SkFixed SkCordicATan2(SkFixed y, SkFixed x) {
+ if ((x | y) == 0)
+ return 0;
+ int32_t xsign = SkExtractSign(x);
+ x = SkFixedAbs(x);
+ int32_t result = SkCircularVector(&y, &x, 0);
+ if (xsign) {
+ int32_t rsign = SkExtractSign(result);
+ if (y == 0)
+ rsign = 0;
+ SkFixed pi = SkApplySign(SK_FixedPI, rsign);
+ result = pi - result;
+ }
+ return result;
+}
+
+const int32_t kATanHDegrees[] = {
+ 0x1661788D, 0xA680D61, 0x51EA6FC, 0x28CBFDD, 0x1460E34,
+ 0xA2FCE8, 0x517D2E, 0x28BE6E, 0x145F32,
+ 0xA2F98, 0x517CC, 0x28BE6, 0x145F3, 0xA2F9, 0x517C,
+ 0x28BE, 0x145F, 0xA2F, 0x517, 0x28B, 0x145, 0xA2, 0x51, 0x28, 0x14,
+ 0xA, 0x5, 0x2, 0x1 };
+
+const int32_t kFixedInvGain2 = 0x31330AAA; // 1.207534495
+
+static void SkHyperbolic(int32_t* x0, int32_t* y0, int32_t* z0, int mode)
+{
+ int32_t t = 1;
+ int32_t x = *x0;
+ int32_t y = *y0;
+ int32_t z = *z0;
+ const int32_t* tanPtr = kATanHDegrees;
+ int k = -3;
+ do {
+ int32_t x1 = y >> t;
+ int32_t y1 = x >> t;
+ int32_t tan = *tanPtr++;
+ int count = 2 + (k >> 31);
+ if (++k == 1)
+ k = -2;
+ do {
+ if (((y >> 31) & mode) | ~((z >> 31) | mode)) {
+ x += x1;
+ y += y1;
+ z -= tan;
+ } else {
+ x -= x1;
+ y -= y1;
+ z += tan;
+ }
+ } while (--count);
+ } while (++t < 30);
+ *x0 = x;
+ *y0 = y;
+ *z0 = z;
+}
+
+SkFixed SkCordicLog(SkFixed a) {
+ a *= 0x28be;
+ int32_t x = a + 0x28BE60DB; // 1.0
+ int32_t y = a - 0x28BE60DB;
+ int32_t z = 0;
+ SkHyperbolic(&x, &y, &z, -1);
+ Sk64 scaled;
+ scaled.setMul(z, 0x6488d);
+ z = scaled.fHi;
+ return z << 1;
+}
+
+SkFixed SkCordicExp(SkFixed a) {
+ int32_t cosh = kFixedInvGain2;
+ int32_t sinh = 0;
+ SkHyperbolic(&cosh, &sinh, &a, 0);
+ return cosh + sinh;
+}
+
+#ifdef SK_DEBUG
+
+#include "SkFloatingPoint.h"
+
+void SkCordic_UnitTest()
+{
+#if defined(SK_SUPPORT_UNITTEST)
+ float val;
+ for (float angle = -720; angle < 720; angle += 30) {
+ float radian = angle * 3.1415925358f / 180.0f;
+ SkFixed f_angle = SkFloatToFixed(radian);
+ // sincos
+ float sine = sinf(radian);
+ float cosine = cosf(radian);
+ SkFixed f_cosine;
+ SkFixed f_sine = SkCordicSinCos(f_angle, &f_cosine);
+ float sine2 = (float) f_sine / 65536.0f;
+ float cosine2 = (float) f_cosine / 65536.0f;
+ float error = fabsf(sine - sine2);
+ if (error > 0.001)
+ SkDebugf("sin error : angle = %g ; sin = %g ; cordic = %g\n", angle, sine, sine2);
+ error = fabsf(cosine - cosine2);
+ if (error > 0.001)
+ SkDebugf("cos error : angle = %g ; cos = %g ; cordic = %g\n", angle, cosine, cosine2);
+ // tan
+ float _tan = tanf(radian);
+ SkFixed f_tan = SkCordicTan(f_angle);
+ float tan2 = (float) f_tan / 65536.0f;
+ error = fabsf(_tan - tan2);
+ if (error > 0.05 && fabsf(_tan) < 1e6)
+ SkDebugf("tan error : angle = %g ; tan = %g ; cordic = %g\n", angle, _tan, tan2);
+ }
+ for (val = -1; val <= 1; val += .1f) {
+ SkFixed f_val = SkFloatToFixed(val);
+ // asin
+ float arcsine = asinf(val);
+ SkFixed f_arcsine = SkCordicASin(f_val);
+ float arcsine2 = (float) f_arcsine / 65536.0f;
+ float error = fabsf(arcsine - arcsine2);
+ if (error > 0.001)
+ SkDebugf("asin error : val = %g ; asin = %g ; cordic = %g\n", val, arcsine, arcsine2);
+ }
+#if 1
+ for (val = -1; val <= 1; val += .1f) {
+#else
+ val = .5; {
+#endif
+ SkFixed f_val = SkFloatToFixed(val);
+ // acos
+ float arccos = acosf(val);
+ SkFixed f_arccos = SkCordicACos(f_val);
+ float arccos2 = (float) f_arccos / 65536.0f;
+ float error = fabsf(arccos - arccos2);
+ if (error > 0.001)
+ SkDebugf("acos error : val = %g ; acos = %g ; cordic = %g\n", val, arccos, arccos2);
+ }
+ // atan2
+#if 1
+ for (val = -1000; val <= 1000; val += 500.f) {
+ for (float val2 = -1000; val2 <= 1000; val2 += 500.f) {
+#else
+ val = 0; {
+ float val2 = -1000; {
+#endif
+ SkFixed f_val = SkFloatToFixed(val);
+ SkFixed f_val2 = SkFloatToFixed(val2);
+ float arctan = atan2f(val, val2);
+ SkFixed f_arctan = SkCordicATan2(f_val, f_val2);
+ float arctan2 = (float) f_arctan / 65536.0f;
+ float error = fabsf(arctan - arctan2);
+ if (error > 0.001)
+ SkDebugf("atan2 error : val = %g ; val2 = %g ; atan2 = %g ; cordic = %g\n", val, val2, arctan, arctan2);
+ }
+ }
+ // log
+#if 1
+ for (val = 0.125f; val <= 8.f; val *= 2.0f) {
+#else
+ val = .5; {
+#endif
+ SkFixed f_val = SkFloatToFixed(val);
+ // acos
+ float log = logf(val);
+ SkFixed f_log = SkCordicLog(f_val);
+ float log2 = (float) f_log / 65536.0f;
+ float error = fabsf(log - log2);
+ if (error > 0.001)
+ SkDebugf("log error : val = %g ; log = %g ; cordic = %g\n", val, log, log2);
+ }
+ // exp
+#endif
+}
+
+#endif
diff --git a/core/SkCordic.h b/core/SkCordic.h
new file mode 100644
index 00000000..fecf6456
--- /dev/null
+++ b/core/SkCordic.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkCordic_DEFINED
+#define SkCordic_DEFINED
+
+#include "SkTypes.h"
+#include "SkFixed.h"
+
+SkFixed SkCordicACos(SkFixed a);
+SkFixed SkCordicASin(SkFixed a);
+SkFixed SkCordicATan2(SkFixed y, SkFixed x);
+SkFixed SkCordicExp(SkFixed a);
+SkFixed SkCordicLog(SkFixed a);
+SkFixed SkCordicSinCos(SkFixed radians, SkFixed* cosp);
+SkFixed SkCordicTan(SkFixed a);
+
+#ifdef SK_DEBUG
+ void SkCordic_UnitTest();
+#endif
+
+#endif // SkCordic
diff --git a/core/SkCoreBlitters.h b/core/SkCoreBlitters.h
new file mode 100644
index 00000000..9455509c
--- /dev/null
+++ b/core/SkCoreBlitters.h
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+#ifndef SkCoreBlitters_DEFINED
+#define SkCoreBlitters_DEFINED
+
+#include "SkBlitter.h"
+#include "SkBlitRow.h"
+
+class SkRasterBlitter : public SkBlitter {
+public:
+ SkRasterBlitter(const SkBitmap& device) : fDevice(device) {}
+
+protected:
+ const SkBitmap& fDevice;
+
+private:
+ typedef SkBlitter INHERITED;
+};
+
+class SkShaderBlitter : public SkRasterBlitter {
+public:
+ SkShaderBlitter(const SkBitmap& device, const SkPaint& paint);
+ virtual ~SkShaderBlitter();
+
+protected:
+ uint32_t fShaderFlags;
+ SkShader* fShader;
+
+private:
+ // illegal
+ SkShaderBlitter& operator=(const SkShaderBlitter&);
+
+ typedef SkRasterBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkA8_Blitter : public SkRasterBlitter {
+public:
+ SkA8_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ virtual void blitRect(int x, int y, int width, int height);
+ virtual void blitMask(const SkMask&, const SkIRect&);
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t*);
+
+private:
+ unsigned fSrcA;
+
+ // illegal
+ SkA8_Blitter& operator=(const SkA8_Blitter&);
+
+ typedef SkRasterBlitter INHERITED;
+};
+
+class SkA8_Shader_Blitter : public SkShaderBlitter {
+public:
+ SkA8_Shader_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual ~SkA8_Shader_Blitter();
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
+ virtual void blitMask(const SkMask&, const SkIRect&);
+
+private:
+ SkXfermode* fXfermode;
+ SkPMColor* fBuffer;
+ uint8_t* fAAExpand;
+
+ // illegal
+ SkA8_Shader_Blitter& operator=(const SkA8_Shader_Blitter&);
+
+ typedef SkShaderBlitter INHERITED;
+};
+
+////////////////////////////////////////////////////////////////
+
+class SkARGB32_Blitter : public SkRasterBlitter {
+public:
+ SkARGB32_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ virtual void blitRect(int x, int y, int width, int height);
+ virtual void blitMask(const SkMask&, const SkIRect&);
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t*);
+
+protected:
+ SkColor fColor;
+ SkPMColor fPMColor;
+ SkBlitRow::ColorProc fColor32Proc;
+ SkBlitRow::ColorRectProc fColorRect32Proc;
+
+private:
+ unsigned fSrcA, fSrcR, fSrcG, fSrcB;
+
+ // illegal
+ SkARGB32_Blitter& operator=(const SkARGB32_Blitter&);
+
+ typedef SkRasterBlitter INHERITED;
+};
+
+class SkARGB32_Opaque_Blitter : public SkARGB32_Blitter {
+public:
+ SkARGB32_Opaque_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device, paint) { SkASSERT(paint.getAlpha() == 0xFF); }
+ virtual void blitMask(const SkMask&, const SkIRect&);
+
+private:
+ typedef SkARGB32_Blitter INHERITED;
+};
+
+class SkARGB32_Black_Blitter : public SkARGB32_Opaque_Blitter {
+public:
+ SkARGB32_Black_Blitter(const SkBitmap& device, const SkPaint& paint)
+ : INHERITED(device, paint) {}
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
+
+private:
+ typedef SkARGB32_Opaque_Blitter INHERITED;
+};
+
+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) 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[], 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&);
+
+ typedef SkShaderBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkA1_Blitter : public SkRasterBlitter {
+public:
+ SkA1_Blitter(const SkBitmap& device, const SkPaint& paint);
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+
+private:
+ uint8_t fSrcA;
+
+ // illegal
+ SkA1_Blitter& operator=(const SkA1_Blitter&);
+
+ typedef SkRasterBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* These return the correct subclass of blitter for their device config.
+
+ Currently, they make the following assumptions about the state of the
+ paint:
+
+ 1. If there is an xfermode, there will also be a shader
+ 2. If there is a colorfilter, there will be a shader that itself handles
+ calling the filter, so the blitter can always ignore the colorfilter obj
+
+ These pre-conditions must be handled by the caller, in our case
+ SkBlitter::Choose(...)
+ */
+
+extern SkBlitter* SkBlitter_ChooseD565(const SkBitmap& device,
+ const SkPaint& paint,
+ void* storage, size_t storageSize);
+
+#endif
diff --git a/core/SkCubicClipper.cpp b/core/SkCubicClipper.cpp
new file mode 100644
index 00000000..aed681b9
--- /dev/null
+++ b/core/SkCubicClipper.cpp
@@ -0,0 +1,162 @@
+
+/*
+ * Copyright 2009 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 "SkCubicClipper.h"
+#include "SkGeometry.h"
+
+SkCubicClipper::SkCubicClipper() {
+ fClip.setEmpty();
+}
+
+void SkCubicClipper::setClip(const SkIRect& clip) {
+ // conver to scalars, since that's where we'll see the points
+ fClip.set(clip);
+}
+
+
+static bool chopMonoCubicAtY(SkPoint pts[4], SkScalar y, SkScalar* t) {
+ SkScalar ycrv[4];
+ ycrv[0] = pts[0].fY - y;
+ ycrv[1] = pts[1].fY - y;
+ ycrv[2] = pts[2].fY - y;
+ ycrv[3] = pts[3].fY - y;
+
+#ifdef NEWTON_RAPHSON // Quadratic convergence, typically <= 3 iterations.
+ // Initial guess.
+ // TODO(turk): Check for zero denominator? Shouldn't happen unless the curve
+ // is not only monotonic but degenerate.
+#ifdef SK_SCALAR_IS_FLOAT
+ SkScalar t1 = ycrv[0] / (ycrv[0] - ycrv[3]);
+#else // !SK_SCALAR_IS_FLOAT
+ SkScalar t1 = SkDivBits(ycrv[0], ycrv[0] - ycrv[3], 16);
+#endif // !SK_SCALAR_IS_FLOAT
+
+ // Newton's iterations.
+ const SkScalar tol = SK_Scalar1 / 16384; // This leaves 2 fixed noise bits.
+ SkScalar t0;
+ const int maxiters = 5;
+ int iters = 0;
+ bool converged;
+ do {
+ t0 = t1;
+ SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], t0);
+ SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], t0);
+ SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], t0);
+ SkScalar y012 = SkScalarInterp(y01, y12, t0);
+ SkScalar y123 = SkScalarInterp(y12, y23, t0);
+ SkScalar y0123 = SkScalarInterp(y012, y123, t0);
+ SkScalar yder = (y123 - y012) * 3;
+ // TODO(turk): check for yder==0: horizontal.
+#ifdef SK_SCALAR_IS_FLOAT
+ t1 -= y0123 / yder;
+#else // !SK_SCALAR_IS_FLOAT
+ t1 -= SkDivBits(y0123, yder, 16);
+#endif // !SK_SCALAR_IS_FLOAT
+ converged = SkScalarAbs(t1 - t0) <= tol; // NaN-safe
+ ++iters;
+ } while (!converged && (iters < maxiters));
+ *t = t1; // Return the result.
+
+ // The result might be valid, even if outside of the range [0, 1], but
+ // we never evaluate a Bezier outside this interval, so we return false.
+ if (t1 < 0 || t1 > SK_Scalar1)
+ return false; // This shouldn't happen, but check anyway.
+ return converged;
+
+#else // BISECTION // Linear convergence, typically 16 iterations.
+
+ // Check that the endpoints straddle zero.
+ SkScalar tNeg, tPos; // Negative and positive function parameters.
+ if (ycrv[0] < 0) {
+ if (ycrv[3] < 0)
+ return false;
+ tNeg = 0;
+ tPos = SK_Scalar1;
+ } else if (ycrv[0] > 0) {
+ if (ycrv[3] > 0)
+ return false;
+ tNeg = SK_Scalar1;
+ tPos = 0;
+ } else {
+ *t = 0;
+ return true;
+ }
+
+ const SkScalar tol = SK_Scalar1 / 65536; // 1 for fixed, 1e-5 for float.
+ int iters = 0;
+ do {
+ SkScalar tMid = (tPos + tNeg) / 2;
+ SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], tMid);
+ SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], tMid);
+ SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], tMid);
+ SkScalar y012 = SkScalarInterp(y01, y12, tMid);
+ SkScalar y123 = SkScalarInterp(y12, y23, tMid);
+ SkScalar y0123 = SkScalarInterp(y012, y123, tMid);
+ if (y0123 == 0) {
+ *t = tMid;
+ return true;
+ }
+ if (y0123 < 0) tNeg = tMid;
+ else tPos = tMid;
+ ++iters;
+ } while (!(SkScalarAbs(tPos - tNeg) <= tol)); // Nan-safe
+
+ *t = (tNeg + tPos) / 2;
+ return true;
+#endif // BISECTION
+}
+
+
+bool SkCubicClipper::clipCubic(const SkPoint srcPts[4], SkPoint dst[4]) {
+ bool reverse;
+
+ // we need the data to be monotonically descending in Y
+ if (srcPts[0].fY > srcPts[3].fY) {
+ dst[0] = srcPts[3];
+ dst[1] = srcPts[2];
+ dst[2] = srcPts[1];
+ dst[3] = srcPts[0];
+ reverse = true;
+ } else {
+ memcpy(dst, srcPts, 4 * sizeof(SkPoint));
+ reverse = false;
+ }
+
+ // are we completely above or below
+ const SkScalar ctop = fClip.fTop;
+ const SkScalar cbot = fClip.fBottom;
+ if (dst[3].fY <= ctop || dst[0].fY >= cbot) {
+ return false;
+ }
+
+ SkScalar t;
+ SkPoint tmp[7]; // for SkChopCubicAt
+
+ // are we partially above
+ if (dst[0].fY < ctop && chopMonoCubicAtY(dst, ctop, &t)) {
+ SkChopCubicAt(dst, tmp, t);
+ dst[0] = tmp[3];
+ dst[1] = tmp[4];
+ dst[2] = tmp[5];
+ }
+
+ // are we partially below
+ if (dst[3].fY > cbot && chopMonoCubicAtY(dst, cbot, &t)) {
+ SkChopCubicAt(dst, tmp, t);
+ dst[1] = tmp[1];
+ dst[2] = tmp[2];
+ dst[3] = tmp[3];
+ }
+
+ if (reverse) {
+ SkTSwap<SkPoint>(dst[0], dst[3]);
+ SkTSwap<SkPoint>(dst[1], dst[2]);
+ }
+ return true;
+}
diff --git a/core/SkCubicClipper.h b/core/SkCubicClipper.h
new file mode 100644
index 00000000..c52eabe4
--- /dev/null
+++ b/core/SkCubicClipper.h
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright 2009 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 SkCubicClipper_DEFINED
+#define SkCubicClipper_DEFINED
+
+#include "SkPoint.h"
+#include "SkRect.h"
+
+/** This class is initialized with a clip rectangle, and then can be fed cubics,
+ which must already be monotonic in Y.
+
+ In the future, it might return a series of segments, allowing it to clip
+ also in X, to ensure that all segments fit in a finite coordinate system.
+ */
+class SkCubicClipper {
+public:
+ SkCubicClipper();
+
+ void setClip(const SkIRect& clip);
+
+ bool clipCubic(const SkPoint src[4], SkPoint dst[4]);
+
+private:
+ SkRect fClip;
+};
+
+#endif // SkCubicClipper_DEFINED
diff --git a/core/SkData.cpp b/core/SkData.cpp
new file mode 100644
index 00000000..a1b42b0c
--- /dev/null
+++ b/core/SkData.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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 "SkData.h"
+#include "SkFlattenableBuffers.h"
+#include "SkOSFile.h"
+
+SK_DEFINE_INST_COUNT(SkData)
+
+SkData::SkData(const void* ptr, size_t size, ReleaseProc proc, void* context) {
+ fPtr = ptr;
+ fSize = size;
+ fReleaseProc = proc;
+ fReleaseProcContext = context;
+}
+
+SkData::~SkData() {
+ if (fReleaseProc) {
+ fReleaseProc(fPtr, fSize, fReleaseProcContext);
+ }
+}
+
+bool SkData::equals(const SkData* other) const {
+ if (NULL == other) {
+ return false;
+ }
+
+ return fSize == other->fSize && !memcmp(fPtr, other->fPtr, fSize);
+}
+
+size_t SkData::copyRange(size_t offset, size_t length, void* buffer) const {
+ size_t available = fSize;
+ if (offset >= available || 0 == length) {
+ return 0;
+ }
+ available -= offset;
+ if (length > available) {
+ length = available;
+ }
+ SkASSERT(length > 0);
+
+ memcpy(buffer, this->bytes() + offset, length);
+ return length;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkData* SkData::NewEmpty() {
+ static SkData* gEmptyRef;
+ if (NULL == gEmptyRef) {
+ gEmptyRef = new SkData(NULL, 0, NULL, NULL);
+ }
+ gEmptyRef->ref();
+ return gEmptyRef;
+}
+
+// assumes fPtr was allocated via sk_malloc
+static void sk_free_releaseproc(const void* ptr, size_t, void*) {
+ sk_free((void*)ptr);
+}
+
+SkData* SkData::NewFromMalloc(const void* data, size_t length) {
+ return new SkData(data, length, sk_free_releaseproc, NULL);
+}
+
+SkData* SkData::NewWithCopy(const void* data, size_t length) {
+ if (0 == length) {
+ return SkData::NewEmpty();
+ }
+
+ void* copy = sk_malloc_throw(length); // balanced in sk_free_releaseproc
+ memcpy(copy, data, length);
+ return new SkData(copy, length, sk_free_releaseproc, NULL);
+}
+
+SkData* SkData::NewWithProc(const void* data, size_t length,
+ ReleaseProc proc, void* context) {
+ return new SkData(data, length, proc, context);
+}
+
+// assumes fPtr was allocated with sk_fmmap
+static void sk_mmap_releaseproc(const void* addr, size_t length, void*) {
+ sk_fmunmap(addr, length);
+}
+
+SkData* SkData::NewFromFILE(SkFILE* f) {
+ size_t size;
+ void* addr = sk_fmmap(f, &size);
+ if (NULL == addr) {
+ return NULL;
+ }
+
+ return SkData::NewWithProc(addr, size, sk_mmap_releaseproc, NULL);
+}
+
+SkData* SkData::NewFromFileName(const char path[]) {
+ SkFILE* f = path ? sk_fopen(path, kRead_SkFILE_Flag) : NULL;
+ if (NULL == f) {
+ return NULL;
+ }
+ SkData* data = NewFromFILE(f);
+ sk_fclose(f);
+ return data;
+}
+
+SkData* SkData::NewFromFD(int fd) {
+ size_t size;
+ void* addr = sk_fdmmap(fd, &size);
+ if (NULL == addr) {
+ return NULL;
+ }
+
+ return SkData::NewWithProc(addr, size, sk_mmap_releaseproc, NULL);
+}
+
+// assumes context is a SkData
+static void sk_dataref_releaseproc(const void*, size_t, void* context) {
+ SkData* src = reinterpret_cast<SkData*>(context);
+ src->unref();
+}
+
+SkData* SkData::NewSubset(const SkData* src, size_t offset, size_t length) {
+ /*
+ We could, if we wanted/need to, just make a deep copy of src's data,
+ rather than referencing it. This would duplicate the storage (of the
+ subset amount) but would possibly allow src to go out of scope sooner.
+ */
+
+ size_t available = src->size();
+ if (offset >= available || 0 == length) {
+ return SkData::NewEmpty();
+ }
+ available -= offset;
+ if (length > available) {
+ length = available;
+ }
+ SkASSERT(length > 0);
+
+ src->ref(); // this will be balanced in sk_dataref_releaseproc
+ return new SkData(src->bytes() + offset, length, sk_dataref_releaseproc,
+ const_cast<SkData*>(src));
+}
+
+SkData* SkData::NewWithCString(const char cstr[]) {
+ size_t size;
+ if (NULL == cstr) {
+ cstr = "";
+ size = 1;
+ } else {
+ size = strlen(cstr) + 1;
+ }
+ return NewWithCopy(cstr, size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkData::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeByteArray(fPtr, fSize);
+}
+
+SkData::SkData(SkFlattenableReadBuffer& buffer) {
+ fSize = buffer.getArrayCount();
+ fReleaseProcContext = NULL;
+
+ if (fSize > 0) {
+ fPtr = sk_malloc_throw(fSize);
+ fReleaseProc = sk_free_releaseproc;
+ } else {
+ fPtr = NULL;
+ fReleaseProc = NULL;
+ }
+
+ buffer.readByteArray(const_cast<void*>(fPtr));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkDataSet.h"
+#include "SkFlattenable.h"
+#include "SkStream.h"
+
+static SkData* dupdata(SkData* data) {
+ if (data) {
+ data->ref();
+ } else {
+ data = SkData::NewEmpty();
+ }
+ return data;
+}
+
+static SkData* findValue(const char key[], const SkDataSet::Pair array[], int n) {
+ for (int i = 0; i < n; ++i) {
+ if (!strcmp(key, array[i].fKey)) {
+ return array[i].fValue;
+ }
+ }
+ return NULL;
+}
+
+static SkDataSet::Pair* allocatePairStorage(int count, size_t storage) {
+ size_t size = count * sizeof(SkDataSet::Pair) + storage;
+ return (SkDataSet::Pair*)sk_malloc_throw(size);
+}
+
+SkDataSet::SkDataSet(const char key[], SkData* value) {
+ size_t keyLen = strlen(key);
+
+ fCount = 1;
+ fKeySize = keyLen + 1;
+ fPairs = allocatePairStorage(1, keyLen + 1);
+
+ fPairs[0].fKey = (char*)(fPairs + 1);
+ memcpy(const_cast<char*>(fPairs[0].fKey), key, keyLen + 1);
+
+ fPairs[0].fValue = dupdata(value);
+}
+
+SkDataSet::SkDataSet(const Pair array[], int count) {
+ if (count < 1) {
+ fCount = 0;
+ fKeySize = 0;
+ fPairs = NULL;
+ return;
+ }
+
+ int i;
+ size_t keySize = 0;
+ for (i = 0; i < count; ++i) {
+ keySize += strlen(array[i].fKey) + 1;
+ }
+
+ Pair* pairs = fPairs = allocatePairStorage(count, keySize);
+ char* keyStorage = (char*)(pairs + count);
+
+ keySize = 0; // reset this, so we can compute the size for unique keys
+ int uniqueCount = 0;
+ for (int i = 0; i < count; ++i) {
+ if (!findValue(array[i].fKey, pairs, uniqueCount)) {
+ size_t len = strlen(array[i].fKey);
+ memcpy(keyStorage, array[i].fKey, len + 1);
+ pairs[uniqueCount].fKey = keyStorage;
+ keyStorage += len + 1;
+ keySize += len + 1;
+
+ pairs[uniqueCount].fValue = dupdata(array[i].fValue);
+ uniqueCount += 1;
+ }
+ }
+ fCount = uniqueCount;
+ fKeySize = keySize;
+}
+
+SkDataSet::~SkDataSet() {
+ for (int i = 0; i < fCount; ++i) {
+ fPairs[i].fValue->unref();
+ }
+ sk_free(fPairs); // this also frees the key storage
+}
+
+SkData* SkDataSet::find(const char key[]) const {
+ return findValue(key, fPairs, fCount);
+}
+
+void SkDataSet::writeToStream(SkWStream* stream) const {
+ stream->write32(fCount);
+ if (fCount > 0) {
+ stream->write32(fKeySize);
+ // our first key points to all the key storage
+ stream->write(fPairs[0].fKey, fKeySize);
+ for (int i = 0; i < fCount; ++i) {
+ stream->writeData(fPairs[i].fValue);
+ }
+ }
+}
+
+void SkDataSet::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeInt(fCount);
+ if (fCount > 0) {
+ buffer.writeByteArray(fPairs[0].fKey, fKeySize);
+ for (int i = 0; i < fCount; ++i) {
+ buffer.writeFlattenable(fPairs[i].fValue);
+ }
+ }
+}
+
+SkDataSet::SkDataSet(SkStream* stream) {
+ fCount = stream->readU32();
+ if (fCount > 0) {
+ fKeySize = stream->readU32();
+ fPairs = allocatePairStorage(fCount, fKeySize);
+ char* keyStorage = (char*)(fPairs + fCount);
+
+ stream->read(keyStorage, fKeySize);
+
+ for (int i = 0; i < fCount; ++i) {
+ fPairs[i].fKey = keyStorage;
+ keyStorage += strlen(keyStorage) + 1;
+ fPairs[i].fValue = stream->readData();
+ }
+ } else {
+ fKeySize = 0;
+ fPairs = NULL;
+ }
+}
+
+SkDataSet::SkDataSet(SkFlattenableReadBuffer& buffer) {
+ fCount = buffer.readInt();
+ if (fCount > 0) {
+ fKeySize = buffer.getArrayCount();
+ fPairs = allocatePairStorage(fCount, fKeySize);
+ char* keyStorage = (char*)(fPairs + fCount);
+
+ buffer.readByteArray(keyStorage);
+
+ for (int i = 0; i < fCount; ++i) {
+ fPairs[i].fKey = keyStorage;
+ keyStorage += strlen(keyStorage) + 1;
+ fPairs[i].fValue = buffer.readFlattenableT<SkData>();
+ }
+ } else {
+ fKeySize = 0;
+ fPairs = NULL;
+ }
+}
+
+SkDataSet* SkDataSet::NewEmpty() {
+ static SkDataSet* gEmptySet;
+ if (NULL == gEmptySet) {
+ gEmptySet = SkNEW_ARGS(SkDataSet, (NULL, 0));
+ }
+ gEmptySet->ref();
+ return gEmptySet;
+}
diff --git a/core/SkDataTable.cpp b/core/SkDataTable.cpp
new file mode 100644
index 00000000..608d1470
--- /dev/null
+++ b/core/SkDataTable.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2013 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 "SkDataTable.h"
+#include "SkFlattenableBuffers.h"
+
+SK_DEFINE_INST_COUNT(SkDataTable)
+
+static void malloc_freeproc(void* context) {
+ sk_free(context);
+}
+
+// Makes empty table
+SkDataTable::SkDataTable() {
+ fCount = 0;
+ fElemSize = 0; // 0 signals that we use fDir instead of fElems
+ fU.fDir = NULL;
+ fFreeProc = NULL;
+ fFreeProcContext = NULL;
+}
+
+SkDataTable::SkDataTable(const void* array, size_t elemSize, int count,
+ FreeProc proc, void* context) {
+ SkASSERT(count > 0);
+
+ fCount = count;
+ fElemSize = elemSize; // non-zero signals we use fElems instead of fDir
+ fU.fElems = (const char*)array;
+ fFreeProc = proc;
+ fFreeProcContext = context;
+}
+
+SkDataTable::SkDataTable(const Dir* dir, int count, FreeProc proc, void* ctx) {
+ SkASSERT(count > 0);
+
+ fCount = count;
+ fElemSize = 0; // 0 signals that we use fDir instead of fElems
+ fU.fDir = dir;
+ fFreeProc = proc;
+ fFreeProcContext = ctx;
+}
+
+SkDataTable::~SkDataTable() {
+ if (fFreeProc) {
+ fFreeProc(fFreeProcContext);
+ }
+}
+
+size_t SkDataTable::atSize(int index) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+
+ if (fElemSize) {
+ return fElemSize;
+ } else {
+ return fU.fDir[index].fSize;
+ }
+}
+
+const void* SkDataTable::at(int index, size_t* size) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+
+ if (fElemSize) {
+ if (size) {
+ *size = fElemSize;
+ }
+ return fU.fElems + index * fElemSize;
+ } else {
+ if (size) {
+ *size = fU.fDir[index].fSize;
+ }
+ return fU.fDir[index].fPtr;
+ }
+}
+
+SkDataTable::SkDataTable(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fElemSize = 0;
+ fU.fElems = NULL;
+ fFreeProc = NULL;
+ fFreeProcContext = NULL;
+
+ fCount = buffer.read32();
+ if (fCount) {
+ fElemSize = buffer.read32();
+ if (fElemSize) {
+ size_t size = buffer.getArrayCount();
+ // size is the size of our elems data
+ SkASSERT(fCount * fElemSize == size);
+ void* addr = sk_malloc_throw(size);
+ if (buffer.readByteArray(addr) != size) {
+ sk_throw();
+ }
+ fU.fElems = (const char*)addr;
+ fFreeProcContext = addr;
+ } else {
+ size_t dataSize = buffer.read32();
+
+ size_t allocSize = fCount * sizeof(Dir) + dataSize;
+ void* addr = sk_malloc_throw(allocSize);
+ Dir* dir = (Dir*)addr;
+ char* elem = (char*)(dir + fCount);
+ for (int i = 0; i < fCount; ++i) {
+ dir[i].fPtr = elem;
+ dir[i].fSize = buffer.readByteArray(elem);
+ elem += dir[i].fSize;
+ }
+ fU.fDir = dir;
+ fFreeProcContext = addr;
+ }
+ fFreeProc = malloc_freeproc;
+ }
+}
+
+void SkDataTable::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.write32(fCount);
+ if (fCount) {
+ buffer.write32(fElemSize);
+ if (fElemSize) {
+ buffer.writeByteArray(fU.fElems, fCount * fElemSize);
+ } else {
+ size_t dataSize = 0;
+ for (int i = 0; i < fCount; ++i) {
+ dataSize += fU.fDir[i].fSize;
+ }
+ buffer.write32(dataSize);
+ for (int i = 0; i < fCount; ++i) {
+ buffer.writeByteArray(fU.fDir[i].fPtr, fU.fDir[i].fSize);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDataTable* SkDataTable::NewEmpty() {
+ static SkDataTable* gEmpty;
+ if (NULL == gEmpty) {
+ gEmpty = SkNEW(SkDataTable);
+ }
+ gEmpty->ref();
+ return gEmpty;
+}
+
+SkDataTable* SkDataTable::NewCopyArrays(const void * const * ptrs,
+ const size_t sizes[], int count) {
+ if (count <= 0) {
+ return SkDataTable::NewEmpty();
+ }
+
+ size_t dataSize = 0;
+ for (int i = 0; i < count; ++i) {
+ dataSize += sizes[i];
+ }
+
+ size_t bufferSize = count * sizeof(Dir) + dataSize;
+ void* buffer = sk_malloc_throw(bufferSize);
+
+ Dir* dir = (Dir*)buffer;
+ char* elem = (char*)(dir + count);
+ for (int i = 0; i < count; ++i) {
+ dir[i].fPtr = elem;
+ dir[i].fSize = sizes[i];
+ memcpy(elem, ptrs[i], sizes[i]);
+ elem += sizes[i];
+ }
+
+ return SkNEW_ARGS(SkDataTable, (dir, count, malloc_freeproc, buffer));
+}
+
+SkDataTable* SkDataTable::NewCopyArray(const void* array, size_t elemSize,
+ int count) {
+ if (count <= 0) {
+ return SkDataTable::NewEmpty();
+ }
+
+ size_t bufferSize = elemSize * count;
+ void* buffer = sk_malloc_throw(bufferSize);
+ memcpy(buffer, array, bufferSize);
+
+ return SkNEW_ARGS(SkDataTable,
+ (buffer, elemSize, count, malloc_freeproc, buffer));
+}
+
+SkDataTable* SkDataTable::NewArrayProc(const void* array, size_t elemSize,
+ int count, FreeProc proc, void* ctx) {
+ if (count <= 0) {
+ return SkDataTable::NewEmpty();
+ }
+ return SkNEW_ARGS(SkDataTable, (array, elemSize, count, proc, ctx));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void chunkalloc_freeproc(void* context) {
+ SkDELETE((SkChunkAlloc*)context);
+}
+
+SkDataTableBuilder::SkDataTableBuilder(size_t minChunkSize)
+ : fHeap(NULL)
+ , fMinChunkSize(minChunkSize) {}
+
+SkDataTableBuilder::~SkDataTableBuilder() { this->reset(); }
+
+void SkDataTableBuilder::reset(size_t minChunkSize) {
+ fMinChunkSize = minChunkSize;
+ fDir.reset();
+ if (fHeap) {
+ SkDELETE(fHeap);
+ fHeap = NULL;
+ }
+}
+
+void SkDataTableBuilder::append(const void* src, size_t size) {
+ if (NULL == fHeap) {
+ fHeap = SkNEW_ARGS(SkChunkAlloc, (fMinChunkSize));
+ }
+
+ void* dst = fHeap->alloc(size, SkChunkAlloc::kThrow_AllocFailType);
+ memcpy(dst, src, size);
+
+ SkDataTable::Dir* dir = fDir.append();
+ dir->fPtr = dst;
+ dir->fSize = size;
+}
+
+SkDataTable* SkDataTableBuilder::detachDataTable() {
+ const int count = fDir.count();
+ if (0 == count) {
+ return SkDataTable::NewEmpty();
+ }
+
+ // Copy the dir into the heap;
+ void* dir = fHeap->alloc(count * sizeof(SkDataTable::Dir),
+ SkChunkAlloc::kThrow_AllocFailType);
+ memcpy(dir, fDir.begin(), count * sizeof(SkDataTable::Dir));
+
+ SkDataTable* table = SkNEW_ARGS(SkDataTable,
+ ((SkDataTable::Dir*)dir, count,
+ chunkalloc_freeproc, fHeap));
+ // we have to detach our fHeap, since we are giving that to the table
+ fHeap = NULL;
+ fDir.reset();
+ return table;
+}
diff --git a/core/SkDebug.cpp b/core/SkDebug.cpp
new file mode 100644
index 00000000..2a04b302
--- /dev/null
+++ b/core/SkDebug.cpp
@@ -0,0 +1,44 @@
+
+/*
+ * 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 "SkTypes.h"
+
+#ifdef SK_DEBUG
+
+int8_t SkToS8(intmax_t x) {
+ SkASSERT((int8_t)x == x);
+ return (int8_t)x;
+}
+
+uint8_t SkToU8(uintmax_t x) {
+ SkASSERT((uint8_t)x == x);
+ return (uint8_t)x;
+}
+
+int16_t SkToS16(intmax_t x) {
+ SkASSERT((int16_t)x == x);
+ return (int16_t)x;
+}
+
+uint16_t SkToU16(uintmax_t x) {
+ SkASSERT((uint16_t)x == x);
+ return (uint16_t)x;
+}
+
+int32_t SkToS32(intmax_t x) {
+ SkASSERT((int32_t)x == x);
+ return (int32_t)x;
+}
+
+uint32_t SkToU32(uintmax_t x) {
+ SkASSERT((uint32_t)x == x);
+ return (uint32_t)x;
+}
+
+#endif
diff --git a/core/SkDeque.cpp b/core/SkDeque.cpp
new file mode 100644
index 00000000..d210dcf2
--- /dev/null
+++ b/core/SkDeque.cpp
@@ -0,0 +1,308 @@
+
+/*
+ * 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 "SkDeque.h"
+
+struct SkDeque::Block {
+ Block* fNext;
+ Block* fPrev;
+ char* fBegin; // start of used section in this chunk
+ char* fEnd; // end of used section in this chunk
+ char* fStop; // end of the allocated chunk
+
+ char* start() { return (char*)(this + 1); }
+ const char* start() const { return (const char*)(this + 1); }
+
+ void init(size_t size) {
+ fNext = fPrev = NULL;
+ fBegin = fEnd = NULL;
+ fStop = (char*)this + size;
+ }
+};
+
+SkDeque::SkDeque(size_t elemSize, int allocCount)
+ : fElemSize(elemSize)
+ , fInitialStorage(NULL)
+ , fCount(0)
+ , fAllocCount(allocCount) {
+ SkASSERT(allocCount >= 1);
+ fFrontBlock = fBackBlock = NULL;
+ fFront = fBack = NULL;
+}
+
+SkDeque::SkDeque(size_t elemSize, void* storage, size_t storageSize, int allocCount)
+ : fElemSize(elemSize)
+ , fInitialStorage(storage)
+ , fCount(0)
+ , fAllocCount(allocCount) {
+ SkASSERT(storageSize == 0 || storage != NULL);
+ SkASSERT(allocCount >= 1);
+
+ if (storageSize >= sizeof(Block) + elemSize) {
+ fFrontBlock = (Block*)storage;
+ fFrontBlock->init(storageSize);
+ } else {
+ fFrontBlock = NULL;
+ }
+ fBackBlock = fFrontBlock;
+ fFront = fBack = NULL;
+}
+
+SkDeque::~SkDeque() {
+ Block* head = fFrontBlock;
+ Block* initialHead = (Block*)fInitialStorage;
+
+ while (head) {
+ Block* next = head->fNext;
+ if (head != initialHead) {
+ this->freeBlock(head);
+ }
+ head = next;
+ }
+}
+
+void* SkDeque::push_front() {
+ fCount += 1;
+
+ if (NULL == fFrontBlock) {
+ fFrontBlock = this->allocateBlock(fAllocCount);
+ fBackBlock = fFrontBlock; // update our linklist
+ }
+
+ Block* first = fFrontBlock;
+ char* begin;
+
+ if (NULL == first->fBegin) {
+ INIT_CHUNK:
+ first->fEnd = first->fStop;
+ begin = first->fStop - fElemSize;
+ } else {
+ begin = first->fBegin - fElemSize;
+ if (begin < first->start()) { // no more room in this chunk
+ // should we alloc more as we accumulate more elements?
+ first = this->allocateBlock(fAllocCount);
+ first->fNext = fFrontBlock;
+ fFrontBlock->fPrev = first;
+ fFrontBlock = first;
+ goto INIT_CHUNK;
+ }
+ }
+
+ first->fBegin = begin;
+
+ if (NULL == fFront) {
+ SkASSERT(NULL == fBack);
+ fFront = fBack = begin;
+ } else {
+ SkASSERT(NULL != fBack);
+ fFront = begin;
+ }
+
+ return begin;
+}
+
+void* SkDeque::push_back() {
+ fCount += 1;
+
+ if (NULL == fBackBlock) {
+ fBackBlock = this->allocateBlock(fAllocCount);
+ fFrontBlock = fBackBlock; // update our linklist
+ }
+
+ Block* last = fBackBlock;
+ char* end;
+
+ if (NULL == last->fBegin) {
+ INIT_CHUNK:
+ last->fBegin = last->start();
+ end = last->fBegin + fElemSize;
+ } else {
+ end = last->fEnd + fElemSize;
+ if (end > last->fStop) { // no more room in this chunk
+ // should we alloc more as we accumulate more elements?
+ last = this->allocateBlock(fAllocCount);
+ last->fPrev = fBackBlock;
+ fBackBlock->fNext = last;
+ fBackBlock = last;
+ goto INIT_CHUNK;
+ }
+ }
+
+ last->fEnd = end;
+ end -= fElemSize;
+
+ if (NULL == fBack) {
+ SkASSERT(NULL == fFront);
+ fFront = fBack = end;
+ } else {
+ SkASSERT(NULL != fFront);
+ fBack = end;
+ }
+
+ return end;
+}
+
+void SkDeque::pop_front() {
+ SkASSERT(fCount > 0);
+ fCount -= 1;
+
+ Block* first = fFrontBlock;
+
+ SkASSERT(first != NULL);
+
+ if (first->fBegin == NULL) { // we were marked empty from before
+ first = first->fNext;
+ first->fPrev = NULL;
+ this->freeBlock(fFrontBlock);
+ fFrontBlock = first;
+ SkASSERT(first != NULL); // else we popped too far
+ }
+
+ char* begin = first->fBegin + fElemSize;
+ SkASSERT(begin <= first->fEnd);
+
+ if (begin < fFrontBlock->fEnd) {
+ first->fBegin = begin;
+ SkASSERT(NULL != first->fBegin);
+ fFront = first->fBegin;
+ } else {
+ first->fBegin = first->fEnd = NULL; // mark as empty
+ if (NULL == first->fNext) {
+ fFront = fBack = NULL;
+ } else {
+ SkASSERT(NULL != first->fNext->fBegin);
+ fFront = first->fNext->fBegin;
+ }
+ }
+}
+
+void SkDeque::pop_back() {
+ SkASSERT(fCount > 0);
+ fCount -= 1;
+
+ Block* last = fBackBlock;
+
+ SkASSERT(last != NULL);
+
+ if (last->fEnd == NULL) { // we were marked empty from before
+ last = last->fPrev;
+ last->fNext = NULL;
+ this->freeBlock(fBackBlock);
+ fBackBlock = last;
+ SkASSERT(last != NULL); // else we popped too far
+ }
+
+ char* end = last->fEnd - fElemSize;
+ SkASSERT(end >= last->fBegin);
+
+ if (end > last->fBegin) {
+ last->fEnd = end;
+ SkASSERT(NULL != last->fEnd);
+ fBack = last->fEnd - fElemSize;
+ } else {
+ last->fBegin = last->fEnd = NULL; // mark as empty
+ if (NULL == last->fPrev) {
+ fFront = fBack = NULL;
+ } else {
+ SkASSERT(NULL != last->fPrev->fEnd);
+ fBack = last->fPrev->fEnd - fElemSize;
+ }
+ }
+}
+
+int SkDeque::numBlocksAllocated() const {
+ int numBlocks = 0;
+
+ for (const Block* temp = fFrontBlock; temp; temp = temp->fNext) {
+ ++numBlocks;
+ }
+
+ return numBlocks;
+}
+
+SkDeque::Block* SkDeque::allocateBlock(int allocCount) {
+ Block* newBlock = (Block*)sk_malloc_throw(sizeof(Block) + allocCount * fElemSize);
+ newBlock->init(sizeof(Block) + allocCount * fElemSize);
+ return newBlock;
+}
+
+void SkDeque::freeBlock(Block* block) {
+ sk_free(block);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDeque::Iter::Iter() : fCurBlock(NULL), fPos(NULL), fElemSize(0) {}
+
+SkDeque::Iter::Iter(const SkDeque& d, IterStart startLoc) {
+ this->reset(d, startLoc);
+}
+
+// Due to how reset and next work, next actually returns the current element
+// pointed to by fPos and then updates fPos to point to the next one.
+void* SkDeque::Iter::next() {
+ char* pos = fPos;
+
+ if (pos) { // if we were valid, try to move to the next setting
+ char* next = pos + fElemSize;
+ SkASSERT(next <= fCurBlock->fEnd);
+ if (next == fCurBlock->fEnd) { // exhausted this chunk, move to next
+ do {
+ fCurBlock = fCurBlock->fNext;
+ } while (fCurBlock != NULL && fCurBlock->fBegin == NULL);
+ next = fCurBlock ? fCurBlock->fBegin : NULL;
+ }
+ fPos = next;
+ }
+ return pos;
+}
+
+// Like next, prev actually returns the current element pointed to by fPos and
+// then makes fPos point to the previous element.
+void* SkDeque::Iter::prev() {
+ char* pos = fPos;
+
+ if (pos) { // if we were valid, try to move to the prior setting
+ char* prev = pos - fElemSize;
+ SkASSERT(prev >= fCurBlock->fBegin - fElemSize);
+ if (prev < fCurBlock->fBegin) { // exhausted this chunk, move to prior
+ do {
+ fCurBlock = fCurBlock->fPrev;
+ } while (fCurBlock != NULL && fCurBlock->fEnd == NULL);
+ prev = fCurBlock ? fCurBlock->fEnd - fElemSize : NULL;
+ }
+ fPos = prev;
+ }
+ return pos;
+}
+
+// reset works by skipping through the spare blocks at the start (or end)
+// of the doubly linked list until a non-empty one is found. The fPos
+// member is then set to the first (or last) element in the block. If
+// there are no elements in the deque both fCurBlock and fPos will come
+// out of this routine NULL.
+void SkDeque::Iter::reset(const SkDeque& d, IterStart startLoc) {
+ fElemSize = d.fElemSize;
+
+ if (kFront_IterStart == startLoc) {
+ // initialize the iterator to start at the front
+ fCurBlock = d.fFrontBlock;
+ while (NULL != fCurBlock && NULL == fCurBlock->fBegin) {
+ fCurBlock = fCurBlock->fNext;
+ }
+ fPos = fCurBlock ? fCurBlock->fBegin : NULL;
+ } else {
+ // initialize the iterator to start at the back
+ fCurBlock = d.fBackBlock;
+ while (NULL != fCurBlock && NULL == fCurBlock->fEnd) {
+ fCurBlock = fCurBlock->fPrev;
+ }
+ fPos = fCurBlock ? fCurBlock->fEnd - fElemSize : NULL;
+ }
+}
diff --git a/core/SkDescriptor.h b/core/SkDescriptor.h
new file mode 100644
index 00000000..79b086f6
--- /dev/null
+++ b/core/SkDescriptor.h
@@ -0,0 +1,164 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkDescriptor_DEFINED
+#define SkDescriptor_DEFINED
+
+#include "SkChecksum.h"
+#include "SkTypes.h"
+
+class SkDescriptor : SkNoncopyable {
+public:
+ static size_t ComputeOverhead(int entryCount) {
+ SkASSERT(entryCount >= 0);
+ return sizeof(SkDescriptor) + entryCount * sizeof(Entry);
+ }
+
+ static SkDescriptor* Alloc(size_t length) {
+ SkASSERT(SkAlign4(length) == length);
+ SkDescriptor* desc = (SkDescriptor*)sk_malloc_throw(length);
+ return desc;
+ }
+
+ static void Free(SkDescriptor* desc) {
+ sk_free(desc);
+ }
+
+ void init() {
+ fLength = sizeof(SkDescriptor);
+ fCount = 0;
+ }
+
+ uint32_t getLength() const { return fLength; }
+
+ void* addEntry(uint32_t tag, uint32_t length, const void* data = NULL) {
+ SkASSERT(tag);
+ SkASSERT(SkAlign4(length) == length);
+ SkASSERT(this->findEntry(tag, NULL) == NULL);
+
+ Entry* entry = (Entry*)((char*)this + fLength);
+ entry->fTag = tag;
+ entry->fLen = length;
+ if (data) {
+ memcpy(entry + 1, data, length);
+ }
+
+ fCount += 1;
+ fLength += sizeof(Entry) + length;
+ return (entry + 1); // return its data
+ }
+
+ void computeChecksum() {
+ fChecksum = SkDescriptor::ComputeChecksum(this);
+ }
+
+#ifdef SK_DEBUG
+ void assertChecksum() const {
+ SkASSERT(SkDescriptor::ComputeChecksum(this) == fChecksum);
+ }
+#endif
+
+ const void* findEntry(uint32_t tag, uint32_t* length) const {
+ const Entry* entry = (const Entry*)(this + 1);
+ int count = fCount;
+
+ while (--count >= 0) {
+ if (entry->fTag == tag) {
+ if (length) {
+ *length = entry->fLen;
+ }
+ return entry + 1;
+ }
+ entry = (const Entry*)((const char*)(entry + 1) + entry->fLen);
+ }
+ return NULL;
+ }
+
+ SkDescriptor* copy() const {
+ SkDescriptor* desc = SkDescriptor::Alloc(fLength);
+ memcpy(desc, this, fLength);
+ return desc;
+ }
+
+ bool equals(const SkDescriptor& other) const {
+ // probe to see if we have a good checksum algo
+// SkASSERT(a.fChecksum != b.fChecksum || memcmp(&a, &b, a.fLength) == 0);
+
+ // the first value we should look at is the checksum, so this loop
+ // should terminate early if they descriptors are different.
+ // NOTE: if we wrote a sentinel value at the end of each, we chould
+ // remove the aa < stop test in the loop...
+ const uint32_t* aa = (const uint32_t*)this;
+ const uint32_t* bb = (const uint32_t*)&other;
+ const uint32_t* stop = (const uint32_t*)((const char*)aa + fLength);
+ do {
+ if (*aa++ != *bb++)
+ return false;
+ } while (aa < stop);
+ return true;
+ }
+
+ uint32_t getChecksum() const { return fChecksum; }
+
+ struct Entry {
+ uint32_t fTag;
+ uint32_t fLen;
+ };
+
+#ifdef SK_DEBUG
+ uint32_t getCount() const { return fCount; }
+#endif
+
+private:
+ uint32_t fChecksum; // must be first
+ uint32_t fLength; // must be second
+ uint32_t fCount;
+
+ static uint32_t ComputeChecksum(const SkDescriptor* desc) {
+ const uint32_t* ptr = (const uint32_t*)desc + 1; // skip the checksum field
+ size_t len = desc->fLength - sizeof(uint32_t);
+ return SkChecksum::Compute(ptr, len);
+ }
+
+ // private so no one can create one except our factories
+ SkDescriptor() {}
+};
+
+#include "SkScalerContext.h"
+
+class SkAutoDescriptor : SkNoncopyable {
+public:
+ SkAutoDescriptor(size_t size) {
+ if (size <= sizeof(fStorage)) {
+ fDesc = (SkDescriptor*)(void*)fStorage;
+ } else {
+ fDesc = SkDescriptor::Alloc(size);
+ }
+ }
+
+ ~SkAutoDescriptor() {
+ if (fDesc != (SkDescriptor*)(void*)fStorage) {
+ SkDescriptor::Free(fDesc);
+ }
+ }
+
+ SkDescriptor* getDesc() const { return fDesc; }
+private:
+ enum {
+ kStorageSize = sizeof(SkDescriptor)
+ + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContext::Rec) // for rec
+ + sizeof(SkDescriptor::Entry) + sizeof(void*) // for typeface
+ + 32 // slop for occational small extras
+ };
+ SkDescriptor* fDesc;
+ uint32_t fStorage[(kStorageSize + 3) >> 2];
+};
+
+
+#endif
diff --git a/core/SkDevice.cpp b/core/SkDevice.cpp
new file mode 100644
index 00000000..408cda23
--- /dev/null
+++ b/core/SkDevice.cpp
@@ -0,0 +1,532 @@
+
+/*
+ * 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 "SkDevice.h"
+#include "SkDeviceProperties.h"
+#include "SkDraw.h"
+#include "SkImageFilter.h"
+#include "SkMetaData.h"
+#include "SkRasterClip.h"
+#include "SkRect.h"
+#include "SkRRect.h"
+#include "SkShader.h"
+
+SK_DEFINE_INST_COUNT(SkDevice)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define CHECK_FOR_NODRAW_ANNOTATION(paint) \
+ do { if (paint.isNoDrawAnnotation()) { return; } } while (0)
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDevice::SkDevice(const SkBitmap& bitmap)
+ : fBitmap(bitmap), fLeakyProperties(SkDeviceProperties::MakeDefault())
+#ifdef SK_DEBUG
+ , fAttachedToCanvas(false)
+#endif
+{
+ fOrigin.setZero();
+ fMetaData = NULL;
+
+ SkASSERT(SkBitmap::kARGB_4444_Config != bitmap.config());
+}
+
+SkDevice::SkDevice(const SkBitmap& bitmap, const SkDeviceProperties& deviceProperties)
+ : fBitmap(bitmap), fLeakyProperties(deviceProperties)
+#ifdef SK_DEBUG
+ , fAttachedToCanvas(false)
+#endif
+{
+ fOrigin.setZero();
+ fMetaData = NULL;
+}
+
+SkDevice::SkDevice(SkBitmap::Config config, int width, int height, bool isOpaque)
+ : fLeakyProperties(SkDeviceProperties::MakeDefault())
+#ifdef SK_DEBUG
+ , fAttachedToCanvas(false)
+#endif
+{
+ fOrigin.setZero();
+ fMetaData = NULL;
+
+ fBitmap.setConfig(config, width, height);
+ fBitmap.allocPixels();
+ fBitmap.setIsOpaque(isOpaque);
+ if (!isOpaque) {
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
+ }
+}
+
+SkDevice::SkDevice(SkBitmap::Config config, int width, int height, bool isOpaque,
+ const SkDeviceProperties& deviceProperties)
+ : fLeakyProperties(deviceProperties)
+#ifdef SK_DEBUG
+ , fAttachedToCanvas(false)
+#endif
+{
+ fOrigin.setZero();
+ fMetaData = NULL;
+
+ fBitmap.setConfig(config, width, height);
+ fBitmap.allocPixels();
+ fBitmap.setIsOpaque(isOpaque);
+ if (!isOpaque) {
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
+ }
+}
+
+SkDevice::~SkDevice() {
+ delete fMetaData;
+}
+
+void SkDevice::replaceBitmapBackendForRasterSurface(const SkBitmap& bm) {
+ SkASSERT(bm.width() == fBitmap.width());
+ SkASSERT(bm.height() == fBitmap.height());
+ fBitmap = bm; // intent is to use bm's pixelRef (and rowbytes/config)
+ fBitmap.lockPixels();
+}
+
+SkDevice* SkDevice::createCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque) {
+ return this->onCreateCompatibleDevice(config, width, height,
+ isOpaque, kGeneral_Usage);
+}
+
+SkDevice* SkDevice::createCompatibleDeviceForSaveLayer(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque) {
+ return this->onCreateCompatibleDevice(config, width, height,
+ isOpaque, kSaveLayer_Usage);
+}
+
+SkDevice* SkDevice::onCreateCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque,
+ Usage usage) {
+ return SkNEW_ARGS(SkDevice,(config, width, height, isOpaque, fLeakyProperties));
+}
+
+SkMetaData& SkDevice::getMetaData() {
+ // metadata users are rare, so we lazily allocate it. If that changes we
+ // can decide to just make it a field in the device (rather than a ptr)
+ if (NULL == fMetaData) {
+ fMetaData = new SkMetaData;
+ }
+ return *fMetaData;
+}
+
+void SkDevice::lockPixels() {
+ if (fBitmap.lockPixelsAreWritable()) {
+ fBitmap.lockPixels();
+ }
+}
+
+void SkDevice::unlockPixels() {
+ if (fBitmap.lockPixelsAreWritable()) {
+ fBitmap.unlockPixels();
+ }
+}
+
+const SkBitmap& SkDevice::accessBitmap(bool changePixels) {
+ const SkBitmap& bitmap = this->onAccessBitmap(&fBitmap);
+ if (changePixels) {
+ bitmap.notifyPixelsChanged();
+ }
+ return bitmap;
+}
+
+void SkDevice::getGlobalBounds(SkIRect* bounds) const {
+ if (bounds) {
+ bounds->setXYWH(fOrigin.x(), fOrigin.y(),
+ fBitmap.width(), fBitmap.height());
+ }
+}
+
+void SkDevice::clear(SkColor color) {
+ fBitmap.eraseColor(color);
+}
+
+const SkBitmap& SkDevice::onAccessBitmap(SkBitmap* bitmap) {return *bitmap;}
+
+void SkDevice::setMatrixClip(const SkMatrix& matrix, const SkRegion& region,
+ const SkClipStack& clipStack) {
+}
+
+bool SkDevice::canHandleImageFilter(SkImageFilter*) {
+ return false;
+}
+
+bool SkDevice::filterImage(SkImageFilter* filter, const SkBitmap& src,
+ const SkMatrix& ctm, SkBitmap* result,
+ SkIPoint* offset) {
+ return false;
+}
+
+bool SkDevice::allowImageFilter(SkImageFilter*) {
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDevice::readPixels(SkBitmap* bitmap, int x, int y,
+ SkCanvas::Config8888 config8888) {
+ if (SkBitmap::kARGB_8888_Config != bitmap->config() ||
+ NULL != bitmap->getTexture()) {
+ return false;
+ }
+
+ const SkBitmap& src = this->accessBitmap(false);
+
+ SkIRect srcRect = SkIRect::MakeXYWH(x, y, bitmap->width(),
+ bitmap->height());
+ SkIRect devbounds = SkIRect::MakeWH(src.width(), src.height());
+ if (!srcRect.intersect(devbounds)) {
+ return false;
+ }
+
+ SkBitmap tmp;
+ SkBitmap* bmp;
+ if (bitmap->isNull()) {
+ tmp.setConfig(SkBitmap::kARGB_8888_Config, bitmap->width(),
+ bitmap->height());
+ if (!tmp.allocPixels()) {
+ return false;
+ }
+ bmp = &tmp;
+ } else {
+ bmp = bitmap;
+ }
+
+ SkIRect subrect = srcRect;
+ subrect.offset(-x, -y);
+ SkBitmap bmpSubset;
+ bmp->extractSubset(&bmpSubset, subrect);
+
+ bool result = this->onReadPixels(bmpSubset,
+ srcRect.fLeft,
+ srcRect.fTop,
+ config8888);
+ if (result && bmp == &tmp) {
+ tmp.swap(*bitmap);
+ }
+ return result;
+}
+
+#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
+ const SkCanvas::Config8888 SkDevice::kPMColorAlias =
+ SkCanvas::kBGRA_Premul_Config8888;
+#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
+ const SkCanvas::Config8888 SkDevice::kPMColorAlias =
+ SkCanvas::kRGBA_Premul_Config8888;
+#else
+ const SkCanvas::Config8888 SkDevice::kPMColorAlias =
+ (SkCanvas::Config8888) -1;
+#endif
+
+#include <SkConfig8888.h>
+
+bool SkDevice::onReadPixels(const SkBitmap& bitmap,
+ int x, int y,
+ SkCanvas::Config8888 config8888) {
+ SkASSERT(SkBitmap::kARGB_8888_Config == bitmap.config());
+ SkASSERT(!bitmap.isNull());
+ SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height())));
+
+ SkIRect srcRect = SkIRect::MakeXYWH(x, y, bitmap.width(),
+ bitmap.height());
+ const SkBitmap& src = this->accessBitmap(false);
+
+ SkBitmap subset;
+ if (!src.extractSubset(&subset, srcRect)) {
+ return false;
+ }
+ if (SkBitmap::kARGB_8888_Config != subset.config()) {
+ // It'd be preferable to do this directly to bitmap.
+ subset.copyTo(&subset, SkBitmap::kARGB_8888_Config);
+ }
+ SkAutoLockPixels alp(bitmap);
+ uint32_t* bmpPixels = reinterpret_cast<uint32_t*>(bitmap.getPixels());
+ SkCopyBitmapToConfig8888(bmpPixels, bitmap.rowBytes(), config8888, subset);
+ return true;
+}
+
+void SkDevice::writePixels(const SkBitmap& bitmap,
+ int x, int y,
+ SkCanvas::Config8888 config8888) {
+ if (bitmap.isNull() || bitmap.getTexture()) {
+ return;
+ }
+ const SkBitmap* sprite = &bitmap;
+ // check whether we have to handle a config8888 that doesn't match SkPMColor
+ if (SkBitmap::kARGB_8888_Config == bitmap.config() &&
+ SkCanvas::kNative_Premul_Config8888 != config8888 &&
+ kPMColorAlias != config8888) {
+
+ // We're going to have to convert from a config8888 to the native config
+ // First we clip to the device bounds.
+ SkBitmap dstBmp = this->accessBitmap(true);
+ SkIRect spriteRect = SkIRect::MakeXYWH(x, y,
+ bitmap.width(), bitmap.height());
+ SkIRect devRect = SkIRect::MakeWH(dstBmp.width(), dstBmp.height());
+ if (!spriteRect.intersect(devRect)) {
+ return;
+ }
+
+ // write directly to the device if it has pixels and is SkPMColor
+ bool drawSprite;
+ if (SkBitmap::kARGB_8888_Config == dstBmp.config() && !dstBmp.isNull()) {
+ // we can write directly to the dst when doing the conversion
+ dstBmp.extractSubset(&dstBmp, spriteRect);
+ drawSprite = false;
+ } else {
+ // we convert to a temporary bitmap and draw that as a sprite
+ dstBmp.setConfig(SkBitmap::kARGB_8888_Config,
+ spriteRect.width(),
+ spriteRect.height());
+ if (!dstBmp.allocPixels()) {
+ return;
+ }
+ drawSprite = true;
+ }
+
+ // copy pixels to dstBmp and convert from config8888 to native config.
+ SkAutoLockPixels alp(bitmap);
+ uint32_t* srcPixels = bitmap.getAddr32(spriteRect.fLeft - x,
+ spriteRect.fTop - y);
+ SkCopyConfig8888ToBitmap(dstBmp,
+ srcPixels,
+ bitmap.rowBytes(),
+ config8888);
+
+ if (drawSprite) {
+ // we've clipped the sprite when we made a copy
+ x = spriteRect.fLeft;
+ y = spriteRect.fTop;
+ sprite = &dstBmp;
+ } else {
+ return;
+ }
+ }
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ SkRasterClip clip(SkIRect::MakeWH(fBitmap.width(), fBitmap.height()));
+ SkDraw draw;
+ draw.fRC = &clip;
+ draw.fClip = &clip.bwRgn();
+ draw.fBitmap = &fBitmap; // canvas should have already called accessBitmap
+ draw.fMatrix = &SkMatrix::I();
+ this->drawSprite(draw, *sprite, x, y, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
+ draw.drawPaint(paint);
+}
+
+void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ draw.drawPoints(mode, count, pts, paint);
+}
+
+void SkDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ draw.drawRect(r, paint);
+}
+
+void SkDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+
+ SkPath path;
+ path.addOval(oval);
+ // call the VIRTUAL version, so any subclasses who do handle drawPath aren't
+ // required to override drawOval.
+ this->drawPath(draw, path, paint, NULL, true);
+}
+
+void SkDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect, const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+
+ SkPath path;
+ path.addRRect(rrect);
+ // call the VIRTUAL version, so any subclasses who do handle drawPath aren't
+ // required to override drawRRect.
+ this->drawPath(draw, path, paint, NULL, true);
+}
+
+void SkDevice::drawPath(const SkDraw& draw, const SkPath& path,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ draw.drawPath(path, paint, prePathMatrix, pathIsMutable);
+}
+
+void SkDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint& paint) {
+ draw.drawBitmap(bitmap, matrix, paint);
+}
+
+void SkDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
+ const SkRect* src, const SkRect& dst,
+ const SkPaint& paint) {
+ SkMatrix matrix;
+ SkRect bitmapBounds, tmpSrc, tmpDst;
+ SkBitmap tmpBitmap;
+
+ bitmapBounds.isetWH(bitmap.width(), bitmap.height());
+
+ // Compute matrix from the two rectangles
+ if (src) {
+ tmpSrc = *src;
+ } else {
+ tmpSrc = bitmapBounds;
+ }
+ matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
+
+ const SkRect* dstPtr = &dst;
+ const SkBitmap* bitmapPtr = &bitmap;
+
+ // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if
+ // needed (if the src was clipped). No check needed if src==null.
+ if (src) {
+ if (!bitmapBounds.contains(*src)) {
+ if (!tmpSrc.intersect(bitmapBounds)) {
+ return; // nothing to draw
+ }
+ // recompute dst, based on the smaller tmpSrc
+ matrix.mapRect(&tmpDst, tmpSrc);
+ dstPtr = &tmpDst;
+ }
+
+ // since we may need to clamp to the borders of the src rect within
+ // the bitmap, we extract a subset.
+ SkIRect srcIR;
+ tmpSrc.roundOut(&srcIR);
+ if (!bitmap.extractSubset(&tmpBitmap, srcIR)) {
+ return;
+ }
+ bitmapPtr = &tmpBitmap;
+
+ // Since we did an extract, we need to adjust the matrix accordingly
+ SkScalar dx = 0, dy = 0;
+ if (srcIR.fLeft > 0) {
+ dx = SkIntToScalar(srcIR.fLeft);
+ }
+ if (srcIR.fTop > 0) {
+ dy = SkIntToScalar(srcIR.fTop);
+ }
+ if (dx || dy) {
+ matrix.preTranslate(dx, dy);
+ }
+
+ SkRect extractedBitmapBounds;
+ extractedBitmapBounds.isetWH(bitmapPtr->width(), bitmapPtr->height());
+ if (extractedBitmapBounds == tmpSrc) {
+ // no fractional part in src, we can just call drawBitmap
+ goto USE_DRAWBITMAP;
+ }
+ } else {
+ USE_DRAWBITMAP:
+ // We can go faster by just calling drawBitmap, which will concat the
+ // matrix with the CTM, and try to call drawSprite if it can. If not,
+ // it will make a shader and call drawRect, as we do below.
+ this->drawBitmap(draw, *bitmapPtr, matrix, paint);
+ return;
+ }
+
+ // construct a shader, so we can call drawRect with the dst
+ SkShader* s = SkShader::CreateBitmapShader(*bitmapPtr,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode);
+ if (NULL == s) {
+ return;
+ }
+ s->setLocalMatrix(matrix);
+
+ SkPaint paintWithShader(paint);
+ paintWithShader.setStyle(SkPaint::kFill_Style);
+ paintWithShader.setShader(s)->unref();
+
+ // Call ourself, in case the subclass wanted to share this setup code
+ // but handle the drawRect code themselves.
+ this->drawRect(draw, *dstPtr, paintWithShader);
+}
+
+void SkDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
+ int x, int y, const SkPaint& paint) {
+ draw.drawSprite(bitmap, x, y, paint);
+}
+
+void SkDevice::drawText(const SkDraw& draw, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint& paint) {
+ draw.drawText((const char*)text, len, x, y, paint);
+}
+
+void SkDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
+ const SkScalar xpos[], SkScalar y,
+ int scalarsPerPos, const SkPaint& paint) {
+ draw.drawPosText((const char*)text, len, xpos, y, scalarsPerPos, paint);
+}
+
+void SkDevice::drawTextOnPath(const SkDraw& draw, const void* text,
+ size_t len, const SkPath& path,
+ const SkMatrix* matrix,
+ const SkPaint& paint) {
+ draw.drawTextOnPath((const char*)text, len, path, matrix, paint);
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+void SkDevice::drawPosTextOnPath(const SkDraw& draw, const void* text, size_t len,
+ const SkPoint pos[], const SkPaint& paint,
+ const SkPath& path, const SkMatrix* matrix) {
+ draw.drawPosTextOnPath((const char*)text, len, pos, paint, path, matrix);
+}
+#endif
+
+void SkDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode,
+ int vertexCount,
+ const SkPoint verts[], const SkPoint textures[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ draw.drawVertices(vmode, vertexCount, verts, textures, colors, xmode,
+ indices, indexCount, paint);
+}
+
+void SkDevice::drawDevice(const SkDraw& draw, SkDevice* device,
+ int x, int y, const SkPaint& paint) {
+ const SkBitmap& src = device->accessBitmap(false);
+ draw.drawSprite(src, x, y, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDevice::filterTextFlags(const SkPaint& paint, TextFlags* flags) {
+ if (!paint.isLCDRenderText() || !paint.isAntiAlias()) {
+ // we're cool with the paint as is
+ return false;
+ }
+
+ if (SkBitmap::kARGB_8888_Config != fBitmap.config() ||
+ paint.getRasterizer() ||
+ paint.getPathEffect() ||
+ paint.isFakeBoldText() ||
+ paint.getStyle() != SkPaint::kFill_Style ||
+ !SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode)) {
+ // turn off lcd
+ flags->fFlags = paint.getFlags() & ~SkPaint::kLCDRenderText_Flag;
+ flags->fHinting = paint.getHinting();
+ return true;
+ }
+ // we're cool with the paint as is
+ return false;
+}
diff --git a/core/SkDeviceImageFilterProxy.h b/core/SkDeviceImageFilterProxy.h
new file mode 100644
index 00000000..98a120cd
--- /dev/null
+++ b/core/SkDeviceImageFilterProxy.h
@@ -0,0 +1,34 @@
+/*
+ * 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 SkDeviceImageFilterProxy_DEFINED
+#define SkDeviceImageFilterProxy_DEFINED
+
+#include "SkImageFilter.h"
+
+class SkDeviceImageFilterProxy : public SkImageFilter::Proxy {
+public:
+ SkDeviceImageFilterProxy(SkDevice* device) : fDevice(device) {}
+
+ virtual SkDevice* createDevice(int w, int h) SK_OVERRIDE {
+ return fDevice->createCompatibleDevice(SkBitmap::kARGB_8888_Config,
+ w, h, false);
+ }
+ virtual bool canHandleImageFilter(SkImageFilter* filter) SK_OVERRIDE {
+ return fDevice->canHandleImageFilter(filter);
+ }
+ virtual bool filterImage(SkImageFilter* filter, const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) SK_OVERRIDE {
+ return fDevice->filterImage(filter, src, ctm, result, offset);
+ }
+
+private:
+ SkDevice* fDevice;
+};
+
+#endif
diff --git a/core/SkDeviceLooper.cpp b/core/SkDeviceLooper.cpp
new file mode 100644
index 00000000..4122fd7a
--- /dev/null
+++ b/core/SkDeviceLooper.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDeviceLooper.h"
+
+SkDeviceLooper::SkDeviceLooper(const SkBitmap& base,
+ const SkRasterClip& rc,
+ const SkIRect& bounds, bool aa)
+: fBaseBitmap(base)
+, fBaseRC(rc)
+, fDelta(aa ? kAA_Delta : kBW_Delta)
+{
+ // sentinels that next() has not yet been called, and so our mapper functions
+ // should not be called either.
+ fCurrBitmap = NULL;
+ fCurrRC = NULL;
+
+ SkIRect bitmapBounds = SkIRect::MakeWH(base.width(), base.height());
+ if (!fClippedBounds.intersect(bounds, bitmapBounds)) {
+ fState = kDone_State;
+ } else if (this->fitsInDelta(bounds)) {
+ fState = kSimple_State;
+ } else {
+ // back up by 1 DX, so that next() will put us in a correct starting
+ // position.
+ fCurrOffset.set(fClippedBounds.left() - fDelta,
+ fClippedBounds.top());
+ fState = kComplex_State;
+ }
+}
+
+SkDeviceLooper::~SkDeviceLooper() {
+}
+
+void SkDeviceLooper::mapRect(SkRect* dst, const SkRect& src) const {
+ SkASSERT(kDone_State != fState);
+ SkASSERT(fCurrBitmap);
+ SkASSERT(fCurrRC);
+
+ *dst = src;
+ dst->offset(SkIntToScalar(-fCurrOffset.fX),
+ SkIntToScalar(-fCurrOffset.fY));
+}
+
+void SkDeviceLooper::mapMatrix(SkMatrix* dst, const SkMatrix& src) const {
+ SkASSERT(kDone_State != fState);
+ SkASSERT(fCurrBitmap);
+ SkASSERT(fCurrRC);
+
+ *dst = src;
+ dst->postTranslate(SkIntToScalar(-fCurrOffset.fX),
+ SkIntToScalar(-fCurrOffset.fY));
+}
+
+bool SkDeviceLooper::computeCurrBitmapAndClip() {
+ SkASSERT(kComplex_State == fState);
+
+ SkIRect r = SkIRect::MakeXYWH(fCurrOffset.x(), fCurrOffset.y(),
+ fDelta, fDelta);
+ if (!fBaseBitmap.extractSubset(&fSubsetBitmap, r)) {
+ fState = kDone_State;
+ return false;
+ }
+ fSubsetBitmap.lockPixels();
+
+ fBaseRC.translate(-r.left(), -r.top(), &fSubsetRC);
+ (void)fSubsetRC.op(SkIRect::MakeWH(fDelta, fDelta), SkRegion::kIntersect_Op);
+
+ fCurrBitmap = &fSubsetBitmap;
+ fCurrRC = &fSubsetRC;
+ return true;
+}
+
+bool SkDeviceLooper::next() {
+ switch (fState) {
+ case kDone_State:
+ // in theory, we should not get called here, since we must have
+ // previously returned false, but we check anyway.
+ break;
+
+ case kSimple_State:
+ // first time for simple
+ if (NULL == fCurrBitmap) {
+ fCurrBitmap = &fBaseBitmap;
+ fCurrRC = &fBaseRC;
+ fCurrOffset.set(0, 0);
+ return true;
+ }
+ // 2nd time for simple, we are done
+ break;
+
+ case kComplex_State:
+ // need to propogate fCurrOffset through clippedbounds
+ // left to right, until we wrap around and move down
+
+ if (fCurrOffset.x() + fDelta < fClippedBounds.right()) {
+ fCurrOffset.fX += fDelta;
+ return this->computeCurrBitmapAndClip();
+ }
+ fCurrOffset.fX = fClippedBounds.left();
+ if (fCurrOffset.y() + fDelta < fClippedBounds.bottom()) {
+ fCurrOffset.fY += fDelta;
+ return this->computeCurrBitmapAndClip();
+ }
+ break;
+ }
+
+ fState = kDone_State;
+ return false;
+}
diff --git a/core/SkDeviceLooper.h b/core/SkDeviceLooper.h
new file mode 100644
index 00000000..405173dd
--- /dev/null
+++ b/core/SkDeviceLooper.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkDeviceLooper_DEFINED
+#define SkDeviceLooper_DEFINED
+
+#include "SkBitmap.h"
+#include "SkMatrix.h"
+#include "SkRasterClip.h"
+
+/**
+ * Helper class to manage "tiling" a large coordinate space into managable
+ * chunks, where managable means areas that are <= some max critical coordinate
+ * size.
+ *
+ * The constructor takes an antialiasing bool, which affects what this maximum
+ * allowable size is: If we're drawing BW, then we need coordinates to stay
+ * safely within fixed-point range (we use +- 16K, to give ourselves room to
+ * add/subtract two fixed values and still be in range. If we're drawing AA,
+ * then we reduce that size by the amount that the supersampler scan converter
+ * needs (at the moment, that is 4X, so the "safe" range is +- 4K).
+ *
+ * For performance reasons, the class first checks to see if any help is needed
+ * at all, and if not (i.e. the specified bounds and base bitmap area already
+ * in the safe-zone, then the class does nothing (effectively).
+ */
+class SkDeviceLooper {
+public:
+ SkDeviceLooper(const SkBitmap& base, const SkRasterClip&,
+ const SkIRect& bounds, bool aa);
+ ~SkDeviceLooper();
+
+ const SkBitmap& getBitmap() const {
+ SkASSERT(kDone_State != fState);
+ SkASSERT(fCurrBitmap);
+ return *fCurrBitmap;
+ }
+
+ const SkRasterClip& getRC() const {
+ SkASSERT(kDone_State != fState);
+ SkASSERT(fCurrRC);
+ return *fCurrRC;
+ }
+
+ void mapRect(SkRect* dst, const SkRect& src) const;
+ void mapMatrix(SkMatrix* dst, const SkMatrix& src) const;
+
+ /**
+ * Call next to setup the looper to return a valid coordinate chunk.
+ * Each time this returns true, it is safe to call mapRect() and
+ * mapMatrix(), to convert from "global" coordinate values to ones that
+ * are local to this chunk.
+ *
+ * When next() returns false, the list of chunks is done, and mapRect()
+ * and mapMatrix() should no longer be called.
+ */
+ bool next();
+
+private:
+ const SkBitmap& fBaseBitmap;
+ const SkRasterClip& fBaseRC;
+
+ enum State {
+ kDone_State, // iteration is complete, getters will assert
+ kSimple_State, // no translate/clip mods needed
+ kComplex_State
+ };
+
+ // storage for our tiled versions. Perhaps could use SkTLazy
+ SkBitmap fSubsetBitmap;
+ SkRasterClip fSubsetRC;
+
+ const SkBitmap* fCurrBitmap;
+ const SkRasterClip* fCurrRC;
+ SkIRect fClippedBounds;
+ SkIPoint fCurrOffset;
+ int fDelta;
+ State fState;
+
+ enum Delta {
+ kBW_Delta = 1 << 14, // 16K, gives room to spare for fixedpoint
+ kAA_Delta = kBW_Delta >> 2 // supersample 4x
+ };
+
+ bool fitsInDelta(const SkIRect& r) const {
+ return r.right() < fDelta && r.bottom() < fDelta;
+ }
+
+ bool computeCurrBitmapAndClip();
+};
+
+#endif
diff --git a/core/SkDeviceProfile.cpp b/core/SkDeviceProfile.cpp
new file mode 100644
index 00000000..a15069a1
--- /dev/null
+++ b/core/SkDeviceProfile.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 "SkDeviceProfile.h"
+#include "SkThread.h"
+
+SK_DEFINE_INST_COUNT(SkDeviceProfile)
+
+#define DEFAULT_GAMMAEXP 2.2f
+#define DEFAULT_CONTRASTSCALE 0.5f
+#define DEFAULT_LCDCONFIG SkDeviceProfile::kNone_LCDConfig
+#define DEFAULT_FONTHINTLEVEL SkDeviceProfile::kSlight_FontHintLevel
+
+static float pin(float value, float min, float max) {
+ if (value < min) {
+ value = min;
+ } else if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+SkDeviceProfile::SkDeviceProfile(float gammaExp, float contrast,
+ LCDConfig config, FontHintLevel level) {
+ fGammaExponent = pin(gammaExp, 0, 10);
+ fContrastScale = pin(contrast, 0, 1);
+ fLCDConfig = config;
+ fFontHintLevel = level;
+}
+
+void SkDeviceProfile::generateTableForLuminanceByte(U8CPU lumByte,
+ uint8_t table[256]) const {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDeviceProfile* SkDeviceProfile::Create(float gammaExp,
+ float contrast,
+ LCDConfig config,
+ FontHintLevel level) {
+ return SkNEW_ARGS(SkDeviceProfile, (gammaExp, contrast, config, level));
+}
+
+SK_DECLARE_STATIC_MUTEX(gMutex);
+static SkDeviceProfile* gDefaultProfile;
+static SkDeviceProfile* gGlobalProfile;
+
+SkDeviceProfile* SkDeviceProfile::GetDefault() {
+ SkAutoMutexAcquire amc(gMutex);
+
+ if (NULL == gDefaultProfile) {
+ gDefaultProfile = SkDeviceProfile::Create(DEFAULT_GAMMAEXP,
+ DEFAULT_CONTRASTSCALE,
+ DEFAULT_LCDCONFIG,
+ DEFAULT_FONTHINTLEVEL);
+ }
+ return gDefaultProfile;
+}
+
+SkDeviceProfile* SkDeviceProfile::RefGlobal() {
+ SkAutoMutexAcquire amc(gMutex);
+
+ if (NULL == gGlobalProfile) {
+ gGlobalProfile = SkDeviceProfile::GetDefault();
+ }
+ gGlobalProfile->ref();
+ return gGlobalProfile;
+}
+
+void SkDeviceProfile::SetGlobal(SkDeviceProfile* profile) {
+ SkAutoMutexAcquire amc(gMutex);
+
+ SkRefCnt_SafeAssign(gGlobalProfile, profile);
+}
diff --git a/core/SkDeviceProfile.h b/core/SkDeviceProfile.h
new file mode 100644
index 00000000..6ef8dfb8
--- /dev/null
+++ b/core/SkDeviceProfile.h
@@ -0,0 +1,98 @@
+/*
+ * 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 SkDeviceProfile_DEFINED
+#define SkDeviceProfile_DEFINED
+
+#include "SkRefCnt.h"
+
+class SkDeviceProfile : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkDeviceProfile)
+
+ enum LCDConfig {
+ kNone_LCDConfig, // disables LCD text rendering, uses A8 instead
+ kRGB_Horizontal_LCDConfig,
+ kBGR_Horizontal_LCDConfig,
+ kRGB_Vertical_LCDConfig,
+ kBGR_Vertical_LCDConfig
+ };
+
+ enum FontHintLevel {
+ kNone_FontHintLevel,
+ kSlight_FontHintLevel,
+ kNormal_FontHintLevel,
+ kFull_FontHintLevel,
+ kAuto_FontHintLevel
+ };
+
+ /**
+ * gammaExp is typically between 1.0 and 2.2. For no gamma adjustment,
+ * specify 1.0
+ *
+ * contrastScale will be pinned between 0.0 and 1.0. For no contrast
+ * adjustment, specify 0.0
+ *
+ * @param config Describes the LCD layout for this device. If this is set
+ * to kNone, then all requests for LCD text will be
+ * devolved to A8 antialiasing.
+ *
+ * @param level The hinting level to be used, IF the paint specifies
+ * "default". Otherwise the paint's hinting level will be
+ * respected.
+ */
+ static SkDeviceProfile* Create(float gammaExp,
+ float contrastScale,
+ LCDConfig,
+ FontHintLevel);
+
+ /**
+ * Returns the global default profile, that is used if no global profile is
+ * specified with SetGlobal(), or if NULL is specified to SetGlobal().
+ * The references count is *not* incremented, and the caller should not
+ * call unref().
+ */
+ static SkDeviceProfile* GetDefault();
+
+ /**
+ * Return the current global profile (or the default if no global had yet
+ * been set) and increment its reference count. The call *must* call unref()
+ * when it is done using it.
+ */
+ static SkDeviceProfile* RefGlobal();
+
+ /**
+ * Make the specified profile be the global value for all subsequently
+ * instantiated devices. Does not affect any existing devices.
+ * Increments the reference count on the profile.
+ * Specify NULL for the "identity" profile (where there is no gamma or
+ * contrast correction).
+ */
+ static void SetGlobal(SkDeviceProfile*);
+
+ float getFontGammaExponent() const { return fGammaExponent; }
+ float getFontContrastScale() const { return fContrastScale; }
+
+ /**
+ * Given a luminance byte (0 for black, 0xFF for white), generate a table
+ * that applies the gamma/contrast settings to linear coverage values.
+ */
+ void generateTableForLuminanceByte(U8CPU lumByte, uint8_t table[256]) const;
+
+private:
+ SkDeviceProfile(float gammaExp, float contrastScale, LCDConfig,
+ FontHintLevel);
+
+ float fGammaExponent;
+ float fContrastScale;
+ LCDConfig fLCDConfig;
+ FontHintLevel fFontHintLevel;
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkDither.cpp b/core/SkDither.cpp
new file mode 100644
index 00000000..546d4799
--- /dev/null
+++ b/core/SkDither.cpp
@@ -0,0 +1,55 @@
+
+/*
+ * 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 "SkDither.h"
+
+/* The base dither matrix we use to derive optimized ones for 565 and 4444
+
+ { 0, 32, 8, 40, 2, 34, 10, 42 },
+ { 48, 16, 56, 24, 50, 18, 58, 26 },
+ { 12, 44, 4, 36, 14, 46, 6, 38 },
+ { 60, 28, 52, 20, 62, 30, 54, 22 },
+ { 3, 35, 11, 43, 1, 33, 9, 41 },
+ { 51, 19, 59, 27, 49, 17, 57, 25 },
+ { 15, 47, 7, 39, 13, 45, 5, 37 },
+ { 63, 31, 55, 23, 61, 29, 53, 21 }
+
+ The 4444 version only needs 4 bits, and given that we can reduce its size
+ since the other 4x4 sub pieces all look the same once we truncate the bits.
+
+ The 565 version only needs 3 bits for red/blue, and only 2 bits for green.
+ For simplicity, we store 3 bits, and have the dither macros for green know
+ this, and they shift the dither value down by 1 to make it 2 bits.
+ */
+
+#ifdef ENABLE_DITHER_MATRIX_4X4
+
+const uint8_t gDitherMatrix_4Bit_4X4[4][4] = {
+ { 0, 8, 2, 10 },
+ { 12, 4, 14, 6 },
+ { 3, 11, 1, 9 },
+ { 15, 7, 13, 5 }
+};
+
+const uint8_t gDitherMatrix_3Bit_4X4[4][4] = {
+ { 0, 4, 1, 5 },
+ { 6, 2, 7, 3 },
+ { 1, 5, 0, 4 },
+ { 7, 3, 6, 2 }
+};
+
+#else // used packed shorts for a scanlines worth of dither values
+
+const uint16_t gDitherMatrix_4Bit_16[4] = {
+ 0xA280, 0x6E4C, 0x91B3, 0x5D7F
+};
+
+const uint16_t gDitherMatrix_3Bit_16[4] = {
+ 0x5140, 0x3726, 0x4051, 0x2637
+};
+
+#endif
diff --git a/core/SkDraw.cpp b/core/SkDraw.cpp
new file mode 100644
index 00000000..a9d5fbb0
--- /dev/null
+++ b/core/SkDraw.cpp
@@ -0,0 +1,2836 @@
+/*
+ * 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 "SkDraw.h"
+#include "SkBlitter.h"
+#include "SkBounder.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkDevice.h"
+#include "SkDeviceLooper.h"
+#include "SkFixed.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkPathEffect.h"
+#include "SkRasterClip.h"
+#include "SkRasterizer.h"
+#include "SkScan.h"
+#include "SkShader.h"
+#include "SkString.h"
+#include "SkStroke.h"
+#include "SkTemplatesPriv.h"
+#include "SkTLazy.h"
+#include "SkUtils.h"
+
+#include "SkAutoKern.h"
+#include "SkBitmapProcShader.h"
+#include "SkDrawProcs.h"
+#include "SkMatrixUtils.h"
+
+bool SkDraw::ShouldDrawTextAsPaths(const SkPaint& paint, const SkMatrix& ctm) {
+ // we don't cache hairlines in the cache
+ if (SkPaint::kStroke_Style == paint.getStyle() &&
+ 0 == paint.getStrokeWidth()) {
+ return true;
+ }
+
+ // we don't cache perspective
+ if (ctm.hasPerspective()) {
+ return true;
+ }
+
+ SkMatrix textM;
+ return SkPaint::TooBigToUseCache(ctm, *paint.setTextMatrix(&textM));
+}
+
+//#define TRACE_BITMAP_DRAWS
+
+#define kBlitterStorageLongCount (sizeof(SkBitmapProcShader) >> 2)
+
+/** Helper for allocating small blitters on the stack.
+ */
+class SkAutoBlitterChoose : SkNoncopyable {
+public:
+ SkAutoBlitterChoose() {
+ fBlitter = NULL;
+ }
+ SkAutoBlitterChoose(const SkBitmap& device, const SkMatrix& matrix,
+ const SkPaint& paint) {
+ fBlitter = SkBlitter::Choose(device, matrix, paint,
+ fStorage, sizeof(fStorage));
+ }
+
+ ~SkAutoBlitterChoose();
+
+ SkBlitter* operator->() { return fBlitter; }
+ SkBlitter* get() const { return fBlitter; }
+
+ void choose(const SkBitmap& device, const SkMatrix& matrix,
+ const SkPaint& paint) {
+ SkASSERT(!fBlitter);
+ fBlitter = SkBlitter::Choose(device, matrix, paint,
+ fStorage, sizeof(fStorage));
+ }
+
+private:
+ SkBlitter* fBlitter;
+ uint32_t fStorage[kBlitterStorageLongCount];
+};
+
+SkAutoBlitterChoose::~SkAutoBlitterChoose() {
+ if ((void*)fBlitter == (void*)fStorage) {
+ fBlitter->~SkBlitter();
+ } else {
+ SkDELETE(fBlitter);
+ }
+}
+
+/**
+ * Since we are providing the storage for the shader (to avoid the perf cost
+ * of calling new) we insist that in our destructor we can account for all
+ * owners of the shader.
+ */
+class SkAutoBitmapShaderInstall : SkNoncopyable {
+public:
+ SkAutoBitmapShaderInstall(const SkBitmap& src, const SkPaint& paint)
+ : fPaint(paint) /* makes a copy of the paint */ {
+ fPaint.setShader(SkShader::CreateBitmapShader(src,
+ SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
+ fStorage, sizeof(fStorage)));
+ // we deliberately left the shader with an owner-count of 2
+ SkASSERT(2 == fPaint.getShader()->getRefCnt());
+ }
+
+ ~SkAutoBitmapShaderInstall() {
+ SkShader* shader = fPaint.getShader();
+ // since we manually destroy shader, we insist that owners == 2
+ SkASSERT(2 == shader->getRefCnt());
+
+ fPaint.setShader(NULL); // unref the shader by 1
+
+ // now destroy to take care of the 2nd owner-count
+ if ((void*)shader == (void*)fStorage) {
+ shader->~SkShader();
+ } else {
+ SkDELETE(shader);
+ }
+ }
+
+ // return the new paint that has the shader applied
+ const SkPaint& paintWithShader() const { return fPaint; }
+
+private:
+ SkPaint fPaint; // copy of caller's paint (which we then modify)
+ uint32_t fStorage[kBlitterStorageLongCount];
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDraw::SkDraw() {
+ sk_bzero(this, sizeof(*this));
+}
+
+SkDraw::SkDraw(const SkDraw& src) {
+ memcpy(this, &src, sizeof(*this));
+}
+
+bool SkDraw::computeConservativeLocalClipBounds(SkRect* localBounds) const {
+ if (fRC->isEmpty()) {
+ return false;
+ }
+
+ SkMatrix inverse;
+ if (!fMatrix->invert(&inverse)) {
+ return false;
+ }
+
+ SkIRect devBounds = fRC->getBounds();
+ // outset to have slop for antialasing and hairlines
+ devBounds.outset(1, 1);
+ inverse.mapRect(localBounds, SkRect::Make(devBounds));
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef void (*BitmapXferProc)(void* pixels, size_t bytes, uint32_t data);
+
+static void D_Clear_BitmapXferProc(void* pixels, size_t bytes, uint32_t) {
+ sk_bzero(pixels, bytes);
+}
+
+static void D_Dst_BitmapXferProc(void*, size_t, uint32_t data) {}
+
+static void D32_Src_BitmapXferProc(void* pixels, size_t bytes, uint32_t data) {
+ sk_memset32((uint32_t*)pixels, data, bytes >> 2);
+}
+
+static void D16_Src_BitmapXferProc(void* pixels, size_t bytes, uint32_t data) {
+ sk_memset16((uint16_t*)pixels, data, bytes >> 1);
+}
+
+static void DA8_Src_BitmapXferProc(void* pixels, size_t bytes, uint32_t data) {
+ memset(pixels, data, bytes);
+}
+
+static BitmapXferProc ChooseBitmapXferProc(const SkBitmap& bitmap,
+ const SkPaint& paint,
+ uint32_t* data) {
+ // todo: we can apply colorfilter up front if no shader, so we wouldn't
+ // need to abort this fastpath
+ if (paint.getShader() || paint.getColorFilter()) {
+ return NULL;
+ }
+
+ SkXfermode::Mode mode;
+ if (!SkXfermode::AsMode(paint.getXfermode(), &mode)) {
+ return NULL;
+ }
+
+ SkColor color = paint.getColor();
+
+ // collaps modes based on color...
+ if (SkXfermode::kSrcOver_Mode == mode) {
+ unsigned alpha = SkColorGetA(color);
+ if (0 == alpha) {
+ mode = SkXfermode::kDst_Mode;
+ } else if (0xFF == alpha) {
+ mode = SkXfermode::kSrc_Mode;
+ }
+ }
+
+ switch (mode) {
+ case SkXfermode::kClear_Mode:
+// SkDebugf("--- D_Clear_BitmapXferProc\n");
+ return D_Clear_BitmapXferProc; // ignore data
+ case SkXfermode::kDst_Mode:
+// SkDebugf("--- D_Dst_BitmapXferProc\n");
+ return D_Dst_BitmapXferProc; // ignore data
+ case SkXfermode::kSrc_Mode: {
+ /*
+ should I worry about dithering for the lower depths?
+ */
+ SkPMColor pmc = SkPreMultiplyColor(color);
+ switch (bitmap.config()) {
+ case SkBitmap::kARGB_8888_Config:
+ if (data) {
+ *data = pmc;
+ }
+// SkDebugf("--- D32_Src_BitmapXferProc\n");
+ return D32_Src_BitmapXferProc;
+ case SkBitmap::kRGB_565_Config:
+ if (data) {
+ *data = SkPixel32ToPixel16(pmc);
+ }
+// SkDebugf("--- D16_Src_BitmapXferProc\n");
+ return D16_Src_BitmapXferProc;
+ case SkBitmap::kA8_Config:
+ if (data) {
+ *data = SkGetPackedA32(pmc);
+ }
+// SkDebugf("--- DA8_Src_BitmapXferProc\n");
+ return DA8_Src_BitmapXferProc;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static void CallBitmapXferProc(const SkBitmap& bitmap, const SkIRect& rect,
+ BitmapXferProc proc, uint32_t procData) {
+ int shiftPerPixel;
+ switch (bitmap.config()) {
+ case SkBitmap::kARGB_8888_Config:
+ shiftPerPixel = 2;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ shiftPerPixel = 1;
+ break;
+ case SkBitmap::kA8_Config:
+ shiftPerPixel = 0;
+ break;
+ default:
+ SkDEBUGFAIL("Can't use xferproc on this config");
+ return;
+ }
+
+ uint8_t* pixels = (uint8_t*)bitmap.getPixels();
+ SkASSERT(pixels);
+ const size_t rowBytes = bitmap.rowBytes();
+ const int widthBytes = rect.width() << shiftPerPixel;
+
+ // skip down to the first scanline and X position
+ pixels += rect.fTop * rowBytes + (rect.fLeft << shiftPerPixel);
+ for (int scans = rect.height() - 1; scans >= 0; --scans) {
+ proc(pixels, widthBytes, procData);
+ pixels += rowBytes;
+ }
+}
+
+void SkDraw::drawPaint(const SkPaint& paint) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (fRC->isEmpty()) {
+ return;
+ }
+
+ SkIRect devRect;
+ devRect.set(0, 0, fBitmap->width(), fBitmap->height());
+ if (fBounder && !fBounder->doIRect(devRect)) {
+ return;
+ }
+
+ if (fRC->isBW()) {
+ /* If we don't have a shader (i.e. we're just a solid color) we may
+ be faster to operate directly on the device bitmap, rather than invoking
+ a blitter. Esp. true for xfermodes, which require a colorshader to be
+ present, which is just redundant work. Since we're drawing everywhere
+ in the clip, we don't have to worry about antialiasing.
+ */
+ uint32_t procData = 0; // to avoid the warning
+ BitmapXferProc proc = ChooseBitmapXferProc(*fBitmap, paint, &procData);
+ if (proc) {
+ if (D_Dst_BitmapXferProc == proc) { // nothing to do
+ return;
+ }
+
+ SkRegion::Iterator iter(fRC->bwRgn());
+ while (!iter.done()) {
+ CallBitmapXferProc(*fBitmap, iter.rect(), proc, procData);
+ iter.next();
+ }
+ return;
+ }
+ }
+
+ // normal case: use a blitter
+ SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
+ SkScan::FillIRect(devRect, *fRC, blitter.get());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct PtProcRec {
+ SkCanvas::PointMode fMode;
+ const SkPaint* fPaint;
+ const SkRegion* fClip;
+ const SkRasterClip* fRC;
+
+ // computed values
+ SkFixed fRadius;
+
+ typedef void (*Proc)(const PtProcRec&, const SkPoint devPts[], int count,
+ SkBlitter*);
+
+ bool init(SkCanvas::PointMode, const SkPaint&, const SkMatrix* matrix,
+ const SkRasterClip*);
+ Proc chooseProc(SkBlitter** blitter);
+
+private:
+ SkAAClipBlitterWrapper fWrapper;
+};
+
+static void bw_pt_rect_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ SkASSERT(rec.fClip->isRect());
+ const SkIRect& r = rec.fClip->getBounds();
+
+ for (int i = 0; i < count; i++) {
+ int x = SkScalarFloorToInt(devPts[i].fX);
+ int y = SkScalarFloorToInt(devPts[i].fY);
+ if (r.contains(x, y)) {
+ blitter->blitH(x, y, 1);
+ }
+ }
+}
+
+static void bw_pt_rect_16_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);
+
+ uint16_t* addr = bitmap->getAddr16(0, 0);
+ size_t 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)) {
+ ((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);
+ size_t 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++) {
+ int x = SkScalarFloor(devPts[i].fX);
+ int y = SkScalarFloor(devPts[i].fY);
+ if (rec.fClip->contains(x, y)) {
+ blitter->blitH(x, y, 1);
+ }
+ }
+}
+
+static void bw_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ for (int i = 0; i < count; i += 2) {
+ SkScan::HairLine(devPts[i], devPts[i+1], *rec.fRC, blitter);
+ }
+}
+
+static void bw_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ for (int i = 0; i < count - 1; i++) {
+ SkScan::HairLine(devPts[i], devPts[i+1], *rec.fRC, blitter);
+ }
+}
+
+// aa versions
+
+static void aa_line_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ for (int i = 0; i < count; i += 2) {
+ SkScan::AntiHairLine(devPts[i], devPts[i+1], *rec.fRC, blitter);
+ }
+}
+
+static void aa_poly_hair_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ for (int i = 0; i < count - 1; i++) {
+ SkScan::AntiHairLine(devPts[i], devPts[i+1], *rec.fRC, blitter);
+ }
+}
+
+// square procs (strokeWidth > 0 but matrix is square-scale (sx == sy)
+
+static void bw_square_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ const SkFixed radius = rec.fRadius;
+ for (int i = 0; i < count; i++) {
+ SkFixed x = SkScalarToFixed(devPts[i].fX);
+ SkFixed y = SkScalarToFixed(devPts[i].fY);
+
+ SkXRect r;
+ r.fLeft = x - radius;
+ r.fTop = y - radius;
+ r.fRight = x + radius;
+ r.fBottom = y + radius;
+
+ SkScan::FillXRect(r, *rec.fRC, blitter);
+ }
+}
+
+static void aa_square_proc(const PtProcRec& rec, const SkPoint devPts[],
+ int count, SkBlitter* blitter) {
+ const SkFixed radius = rec.fRadius;
+ for (int i = 0; i < count; i++) {
+ SkFixed x = SkScalarToFixed(devPts[i].fX);
+ SkFixed y = SkScalarToFixed(devPts[i].fY);
+
+ SkXRect r;
+ r.fLeft = x - radius;
+ r.fTop = y - radius;
+ r.fRight = x + radius;
+ r.fBottom = y + radius;
+
+ SkScan::AntiFillXRect(r, *rec.fRC, blitter);
+ }
+}
+
+// If this guy returns true, then chooseProc() must return a valid proc
+bool PtProcRec::init(SkCanvas::PointMode mode, const SkPaint& paint,
+ const SkMatrix* matrix, const SkRasterClip* rc) {
+ if (paint.getPathEffect()) {
+ return false;
+ }
+ SkScalar width = paint.getStrokeWidth();
+ if (0 == width) {
+ fMode = mode;
+ fPaint = &paint;
+ fClip = NULL;
+ fRC = rc;
+ fRadius = SK_FixedHalf;
+ return true;
+ }
+ if (paint.getStrokeCap() != SkPaint::kRound_Cap &&
+ matrix->rectStaysRect() && SkCanvas::kPoints_PointMode == mode) {
+ SkScalar sx = matrix->get(SkMatrix::kMScaleX);
+ SkScalar sy = matrix->get(SkMatrix::kMScaleY);
+ if (SkScalarNearlyZero(sx - sy)) {
+ if (sx < 0) {
+ sx = -sx;
+ }
+
+ fMode = mode;
+ fPaint = &paint;
+ fClip = NULL;
+ fRC = rc;
+ fRadius = SkScalarToFixed(SkScalarMul(width, sx)) >> 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+PtProcRec::Proc PtProcRec::chooseProc(SkBlitter** blitterPtr) {
+ Proc proc = NULL;
+
+ SkBlitter* blitter = *blitterPtr;
+ if (fRC->isBW()) {
+ fClip = &fRC->bwRgn();
+ } else {
+ fWrapper.init(*fRC, blitter);
+ fClip = &fWrapper.getRgn();
+ blitter = fWrapper.getBlitter();
+ *blitterPtr = blitter;
+ }
+
+ // for our arrays
+ SkASSERT(0 == SkCanvas::kPoints_PointMode);
+ SkASSERT(1 == SkCanvas::kLines_PointMode);
+ SkASSERT(2 == SkCanvas::kPolygon_PointMode);
+ SkASSERT((unsigned)fMode <= (unsigned)SkCanvas::kPolygon_PointMode);
+
+ 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 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 && 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;
+ }
+ } else {
+ static Proc gBWProcs[] = {
+ bw_pt_hair_proc, bw_line_hair_proc, bw_poly_hair_proc
+ };
+ proc = gBWProcs[fMode];
+ }
+ } else {
+ proc = bw_square_proc;
+ }
+ }
+ return proc;
+}
+
+static bool bounder_points(SkBounder* bounder, SkCanvas::PointMode mode,
+ size_t count, const SkPoint pts[],
+ const SkPaint& paint, const SkMatrix& matrix) {
+ SkIRect ibounds;
+ SkRect bounds;
+ SkScalar inset = paint.getStrokeWidth();
+
+ bounds.set(pts, count);
+ bounds.inset(-inset, -inset);
+ matrix.mapRect(&bounds);
+
+ bounds.roundOut(&ibounds);
+ return bounder->doIRect(ibounds);
+}
+
+// each of these costs 8-bytes of stack space, so don't make it too large
+// must be even for lines/polygon to work
+#define MAX_DEV_PTS 32
+
+void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint,
+ bool forceUseDevice) const {
+ // if we're in lines mode, force count to be even
+ if (SkCanvas::kLines_PointMode == mode) {
+ count &= ~(size_t)1;
+ }
+
+ if ((long)count <= 0) {
+ return;
+ }
+
+ SkASSERT(pts != NULL);
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (fRC->isEmpty()) {
+ return;
+ }
+
+ if (fBounder) {
+ if (!bounder_points(fBounder, mode, count, pts, paint, *fMatrix)) {
+ return;
+ }
+
+ // clear the bounder and call this again, so we don't invoke the bounder
+ // later if we happen to call ourselves for drawRect, drawPath, etc.
+ SkDraw noBounder(*this);
+ noBounder.fBounder = NULL;
+ noBounder.drawPoints(mode, count, pts, paint, forceUseDevice);
+ return;
+ }
+
+ PtProcRec rec;
+ if (!forceUseDevice && rec.init(mode, paint, fMatrix, fRC)) {
+ SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
+
+ SkPoint devPts[MAX_DEV_PTS];
+ const SkMatrix* matrix = fMatrix;
+ SkBlitter* bltr = blitter.get();
+ PtProcRec::Proc proc = rec.chooseProc(&bltr);
+ // we have to back up subsequent passes if we're in polygon mode
+ const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
+
+ do {
+ size_t n = count;
+ if (n > MAX_DEV_PTS) {
+ n = MAX_DEV_PTS;
+ }
+ matrix->mapPoints(devPts, pts, n);
+ proc(rec, devPts, n, bltr);
+ pts += n - backup;
+ SkASSERT(count >= n);
+ count -= n;
+ if (count > 0) {
+ count += backup;
+ }
+ } while (count != 0);
+ } else {
+ switch (mode) {
+ case SkCanvas::kPoints_PointMode: {
+ // temporarily mark the paint as filling.
+ SkPaint newPaint(paint);
+ newPaint.setStyle(SkPaint::kFill_Style);
+
+ SkScalar width = newPaint.getStrokeWidth();
+ SkScalar radius = SkScalarHalf(width);
+
+ if (newPaint.getStrokeCap() == SkPaint::kRound_Cap) {
+ SkPath path;
+ SkMatrix preMatrix;
+
+ path.addCircle(0, 0, radius);
+ for (size_t i = 0; i < count; i++) {
+ preMatrix.setTranslate(pts[i].fX, pts[i].fY);
+ // pass true for the last point, since we can modify
+ // then path then
+ if (fDevice) {
+ fDevice->drawPath(*this, path, newPaint, &preMatrix,
+ (count-1) == i);
+ } else {
+ this->drawPath(path, newPaint, &preMatrix,
+ (count-1) == i);
+ }
+ }
+ } else {
+ SkRect r;
+
+ for (size_t i = 0; i < count; i++) {
+ r.fLeft = pts[i].fX - radius;
+ r.fTop = pts[i].fY - radius;
+ r.fRight = r.fLeft + width;
+ r.fBottom = r.fTop + width;
+ if (fDevice) {
+ fDevice->drawRect(*this, r, newPaint);
+ } else {
+ this->drawRect(r, newPaint);
+ }
+ }
+ }
+ 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]);
+
+ SkRect cullRect = SkRect::Make(fRC->getBounds());
+
+ if (paint.getPathEffect()->asPoints(&pointData, path, rec,
+ *fMatrix, &cullRect)) {
+ // '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;
+ SkPaint p(paint);
+ p.setStyle(SkPaint::kStroke_Style);
+ size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
+ for (size_t i = 0; i < count; i += inc) {
+ path.moveTo(pts[i]);
+ path.lineTo(pts[i+1]);
+ if (fDevice) {
+ fDevice->drawPath(*this, path, p, NULL, true);
+ } else {
+ this->drawPath(path, p, NULL, true);
+ }
+ path.rewind();
+ }
+ break;
+ }
+ }
+ }
+}
+
+static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix,
+ SkPoint* strokeSize) {
+ if (SkPaint::kMiter_Join != paint.getStrokeJoin() ||
+ paint.getStrokeMiter() < SK_ScalarSqrt2) {
+ return false;
+ }
+
+ SkASSERT(matrix.rectStaysRect());
+ SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() };
+ matrix.mapVectors(strokeSize, &pt, 1);
+ strokeSize->fX = SkScalarAbs(strokeSize->fX);
+ strokeSize->fY = SkScalarAbs(strokeSize->fY);
+ return true;
+}
+
+SkDraw::RectType SkDraw::ComputeRectType(const SkPaint& paint,
+ const SkMatrix& matrix,
+ SkPoint* strokeSize) {
+ RectType rtype;
+ const SkScalar width = paint.getStrokeWidth();
+ const bool zeroWidth = (0 == width);
+ SkPaint::Style style = paint.getStyle();
+
+ if ((SkPaint::kStrokeAndFill_Style == style) && zeroWidth) {
+ style = SkPaint::kFill_Style;
+ }
+
+ if (paint.getPathEffect() || paint.getMaskFilter() ||
+ paint.getRasterizer() || !matrix.rectStaysRect() ||
+ SkPaint::kStrokeAndFill_Style == style) {
+ rtype = kPath_RectType;
+ } else if (SkPaint::kFill_Style == style) {
+ rtype = kFill_RectType;
+ } else if (zeroWidth) {
+ rtype = kHair_RectType;
+ } else if (easy_rect_join(paint, matrix, strokeSize)) {
+ rtype = kStroke_RectType;
+ } else {
+ rtype = kPath_RectType;
+ }
+ return rtype;
+}
+
+static const SkPoint* rect_points(const SkRect& r) {
+ return SkTCast<const SkPoint*>(&r);
+}
+
+static SkPoint* rect_points(SkRect& r) {
+ return SkTCast<SkPoint*>(&r);
+}
+
+void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const {
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (fRC->isEmpty()) {
+ return;
+ }
+
+ SkPoint strokeSize;
+ RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize);
+
+ if (kPath_RectType == rtype) {
+ SkPath tmp;
+ tmp.addRect(rect);
+ tmp.setFillType(SkPath::kWinding_FillType);
+ this->drawPath(tmp, paint, NULL, true);
+ return;
+ }
+
+ const SkMatrix& matrix = *fMatrix;
+ SkRect devRect;
+
+ // transform rect into devRect
+ matrix.mapPoints(rect_points(devRect), rect_points(rect), 2);
+ devRect.sort();
+
+ if (fBounder && !fBounder->doRect(devRect, paint)) {
+ return;
+ }
+
+ // look for the quick exit, before we build a blitter
+ SkIRect ir;
+ devRect.roundOut(&ir);
+ if (paint.getStyle() != SkPaint::kFill_Style) {
+ // extra space for hairlines
+ ir.inset(-1, -1);
+ }
+ if (fRC->quickReject(ir)) {
+ return;
+ }
+
+ SkDeviceLooper looper(*fBitmap, *fRC, ir, paint.isAntiAlias());
+ while (looper.next()) {
+ SkRect localDevRect;
+ looper.mapRect(&localDevRect, devRect);
+ SkMatrix localMatrix;
+ looper.mapMatrix(&localMatrix, matrix);
+
+ SkAutoBlitterChoose blitterStorage(looper.getBitmap(), localMatrix,
+ paint);
+ const SkRasterClip& clip = looper.getRC();
+ SkBlitter* blitter = blitterStorage.get();
+
+ // we want to "fill" if we are kFill or kStrokeAndFill, since in the latter
+ // case we are also hairline (if we've gotten to here), which devolves to
+ // effectively just kFill
+ switch (rtype) {
+ case kFill_RectType:
+ if (paint.isAntiAlias()) {
+ SkScan::AntiFillRect(localDevRect, clip, blitter);
+ } else {
+ SkScan::FillRect(localDevRect, clip, blitter);
+ }
+ break;
+ case kStroke_RectType:
+ if (paint.isAntiAlias()) {
+ SkScan::AntiFrameRect(localDevRect, strokeSize, clip, blitter);
+ } else {
+ SkScan::FrameRect(localDevRect, strokeSize, clip, blitter);
+ }
+ break;
+ case kHair_RectType:
+ if (paint.isAntiAlias()) {
+ SkScan::AntiHairRect(localDevRect, clip, blitter);
+ } else {
+ SkScan::HairRect(localDevRect, clip, blitter);
+ }
+ break;
+ default:
+ SkDEBUGFAIL("bad rtype");
+ }
+ }
+}
+
+void SkDraw::drawDevMask(const SkMask& srcM, const SkPaint& paint) const {
+ if (srcM.fBounds.isEmpty()) {
+ return;
+ }
+
+ const SkMask* mask = &srcM;
+
+ SkMask dstM;
+ if (paint.getMaskFilter() &&
+ paint.getMaskFilter()->filterMask(&dstM, srcM, *fMatrix, NULL)) {
+ mask = &dstM;
+ } else {
+ dstM.fImage = NULL;
+ }
+ SkAutoMaskFreeImage ami(dstM.fImage);
+
+ if (fBounder && !fBounder->doIRect(mask->fBounds)) {
+ return;
+ }
+
+ SkAutoBlitterChoose blitterChooser(*fBitmap, *fMatrix, paint);
+ SkBlitter* blitter = blitterChooser.get();
+
+ SkAAClipBlitterWrapper wrapper;
+ const SkRegion* clipRgn;
+
+ if (fRC->isBW()) {
+ clipRgn = &fRC->bwRgn();
+ } else {
+ wrapper.init(*fRC, blitter);
+ clipRgn = &wrapper.getRgn();
+ blitter = wrapper.getBlitter();
+ }
+ blitter->blitMaskRegion(*mask, *clipRgn);
+}
+
+static SkScalar fast_len(const SkVector& vec) {
+ SkScalar x = SkScalarAbs(vec.fX);
+ SkScalar y = SkScalarAbs(vec.fY);
+ if (x < y) {
+ SkTSwap(x, y);
+ }
+ return x + SkScalarHalf(y);
+}
+
+static bool xfermodeSupportsCoverageAsAlpha(SkXfermode* xfer) {
+ SkXfermode::Coeff dc;
+ if (!SkXfermode::AsCoeff(xfer, NULL, &dc)) {
+ return false;
+ }
+
+ switch (dc) {
+ case SkXfermode::kOne_Coeff:
+ case SkXfermode::kISA_Coeff:
+ case SkXfermode::kISC_Coeff:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool SkDrawTreatAsHairline(const SkPaint& paint, const SkMatrix& matrix,
+ SkScalar* coverage) {
+ SkASSERT(coverage);
+ if (SkPaint::kStroke_Style != paint.getStyle()) {
+ return false;
+ }
+ SkScalar strokeWidth = paint.getStrokeWidth();
+ if (0 == strokeWidth) {
+ *coverage = SK_Scalar1;
+ return true;
+ }
+
+ // if we get here, we need to try to fake a thick-stroke with a modulated
+ // hairline
+
+ if (!paint.isAntiAlias()) {
+ return false;
+ }
+ if (matrix.hasPerspective()) {
+ return false;
+ }
+
+ SkVector src[2], dst[2];
+ src[0].set(strokeWidth, 0);
+ src[1].set(0, strokeWidth);
+ matrix.mapVectors(dst, src, 2);
+ SkScalar len0 = fast_len(dst[0]);
+ SkScalar len1 = fast_len(dst[1]);
+ if (len0 <= SK_Scalar1 && len1 <= SK_Scalar1) {
+ *coverage = SkScalarAve(len0, len1);
+ return true;
+ }
+ return false;
+}
+
+void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint,
+ const SkMatrix* prePathMatrix, bool pathIsMutable) const {
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (fRC->isEmpty()) {
+ return;
+ }
+
+ SkPath* pathPtr = (SkPath*)&origSrcPath;
+ bool doFill = true;
+ SkPath tmpPath;
+ SkMatrix tmpMatrix;
+ const SkMatrix* matrix = fMatrix;
+
+ if (prePathMatrix) {
+ if (origPaint.getPathEffect() || origPaint.getStyle() != SkPaint::kFill_Style ||
+ origPaint.getRasterizer()) {
+ SkPath* result = pathPtr;
+
+ if (!pathIsMutable) {
+ result = &tmpPath;
+ pathIsMutable = true;
+ }
+ pathPtr->transform(*prePathMatrix, result);
+ pathPtr = result;
+ } else {
+ if (!tmpMatrix.setConcat(*matrix, *prePathMatrix)) {
+ // overflow
+ return;
+ }
+ matrix = &tmpMatrix;
+ }
+ }
+ // at this point we're done with prePathMatrix
+ SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
+
+ SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+
+ {
+ SkScalar coverage;
+ if (SkDrawTreatAsHairline(origPaint, *matrix, &coverage)) {
+ if (SK_Scalar1 == coverage) {
+ paint.writable()->setStrokeWidth(0);
+ } else if (xfermodeSupportsCoverageAsAlpha(origPaint.getXfermode())) {
+ U8CPU newAlpha;
+#if 0
+ newAlpha = SkToU8(SkScalarRoundToInt(coverage *
+ origPaint.getAlpha()));
+#else
+ // this is the old technique, which we preserve for now so
+ // we don't change previous results (testing)
+ // the new way seems fine, its just (a tiny bit) different
+ int scale = (int)SkScalarMul(coverage, 256);
+ newAlpha = origPaint.getAlpha() * scale >> 8;
+#endif
+ SkPaint* writablePaint = paint.writable();
+ writablePaint->setStrokeWidth(0);
+ writablePaint->setAlpha(newAlpha);
+ }
+ }
+ }
+
+ if (paint->getPathEffect() || paint->getStyle() != SkPaint::kFill_Style) {
+ SkRect cullRect;
+ const SkRect* cullRectPtr = NULL;
+ if (this->computeConservativeLocalClipBounds(&cullRect)) {
+ cullRectPtr = &cullRect;
+ }
+ doFill = paint->getFillPath(*pathPtr, &tmpPath, cullRectPtr);
+ pathPtr = &tmpPath;
+ }
+
+ if (paint->getRasterizer()) {
+ SkMask mask;
+ if (paint->getRasterizer()->rasterize(*pathPtr, *matrix,
+ &fRC->getBounds(), paint->getMaskFilter(), &mask,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
+ this->drawDevMask(mask, *paint);
+ SkMask::FreeImage(mask.fImage);
+ }
+ return;
+ }
+
+ // avoid possibly allocating a new path in transform if we can
+ SkPath* devPathPtr = pathIsMutable ? pathPtr : &tmpPath;
+
+ // transform the path into device space
+ pathPtr->transform(*matrix, devPathPtr);
+
+ SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, *paint);
+
+ if (paint->getMaskFilter()) {
+ SkPaint::Style style = doFill ? SkPaint::kFill_Style :
+ SkPaint::kStroke_Style;
+ if (paint->getMaskFilter()->filterPath(*devPathPtr, *fMatrix, *fRC,
+ fBounder, blitter.get(),
+ style)) {
+ return; // filterPath() called the blitter, so we're done
+ }
+ }
+
+ if (fBounder && !fBounder->doPath(*devPathPtr, *paint, doFill)) {
+ return;
+ }
+
+ void (*proc)(const SkPath&, const SkRasterClip&, SkBlitter*);
+ if (doFill) {
+ if (paint->isAntiAlias()) {
+ proc = SkScan::AntiFillPath;
+ } else {
+ proc = SkScan::FillPath;
+ }
+ } else { // hairline
+ if (paint->isAntiAlias()) {
+ proc = SkScan::AntiHairPath;
+ } else {
+ proc = SkScan::HairPath;
+ }
+ }
+ proc(*devPathPtr, *fRC, blitter.get());
+}
+
+/** 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_translate(const SkMatrix& matrix, const SkBitmap& bitmap) {
+ unsigned bits = 0; // TODO: find a way to allow the caller to tell us to
+ // respect filtering.
+ return SkTreatAsSprite(matrix, bitmap.width(), bitmap.height(), bits);
+}
+
+void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap,
+ const SkPaint& paint) const {
+ SkASSERT(bitmap.getConfig() == SkBitmap::kA8_Config);
+
+ if (just_translate(*fMatrix, 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;
+ mask.fRowBytes = SkToU32(bitmap.rowBytes());
+ mask.fImage = bitmap.getAddr8(0, 0);
+
+ this->drawDevMask(mask, paint);
+ } else { // need to xform the bitmap first
+ SkRect r;
+ SkMask mask;
+
+ r.set(0, 0,
+ SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
+ fMatrix->mapRect(&r);
+ r.round(&mask.fBounds);
+
+ // set the mask's bounds to the transformed bitmap-bounds,
+ // clipped to the actual device
+ {
+ SkIRect devBounds;
+ devBounds.set(0, 0, fBitmap->width(), fBitmap->height());
+ // need intersect(l, t, r, b) on irect
+ if (!mask.fBounds.intersect(devBounds)) {
+ return;
+ }
+ }
+
+ mask.fFormat = SkMask::kA8_Format;
+ mask.fRowBytes = SkAlign4(mask.fBounds.width());
+ size_t size = mask.computeImageSize();
+ if (0 == size) {
+ // the mask is too big to allocated, draw nothing
+ return;
+ }
+
+ // allocate (and clear) our temp buffer to hold the transformed bitmap
+ SkAutoMalloc storage(size);
+ mask.fImage = (uint8_t*)storage.get();
+ memset(mask.fImage, 0, size);
+
+ // now draw our bitmap(src) into mask(dst), transformed by the matrix
+ {
+ SkBitmap device;
+ device.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(),
+ mask.fBounds.height(), mask.fRowBytes);
+ device.setPixels(mask.fImage);
+
+ SkCanvas c(device);
+ // need the unclipped top/left for the translate
+ c.translate(-SkIntToScalar(mask.fBounds.fLeft),
+ -SkIntToScalar(mask.fBounds.fTop));
+ c.concat(*fMatrix);
+
+ // We can't call drawBitmap, or we'll infinitely recurse. Instead
+ // we manually build a shader and draw that into our new mask
+ SkPaint tmpPaint;
+ tmpPaint.setFlags(paint.getFlags());
+ SkAutoBitmapShaderInstall install(bitmap, tmpPaint);
+ SkRect rr;
+ rr.set(0, 0, SkIntToScalar(bitmap.width()),
+ SkIntToScalar(bitmap.height()));
+ c.drawRect(rr, install.paintWithShader());
+ }
+ this->drawDevMask(mask, paint);
+ }
+}
+
+static bool clipped_out(const SkMatrix& m, const SkRasterClip& c,
+ const SkRect& srcR) {
+ SkRect dstR;
+ SkIRect devIR;
+
+ m.mapRect(&dstR, srcR);
+ dstR.roundOut(&devIR);
+ return c.quickReject(devIR);
+}
+
+static bool clipped_out(const SkMatrix& matrix, const SkRasterClip& clip,
+ int width, int height) {
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(width), SkIntToScalar(height));
+ return clipped_out(matrix, clip, r);
+}
+
+static bool clipHandlesSprite(const SkRasterClip& clip, int x, int y,
+ const SkBitmap& bitmap) {
+ return clip.isBW() ||
+ clip.quickContains(x, y, x + bitmap.width(), y + bitmap.height());
+}
+
+void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix,
+ const SkPaint& origPaint) const {
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (fRC->isEmpty() ||
+ bitmap.width() == 0 || bitmap.height() == 0 ||
+ bitmap.getConfig() == SkBitmap::kNo_Config) {
+ return;
+ }
+
+ SkPaint paint(origPaint);
+ paint.setStyle(SkPaint::kFill_Style);
+
+ SkMatrix matrix;
+ if (!matrix.setConcat(*fMatrix, prematrix)) {
+ return;
+ }
+
+ if (clipped_out(matrix, *fRC, bitmap.width(), bitmap.height())) {
+ return;
+ }
+
+ if (fBounder && just_translate(matrix, bitmap)) {
+ SkIRect ir;
+ int32_t ix = SkScalarRound(matrix.getTranslateX());
+ int32_t iy = SkScalarRound(matrix.getTranslateY());
+ ir.set(ix, iy, ix + bitmap.width(), iy + bitmap.height());
+ if (!fBounder->doIRect(ir)) {
+ 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)) {
+ uint32_t storage[kBlitterStorageLongCount];
+ SkBlitter* blitter = SkBlitter::ChooseSprite(*fBitmap, paint, bitmap,
+ ix, iy, storage, sizeof(storage));
+ if (blitter) {
+ SkAutoTPlacementDelete<SkBlitter> ad(blitter, storage);
+
+ SkIRect ir;
+ ir.set(ix, iy, ix + bitmap.width(), iy + bitmap.height());
+
+ SkScan::FillIRect(ir, *fRC, blitter);
+ return;
+ }
+ }
+ }
+
+ // now make a temp draw on the stack, and use it
+ //
+ SkDraw draw(*this);
+ draw.fMatrix = &matrix;
+
+ if (bitmap.getConfig() == SkBitmap::kA8_Config) {
+ draw.drawBitmapAsMask(bitmap, paint);
+ } else {
+ SkAutoBitmapShaderInstall install(bitmap, paint);
+
+ SkRect r;
+ r.set(0, 0, SkIntToScalar(bitmap.width()),
+ SkIntToScalar(bitmap.height()));
+ // is this ok if paint has a rasterizer?
+ draw.drawRect(r, install.paintWithShader());
+ }
+}
+
+void SkDraw::drawSprite(const SkBitmap& bitmap, int x, int y,
+ const SkPaint& origPaint) const {
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (fRC->isEmpty() ||
+ bitmap.width() == 0 || bitmap.height() == 0 ||
+ bitmap.getConfig() == SkBitmap::kNo_Config) {
+ return;
+ }
+
+ SkIRect bounds;
+ bounds.set(x, y, x + bitmap.width(), y + bitmap.height());
+
+ if (fRC->quickReject(bounds)) {
+ return; // nothing to draw
+ }
+
+ SkPaint paint(origPaint);
+ paint.setStyle(SkPaint::kFill_Style);
+
+ if (NULL == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, bitmap)) {
+ uint32_t storage[kBlitterStorageLongCount];
+ SkBlitter* blitter = SkBlitter::ChooseSprite(*fBitmap, paint, bitmap,
+ x, y, storage, sizeof(storage));
+
+ if (blitter) {
+ SkAutoTPlacementDelete<SkBlitter> ad(blitter, storage);
+
+ if (fBounder && !fBounder->doIRect(bounds)) {
+ return;
+ }
+
+ SkScan::FillIRect(bounds, *fRC, blitter);
+ return;
+ }
+ }
+
+ SkAutoBitmapShaderInstall install(bitmap, paint);
+ const SkPaint& shaderPaint = install.paintWithShader();
+
+ SkMatrix matrix;
+ SkRect r;
+
+ // get a scalar version of our rect
+ r.set(bounds);
+
+ // tell the shader our offset
+ matrix.setTranslate(r.fLeft, r.fTop);
+ shaderPaint.getShader()->setLocalMatrix(matrix);
+
+ SkDraw draw(*this);
+ matrix.reset();
+ draw.fMatrix = &matrix;
+ // call ourself with a rect
+ // is this OK if paint has a rasterizer?
+ draw.drawRect(r, shaderPaint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkScalerContext.h"
+#include "SkGlyphCache.h"
+#include "SkTextToPathIter.h"
+#include "SkUtils.h"
+
+static void measure_text(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc,
+ const char text[], size_t byteLength, SkVector* stopVector) {
+ SkFixed x = 0, y = 0;
+ const char* stop = text + byteLength;
+
+ SkAutoKern autokern;
+
+ while (text < stop) {
+ // don't need x, y here, since all subpixel variants will have the
+ // same advance
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+ x += autokern.adjust(glyph) + glyph.fAdvanceX;
+ y += glyph.fAdvanceY;
+ }
+ stopVector->set(SkFixedToScalar(x), SkFixedToScalar(y));
+
+ SkASSERT(text == stop);
+}
+
+void SkDraw::drawText_asPaths(const char text[], size_t byteLength,
+ SkScalar x, SkScalar y,
+ const SkPaint& paint) const {
+ SkDEBUGCODE(this->validate();)
+
+ SkTextToPathIter iter(text, byteLength, paint, true);
+
+ SkMatrix matrix;
+ matrix.setScale(iter.getPathScale(), iter.getPathScale());
+ matrix.postTranslate(x, y);
+
+ const SkPath* iterPath;
+ SkScalar xpos, prevXPos = 0;
+
+ while (iter.next(&iterPath, &xpos)) {
+ matrix.postTranslate(xpos - prevXPos, 0);
+ if (iterPath) {
+ const SkPaint& pnt = iter.getPaint();
+ if (fDevice) {
+ fDevice->drawPath(*this, *iterPath, pnt, &matrix, false);
+ } else {
+ this->drawPath(*iterPath, pnt, &matrix, false);
+ }
+ }
+ prevXPos = xpos;
+ }
+}
+
+// disable warning : local variable used without having been initialized
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void D1G_NoBounder_RectClip(const SkDraw1Glyph& state,
+ SkFixed fx, SkFixed fy,
+ const SkGlyph& glyph) {
+ int left = SkFixedFloor(fx);
+ int top = SkFixedFloor(fy);
+ SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0);
+ SkASSERT(NULL == state.fBounder);
+ SkASSERT((NULL == state.fClip && state.fAAClip) ||
+ (state.fClip && NULL == state.fAAClip && state.fClip->isRect()));
+
+ left += glyph.fLeft;
+ top += glyph.fTop;
+
+ int right = left + glyph.fWidth;
+ int bottom = top + glyph.fHeight;
+
+ SkMask mask;
+ SkIRect storage;
+ SkIRect* bounds = &mask.fBounds;
+
+ mask.fBounds.set(left, top, right, bottom);
+
+ // this extra test is worth it, assuming that most of the time it succeeds
+ // since we can avoid writing to storage
+ if (!state.fClipBounds.containsNoEmptyCheck(left, top, right, bottom)) {
+ if (!storage.intersectNoEmptyCheck(mask.fBounds, state.fClipBounds))
+ return;
+ bounds = &storage;
+ }
+
+ uint8_t* aa = (uint8_t*)glyph.fImage;
+ if (NULL == aa) {
+ aa = (uint8_t*)state.fCache->findImage(glyph);
+ if (NULL == aa) {
+ return; // can't rasterize glyph
+ }
+ }
+
+ mask.fRowBytes = glyph.rowBytes();
+ mask.fFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ mask.fImage = aa;
+ state.blitMask(mask, *bounds);
+}
+
+static void D1G_NoBounder_RgnClip(const SkDraw1Glyph& state,
+ SkFixed fx, SkFixed fy,
+ const SkGlyph& glyph) {
+ int left = SkFixedFloor(fx);
+ int top = SkFixedFloor(fy);
+ SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0);
+ SkASSERT(!state.fClip->isRect());
+ SkASSERT(NULL == state.fBounder);
+
+ SkMask mask;
+
+ left += glyph.fLeft;
+ top += glyph.fTop;
+
+ mask.fBounds.set(left, top, left + glyph.fWidth, top + glyph.fHeight);
+ SkRegion::Cliperator clipper(*state.fClip, mask.fBounds);
+
+ if (!clipper.done()) {
+ const SkIRect& cr = clipper.rect();
+ const uint8_t* aa = (const uint8_t*)glyph.fImage;
+ if (NULL == aa) {
+ aa = (uint8_t*)state.fCache->findImage(glyph);
+ if (NULL == aa) {
+ return;
+ }
+ }
+
+ mask.fRowBytes = glyph.rowBytes();
+ mask.fFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ mask.fImage = (uint8_t*)aa;
+ do {
+ state.blitMask(mask, cr);
+ clipper.next();
+ } while (!clipper.done());
+ }
+}
+
+static void D1G_Bounder(const SkDraw1Glyph& state,
+ SkFixed fx, SkFixed fy,
+ const SkGlyph& glyph) {
+ int left = SkFixedFloor(fx);
+ int top = SkFixedFloor(fy);
+ SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0);
+
+ SkMask mask;
+
+ left += glyph.fLeft;
+ top += glyph.fTop;
+
+ mask.fBounds.set(left, top, left + glyph.fWidth, top + glyph.fHeight);
+ SkRegion::Cliperator clipper(*state.fClip, mask.fBounds);
+
+ if (!clipper.done()) {
+ const SkIRect& cr = clipper.rect();
+ const uint8_t* aa = (const uint8_t*)glyph.fImage;
+ if (NULL == aa) {
+ aa = (uint8_t*)state.fCache->findImage(glyph);
+ if (NULL == aa) {
+ return;
+ }
+ }
+
+ // we need to pass the origin, which we approximate with our
+ // (unadjusted) left,top coordinates (the caller called fixedfloor)
+ if (state.fBounder->doIRectGlyph(cr,
+ left - glyph.fLeft,
+ top - glyph.fTop, glyph)) {
+ mask.fRowBytes = glyph.rowBytes();
+ mask.fFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ mask.fImage = (uint8_t*)aa;
+ do {
+ state.blitMask(mask, cr);
+ clipper.next();
+ } while (!clipper.done());
+ }
+ }
+}
+
+static void D1G_Bounder_AAClip(const SkDraw1Glyph& state,
+ SkFixed fx, SkFixed fy,
+ const SkGlyph& glyph) {
+ int left = SkFixedFloor(fx);
+ int top = SkFixedFloor(fy);
+ SkIRect bounds;
+ bounds.set(left, top, left + glyph.fWidth, top + glyph.fHeight);
+
+ if (state.fBounder->doIRectGlyph(bounds, left, top, glyph)) {
+ D1G_NoBounder_RectClip(state, fx, fy, glyph);
+ }
+}
+
+static bool hasCustomD1GProc(const SkDraw& draw) {
+ return draw.fProcs && draw.fProcs->fD1GProc;
+}
+
+static bool needsRasterTextBlit(const SkDraw& draw) {
+ return !hasCustomD1GProc(draw);
+}
+
+SkDraw1Glyph::Proc SkDraw1Glyph::init(const SkDraw* draw, SkBlitter* blitter,
+ SkGlyphCache* cache, const SkPaint& pnt) {
+ fDraw = draw;
+ fBounder = draw->fBounder;
+ fBlitter = blitter;
+ fCache = cache;
+ fPaint = &pnt;
+
+ if (cache->isSubpixel()) {
+ fHalfSampleX = fHalfSampleY = (SK_FixedHalf >> SkGlyph::kSubBits);
+ } else {
+ fHalfSampleX = fHalfSampleY = SK_FixedHalf;
+ }
+
+ if (hasCustomD1GProc(*draw)) {
+ // todo: fix this assumption about clips w/ custom
+ fClip = draw->fClip;
+ fClipBounds = fClip->getBounds();
+ return draw->fProcs->fD1GProc;
+ }
+
+ if (draw->fRC->isBW()) {
+ fAAClip = NULL;
+ fClip = &draw->fRC->bwRgn();
+ fClipBounds = fClip->getBounds();
+ if (NULL == fBounder) {
+ if (fClip->isRect()) {
+ return D1G_NoBounder_RectClip;
+ } else {
+ return D1G_NoBounder_RgnClip;
+ }
+ } else {
+ return D1G_Bounder;
+ }
+ } else { // aaclip
+ fAAClip = &draw->fRC->aaRgn();
+ fClip = NULL;
+ fClipBounds = fAAClip->getBounds();
+ if (NULL == fBounder) {
+ return D1G_NoBounder_RectClip;
+ } else {
+ return D1G_Bounder_AAClip;
+ }
+ }
+}
+
+void SkDraw1Glyph::blitMaskAsSprite(const SkMask& mask) const {
+ SkASSERT(SkMask::kARGB32_Format == mask.fFormat);
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config,
+ mask.fBounds.width(), mask.fBounds.height(), mask.fRowBytes);
+ bm.setPixels((SkPMColor*)mask.fImage);
+
+ fDraw->drawSprite(bm, mask.fBounds.x(), mask.fBounds.y(), *fPaint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkDraw::drawText(const char text[], size_t byteLength,
+ SkScalar x, SkScalar y, const SkPaint& paint) const {
+ SkASSERT(byteLength == 0 || text != NULL);
+
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (text == NULL || byteLength == 0 || fRC->isEmpty()) {
+ return;
+ }
+
+ // SkScalarRec doesn't currently have a way of representing hairline stroke and
+ // will fill if its frame-width is 0.
+ if (ShouldDrawTextAsPaths(paint, *fMatrix)) {
+ this->drawText_asPaths(text, byteLength, x, y, paint);
+ return;
+ }
+
+ SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
+
+ SkAutoGlyphCache autoCache(paint, &fDevice->fLeakyProperties, fMatrix);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ // transform our starting point
+ {
+ SkPoint loc;
+ fMatrix->mapXY(x, y, &loc);
+ x = loc.fX;
+ y = loc.fY;
+ }
+
+ // need to measure first
+ if (paint.getTextAlign() != SkPaint::kLeft_Align) {
+ SkVector stop;
+
+ measure_text(cache, glyphCacheProc, text, byteLength, &stop);
+
+ SkScalar stopX = stop.fX;
+ SkScalar stopY = stop.fY;
+
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ stopX = SkScalarHalf(stopX);
+ stopY = SkScalarHalf(stopY);
+ }
+ x -= stopX;
+ y -= stopY;
+ }
+
+ const char* stop = text + byteLength;
+
+ SkAAClipBlitter aaBlitter;
+ SkAutoBlitterChoose blitterChooser;
+ SkBlitter* blitter = NULL;
+ if (needsRasterTextBlit(*this)) {
+ blitterChooser.choose(*fBitmap, *fMatrix, paint);
+ blitter = blitterChooser.get();
+ if (fRC->isAA()) {
+ aaBlitter.init(blitter, &fRC->aaRgn());
+ blitter = &aaBlitter;
+ }
+ }
+
+ SkAutoKern autokern;
+ SkDraw1Glyph d1g;
+ SkDraw1Glyph::Proc proc = d1g.init(this, blitter, cache, paint);
+
+ SkFixed fxMask = ~0;
+ SkFixed fyMask = ~0;
+ if (cache->isSubpixel()) {
+ SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(*fMatrix);
+ if (kX_SkAxisAlignment == baseline) {
+ fyMask = 0;
+ d1g.fHalfSampleY = SK_FixedHalf;
+ } else if (kY_SkAxisAlignment == baseline) {
+ fxMask = 0;
+ d1g.fHalfSampleX = SK_FixedHalf;
+ }
+ }
+
+ SkFixed fx = SkScalarToFixed(x) + d1g.fHalfSampleX;
+ SkFixed fy = SkScalarToFixed(y) + d1g.fHalfSampleY;
+
+ while (text < stop) {
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
+
+ fx += autokern.adjust(glyph);
+
+ if (glyph.fWidth) {
+ proc(d1g, fx, fy, glyph);
+ }
+ fx += glyph.fAdvanceX;
+ fy += glyph.fAdvanceY;
+ }
+}
+
+// last parameter is interpreted as SkFixed [x, y]
+// return the fixed position, which may be rounded or not by the caller
+// e.g. subpixel doesn't round
+typedef void (*AlignProc)(const SkPoint&, const SkGlyph&, SkIPoint*);
+
+static void leftAlignProc(const SkPoint& loc, const SkGlyph& glyph,
+ SkIPoint* dst) {
+ dst->set(SkScalarToFixed(loc.fX), SkScalarToFixed(loc.fY));
+}
+
+static void centerAlignProc(const SkPoint& loc, const SkGlyph& glyph,
+ SkIPoint* dst) {
+ dst->set(SkScalarToFixed(loc.fX) - (glyph.fAdvanceX >> 1),
+ SkScalarToFixed(loc.fY) - (glyph.fAdvanceY >> 1));
+}
+
+static void rightAlignProc(const SkPoint& loc, const SkGlyph& glyph,
+ SkIPoint* dst) {
+ dst->set(SkScalarToFixed(loc.fX) - glyph.fAdvanceX,
+ SkScalarToFixed(loc.fY) - glyph.fAdvanceY);
+}
+
+static AlignProc pick_align_proc(SkPaint::Align align) {
+ static const AlignProc gProcs[] = {
+ leftAlignProc, centerAlignProc, rightAlignProc
+ };
+
+ SkASSERT((unsigned)align < SK_ARRAY_COUNT(gProcs));
+
+ return gProcs[align];
+}
+
+class TextMapState {
+public:
+ mutable SkPoint fLoc;
+
+ TextMapState(const SkMatrix& matrix, SkScalar y)
+ : fMatrix(matrix), fProc(matrix.getMapXYProc()), fY(y) {}
+
+ typedef void (*Proc)(const TextMapState&, const SkScalar pos[]);
+
+ Proc pickProc(int scalarsPerPosition);
+
+private:
+ const SkMatrix& fMatrix;
+ SkMatrix::MapXYProc fProc;
+ SkScalar fY; // ignored by MapXYProc
+ // these are only used by Only... procs
+ SkScalar fScaleX, fTransX, fTransformedY;
+
+ static void MapXProc(const TextMapState& state, const SkScalar pos[]) {
+ state.fProc(state.fMatrix, *pos, state.fY, &state.fLoc);
+ }
+
+ static void MapXYProc(const TextMapState& state, const SkScalar pos[]) {
+ state.fProc(state.fMatrix, pos[0], pos[1], &state.fLoc);
+ }
+
+ static void MapOnlyScaleXProc(const TextMapState& state,
+ const SkScalar pos[]) {
+ state.fLoc.set(SkScalarMul(state.fScaleX, *pos) + state.fTransX,
+ state.fTransformedY);
+ }
+
+ static void MapOnlyTransXProc(const TextMapState& state,
+ const SkScalar pos[]) {
+ state.fLoc.set(*pos + state.fTransX, state.fTransformedY);
+ }
+};
+
+TextMapState::Proc TextMapState::pickProc(int scalarsPerPosition) {
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+ if (1 == scalarsPerPosition) {
+ unsigned mtype = fMatrix.getType();
+ if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)) {
+ return MapXProc;
+ } else {
+ fScaleX = fMatrix.getScaleX();
+ fTransX = fMatrix.getTranslateX();
+ fTransformedY = SkScalarMul(fY, fMatrix.getScaleY()) +
+ fMatrix.getTranslateY();
+ return (mtype & SkMatrix::kScale_Mask) ?
+ MapOnlyScaleXProc : MapOnlyTransXProc;
+ }
+ } else {
+ return MapXYProc;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SkDraw::drawPosText_asPaths(const char text[], size_t byteLength,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPosition,
+ const SkPaint& origPaint) const {
+ // setup our std paint, in hopes of getting hits in the cache
+ SkPaint paint(origPaint);
+ SkScalar matrixScale = paint.setupForAsPaths();
+
+ SkMatrix matrix;
+ matrix.setScale(matrixScale, matrixScale);
+
+ SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
+ SkAutoGlyphCache autoCache(paint, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ const char* stop = text + byteLength;
+ AlignProc alignProc = pick_align_proc(paint.getTextAlign());
+ TextMapState tms(SkMatrix::I(), constY);
+ TextMapState::Proc tmsProc = tms.pickProc(scalarsPerPosition);
+
+ while (text < stop) {
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+ if (glyph.fWidth) {
+ const SkPath* path = cache->findPath(glyph);
+ if (path) {
+ tmsProc(tms, pos);
+ SkIPoint fixedLoc;
+ alignProc(tms.fLoc, glyph, &fixedLoc);
+
+ matrix[SkMatrix::kMTransX] = SkFixedToScalar(fixedLoc.fX);
+ matrix[SkMatrix::kMTransY] = SkFixedToScalar(fixedLoc.fY);
+ if (fDevice) {
+ fDevice->drawPath(*this, *path, paint, &matrix, false);
+ } else {
+ this->drawPath(*path, paint, &matrix, false);
+ }
+ }
+ }
+ pos += scalarsPerPosition;
+ }
+}
+
+void SkDraw::drawPosText(const char text[], size_t byteLength,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPosition, const SkPaint& paint) const {
+ SkASSERT(byteLength == 0 || text != NULL);
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+ SkDEBUGCODE(this->validate();)
+
+ // nothing to draw
+ if (text == NULL || byteLength == 0 || fRC->isEmpty()) {
+ return;
+ }
+
+ if (ShouldDrawTextAsPaths(paint, *fMatrix)) {
+ this->drawPosText_asPaths(text, byteLength, pos, constY,
+ scalarsPerPosition, paint);
+ return;
+ }
+
+ SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
+ SkAutoGlyphCache autoCache(paint, &fDevice->fLeakyProperties, fMatrix);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ SkAAClipBlitterWrapper wrapper;
+ SkAutoBlitterChoose blitterChooser;
+ SkBlitter* blitter = NULL;
+ if (needsRasterTextBlit(*this)) {
+ blitterChooser.choose(*fBitmap, *fMatrix, paint);
+ blitter = blitterChooser.get();
+ if (fRC->isAA()) {
+ wrapper.init(*fRC, blitter);
+ blitter = wrapper.getBlitter();
+ }
+ }
+
+ const char* stop = text + byteLength;
+ AlignProc alignProc = pick_align_proc(paint.getTextAlign());
+ SkDraw1Glyph d1g;
+ SkDraw1Glyph::Proc proc = d1g.init(this, blitter, cache, paint);
+ TextMapState tms(*fMatrix, constY);
+ TextMapState::Proc tmsProc = tms.pickProc(scalarsPerPosition);
+
+ if (cache->isSubpixel()) {
+ // maybe we should skip the rounding if linearText is set
+ SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(*fMatrix);
+
+ SkFixed fxMask = ~0;
+ SkFixed fyMask = ~0;
+ if (kX_SkAxisAlignment == baseline) {
+ fyMask = 0;
+#ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
+ d1g.fHalfSampleY = SK_FixedHalf;
+#endif
+ } else if (kY_SkAxisAlignment == baseline) {
+ fxMask = 0;
+#ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX
+ d1g.fHalfSampleX = SK_FixedHalf;
+#endif
+ }
+
+ if (SkPaint::kLeft_Align == paint.getTextAlign()) {
+ while (text < stop) {
+ tmsProc(tms, pos);
+
+ SkFixed fx = SkScalarToFixed(tms.fLoc.fX) + d1g.fHalfSampleX;
+ SkFixed fy = SkScalarToFixed(tms.fLoc.fY) + d1g.fHalfSampleY;
+
+ const SkGlyph& glyph = glyphCacheProc(cache, &text,
+ fx & fxMask, fy & fyMask);
+
+ if (glyph.fWidth) {
+ proc(d1g, fx, fy, glyph);
+ }
+ pos += scalarsPerPosition;
+ }
+ } else {
+ while (text < stop) {
+ const char* currentText = text;
+ const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
+
+ if (metricGlyph.fWidth) {
+ SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
+ SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
+
+ tmsProc(tms, pos);
+ SkIPoint fixedLoc;
+ alignProc(tms.fLoc, metricGlyph, &fixedLoc);
+
+ SkFixed fx = fixedLoc.fX + d1g.fHalfSampleX;
+ SkFixed fy = fixedLoc.fY + d1g.fHalfSampleY;
+
+ // have to call again, now that we've been "aligned"
+ const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
+ fx & fxMask, fy & fyMask);
+ // the assumption is that the metrics haven't changed
+ SkASSERT(prevAdvX == glyph.fAdvanceX);
+ SkASSERT(prevAdvY == glyph.fAdvanceY);
+ SkASSERT(glyph.fWidth);
+
+ proc(d1g, fx, fy, glyph);
+ }
+ pos += scalarsPerPosition;
+ }
+ }
+ } else { // not subpixel
+ if (SkPaint::kLeft_Align == paint.getTextAlign()) {
+ while (text < stop) {
+ // the last 2 parameters are ignored
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+ if (glyph.fWidth) {
+ tmsProc(tms, pos);
+
+ proc(d1g,
+ SkScalarToFixed(tms.fLoc.fX) + SK_FixedHalf, //d1g.fHalfSampleX,
+ SkScalarToFixed(tms.fLoc.fY) + SK_FixedHalf, //d1g.fHalfSampleY,
+ glyph);
+ }
+ pos += scalarsPerPosition;
+ }
+ } else {
+ while (text < stop) {
+ // the last 2 parameters are ignored
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+ if (glyph.fWidth) {
+ tmsProc(tms, pos);
+
+ SkIPoint fixedLoc;
+ alignProc(tms.fLoc, glyph, &fixedLoc);
+
+ proc(d1g,
+ fixedLoc.fX + SK_FixedHalf, //d1g.fHalfSampleX,
+ fixedLoc.fY + SK_FixedHalf, //d1g.fHalfSampleY,
+ glyph);
+ }
+ pos += scalarsPerPosition;
+ }
+ }
+ }
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkPathMeasure.h"
+
+static void morphpoints(SkPoint dst[], const SkPoint src[], int count,
+ SkPathMeasure& meas, const SkMatrix& matrix) {
+ SkMatrix::MapXYProc proc = matrix.getMapXYProc();
+
+ for (int i = 0; i < count; i++) {
+ SkPoint pos;
+ SkVector tangent;
+
+ proc(matrix, src[i].fX, src[i].fY, &pos);
+ SkScalar sx = pos.fX;
+ SkScalar sy = pos.fY;
+
+ if (!meas.getPosTan(sx, &pos, &tangent)) {
+ // set to 0 if the measure failed, so that we just set dst == pos
+ tangent.set(0, 0);
+ }
+
+ /* This is the old way (that explains our approach but is way too slow
+ SkMatrix matrix;
+ SkPoint pt;
+
+ pt.set(sx, sy);
+ matrix.setSinCos(tangent.fY, tangent.fX);
+ matrix.preTranslate(-sx, 0);
+ matrix.postTranslate(pos.fX, pos.fY);
+ matrix.mapPoints(&dst[i], &pt, 1);
+ */
+ dst[i].set(pos.fX - SkScalarMul(tangent.fY, sy),
+ pos.fY + SkScalarMul(tangent.fX, sy));
+ }
+}
+
+/* TODO
+
+ Need differentially more subdivisions when the follow-path is curvy. Not sure how to
+ determine that, but we need it. I guess a cheap answer is let the caller tell us,
+ but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
+*/
+static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
+ const SkMatrix& matrix) {
+ SkPath::Iter iter(src, false);
+ SkPoint srcP[4], dstP[3];
+ SkPath::Verb verb;
+
+ while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ morphpoints(dstP, srcP, 1, meas, matrix);
+ dst->moveTo(dstP[0]);
+ break;
+ case SkPath::kLine_Verb:
+ // turn lines into quads to look bendy
+ srcP[0].fX = SkScalarAve(srcP[0].fX, srcP[1].fX);
+ srcP[0].fY = SkScalarAve(srcP[0].fY, srcP[1].fY);
+ morphpoints(dstP, srcP, 2, meas, matrix);
+ dst->quadTo(dstP[0], dstP[1]);
+ break;
+ case SkPath::kQuad_Verb:
+ morphpoints(dstP, &srcP[1], 2, meas, matrix);
+ dst->quadTo(dstP[0], dstP[1]);
+ break;
+ case SkPath::kCubic_Verb:
+ morphpoints(dstP, &srcP[1], 3, meas, matrix);
+ dst->cubicTo(dstP[0], dstP[1], dstP[2]);
+ break;
+ case SkPath::kClose_Verb:
+ dst->close();
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ break;
+ }
+ }
+}
+
+void SkDraw::drawTextOnPath(const char text[], size_t byteLength,
+ const SkPath& follow, const SkMatrix* matrix,
+ const SkPaint& paint) const {
+ SkASSERT(byteLength == 0 || text != NULL);
+
+ // nothing to draw
+ if (text == NULL || byteLength == 0 || fRC->isEmpty()) {
+ return;
+ }
+
+ SkTextToPathIter iter(text, byteLength, paint, true);
+ SkPathMeasure meas(follow, false);
+ SkScalar hOffset = 0;
+
+ // need to measure first
+ if (paint.getTextAlign() != SkPaint::kLeft_Align) {
+ SkScalar pathLen = meas.getLength();
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ pathLen = SkScalarHalf(pathLen);
+ }
+ hOffset += pathLen;
+ }
+
+ const SkPath* iterPath;
+ SkScalar xpos;
+ SkMatrix scaledMatrix;
+ SkScalar scale = iter.getPathScale();
+
+ scaledMatrix.setScale(scale, scale);
+
+ while (iter.next(&iterPath, &xpos)) {
+ if (iterPath) {
+ SkPath tmp;
+ SkMatrix m(scaledMatrix);
+
+ m.postTranslate(xpos + hOffset, 0);
+ if (matrix) {
+ m.postConcat(*matrix);
+ }
+ morphpath(&tmp, *iterPath, meas, m);
+ if (fDevice) {
+ fDevice->drawPath(*this, tmp, iter.getPaint(), NULL, true);
+ } else {
+ this->drawPath(tmp, iter.getPaint(), NULL, true);
+ }
+ }
+ }
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+void SkDraw::drawPosTextOnPath(const char text[], size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint,
+ const SkPath& path, const SkMatrix* matrix) const {
+ // nothing to draw
+ if (text == NULL || byteLength == 0 || fRC->isEmpty()) {
+ return;
+ }
+
+ SkMatrix scaledMatrix;
+ SkPathMeasure meas(path, false);
+
+ SkMeasureCacheProc glyphCacheProc = paint.getMeasureCacheProc(
+ SkPaint::kForward_TextBufferDirection, true);
+
+ // Copied (modified) from SkTextToPathIter constructor to setup paint
+ SkPaint tempPaint(paint);
+
+ tempPaint.setLinearText(true);
+ tempPaint.setMaskFilter(NULL); // don't want this affecting our path-cache lookup
+
+ if (tempPaint.getPathEffect() == NULL && !(tempPaint.getStrokeWidth() > 0
+ && tempPaint.getStyle() != SkPaint::kFill_Style)) {
+ tempPaint.setStyle(SkPaint::kFill_Style);
+ tempPaint.setPathEffect(NULL);
+ }
+ // End copied from SkTextToPathIter constructor
+
+ // detach cache
+ SkGlyphCache* cache = tempPaint.detachCache(NULL, NULL);
+
+ // Must set scale, even if 1
+ SkScalar scale = SK_Scalar1;
+ scaledMatrix.setScale(scale, scale);
+
+ // Loop over all glyph ids
+ for (const char* stop = text + byteLength; text < stop; pos++) {
+
+ const SkGlyph& glyph = glyphCacheProc(cache, &text);
+ SkPath tmp;
+
+ const SkPath* glyphPath = cache->findPath(glyph);
+ if (glyphPath == NULL) {
+ continue;
+ }
+
+ SkMatrix m(scaledMatrix);
+ m.postTranslate(pos->fX, 0);
+
+ if (matrix) {
+ m.postConcat(*matrix);
+ }
+
+ morphpath(&tmp, *glyphPath, meas, m);
+ this->drawPath(tmp, tempPaint);
+
+ }
+
+ // re-attach cache
+ SkGlyphCache::AttachCache(cache);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct VertState {
+ int f0, f1, f2;
+
+ VertState(int vCount, const uint16_t indices[], int indexCount)
+ : fIndices(indices) {
+ fCurrIndex = 0;
+ if (indices) {
+ fCount = indexCount;
+ } else {
+ fCount = vCount;
+ }
+ }
+
+ typedef bool (*Proc)(VertState*);
+ Proc chooseProc(SkCanvas::VertexMode mode);
+
+private:
+ int fCount;
+ int fCurrIndex;
+ const uint16_t* fIndices;
+
+ static bool Triangles(VertState*);
+ static bool TrianglesX(VertState*);
+ static bool TriangleStrip(VertState*);
+ static bool TriangleStripX(VertState*);
+ static bool TriangleFan(VertState*);
+ static bool TriangleFanX(VertState*);
+};
+
+bool VertState::Triangles(VertState* state) {
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f0 = index + 0;
+ state->f1 = index + 1;
+ state->f2 = index + 2;
+ state->fCurrIndex = index + 3;
+ return true;
+}
+
+bool VertState::TrianglesX(VertState* state) {
+ const uint16_t* indices = state->fIndices;
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f0 = indices[index + 0];
+ state->f1 = indices[index + 1];
+ state->f2 = indices[index + 2];
+ state->fCurrIndex = index + 3;
+ return true;
+}
+
+bool VertState::TriangleStrip(VertState* state) {
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f2 = index + 2;
+ if (index & 1) {
+ state->f0 = index + 1;
+ state->f1 = index + 0;
+ } else {
+ state->f0 = index + 0;
+ state->f1 = index + 1;
+ }
+ state->fCurrIndex = index + 1;
+ return true;
+}
+
+bool VertState::TriangleStripX(VertState* state) {
+ const uint16_t* indices = state->fIndices;
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f2 = indices[index + 2];
+ if (index & 1) {
+ state->f0 = indices[index + 1];
+ state->f1 = indices[index + 0];
+ } else {
+ state->f0 = indices[index + 0];
+ state->f1 = indices[index + 1];
+ }
+ state->fCurrIndex = index + 1;
+ return true;
+}
+
+bool VertState::TriangleFan(VertState* state) {
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f0 = 0;
+ state->f1 = index + 1;
+ state->f2 = index + 2;
+ state->fCurrIndex = index + 1;
+ return true;
+}
+
+bool VertState::TriangleFanX(VertState* state) {
+ const uint16_t* indices = state->fIndices;
+ int index = state->fCurrIndex;
+ if (index + 3 > state->fCount) {
+ return false;
+ }
+ state->f0 = indices[0];
+ state->f1 = indices[index + 1];
+ state->f2 = indices[index + 2];
+ state->fCurrIndex = index + 1;
+ return true;
+}
+
+VertState::Proc VertState::chooseProc(SkCanvas::VertexMode mode) {
+ switch (mode) {
+ case SkCanvas::kTriangles_VertexMode:
+ return fIndices ? TrianglesX : Triangles;
+ case SkCanvas::kTriangleStrip_VertexMode:
+ return fIndices ? TriangleStripX : TriangleStrip;
+ case SkCanvas::kTriangleFan_VertexMode:
+ return fIndices ? TriangleFanX : TriangleFan;
+ default:
+ return NULL;
+ }
+}
+
+typedef void (*HairProc)(const SkPoint&, const SkPoint&, const SkRasterClip&,
+ SkBlitter*);
+
+static HairProc ChooseHairProc(bool doAntiAlias) {
+ return doAntiAlias ? SkScan::AntiHairLine : SkScan::HairLine;
+}
+
+static bool texture_to_matrix(const VertState& state, const SkPoint verts[],
+ const SkPoint texs[], SkMatrix* matrix) {
+ SkPoint src[3], dst[3];
+
+ src[0] = texs[state.f0];
+ src[1] = texs[state.f1];
+ src[2] = texs[state.f2];
+ dst[0] = verts[state.f0];
+ dst[1] = verts[state.f1];
+ dst[2] = verts[state.f2];
+ return matrix->setPolyToPoly(src, dst, 3);
+}
+
+class SkTriColorShader : public SkShader {
+public:
+ SkTriColorShader() {}
+
+ bool setup(const SkPoint pts[], const SkColor colors[], int, int, int);
+
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTriColorShader)
+
+protected:
+ SkTriColorShader(SkFlattenableReadBuffer& buffer) : SkShader(buffer) {}
+
+private:
+ SkMatrix fDstToUnit;
+ SkPMColor fColors[3];
+
+ typedef SkShader INHERITED;
+};
+
+bool SkTriColorShader::setup(const SkPoint pts[], const SkColor colors[],
+ int index0, int index1, int index2) {
+
+ fColors[0] = SkPreMultiplyColor(colors[index0]);
+ fColors[1] = SkPreMultiplyColor(colors[index1]);
+ fColors[2] = SkPreMultiplyColor(colors[index2]);
+
+ SkMatrix m, im;
+ m.reset();
+ m.set(0, pts[index1].fX - pts[index0].fX);
+ m.set(1, pts[index2].fX - pts[index0].fX);
+ m.set(2, pts[index0].fX);
+ m.set(3, pts[index1].fY - pts[index0].fY);
+ m.set(4, pts[index2].fY - pts[index0].fY);
+ m.set(5, pts[index0].fY);
+ if (!m.invert(&im)) {
+ return false;
+ }
+ return fDstToUnit.setConcat(im, this->getTotalInverse());
+}
+
+#include "SkColorPriv.h"
+#include "SkComposeShader.h"
+
+static int ScalarTo256(SkScalar v) {
+ int scale = SkScalarToFixed(v) >> 8;
+ if (scale < 0) {
+ scale = 0;
+ }
+ if (scale > 255) {
+ scale = 255;
+ }
+ return SkAlpha255To256(scale);
+}
+
+void SkTriColorShader::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
+ SkPoint src;
+
+ for (int i = 0; i < count; i++) {
+ fDstToUnit.mapXY(SkIntToScalar(x), SkIntToScalar(y), &src);
+ x += 1;
+
+ int scale1 = ScalarTo256(src.fX);
+ int scale2 = ScalarTo256(src.fY);
+ int scale0 = 256 - scale1 - scale2;
+ if (scale0 < 0) {
+ if (scale1 > scale2) {
+ scale2 = 256 - scale1;
+ } else {
+ scale1 = 256 - scale2;
+ }
+ scale0 = 0;
+ }
+
+ dstC[i] = SkAlphaMulQ(fColors[0], scale0) +
+ SkAlphaMulQ(fColors[1], scale1) +
+ SkAlphaMulQ(fColors[2], scale2);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkTriColorShader::toString(SkString* str) const {
+ str->append("SkTriColorShader: (");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+void SkDraw::drawVertices(SkCanvas::VertexMode vmode, int count,
+ const SkPoint vertices[], const SkPoint textures[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) const {
+ SkASSERT(0 == count || NULL != vertices);
+
+ // abort early if there is nothing to draw
+ if (count < 3 || (indices && indexCount < 3) || fRC->isEmpty()) {
+ return;
+ }
+
+ // transform out vertices into device coordinates
+ SkAutoSTMalloc<16, SkPoint> storage(count);
+ SkPoint* devVerts = storage.get();
+ fMatrix->mapPoints(devVerts, vertices, count);
+
+ if (fBounder) {
+ SkRect bounds;
+ bounds.set(devVerts, count);
+ if (!fBounder->doRect(bounds, paint)) {
+ return;
+ }
+ }
+
+ /*
+ We can draw the vertices in 1 of 4 ways:
+
+ - solid color (no shader/texture[], no colors[])
+ - just colors (no shader/texture[], has colors[])
+ - just texture (has shader/texture[], no colors[])
+ - colors * texture (has shader/texture[], has colors[])
+
+ Thus for texture drawing, we need both texture[] and a shader.
+ */
+
+ SkTriColorShader triShader; // must be above declaration of p
+ SkPaint p(paint);
+
+ SkShader* shader = p.getShader();
+ if (NULL == shader) {
+ // if we have no shader, we ignore the texture coordinates
+ textures = NULL;
+ } else if (NULL == textures) {
+ // if we don't have texture coordinates, ignore the shader
+ p.setShader(NULL);
+ shader = NULL;
+ }
+
+ // setup the custom shader (if needed)
+ if (NULL != colors) {
+ if (NULL == textures) {
+ // just colors (no texture)
+ shader = p.setShader(&triShader);
+ } else {
+ // colors * texture
+ SkASSERT(shader);
+ bool releaseMode = false;
+ if (NULL == xmode) {
+ xmode = SkXfermode::Create(SkXfermode::kModulate_Mode);
+ releaseMode = true;
+ }
+ SkShader* compose = SkNEW_ARGS(SkComposeShader,
+ (&triShader, shader, xmode));
+ p.setShader(compose)->unref();
+ 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 tempM;
+ SkMatrix savedLocalM;
+ if (shader) {
+ savedLocalM = shader->getLocalMatrix();
+ }
+
+ // setContext has already been called and verified to return true
+ // by the constructor of SkAutoBlitterChoose
+ bool prevContextSuccess = true;
+ while (vertProc(&state)) {
+ if (NULL != textures) {
+ if (texture_to_matrix(state, vertices, textures, &tempM)) {
+ tempM.postConcat(savedLocalM);
+ shader->setLocalMatrix(tempM);
+ // Need to recall setContext since we changed the local matrix.
+ // However, we also need to balance the calls this with a
+ // call to endContext which requires tracking the result of
+ // the previous call to setContext.
+ if (prevContextSuccess) {
+ shader->endContext();
+ }
+ prevContextSuccess = shader->setContext(*fBitmap, p, *fMatrix);
+ if (!prevContextSuccess) {
+ continue;
+ }
+ }
+ }
+ if (NULL != colors) {
+ if (!triShader.setup(vertices, colors,
+ state.f0, state.f1, state.f2)) {
+ continue;
+ }
+ }
+
+ SkPoint tmp[] = {
+ devVerts[state.f0], devVerts[state.f1], devVerts[state.f2]
+ };
+ SkScan::FillTriangle(tmp, *fRC, blitter.get());
+ }
+
+ // now restore the shader's original local matrix
+ if (NULL != shader) {
+ shader->setLocalMatrix(savedLocalM);
+ }
+
+ // If the final call to setContext fails we must make it suceed so that the
+ // call to endContext in the destructor for SkAutoBlitterChoose is balanced.
+ if (!prevContextSuccess) {
+ prevContextSuccess = shader->setContext(*fBitmap, paint, SkMatrix::I());
+ SkASSERT(prevContextSuccess);
+ }
+ } else {
+ // no colors[] and no texture
+ HairProc hairProc = ChooseHairProc(paint.isAntiAlias());
+ const SkRasterClip& clip = *fRC;
+ while (vertProc(&state)) {
+ hairProc(devVerts[state.f0], devVerts[state.f1], clip, blitter.get());
+ hairProc(devVerts[state.f1], devVerts[state.f2], clip, blitter.get());
+ hairProc(devVerts[state.f2], devVerts[state.f0], clip, blitter.get());
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkDraw::validate() const {
+ SkASSERT(fBitmap != NULL);
+ SkASSERT(fMatrix != NULL);
+ SkASSERT(fClip != NULL);
+ SkASSERT(fRC != NULL);
+
+ const SkIRect& cr = fRC->getBounds();
+ SkIRect br;
+
+ br.set(0, 0, fBitmap->width(), fBitmap->height());
+ SkASSERT(cr.isEmpty() || br.contains(cr));
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBounder::SkBounder() {
+ // initialize up front. This gets reset by SkCanvas before each draw call.
+ fClip = &SkRegion::GetEmptyRegion();
+}
+
+bool SkBounder::doIRect(const SkIRect& r) {
+ SkIRect rr;
+ return rr.intersect(fClip->getBounds(), r) && this->onIRect(rr);
+}
+
+// TODO: change the prototype to take fixed, and update the callers
+bool SkBounder::doIRectGlyph(const SkIRect& r, int x, int y,
+ const SkGlyph& glyph) {
+ SkIRect rr;
+ if (!rr.intersect(fClip->getBounds(), r)) {
+ return false;
+ }
+ GlyphRec rec;
+ rec.fLSB.set(SkIntToFixed(x), SkIntToFixed(y));
+ rec.fRSB.set(rec.fLSB.fX + glyph.fAdvanceX,
+ rec.fLSB.fY + glyph.fAdvanceY);
+ rec.fGlyphID = glyph.getGlyphID();
+ rec.fFlags = 0;
+ return this->onIRectGlyph(rr, rec);
+}
+
+bool SkBounder::doHairline(const SkPoint& pt0, const SkPoint& pt1,
+ const SkPaint& paint) {
+ SkIRect r;
+ SkScalar v0, v1;
+
+ v0 = pt0.fX;
+ v1 = pt1.fX;
+ if (v0 > v1) {
+ SkTSwap<SkScalar>(v0, v1);
+ }
+ r.fLeft = SkScalarFloor(v0);
+ r.fRight = SkScalarCeil(v1);
+
+ v0 = pt0.fY;
+ v1 = pt1.fY;
+ if (v0 > v1) {
+ SkTSwap<SkScalar>(v0, v1);
+ }
+ r.fTop = SkScalarFloor(v0);
+ r.fBottom = SkScalarCeil(v1);
+
+ if (paint.isAntiAlias()) {
+ r.inset(-1, -1);
+ }
+ return this->doIRect(r);
+}
+
+bool SkBounder::doRect(const SkRect& rect, const SkPaint& paint) {
+ SkIRect r;
+
+ if (paint.getStyle() == SkPaint::kFill_Style) {
+ rect.round(&r);
+ } else {
+ int rad = -1;
+ rect.roundOut(&r);
+ if (paint.isAntiAlias()) {
+ rad = -2;
+ }
+ r.inset(rad, rad);
+ }
+ return this->doIRect(r);
+}
+
+bool SkBounder::doPath(const SkPath& path, const SkPaint& paint, bool doFill) {
+ SkIRect r;
+ const SkRect& bounds = path.getBounds();
+
+ if (doFill) {
+ bounds.round(&r);
+ } else { // hairline
+ bounds.roundOut(&r);
+ }
+
+ if (paint.isAntiAlias()) {
+ r.inset(-1, -1);
+ }
+ return this->doIRect(r);
+}
+
+void SkBounder::commit() {
+ // override in subclass
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "SkPath.h"
+#include "SkDraw.h"
+#include "SkRegion.h"
+#include "SkBlitter.h"
+
+static bool compute_bounds(const SkPath& devPath, const SkIRect* clipBounds,
+ const SkMaskFilter* filter, const SkMatrix* filterMatrix,
+ SkIRect* bounds) {
+ if (devPath.isEmpty()) {
+ return false;
+ }
+
+ // init our bounds from the path
+ {
+ SkRect pathBounds = devPath.getBounds();
+ pathBounds.inset(-SK_ScalarHalf, -SK_ScalarHalf);
+ pathBounds.roundOut(bounds);
+ }
+
+ SkIPoint margin = SkIPoint::Make(0, 0);
+ if (filter) {
+ SkASSERT(filterMatrix);
+
+ SkMask srcM, dstM;
+
+ srcM.fBounds = *bounds;
+ srcM.fFormat = SkMask::kA8_Format;
+ srcM.fImage = NULL;
+ if (!filter->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
+ return false;
+ }
+ }
+
+ // (possibly) trim the bounds to reflect the clip
+ // (plus whatever slop the filter needs)
+ if (clipBounds) {
+ SkIRect tmp = *clipBounds;
+ // Ugh. Guard against gigantic margins from wacky filters. Without this
+ // check we can request arbitrary amounts of slop beyond our visible
+ // clip, and bring down the renderer (at least on finite RAM machines
+ // like handsets, etc.). Need to balance this invented value between
+ // quality of large filters like blurs, and the corresponding memory
+ // requests.
+ static const int MAX_MARGIN = 128;
+ tmp.inset(-SkMin32(margin.fX, MAX_MARGIN),
+ -SkMin32(margin.fY, MAX_MARGIN));
+ if (!bounds->intersect(tmp)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void draw_into_mask(const SkMask& mask, const SkPath& devPath,
+ SkPaint::Style style) {
+ SkBitmap bm;
+ SkDraw draw;
+ SkRasterClip clip;
+ SkMatrix matrix;
+ SkPaint paint;
+
+ bm.setConfig(SkBitmap::kA8_Config, mask.fBounds.width(), mask.fBounds.height(), mask.fRowBytes);
+ bm.setPixels(mask.fImage);
+
+ clip.setRect(SkIRect::MakeWH(mask.fBounds.width(), mask.fBounds.height()));
+ matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
+ -SkIntToScalar(mask.fBounds.fTop));
+
+ draw.fBitmap = &bm;
+ draw.fRC = &clip;
+ draw.fClip = &clip.bwRgn();
+ draw.fMatrix = &matrix;
+ draw.fBounder = NULL;
+ paint.setAntiAlias(true);
+ paint.setStyle(style);
+ draw.drawPath(devPath, paint);
+}
+
+bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
+ const SkMaskFilter* filter, const SkMatrix* filterMatrix,
+ SkMask* mask, SkMask::CreateMode mode,
+ SkPaint::Style style) {
+ if (SkMask::kJustRenderImage_CreateMode != mode) {
+ if (!compute_bounds(devPath, clipBounds, filter, filterMatrix, &mask->fBounds))
+ return false;
+ }
+
+ if (SkMask::kComputeBoundsAndRenderImage_CreateMode == mode) {
+ mask->fFormat = SkMask::kA8_Format;
+ mask->fRowBytes = mask->fBounds.width();
+ size_t size = mask->computeImageSize();
+ if (0 == size) {
+ // we're too big to allocate the mask, abort
+ return false;
+ }
+ mask->fImage = SkMask::AllocImage(size);
+ memset(mask->fImage, 0, mask->computeImageSize());
+ }
+
+ if (SkMask::kJustComputeBounds_CreateMode != mode) {
+ draw_into_mask(*mask, devPath, style);
+ }
+
+ return true;
+}
+
diff --git a/core/SkDrawLooper.cpp b/core/SkDrawLooper.cpp
new file mode 100644
index 00000000..0277986a
--- /dev/null
+++ b/core/SkDrawLooper.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDrawLooper.h"
+#include "SkCanvas.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkRect.h"
+
+SK_DEFINE_INST_COUNT(SkDrawLooper)
+
+bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
+ SkCanvas canvas;
+
+ this->init(&canvas);
+ for (;;) {
+ SkPaint p(paint);
+ if (this->next(&canvas, &p)) {
+ p.setLooper(NULL);
+ if (!p.canComputeFastBounds()) {
+ return false;
+ }
+ } else {
+ break;
+ }
+ }
+ return true;
+}
+
+void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& src,
+ SkRect* dst) {
+ SkCanvas canvas;
+
+ *dst = src; // catch case where there are no loops
+ this->init(&canvas);
+ for (bool firstTime = true;; firstTime = false) {
+ SkPaint p(paint);
+ if (this->next(&canvas, &p)) {
+ SkRect r(src);
+
+ p.setLooper(NULL);
+ p.computeFastBounds(r, &r);
+ canvas.getTotalMatrix().mapRect(&r);
+
+ if (firstTime) {
+ *dst = r;
+ } else {
+ dst->join(r);
+ }
+ } else {
+ break;
+ }
+ }
+}
diff --git a/core/SkDrawProcs.h b/core/SkDrawProcs.h
new file mode 100644
index 00000000..8b8c3826
--- /dev/null
+++ b/core/SkDrawProcs.h
@@ -0,0 +1,70 @@
+
+/*
+ * 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 SkDrawProcs_DEFINED
+#define SkDrawProcs_DEFINED
+
+#include "SkBlitter.h"
+#include "SkDraw.h"
+
+class SkAAClip;
+class SkBlitter;
+
+struct SkDraw1Glyph {
+ const SkDraw* fDraw;
+ SkBounder* fBounder;
+ const SkRegion* fClip;
+ const SkAAClip* fAAClip;
+ SkBlitter* fBlitter;
+ SkGlyphCache* fCache;
+ const SkPaint* fPaint;
+ SkIRect fClipBounds;
+ /** Half the sampling frequency of the rasterized glyph in x. */
+ SkFixed fHalfSampleX;
+ /** Half the sampling frequency of the rasterized glyph in y. */
+ SkFixed fHalfSampleY;
+
+ /** Draws one glyph.
+ *
+ * The x and y are pre-biased, so implementations may just truncate them.
+ * i.e. half the sampling frequency has been added.
+ * e.g. 1/2 or 1/(2^(SkGlyph::kSubBits+1)) has already been added.
+ * This added bias can be found in fHalfSampleX,Y.
+ */
+ typedef void (*Proc)(const SkDraw1Glyph&, SkFixed x, SkFixed y, const SkGlyph&);
+
+ Proc init(const SkDraw* draw, SkBlitter* blitter, SkGlyphCache* cache,
+ const SkPaint&);
+
+ // call this instead of fBlitter->blitMask() since this wrapper will handle
+ // the case when the mask is ARGB32_Format
+ //
+ void blitMask(const SkMask& mask, const SkIRect& clip) const {
+ if (SkMask::kARGB32_Format == mask.fFormat) {
+ this->blitMaskAsSprite(mask);
+ } else {
+ fBlitter->blitMask(mask, clip);
+ }
+ }
+
+ // mask must be kARGB32_Format
+ void blitMaskAsSprite(const SkMask& mask) const;
+};
+
+struct SkDrawProcs {
+ SkDraw1Glyph::Proc fD1GProc;
+};
+
+/**
+ * If the current paint is set to stroke and the stroke-width when applied to
+ * the matrix is <= 1.0, then this returns true, and sets coverage (simulating
+ * a stroke by drawing a hairline with partial coverage). If any of these
+ * conditions are false, then this returns false and coverage is ignored.
+ */
+bool SkDrawTreatAsHairline(const SkPaint&, const SkMatrix&, SkScalar* coverage);
+
+#endif
diff --git a/core/SkEdge.cpp b/core/SkEdge.cpp
new file mode 100644
index 00000000..8904ca72
--- /dev/null
+++ b/core/SkEdge.cpp
@@ -0,0 +1,497 @@
+
+/*
+ * 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 "SkEdge.h"
+#include "SkFDot6.h"
+#include "SkMath.h"
+
+/*
+ In setLine, setQuadratic, setCubic, the first thing we do is to convert
+ the points into FDot6. This is modulated by the shift parameter, which
+ will either be 0, or something like 2 for antialiasing.
+
+ In the float case, we want to turn the float into .6 by saying pt * 64,
+ or pt * 256 for antialiasing. This is implemented as 1 << (shift + 6).
+
+ In the fixed case, we want to turn the fixed into .6 by saying pt >> 10,
+ or pt >> 8 for antialiasing. This is implemented as pt >> (10 - shift).
+*/
+
+static inline SkFixed SkFDot6ToFixedDiv2(SkFDot6 value) {
+ // we want to return SkFDot6ToFixed(value >> 1), but we don't want to throw
+ // away data in value, so just perform a modify up-shift
+ return value << (16 - 6 - 1);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip,
+ int shift) {
+ SkFDot6 x0, y0, x1, y1;
+
+ {
+#ifdef SK_SCALAR_IS_FLOAT
+ float scale = float(1 << (shift + 6));
+ x0 = int(p0.fX * scale);
+ y0 = int(p0.fY * scale);
+ x1 = int(p1.fX * scale);
+ y1 = int(p1.fY * scale);
+#else
+ shift = 10 - shift;
+ x0 = p0.fX >> shift;
+ y0 = p0.fY >> shift;
+ x1 = p1.fX >> shift;
+ y1 = p1.fY >> shift;
+#endif
+ }
+
+ int winding = 1;
+
+ if (y0 > y1) {
+ SkTSwap(x0, x1);
+ SkTSwap(y0, y1);
+ winding = -1;
+ }
+
+ int top = SkFDot6Round(y0);
+ int bot = SkFDot6Round(y1);
+
+ // are we a zero-height line?
+ if (top == bot) {
+ return 0;
+ }
+ // are we completely above or below the clip?
+ if (NULL != clip && (top >= clip->fBottom || bot <= clip->fTop)) {
+ return 0;
+ }
+
+ SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
+
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
+ fDX = slope;
+ fFirstY = top;
+ fLastY = bot - 1;
+ fCurveCount = 0;
+ fWinding = SkToS8(winding);
+ fCurveShift = 0;
+
+ if (clip) {
+ this->chopLineWithClip(*clip);
+ }
+ return 1;
+}
+
+// called from a curve subclass
+int SkEdge::updateLine(SkFixed x0, SkFixed y0, SkFixed x1, SkFixed y1)
+{
+ SkASSERT(fWinding == 1 || fWinding == -1);
+ SkASSERT(fCurveCount != 0);
+// SkASSERT(fCurveShift != 0);
+
+ y0 >>= 10;
+ y1 >>= 10;
+
+ SkASSERT(y0 <= y1);
+
+ int top = SkFDot6Round(y0);
+ int bot = SkFDot6Round(y1);
+
+// SkASSERT(top >= fFirstY);
+
+ // are we a zero-height line?
+ if (top == bot)
+ return 0;
+
+ x0 >>= 10;
+ x1 >>= 10;
+
+ SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
+
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
+ fDX = slope;
+ fFirstY = top;
+ fLastY = bot - 1;
+
+ return 1;
+}
+
+void SkEdge::chopLineWithClip(const SkIRect& clip)
+{
+ int top = fFirstY;
+
+ SkASSERT(top < clip.fBottom);
+
+ // clip the line to the top
+ if (top < clip.fTop)
+ {
+ SkASSERT(fLastY >= clip.fTop);
+ fX += fDX * (clip.fTop - top);
+ fFirstY = clip.fTop;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* We store 1<<shift in a (signed) byte, so its maximum value is 1<<6 == 64.
+ Note that this limits the number of lines we use to approximate a curve.
+ If we need to increase this, we need to store fCurveCount in something
+ larger than int8_t.
+*/
+#define MAX_COEFF_SHIFT 6
+
+static inline SkFDot6 cheap_distance(SkFDot6 dx, SkFDot6 dy)
+{
+ dx = SkAbs32(dx);
+ dy = SkAbs32(dy);
+ // return max + min/2
+ if (dx > dy)
+ dx += dy >> 1;
+ else
+ dx = dy + (dx >> 1);
+ return dx;
+}
+
+static inline int diff_to_shift(SkFDot6 dx, SkFDot6 dy)
+{
+ // cheap calc of distance from center of p0-p2 to the center of the curve
+ SkFDot6 dist = cheap_distance(dx, dy);
+
+ // shift down dist (it is currently in dot6)
+ // down by 5 should give us 1/2 pixel accuracy (assuming our dist is accurate...)
+ // this is chosen by heuristic: make it as big as possible (to minimize segments)
+ // ... but small enough so that our curves still look smooth
+ dist = (dist + (1 << 4)) >> 5;
+
+ // each subdivision (shift value) cuts this dist (error) by 1/4
+ return (32 - SkCLZ(dist)) >> 1;
+}
+
+int SkQuadraticEdge::setQuadratic(const SkPoint pts[3], int shift)
+{
+ SkFDot6 x0, y0, x1, y1, x2, y2;
+
+ {
+#ifdef SK_SCALAR_IS_FLOAT
+ float scale = float(1 << (shift + 6));
+ x0 = int(pts[0].fX * scale);
+ y0 = int(pts[0].fY * scale);
+ x1 = int(pts[1].fX * scale);
+ y1 = int(pts[1].fY * scale);
+ x2 = int(pts[2].fX * scale);
+ y2 = int(pts[2].fY * scale);
+#else
+ shift = 10 - shift;
+ x0 = pts[0].fX >> shift;
+ y0 = pts[0].fY >> shift;
+ x1 = pts[1].fX >> shift;
+ y1 = pts[1].fY >> shift;
+ x2 = pts[2].fX >> shift;
+ y2 = pts[2].fY >> shift;
+#endif
+ }
+
+ int winding = 1;
+ if (y0 > y2)
+ {
+ SkTSwap(x0, x2);
+ SkTSwap(y0, y2);
+ winding = -1;
+ }
+ SkASSERT(y0 <= y1 && y1 <= y2);
+
+ int top = SkFDot6Round(y0);
+ int bot = SkFDot6Round(y2);
+
+ // are we a zero-height quad (line)?
+ if (top == bot)
+ return 0;
+
+ // compute number of steps needed (1 << shift)
+ {
+ SkFDot6 dx = ((x1 << 1) - x0 - x2) >> 2;
+ SkFDot6 dy = ((y1 << 1) - y0 - y2) >> 2;
+ shift = diff_to_shift(dx, dy);
+ SkASSERT(shift >= 0);
+ }
+ // need at least 1 subdivision for our bias trick
+ if (shift == 0) {
+ shift = 1;
+ } else if (shift > MAX_COEFF_SHIFT) {
+ shift = MAX_COEFF_SHIFT;
+ }
+
+ fWinding = SkToS8(winding);
+ //fCubicDShift only set for cubics
+ fCurveCount = SkToS8(1 << shift);
+
+ /*
+ * We want to reformulate into polynomial form, to make it clear how we
+ * should forward-difference.
+ *
+ * p0 (1 - t)^2 + p1 t(1 - t) + p2 t^2 ==> At^2 + Bt + C
+ *
+ * A = p0 - 2p1 + p2
+ * B = 2(p1 - p0)
+ * C = p0
+ *
+ * Our caller must have constrained our inputs (p0..p2) to all fit into
+ * 16.16. However, as seen above, we sometimes compute values that can be
+ * larger (e.g. B = 2*(p1 - p0)). To guard against overflow, we will store
+ * A and B at 1/2 of their actual value, and just apply a 2x scale during
+ * application in updateQuadratic(). Hence we store (shift - 1) in
+ * fCurveShift.
+ */
+
+ fCurveShift = SkToU8(shift - 1);
+
+ SkFixed A = SkFDot6ToFixedDiv2(x0 - x1 - x1 + x2); // 1/2 the real value
+ SkFixed B = SkFDot6ToFixed(x1 - x0); // 1/2 the real value
+
+ fQx = SkFDot6ToFixed(x0);
+ fQDx = B + (A >> shift); // biased by shift
+ fQDDx = A >> (shift - 1); // biased by shift
+
+ A = SkFDot6ToFixedDiv2(y0 - y1 - y1 + y2); // 1/2 the real value
+ B = SkFDot6ToFixed(y1 - y0); // 1/2 the real value
+
+ fQy = SkFDot6ToFixed(y0);
+ fQDy = B + (A >> shift); // biased by shift
+ fQDDy = A >> (shift - 1); // biased by shift
+
+ fQLastX = SkFDot6ToFixed(x2);
+ fQLastY = SkFDot6ToFixed(y2);
+
+ return this->updateQuadratic();
+}
+
+int SkQuadraticEdge::updateQuadratic()
+{
+ int success;
+ int count = fCurveCount;
+ SkFixed oldx = fQx;
+ SkFixed oldy = fQy;
+ SkFixed dx = fQDx;
+ SkFixed dy = fQDy;
+ SkFixed newx, newy;
+ int shift = fCurveShift;
+
+ SkASSERT(count > 0);
+
+ do {
+ if (--count > 0)
+ {
+ newx = oldx + (dx >> shift);
+ dx += fQDDx;
+ newy = oldy + (dy >> shift);
+ dy += fQDDy;
+ }
+ else // last segment
+ {
+ newx = fQLastX;
+ newy = fQLastY;
+ }
+ success = this->updateLine(oldx, oldy, newx, newy);
+ oldx = newx;
+ oldy = newy;
+ } while (count > 0 && !success);
+
+ fQx = newx;
+ fQy = newy;
+ fQDx = dx;
+ fQDy = dy;
+ fCurveCount = SkToS8(count);
+ return success;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+static inline int SkFDot6UpShift(SkFDot6 x, int upShift) {
+ SkASSERT((x << upShift >> upShift) == x);
+ return x << upShift;
+}
+
+/* f(1/3) = (8a + 12b + 6c + d) / 27
+ f(2/3) = (a + 6b + 12c + 8d) / 27
+
+ f(1/3)-b = (8a - 15b + 6c + d) / 27
+ f(2/3)-c = (a + 6b - 15c + 8d) / 27
+
+ use 16/512 to approximate 1/27
+*/
+static SkFDot6 cubic_delta_from_line(SkFDot6 a, SkFDot6 b, SkFDot6 c, SkFDot6 d)
+{
+ SkFDot6 oneThird = ((a << 3) - ((b << 4) - b) + 6*c + d) * 19 >> 9;
+ SkFDot6 twoThird = (a + 6*b - ((c << 4) - c) + (d << 3)) * 19 >> 9;
+
+ return SkMax32(SkAbs32(oneThird), SkAbs32(twoThird));
+}
+
+int SkCubicEdge::setCubic(const SkPoint pts[4], const SkIRect* clip, int shift)
+{
+ SkFDot6 x0, y0, x1, y1, x2, y2, x3, y3;
+
+ {
+#ifdef SK_SCALAR_IS_FLOAT
+ float scale = float(1 << (shift + 6));
+ x0 = int(pts[0].fX * scale);
+ y0 = int(pts[0].fY * scale);
+ x1 = int(pts[1].fX * scale);
+ y1 = int(pts[1].fY * scale);
+ x2 = int(pts[2].fX * scale);
+ y2 = int(pts[2].fY * scale);
+ x3 = int(pts[3].fX * scale);
+ y3 = int(pts[3].fY * scale);
+#else
+ shift = 10 - shift;
+ x0 = pts[0].fX >> shift;
+ y0 = pts[0].fY >> shift;
+ x1 = pts[1].fX >> shift;
+ y1 = pts[1].fY >> shift;
+ x2 = pts[2].fX >> shift;
+ y2 = pts[2].fY >> shift;
+ x3 = pts[3].fX >> shift;
+ y3 = pts[3].fY >> shift;
+#endif
+ }
+
+ int winding = 1;
+ if (y0 > y3)
+ {
+ SkTSwap(x0, x3);
+ SkTSwap(x1, x2);
+ SkTSwap(y0, y3);
+ SkTSwap(y1, y2);
+ winding = -1;
+ }
+
+ int top = SkFDot6Round(y0);
+ int bot = SkFDot6Round(y3);
+
+ // are we a zero-height cubic (line)?
+ if (top == bot)
+ return 0;
+
+ // are we completely above or below the clip?
+ if (clip && (top >= clip->fBottom || bot <= clip->fTop))
+ return 0;
+
+ // compute number of steps needed (1 << shift)
+ {
+ // Can't use (center of curve - center of baseline), since center-of-curve
+ // need not be the max delta from the baseline (it could even be coincident)
+ // so we try just looking at the two off-curve points
+ SkFDot6 dx = cubic_delta_from_line(x0, x1, x2, x3);
+ SkFDot6 dy = cubic_delta_from_line(y0, y1, y2, y3);
+ // add 1 (by observation)
+ shift = diff_to_shift(dx, dy) + 1;
+ }
+ // need at least 1 subdivision for our bias trick
+ SkASSERT(shift > 0);
+ if (shift > MAX_COEFF_SHIFT) {
+ shift = MAX_COEFF_SHIFT;
+ }
+
+ /* Since our in coming data is initially shifted down by 10 (or 8 in
+ antialias). That means the most we can shift up is 8. However, we
+ compute coefficients with a 3*, so the safest upshift is really 6
+ */
+ int upShift = 6; // largest safe value
+ int downShift = shift + upShift - 10;
+ if (downShift < 0) {
+ downShift = 0;
+ upShift = 10 - shift;
+ }
+
+ fWinding = SkToS8(winding);
+ fCurveCount = SkToS8(-1 << shift);
+ fCurveShift = SkToU8(shift);
+ fCubicDShift = SkToU8(downShift);
+
+ SkFixed B = SkFDot6UpShift(3 * (x1 - x0), upShift);
+ SkFixed C = SkFDot6UpShift(3 * (x0 - x1 - x1 + x2), upShift);
+ SkFixed D = SkFDot6UpShift(x3 + 3 * (x1 - x2) - x0, upShift);
+
+ fCx = SkFDot6ToFixed(x0);
+ fCDx = B + (C >> shift) + (D >> 2*shift); // biased by shift
+ fCDDx = 2*C + (3*D >> (shift - 1)); // biased by 2*shift
+ fCDDDx = 3*D >> (shift - 1); // biased by 2*shift
+
+ B = SkFDot6UpShift(3 * (y1 - y0), upShift);
+ C = SkFDot6UpShift(3 * (y0 - y1 - y1 + y2), upShift);
+ D = SkFDot6UpShift(y3 + 3 * (y1 - y2) - y0, upShift);
+
+ fCy = SkFDot6ToFixed(y0);
+ fCDy = B + (C >> shift) + (D >> 2*shift); // biased by shift
+ fCDDy = 2*C + (3*D >> (shift - 1)); // biased by 2*shift
+ fCDDDy = 3*D >> (shift - 1); // biased by 2*shift
+
+ fCLastX = SkFDot6ToFixed(x3);
+ fCLastY = SkFDot6ToFixed(y3);
+
+ if (clip)
+ {
+ do {
+ if (!this->updateCubic()) {
+ return 0;
+ }
+ } while (!this->intersectsClip(*clip));
+ this->chopLineWithClip(*clip);
+ return 1;
+ }
+ return this->updateCubic();
+}
+
+int SkCubicEdge::updateCubic()
+{
+ int success;
+ int count = fCurveCount;
+ SkFixed oldx = fCx;
+ SkFixed oldy = fCy;
+ SkFixed newx, newy;
+ const int ddshift = fCurveShift;
+ const int dshift = fCubicDShift;
+
+ SkASSERT(count < 0);
+
+ do {
+ if (++count < 0)
+ {
+ newx = oldx + (fCDx >> dshift);
+ fCDx += fCDDx >> ddshift;
+ fCDDx += fCDDDx;
+
+ newy = oldy + (fCDy >> dshift);
+ fCDy += fCDDy >> ddshift;
+ fCDDy += fCDDDy;
+ }
+ else // last segment
+ {
+ // SkDebugf("LastX err=%d, LastY err=%d\n", (oldx + (fCDx >> shift) - fLastX), (oldy + (fCDy >> shift) - fLastY));
+ newx = fCLastX;
+ newy = fCLastY;
+ }
+
+ // we want to say SkASSERT(oldy <= newy), but our finite fixedpoint
+ // doesn't always achieve that, so we have to explicitly pin it here.
+ if (newy < oldy) {
+ newy = oldy;
+ }
+
+ success = this->updateLine(oldx, oldy, newx, newy);
+ oldx = newx;
+ oldy = newy;
+ } while (count < 0 && !success);
+
+ fCx = newx;
+ fCy = newy;
+ fCurveCount = SkToS8(count);
+ return success;
+}
diff --git a/core/SkEdge.h b/core/SkEdge.h
new file mode 100644
index 00000000..ea7c84f7
--- /dev/null
+++ b/core/SkEdge.h
@@ -0,0 +1,137 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkEdge_DEFINED
+#define SkEdge_DEFINED
+
+#include "SkRect.h"
+#include "SkFDot6.h"
+#include "SkMath.h"
+
+// This correctly favors the lower-pixel when y0 is on a 1/2 pixel boundary
+#define SkEdge_Compute_DY(top, y0) ((top << 6) + 32 - (y0))
+
+struct SkEdge {
+ enum Type {
+ kLine_Type,
+ kQuad_Type,
+ kCubic_Type
+ };
+
+ SkEdge* fNext;
+ SkEdge* fPrev;
+
+ SkFixed fX;
+ SkFixed fDX;
+ int32_t fFirstY;
+ int32_t fLastY;
+ int8_t fCurveCount; // only used by kQuad(+) and kCubic(-)
+ uint8_t fCurveShift; // appled to all Dx/DDx/DDDx except for fCubicDShift exception
+ uint8_t fCubicDShift; // applied to fCDx and fCDy only in cubic
+ int8_t fWinding; // 1 or -1
+
+ int setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip,
+ int shiftUp);
+ // call this version if you know you don't have a clip
+ inline int setLine(const SkPoint& p0, const SkPoint& p1, int shiftUp);
+ inline int updateLine(SkFixed ax, SkFixed ay, SkFixed bx, SkFixed by);
+ void chopLineWithClip(const SkIRect& clip);
+
+ inline bool intersectsClip(const SkIRect& clip) const {
+ SkASSERT(fFirstY < clip.fBottom);
+ return fLastY >= clip.fTop;
+ }
+
+#ifdef SK_DEBUG
+ void dump() const {
+ SkDebugf("edge: firstY:%d lastY:%d x:%g dx:%g w:%d\n", fFirstY, fLastY, SkFixedToFloat(fX), SkFixedToFloat(fDX), fWinding);
+ }
+
+ void validate() const {
+ SkASSERT(fPrev && fNext);
+ SkASSERT(fPrev->fNext == this);
+ SkASSERT(fNext->fPrev == this);
+
+ SkASSERT(fFirstY <= fLastY);
+ SkASSERT(SkAbs32(fWinding) == 1);
+ }
+#endif
+};
+
+struct SkQuadraticEdge : public SkEdge {
+ SkFixed fQx, fQy;
+ SkFixed fQDx, fQDy;
+ SkFixed fQDDx, fQDDy;
+ SkFixed fQLastX, fQLastY;
+
+ int setQuadratic(const SkPoint pts[3], int shiftUp);
+ int updateQuadratic();
+};
+
+struct SkCubicEdge : public SkEdge {
+ SkFixed fCx, fCy;
+ SkFixed fCDx, fCDy;
+ SkFixed fCDDx, fCDDy;
+ SkFixed fCDDDx, fCDDDy;
+ SkFixed fCLastX, fCLastY;
+
+ int setCubic(const SkPoint pts[4], const SkIRect* clip, int shiftUp);
+ int updateCubic();
+};
+
+int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, int shift) {
+ SkFDot6 x0, y0, x1, y1;
+
+ {
+#ifdef SK_SCALAR_IS_FLOAT
+ float scale = float(1 << (shift + 6));
+ x0 = int(p0.fX * scale);
+ y0 = int(p0.fY * scale);
+ x1 = int(p1.fX * scale);
+ y1 = int(p1.fY * scale);
+#else
+ shift = 10 - shift;
+ x0 = p0.fX >> shift;
+ y0 = p0.fY >> shift;
+ x1 = p1.fX >> shift;
+ y1 = p1.fY >> shift;
+#endif
+ }
+
+ int winding = 1;
+
+ if (y0 > y1) {
+ SkTSwap(x0, x1);
+ SkTSwap(y0, y1);
+ winding = -1;
+ }
+
+ int top = SkFDot6Round(y0);
+ int bot = SkFDot6Round(y1);
+
+ // are we a zero-height line?
+ if (top == bot) {
+ return 0;
+ }
+
+ SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
+ const int dy = SkEdge_Compute_DY(top, y0);
+
+ fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy)); // + SK_Fixed1/2
+ fDX = slope;
+ fFirstY = top;
+ fLastY = bot - 1;
+ fCurveCount = 0;
+ fWinding = SkToS8(winding);
+ fCurveShift = 0;
+ return 1;
+}
+
+
+#endif
diff --git a/core/SkEdgeBuilder.cpp b/core/SkEdgeBuilder.cpp
new file mode 100644
index 00000000..799c6762
--- /dev/null
+++ b/core/SkEdgeBuilder.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 "SkEdgeBuilder.h"
+#include "SkPath.h"
+#include "SkEdge.h"
+#include "SkEdgeClipper.h"
+#include "SkLineClipper.h"
+#include "SkGeometry.h"
+
+template <typename T> static T* typedAllocThrow(SkChunkAlloc& alloc) {
+ return static_cast<T*>(alloc.allocThrow(sizeof(T)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkEdgeBuilder::SkEdgeBuilder() : fAlloc(16*1024) {
+ fEdgeList = NULL;
+}
+
+void SkEdgeBuilder::addLine(const SkPoint pts[]) {
+ SkEdge* edge = typedAllocThrow<SkEdge>(fAlloc);
+ if (edge->setLine(pts[0], pts[1], fShiftUp)) {
+ fList.push(edge);
+ } else {
+ // TODO: unallocate edge from storage...
+ }
+}
+
+void SkEdgeBuilder::addQuad(const SkPoint pts[]) {
+ SkQuadraticEdge* edge = typedAllocThrow<SkQuadraticEdge>(fAlloc);
+ if (edge->setQuadratic(pts, fShiftUp)) {
+ fList.push(edge);
+ } else {
+ // TODO: unallocate edge from storage...
+ }
+}
+
+void SkEdgeBuilder::addCubic(const SkPoint pts[]) {
+ SkCubicEdge* edge = typedAllocThrow<SkCubicEdge>(fAlloc);
+ if (edge->setCubic(pts, NULL, fShiftUp)) {
+ fList.push(edge);
+ } else {
+ // TODO: unallocate edge from storage...
+ }
+}
+
+void SkEdgeBuilder::addClipper(SkEdgeClipper* clipper) {
+ SkPoint pts[4];
+ SkPath::Verb verb;
+
+ while ((verb = clipper->next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ this->addLine(pts);
+ break;
+ case SkPath::kQuad_Verb:
+ this->addQuad(pts);
+ break;
+ case SkPath::kCubic_Verb:
+ this->addCubic(pts);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void setShiftedClip(SkRect* dst, const SkIRect& src, int shift) {
+ dst->set(SkIntToScalar(src.fLeft >> shift),
+ SkIntToScalar(src.fTop >> shift),
+ SkIntToScalar(src.fRight >> shift),
+ SkIntToScalar(src.fBottom >> shift));
+}
+
+int SkEdgeBuilder::buildPoly(const SkPath& path, const SkIRect* iclip,
+ int shiftUp) {
+ SkPath::Iter iter(path, true);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+
+ int maxEdgeCount = path.countPoints();
+ if (iclip) {
+ // clipping can turn 1 line into (up to) kMaxClippedLineSegments, since
+ // we turn portions that are clipped out on the left/right into vertical
+ // segments.
+ maxEdgeCount *= SkLineClipper::kMaxClippedLineSegments;
+ }
+ size_t maxEdgeSize = maxEdgeCount * sizeof(SkEdge);
+ size_t maxEdgePtrSize = maxEdgeCount * sizeof(SkEdge*);
+
+ // lets store the edges and their pointers in the same block
+ char* storage = (char*)fAlloc.allocThrow(maxEdgeSize + maxEdgePtrSize);
+ SkEdge* edge = reinterpret_cast<SkEdge*>(storage);
+ SkEdge** edgePtr = reinterpret_cast<SkEdge**>(storage + maxEdgeSize);
+ // Record the beginning of our pointers, so we can return them to the caller
+ fEdgeList = edgePtr;
+
+ if (iclip) {
+ SkRect clip;
+ setShiftedClip(&clip, *iclip, shiftUp);
+
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ case SkPath::kClose_Verb:
+ // we ignore these, and just get the whole segment from
+ // the corresponding line/quad/cubic verbs
+ break;
+ case SkPath::kLine_Verb: {
+ SkPoint lines[SkLineClipper::kMaxPoints];
+ int lineCount = SkLineClipper::ClipLine(pts, clip, lines);
+ SkASSERT(lineCount <= SkLineClipper::kMaxClippedLineSegments);
+ for (int i = 0; i < lineCount; i++) {
+ if (edge->setLine(lines[i], lines[i + 1], shiftUp)) {
+ *edgePtr++ = edge++;
+ }
+ }
+ break;
+ }
+ default:
+ SkDEBUGFAIL("unexpected verb");
+ break;
+ }
+ }
+ } else {
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ case SkPath::kClose_Verb:
+ // we ignore these, and just get the whole segment from
+ // the corresponding line/quad/cubic verbs
+ break;
+ case SkPath::kLine_Verb:
+ if (edge->setLine(pts[0], pts[1], shiftUp)) {
+ *edgePtr++ = edge++;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unexpected verb");
+ break;
+ }
+ }
+ }
+ SkASSERT((char*)edge <= (char*)fEdgeList);
+ SkASSERT(edgePtr - fEdgeList <= maxEdgeCount);
+ return edgePtr - fEdgeList;
+}
+
+static void handle_quad(SkEdgeBuilder* builder, const SkPoint pts[3]) {
+ SkPoint monoX[5];
+ int n = SkChopQuadAtYExtrema(pts, monoX);
+ for (int i = 0; i <= n; i++) {
+ builder->addQuad(&monoX[i * 2]);
+ }
+}
+
+int SkEdgeBuilder::build(const SkPath& path, const SkIRect* iclip,
+ int shiftUp) {
+ fAlloc.reset();
+ fList.reset();
+ fShiftUp = shiftUp;
+
+ SkScalar conicTol = SK_ScalarHalf * (1 << shiftUp);
+
+ if (SkPath::kLine_SegmentMask == path.getSegmentMasks()) {
+ return this->buildPoly(path, iclip, shiftUp);
+ }
+
+ SkPath::Iter iter(path, true);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+
+ if (iclip) {
+ SkRect clip;
+ setShiftedClip(&clip, *iclip, shiftUp);
+ SkEdgeClipper clipper;
+
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ case SkPath::kClose_Verb:
+ // we ignore these, and just get the whole segment from
+ // the corresponding line/quad/cubic verbs
+ break;
+ case SkPath::kLine_Verb: {
+ SkPoint lines[SkLineClipper::kMaxPoints];
+ int lineCount = SkLineClipper::ClipLine(pts, clip, lines);
+ for (int i = 0; i < lineCount; i++) {
+ this->addLine(&lines[i]);
+ }
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ if (clipper.clipQuad(pts, clip)) {
+ this->addClipper(&clipper);
+ }
+ break;
+ case SkPath::kConic_Verb: {
+ const int MAX_POW2 = 4;
+ const int MAX_QUADS = 1 << MAX_POW2;
+ const int MAX_QUAD_PTS = 1 + 2 * MAX_QUADS;
+ SkPoint storage[MAX_QUAD_PTS];
+
+ SkConic conic;
+ conic.set(pts, iter.conicWeight());
+ int pow2 = conic.computeQuadPOW2(conicTol);
+ pow2 = SkMin32(pow2, MAX_POW2);
+ int quadCount = conic.chopIntoQuadsPOW2(storage, pow2);
+ SkASSERT(quadCount <= MAX_QUADS);
+ for (int i = 0; i < quadCount; ++i) {
+ if (clipper.clipQuad(&storage[i * 2], clip)) {
+ this->addClipper(&clipper);
+ }
+ }
+ } break;
+ case SkPath::kCubic_Verb:
+ if (clipper.clipCubic(pts, clip)) {
+ this->addClipper(&clipper);
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unexpected verb");
+ break;
+ }
+ }
+ } else {
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ case SkPath::kClose_Verb:
+ // we ignore these, and just get the whole segment from
+ // the corresponding line/quad/cubic verbs
+ break;
+ case SkPath::kLine_Verb:
+ this->addLine(pts);
+ break;
+ case SkPath::kQuad_Verb: {
+ handle_quad(this, pts);
+ break;
+ }
+ case SkPath::kConic_Verb: {
+ const int MAX_POW2 = 4;
+ const int MAX_QUADS = 1 << MAX_POW2;
+ const int MAX_QUAD_PTS = 1 + 2 * MAX_QUADS;
+ SkPoint storage[MAX_QUAD_PTS];
+
+ SkConic conic;
+ conic.set(pts, iter.conicWeight());
+ int pow2 = conic.computeQuadPOW2(conicTol);
+ pow2 = SkMin32(pow2, MAX_POW2);
+ int quadCount = conic.chopIntoQuadsPOW2(storage, pow2);
+ SkASSERT(quadCount <= MAX_QUADS);
+ for (int i = 0; i < quadCount; ++i) {
+ handle_quad(this, &storage[i * 2]);
+ }
+ } break;
+ case SkPath::kCubic_Verb: {
+ SkPoint monoY[10];
+ int n = SkChopCubicAtYExtrema(pts, monoY);
+ for (int i = 0; i <= n; i++) {
+ this->addCubic(&monoY[i * 3]);
+ }
+ break;
+ }
+ default:
+ SkDEBUGFAIL("unexpected verb");
+ break;
+ }
+ }
+ }
+ fEdgeList = fList.begin();
+ return fList.count();
+}
diff --git a/core/SkEdgeBuilder.h b/core/SkEdgeBuilder.h
new file mode 100644
index 00000000..f9e5976e
--- /dev/null
+++ b/core/SkEdgeBuilder.h
@@ -0,0 +1,52 @@
+
+/*
+ * 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 SkEdgeBuilder_DEFINED
+#define SkEdgeBuilder_DEFINED
+
+#include "SkChunkAlloc.h"
+#include "SkRect.h"
+#include "SkTDArray.h"
+
+struct SkEdge;
+class SkEdgeClipper;
+class SkPath;
+
+class SkEdgeBuilder {
+public:
+ SkEdgeBuilder();
+
+ // returns the number of built edges. The array of those edge pointers
+ // is returned from edgeList().
+ int build(const SkPath& path, const SkIRect* clip, int shiftUp);
+
+ SkEdge** edgeList() { return fEdgeList; }
+
+private:
+ SkChunkAlloc fAlloc;
+ SkTDArray<SkEdge*> fList;
+
+ /*
+ * If we're in general mode, we allcoate the pointers in fList, and this
+ * will point at fList.begin(). If we're in polygon mode, fList will be
+ * empty, as we will have preallocated room for the pointers in fAlloc's
+ * block, and fEdgeList will point into that.
+ */
+ SkEdge** fEdgeList;
+
+ int fShiftUp;
+
+public:
+ void addLine(const SkPoint pts[]);
+ void addQuad(const SkPoint pts[]);
+ void addCubic(const SkPoint pts[]);
+ void addClipper(SkEdgeClipper*);
+
+ int buildPoly(const SkPath& path, const SkIRect* clip, int shiftUp);
+};
+
+#endif
diff --git a/core/SkEdgeClipper.cpp b/core/SkEdgeClipper.cpp
new file mode 100644
index 00000000..00a1dd24
--- /dev/null
+++ b/core/SkEdgeClipper.cpp
@@ -0,0 +1,531 @@
+
+/*
+ * Copyright 2009 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 "SkEdgeClipper.h"
+#include "SkGeometry.h"
+
+static bool quick_reject(const SkRect& bounds, const SkRect& clip) {
+ return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop;
+}
+
+static inline void clamp_le(SkScalar& value, SkScalar max) {
+ if (value > max) {
+ value = max;
+ }
+}
+
+static inline void clamp_ge(SkScalar& value, SkScalar min) {
+ if (value < min) {
+ value = min;
+ }
+}
+
+/* src[] must be monotonic in Y. This routine copies src into dst, and sorts
+ it to be increasing in Y. If it had to reverse the order of the points,
+ it returns true, otherwise it returns false
+ */
+static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) {
+ // we need the data to be monotonically increasing in Y
+ if (src[0].fY > src[count - 1].fY) {
+ for (int i = 0; i < count; i++) {
+ dst[i] = src[count - i - 1];
+ }
+ return true;
+ } else {
+ memcpy(dst, src, count * sizeof(SkPoint));
+ return false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
+ SkScalar target, SkScalar* t) {
+ /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
+ * We solve for t, using quadratic equation, hence we have to rearrange
+ * our cooefficents to look like At^2 + Bt + C
+ */
+ SkScalar A = c0 - c1 - c1 + c2;
+ SkScalar B = 2*(c1 - c0);
+ SkScalar C = c0 - target;
+
+ SkScalar roots[2]; // we only expect one, but make room for 2 for safety
+ int count = SkFindUnitQuadRoots(A, B, C, roots);
+ if (count) {
+ *t = roots[0];
+ return true;
+ }
+ return false;
+}
+
+static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
+ return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
+}
+
+static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
+ return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
+}
+
+// Modify pts[] in place so that it is clipped in Y to the clip rect
+static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
+ SkScalar t;
+ SkPoint tmp[5]; // for SkChopQuadAt
+
+ // are we partially above
+ if (pts[0].fY < clip.fTop) {
+ if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
+ // take the 2nd chopped quad
+ SkChopQuadAt(pts, tmp, t);
+ // clamp to clean up imprecise numerics in the chop
+ tmp[2].fY = clip.fTop;
+ clamp_ge(tmp[3].fY, clip.fTop);
+
+ pts[0] = tmp[2];
+ pts[1] = tmp[3];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the top
+ for (int i = 0; i < 3; i++) {
+ if (pts[i].fY < clip.fTop) {
+ pts[i].fY = clip.fTop;
+ }
+ }
+ }
+ }
+
+ // are we partially below
+ if (pts[2].fY > clip.fBottom) {
+ if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ // clamp to clean up imprecise numerics in the chop
+ clamp_le(tmp[1].fY, clip.fBottom);
+ tmp[2].fY = clip.fBottom;
+
+ pts[1] = tmp[1];
+ pts[2] = tmp[2];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the bottom
+ for (int i = 0; i < 3; i++) {
+ if (pts[i].fY > clip.fBottom) {
+ pts[i].fY = clip.fBottom;
+ }
+ }
+ }
+ }
+}
+
+// srcPts[] must be monotonic in X and Y
+void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
+ SkPoint pts[3];
+ bool reverse = sort_increasing_Y(pts, srcPts, 3);
+
+ // are we completely above or below
+ if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
+ return;
+ }
+
+ // Now chop so that pts is contained within clip in Y
+ chop_quad_in_Y(pts, clip);
+
+ if (pts[0].fX > pts[2].fX) {
+ SkTSwap<SkPoint>(pts[0], pts[2]);
+ reverse = !reverse;
+ }
+ SkASSERT(pts[0].fX <= pts[1].fX);
+ SkASSERT(pts[1].fX <= pts[2].fX);
+
+ // Now chop in X has needed, and record the segments
+
+ if (pts[2].fX <= clip.fLeft) { // wholly to the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
+ return;
+ }
+ if (pts[0].fX >= clip.fRight) { // wholly to the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
+ return;
+ }
+
+ SkScalar t;
+ SkPoint tmp[5]; // for SkChopQuadAt
+
+ // are we partially to the left
+ if (pts[0].fX < clip.fLeft) {
+ if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
+ // clamp to clean up imprecise numerics in the chop
+ tmp[2].fX = clip.fLeft;
+ clamp_ge(tmp[3].fX, clip.fLeft);
+
+ pts[0] = tmp[2];
+ pts[1] = tmp[3];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
+ return;
+ }
+ }
+
+ // are we partially to the right
+ if (pts[2].fX > clip.fRight) {
+ if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ // clamp to clean up imprecise numerics in the chop
+ clamp_le(tmp[1].fX, clip.fRight);
+ tmp[2].fX = clip.fRight;
+
+ this->appendQuad(tmp, reverse);
+ this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
+ }
+ } else { // wholly inside the clip
+ this->appendQuad(pts, reverse);
+ }
+}
+
+bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+
+ SkRect bounds;
+ bounds.set(srcPts, 3);
+
+ if (!quick_reject(bounds, clip)) {
+ SkPoint monoY[5];
+ int countY = SkChopQuadAtYExtrema(srcPts, monoY);
+ for (int y = 0; y <= countY; y++) {
+ SkPoint monoX[5];
+ int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
+ for (int x = 0; x <= countX; x++) {
+ this->clipMonoQuad(&monoX[x * 2], clip);
+ SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
+ SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
+ }
+ }
+ }
+
+ *fCurrVerb = SkPath::kDone_Verb;
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+ return SkPath::kDone_Verb != fVerbs[0];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
+ SkScalar D, SkScalar t) {
+ return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
+}
+
+/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
+ t value such that cubic(t) = target
+ */
+static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
+ SkScalar target, SkScalar* t) {
+ // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
+ SkASSERT(c0 < target && target < c3);
+
+ SkScalar D = c0 - target;
+ SkScalar A = c3 + 3*(c1 - c2) - c0;
+ SkScalar B = 3*(c2 - c1 - c1 + c0);
+ SkScalar C = 3*(c1 - c0);
+
+ const SkScalar TOLERANCE = SK_Scalar1 / 4096;
+ SkScalar minT = 0;
+ SkScalar maxT = SK_Scalar1;
+ SkScalar mid;
+
+ // This is a lot of iterations. Is there a faster way?
+ for (int i = 0; i < 24; i++) {
+ mid = SkScalarAve(minT, maxT);
+ SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
+ if (delta < 0) {
+ minT = mid;
+ delta = -delta;
+ } else {
+ maxT = mid;
+ }
+ if (delta < TOLERANCE) {
+ break;
+ }
+ }
+ *t = mid;
+// SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t));
+ return true;
+}
+
+static bool chopMonoCubicAtY(SkPoint pts[4], SkScalar y, SkScalar* t) {
+ return chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, t);
+}
+
+static bool chopMonoCubicAtX(SkPoint pts[4], SkScalar x, SkScalar* t) {
+ return chopMonoCubicAt(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, x, t);
+}
+
+// Modify pts[] in place so that it is clipped in Y to the clip rect
+static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) {
+
+ // are we partially above
+ if (pts[0].fY < clip.fTop) {
+ SkScalar t;
+ if (chopMonoCubicAtY(pts, clip.fTop, &t)) {
+ SkPoint tmp[7];
+ SkChopCubicAt(pts, tmp, t);
+
+ // tmp[3, 4, 5].fY should all be to the below clip.fTop.
+ // Since we can't trust the numerics of
+ // the chopper, we force those conditions now
+ tmp[3].fY = clip.fTop;
+ clamp_ge(tmp[4].fY, clip.fTop);
+ clamp_ge(tmp[5].fY, clip.fTop);
+
+ pts[0] = tmp[3];
+ pts[1] = tmp[4];
+ pts[2] = tmp[5];
+ } else {
+ // if chopMonoCubicAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the top
+ for (int i = 0; i < 4; i++) {
+ clamp_ge(pts[i].fY, clip.fTop);
+ }
+ }
+ }
+
+ // are we partially below
+ if (pts[3].fY > clip.fBottom) {
+ SkScalar t;
+ if (chopMonoCubicAtY(pts, clip.fBottom, &t)) {
+ SkPoint tmp[7];
+ SkChopCubicAt(pts, tmp, t);
+ tmp[3].fY = clip.fBottom;
+ clamp_le(tmp[2].fY, clip.fBottom);
+
+ pts[1] = tmp[1];
+ pts[2] = tmp[2];
+ pts[3] = tmp[3];
+ } else {
+ // if chopMonoCubicAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the bottom
+ for (int i = 0; i < 4; i++) {
+ clamp_le(pts[i].fY, clip.fBottom);
+ }
+ }
+ }
+}
+
+// srcPts[] must be monotonic in X and Y
+void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) {
+ SkPoint pts[4];
+ bool reverse = sort_increasing_Y(pts, src, 4);
+
+ // are we completely above or below
+ if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
+ return;
+ }
+
+ // Now chop so that pts is contained within clip in Y
+ chop_cubic_in_Y(pts, clip);
+
+ if (pts[0].fX > pts[3].fX) {
+ SkTSwap<SkPoint>(pts[0], pts[3]);
+ SkTSwap<SkPoint>(pts[1], pts[2]);
+ reverse = !reverse;
+ }
+
+ // Now chop in X has needed, and record the segments
+
+ if (pts[3].fX <= clip.fLeft) { // wholly to the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
+ return;
+ }
+ if (pts[0].fX >= clip.fRight) { // wholly to the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
+ return;
+ }
+
+ // are we partially to the left
+ if (pts[0].fX < clip.fLeft) {
+ SkScalar t;
+ if (chopMonoCubicAtX(pts, clip.fLeft, &t)) {
+ SkPoint tmp[7];
+ SkChopCubicAt(pts, tmp, t);
+ this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse);
+
+ // tmp[3, 4, 5].fX should all be to the right of clip.fLeft.
+ // Since we can't trust the numerics of
+ // the chopper, we force those conditions now
+ tmp[3].fX = clip.fLeft;
+ clamp_ge(tmp[4].fX, clip.fLeft);
+ clamp_ge(tmp[5].fX, clip.fLeft);
+
+ pts[0] = tmp[3];
+ pts[1] = tmp[4];
+ pts[2] = tmp[5];
+ } else {
+ // if chopMonocubicAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse);
+ return;
+ }
+ }
+
+ // are we partially to the right
+ if (pts[3].fX > clip.fRight) {
+ SkScalar t;
+ if (chopMonoCubicAtX(pts, clip.fRight, &t)) {
+ SkPoint tmp[7];
+ SkChopCubicAt(pts, tmp, t);
+ tmp[3].fX = clip.fRight;
+ clamp_le(tmp[2].fX, clip.fRight);
+ clamp_le(tmp[1].fX, clip.fRight);
+
+ this->appendCubic(tmp, reverse);
+ this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse);
+ } else {
+ // if chopMonoCubicAtX failed, then we may have hit inexact numerics
+ // so we just clamp against the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
+ }
+ } else { // wholly inside the clip
+ this->appendCubic(pts, reverse);
+ }
+}
+
+bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) {
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+
+ SkRect bounds;
+ bounds.set(srcPts, 4);
+
+ if (!quick_reject(bounds, clip)) {
+ SkPoint monoY[10];
+ int countY = SkChopCubicAtYExtrema(srcPts, monoY);
+ for (int y = 0; y <= countY; y++) {
+ SkPoint monoX[10];
+ int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX);
+ for (int x = 0; x <= countX; x++) {
+ this->clipMonoCubic(&monoX[x * 3], clip);
+ SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
+ SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
+ }
+ }
+ }
+
+ *fCurrVerb = SkPath::kDone_Verb;
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+ return SkPath::kDone_Verb != fVerbs[0];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
+ bool reverse) {
+ *fCurrVerb++ = SkPath::kLine_Verb;
+
+ if (reverse) {
+ SkTSwap<SkScalar>(y0, y1);
+ }
+ fCurrPoint[0].set(x, y0);
+ fCurrPoint[1].set(x, y1);
+ fCurrPoint += 2;
+}
+
+void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) {
+ *fCurrVerb++ = SkPath::kQuad_Verb;
+
+ if (reverse) {
+ fCurrPoint[0] = pts[2];
+ fCurrPoint[2] = pts[0];
+ } else {
+ fCurrPoint[0] = pts[0];
+ fCurrPoint[2] = pts[2];
+ }
+ fCurrPoint[1] = pts[1];
+ fCurrPoint += 3;
+}
+
+void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) {
+ *fCurrVerb++ = SkPath::kCubic_Verb;
+
+ if (reverse) {
+ for (int i = 0; i < 4; i++) {
+ fCurrPoint[i] = pts[3 - i];
+ }
+ } else {
+ memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint));
+ }
+ fCurrPoint += 4;
+}
+
+SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) {
+ SkPath::Verb verb = *fCurrVerb;
+
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
+ fCurrPoint += 2;
+ fCurrVerb += 1;
+ break;
+ case SkPath::kQuad_Verb:
+ memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
+ fCurrPoint += 3;
+ fCurrVerb += 1;
+ break;
+ case SkPath::kCubic_Verb:
+ memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint));
+ fCurrPoint += 4;
+ fCurrVerb += 1;
+ break;
+ case SkPath::kDone_Verb:
+ break;
+ default:
+ SkDEBUGFAIL("unexpected verb in quadclippper2 iter");
+ break;
+ }
+ return verb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+static void assert_monotonic(const SkScalar coord[], int count) {
+ if (coord[0] > coord[(count - 1) * 2]) {
+ for (int i = 1; i < count; i++) {
+ SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]);
+ }
+ } else if (coord[0] < coord[(count - 1) * 2]) {
+ for (int i = 1; i < count; i++) {
+ SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]);
+ }
+ } else {
+ for (int i = 1; i < count; i++) {
+ SkASSERT(coord[2 * (i - 1)] == coord[i * 2]);
+ }
+ }
+}
+
+void sk_assert_monotonic_y(const SkPoint pts[], int count) {
+ if (count > 1) {
+ assert_monotonic(&pts[0].fY, count);
+ }
+}
+
+void sk_assert_monotonic_x(const SkPoint pts[], int count) {
+ if (count > 1) {
+ assert_monotonic(&pts[0].fX, count);
+ }
+}
+#endif
diff --git a/core/SkEdgeClipper.h b/core/SkEdgeClipper.h
new file mode 100644
index 00000000..e16ed552
--- /dev/null
+++ b/core/SkEdgeClipper.h
@@ -0,0 +1,51 @@
+
+/*
+ * Copyright 2009 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 SkEdgeClipper_DEFINED
+#define SkEdgeClipper_DEFINED
+
+#include "SkPath.h"
+
+/** This is basically an iterator. It is initialized with an edge and a clip,
+ and then next() is called until it returns kDone_Verb.
+ */
+class SkEdgeClipper {
+public:
+ bool clipQuad(const SkPoint pts[3], const SkRect& clip);
+ bool clipCubic(const SkPoint pts[4], const SkRect& clip);
+
+ SkPath::Verb next(SkPoint pts[]);
+
+private:
+ SkPoint* fCurrPoint;
+ SkPath::Verb* fCurrVerb;
+
+ enum {
+ kMaxVerbs = 13,
+ kMaxPoints = 32
+ };
+ SkPoint fPoints[kMaxPoints];
+ SkPath::Verb fVerbs[kMaxVerbs];
+
+ void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip);
+ void clipMonoCubic(const SkPoint srcPts[4], const SkRect& clip);
+ void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse);
+ void appendQuad(const SkPoint pts[3], bool reverse);
+ void appendCubic(const SkPoint pts[4], bool reverse);
+};
+
+#ifdef SK_DEBUG
+ void sk_assert_monotonic_x(const SkPoint pts[], int count);
+ void sk_assert_monotonic_y(const SkPoint pts[], int count);
+#else
+ #define sk_assert_monotonic_x(pts, count)
+ #define sk_assert_monotonic_y(pts, count)
+#endif
+
+#endif
diff --git a/core/SkError.cpp b/core/SkError.cpp
new file mode 100644
index 00000000..f5d233c6
--- /dev/null
+++ b/core/SkError.cpp
@@ -0,0 +1,143 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTLS.h"
+#include "SkTypes.h"
+#include "SkError.h"
+#include "SkErrorInternals.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+namespace {
+ void *CreateThreadError() {
+ return SkNEW_ARGS(SkError, (kNoError_SkError));
+ }
+ void DeleteThreadError(void* v) {
+ SkDELETE(reinterpret_cast<SkError*>(v));
+ }
+ #define THREAD_ERROR \
+ (*reinterpret_cast<SkError*>(SkTLS::Get(CreateThreadError, DeleteThreadError)))
+
+ void *CreateThreadErrorCallback() {
+ return SkNEW_ARGS(SkErrorCallbackFunction, (SkErrorInternals::DefaultErrorCallback));
+ }
+ void DeleteThreadErrorCallback(void* v) {
+ SkDELETE(reinterpret_cast<SkErrorCallbackFunction *>(v));
+ }
+
+ #define THREAD_ERROR_CALLBACK \
+ *(reinterpret_cast<SkErrorCallbackFunction *>(SkTLS::Get(CreateThreadErrorCallback, \
+ DeleteThreadErrorCallback)))
+
+ void *CreateThreadErrorContext() {
+ return SkNEW_ARGS(void **, (NULL));
+ }
+ void DeleteThreadErrorContext(void* v) {
+ SkDELETE(reinterpret_cast<void **>(v));
+ }
+ #define THREAD_ERROR_CONTEXT \
+ (*reinterpret_cast<void **>(SkTLS::Get(CreateThreadErrorContext, DeleteThreadErrorContext)))
+
+ #define ERROR_STRING_LENGTH 2048
+
+ void *CreateThreadErrorString() {
+ return SkNEW_ARRAY(char, (ERROR_STRING_LENGTH));
+ }
+ void DeleteThreadErrorString(void* v) {
+ SkDELETE_ARRAY(reinterpret_cast<char *>(v));
+ }
+ #define THREAD_ERROR_STRING \
+ (reinterpret_cast<char *>(SkTLS::Get(CreateThreadErrorString, DeleteThreadErrorString)))
+}
+
+SkError SkGetLastError() {
+ return SkErrorInternals::GetLastError();
+}
+void SkClearLastError() {
+ SkErrorInternals::ClearError();
+}
+void SkSetErrorCallback(SkErrorCallbackFunction cb, void *context) {
+ SkErrorInternals::SetErrorCallback(cb, context);
+}
+const char *SkGetLastErrorString() {
+ return SkErrorInternals::GetLastErrorString();
+}
+
+// ------------ Private Error functions ---------
+
+void SkErrorInternals::SetErrorCallback(SkErrorCallbackFunction cb, void *context) {
+ if (cb == NULL) {
+ THREAD_ERROR_CALLBACK = SkErrorInternals::DefaultErrorCallback;
+ } else {
+ THREAD_ERROR_CALLBACK = cb;
+ }
+ THREAD_ERROR_CONTEXT = context;
+}
+
+void SkErrorInternals::DefaultErrorCallback(SkError code, void *context) {
+ SkDebugf("Skia Error: %s\n", SkGetLastErrorString());
+}
+
+void SkErrorInternals::ClearError() {
+ SkErrorInternals::SetError( kNoError_SkError, "All is well" );
+}
+
+SkError SkErrorInternals::GetLastError() {
+ return THREAD_ERROR;
+}
+
+const char *SkErrorInternals::GetLastErrorString() {
+ return THREAD_ERROR_STRING;
+}
+
+void SkErrorInternals::SetError(SkError code, const char *fmt, ...) {
+ THREAD_ERROR = code;
+ va_list args;
+
+ char *str = THREAD_ERROR_STRING;
+ const char *error_name = NULL;
+ switch( code ) {
+ case kNoError_SkError:
+ error_name = "No Error";
+ break;
+ case kInvalidArgument_SkError:
+ error_name = "Invalid Argument";
+ break;
+ case kInvalidOperation_SkError:
+ error_name = "Invalid Operation";
+ break;
+ case kInvalidHandle_SkError:
+ error_name = "Invalid Handle";
+ break;
+ case kInvalidPaint_SkError:
+ error_name = "Invalid Paint";
+ break;
+ case kOutOfMemory_SkError:
+ error_name = "Out Of Memory";
+ break;
+ case kParseError_SkError:
+ error_name = "Parse Error";
+ break;
+ default:
+ error_name = "Unknown error";
+ break;
+ }
+
+ sprintf( str, "%s: ", error_name );
+ int string_left = ERROR_STRING_LENGTH - strlen( str );
+ str += strlen(str);
+
+ va_start( args, fmt );
+ vsnprintf( str, string_left, fmt, args );
+ va_end( args );
+ SkErrorCallbackFunction fn = THREAD_ERROR_CALLBACK;
+ if (fn && code != kNoError_SkError) {
+ fn(code, THREAD_ERROR_CONTEXT);
+ }
+}
diff --git a/core/SkErrorInternals.h b/core/SkErrorInternals.h
new file mode 100644
index 00000000..65ff2318
--- /dev/null
+++ b/core/SkErrorInternals.h
@@ -0,0 +1,27 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkErrorInternals_DEFINED
+#define SkErrorInternals_DEFINED
+
+#include "SkError.h"
+
+class SkErrorInternals {
+
+public:
+ static void ClearError();
+ static void SetError(SkError code, const char *fmt, ...);
+ static SkError GetLastError();
+ static const char *GetLastErrorString();
+ static void SetErrorCallback(SkErrorCallbackFunction cb, void *context);
+ static void DefaultErrorCallback(SkError code, void *context);
+};
+
+
+
+#endif /* SkErrorInternals_DEFINED */
diff --git a/core/SkFDot6.h b/core/SkFDot6.h
new file mode 100644
index 00000000..a88ea925
--- /dev/null
+++ b/core/SkFDot6.h
@@ -0,0 +1,60 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkFDot6_DEFINED
+#define SkFDot6_DEFINED
+
+#include "SkScalar.h"
+#include "SkMath.h"
+
+typedef int32_t SkFDot6;
+
+#define SK_FDot6One (64)
+#define SK_FDot6Half (32)
+
+#ifdef SK_DEBUG
+ inline SkFDot6 SkIntToFDot6(S16CPU x) {
+ SkASSERT(SkToS16(x) == x);
+ return x << 6;
+ }
+#else
+ #define SkIntToFDot6(x) ((x) << 6)
+#endif
+
+#define SkFDot6Floor(x) ((x) >> 6)
+#define SkFDot6Ceil(x) (((x) + 63) >> 6)
+#define SkFDot6Round(x) (((x) + 32) >> 6)
+
+#define SkFixedToFDot6(x) ((x) >> 10)
+
+inline SkFixed SkFDot6ToFixed(SkFDot6 x) {
+ SkASSERT((x << 10 >> 10) == x);
+
+ return x << 10;
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+ #define SkScalarToFDot6(x) (SkFDot6)((x) * 64)
+ #define SkFDot6ToScalar(x) ((SkScalar)(x) * SkFloatToScalar(0.015625f))
+#else
+ #define SkScalarToFDot6(x) ((x) >> 10)
+ #define SkFDot6ToScalar(x) ((x) << 10)
+#endif
+
+inline SkFixed SkFDot6Div(SkFDot6 a, SkFDot6 b) {
+ SkASSERT(b != 0);
+
+ if (a == (int16_t)a) {
+ return (a << 16) / b;
+ } else {
+ return SkFixedDiv(a, b);
+ }
+}
+
+#endif
diff --git a/core/SkFP.h b/core/SkFP.h
new file mode 100644
index 00000000..1d1507a3
--- /dev/null
+++ b/core/SkFP.h
@@ -0,0 +1,79 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkFP_DEFINED
+#define SkFP_DEFINED
+
+#include "SkMath.h"
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+ typedef float SkFP;
+
+ #define SkScalarToFP(n) (n)
+ #define SkFPToScalar(n) (n)
+ #define SkIntToFP(n) SkIntToScalar(n)
+ #define SkFPRound(x) SkScalarRound(n)
+ #define SkFPCeil(x) SkScalarCeil(n)
+ #define SkFPFloor(x) SkScalarFloor(n)
+
+ #define SkFPNeg(x) (-(x))
+ #define SkFPAbs(x) SkScalarAbs(x)
+ #define SkFPAdd(a, b) ((a) + (b))
+ #define SkFPSub(a, b) ((a) - (b))
+ #define SkFPMul(a, b) ((a) * (b))
+ #define SkFPMulInt(a, n) ((a) * (n))
+ #define SkFPDiv(a, b) ((a) / (b))
+ #define SkFPDivInt(a, n) ((a) / (n))
+ #define SkFPInvert(x) SkScalarInvert(x)
+ #define SkFPSqrt(x) SkScalarSqrt(x)
+ #define SkFPCubeRoot(x) sk_float_pow(x, 0.3333333f)
+
+ #define SkFPLT(a, b) ((a) < (b))
+ #define SkFPLE(a, b) ((a) <= (b))
+ #define SkFPGT(a, b) ((a) > (b))
+ #define SkFPGE(a, b) ((a) >= (b))
+
+#else // scalar is fixed
+
+ #include "SkFloat.h"
+
+ typedef int32_t SkFP;
+
+ #define SkScalarToFP(n) SkFloat::SetShift(n, -16)
+ #define SkFPToScalar(n) SkFloat::GetShift(n, -16)
+ #define SkIntToFP(n) SkFloat::SetShift(n, 0)
+ #define SkFPRound(x) SkFloat::Round(x);
+ #define SkFPCeil(x) SkFloat::Ceil();
+ #define SkFPFloor(x) SkFloat::Floor();
+
+ #define SkFPNeg(x) SkFloat::Neg(x)
+ #define SkFPAbs(x) SkFloat::Abs(x)
+ #define SkFPAdd(a, b) SkFloat::Add(a, b)
+ #define SkFPSub(a, b) SkFloat::Add(a, SkFloat::Neg(b))
+ #define SkFPMul(a, b) SkFloat::Mul(a, b)
+ #define SkFPMulInt(a, n) SkFloat::MulInt(a, n)
+ #define SkFPDiv(a, b) SkFloat::Div(a, b)
+ #define SkFPDivInt(a, n) SkFloat::DivInt(a, n)
+ #define SkFPInvert(x) SkFloat::Invert(x)
+ #define SkFPSqrt(x) SkFloat::Sqrt(x)
+ #define SkFPCubeRoot(x) SkFloat::CubeRoot(x)
+
+ #define SkFPLT(a, b) (SkFloat::Cmp(a, b) < 0)
+ #define SkFPLE(a, b) (SkFloat::Cmp(a, b) <= 0)
+ #define SkFPGT(a, b) (SkFloat::Cmp(a, b) > 0)
+ #define SkFPGE(a, b) (SkFloat::Cmp(a, b) >= 0)
+
+#endif
+
+#ifdef SK_DEBUG
+ void SkFP_UnitTest();
+#endif
+
+#endif
diff --git a/core/SkFilterProc.cpp b/core/SkFilterProc.cpp
new file mode 100644
index 00000000..29038490
--- /dev/null
+++ b/core/SkFilterProc.cpp
@@ -0,0 +1,294 @@
+
+/*
+ * 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 "SkFilterProc.h"
+
+/* [1-x 1-y] [x 1-y]
+ [1-x y] [x y]
+*/
+
+static unsigned bilerp00(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return a00; }
+static unsigned bilerp01(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * a00 + a01) >> 2; }
+static unsigned bilerp02(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (a00 + a01) >> 1; }
+static unsigned bilerp03(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (a00 + 3 * a01) >> 2; }
+
+static unsigned bilerp10(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * a00 + a10) >> 2; }
+static unsigned bilerp11(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (9 * a00 + 3 * (a01 + a10) + a11) >> 4; }
+static unsigned bilerp12(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * (a00 + a01) + a10 + a11) >> 3; }
+static unsigned bilerp13(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (9 * a01 + 3 * (a00 + a11) + a10) >> 4; }
+
+static unsigned bilerp20(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (a00 + a10) >> 1; }
+static unsigned bilerp21(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * (a00 + a10) + a01 + a11) >> 3; }
+static unsigned bilerp22(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (a00 + a01 + a10 + a11) >> 2; }
+static unsigned bilerp23(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * (a01 + a11) + a00 + a10) >> 3; }
+
+static unsigned bilerp30(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (a00 + 3 * a10) >> 2; }
+static unsigned bilerp31(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (9 * a10 + 3 * (a00 + a11) + a01) >> 4; }
+static unsigned bilerp32(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (3 * (a10 + a11) + a00 + a01) >> 3; }
+static unsigned bilerp33(unsigned a00, unsigned a01, unsigned a10, unsigned a11) { return (9 * a11 + 3 * (a01 + a10) + a00) >> 4; }
+
+static const SkFilterProc gBilerpProcs[4 * 4] = {
+ bilerp00, bilerp01, bilerp02, bilerp03,
+ bilerp10, bilerp11, bilerp12, bilerp13,
+ bilerp20, bilerp21, bilerp22, bilerp23,
+ bilerp30, bilerp31, bilerp32, bilerp33
+};
+
+const SkFilterProc* SkGetBilinearFilterProcTable()
+{
+ return gBilerpProcs;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define MASK 0xFF00FF
+#define LO_PAIR(x) ((x) & MASK)
+#define HI_PAIR(x) (((x) >> 8) & MASK)
+#define COMBINE(lo, hi) (((lo) & ~0xFF00) | (((hi) & ~0xFF00) << 8))
+
+///////////////////////////////////////////////////////////////////////////////
+
+static unsigned bilerp4_00(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ return c00;
+}
+static unsigned bilerp4_01(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * LO_PAIR(c00) + LO_PAIR(c01)) >> 2;
+ uint32_t hi = (3 * HI_PAIR(c00) + HI_PAIR(c01)) >> 2;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_02(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c01)) >> 1;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c01)) >> 1;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_03(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (LO_PAIR(c00) + 3 * LO_PAIR(c01)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + 3 * HI_PAIR(c01)) >> 2;
+ return COMBINE(lo, hi);
+}
+
+static unsigned bilerp4_10(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * LO_PAIR(c00) + LO_PAIR(c10)) >> 2;
+ uint32_t hi = (3 * HI_PAIR(c00) + HI_PAIR(c10)) >> 2;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_11(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (9 * LO_PAIR(c00) + 3 * (LO_PAIR(c01) + LO_PAIR(c10)) + LO_PAIR(c11)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c00) + 3 * (HI_PAIR(c01) + HI_PAIR(c10)) + HI_PAIR(c11)) >> 4;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_12(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * (LO_PAIR(c00) + LO_PAIR(c01)) + LO_PAIR(c10) + LO_PAIR(c11)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c00) + HI_PAIR(c01)) + HI_PAIR(c10) + HI_PAIR(c11)) >> 3;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_13(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (9 * LO_PAIR(c01) + 3 * (LO_PAIR(c00) + LO_PAIR(c11)) + LO_PAIR(c10)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c01) + 3 * (HI_PAIR(c00) + HI_PAIR(c11)) + HI_PAIR(c10)) >> 4;
+ return COMBINE(lo, hi);
+}
+
+static unsigned bilerp4_20(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c10)) >> 1;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c10)) >> 1;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_21(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * (LO_PAIR(c00) + LO_PAIR(c10)) + LO_PAIR(c01) + LO_PAIR(c11)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c00) + HI_PAIR(c10)) + HI_PAIR(c01) + HI_PAIR(c11)) >> 3;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_22(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c01) + LO_PAIR(c10) + LO_PAIR(c11)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c01) + HI_PAIR(c10) + HI_PAIR(c11)) >> 2;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_23(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * (LO_PAIR(c01) + LO_PAIR(c11)) + LO_PAIR(c00) + LO_PAIR(c10)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c01) + HI_PAIR(c11)) + HI_PAIR(c00) + HI_PAIR(c10)) >> 3;
+ return COMBINE(lo, hi);
+}
+
+static unsigned bilerp4_30(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (LO_PAIR(c00) + 3 * LO_PAIR(c10)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + 3 * HI_PAIR(c10)) >> 2;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_31(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (9 * LO_PAIR(c10) + 3 * (LO_PAIR(c00) + LO_PAIR(c11)) + LO_PAIR(c01)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c10) + 3 * (HI_PAIR(c00) + HI_PAIR(c11)) + HI_PAIR(c01)) >> 4;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_32(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (3 * (LO_PAIR(c10) + LO_PAIR(c11)) + LO_PAIR(c00) + LO_PAIR(c01)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c10) + HI_PAIR(c11)) + HI_PAIR(c00) + HI_PAIR(c01)) >> 3;
+ return COMBINE(lo, hi);
+}
+static unsigned bilerp4_33(uint32_t c00, uint32_t c01, uint32_t c10, uint32_t c11) {
+ uint32_t lo = (9 * LO_PAIR(c11) + 3 * (LO_PAIR(c01) + LO_PAIR(c10)) + LO_PAIR(c00)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c11) + 3 * (HI_PAIR(c01) + HI_PAIR(c10)) + HI_PAIR(c00)) >> 4;
+ return COMBINE(lo, hi);
+}
+
+static const SkFilter32Proc gBilerp32Procs[4 * 4] = {
+ bilerp4_00, bilerp4_01, bilerp4_02, bilerp4_03,
+ bilerp4_10, bilerp4_11, bilerp4_12, bilerp4_13,
+ bilerp4_20, bilerp4_21, bilerp4_22, bilerp4_23,
+ bilerp4_30, bilerp4_31, bilerp4_32, bilerp4_33
+};
+
+const SkFilter32Proc* SkGetFilter32ProcTable()
+{
+ return gBilerp32Procs;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static uint32_t bilerptr00(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ return *a00;
+}
+static uint32_t bilerptr01(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t lo = (3 * LO_PAIR(c00) + LO_PAIR(c01)) >> 2;
+ uint32_t hi = (3 * HI_PAIR(c00) + HI_PAIR(c01)) >> 2;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr02(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c01)) >> 1;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c01)) >> 1;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr03(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t lo = (LO_PAIR(c00) + 3 * LO_PAIR(c01)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + 3 * HI_PAIR(c01)) >> 2;
+ return COMBINE(lo, hi);
+}
+
+static uint32_t bilerptr10(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c10 = *a10;
+ uint32_t lo = (3 * LO_PAIR(c00) + LO_PAIR(c10)) >> 2;
+ uint32_t hi = (3 * HI_PAIR(c00) + HI_PAIR(c10)) >> 2;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr11(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (9 * LO_PAIR(c00) + 3 * (LO_PAIR(c01) + LO_PAIR(c10)) + LO_PAIR(c11)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c00) + 3 * (HI_PAIR(c01) + HI_PAIR(c10)) + HI_PAIR(c11)) >> 4;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr12(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (3 * (LO_PAIR(c00) + LO_PAIR(c01)) + LO_PAIR(c10) + LO_PAIR(c11)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c00) + HI_PAIR(c01)) + HI_PAIR(c10) + HI_PAIR(c11)) >> 3;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr13(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (9 * LO_PAIR(c01) + 3 * (LO_PAIR(c00) + LO_PAIR(c11)) + LO_PAIR(c10)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c01) + 3 * (HI_PAIR(c00) + HI_PAIR(c11)) + HI_PAIR(c10)) >> 4;
+ return COMBINE(lo, hi);
+}
+
+static uint32_t bilerptr20(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c10 = *a10;
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c10)) >> 1;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c10)) >> 1;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr21(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (3 * (LO_PAIR(c00) + LO_PAIR(c10)) + LO_PAIR(c01) + LO_PAIR(c11)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c00) + HI_PAIR(c10)) + HI_PAIR(c01) + HI_PAIR(c11)) >> 3;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr22(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (LO_PAIR(c00) + LO_PAIR(c01) + LO_PAIR(c10) + LO_PAIR(c11)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + HI_PAIR(c01) + HI_PAIR(c10) + HI_PAIR(c11)) >> 2;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr23(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (3 * (LO_PAIR(c01) + LO_PAIR(c11)) + LO_PAIR(c00) + LO_PAIR(c10)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c01) + HI_PAIR(c11)) + HI_PAIR(c00) + HI_PAIR(c10)) >> 3;
+ return COMBINE(lo, hi);
+}
+
+static uint32_t bilerptr30(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c10 = *a10;
+ uint32_t lo = (LO_PAIR(c00) + 3 * LO_PAIR(c10)) >> 2;
+ uint32_t hi = (HI_PAIR(c00) + 3 * HI_PAIR(c10)) >> 2;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr31(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (9 * LO_PAIR(c10) + 3 * (LO_PAIR(c00) + LO_PAIR(c11)) + LO_PAIR(c01)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c10) + 3 * (HI_PAIR(c00) + HI_PAIR(c11)) + HI_PAIR(c01)) >> 4;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr32(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (3 * (LO_PAIR(c10) + LO_PAIR(c11)) + LO_PAIR(c00) + LO_PAIR(c01)) >> 3;
+ uint32_t hi = (3 * (HI_PAIR(c10) + HI_PAIR(c11)) + HI_PAIR(c00) + HI_PAIR(c01)) >> 3;
+ return COMBINE(lo, hi);
+}
+static uint32_t bilerptr33(const uint32_t* a00, const uint32_t* a01, const uint32_t* a10, const uint32_t* a11) {
+ uint32_t c00 = *a00;
+ uint32_t c01 = *a01;
+ uint32_t c10 = *a10;
+ uint32_t c11 = *a11;
+ uint32_t lo = (9 * LO_PAIR(c11) + 3 * (LO_PAIR(c01) + LO_PAIR(c10)) + LO_PAIR(c00)) >> 4;
+ uint32_t hi = (9 * HI_PAIR(c11) + 3 * (HI_PAIR(c01) + HI_PAIR(c10)) + HI_PAIR(c00)) >> 4;
+ return COMBINE(lo, hi);
+}
+
+static const SkFilterPtrProc gBilerpPtrProcs[4 * 4] = {
+ bilerptr00, bilerptr01, bilerptr02, bilerptr03,
+ bilerptr10, bilerptr11, bilerptr12, bilerptr13,
+ bilerptr20, bilerptr21, bilerptr22, bilerptr23,
+ bilerptr30, bilerptr31, bilerptr32, bilerptr33
+};
+
+const SkFilterPtrProc* SkGetBilinearFilterPtrProcTable()
+{
+ return gBilerpPtrProcs;
+}
diff --git a/core/SkFilterProc.h b/core/SkFilterProc.h
new file mode 100644
index 00000000..3b20d592
--- /dev/null
+++ b/core/SkFilterProc.h
@@ -0,0 +1,135 @@
+
+/*
+ * 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 SkFilter_DEFINED
+#define SkFilter_DEFINED
+
+#include "SkMath.h"
+#include "SkFixed.h"
+
+typedef unsigned (*SkFilterProc)(unsigned x00, unsigned x01,
+ unsigned x10, unsigned x11);
+
+const SkFilterProc* SkGetBilinearFilterProcTable();
+
+inline SkFilterProc SkGetBilinearFilterProc(const SkFilterProc* table,
+ SkFixed x, SkFixed y)
+{
+ SkASSERT(table);
+
+ // convert to dot 2
+ x = (unsigned)(x << 16) >> 30;
+ y = (unsigned)(y << 16) >> 30;
+ return table[(y << 2) | x];
+}
+
+inline SkFilterProc SkGetBilinearFilterProc22(const SkFilterProc* table,
+ unsigned x, unsigned y)
+{
+ SkASSERT(table);
+
+ // extract low 2 bits
+ x = x << 30 >> 30;
+ y = y << 30 >> 30;
+ return table[(y << 2) | x];
+}
+
+inline const SkFilterProc* SkGetBilinearFilterProc22Row(const SkFilterProc* table,
+ unsigned y)
+{
+ SkASSERT(table);
+ // extract low 2 bits and shift up 2
+ return &table[y << 30 >> 28];
+}
+
+inline SkFilterProc SkGetBilinearFilterProc22RowProc(const SkFilterProc* row,
+ unsigned x)
+{
+ SkASSERT(row);
+ // extract low 2 bits
+ return row[x << 30 >> 30];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef unsigned (*SkFilter32Proc)(uint32_t x00, uint32_t x01,
+ uint32_t x10, uint32_t x11);
+
+const SkFilter32Proc* SkGetFilter32ProcTable();
+
+inline SkFilter32Proc SkGetFilter32Proc22(const SkFilter32Proc* table,
+ unsigned x, unsigned y)
+{
+ SkASSERT(table);
+
+ // extract low 2 bits
+ x = x << 30 >> 30;
+ y = y << 30 >> 30;
+ return table[(y << 2) | x];
+}
+
+inline const SkFilter32Proc* SkGetFilter32Proc22Row(const SkFilter32Proc* table,
+ unsigned y)
+{
+ SkASSERT(table);
+ // extract low 2 bits and shift up 2
+ return &table[y << 30 >> 28];
+}
+
+inline SkFilter32Proc SkGetFilter32Proc22RowProc(const SkFilter32Proc* row,
+ unsigned x)
+{
+ SkASSERT(row);
+ // extract low 2 bits
+ return row[x << 30 >> 30];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** Special version of SkFilterProc. This takes the address of 4 ints, and combines them a byte at a
+ time. AABBCCDD.
+*/
+typedef uint32_t (*SkFilterPtrProc)(const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*);
+
+const SkFilterPtrProc* SkGetBilinearFilterPtrProcTable();
+inline SkFilterPtrProc SkGetBilinearFilterPtrProc(const SkFilterPtrProc* table, SkFixed x, SkFixed y)
+{
+ SkASSERT(table);
+
+ // convert to dot 2
+ x = (unsigned)(x << 16) >> 30;
+ y = (unsigned)(y << 16) >> 30;
+ return table[(y << 2) | x];
+}
+
+/** Given a Y value, return a subset of the proc table for that value.
+ Pass this to SkGetBilinearFilterPtrXProc with the corresponding X value to get the
+ correct proc.
+*/
+inline const SkFilterPtrProc* SkGetBilinearFilterPtrProcYTable(const SkFilterPtrProc* table, SkFixed y)
+{
+ SkASSERT(table);
+
+ y = (unsigned)(y << 16) >> 30;
+ return table + (y << 2);
+}
+
+/** Given a subtable returned by SkGetBilinearFilterPtrProcYTable(), return the proc for the
+ specified X value.
+*/
+inline SkFilterPtrProc SkGetBilinearFilterPtrXProc(const SkFilterPtrProc* table, SkFixed x)
+{
+ SkASSERT(table);
+
+ // convert to dot 2
+ x = (unsigned)(x << 16) >> 30;
+ return table[x];
+}
+
+#endif
diff --git a/core/SkFilterShader.cpp b/core/SkFilterShader.cpp
new file mode 100644
index 00000000..4fcb9361
--- /dev/null
+++ b/core/SkFilterShader.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFilterShader.h"
+
+#include "SkColorFilter.h"
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+#include "SkString.h"
+
+SkFilterShader::SkFilterShader(SkShader* shader, SkColorFilter* filter) {
+ fShader = shader;
+ shader->ref();
+
+ fFilter = filter;
+ filter->ref();
+}
+
+SkFilterShader::SkFilterShader(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fShader = buffer.readFlattenableT<SkShader>();
+ fFilter = buffer.readFlattenableT<SkColorFilter>();
+}
+
+SkFilterShader::~SkFilterShader() {
+ fFilter->unref();
+ fShader->unref();
+}
+
+void SkFilterShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fShader);
+ buffer.writeFlattenable(fFilter);
+}
+
+uint32_t SkFilterShader::getFlags() {
+ uint32_t shaderF = fShader->getFlags();
+ uint32_t filterF = fFilter->getFlags();
+
+ // if the filter doesn't support 16bit, clear the matching bit in the shader
+ if (!(filterF & SkColorFilter::kHasFilter16_Flag)) {
+ shaderF &= ~SkShader::kHasSpan16_Flag;
+ }
+ // if the filter might change alpha, clear the opaque flag in the shader
+ if (!(filterF & SkColorFilter::kAlphaUnchanged_Flag)) {
+ shaderF &= ~(SkShader::kOpaqueAlpha_Flag | SkShader::kHasSpan16_Flag);
+ }
+ return shaderF;
+}
+
+bool SkFilterShader::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& 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) {
+ fShader->shadeSpan(x, y, result, count);
+ fFilter->filterSpan(result, count, result);
+}
+
+void SkFilterShader::shadeSpan16(int x, int y, uint16_t result[], int count) {
+ SkASSERT(fShader->getFlags() & SkShader::kHasSpan16_Flag);
+ SkASSERT(fFilter->getFlags() & SkColorFilter::kHasFilter16_Flag);
+
+ fShader->shadeSpan16(x, y, result, count);
+ fFilter->filterSpan16(result, count, result);
+}
+
+#ifdef SK_DEVELOPER
+void SkFilterShader::toString(SkString* str) const {
+ str->append("SkFilterShader: (");
+
+ str->append("Shader: ");
+ fShader->toString(str);
+ str->append(" Filter: ");
+ // TODO: add "fFilter->toString(str);" once SkColorFilter::toString is added
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/core/SkFilterShader.h b/core/SkFilterShader.h
new file mode 100644
index 00000000..be19640d
--- /dev/null
+++ b/core/SkFilterShader.h
@@ -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.
+ */
+
+#ifndef SkFilterShader_DEFINED
+#define SkFilterShader_DEFINED
+
+#include "SkShader.h"
+
+class SkColorFilter;
+
+class SkFilterShader : public SkShader {
+public:
+ SkFilterShader(SkShader* shader, SkColorFilter* filter);
+ virtual ~SkFilterShader();
+
+ 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_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkFilterShader)
+
+protected:
+ SkFilterShader(SkFlattenableReadBuffer& );
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+private:
+ SkShader* fShader;
+ SkColorFilter* fFilter;
+
+ typedef SkShader INHERITED;
+};
+
+#endif
diff --git a/core/SkFlate.cpp b/core/SkFlate.cpp
new file mode 100644
index 00000000..8258cdcd
--- /dev/null
+++ b/core/SkFlate.cpp
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright 2010 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 "SkData.h"
+#include "SkFlate.h"
+#include "SkStream.h"
+
+#ifndef SK_HAS_ZLIB
+bool SkFlate::HaveFlate() { return false; }
+bool SkFlate::Deflate(SkStream*, SkWStream*) { return false; }
+bool SkFlate::Deflate(const void*, size_t, SkWStream*) { return false; }
+bool SkFlate::Deflate(const SkData*, SkWStream*) { return false; }
+bool SkFlate::Inflate(SkStream*, SkWStream*) { return false; }
+#else
+
+// static
+bool SkFlate::HaveFlate() {
+ return true;
+}
+
+namespace {
+
+#ifdef SK_SYSTEM_ZLIB
+#include <zlib.h>
+#else
+#include SK_ZLIB_INCLUDE
+#endif
+
+// static
+const size_t kBufferSize = 1024;
+
+bool doFlate(bool compress, SkStream* src, SkWStream* dst) {
+ uint8_t inputBuffer[kBufferSize];
+ uint8_t outputBuffer[kBufferSize];
+ z_stream flateData;
+ flateData.zalloc = NULL;
+ flateData.zfree = NULL;
+ flateData.next_in = NULL;
+ flateData.avail_in = 0;
+ flateData.next_out = outputBuffer;
+ flateData.avail_out = kBufferSize;
+ int rc;
+ if (compress)
+ rc = deflateInit(&flateData, Z_DEFAULT_COMPRESSION);
+ else
+ rc = inflateInit(&flateData);
+ if (rc != Z_OK)
+ return false;
+
+ uint8_t* input = (uint8_t*)src->getMemoryBase();
+ size_t inputLength = src->getLength();
+ if (input == NULL || inputLength == 0) {
+ input = NULL;
+ flateData.next_in = inputBuffer;
+ flateData.avail_in = 0;
+ } else {
+ flateData.next_in = input;
+ flateData.avail_in = inputLength;
+ }
+
+ rc = Z_OK;
+ while (true) {
+ if (flateData.avail_out < kBufferSize) {
+ if (!dst->write(outputBuffer, kBufferSize - flateData.avail_out)) {
+ rc = Z_BUF_ERROR;
+ break;
+ }
+ flateData.next_out = outputBuffer;
+ flateData.avail_out = kBufferSize;
+ }
+ if (rc != Z_OK)
+ break;
+ if (flateData.avail_in == 0) {
+ if (input != NULL)
+ break;
+ size_t read = src->read(&inputBuffer, kBufferSize);
+ if (read == 0)
+ break;
+ flateData.next_in = inputBuffer;
+ flateData.avail_in = read;
+ }
+ if (compress)
+ rc = deflate(&flateData, Z_NO_FLUSH);
+ else
+ rc = inflate(&flateData, Z_NO_FLUSH);
+ }
+ while (rc == Z_OK) {
+ if (compress)
+ rc = deflate(&flateData, Z_FINISH);
+ else
+ rc = inflate(&flateData, Z_FINISH);
+ if (flateData.avail_out < kBufferSize) {
+ if (!dst->write(outputBuffer, kBufferSize - flateData.avail_out))
+ return false;
+ flateData.next_out = outputBuffer;
+ flateData.avail_out = kBufferSize;
+ }
+ }
+
+ if (compress)
+ deflateEnd(&flateData);
+ else
+ inflateEnd(&flateData);
+ if (rc == Z_STREAM_END)
+ return true;
+ return false;
+}
+
+}
+
+// static
+bool SkFlate::Deflate(SkStream* src, SkWStream* dst) {
+ return doFlate(true, src, dst);
+}
+
+bool SkFlate::Deflate(const void* ptr, size_t len, SkWStream* dst) {
+ SkMemoryStream stream(ptr, len);
+ return doFlate(true, &stream, dst);
+}
+
+bool SkFlate::Deflate(const SkData* data, SkWStream* dst) {
+ if (data) {
+ SkMemoryStream stream(data->data(), data->size());
+ return doFlate(true, &stream, dst);
+ }
+ return false;
+}
+
+// static
+bool SkFlate::Inflate(SkStream* src, SkWStream* dst) {
+ return doFlate(false, src, dst);
+}
+
+#endif
diff --git a/core/SkFlattenable.cpp b/core/SkFlattenable.cpp
new file mode 100644
index 00000000..b4efe910
--- /dev/null
+++ b/core/SkFlattenable.cpp
@@ -0,0 +1,127 @@
+
+/*
+ * 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 "SkFlattenable.h"
+#include "SkPtrRecorder.h"
+
+SK_DEFINE_INST_COUNT(SkFlattenable)
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkFlattenable::flatten(SkFlattenableWriteBuffer&) const
+{
+ /* we don't write anything at the moment, but this allows our subclasses
+ to not know that, since we want them to always call INHERITED::flatten()
+ in their code.
+ */
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkNamedFactorySet::SkNamedFactorySet() : fNextAddedFactory(0) {}
+
+uint32_t SkNamedFactorySet::find(SkFlattenable::Factory factory) {
+ uint32_t index = fFactorySet.find(factory);
+ if (index > 0) {
+ return index;
+ }
+ const char* name = SkFlattenable::FactoryToName(factory);
+ if (NULL == name) {
+ return 0;
+ }
+ *fNames.append() = name;
+ return fFactorySet.add(factory);
+}
+
+const char* SkNamedFactorySet::getNextAddedFactoryName() {
+ if (fNextAddedFactory < fNames.count()) {
+ return fNames[fNextAddedFactory++];
+ }
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRefCntSet::~SkRefCntSet() {
+ // call this now, while our decPtr() is sill in scope
+ this->reset();
+}
+
+void SkRefCntSet::incPtr(void* ptr) {
+ ((SkRefCnt*)ptr)->ref();
+}
+
+void SkRefCntSet::decPtr(void* ptr) {
+ ((SkRefCnt*)ptr)->unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define MAX_PAIR_COUNT 1024
+
+struct Pair {
+ const char* fName;
+ SkFlattenable::Factory fFactory;
+};
+
+static int gCount;
+static Pair gPairs[MAX_PAIR_COUNT];
+
+void SkFlattenable::Register(const char name[], Factory factory) {
+ SkASSERT(name);
+ SkASSERT(factory);
+
+ static bool gOnce;
+ if (!gOnce) {
+ gCount = 0;
+ gOnce = true;
+ }
+
+ SkASSERT(gCount < MAX_PAIR_COUNT);
+
+ gPairs[gCount].fName = name;
+ gPairs[gCount].fFactory = factory;
+ gCount += 1;
+}
+
+#ifdef SK_DEBUG
+static void report_no_entries(const char* functionName) {
+ if (!gCount) {
+ SkDebugf("%s has no registered name/factory pairs."
+ " Call SkGraphics::Init() at process initialization time.",
+ functionName);
+ }
+}
+#endif
+
+SkFlattenable::Factory SkFlattenable::NameToFactory(const char name[]) {
+#ifdef SK_DEBUG
+ report_no_entries(__FUNCTION__);
+#endif
+ const Pair* pairs = gPairs;
+ for (int i = gCount - 1; i >= 0; --i) {
+ if (strcmp(pairs[i].fName, name) == 0) {
+ return pairs[i].fFactory;
+ }
+ }
+ return NULL;
+}
+
+const char* SkFlattenable::FactoryToName(Factory fact) {
+#ifdef SK_DEBUG
+ report_no_entries(__FUNCTION__);
+#endif
+ const Pair* pairs = gPairs;
+ for (int i = gCount - 1; i >= 0; --i) {
+ if (pairs[i].fFactory == fact) {
+ return pairs[i].fName;
+ }
+ }
+ return NULL;
+}
diff --git a/core/SkFlattenableBuffers.cpp b/core/SkFlattenableBuffers.cpp
new file mode 100644
index 00000000..50a47d5c
--- /dev/null
+++ b/core/SkFlattenableBuffers.cpp
@@ -0,0 +1,56 @@
+
+/*
+ * 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 "SkFlattenableBuffers.h"
+#include "SkPaint.h"
+#include "SkTypeface.h"
+
+SkFlattenableReadBuffer::SkFlattenableReadBuffer() {
+ // Set default values. These should be explicitly set by our client
+ // via setFlags() if the buffer came from serialization.
+ fFlags = 0;
+#ifdef SK_SCALAR_IS_FLOAT
+ fFlags |= kScalarIsFloat_Flag;
+#endif
+ if (8 == sizeof(void*)) {
+ fFlags |= kPtrIs64Bit_Flag;
+ }
+}
+
+SkFlattenableReadBuffer::~SkFlattenableReadBuffer() { }
+
+void* SkFlattenableReadBuffer::readFunctionPtr() {
+ void* proc;
+ SkASSERT(sizeof(void*) == this->getArrayCount());
+ this->readByteArray(&proc);
+ return proc;
+}
+
+void SkFlattenableReadBuffer::readPaint(SkPaint* paint) {
+ paint->unflatten(*this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFlattenableWriteBuffer::SkFlattenableWriteBuffer() {
+ fFlags = (Flags)0;
+}
+
+SkFlattenableWriteBuffer::~SkFlattenableWriteBuffer() { }
+
+void SkFlattenableWriteBuffer::writeFunctionPtr(void* ptr) {
+ void* ptrStorage[] = { ptr };
+ this->writeByteArray(ptrStorage, sizeof(void*));
+}
+
+void SkFlattenableWriteBuffer::writePaint(const SkPaint& paint) {
+ paint.flatten(*this);
+}
+
+void SkFlattenableWriteBuffer::flattenObject(SkFlattenable* obj, SkFlattenableWriteBuffer& buffer) {
+ obj->flatten(buffer);
+}
diff --git a/core/SkFlattenableSerialization.cpp b/core/SkFlattenableSerialization.cpp
new file mode 100644
index 00000000..b74c82f0
--- /dev/null
+++ b/core/SkFlattenableSerialization.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFlattenableSerialization.h"
+
+#include "SkData.h"
+#include "SkFlattenable.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+
+SkData* SkSerializeFlattenable(SkFlattenable* flattenable) {
+ SkOrderedWriteBuffer writer(1024);
+ writer.setFlags(SkOrderedWriteBuffer::kCrossProcess_Flag);
+ writer.writeFlattenable(flattenable);
+ uint32_t size = writer.bytesWritten();
+ void* data = sk_malloc_throw(size);
+ writer.writeToMemory(data);
+ return SkData::NewFromMalloc(data, size);
+}
+
+SkFlattenable* SkDeserializeFlattenable(const void* data, size_t size) {
+ SkOrderedReadBuffer buffer(data, size);
+ return buffer.readFlattenable();
+}
diff --git a/core/SkFloat.cpp b/core/SkFloat.cpp
new file mode 100644
index 00000000..721d8ec5
--- /dev/null
+++ b/core/SkFloat.cpp
@@ -0,0 +1,393 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkFloat.h"
+#include "SkMathPriv.h"
+
+#define EXP_BIAS (127+23)
+
+static int get_unsigned_exp(uint32_t packed)
+{
+ return (packed << 1 >> 24);
+}
+
+static unsigned get_unsigned_value(uint32_t packed)
+{
+ return (packed << 9 >> 9) | (1 << 23);
+}
+
+static int get_signed_value(int32_t packed)
+{
+ return SkApplySign(get_unsigned_value(packed), SkExtractSign(packed));
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+int SkFloat::GetShift(int32_t packed, int shift)
+{
+ if (packed == 0)
+ return 0;
+
+ int exp = get_unsigned_exp(packed) - EXP_BIAS - shift;
+ int value = get_unsigned_value(packed);
+
+ if (exp >= 0)
+ {
+ if (exp > 8) // overflow
+ value = SK_MaxS32;
+ else
+ value <<= exp;
+ }
+ else
+ {
+ exp = -exp;
+ if (exp > 23) // underflow
+ value = 0;
+ else
+ value >>= exp;
+ }
+ return SkApplySign(value, SkExtractSign(packed));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+int32_t SkFloat::SetShift(int value, int shift)
+{
+ if (value == 0)
+ return 0;
+
+ // record the sign and make value positive
+ int sign = SkExtractSign(value);
+ value = SkApplySign(value, sign);
+
+ if (value >> 24) // value is too big (has more than 24 bits set)
+ {
+ int bias = 8 - SkCLZ(value);
+ SkASSERT(bias > 0 && bias < 8);
+ value >>= bias;
+ shift += bias;
+ }
+ else
+ {
+ int zeros = SkCLZ(value << 8);
+ SkASSERT(zeros >= 0 && zeros <= 23);
+ value <<= zeros;
+ shift -= zeros;
+ }
+ // now value is left-aligned to 24 bits
+ SkASSERT((value >> 23) == 1);
+
+ shift += EXP_BIAS;
+ if (shift < 0) // underflow
+ return 0;
+ else
+ {
+ if (shift > 255) // overflow
+ {
+ shift = 255;
+ value = 0x00FFFFFF;
+ }
+ int32_t packed = sign << 31; // set the sign-bit
+ packed |= shift << 23; // store the packed exponent
+ packed |= ((unsigned)(value << 9) >> 9); // clear 24th bit of value (its implied)
+
+#ifdef SK_DEBUG
+ {
+ int n;
+
+ n = SkExtractSign(packed);
+ SkASSERT(n == sign);
+ n = get_unsigned_exp(packed);
+ SkASSERT(n == shift);
+ n = get_unsigned_value(packed);
+ SkASSERT(n == value);
+ }
+#endif
+ return packed;
+ }
+}
+
+int32_t SkFloat::Neg(int32_t packed)
+{
+ if (packed)
+ packed = packed ^ (1 << 31);
+ return packed;
+}
+
+int32_t SkFloat::Add(int32_t packed_a, int32_t packed_b)
+{
+ if (packed_a == 0)
+ return packed_b;
+ if (packed_b == 0)
+ return packed_a;
+
+ int exp_a = get_unsigned_exp(packed_a);
+ int exp_b = get_unsigned_exp(packed_b);
+ int exp_diff = exp_a - exp_b;
+
+ int shift_a = 0, shift_b = 0;
+ int exp;
+
+ if (exp_diff >= 0)
+ {
+ if (exp_diff > 24) // B is too small to contribute
+ return packed_a;
+ shift_b = exp_diff;
+ exp = exp_a;
+ }
+ else
+ {
+ exp_diff = -exp_diff;
+ if (exp_diff > 24) // A is too small to contribute
+ return packed_b;
+ shift_a = exp_diff;
+ exp = exp_b;
+ }
+
+ int value_a = get_signed_value(packed_a) >> shift_a;
+ int value_b = get_signed_value(packed_b) >> shift_b;
+
+ return SkFloat::SetShift(value_a + value_b, exp - EXP_BIAS);
+}
+
+#include "Sk64.h"
+
+static inline int32_t mul24(int32_t a, int32_t b)
+{
+ Sk64 tmp;
+
+ tmp.setMul(a, b);
+ tmp.roundRight(24);
+ return tmp.get32();
+}
+
+int32_t SkFloat::Mul(int32_t packed_a, int32_t packed_b)
+{
+ if (packed_a == 0 || packed_b == 0)
+ return 0;
+
+ int exp_a = get_unsigned_exp(packed_a);
+ int exp_b = get_unsigned_exp(packed_b);
+
+ int value_a = get_signed_value(packed_a);
+ int value_b = get_signed_value(packed_b);
+
+ return SkFloat::SetShift(mul24(value_a, value_b), exp_a + exp_b - 2*EXP_BIAS + 24);
+}
+
+int32_t SkFloat::MulInt(int32_t packed, int n)
+{
+ return Mul(packed, SetShift(n, 0));
+}
+
+int32_t SkFloat::Div(int32_t packed_n, int32_t packed_d)
+{
+ SkASSERT(packed_d != 0);
+
+ if (packed_n == 0)
+ return 0;
+
+ int exp_n = get_unsigned_exp(packed_n);
+ int exp_d = get_unsigned_exp(packed_d);
+
+ int value_n = get_signed_value(packed_n);
+ int value_d = get_signed_value(packed_d);
+
+ return SkFloat::SetShift(SkDivBits(value_n, value_d, 24), exp_n - exp_d - 24);
+}
+
+int32_t SkFloat::DivInt(int32_t packed, int n)
+{
+ return Div(packed, SetShift(n, 0));
+}
+
+int32_t SkFloat::Invert(int32_t packed)
+{
+ return Div(packed, SetShift(1, 0));
+}
+
+int32_t SkFloat::Sqrt(int32_t packed)
+{
+ if (packed < 0)
+ {
+ SkDEBUGFAIL("can't sqrt a negative number");
+ return 0;
+ }
+
+ int exp = get_unsigned_exp(packed);
+ int value = get_unsigned_value(packed);
+
+ int nexp = exp - EXP_BIAS;
+ int root = SkSqrtBits(value << (nexp & 1), 26);
+ nexp >>= 1;
+ return SkFloat::SetShift(root, nexp - 11);
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable warning : unreachable code
+#pragma warning ( push )
+#pragma warning ( disable : 4702 )
+#endif
+
+int32_t SkFloat::CubeRoot(int32_t packed)
+{
+ sk_throw();
+ return 0;
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+static inline int32_t clear_high_bit(int32_t n)
+{
+ return ((uint32_t)(n << 1)) >> 1;
+}
+
+static inline int int_sign(int32_t a, int32_t b)
+{
+ return a > b ? 1 : (a < b ? -1 : 0);
+}
+
+int SkFloat::Cmp(int32_t packed_a, int32_t packed_b)
+{
+ packed_a = SkApplySign(clear_high_bit(packed_a), SkExtractSign(packed_a));
+ packed_b = SkApplySign(clear_high_bit(packed_b), SkExtractSign(packed_b));
+
+ return int_sign(packed_a, packed_b);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+#include "SkRandom.h"
+#include "SkFloatingPoint.h"
+
+void SkFloat::UnitTest()
+{
+#ifdef SK_SUPPORT_UNITTEST
+ SkFloat a, b, c, d;
+ int n;
+
+ a.setZero();
+ n = a.getInt();
+ SkASSERT(n == 0);
+
+ b.setInt(5);
+ n = b.getInt();
+ SkASSERT(n == 5);
+
+ c.setInt(-3);
+ n = c.getInt();
+ SkASSERT(n == -3);
+
+ d.setAdd(c, b);
+ SkDebugf("SkFloat: %d + %d = %d\n", c.getInt(), b.getInt(), d.getInt());
+
+ SkMWCRandom rand;
+
+ int i;
+ for (i = 0; i < 1000; i++)
+ {
+ float fa, fb;
+ int aa = rand.nextS() >> 14;
+ int bb = rand.nextS() >> 14;
+ a.setInt(aa);
+ b.setInt(bb);
+ SkASSERT(a.getInt() == aa);
+ SkASSERT(b.getInt() == bb);
+
+ c.setAdd(a, b);
+ int cc = c.getInt();
+ SkASSERT(cc == aa + bb);
+
+ c.setSub(a, b);
+ cc = c.getInt();
+ SkASSERT(cc == aa - bb);
+
+ aa >>= 5;
+ bb >>= 5;
+ a.setInt(aa);
+ b.setInt(bb);
+ c.setMul(a, b);
+ cc = c.getInt();
+ SkASSERT(cc == aa * bb);
+ /////////////////////////////////////
+
+ aa = rand.nextS() >> 11;
+ a.setFixed(aa);
+ cc = a.getFixed();
+ SkASSERT(aa == cc);
+
+ bb = rand.nextS() >> 11;
+ b.setFixed(bb);
+ cc = b.getFixed();
+ SkASSERT(bb == cc);
+
+ cc = SkFixedMul(aa, bb);
+ c.setMul(a, b);
+ SkFixed dd = c.getFixed();
+ int diff = cc - dd;
+ SkASSERT(SkAbs32(diff) <= 1);
+
+ fa = (float)aa / 65536.0f;
+ fb = (float)bb / 65536.0f;
+ a.assertEquals(fa);
+ b.assertEquals(fb);
+ fa = a.getFloat();
+ fb = b.getFloat();
+
+ c.assertEquals(fa * fb, 1);
+
+ c.setDiv(a, b);
+ cc = SkFixedDiv(aa, bb);
+ dd = c.getFixed();
+ diff = cc - dd;
+ SkASSERT(SkAbs32(diff) <= 3);
+
+ c.assertEquals(fa / fb, 1);
+
+ SkASSERT((aa == bb) == (a == b));
+ SkASSERT((aa != bb) == (a != b));
+ SkASSERT((aa < bb) == (a < b));
+ SkASSERT((aa <= bb) == (a <= b));
+ SkASSERT((aa > bb) == (a > b));
+ SkASSERT((aa >= bb) == (a >= b));
+
+ if (aa < 0)
+ {
+ aa = -aa;
+ fa = -fa;
+ }
+ a.setFixed(aa);
+ c.setSqrt(a);
+ cc = SkFixedSqrt(aa);
+ dd = c.getFixed();
+ SkASSERT(dd == cc);
+
+ c.assertEquals(sk_float_sqrt(fa), 2);
+
+ // cuberoot
+#if 0
+ a.setInt(1);
+ a.cubeRoot();
+ a.assertEquals(1.0f, 0);
+ a.setInt(8);
+ a.cubeRoot();
+ a.assertEquals(2.0f, 0);
+ a.setInt(27);
+ a.cubeRoot();
+ a.assertEquals(3.0f, 0);
+#endif
+ }
+#endif
+}
+
+#endif
diff --git a/core/SkFloat.h b/core/SkFloat.h
new file mode 100644
index 00000000..74cd19e5
--- /dev/null
+++ b/core/SkFloat.h
@@ -0,0 +1,109 @@
+
+/*
+ * 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 SkFloat_DEFINED
+#define SkFloat_DEFINED
+
+#include "SkFixed.h"
+
+class SkFloat {
+public:
+ SkFloat() {}
+
+ void setZero() { fPacked = 0; }
+// void setShift(int value, int shift) { fPacked = SetShift(value, shift); }
+ void setInt(int value) { fPacked = SetShift(value, 0); }
+ void setFixed(SkFixed value) { fPacked = SetShift(value, -16); }
+ void setFract(SkFract value) { fPacked = SetShift(value, -30); }
+
+// int getShift(int shift) const { return GetShift(fPacked, shift); }
+ int getInt() const { return GetShift(fPacked, 0); }
+ SkFixed getFixed() const { return GetShift(fPacked, -16); }
+ SkFract getFract() const { return GetShift(fPacked, -30); }
+
+ void abs() { fPacked = Abs(fPacked); }
+ void negate() { fPacked = Neg(fPacked); }
+
+ void shiftLeft(int bits) { fPacked = Shift(fPacked, bits); }
+ void setShiftLeft(const SkFloat& a, int bits) { fPacked = Shift(a.fPacked, bits); }
+
+ void shiftRight(int bits) { fPacked = Shift(fPacked, -bits); }
+ void setShiftRight(const SkFloat& a, int bits) { fPacked = Shift(a.fPacked, -bits); }
+
+ void add(const SkFloat& a) { fPacked = Add(fPacked, a.fPacked); }
+ void setAdd(const SkFloat& a, const SkFloat& b) { fPacked = Add(a.fPacked, b.fPacked); }
+
+ void sub(const SkFloat& a) { fPacked = Add(fPacked, Neg(a.fPacked)); }
+ void setSub(const SkFloat& a, const SkFloat& b) { fPacked = Add(a.fPacked, Neg(b.fPacked)); }
+
+ void mul(const SkFloat& a) { fPacked = Mul(fPacked, a.fPacked); }
+ void setMul(const SkFloat& a, const SkFloat& b) { fPacked = Mul(a.fPacked, b.fPacked); }
+
+ void div(const SkFloat& a) { fPacked = Div(fPacked, a.fPacked); }
+ void setDiv(const SkFloat& a, const SkFloat& b) { fPacked = Div(a.fPacked, b.fPacked); }
+
+ void sqrt() { fPacked = Sqrt(fPacked); }
+ void setSqrt(const SkFloat& a) { fPacked = Sqrt(a.fPacked); }
+ void cubeRoot() { fPacked = CubeRoot(fPacked); }
+ void setCubeRoot(const SkFloat& a) { fPacked = CubeRoot(a.fPacked); }
+
+ friend bool operator==(const SkFloat& a, const SkFloat& b) { return a.fPacked == b.fPacked; }
+ friend bool operator!=(const SkFloat& a, const SkFloat& b) { return a.fPacked != b.fPacked; }
+ friend bool operator<(const SkFloat& a, const SkFloat& b) { return Cmp(a.fPacked, b.fPacked) < 0; }
+ friend bool operator<=(const SkFloat& a, const SkFloat& b) { return Cmp(a.fPacked, b.fPacked) <= 0; }
+ friend bool operator>(const SkFloat& a, const SkFloat& b) { return Cmp(a.fPacked, b.fPacked) > 0; }
+ friend bool operator>=(const SkFloat& a, const SkFloat& b) { return Cmp(a.fPacked, b.fPacked) >= 0; }
+
+#ifdef SK_DEBUG
+ static void UnitTest();
+
+ void assertEquals(float f, int tolerance = 0)
+ {
+ union {
+ float fFloat;
+ int32_t fPacked;
+ } tmp;
+
+ tmp.fFloat = f;
+ int d = tmp.fPacked - fPacked;
+ SkASSERT(SkAbs32(d) <= tolerance);
+ }
+ float getFloat() const
+ {
+ union {
+ float fFloat;
+ int32_t fPacked;
+ } tmp;
+
+ tmp.fPacked = fPacked;
+ return tmp.fFloat;
+ }
+#endif
+
+private:
+ int32_t fPacked;
+
+public:
+ static int GetShift(int32_t packed, int shift);
+ static int32_t SetShift(int value, int shift);
+ static int32_t Neg(int32_t);
+ static int32_t Abs(int32_t packed) { return (uint32_t)(packed << 1) >> 1; }
+ static int32_t Shift(int32_t, int bits);
+ static int32_t Add(int32_t, int32_t);
+ static int32_t Mul(int32_t, int32_t);
+ static int32_t MulInt(int32_t, int);
+ static int32_t Div(int32_t, int32_t);
+ static int32_t DivInt(int32_t, int);
+ static int32_t Invert(int32_t);
+ static int32_t Sqrt(int32_t);
+ static int32_t CubeRoot(int32_t);
+ static int Cmp(int32_t, int32_t);
+};
+
+#endif
diff --git a/core/SkFloatBits.cpp b/core/SkFloatBits.cpp
new file mode 100644
index 00000000..39b51abf
--- /dev/null
+++ b/core/SkFloatBits.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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 "SkFloatBits.h"
+#include "SkMathPriv.h"
+
+/******************************************************************************
+ SkFloatBits_toInt[Floor, Round, Ceil] are identical except for what they
+ do right before they return ... >> exp;
+ Floor - adds nothing
+ Round - adds 1 << (exp - 1)
+ Ceil - adds (1 << exp) - 1
+
+ Floor and Cast are very similar, but Cast applies its sign after all other
+ computations on value. Also, Cast does not need to check for negative zero,
+ as that value (0x80000000) "does the right thing" for Ceil. Note that it
+ doesn't for Floor/Round/Ceil, hence the explicit check.
+******************************************************************************/
+
+#define EXP_BIAS (127+23)
+#define MATISSA_MAGIC_BIG (1 << 23)
+
+static inline int unpack_exp(uint32_t packed) {
+ return (packed << 1 >> 24);
+}
+
+#if 0
+// the ARM compiler generates an extra BIC, so I use the dirty version instead
+static inline int unpack_matissa(uint32_t packed) {
+ // we could mask with 0x7FFFFF, but that is harder for ARM to encode
+ return (packed & ~0xFF000000) | MATISSA_MAGIC_BIG;
+}
+#endif
+
+// returns the low 24-bits, so we need to OR in the magic_bit afterwards
+static inline int unpack_matissa_dirty(uint32_t packed) {
+ return packed & ~0xFF000000;
+}
+
+// same as (int)float
+int32_t SkFloatBits_toIntCast(int32_t packed) {
+ int exp = unpack_exp(packed) - EXP_BIAS;
+ int value = unpack_matissa_dirty(packed) | MATISSA_MAGIC_BIG;
+
+ if (exp >= 0) {
+ if (exp > 7) { // overflow
+ value = SK_MaxS32;
+ } else {
+ value <<= exp;
+ }
+ } else {
+ exp = -exp;
+ if (exp > 25) { // underflow
+ exp = 25;
+ }
+ value >>= exp;
+ }
+ return SkApplySign(value, SkExtractSign(packed));
+}
+
+// same as (int)floor(float)
+int32_t SkFloatBits_toIntFloor(int32_t packed) {
+ // curse you negative 0
+ if ((packed << 1) == 0) {
+ return 0;
+ }
+
+ int exp = unpack_exp(packed) - EXP_BIAS;
+ int value = unpack_matissa_dirty(packed) | MATISSA_MAGIC_BIG;
+
+ if (exp >= 0) {
+ if (exp > 7) { // overflow
+ value = SK_MaxS32;
+ } else {
+ value <<= exp;
+ }
+ // apply the sign after we check for overflow
+ return SkApplySign(value, SkExtractSign(packed));
+ } else {
+ // apply the sign before we right-shift
+ value = SkApplySign(value, SkExtractSign(packed));
+ exp = -exp;
+ if (exp > 25) { // underflow
+ exp = 25;
+ }
+ // int add = 0;
+ return value >> exp;
+ }
+}
+
+// same as (int)floor(float + 0.5)
+int32_t SkFloatBits_toIntRound(int32_t packed) {
+ // curse you negative 0
+ if ((packed << 1) == 0) {
+ return 0;
+ }
+
+ int exp = unpack_exp(packed) - EXP_BIAS;
+ int value = unpack_matissa_dirty(packed) | MATISSA_MAGIC_BIG;
+
+ if (exp >= 0) {
+ if (exp > 7) { // overflow
+ value = SK_MaxS32;
+ } else {
+ value <<= exp;
+ }
+ // apply the sign after we check for overflow
+ return SkApplySign(value, SkExtractSign(packed));
+ } else {
+ // apply the sign before we right-shift
+ value = SkApplySign(value, SkExtractSign(packed));
+ exp = -exp;
+ if (exp > 25) { // underflow
+ exp = 25;
+ }
+ int add = 1 << (exp - 1);
+ return (value + add) >> exp;
+ }
+}
+
+// same as (int)ceil(float)
+int32_t SkFloatBits_toIntCeil(int32_t packed) {
+ // curse you negative 0
+ if ((packed << 1) == 0) {
+ return 0;
+ }
+
+ int exp = unpack_exp(packed) - EXP_BIAS;
+ int value = unpack_matissa_dirty(packed) | MATISSA_MAGIC_BIG;
+
+ if (exp >= 0) {
+ if (exp > 7) { // overflow
+ value = SK_MaxS32;
+ } else {
+ value <<= exp;
+ }
+ // apply the sign after we check for overflow
+ return SkApplySign(value, SkExtractSign(packed));
+ } else {
+ // apply the sign before we right-shift
+ value = SkApplySign(value, SkExtractSign(packed));
+ exp = -exp;
+ if (exp > 25) { // underflow
+ exp = 25;
+ }
+ int add = (1 << exp) - 1;
+ return (value + add) >> exp;
+ }
+}
+
+float SkIntToFloatCast(int32_t value) {
+ if (0 == value) {
+ return 0;
+ }
+
+ int shift = EXP_BIAS;
+
+ // record the sign and make value positive
+ int sign = SkExtractSign(value);
+ value = SkApplySign(value, sign);
+
+ if (value >> 24) { // value is too big (has more than 24 bits set)
+ int bias = 8 - SkCLZ(value);
+ SkDebugf("value = %d, bias = %d\n", value, bias);
+ SkASSERT(bias > 0 && bias < 8);
+ value >>= bias; // need to round?
+ shift += bias;
+ } else {
+ int zeros = SkCLZ(value << 8);
+ SkASSERT(zeros >= 0 && zeros <= 23);
+ value <<= zeros;
+ shift -= zeros;
+ }
+
+ // now value is left-aligned to 24 bits
+ SkASSERT((value >> 23) == 1);
+ SkASSERT(shift >= 0 && shift <= 255);
+
+ SkFloatIntUnion data;
+ data.fSignBitInt = (sign << 31) | (shift << 23) | (value & ~MATISSA_MAGIC_BIG);
+ return data.fFloat;
+}
+
+float SkIntToFloatCast_NoOverflowCheck(int32_t value) {
+ if (0 == value) {
+ return 0;
+ }
+
+ int shift = EXP_BIAS;
+
+ // record the sign and make value positive
+ int sign = SkExtractSign(value);
+ value = SkApplySign(value, sign);
+
+ int zeros = SkCLZ(value << 8);
+ value <<= zeros;
+ shift -= zeros;
+
+ SkFloatIntUnion data;
+ data.fSignBitInt = (sign << 31) | (shift << 23) | (value & ~MATISSA_MAGIC_BIG);
+ return data.fFloat;
+}
diff --git a/core/SkFontDescriptor.cpp b/core/SkFontDescriptor.cpp
new file mode 100644
index 00000000..7679d92a
--- /dev/null
+++ b/core/SkFontDescriptor.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 "SkFontDescriptor.h"
+#include "SkStream.h"
+
+enum {
+ // these must match the sfnt 'name' enums
+ kFontFamilyName = 0x01,
+ kFullName = 0x04,
+ kPostscriptName = 0x06,
+
+ // These count backwards from 0xFF, so as not to collide with the SFNT
+ // defines for names in its 'name' table.
+ kFontFileName = 0xFE,
+ kSentinel = 0xFF,
+};
+
+SkFontDescriptor::SkFontDescriptor(SkTypeface::Style style) {
+ fStyle = style;
+}
+
+static void read_string(SkStream* stream, SkString* string) {
+ const uint32_t length = stream->readPackedUInt();
+ if (length > 0) {
+ string->resize(length);
+ stream->read(string->writable_str(), length);
+ }
+}
+
+static void write_string(SkWStream* stream, const SkString& string,
+ uint32_t id) {
+ if (!string.isEmpty()) {
+ stream->writePackedUInt(id);
+ stream->writePackedUInt(string.size());
+ stream->write(string.c_str(), string.size());
+ }
+}
+
+SkFontDescriptor::SkFontDescriptor(SkStream* stream) {
+ fStyle = (SkTypeface::Style)stream->readPackedUInt();
+
+ for (;;) {
+ switch (stream->readPackedUInt()) {
+ case kFontFamilyName:
+ read_string(stream, &fFamilyName);
+ break;
+ case kFullName:
+ read_string(stream, &fFullName);
+ break;
+ case kPostscriptName:
+ read_string(stream, &fPostscriptName);
+ break;
+ case kFontFileName:
+ read_string(stream, &fFontFileName);
+ break;
+ case kSentinel:
+ return;
+ default:
+ SkDEBUGFAIL("Unknown id used by a font descriptor");
+ return;
+ }
+ }
+}
+
+void SkFontDescriptor::serialize(SkWStream* stream) {
+ stream->writePackedUInt(fStyle);
+
+ write_string(stream, fFamilyName, kFontFamilyName);
+ write_string(stream, fFullName, kFullName);
+ write_string(stream, fPostscriptName, kPostscriptName);
+ write_string(stream, fFontFileName, kFontFileName);
+
+ stream->writePackedUInt(kSentinel);
+}
diff --git a/core/SkFontDescriptor.h b/core/SkFontDescriptor.h
new file mode 100644
index 00000000..5febfd81
--- /dev/null
+++ b/core/SkFontDescriptor.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 SkFontDescriptor_DEFINED
+#define SkFontDescriptor_DEFINED
+
+#include "SkString.h"
+#include "SkTypeface.h"
+
+class SkStream;
+class SkWStream;
+
+class SkFontDescriptor {
+public:
+ SkFontDescriptor(SkTypeface::Style = SkTypeface::kNormal);
+ SkFontDescriptor(SkStream*);
+
+ void serialize(SkWStream*);
+
+ SkTypeface::Style getStyle() { return fStyle; }
+ void setStyle(SkTypeface::Style style) { fStyle = style; }
+
+ const char* getFamilyName() { return fFamilyName.c_str(); }
+ const char* getFullName() { return fFullName.c_str(); }
+ const char* getPostscriptName() { return fPostscriptName.c_str(); }
+ const char* getFontFileName() { return fFontFileName.c_str(); }
+
+ void setFamilyName(const char* name) { fFamilyName.set(name); }
+ void setFullName(const char* name) { fFullName.set(name); }
+ void setPostscriptName(const char* name) { fPostscriptName.set(name); }
+ void setFontFileName(const char* name) { fFontFileName.set(name); }
+
+private:
+ SkString fFamilyName;
+ SkString fFullName;
+ SkString fPostscriptName;
+ SkString fFontFileName;
+
+ SkTypeface::Style fStyle;
+};
+
+#endif // SkFontDescriptor_DEFINED
diff --git a/core/SkFontHost.cpp b/core/SkFontHost.cpp
new file mode 100644
index 00000000..799b95b5
--- /dev/null
+++ b/core/SkFontHost.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2009 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 "SkFontLCDConfig.h"
+
+static SkFontLCDConfig::LCDOrientation gLCDOrientation = SkFontLCDConfig::kHorizontal_LCDOrientation;
+static SkFontLCDConfig::LCDOrder gLCDOrder = SkFontLCDConfig::kRGB_LCDOrder;
+
+SkFontLCDConfig::LCDOrientation SkFontLCDConfig::GetSubpixelOrientation() {
+ return gLCDOrientation;
+}
+
+void SkFontLCDConfig::SetSubpixelOrientation(LCDOrientation orientation) {
+ gLCDOrientation = orientation;
+}
+
+SkFontLCDConfig::LCDOrder SkFontLCDConfig::GetSubpixelOrder() {
+ return gLCDOrder;
+}
+
+void SkFontLCDConfig::SetSubpixelOrder(LCDOrder order) {
+ gLCDOrder = order;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Legacy wrappers : remove from SkFontHost when webkit switches to new API
+
+#include "SkFontHost.h"
+
+SkFontHost::LCDOrientation SkFontHost::GetSubpixelOrientation() {
+ return (SkFontHost::LCDOrientation)SkFontLCDConfig::GetSubpixelOrientation();
+}
+
+void SkFontHost::SetSubpixelOrientation(LCDOrientation orientation) {
+ SkFontLCDConfig::SetSubpixelOrientation((SkFontLCDConfig::LCDOrientation)orientation);
+}
+
+SkFontHost::LCDOrder SkFontHost::GetSubpixelOrder() {
+ return (SkFontHost::LCDOrder)SkFontLCDConfig::GetSubpixelOrder();
+}
+
+void SkFontHost::SetSubpixelOrder(LCDOrder order) {
+ SkFontLCDConfig::SetSubpixelOrder((SkFontLCDConfig::LCDOrder)order);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontStyle.h"
+
+SkFontStyle::SkFontStyle() {
+ fUnion.fU32 = 0;
+ fUnion.fR.fWeight = kNormal_Weight;
+ fUnion.fR.fWidth = kNormal_Width;
+ fUnion.fR.fSlant = kUpright_Slant;
+}
+
+SkFontStyle::SkFontStyle(int weight, int width, Slant slant) {
+ fUnion.fU32 = 0;
+ fUnion.fR.fWeight = SkPin32(weight, kThin_Weight, kBlack_Weight);
+ fUnion.fR.fWidth = SkPin32(width, kUltraCondensed_Width, kUltaExpanded_Width);
+ fUnion.fR.fSlant = SkPin32(slant, kUpright_Slant, kItalic_Slant);
+}
+
+#include "SkFontMgr.h"
+
+SK_DEFINE_INST_COUNT(SkFontStyleSet)
+
+class SkEmptyFontStyleSet : public SkFontStyleSet {
+public:
+ virtual int count() SK_OVERRIDE { return 0; }
+ virtual void getStyle(int, SkFontStyle*, SkString*) SK_OVERRIDE {
+ SkASSERT(!"SkFontStyleSet::getStyle called on empty set");
+ }
+ virtual SkTypeface* createTypeface(int index) SK_OVERRIDE {
+ SkASSERT(!"SkFontStyleSet::createTypeface called on empty set");
+ return NULL;
+ }
+ virtual SkTypeface* matchStyle(const SkFontStyle&) SK_OVERRIDE {
+ return NULL;
+ }
+};
+
+SkFontStyleSet* SkFontStyleSet::CreateEmpty() {
+ return SkNEW(SkEmptyFontStyleSet);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_INST_COUNT(SkFontMgr)
+
+class SkEmptyFontMgr : public SkFontMgr {
+protected:
+ virtual int onCountFamilies() SK_OVERRIDE {
+ return 0;
+ }
+ virtual void onGetFamilyName(int index, SkString* familyName) SK_OVERRIDE {
+ SkASSERT(!"onGetFamilyName called with bad index");
+ }
+ virtual SkFontStyleSet* onCreateStyleSet(int index) SK_OVERRIDE {
+ SkASSERT(!"onCreateStyleSet called with bad index");
+ return NULL;
+ }
+ virtual SkFontStyleSet* onMatchFamily(const char[]) SK_OVERRIDE {
+ return SkFontStyleSet::CreateEmpty();
+ }
+
+ virtual SkTypeface* onMatchFamilyStyle(const char[],
+ const SkFontStyle&) SK_OVERRIDE {
+ return NULL;
+ }
+ virtual SkTypeface* onMatchFaceStyle(const SkTypeface*,
+ const SkFontStyle&) SK_OVERRIDE {
+ return NULL;
+ }
+ virtual SkTypeface* onCreateFromData(SkData*, int) SK_OVERRIDE {
+ return NULL;
+ }
+ virtual SkTypeface* onCreateFromStream(SkStream*, int) SK_OVERRIDE {
+ return NULL;
+ }
+ virtual SkTypeface* onCreateFromFile(const char[], int) SK_OVERRIDE {
+ return NULL;
+ }
+};
+
+static SkFontStyleSet* emptyOnNull(SkFontStyleSet* fsset) {
+ if (NULL == fsset) {
+ fsset = SkFontStyleSet::CreateEmpty();
+ }
+ return fsset;
+}
+
+int SkFontMgr::countFamilies() {
+ return this->onCountFamilies();
+}
+
+void SkFontMgr::getFamilyName(int index, SkString* familyName) {
+ this->onGetFamilyName(index, familyName);
+}
+
+SkFontStyleSet* SkFontMgr::createStyleSet(int index) {
+ return emptyOnNull(this->onCreateStyleSet(index));
+}
+
+SkFontStyleSet* SkFontMgr::matchFamily(const char familyName[]) {
+ return emptyOnNull(this->onMatchFamily(familyName));
+}
+
+SkTypeface* SkFontMgr::matchFamilyStyle(const char familyName[],
+ const SkFontStyle& fs) {
+ return this->onMatchFamilyStyle(familyName, fs);
+}
+
+SkTypeface* SkFontMgr::matchFaceStyle(const SkTypeface* face,
+ const SkFontStyle& fs) {
+ return this->onMatchFaceStyle(face, fs);
+}
+
+SkTypeface* SkFontMgr::createFromData(SkData* data, int ttcIndex) {
+ if (NULL == data) {
+ return NULL;
+ }
+ return this->onCreateFromData(data, ttcIndex);
+}
+
+SkTypeface* SkFontMgr::createFromStream(SkStream* stream, int ttcIndex) {
+ if (NULL == stream) {
+ return NULL;
+ }
+ return this->onCreateFromStream(stream, ttcIndex);
+}
+
+SkTypeface* SkFontMgr::createFromFile(const char path[], int ttcIndex) {
+ if (NULL == path) {
+ return NULL;
+ }
+ return this->onCreateFromFile(path, ttcIndex);
+}
+
+SkTypeface* SkFontMgr::legacyCreateTypeface(const char familyName[],
+ unsigned styleBits) {
+ return this->onLegacyCreateTypeface(familyName, styleBits);
+}
+
+SkTypeface* SkFontMgr::onLegacyCreateTypeface(const char familyName[],
+ unsigned styleBits) {
+ SkASSERT(!"unimplemented");
+ return NULL;
+}
+
+SkFontMgr* SkFontMgr::RefDefault() {
+ static SkFontMgr* gFM;
+ if (NULL == gFM) {
+ gFM = SkFontMgr::Factory();
+ // we never want to return NULL
+ if (NULL == gFM) {
+ gFM = SkNEW(SkEmptyFontMgr);
+ }
+ }
+ return SkRef(gFM);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_FONTHOST_USES_FONTMGR
+
+#if 0
+static SkFontStyle TypefaceStyleBitsToFontStyle(SkTypeface::Style styleBits) {
+ SkFontStyle::Weight weight = (styleBits & SkTypeface::kBold) ?
+ SkFontStyle::kBold_Weight :
+ SkFontStyle::kNormal_Weight;
+ SkFontStyle::Width width = SkFontStyle::kNormal_Width;
+ SkFontStyle::Slant slant = (styleBits & SkTypeface::kItalic) ?
+ SkFontStyle::kUpright_Slant :
+ SkFontStyle::kItalic_Slant;
+ return SkFontStyle(weight, width, slant);
+}
+#endif
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ if (familyFace) {
+ return familyFace->refMatchingStyle(style);
+ } else {
+ SkAutoTUnref<SkFontMgr> fm(SkFontMgr::RefDefault());
+ return fm->legacyCreateTypeface(familyName, style);
+ }
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ SkAutoTUnref<SkFontMgr> fm(SkFontMgr::RefDefault());
+ return fm->createFromFile(path);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ SkAutoTUnref<SkFontMgr> fm(SkFontMgr::RefDefault());
+ return fm->createFromStream(stream);
+}
+
+#endif
diff --git a/core/SkFontStream.cpp b/core/SkFontStream.cpp
new file mode 100644
index 00000000..580da37f
--- /dev/null
+++ b/core/SkFontStream.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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 "SkEndian.h"
+#include "SkFontStream.h"
+#include "SkStream.h"
+
+struct SkSFNTHeader {
+ uint32_t fVersion;
+ uint16_t fNumTables;
+ uint16_t fSearchRange;
+ uint16_t fEntrySelector;
+ uint16_t fRangeShift;
+};
+
+struct SkTTCFHeader {
+ uint32_t fTag;
+ uint32_t fVersion;
+ uint32_t fNumOffsets;
+ uint32_t fOffset0; // the first of N (fNumOffsets)
+};
+
+union SkSharedTTHeader {
+ SkSFNTHeader fSingle;
+ SkTTCFHeader fCollection;
+};
+
+struct SkSFNTDirEntry {
+ uint32_t fTag;
+ uint32_t fChecksum;
+ uint32_t fOffset;
+ uint32_t fLength;
+};
+
+static bool read(SkStream* stream, void* buffer, size_t amount) {
+ return stream->read(buffer, amount) == amount;
+}
+
+static bool skip(SkStream* stream, size_t amount) {
+ return stream->skip(amount) == amount;
+}
+
+/** Return the number of tables, or if this is a TTC (collection), return the
+ number of tables in the first element of the collection. In either case,
+ if offsetToDir is not-null, set it to the offset to the beginning of the
+ table headers (SkSFNTDirEntry), relative to the start of the stream.
+
+ On an error, return 0 for number of tables, and ignore offsetToDir
+ */
+static int count_tables(SkStream* stream, int ttcIndex, size_t* offsetToDir) {
+ SkASSERT(ttcIndex >= 0);
+
+ SkAutoSMalloc<1024> storage(sizeof(SkSharedTTHeader));
+ SkSharedTTHeader* header = (SkSharedTTHeader*)storage.get();
+
+ if (!read(stream, header, sizeof(SkSharedTTHeader))) {
+ return 0;
+ }
+
+ // by default, SkSFNTHeader is at the start of the stream
+ size_t offset = 0;
+
+ // if we're really a collection, the first 4-bytes will be 'ttcf'
+ uint32_t tag = SkEndian_SwapBE32(header->fCollection.fTag);
+ if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) {
+ unsigned count = SkEndian_SwapBE32(header->fCollection.fNumOffsets);
+ if ((unsigned)ttcIndex >= count) {
+ return 0;
+ }
+
+ if (ttcIndex > 0) { // need to read more of the shared header
+ stream->rewind();
+ size_t amount = sizeof(SkSharedTTHeader) + ttcIndex * sizeof(uint32_t);
+ header = (SkSharedTTHeader*)storage.reset(amount);
+ if (!read(stream, header, amount)) {
+ return 0;
+ }
+ }
+ // this is the offset to the local SkSFNTHeader
+ offset = SkEndian_SwapBE32((&header->fCollection.fOffset0)[ttcIndex]);
+ stream->rewind();
+ if (!skip(stream, offset)) {
+ return 0;
+ }
+ if (!read(stream, header, sizeof(SkSFNTHeader))) {
+ return 0;
+ }
+ }
+
+ if (offsetToDir) {
+ // add the size of the header, so we will point to the DirEntries
+ *offsetToDir = offset + sizeof(SkSFNTHeader);
+ }
+ return SkEndian_SwapBE16(header->fSingle.fNumTables);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct SfntHeader {
+ SfntHeader() : fCount(0), fDir(NULL) {}
+ ~SfntHeader() { sk_free(fDir); }
+
+ /** If it returns true, then fCount and fDir are properly initialized.
+ Note: fDir will point to the raw array of SkSFNTDirEntry values,
+ meaning they will still be in the file's native endianness (BE).
+
+ fDir will be automatically freed when this object is destroyed
+ */
+ bool init(SkStream* stream, int ttcIndex) {
+ stream->rewind();
+
+ size_t offsetToDir;
+ fCount = count_tables(stream, ttcIndex, &offsetToDir);
+ if (0 == fCount) {
+ return false;
+ }
+
+ stream->rewind();
+ if (!skip(stream, offsetToDir)) {
+ return false;
+ }
+
+ size_t size = fCount * sizeof(SkSFNTDirEntry);
+ fDir = reinterpret_cast<SkSFNTDirEntry*>(sk_malloc_throw(size));
+ return read(stream, fDir, size);
+ }
+
+ int fCount;
+ SkSFNTDirEntry* fDir;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkFontStream::CountTTCEntries(SkStream* stream) {
+ stream->rewind();
+
+ SkSharedTTHeader shared;
+ if (!read(stream, &shared, sizeof(shared))) {
+ return 0;
+ }
+
+ // if we're really a collection, the first 4-bytes will be 'ttcf'
+ uint32_t tag = SkEndian_SwapBE32(shared.fCollection.fTag);
+ if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) {
+ return SkEndian_SwapBE32(shared.fCollection.fNumOffsets);
+ } else {
+ return 1; // normal 'sfnt' has 1 dir entry
+ }
+}
+
+int SkFontStream::GetTableTags(SkStream* stream, int ttcIndex,
+ SkFontTableTag tags[]) {
+ SfntHeader header;
+ if (!header.init(stream, ttcIndex)) {
+ return 0;
+ }
+
+ if (tags) {
+ for (int i = 0; i < header.fCount; i++) {
+ tags[i] = SkEndian_SwapBE32(header.fDir[i].fTag);
+ }
+ }
+ return header.fCount;
+}
+
+size_t SkFontStream::GetTableData(SkStream* stream, int ttcIndex,
+ SkFontTableTag tag,
+ size_t offset, size_t length, void* data) {
+ SfntHeader header;
+ if (!header.init(stream, ttcIndex)) {
+ return 0;
+ }
+
+ for (int i = 0; i < header.fCount; i++) {
+ if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) {
+ size_t realOffset = SkEndian_SwapBE32(header.fDir[i].fOffset);
+ size_t realLength = SkEndian_SwapBE32(header.fDir[i].fLength);
+ // now sanity check the caller's offset/length
+ if (offset >= realLength) {
+ return 0;
+ }
+ // if the caller is trusting the length from the file, then a
+ // hostile file might choose a value which would overflow offset +
+ // length.
+ if (offset + length < offset) {
+ return 0;
+ }
+ if (length > realLength - offset) {
+ length = realLength - offset;
+ }
+ if (data) {
+ // skip the stream to the part of the table we want to copy from
+ stream->rewind();
+ size_t bytesToSkip = realOffset + offset;
+ if (!skip(stream, bytesToSkip)) {
+ return 0;
+ }
+ if (!read(stream, data, length)) {
+ return 0;
+ }
+ }
+ return length;
+ }
+ }
+ return 0;
+}
diff --git a/core/SkFontStream.h b/core/SkFontStream.h
new file mode 100644
index 00000000..0f7a052e
--- /dev/null
+++ b/core/SkFontStream.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkFontStream_DEFINED
+#define SkFontStream_DEFINED
+
+class SkStream;
+
+#include "SkTypeface.h"
+
+class SkFontStream {
+public:
+ /**
+ * Return the number of shared directories inside a TTC sfnt, or return 1
+ * if the stream is a normal sfnt (ttf). If there is an error or
+ * no directory is found, return 0.
+ *
+ * Note: the stream is rewound initially, but is returned at an arbitrary
+ * read offset.
+ */
+ static int CountTTCEntries(SkStream*);
+
+ /**
+ * @param ttcIndex 0 for normal sfnts, or the index within a TTC sfnt.
+ *
+ * Note: the stream is rewound initially, but is returned at an arbitrary
+ * read offset.
+ */
+ static int GetTableTags(SkStream*, int ttcIndex, SkFontTableTag tags[]);
+
+ /**
+ * @param ttcIndex 0 for normal sfnts, or the index within a TTC sfnt.
+ *
+ * Note: the stream is rewound initially, but is returned at an arbitrary
+ * read offset.
+ */
+ static size_t GetTableData(SkStream*, int ttcIndex, SkFontTableTag tag,
+ size_t offset, size_t length, void* data);
+
+ static size_t GetTableSize(SkStream* stream, int ttcIndex, SkFontTableTag tag) {
+ return GetTableData(stream, ttcIndex, tag, 0, ~0U, NULL);
+ }
+};
+
+#endif
diff --git a/core/SkGeometry.cpp b/core/SkGeometry.cpp
new file mode 100644
index 00000000..5e77dd3d
--- /dev/null
+++ b/core/SkGeometry.cpp
@@ -0,0 +1,1666 @@
+
+/*
+ * 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 "SkGeometry.h"
+#include "Sk64.h"
+#include "SkMatrix.h"
+
+bool SkXRayCrossesLine(const SkXRay& pt, const SkPoint pts[2], bool* ambiguous) {
+ if (ambiguous) {
+ *ambiguous = false;
+ }
+ // Determine quick discards.
+ // Consider query line going exactly through point 0 to not
+ // intersect, for symmetry with SkXRayCrossesMonotonicCubic.
+ if (pt.fY == pts[0].fY) {
+ if (ambiguous) {
+ *ambiguous = true;
+ }
+ return false;
+ }
+ if (pt.fY < pts[0].fY && pt.fY < pts[1].fY)
+ return false;
+ if (pt.fY > pts[0].fY && pt.fY > pts[1].fY)
+ return false;
+ if (pt.fX > pts[0].fX && pt.fX > pts[1].fX)
+ return false;
+ // Determine degenerate cases
+ if (SkScalarNearlyZero(pts[0].fY - pts[1].fY))
+ return false;
+ if (SkScalarNearlyZero(pts[0].fX - pts[1].fX)) {
+ // We've already determined the query point lies within the
+ // vertical range of the line segment.
+ if (pt.fX <= pts[0].fX) {
+ if (ambiguous) {
+ *ambiguous = (pt.fY == pts[1].fY);
+ }
+ return true;
+ }
+ return false;
+ }
+ // Ambiguity check
+ if (pt.fY == pts[1].fY) {
+ if (pt.fX <= pts[1].fX) {
+ if (ambiguous) {
+ *ambiguous = true;
+ }
+ return true;
+ }
+ return false;
+ }
+ // Full line segment evaluation
+ SkScalar delta_y = pts[1].fY - pts[0].fY;
+ SkScalar delta_x = pts[1].fX - pts[0].fX;
+ SkScalar slope = SkScalarDiv(delta_y, delta_x);
+ SkScalar b = pts[0].fY - SkScalarMul(slope, pts[0].fX);
+ // Solve for x coordinate at y = pt.fY
+ SkScalar x = SkScalarDiv(pt.fY - b, slope);
+ return pt.fX <= x;
+}
+
+/** If defined, this makes eval_quad and eval_cubic do more setup (sometimes
+ involving integer multiplies by 2 or 3, but fewer calls to SkScalarMul.
+ May also introduce overflow of fixed when we compute our setup.
+*/
+#ifdef SK_SCALAR_IS_FIXED
+ #define DIRECT_EVAL_OF_POLYNOMIALS
+#endif
+
+////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_SCALAR_IS_FIXED
+ static int is_not_monotonic(int a, int b, int c, int d)
+ {
+ return (((a - b) | (b - c) | (c - d)) & ((b - a) | (c - b) | (d - c))) >> 31;
+ }
+
+ static int is_not_monotonic(int a, int b, int c)
+ {
+ return (((a - b) | (b - c)) & ((b - a) | (c - b))) >> 31;
+ }
+#else
+ static int is_not_monotonic(float a, float b, float c)
+ {
+ float ab = a - b;
+ float bc = b - c;
+ if (ab < 0)
+ bc = -bc;
+ return ab == 0 || bc < 0;
+ }
+#endif
+
+////////////////////////////////////////////////////////////////////////
+
+static bool is_unit_interval(SkScalar x)
+{
+ return x > 0 && x < SK_Scalar1;
+}
+
+static int valid_unit_divide(SkScalar numer, SkScalar denom, SkScalar* ratio)
+{
+ SkASSERT(ratio);
+
+ if (numer < 0)
+ {
+ numer = -numer;
+ denom = -denom;
+ }
+
+ if (denom == 0 || numer == 0 || numer >= denom)
+ return 0;
+
+ SkScalar r = SkScalarDiv(numer, denom);
+ if (SkScalarIsNaN(r)) {
+ return 0;
+ }
+ SkASSERT(r >= 0 && r < SK_Scalar1);
+ if (r == 0) // catch underflow if numer <<<< denom
+ return 0;
+ *ratio = r;
+ return 1;
+}
+
+/** From Numerical Recipes in C.
+
+ Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C])
+ x1 = Q / A
+ x2 = C / Q
+*/
+int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2])
+{
+ SkASSERT(roots);
+
+ if (A == 0)
+ return valid_unit_divide(-C, B, roots);
+
+ SkScalar* r = roots;
+
+#ifdef SK_SCALAR_IS_FLOAT
+ float R = B*B - 4*A*C;
+ if (R < 0 || SkScalarIsNaN(R)) { // complex roots
+ return 0;
+ }
+ R = sk_float_sqrt(R);
+#else
+ Sk64 RR, tmp;
+
+ RR.setMul(B,B);
+ tmp.setMul(A,C);
+ tmp.shiftLeft(2);
+ RR.sub(tmp);
+ if (RR.isNeg())
+ return 0;
+ SkFixed R = RR.getSqrt();
+#endif
+
+ SkScalar Q = (B < 0) ? -(B-R)/2 : -(B+R)/2;
+ r += valid_unit_divide(Q, A, r);
+ r += valid_unit_divide(C, Q, r);
+ if (r - roots == 2)
+ {
+ if (roots[0] > roots[1])
+ SkTSwap<SkScalar>(roots[0], roots[1]);
+ else if (roots[0] == roots[1]) // nearly-equal?
+ r -= 1; // skip the double root
+ }
+ return (int)(r - roots);
+}
+
+#ifdef SK_SCALAR_IS_FIXED
+/** Trim A/B/C down so that they are all <= 32bits
+ and then call SkFindUnitQuadRoots()
+*/
+static int Sk64FindFixedQuadRoots(const Sk64& A, const Sk64& B, const Sk64& C, SkFixed roots[2])
+{
+ int na = A.shiftToMake32();
+ int nb = B.shiftToMake32();
+ int nc = C.shiftToMake32();
+
+ int shift = SkMax32(na, SkMax32(nb, nc));
+ SkASSERT(shift >= 0);
+
+ return SkFindUnitQuadRoots(A.getShiftRight(shift), B.getShiftRight(shift), C.getShiftRight(shift), roots);
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+
+static SkScalar eval_quad(const SkScalar src[], SkScalar t)
+{
+ SkASSERT(src);
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+#ifdef DIRECT_EVAL_OF_POLYNOMIALS
+ SkScalar C = src[0];
+ SkScalar A = src[4] - 2 * src[2] + C;
+ SkScalar B = 2 * (src[2] - C);
+ return SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
+#else
+ SkScalar ab = SkScalarInterp(src[0], src[2], t);
+ SkScalar bc = SkScalarInterp(src[2], src[4], t);
+ return SkScalarInterp(ab, bc, t);
+#endif
+}
+
+static SkScalar eval_quad_derivative(const SkScalar src[], SkScalar t)
+{
+ SkScalar A = src[4] - 2 * src[2] + src[0];
+ SkScalar B = src[2] - src[0];
+
+ return 2 * SkScalarMulAdd(A, t, B);
+}
+
+static SkScalar eval_quad_derivative_at_half(const SkScalar src[])
+{
+ SkScalar A = src[4] - 2 * src[2] + src[0];
+ SkScalar B = src[2] - src[0];
+ return A + 2 * B;
+}
+
+void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent)
+{
+ SkASSERT(src);
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ if (pt)
+ pt->set(eval_quad(&src[0].fX, t), eval_quad(&src[0].fY, t));
+ if (tangent)
+ tangent->set(eval_quad_derivative(&src[0].fX, t),
+ eval_quad_derivative(&src[0].fY, t));
+}
+
+void SkEvalQuadAtHalf(const SkPoint src[3], SkPoint* pt, SkVector* tangent)
+{
+ SkASSERT(src);
+
+ if (pt)
+ {
+ SkScalar x01 = SkScalarAve(src[0].fX, src[1].fX);
+ SkScalar y01 = SkScalarAve(src[0].fY, src[1].fY);
+ SkScalar x12 = SkScalarAve(src[1].fX, src[2].fX);
+ SkScalar y12 = SkScalarAve(src[1].fY, src[2].fY);
+ pt->set(SkScalarAve(x01, x12), SkScalarAve(y01, y12));
+ }
+ if (tangent)
+ tangent->set(eval_quad_derivative_at_half(&src[0].fX),
+ eval_quad_derivative_at_half(&src[0].fY));
+}
+
+static void interp_quad_coords(const SkScalar* src, SkScalar* dst, SkScalar t)
+{
+ SkScalar ab = SkScalarInterp(src[0], src[2], t);
+ SkScalar bc = SkScalarInterp(src[2], src[4], t);
+
+ dst[0] = src[0];
+ dst[2] = ab;
+ dst[4] = SkScalarInterp(ab, bc, t);
+ dst[6] = bc;
+ dst[8] = src[4];
+}
+
+void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t)
+{
+ SkASSERT(t > 0 && t < SK_Scalar1);
+
+ interp_quad_coords(&src[0].fX, &dst[0].fX, t);
+ interp_quad_coords(&src[0].fY, &dst[0].fY, t);
+}
+
+void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5])
+{
+ SkScalar x01 = SkScalarAve(src[0].fX, src[1].fX);
+ SkScalar y01 = SkScalarAve(src[0].fY, src[1].fY);
+ SkScalar x12 = SkScalarAve(src[1].fX, src[2].fX);
+ SkScalar y12 = SkScalarAve(src[1].fY, src[2].fY);
+
+ dst[0] = src[0];
+ dst[1].set(x01, y01);
+ dst[2].set(SkScalarAve(x01, x12), SkScalarAve(y01, y12));
+ dst[3].set(x12, y12);
+ dst[4] = src[2];
+}
+
+/** Quad'(t) = At + B, where
+ A = 2(a - 2b + c)
+ B = 2(b - a)
+ Solve for t, only if it fits between 0 < t < 1
+*/
+int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValue[1])
+{
+ /* At + B == 0
+ t = -B / A
+ */
+#ifdef SK_SCALAR_IS_FIXED
+ return is_not_monotonic(a, b, c) && valid_unit_divide(a - b, a - b - b + c, tValue);
+#else
+ return valid_unit_divide(a - b, a - b - b + c, tValue);
+#endif
+}
+
+static inline void flatten_double_quad_extrema(SkScalar coords[14])
+{
+ coords[2] = coords[6] = coords[4];
+}
+
+/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is
+ stored in dst[]. Guarantees that the 1/2 quads will be monotonic.
+ */
+int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5])
+{
+ SkASSERT(src);
+ SkASSERT(dst);
+
+#if 0
+ static bool once = true;
+ if (once)
+ {
+ once = false;
+ SkPoint s[3] = { 0, 26398, 0, 26331, 0, 20621428 };
+ SkPoint d[6];
+
+ int n = SkChopQuadAtYExtrema(s, d);
+ SkDebugf("chop=%d, Y=[%x %x %x %x %x %x]\n", n, d[0].fY, d[1].fY, d[2].fY, d[3].fY, d[4].fY, d[5].fY);
+ }
+#endif
+
+ SkScalar a = src[0].fY;
+ SkScalar b = src[1].fY;
+ SkScalar c = src[2].fY;
+
+ if (is_not_monotonic(a, b, c))
+ {
+ SkScalar tValue;
+ if (valid_unit_divide(a - b, a - b - b + c, &tValue))
+ {
+ SkChopQuadAt(src, dst, tValue);
+ flatten_double_quad_extrema(&dst[0].fY);
+ return 1;
+ }
+ // if we get here, we need to force dst to be monotonic, even though
+ // we couldn't compute a unit_divide value (probably underflow).
+ b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c;
+ }
+ dst[0].set(src[0].fX, a);
+ dst[1].set(src[1].fX, b);
+ dst[2].set(src[2].fX, c);
+ return 0;
+}
+
+/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is
+ stored in dst[]. Guarantees that the 1/2 quads will be monotonic.
+ */
+int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5])
+{
+ SkASSERT(src);
+ SkASSERT(dst);
+
+ SkScalar a = src[0].fX;
+ SkScalar b = src[1].fX;
+ SkScalar c = src[2].fX;
+
+ if (is_not_monotonic(a, b, c)) {
+ SkScalar tValue;
+ if (valid_unit_divide(a - b, a - b - b + c, &tValue)) {
+ SkChopQuadAt(src, dst, tValue);
+ flatten_double_quad_extrema(&dst[0].fX);
+ return 1;
+ }
+ // if we get here, we need to force dst to be monotonic, even though
+ // we couldn't compute a unit_divide value (probably underflow).
+ b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c;
+ }
+ dst[0].set(a, src[0].fY);
+ dst[1].set(b, src[1].fY);
+ dst[2].set(c, src[2].fY);
+ return 0;
+}
+
+// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2
+// F'(t) = 2 (b - a) + 2 (a - 2b + c) t
+// F''(t) = 2 (a - 2b + c)
+//
+// A = 2 (b - a)
+// B = 2 (a - 2b + c)
+//
+// Maximum curvature for a quadratic means solving
+// Fx' Fx'' + Fy' Fy'' = 0
+//
+// t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2)
+//
+float SkFindQuadMaxCurvature(const SkPoint src[3]) {
+ SkScalar Ax = src[1].fX - src[0].fX;
+ SkScalar Ay = src[1].fY - src[0].fY;
+ SkScalar Bx = src[0].fX - src[1].fX - src[1].fX + src[2].fX;
+ SkScalar By = src[0].fY - src[1].fY - src[1].fY + src[2].fY;
+ SkScalar t = 0; // 0 means don't chop
+
+#ifdef SK_SCALAR_IS_FLOAT
+ (void)valid_unit_divide(-(Ax * Bx + Ay * By), Bx * Bx + By * By, &t);
+#else
+ // !!! should I use SkFloat here? seems like it
+ Sk64 numer, denom, tmp;
+
+ numer.setMul(Ax, -Bx);
+ tmp.setMul(Ay, -By);
+ numer.add(tmp);
+
+ if (numer.isPos()) // do nothing if numer <= 0
+ {
+ denom.setMul(Bx, Bx);
+ tmp.setMul(By, By);
+ denom.add(tmp);
+ SkASSERT(!denom.isNeg());
+ if (numer < denom)
+ {
+ t = numer.getFixedDiv(denom);
+ SkASSERT(t >= 0 && t <= SK_Fixed1); // assert that we're numerically stable (ha!)
+ if ((unsigned)t >= SK_Fixed1) // runtime check for numerical stability
+ t = 0; // ignore the chop
+ }
+ }
+#endif
+ return t;
+}
+
+int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5])
+{
+ SkScalar t = SkFindQuadMaxCurvature(src);
+ if (t == 0) {
+ memcpy(dst, src, 3 * sizeof(SkPoint));
+ return 1;
+ } else {
+ SkChopQuadAt(src, dst, t);
+ return 2;
+ }
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+ #define SK_ScalarTwoThirds (0.666666666f)
+#else
+ #define SK_ScalarTwoThirds ((SkFixed)(43691))
+#endif
+
+void SkConvertQuadToCubic(const SkPoint src[3], SkPoint dst[4]) {
+ const SkScalar scale = SK_ScalarTwoThirds;
+ dst[0] = src[0];
+ dst[1].set(src[0].fX + SkScalarMul(src[1].fX - src[0].fX, scale),
+ src[0].fY + SkScalarMul(src[1].fY - src[0].fY, scale));
+ dst[2].set(src[2].fX + SkScalarMul(src[1].fX - src[2].fX, scale),
+ src[2].fY + SkScalarMul(src[1].fY - src[2].fY, scale));
+ dst[3] = src[2];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+///// CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS /////
+////////////////////////////////////////////////////////////////////////////////////////
+
+static void get_cubic_coeff(const SkScalar pt[], SkScalar coeff[4])
+{
+ coeff[0] = pt[6] + 3*(pt[2] - pt[4]) - pt[0];
+ coeff[1] = 3*(pt[4] - pt[2] - pt[2] + pt[0]);
+ coeff[2] = 3*(pt[2] - pt[0]);
+ coeff[3] = pt[0];
+}
+
+void SkGetCubicCoeff(const SkPoint pts[4], SkScalar cx[4], SkScalar cy[4])
+{
+ SkASSERT(pts);
+
+ if (cx)
+ get_cubic_coeff(&pts[0].fX, cx);
+ if (cy)
+ get_cubic_coeff(&pts[0].fY, cy);
+}
+
+static SkScalar eval_cubic(const SkScalar src[], SkScalar t)
+{
+ SkASSERT(src);
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ if (t == 0)
+ return src[0];
+
+#ifdef DIRECT_EVAL_OF_POLYNOMIALS
+ SkScalar D = src[0];
+ SkScalar A = src[6] + 3*(src[2] - src[4]) - D;
+ SkScalar B = 3*(src[4] - src[2] - src[2] + D);
+ SkScalar C = 3*(src[2] - D);
+
+ return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
+#else
+ SkScalar ab = SkScalarInterp(src[0], src[2], t);
+ SkScalar bc = SkScalarInterp(src[2], src[4], t);
+ SkScalar cd = SkScalarInterp(src[4], src[6], t);
+ SkScalar abc = SkScalarInterp(ab, bc, t);
+ SkScalar bcd = SkScalarInterp(bc, cd, t);
+ return SkScalarInterp(abc, bcd, t);
+#endif
+}
+
+/** return At^2 + Bt + C
+*/
+static SkScalar eval_quadratic(SkScalar A, SkScalar B, SkScalar C, SkScalar t)
+{
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ return SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
+}
+
+static SkScalar eval_cubic_derivative(const SkScalar src[], SkScalar t)
+{
+ SkScalar A = src[6] + 3*(src[2] - src[4]) - src[0];
+ SkScalar B = 2*(src[4] - 2 * src[2] + src[0]);
+ SkScalar C = src[2] - src[0];
+
+ return eval_quadratic(A, B, C, t);
+}
+
+static SkScalar eval_cubic_2ndDerivative(const SkScalar src[], SkScalar t)
+{
+ SkScalar A = src[6] + 3*(src[2] - src[4]) - src[0];
+ SkScalar B = src[4] - 2 * src[2] + src[0];
+
+ return SkScalarMulAdd(A, t, B);
+}
+
+void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* loc, SkVector* tangent, SkVector* curvature)
+{
+ SkASSERT(src);
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ if (loc)
+ loc->set(eval_cubic(&src[0].fX, t), eval_cubic(&src[0].fY, t));
+ if (tangent)
+ tangent->set(eval_cubic_derivative(&src[0].fX, t),
+ eval_cubic_derivative(&src[0].fY, t));
+ if (curvature)
+ curvature->set(eval_cubic_2ndDerivative(&src[0].fX, t),
+ eval_cubic_2ndDerivative(&src[0].fY, t));
+}
+
+/** Cubic'(t) = At^2 + Bt + C, where
+ A = 3(-a + 3(b - c) + d)
+ B = 6(a - 2b + c)
+ C = 3(b - a)
+ Solve for t, keeping only those that fit betwee 0 < t < 1
+*/
+int SkFindCubicExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar d, SkScalar tValues[2])
+{
+#ifdef SK_SCALAR_IS_FIXED
+ if (!is_not_monotonic(a, b, c, d))
+ return 0;
+#endif
+
+ // we divide A,B,C by 3 to simplify
+ SkScalar A = d - a + 3*(b - c);
+ SkScalar B = 2*(a - b - b + c);
+ SkScalar C = b - a;
+
+ return SkFindUnitQuadRoots(A, B, C, tValues);
+}
+
+static void interp_cubic_coords(const SkScalar* src, SkScalar* dst, SkScalar t)
+{
+ SkScalar ab = SkScalarInterp(src[0], src[2], t);
+ SkScalar bc = SkScalarInterp(src[2], src[4], t);
+ SkScalar cd = SkScalarInterp(src[4], src[6], t);
+ SkScalar abc = SkScalarInterp(ab, bc, t);
+ SkScalar bcd = SkScalarInterp(bc, cd, t);
+ SkScalar abcd = SkScalarInterp(abc, bcd, t);
+
+ dst[0] = src[0];
+ dst[2] = ab;
+ dst[4] = abc;
+ dst[6] = abcd;
+ dst[8] = bcd;
+ dst[10] = cd;
+ dst[12] = src[6];
+}
+
+void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t)
+{
+ SkASSERT(t > 0 && t < SK_Scalar1);
+
+ interp_cubic_coords(&src[0].fX, &dst[0].fX, t);
+ interp_cubic_coords(&src[0].fY, &dst[0].fY, t);
+}
+
+/* http://code.google.com/p/skia/issues/detail?id=32
+
+ This test code would fail when we didn't check the return result of
+ valid_unit_divide in SkChopCubicAt(... tValues[], int roots). The reason is
+ that after the first chop, the parameters to valid_unit_divide are equal
+ (thanks to finite float precision and rounding in the subtracts). Thus
+ even though the 2nd tValue looks < 1.0, after we renormalize it, we end
+ up with 1.0, hence the need to check and just return the last cubic as
+ a degenerate clump of 4 points in the sampe place.
+
+ static void test_cubic() {
+ SkPoint src[4] = {
+ { 556.25000, 523.03003 },
+ { 556.23999, 522.96002 },
+ { 556.21997, 522.89001 },
+ { 556.21997, 522.82001 }
+ };
+ SkPoint dst[10];
+ SkScalar tval[] = { 0.33333334f, 0.99999994f };
+ SkChopCubicAt(src, dst, tval, 2);
+ }
+ */
+
+void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], const SkScalar tValues[], int roots)
+{
+#ifdef SK_DEBUG
+ {
+ for (int i = 0; i < roots - 1; i++)
+ {
+ SkASSERT(is_unit_interval(tValues[i]));
+ SkASSERT(is_unit_interval(tValues[i+1]));
+ SkASSERT(tValues[i] < tValues[i+1]);
+ }
+ }
+#endif
+
+ if (dst)
+ {
+ if (roots == 0) // nothing to chop
+ memcpy(dst, src, 4*sizeof(SkPoint));
+ else
+ {
+ SkScalar t = tValues[0];
+ SkPoint tmp[4];
+
+ for (int i = 0; i < roots; i++)
+ {
+ SkChopCubicAt(src, dst, t);
+ if (i == roots - 1)
+ break;
+
+ dst += 3;
+ // have src point to the remaining cubic (after the chop)
+ memcpy(tmp, dst, 4 * sizeof(SkPoint));
+ src = tmp;
+
+ // watch out in case the renormalized t isn't in range
+ if (!valid_unit_divide(tValues[i+1] - tValues[i],
+ SK_Scalar1 - tValues[i], &t)) {
+ // if we can't, just create a degenerate cubic
+ dst[4] = dst[5] = dst[6] = src[3];
+ break;
+ }
+ }
+ }
+ }
+}
+
+void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7])
+{
+ SkScalar x01 = SkScalarAve(src[0].fX, src[1].fX);
+ SkScalar y01 = SkScalarAve(src[0].fY, src[1].fY);
+ SkScalar x12 = SkScalarAve(src[1].fX, src[2].fX);
+ SkScalar y12 = SkScalarAve(src[1].fY, src[2].fY);
+ SkScalar x23 = SkScalarAve(src[2].fX, src[3].fX);
+ SkScalar y23 = SkScalarAve(src[2].fY, src[3].fY);
+
+ SkScalar x012 = SkScalarAve(x01, x12);
+ SkScalar y012 = SkScalarAve(y01, y12);
+ SkScalar x123 = SkScalarAve(x12, x23);
+ SkScalar y123 = SkScalarAve(y12, y23);
+
+ dst[0] = src[0];
+ dst[1].set(x01, y01);
+ dst[2].set(x012, y012);
+ dst[3].set(SkScalarAve(x012, x123), SkScalarAve(y012, y123));
+ dst[4].set(x123, y123);
+ dst[5].set(x23, y23);
+ dst[6] = src[3];
+}
+
+static void flatten_double_cubic_extrema(SkScalar coords[14])
+{
+ coords[4] = coords[8] = coords[6];
+}
+
+/** Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that
+ the resulting beziers are monotonic in Y. This is called by the scan converter.
+ Depending on what is returned, dst[] is treated as follows
+ 0 dst[0..3] is the original cubic
+ 1 dst[0..3] and dst[3..6] are the two new cubics
+ 2 dst[0..3], dst[3..6], dst[6..9] are the three new cubics
+ If dst == null, it is ignored and only the count is returned.
+*/
+int SkChopCubicAtYExtrema(const SkPoint src[4], SkPoint dst[10]) {
+ SkScalar tValues[2];
+ int roots = SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY,
+ src[3].fY, tValues);
+
+ SkChopCubicAt(src, dst, tValues, roots);
+ if (dst && roots > 0) {
+ // we do some cleanup to ensure our Y extrema are flat
+ flatten_double_cubic_extrema(&dst[0].fY);
+ if (roots == 2) {
+ flatten_double_cubic_extrema(&dst[3].fY);
+ }
+ }
+ return roots;
+}
+
+int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]) {
+ SkScalar tValues[2];
+ int roots = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX,
+ src[3].fX, tValues);
+
+ SkChopCubicAt(src, dst, tValues, roots);
+ if (dst && roots > 0) {
+ // we do some cleanup to ensure our Y extrema are flat
+ flatten_double_cubic_extrema(&dst[0].fX);
+ if (roots == 2) {
+ flatten_double_cubic_extrema(&dst[3].fX);
+ }
+ }
+ return roots;
+}
+
+/** http://www.faculty.idc.ac.il/arik/quality/appendixA.html
+
+ Inflection means that curvature is zero.
+ Curvature is [F' x F''] / [F'^3]
+ So we solve F'x X F''y - F'y X F''y == 0
+ After some canceling of the cubic term, we get
+ A = b - a
+ B = c - 2b + a
+ C = d - 3c + 3b - a
+ (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0
+*/
+int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[])
+{
+ SkScalar Ax = src[1].fX - src[0].fX;
+ SkScalar Ay = src[1].fY - src[0].fY;
+ SkScalar Bx = src[2].fX - 2 * src[1].fX + src[0].fX;
+ SkScalar By = src[2].fY - 2 * src[1].fY + src[0].fY;
+ SkScalar Cx = src[3].fX + 3 * (src[1].fX - src[2].fX) - src[0].fX;
+ SkScalar Cy = src[3].fY + 3 * (src[1].fY - src[2].fY) - src[0].fY;
+ int count;
+
+#ifdef SK_SCALAR_IS_FLOAT
+ count = SkFindUnitQuadRoots(Bx*Cy - By*Cx, Ax*Cy - Ay*Cx, Ax*By - Ay*Bx, tValues);
+#else
+ Sk64 A, B, C, tmp;
+
+ A.setMul(Bx, Cy);
+ tmp.setMul(By, Cx);
+ A.sub(tmp);
+
+ B.setMul(Ax, Cy);
+ tmp.setMul(Ay, Cx);
+ B.sub(tmp);
+
+ C.setMul(Ax, By);
+ tmp.setMul(Ay, Bx);
+ C.sub(tmp);
+
+ count = Sk64FindFixedQuadRoots(A, B, C, tValues);
+#endif
+
+ return count;
+}
+
+int SkChopCubicAtInflections(const SkPoint src[], SkPoint dst[10])
+{
+ SkScalar tValues[2];
+ int count = SkFindCubicInflections(src, tValues);
+
+ if (dst)
+ {
+ if (count == 0)
+ memcpy(dst, src, 4 * sizeof(SkPoint));
+ else
+ SkChopCubicAt(src, dst, tValues, count);
+ }
+ return count + 1;
+}
+
+template <typename T> void bubble_sort(T array[], int count)
+{
+ for (int i = count - 1; i > 0; --i)
+ for (int j = i; j > 0; --j)
+ if (array[j] < array[j-1])
+ {
+ T tmp(array[j]);
+ array[j] = array[j-1];
+ array[j-1] = tmp;
+ }
+}
+
+#include "SkFP.h"
+
+// newton refinement
+#if 0
+static SkScalar refine_cubic_root(const SkFP coeff[4], SkScalar root)
+{
+ // x1 = x0 - f(t) / f'(t)
+
+ SkFP T = SkScalarToFloat(root);
+ SkFP N, D;
+
+ // f' = 3*coeff[0]*T^2 + 2*coeff[1]*T + coeff[2]
+ D = SkFPMul(SkFPMul(coeff[0], SkFPMul(T,T)), 3);
+ D = SkFPAdd(D, SkFPMulInt(SkFPMul(coeff[1], T), 2));
+ D = SkFPAdd(D, coeff[2]);
+
+ if (D == 0)
+ return root;
+
+ // f = coeff[0]*T^3 + coeff[1]*T^2 + coeff[2]*T + coeff[3]
+ N = SkFPMul(SkFPMul(SkFPMul(T, T), T), coeff[0]);
+ N = SkFPAdd(N, SkFPMul(SkFPMul(T, T), coeff[1]));
+ N = SkFPAdd(N, SkFPMul(T, coeff[2]));
+ N = SkFPAdd(N, coeff[3]);
+
+ if (N)
+ {
+ SkScalar delta = SkFPToScalar(SkFPDiv(N, D));
+
+ if (delta)
+ root -= delta;
+ }
+ return root;
+}
+#endif
+
+/**
+ * Given an array and count, remove all pair-wise duplicates from the array,
+ * keeping the existing sorting, and return the new count
+ */
+static int collaps_duplicates(float array[], int count) {
+ for (int n = count; n > 1; --n) {
+ if (array[0] == array[1]) {
+ for (int i = 1; i < n; ++i) {
+ array[i - 1] = array[i];
+ }
+ count -= 1;
+ } else {
+ array += 1;
+ }
+ }
+ return count;
+}
+
+#ifdef SK_DEBUG
+
+#define TEST_COLLAPS_ENTRY(array) array, SK_ARRAY_COUNT(array)
+
+static void test_collaps_duplicates() {
+ static bool gOnce;
+ if (gOnce) { return; }
+ gOnce = true;
+ const float src0[] = { 0 };
+ const float src1[] = { 0, 0 };
+ const float src2[] = { 0, 1 };
+ const float src3[] = { 0, 0, 0 };
+ const float src4[] = { 0, 0, 1 };
+ const float src5[] = { 0, 1, 1 };
+ const float src6[] = { 0, 1, 2 };
+ const struct {
+ const float* fData;
+ int fCount;
+ int fCollapsedCount;
+ } data[] = {
+ { TEST_COLLAPS_ENTRY(src0), 1 },
+ { TEST_COLLAPS_ENTRY(src1), 1 },
+ { TEST_COLLAPS_ENTRY(src2), 2 },
+ { TEST_COLLAPS_ENTRY(src3), 1 },
+ { TEST_COLLAPS_ENTRY(src4), 2 },
+ { TEST_COLLAPS_ENTRY(src5), 2 },
+ { TEST_COLLAPS_ENTRY(src6), 3 },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(data); ++i) {
+ float dst[3];
+ memcpy(dst, data[i].fData, data[i].fCount * sizeof(dst[0]));
+ int count = collaps_duplicates(dst, data[i].fCount);
+ SkASSERT(data[i].fCollapsedCount == count);
+ for (int j = 1; j < count; ++j) {
+ SkASSERT(dst[j-1] < dst[j]);
+ }
+ }
+}
+#endif
+
+#if defined _WIN32 && _MSC_VER >= 1300 && defined SK_SCALAR_IS_FIXED // disable warning : unreachable code if building fixed point for windows desktop
+#pragma warning ( disable : 4702 )
+#endif
+
+/* Solve coeff(t) == 0, returning the number of roots that
+ lie withing 0 < t < 1.
+ coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3]
+
+ Eliminates repeated roots (so that all tValues are distinct, and are always
+ in increasing order.
+*/
+static int solve_cubic_polynomial(const SkFP coeff[4], SkScalar tValues[3])
+{
+#ifndef SK_SCALAR_IS_FLOAT
+ return 0; // this is not yet implemented for software float
+#endif
+
+ if (SkScalarNearlyZero(coeff[0])) // we're just a quadratic
+ {
+ return SkFindUnitQuadRoots(coeff[1], coeff[2], coeff[3], tValues);
+ }
+
+ SkFP a, b, c, Q, R;
+
+ {
+ SkASSERT(coeff[0] != 0);
+
+ SkFP inva = SkFPInvert(coeff[0]);
+ a = SkFPMul(coeff[1], inva);
+ b = SkFPMul(coeff[2], inva);
+ c = SkFPMul(coeff[3], inva);
+ }
+ Q = SkFPDivInt(SkFPSub(SkFPMul(a,a), SkFPMulInt(b, 3)), 9);
+// R = (2*a*a*a - 9*a*b + 27*c) / 54;
+ R = SkFPMulInt(SkFPMul(SkFPMul(a, a), a), 2);
+ R = SkFPSub(R, SkFPMulInt(SkFPMul(a, b), 9));
+ R = SkFPAdd(R, SkFPMulInt(c, 27));
+ R = SkFPDivInt(R, 54);
+
+ SkFP Q3 = SkFPMul(SkFPMul(Q, Q), Q);
+ SkFP R2MinusQ3 = SkFPSub(SkFPMul(R,R), Q3);
+ SkFP adiv3 = SkFPDivInt(a, 3);
+
+ SkScalar* roots = tValues;
+ SkScalar r;
+
+ if (SkFPLT(R2MinusQ3, 0)) // we have 3 real roots
+ {
+#ifdef SK_SCALAR_IS_FLOAT
+ float theta = sk_float_acos(R / sk_float_sqrt(Q3));
+ float neg2RootQ = -2 * sk_float_sqrt(Q);
+
+ r = neg2RootQ * sk_float_cos(theta/3) - adiv3;
+ if (is_unit_interval(r))
+ *roots++ = r;
+
+ r = neg2RootQ * sk_float_cos((theta + 2*SK_ScalarPI)/3) - adiv3;
+ if (is_unit_interval(r))
+ *roots++ = r;
+
+ r = neg2RootQ * sk_float_cos((theta - 2*SK_ScalarPI)/3) - adiv3;
+ if (is_unit_interval(r))
+ *roots++ = r;
+
+ SkDEBUGCODE(test_collaps_duplicates();)
+
+ // now sort the roots
+ int count = (int)(roots - tValues);
+ SkASSERT((unsigned)count <= 3);
+ bubble_sort(tValues, count);
+ count = collaps_duplicates(tValues, count);
+ roots = tValues + count; // so we compute the proper count below
+#endif
+ }
+ else // we have 1 real root
+ {
+ SkFP A = SkFPAdd(SkFPAbs(R), SkFPSqrt(R2MinusQ3));
+ A = SkFPCubeRoot(A);
+ if (SkFPGT(R, 0))
+ A = SkFPNeg(A);
+
+ if (A != 0)
+ A = SkFPAdd(A, SkFPDiv(Q, A));
+ r = SkFPToScalar(SkFPSub(A, adiv3));
+ if (is_unit_interval(r))
+ *roots++ = r;
+ }
+
+ return (int)(roots - tValues);
+}
+
+/* Looking for F' dot F'' == 0
+
+ A = b - a
+ B = c - 2b + a
+ C = d - 3c + 3b - a
+
+ F' = 3Ct^2 + 6Bt + 3A
+ F'' = 6Ct + 6B
+
+ F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
+*/
+static void formulate_F1DotF2(const SkScalar src[], SkFP coeff[4])
+{
+ SkScalar a = src[2] - src[0];
+ SkScalar b = src[4] - 2 * src[2] + src[0];
+ SkScalar c = src[6] + 3 * (src[2] - src[4]) - src[0];
+
+ SkFP A = SkScalarToFP(a);
+ SkFP B = SkScalarToFP(b);
+ SkFP C = SkScalarToFP(c);
+
+ coeff[0] = SkFPMul(C, C);
+ coeff[1] = SkFPMulInt(SkFPMul(B, C), 3);
+ coeff[2] = SkFPMulInt(SkFPMul(B, B), 2);
+ coeff[2] = SkFPAdd(coeff[2], SkFPMul(C, A));
+ coeff[3] = SkFPMul(A, B);
+}
+
+// EXPERIMENTAL: can set this to zero to accept all t-values 0 < t < 1
+//#define kMinTValueForChopping (SK_Scalar1 / 256)
+#define kMinTValueForChopping 0
+
+/* Looking for F' dot F'' == 0
+
+ A = b - a
+ B = c - 2b + a
+ C = d - 3c + 3b - a
+
+ F' = 3Ct^2 + 6Bt + 3A
+ F'' = 6Ct + 6B
+
+ F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
+*/
+int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3])
+{
+ SkFP coeffX[4], coeffY[4];
+ int i;
+
+ formulate_F1DotF2(&src[0].fX, coeffX);
+ formulate_F1DotF2(&src[0].fY, coeffY);
+
+ for (i = 0; i < 4; i++)
+ coeffX[i] = SkFPAdd(coeffX[i],coeffY[i]);
+
+ SkScalar t[3];
+ int count = solve_cubic_polynomial(coeffX, t);
+ int maxCount = 0;
+
+ // now remove extrema where the curvature is zero (mins)
+ // !!!! need a test for this !!!!
+ for (i = 0; i < count; i++)
+ {
+ // if (not_min_curvature())
+ if (t[i] > kMinTValueForChopping && t[i] < SK_Scalar1 - kMinTValueForChopping)
+ tValues[maxCount++] = t[i];
+ }
+ return maxCount;
+}
+
+int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], SkScalar tValues[3])
+{
+ SkScalar t_storage[3];
+
+ if (tValues == NULL)
+ tValues = t_storage;
+
+ int count = SkFindCubicMaxCurvature(src, tValues);
+
+ if (dst) {
+ if (count == 0)
+ memcpy(dst, src, 4 * sizeof(SkPoint));
+ else
+ SkChopCubicAt(src, dst, tValues, count);
+ }
+ return count + 1;
+}
+
+bool SkXRayCrossesMonotonicCubic(const SkXRay& pt, const SkPoint cubic[4], bool* ambiguous) {
+ if (ambiguous) {
+ *ambiguous = false;
+ }
+
+ // Find the minimum and maximum y of the extrema, which are the
+ // first and last points since this cubic is monotonic
+ SkScalar min_y = SkMinScalar(cubic[0].fY, cubic[3].fY);
+ SkScalar max_y = SkMaxScalar(cubic[0].fY, cubic[3].fY);
+
+ if (pt.fY == cubic[0].fY
+ || pt.fY < min_y
+ || pt.fY > max_y) {
+ // The query line definitely does not cross the curve
+ if (ambiguous) {
+ *ambiguous = (pt.fY == cubic[0].fY);
+ }
+ return false;
+ }
+
+ bool pt_at_extremum = (pt.fY == cubic[3].fY);
+
+ SkScalar min_x =
+ SkMinScalar(
+ SkMinScalar(
+ SkMinScalar(cubic[0].fX, cubic[1].fX),
+ cubic[2].fX),
+ cubic[3].fX);
+ if (pt.fX < min_x) {
+ // The query line definitely crosses the curve
+ if (ambiguous) {
+ *ambiguous = pt_at_extremum;
+ }
+ return true;
+ }
+
+ SkScalar max_x =
+ SkMaxScalar(
+ SkMaxScalar(
+ SkMaxScalar(cubic[0].fX, cubic[1].fX),
+ cubic[2].fX),
+ cubic[3].fX);
+ if (pt.fX > max_x) {
+ // The query line definitely does not cross the curve
+ return false;
+ }
+
+ // Do a binary search to find the parameter value which makes y as
+ // close as possible to the query point. See whether the query
+ // line's origin is to the left of the associated x coordinate.
+
+ // kMaxIter is chosen as the number of mantissa bits for a float,
+ // since there's no way we are going to get more precision by
+ // iterating more times than that.
+ const int kMaxIter = 23;
+ SkPoint eval;
+ int iter = 0;
+ SkScalar upper_t;
+ SkScalar lower_t;
+ // Need to invert direction of t parameter if cubic goes up
+ // instead of down
+ if (cubic[3].fY > cubic[0].fY) {
+ upper_t = SK_Scalar1;
+ lower_t = SkFloatToScalar(0);
+ } else {
+ upper_t = SkFloatToScalar(0);
+ lower_t = SK_Scalar1;
+ }
+ do {
+ SkScalar t = SkScalarAve(upper_t, lower_t);
+ SkEvalCubicAt(cubic, t, &eval, NULL, NULL);
+ if (pt.fY > eval.fY) {
+ lower_t = t;
+ } else {
+ upper_t = t;
+ }
+ } while (++iter < kMaxIter
+ && !SkScalarNearlyZero(eval.fY - pt.fY));
+ if (pt.fX <= eval.fX) {
+ if (ambiguous) {
+ *ambiguous = pt_at_extremum;
+ }
+ return true;
+ }
+ return false;
+}
+
+int SkNumXRayCrossingsForCubic(const SkXRay& pt, const SkPoint cubic[4], bool* ambiguous) {
+ int num_crossings = 0;
+ SkPoint monotonic_cubics[10];
+ int num_monotonic_cubics = SkChopCubicAtYExtrema(cubic, monotonic_cubics);
+ if (ambiguous) {
+ *ambiguous = false;
+ }
+ bool locally_ambiguous;
+ if (SkXRayCrossesMonotonicCubic(pt, &monotonic_cubics[0], &locally_ambiguous))
+ ++num_crossings;
+ if (ambiguous) {
+ *ambiguous |= locally_ambiguous;
+ }
+ if (num_monotonic_cubics > 0)
+ if (SkXRayCrossesMonotonicCubic(pt, &monotonic_cubics[3], &locally_ambiguous))
+ ++num_crossings;
+ if (ambiguous) {
+ *ambiguous |= locally_ambiguous;
+ }
+ if (num_monotonic_cubics > 1)
+ if (SkXRayCrossesMonotonicCubic(pt, &monotonic_cubics[6], &locally_ambiguous))
+ ++num_crossings;
+ if (ambiguous) {
+ *ambiguous |= locally_ambiguous;
+ }
+ return num_crossings;
+}
+////////////////////////////////////////////////////////////////////////////////
+
+/* Find t value for quadratic [a, b, c] = d.
+ Return 0 if there is no solution within [0, 1)
+*/
+static SkScalar quad_solve(SkScalar a, SkScalar b, SkScalar c, SkScalar d)
+{
+ // At^2 + Bt + C = d
+ SkScalar A = a - 2 * b + c;
+ SkScalar B = 2 * (b - a);
+ SkScalar C = a - d;
+
+ SkScalar roots[2];
+ int count = SkFindUnitQuadRoots(A, B, C, roots);
+
+ SkASSERT(count <= 1);
+ return count == 1 ? roots[0] : 0;
+}
+
+/* given a quad-curve and a point (x,y), chop the quad at that point and place
+ the new off-curve point and endpoint into 'dest'. The new end point is used
+ (rather than (x,y)) to compensate for numerical inaccuracies.
+ Should only return false if the computed pos is the start of the curve
+ (i.e. root == 0)
+*/
+static bool truncate_last_curve(const SkPoint quad[3], SkScalar x, SkScalar y, SkPoint* dest)
+{
+ const SkScalar* base;
+ SkScalar value;
+
+ if (SkScalarAbs(x) < SkScalarAbs(y)) {
+ base = &quad[0].fX;
+ value = x;
+ } else {
+ base = &quad[0].fY;
+ value = y;
+ }
+
+ // note: this returns 0 if it thinks value is out of range, meaning the
+ // root might return something outside of [0, 1)
+ SkScalar t = quad_solve(base[0], base[2], base[4], value);
+
+ if (t > 0)
+ {
+ SkPoint tmp[5];
+ SkChopQuadAt(quad, tmp, t);
+ dest[0] = tmp[1];
+ dest[1] = tmp[2];
+ return true;
+ } else {
+ /* t == 0 means either the value triggered a root outside of [0, 1)
+ For our purposes, we can ignore the <= 0 roots, but we want to
+ catch the >= 1 roots (which given our caller, will basically mean
+ a root of 1, give-or-take numerical instability). If we are in the
+ >= 1 case, return the existing offCurve point.
+
+ The test below checks to see if we are close to the "end" of the
+ curve (near base[4]). Rather than specifying a tolerance, I just
+ check to see if value is on to the right/left of the middle point
+ (depending on the direction/sign of the end points).
+ */
+ if ((base[0] < base[4] && value > base[2]) ||
+ (base[0] > base[4] && value < base[2])) // should root have been 1
+ {
+ dest[0] = quad[1];
+ dest[1].set(x, y);
+ return true;
+ }
+ }
+ 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
+#define SK_ScalarRoot2Over2_QuadCircle 0.7072f
+
+#else
+ #define SK_ScalarRoot2Over2_QuadCircle SK_FixedRoot2Over2
+#endif
+
+
+static const SkPoint gQuadCirclePts[kSkBuildQuadArcStorage] = {
+ { 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,
+ SkRotationDirection dir, const SkMatrix* userMatrix,
+ SkPoint quadPoints[])
+{
+ // rotate by x,y so that uStart is (1.0)
+ SkScalar x = SkPoint::DotProduct(uStart, uStop);
+ SkScalar y = SkPoint::CrossProduct(uStart, uStop);
+
+ SkScalar absX = SkScalarAbs(x);
+ SkScalar absY = SkScalarAbs(y);
+
+ int pointCount;
+
+ // check for (effectively) coincident vectors
+ // this can happen if our angle is nearly 0 or nearly 180 (y == 0)
+ // ... we use the dot-prod to distinguish between 0 and 180 (x > 0)
+ if (absY <= SK_ScalarNearlyZero && x > 0 &&
+ ((y >= 0 && kCW_SkRotationDirection == dir) ||
+ (y <= 0 && kCCW_SkRotationDirection == dir))) {
+
+ // just return the start-point
+ quadPoints[0].set(SK_Scalar1, 0);
+ pointCount = 1;
+ } else {
+ if (dir == kCCW_SkRotationDirection)
+ y = -y;
+
+ // what octant (quadratic curve) is [xy] in?
+ int oct = 0;
+ bool sameSign = true;
+
+ if (0 == y)
+ {
+ oct = 4; // 180
+ SkASSERT(SkScalarAbs(x + SK_Scalar1) <= SK_ScalarNearlyZero);
+ }
+ else if (0 == x)
+ {
+ SkASSERT(absY - SK_Scalar1 <= SK_ScalarNearlyZero);
+ if (y > 0)
+ oct = 2; // 90
+ else
+ oct = 6; // 270
+ }
+ else
+ {
+ if (y < 0)
+ oct += 4;
+ if ((x < 0) != (y < 0))
+ {
+ oct += 2;
+ sameSign = false;
+ }
+ if ((absX < absY) == sameSign)
+ oct += 1;
+ }
+
+ int wholeCount = oct << 1;
+ memcpy(quadPoints, gQuadCirclePts, (wholeCount + 1) * sizeof(SkPoint));
+
+ const SkPoint* arc = &gQuadCirclePts[wholeCount];
+ if (truncate_last_curve(arc, x, y, &quadPoints[wholeCount + 1]))
+ {
+ wholeCount += 2;
+ }
+ pointCount = wholeCount + 1;
+ }
+
+ // now handle counter-clockwise and the initial unitStart rotation
+ SkMatrix matrix;
+ matrix.setSinCos(uStart.fY, uStart.fX);
+ if (dir == kCCW_SkRotationDirection) {
+ matrix.preScale(SK_Scalar1, -SK_Scalar1);
+ }
+ if (userMatrix) {
+ matrix.postConcat(*userMatrix);
+ }
+ matrix.mapPoints(quadPoints, pointCount);
+ return pointCount;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// F = (A (1 - t)^2 + C t^2 + 2 B (1 - t) t w)
+// ------------------------------------------
+// ((1 - t)^2 + t^2 + 2 (1 - t) t w)
+//
+// = {t^2 (P0 + P2 - 2 P1 w), t (-2 P0 + 2 P1 w), P0}
+// ------------------------------------------------
+// {t^2 (2 - 2 w), t (-2 + 2 w), 1}
+//
+
+// Take the parametric specification for the conic (either X or Y) and return
+// in coeff[] the coefficients for the simple quadratic polynomial
+// coeff[0] for t^2
+// coeff[1] for t
+// coeff[2] for constant term
+//
+static SkScalar conic_eval_pos(const SkScalar src[], SkScalar w, SkScalar t) {
+ SkASSERT(src);
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ SkScalar src2w = SkScalarMul(src[2], w);
+ SkScalar C = src[0];
+ SkScalar A = src[4] - 2 * src2w + C;
+ SkScalar B = 2 * (src2w - C);
+ SkScalar numer = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
+
+ B = 2 * (w - SK_Scalar1);
+ C = SK_Scalar1;
+ A = -B;
+ SkScalar denom = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
+
+ return SkScalarDiv(numer, denom);
+}
+
+// F' = 2 (C t (1 + t (-1 + w)) - A (-1 + t) (t (-1 + w) - w) + B (1 - 2 t) w)
+//
+// t^2 : (2 P0 - 2 P2 - 2 P0 w + 2 P2 w)
+// t^1 : (-2 P0 + 2 P2 + 4 P0 w - 4 P1 w)
+// t^0 : -2 P0 w + 2 P1 w
+//
+// We disregard magnitude, so we can freely ignore the denominator of F', and
+// divide the numerator by 2
+//
+// coeff[0] for t^2
+// coeff[1] for t^1
+// coeff[2] for t^0
+//
+static void conic_deriv_coeff(const SkScalar src[], SkScalar w, SkScalar coeff[3]) {
+ const SkScalar P20 = src[4] - src[0];
+ const SkScalar P10 = src[2] - src[0];
+ const SkScalar wP10 = w * P10;
+ coeff[0] = w * P20 - P20;
+ coeff[1] = P20 - 2 * wP10;
+ coeff[2] = wP10;
+}
+
+static SkScalar conic_eval_tan(const SkScalar coord[], SkScalar w, SkScalar t) {
+ SkScalar coeff[3];
+ conic_deriv_coeff(coord, w, coeff);
+ return t * (t * coeff[0] + coeff[1]) + coeff[2];
+}
+
+static bool conic_find_extrema(const SkScalar src[], SkScalar w, SkScalar* t) {
+ SkScalar coeff[3];
+ conic_deriv_coeff(src, w, coeff);
+
+ SkScalar tValues[2];
+ int roots = SkFindUnitQuadRoots(coeff[0], coeff[1], coeff[2], tValues);
+ SkASSERT(0 == roots || 1 == roots);
+
+ if (1 == roots) {
+ *t = tValues[0];
+ return true;
+ }
+ return false;
+}
+
+struct SkP3D {
+ SkScalar fX, fY, fZ;
+
+ void set(SkScalar x, SkScalar y, SkScalar z) {
+ fX = x; fY = y; fZ = z;
+ }
+
+ void projectDown(SkPoint* dst) const {
+ dst->set(fX / fZ, fY / fZ);
+ }
+};
+
+// we just return the middle 3 points, since the first and last are dups of src
+//
+static void p3d_interp(const SkScalar src[3], SkScalar dst[3], SkScalar t) {
+ SkScalar ab = SkScalarInterp(src[0], src[3], t);
+ SkScalar bc = SkScalarInterp(src[3], src[6], t);
+ dst[0] = ab;
+ dst[3] = SkScalarInterp(ab, bc, t);
+ dst[6] = bc;
+}
+
+static void ratquad_mapTo3D(const SkPoint src[3], SkScalar w, SkP3D dst[]) {
+ dst[0].set(src[0].fX * 1, src[0].fY * 1, 1);
+ dst[1].set(src[1].fX * w, src[1].fY * w, w);
+ dst[2].set(src[2].fX * 1, src[2].fY * 1, 1);
+}
+
+void SkConic::evalAt(SkScalar t, SkPoint* pt, SkVector* tangent) const {
+ SkASSERT(t >= 0 && t <= SK_Scalar1);
+
+ if (pt) {
+ pt->set(conic_eval_pos(&fPts[0].fX, fW, t),
+ conic_eval_pos(&fPts[0].fY, fW, t));
+ }
+ if (tangent) {
+ tangent->set(conic_eval_tan(&fPts[0].fX, fW, t),
+ conic_eval_tan(&fPts[0].fY, fW, t));
+ }
+}
+
+void SkConic::chopAt(SkScalar t, SkConic dst[2]) const {
+ SkP3D tmp[3], tmp2[3];
+
+ ratquad_mapTo3D(fPts, fW, tmp);
+
+ p3d_interp(&tmp[0].fX, &tmp2[0].fX, t);
+ p3d_interp(&tmp[0].fY, &tmp2[0].fY, t);
+ p3d_interp(&tmp[0].fZ, &tmp2[0].fZ, t);
+
+ dst[0].fPts[0] = fPts[0];
+ tmp2[0].projectDown(&dst[0].fPts[1]);
+ tmp2[1].projectDown(&dst[0].fPts[2]); dst[1].fPts[0] = dst[0].fPts[2];
+ tmp2[2].projectDown(&dst[1].fPts[1]);
+ dst[1].fPts[2] = fPts[2];
+
+ // to put in "standard form", where w0 and w2 are both 1, we compute the
+ // new w1 as sqrt(w1*w1/w0*w2)
+ // or
+ // w1 /= sqrt(w0*w2)
+ //
+ // However, in our case, we know that for dst[0], w0 == 1, and for dst[1], w2 == 1
+ //
+ SkScalar root = SkScalarSqrt(tmp2[1].fZ);
+ dst[0].fW = tmp2[0].fZ / root;
+ dst[1].fW = tmp2[2].fZ / root;
+}
+
+static SkScalar subdivide_w_value(SkScalar w) {
+ return SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf);
+}
+
+void SkConic::chop(SkConic dst[2]) const {
+ SkScalar scale = SkScalarInvert(SK_Scalar1 + fW);
+ SkScalar p1x = fW * fPts[1].fX;
+ SkScalar p1y = fW * fPts[1].fY;
+ SkScalar mx = (fPts[0].fX + 2 * p1x + fPts[2].fX) * scale * SK_ScalarHalf;
+ SkScalar my = (fPts[0].fY + 2 * p1y + fPts[2].fY) * scale * SK_ScalarHalf;
+
+ dst[0].fPts[0] = fPts[0];
+ dst[0].fPts[1].set((fPts[0].fX + p1x) * scale,
+ (fPts[0].fY + p1y) * scale);
+ dst[0].fPts[2].set(mx, my);
+
+ dst[1].fPts[0].set(mx, my);
+ dst[1].fPts[1].set((p1x + fPts[2].fX) * scale,
+ (p1y + fPts[2].fY) * scale);
+ dst[1].fPts[2] = fPts[2];
+
+ dst[0].fW = dst[1].fW = subdivide_w_value(fW);
+}
+
+/*
+ * "High order approximation of conic sections by quadratic splines"
+ * by Michael Floater, 1993
+ */
+#define AS_QUAD_ERROR_SETUP \
+ SkScalar a = fW - 1; \
+ SkScalar k = a / (4 * (2 + a)); \
+ SkScalar x = k * (fPts[0].fX - 2 * fPts[1].fX + fPts[2].fX); \
+ SkScalar y = k * (fPts[0].fY - 2 * fPts[1].fY + fPts[2].fY);
+
+void SkConic::computeAsQuadError(SkVector* err) const {
+ AS_QUAD_ERROR_SETUP
+ err->set(x, y);
+}
+
+bool SkConic::asQuadTol(SkScalar tol) const {
+ AS_QUAD_ERROR_SETUP
+ return (x * x + y * y) <= tol * tol;
+}
+
+int SkConic::computeQuadPOW2(SkScalar tol) const {
+ AS_QUAD_ERROR_SETUP
+ SkScalar error = SkScalarSqrt(x * x + y * y) - tol;
+
+ if (error <= 0) {
+ return 0;
+ }
+ uint32_t ierr = (uint32_t)error;
+ return (34 - SkCLZ(ierr)) >> 1;
+}
+
+static SkPoint* subdivide(const SkConic& src, SkPoint pts[], int level) {
+ SkASSERT(level >= 0);
+
+ if (0 == level) {
+ memcpy(pts, &src.fPts[1], 2 * sizeof(SkPoint));
+ return pts + 2;
+ } else {
+ SkConic dst[2];
+ src.chop(dst);
+ --level;
+ pts = subdivide(dst[0], pts, level);
+ return subdivide(dst[1], pts, level);
+ }
+}
+
+int SkConic::chopIntoQuadsPOW2(SkPoint pts[], int pow2) const {
+ SkASSERT(pow2 >= 0);
+ *pts = fPts[0];
+ SkDEBUGCODE(SkPoint* endPts =) subdivide(*this, pts + 1, pow2);
+ SkASSERT(endPts - pts == (2 * (1 << pow2) + 1));
+ return 1 << pow2;
+}
+
+bool SkConic::findXExtrema(SkScalar* t) const {
+ return conic_find_extrema(&fPts[0].fX, fW, t);
+}
+
+bool SkConic::findYExtrema(SkScalar* t) const {
+ return conic_find_extrema(&fPts[0].fY, fW, t);
+}
+
+bool SkConic::chopAtXExtrema(SkConic dst[2]) const {
+ SkScalar t;
+ if (this->findXExtrema(&t)) {
+ this->chopAt(t, dst);
+ // now clean-up the middle, since we know t was meant to be at
+ // an X-extrema
+ SkScalar value = dst[0].fPts[2].fX;
+ dst[0].fPts[1].fX = value;
+ dst[1].fPts[0].fX = value;
+ dst[1].fPts[1].fX = value;
+ return true;
+ }
+ return false;
+}
+
+bool SkConic::chopAtYExtrema(SkConic dst[2]) const {
+ SkScalar t;
+ if (this->findYExtrema(&t)) {
+ this->chopAt(t, dst);
+ // now clean-up the middle, since we know t was meant to be at
+ // an Y-extrema
+ SkScalar value = dst[0].fPts[2].fY;
+ dst[0].fPts[1].fY = value;
+ dst[1].fPts[0].fY = value;
+ dst[1].fPts[1].fY = value;
+ return true;
+ }
+ return false;
+}
+
+void SkConic::computeTightBounds(SkRect* bounds) const {
+ SkPoint pts[4];
+ pts[0] = fPts[0];
+ pts[1] = fPts[2];
+ int count = 2;
+
+ SkScalar t;
+ if (this->findXExtrema(&t)) {
+ this->evalAt(t, &pts[count++]);
+ }
+ if (this->findYExtrema(&t)) {
+ this->evalAt(t, &pts[count++]);
+ }
+ bounds->set(pts, count);
+}
+
+void SkConic::computeFastBounds(SkRect* bounds) const {
+ bounds->set(fPts, 3);
+}
diff --git a/core/SkGlyph.h b/core/SkGlyph.h
new file mode 100644
index 00000000..649fa7dd
--- /dev/null
+++ b/core/SkGlyph.h
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+
+#ifndef SkGlyph_DEFINED
+#define SkGlyph_DEFINED
+
+#include "SkTypes.h"
+#include "SkFixed.h"
+#include "SkMask.h"
+
+class SkPath;
+
+// needs to be != to any valid SkMask::Format
+#define MASK_FORMAT_UNKNOWN (0xFF)
+#define MASK_FORMAT_JUST_ADVANCE MASK_FORMAT_UNKNOWN
+
+#define kMaxGlyphWidth (1<<13)
+
+struct SkGlyph {
+ void* fImage;
+ SkPath* fPath;
+ SkFixed fAdvanceX, fAdvanceY;
+
+ uint32_t fID;
+ uint16_t fWidth, fHeight;
+ int16_t fTop, fLeft;
+
+ uint8_t fMaskFormat;
+ int8_t fRsbDelta, fLsbDelta; // used by auto-kerning
+
+ void init(uint32_t id) {
+ fID = id;
+ fImage = NULL;
+ fPath = NULL;
+ fMaskFormat = MASK_FORMAT_UNKNOWN;
+ }
+
+ /**
+ * Compute the rowbytes for the specified width and mask-format.
+ */
+ static unsigned ComputeRowBytes(unsigned width, SkMask::Format format) {
+ unsigned rb = width;
+ if (SkMask::kBW_Format == format) {
+ rb = (rb + 7) >> 3;
+ } else if (SkMask::kARGB32_Format == format ||
+ SkMask::kLCD32_Format == format)
+ {
+ rb <<= 2;
+ } else if (SkMask::kLCD16_Format == format) {
+ rb = SkAlign4(rb << 1);
+ } else {
+ rb = SkAlign4(rb);
+ }
+ return rb;
+ }
+
+ unsigned rowBytes() const {
+ return ComputeRowBytes(fWidth, (SkMask::Format)fMaskFormat);
+ }
+
+ bool isJustAdvance() const {
+ return MASK_FORMAT_JUST_ADVANCE == fMaskFormat;
+ }
+
+ bool isFullMetrics() const {
+ return MASK_FORMAT_JUST_ADVANCE != fMaskFormat;
+ }
+
+ uint16_t getGlyphID() const {
+ return ID2Code(fID);
+ }
+
+ unsigned getGlyphID(unsigned baseGlyphCount) const {
+ unsigned code = ID2Code(fID);
+ SkASSERT(code >= baseGlyphCount);
+ return code - baseGlyphCount;
+ }
+
+ unsigned getSubX() const {
+ return ID2SubX(fID);
+ }
+
+ SkFixed getSubXFixed() const {
+ return SubToFixed(ID2SubX(fID));
+ }
+
+ SkFixed getSubYFixed() const {
+ return SubToFixed(ID2SubY(fID));
+ }
+
+ size_t computeImageSize() const;
+
+ /** Call this to set all of the metrics fields to 0 (e.g. if the scaler
+ encounters an error measuring a glyph). Note: this does not alter the
+ fImage, fPath, fID, fMaskFormat fields.
+ */
+ void zeroMetrics();
+
+ enum {
+ kSubBits = 2,
+ kSubMask = ((1 << kSubBits) - 1),
+ kSubShift = 24, // must be large enough for glyphs and unichars
+ kCodeMask = ((1 << kSubShift) - 1),
+ // relative offsets for X and Y subpixel bits
+ kSubShiftX = kSubBits,
+ kSubShiftY = 0
+ };
+
+ static unsigned ID2Code(uint32_t id) {
+ return id & kCodeMask;
+ }
+
+ static unsigned ID2SubX(uint32_t id) {
+ return id >> (kSubShift + kSubShiftX);
+ }
+
+ static unsigned ID2SubY(uint32_t id) {
+ return (id >> (kSubShift + kSubShiftY)) & kSubMask;
+ }
+
+ static unsigned FixedToSub(SkFixed n) {
+ return (n >> (16 - kSubBits)) & kSubMask;
+ }
+
+ static SkFixed SubToFixed(unsigned sub) {
+ SkASSERT(sub <= kSubMask);
+ return sub << (16 - kSubBits);
+ }
+
+ static uint32_t MakeID(unsigned code) {
+ return code;
+ }
+
+ static uint32_t MakeID(unsigned code, SkFixed x, SkFixed y) {
+ SkASSERT(code <= kCodeMask);
+ x = FixedToSub(x);
+ y = FixedToSub(y);
+ return (x << (kSubShift + kSubShiftX)) |
+ (y << (kSubShift + kSubShiftY)) |
+ code;
+ }
+
+ void toMask(SkMask* mask) const;
+};
+
+#endif
diff --git a/core/SkGlyphCache.cpp b/core/SkGlyphCache.cpp
new file mode 100644
index 00000000..d366e125
--- /dev/null
+++ b/core/SkGlyphCache.cpp
@@ -0,0 +1,788 @@
+
+/*
+ * 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 "SkGlyphCache.h"
+#include "SkGraphics.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkTemplates.h"
+#include "SkTLS.h"
+#include "SkTypeface.h"
+
+//#define SPEW_PURGE_STATUS
+//#define USE_CACHE_HASH
+//#define RECORD_HASH_EFFICIENCY
+
+bool gSkSuppressFontCachePurgeSpew;
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef RECORD_HASH_EFFICIENCY
+ static uint32_t gHashSuccess;
+ static uint32_t gHashCollision;
+
+ static void RecordHashSuccess() {
+ gHashSuccess += 1;
+ }
+
+ static void RecordHashCollisionIf(bool pred) {
+ if (pred) {
+ gHashCollision += 1;
+
+ uint32_t total = gHashSuccess + gHashCollision;
+ SkDebugf("Font Cache Hash success rate: %d%%\n",
+ 100 * gHashSuccess / total);
+ }
+ }
+#else
+ #define RecordHashSuccess() (void)0
+ #define RecordHashCollisionIf(pred) (void)0
+#endif
+#define RecordHashCollision() RecordHashCollisionIf(true)
+
+///////////////////////////////////////////////////////////////////////////////
+
+// so we don't grow our arrays a lot
+#define kMinGlyphCount 16
+#define kMinGlyphImageSize (16*2)
+#define kMinAllocAmount ((sizeof(SkGlyph) + kMinGlyphImageSize) * kMinGlyphCount)
+
+SkGlyphCache::SkGlyphCache(SkTypeface* typeface, const SkDescriptor* desc, SkScalerContext* ctx)
+ : fScalerContext(ctx), fGlyphAlloc(kMinAllocAmount) {
+ SkASSERT(typeface);
+ SkASSERT(desc);
+ SkASSERT(ctx);
+
+ fPrev = fNext = NULL;
+
+ fDesc = desc->copy();
+ fScalerContext->getFontMetrics(&fFontMetrics);
+
+ // init to 0 so that all of the pointers will be null
+ memset(fGlyphHash, 0, sizeof(fGlyphHash));
+ // init with 0xFF so that the charCode field will be -1, which is invalid
+ memset(fCharToGlyphHash, 0xFF, sizeof(fCharToGlyphHash));
+
+ fMemoryUsed = sizeof(*this);
+
+ fGlyphArray.setReserve(kMinGlyphCount);
+
+ fMetricsCount = 0;
+ fAdvanceCount = 0;
+ fAuxProcList = NULL;
+}
+
+SkGlyphCache::~SkGlyphCache() {
+#if 0
+ {
+ size_t ptrMem = fGlyphArray.count() * sizeof(SkGlyph*);
+ size_t glyphAlloc = fGlyphAlloc.totalCapacity();
+ size_t glyphHashUsed = 0;
+ size_t uniHashUsed = 0;
+ for (int i = 0; i < kHashCount; ++i) {
+ glyphHashUsed += fGlyphHash[i] ? sizeof(fGlyphHash[0]) : 0;
+ uniHashUsed += fCharToGlyphHash[i].fID != 0xFFFFFFFF ? sizeof(fCharToGlyphHash[0]) : 0;
+ }
+ size_t glyphUsed = fGlyphArray.count() * sizeof(SkGlyph);
+ size_t imageUsed = 0;
+ for (int i = 0; i < fGlyphArray.count(); ++i) {
+ const SkGlyph& g = *fGlyphArray[i];
+ if (g.fImage) {
+ imageUsed += g.fHeight * g.rowBytes();
+ }
+ }
+
+ printf("glyphPtrArray,%zu, Alloc,%zu, imageUsed,%zu, glyphUsed,%zu, glyphHashAlloc,%zu, glyphHashUsed,%zu, unicharHashAlloc,%zu, unicharHashUsed,%zu\n",
+ ptrMem, glyphAlloc, imageUsed, glyphUsed, sizeof(fGlyphHash), glyphHashUsed, sizeof(fCharToGlyphHash), uniHashUsed);
+
+ }
+#endif
+ SkGlyph** gptr = fGlyphArray.begin();
+ SkGlyph** stop = fGlyphArray.end();
+ while (gptr < stop) {
+ SkPath* path = (*gptr)->fPath;
+ if (path) {
+ SkDELETE(path);
+ }
+ gptr += 1;
+ }
+ SkDescriptor::Free(fDesc);
+ SkDELETE(fScalerContext);
+ this->invokeAndRemoveAuxProcs();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+#define VALIDATE() AutoValidate av(this)
+#else
+#define VALIDATE()
+#endif
+
+uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(charCode);
+ const CharGlyphRec& rec = fCharToGlyphHash[ID2HashIndex(id)];
+
+ if (rec.fID == id) {
+ return rec.fGlyph->getGlyphID();
+ } else {
+ return fScalerContext->charToGlyphID(charCode);
+ }
+}
+
+SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) {
+ return fScalerContext->glyphIDToChar(glyphID);
+}
+
+unsigned SkGlyphCache::getGlyphCount() {
+ return fScalerContext->getGlyphCount();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(charCode);
+ CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
+
+ if (rec->fID != id) {
+ // this ID is based on the UniChar
+ rec->fID = id;
+ // this ID is based on the glyph index
+ id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
+ rec->fGlyph = this->lookupMetrics(id, kJustAdvance_MetricsType);
+ }
+ return *rec->fGlyph;
+}
+
+const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(glyphID);
+ unsigned index = ID2HashIndex(id);
+ SkGlyph* glyph = fGlyphHash[index];
+
+ if (NULL == glyph || glyph->fID != id) {
+ glyph = this->lookupMetrics(glyphID, kJustAdvance_MetricsType);
+ fGlyphHash[index] = glyph;
+ }
+ return *glyph;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(charCode);
+ CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
+
+ if (rec->fID != id) {
+ RecordHashCollisionIf(rec->fGlyph != NULL);
+ // this ID is based on the UniChar
+ rec->fID = id;
+ // this ID is based on the glyph index
+ id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
+ rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
+ } else {
+ RecordHashSuccess();
+ if (rec->fGlyph->isJustAdvance()) {
+ fScalerContext->getMetrics(rec->fGlyph);
+ }
+ }
+ SkASSERT(rec->fGlyph->isFullMetrics());
+ return *rec->fGlyph;
+}
+
+const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode,
+ SkFixed x, SkFixed y) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(charCode, x, y);
+ CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
+
+ if (rec->fID != id) {
+ RecordHashCollisionIf(rec->fGlyph != NULL);
+ // this ID is based on the UniChar
+ rec->fID = id;
+ // this ID is based on the glyph index
+ id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
+ rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
+ } else {
+ RecordHashSuccess();
+ if (rec->fGlyph->isJustAdvance()) {
+ fScalerContext->getMetrics(rec->fGlyph);
+ }
+ }
+ SkASSERT(rec->fGlyph->isFullMetrics());
+ return *rec->fGlyph;
+}
+
+const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(glyphID);
+ unsigned index = ID2HashIndex(id);
+ SkGlyph* glyph = fGlyphHash[index];
+
+ if (NULL == glyph || glyph->fID != id) {
+ RecordHashCollisionIf(glyph != NULL);
+ glyph = this->lookupMetrics(glyphID, kFull_MetricsType);
+ fGlyphHash[index] = glyph;
+ } else {
+ RecordHashSuccess();
+ if (glyph->isJustAdvance()) {
+ fScalerContext->getMetrics(glyph);
+ }
+ }
+ SkASSERT(glyph->isFullMetrics());
+ return *glyph;
+}
+
+const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID,
+ SkFixed x, SkFixed y) {
+ VALIDATE();
+ uint32_t id = SkGlyph::MakeID(glyphID, x, y);
+ unsigned index = ID2HashIndex(id);
+ SkGlyph* glyph = fGlyphHash[index];
+
+ if (NULL == glyph || glyph->fID != id) {
+ RecordHashCollisionIf(glyph != NULL);
+ glyph = this->lookupMetrics(id, kFull_MetricsType);
+ fGlyphHash[index] = glyph;
+ } else {
+ RecordHashSuccess();
+ if (glyph->isJustAdvance()) {
+ fScalerContext->getMetrics(glyph);
+ }
+ }
+ SkASSERT(glyph->isFullMetrics());
+ return *glyph;
+}
+
+SkGlyph* SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) {
+ SkGlyph* glyph;
+
+ int hi = 0;
+ int count = fGlyphArray.count();
+
+ if (count) {
+ SkGlyph** gptr = fGlyphArray.begin();
+ int lo = 0;
+
+ hi = count - 1;
+ while (lo < hi) {
+ int mid = (hi + lo) >> 1;
+ if (gptr[mid]->fID < id) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ glyph = gptr[hi];
+ if (glyph->fID == id) {
+ if (kFull_MetricsType == mtype && glyph->isJustAdvance()) {
+ fScalerContext->getMetrics(glyph);
+ }
+ return glyph;
+ }
+
+ // check if we need to bump hi before falling though to the allocator
+ if (glyph->fID < id) {
+ hi += 1;
+ }
+ }
+
+ // not found, but hi tells us where to inser the new glyph
+ fMemoryUsed += sizeof(SkGlyph);
+
+ glyph = (SkGlyph*)fGlyphAlloc.alloc(sizeof(SkGlyph),
+ SkChunkAlloc::kThrow_AllocFailType);
+ glyph->init(id);
+ *fGlyphArray.insert(hi) = glyph;
+
+ if (kJustAdvance_MetricsType == mtype) {
+ fScalerContext->getAdvance(glyph);
+ fAdvanceCount += 1;
+ } else {
+ SkASSERT(kFull_MetricsType == mtype);
+ fScalerContext->getMetrics(glyph);
+ fMetricsCount += 1;
+ }
+
+ return glyph;
+}
+
+const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
+ if (glyph.fWidth > 0 && glyph.fWidth < kMaxGlyphWidth) {
+ if (glyph.fImage == NULL) {
+ size_t size = glyph.computeImageSize();
+ const_cast<SkGlyph&>(glyph).fImage = fGlyphAlloc.alloc(size,
+ SkChunkAlloc::kReturnNil_AllocFailType);
+ // check that alloc() actually succeeded
+ if (glyph.fImage) {
+ fScalerContext->getImage(glyph);
+ // TODO: the scaler may have changed the maskformat during
+ // getImage (e.g. from AA or LCD to BW) which means we may have
+ // overallocated the buffer. Check if the new computedImageSize
+ // is smaller, and if so, strink the alloc size in fImageAlloc.
+ fMemoryUsed += size;
+ }
+ }
+ }
+ return glyph.fImage;
+}
+
+const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) {
+ if (glyph.fWidth) {
+ if (glyph.fPath == NULL) {
+ const_cast<SkGlyph&>(glyph).fPath = SkNEW(SkPath);
+ fScalerContext->getPath(glyph, glyph.fPath);
+ fMemoryUsed += sizeof(SkPath) +
+ glyph.fPath->countPoints() * sizeof(SkPoint);
+ }
+ }
+ return glyph.fPath;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkGlyphCache::getAuxProcData(void (*proc)(void*), void** dataPtr) const {
+ const AuxProcRec* rec = fAuxProcList;
+ while (rec) {
+ if (rec->fProc == proc) {
+ if (dataPtr) {
+ *dataPtr = rec->fData;
+ }
+ return true;
+ }
+ rec = rec->fNext;
+ }
+ return false;
+}
+
+void SkGlyphCache::setAuxProc(void (*proc)(void*), void* data) {
+ if (proc == NULL) {
+ return;
+ }
+
+ AuxProcRec* rec = fAuxProcList;
+ while (rec) {
+ if (rec->fProc == proc) {
+ rec->fData = data;
+ return;
+ }
+ rec = rec->fNext;
+ }
+ // not found, create a new rec
+ rec = SkNEW(AuxProcRec);
+ rec->fProc = proc;
+ rec->fData = data;
+ rec->fNext = fAuxProcList;
+ fAuxProcList = rec;
+}
+
+void SkGlyphCache::invokeAndRemoveAuxProcs() {
+ AuxProcRec* rec = fAuxProcList;
+ while (rec) {
+ rec->fProc(rec->fData);
+ AuxProcRec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_DEFAULT_FONT_CACHE_LIMIT
+ #define SK_DEFAULT_FONT_CACHE_LIMIT (2 * 1024 * 1024)
+#endif
+
+#ifdef USE_CACHE_HASH
+ #define HASH_BITCOUNT 6
+ #define HASH_COUNT (1 << HASH_BITCOUNT)
+ #define HASH_MASK (HASH_COUNT - 1)
+
+ static unsigned desc_to_hashindex(const SkDescriptor* desc)
+ {
+ SkASSERT(HASH_MASK < 256); // since our munging reduces to 8 bits
+
+ uint32_t n = *(const uint32_t*)desc; //desc->getChecksum();
+ SkASSERT(n == desc->getChecksum());
+
+ // don't trust that the low bits of checksum vary enough, so...
+ n ^= (n >> 24) ^ (n >> 16) ^ (n >> 8) ^ (n >> 30);
+
+ return n & HASH_MASK;
+ }
+#endif
+
+#include "SkThread.h"
+
+class SkGlyphCache_Globals {
+public:
+ enum UseMutex {
+ kNo_UseMutex, // thread-local cache
+ kYes_UseMutex // shared cache
+ };
+
+ SkGlyphCache_Globals(UseMutex um) {
+ fHead = NULL;
+ fTotalMemoryUsed = 0;
+ fFontCacheLimit = SK_DEFAULT_FONT_CACHE_LIMIT;
+ fMutex = (kYes_UseMutex == um) ? SkNEW(SkMutex) : NULL;
+
+#ifdef USE_CACHE_HASH
+ sk_bzero(fHash, sizeof(fHash));
+#endif
+ }
+
+ ~SkGlyphCache_Globals() {
+ SkGlyphCache* cache = fHead;
+ while (cache) {
+ SkGlyphCache* next = cache->fNext;
+ SkDELETE(cache);
+ cache = next;
+ }
+
+ SkDELETE(fMutex);
+ }
+
+ SkMutex* fMutex;
+ SkGlyphCache* fHead;
+ size_t fTotalMemoryUsed;
+#ifdef USE_CACHE_HASH
+ SkGlyphCache* fHash[HASH_COUNT];
+#endif
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+ size_t getFontCacheLimit() const { return fFontCacheLimit; }
+ size_t setFontCacheLimit(size_t limit);
+ void purgeAll(); // does not change budget
+
+ // can return NULL
+ static SkGlyphCache_Globals* FindTLS() {
+ return (SkGlyphCache_Globals*)SkTLS::Find(CreateTLS);
+ }
+
+ static SkGlyphCache_Globals& GetTLS() {
+ return *(SkGlyphCache_Globals*)SkTLS::Get(CreateTLS, DeleteTLS);
+ }
+
+ static void DeleteTLS() { SkTLS::Delete(CreateTLS); }
+
+private:
+ size_t fFontCacheLimit;
+
+ static void* CreateTLS() {
+ return SkNEW_ARGS(SkGlyphCache_Globals, (kNo_UseMutex));
+ }
+
+ static void DeleteTLS(void* ptr) {
+ SkDELETE((SkGlyphCache_Globals*)ptr);
+ }
+};
+
+size_t SkGlyphCache_Globals::setFontCacheLimit(size_t newLimit) {
+ static const size_t minLimit = 256 * 1024;
+ if (newLimit < minLimit) {
+ newLimit = minLimit;
+ }
+
+ size_t prevLimit = fFontCacheLimit;
+ fFontCacheLimit = newLimit;
+
+ size_t currUsed = fTotalMemoryUsed;
+ if (currUsed > newLimit) {
+ SkAutoMutexAcquire ac(fMutex);
+ SkGlyphCache::InternalFreeCache(this, currUsed - newLimit);
+ }
+ return prevLimit;
+}
+
+void SkGlyphCache_Globals::purgeAll() {
+ SkAutoMutexAcquire ac(fMutex);
+ SkGlyphCache::InternalFreeCache(this, fTotalMemoryUsed);
+}
+
+// Returns the shared globals
+static SkGlyphCache_Globals& getSharedGlobals() {
+ // we leak this, so we don't incur any shutdown cost of the destructor
+ static SkGlyphCache_Globals* gGlobals = SkNEW_ARGS(SkGlyphCache_Globals,
+ (SkGlyphCache_Globals::kYes_UseMutex));
+ return *gGlobals;
+}
+
+// Returns the TLS globals (if set), or the shared globals
+static SkGlyphCache_Globals& getGlobals() {
+ SkGlyphCache_Globals* tls = SkGlyphCache_Globals::FindTLS();
+ return tls ? *tls : getSharedGlobals();
+}
+
+void SkGlyphCache::VisitAllCaches(bool (*proc)(SkGlyphCache*, void*),
+ void* context) {
+ SkGlyphCache_Globals& globals = getGlobals();
+ SkAutoMutexAcquire ac(globals.fMutex);
+ SkGlyphCache* cache;
+
+ globals.validate();
+
+ for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
+ if (proc(cache, context)) {
+ break;
+ }
+ }
+
+ globals.validate();
+}
+
+/* This guy calls the visitor from within the mutext lock, so the visitor
+ cannot:
+ - take too much time
+ - try to acquire the mutext again
+ - call a fontscaler (which might call into the cache)
+*/
+SkGlyphCache* SkGlyphCache::VisitCache(SkTypeface* typeface,
+ const SkDescriptor* desc,
+ bool (*proc)(const SkGlyphCache*, void*),
+ void* context) {
+ if (!typeface) {
+ typeface = SkTypeface::GetDefaultTypeface();
+ }
+ SkASSERT(desc);
+
+ SkGlyphCache_Globals& globals = getGlobals();
+ SkAutoMutexAcquire ac(globals.fMutex);
+ SkGlyphCache* cache;
+ bool insideMutex = true;
+
+ globals.validate();
+
+#ifdef USE_CACHE_HASH
+ SkGlyphCache** hash = globals.fHash;
+ unsigned index = desc_to_hashindex(desc);
+ cache = hash[index];
+ if (cache && *cache->fDesc == *desc) {
+ cache->detach(&globals.fHead);
+ goto FOUND_IT;
+ }
+#endif
+
+ for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
+ if (cache->fDesc->equals(*desc)) {
+ cache->detach(&globals.fHead);
+ goto FOUND_IT;
+ }
+ }
+
+ /* Release the mutex now, before we create a new entry (which might have
+ side-effects like trying to access the cache/mutex (yikes!)
+ */
+ ac.release(); // release the mutex now
+ insideMutex = false; // can't use globals anymore
+
+ // Check if we can create a scaler-context before creating the glyphcache.
+ // If not, we may have exhausted OS/font resources, so try purging the
+ // cache once and try again.
+ {
+ // pass true the first time, to notice if the scalercontext failed,
+ // so we can try the purge.
+ SkScalerContext* ctx = typeface->createScalerContext(desc, true);
+ if (!ctx) {
+ getSharedGlobals().purgeAll();
+ ctx = typeface->createScalerContext(desc, false);
+ SkASSERT(ctx);
+ }
+ cache = SkNEW_ARGS(SkGlyphCache, (typeface, desc, ctx));
+ }
+
+FOUND_IT:
+
+ AutoValidate av(cache);
+
+ if (proc(cache, context)) { // stay detached
+ if (insideMutex) {
+ SkASSERT(globals.fTotalMemoryUsed >= cache->fMemoryUsed);
+ globals.fTotalMemoryUsed -= cache->fMemoryUsed;
+#ifdef USE_CACHE_HASH
+ hash[index] = NULL;
+#endif
+ }
+ } else { // reattach
+ if (insideMutex) {
+ cache->attachToHead(&globals.fHead);
+#ifdef USE_CACHE_HASH
+ hash[index] = cache;
+#endif
+ } else {
+ AttachCache(cache);
+ }
+ cache = NULL;
+ }
+ return cache;
+}
+
+void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
+ SkASSERT(cache);
+ SkASSERT(cache->fNext == NULL);
+
+ SkGlyphCache_Globals& globals = getGlobals();
+ SkAutoMutexAcquire ac(globals.fMutex);
+
+ globals.validate();
+ cache->validate();
+
+ // if we have a fixed budget for our cache, do a purge here
+ {
+ size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed;
+ size_t budgeted = globals.getFontCacheLimit();
+ if (allocated > budgeted) {
+ (void)InternalFreeCache(&globals, allocated - budgeted);
+ }
+ }
+
+ cache->attachToHead(&globals.fHead);
+ globals.fTotalMemoryUsed += cache->fMemoryUsed;
+
+#ifdef USE_CACHE_HASH
+ unsigned index = desc_to_hashindex(cache->fDesc);
+ SkASSERT(globals.fHash[index] != cache);
+ globals.fHash[index] = cache;
+#endif
+
+ globals.validate();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) {
+ if (cache) {
+ while (cache->fNext) {
+ cache = cache->fNext;
+ }
+ }
+ return cache;
+}
+
+#ifdef SK_DEBUG
+void SkGlyphCache_Globals::validate() const {
+ size_t computed = 0;
+
+ const SkGlyphCache* head = fHead;
+ while (head != NULL) {
+ computed += head->fMemoryUsed;
+ head = head->fNext;
+ }
+
+ if (fTotalMemoryUsed != computed) {
+ printf("total %d, computed %d\n", (int)fTotalMemoryUsed, (int)computed);
+ }
+ SkASSERT(fTotalMemoryUsed == computed);
+}
+#endif
+
+size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals,
+ size_t bytesNeeded) {
+ globals->validate();
+
+ size_t bytesFreed = 0;
+ int count = 0;
+
+ // don't do any "small" purges
+ size_t minToPurge = globals->fTotalMemoryUsed >> 2;
+ if (bytesNeeded < minToPurge)
+ bytesNeeded = minToPurge;
+
+ SkGlyphCache* cache = FindTail(globals->fHead);
+ while (cache != NULL && bytesFreed < bytesNeeded) {
+ SkGlyphCache* prev = cache->fPrev;
+ bytesFreed += cache->fMemoryUsed;
+
+#ifdef USE_CACHE_HASH
+ unsigned index = desc_to_hashindex(cache->fDesc);
+ if (cache == globals->fHash[index]) {
+ globals->fHash[index] = NULL;
+ }
+#endif
+
+ cache->detach(&globals->fHead);
+ SkDELETE(cache);
+ cache = prev;
+ count += 1;
+ }
+
+ SkASSERT(bytesFreed <= globals->fTotalMemoryUsed);
+ globals->fTotalMemoryUsed -= bytesFreed;
+ globals->validate();
+
+#ifdef SPEW_PURGE_STATUS
+ if (count && !gSkSuppressFontCachePurgeSpew) {
+ SkDebugf("purging %dK from font cache [%d entries]\n",
+ (int)(bytesFreed >> 10), count);
+ }
+#endif
+
+ return bytesFreed;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#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];
+ SkASSERT(glyph);
+ SkASSERT(fGlyphAlloc.contains(glyph));
+ if (glyph->fImage) {
+ SkASSERT(fGlyphAlloc.contains(glyph->fImage));
+ }
+ }
+#endif
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTypefaceCache.h"
+
+size_t SkGraphics::GetFontCacheLimit() {
+ return getSharedGlobals().getFontCacheLimit();
+}
+
+size_t SkGraphics::SetFontCacheLimit(size_t bytes) {
+ return getSharedGlobals().setFontCacheLimit(bytes);
+}
+
+size_t SkGraphics::GetFontCacheUsed() {
+ return getSharedGlobals().fTotalMemoryUsed;
+}
+
+void SkGraphics::PurgeFontCache() {
+ getSharedGlobals().purgeAll();
+ SkTypefaceCache::PurgeAll();
+}
+
+size_t SkGraphics::GetTLSFontCacheLimit() {
+ const SkGlyphCache_Globals* tls = SkGlyphCache_Globals::FindTLS();
+ return tls ? tls->getFontCacheLimit() : 0;
+}
+
+void SkGraphics::SetTLSFontCacheLimit(size_t bytes) {
+ if (0 == bytes) {
+ SkGlyphCache_Globals::DeleteTLS();
+ } else {
+ SkGlyphCache_Globals::GetTLS().setFontCacheLimit(bytes);
+ }
+}
diff --git a/core/SkGlyphCache.h b/core/SkGlyphCache.h
new file mode 100644
index 00000000..3da7a225
--- /dev/null
+++ b/core/SkGlyphCache.h
@@ -0,0 +1,301 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkGlyphCache_DEFINED
+#define SkGlyphCache_DEFINED
+
+#include "SkBitmap.h"
+#include "SkChunkAlloc.h"
+#include "SkDescriptor.h"
+#include "SkGlyph.h"
+#include "SkScalerContext.h"
+#include "SkTemplates.h"
+#include "SkTDArray.h"
+
+struct SkDeviceProperties;
+class SkPaint;
+
+class SkGlyphCache_Globals;
+
+/** \class SkGlyphCache
+
+ This class represents a strike: a specific combination of typeface, size,
+ matrix, etc., and holds the glyphs for that strike. Calling any of the
+ getUnichar.../getGlyphID... methods will return the requested glyph,
+ either instantly if it is already cahced, or by first generating it and then
+ adding it to the strike.
+
+ The strikes are held in a global list, available to all threads. To interact
+ with one, call either VisitCache() or DetachCache().
+*/
+class SkGlyphCache {
+public:
+ /** Returns a glyph with valid fAdvance and fDevKern fields.
+ The remaining fields may be valid, but that is not guaranteed. If you
+ require those, call getUnicharMetrics or getGlyphIDMetrics instead.
+ */
+ const SkGlyph& getUnicharAdvance(SkUnichar);
+ const SkGlyph& getGlyphIDAdvance(uint16_t);
+
+ /** Returns a glyph with all fields valid except fImage and fPath, which
+ may be null. If they are null, call findImage or findPath for those.
+ If they are not null, then they are valid.
+
+ This call is potentially slower than the matching ...Advance call. If
+ you only need the fAdvance/fDevKern fields, call those instead.
+ */
+ const SkGlyph& getUnicharMetrics(SkUnichar);
+ const SkGlyph& getGlyphIDMetrics(uint16_t);
+
+ /** These are variants that take the device position of the glyph. Call
+ these only if you are drawing in subpixel mode. Passing 0, 0 is
+ effectively the same as calling the variants w/o the extra params, tho
+ a tiny bit slower.
+ */
+ const SkGlyph& getUnicharMetrics(SkUnichar, SkFixed x, SkFixed y);
+ const SkGlyph& getGlyphIDMetrics(uint16_t, SkFixed x, SkFixed y);
+
+ /** Return the glyphID for the specified Unichar. If the char has already
+ been seen, use the existing cache entry. If not, ask the scalercontext
+ to compute it for us.
+ */
+ uint16_t unicharToGlyph(SkUnichar);
+
+ /** Map the glyph to its Unicode equivalent. Unmappable glyphs map to
+ a character code of zero.
+ */
+ SkUnichar glyphToUnichar(uint16_t);
+
+ /** Returns the number of glyphs for this strike.
+ */
+ unsigned getGlyphCount();
+
+#ifdef SK_BUILD_FOR_ANDROID
+ /** Returns the base glyph count for this strike.
+ */
+ unsigned getBaseGlyphCount(SkUnichar charCode) const {
+ return fScalerContext->getBaseGlyphCount(charCode);
+ }
+#endif
+
+ /** Return the image associated with the glyph. If it has not been generated
+ this will trigger that.
+ */
+ const void* findImage(const SkGlyph&);
+ /** Return the Path associated with the glyph. If it has not been generated
+ this will trigger that.
+ */
+ const SkPath* findPath(const SkGlyph&);
+
+ /** Return the vertical metrics for this strike.
+ */
+ const SkPaint::FontMetrics& getFontMetrics() const {
+ return fFontMetrics;
+ }
+
+ const SkDescriptor& getDescriptor() const { return *fDesc; }
+
+ SkMask::Format getMaskFormat() const {
+ return fScalerContext->getMaskFormat();
+ }
+
+ bool isSubpixel() const {
+ return fScalerContext->isSubpixel();
+ }
+
+ /* AuxProc/Data allow a client to associate data with this cache entry.
+ Multiple clients can use this, as their data is keyed with a function
+ pointer. In addition to serving as a key, the function pointer is called
+ with the data when the glyphcache object is deleted, so the client can
+ cleanup their data as well. NOTE: the auxProc must not try to access
+ this glyphcache in any way, since it may be in the process of being
+ deleted.
+ */
+
+ //! If the proc is found, return true and set *dataPtr to its data
+ bool getAuxProcData(void (*auxProc)(void*), void** dataPtr) const;
+ //! Add a proc/data pair to the glyphcache. proc should be non-null
+ void setAuxProc(void (*auxProc)(void*), void* auxData);
+
+ SkScalerContext* getScalerContext() const { return fScalerContext; }
+
+ /** Call proc on all cache entries, stopping early if proc returns true.
+ The proc should not create or delete caches, since it could produce
+ deadlock.
+ */
+ static void VisitAllCaches(bool (*proc)(SkGlyphCache*, void*), void* ctx);
+
+ /** Find a matching cache entry, and call proc() with it. If none is found
+ create a new one. If the proc() returns true, detach the cache and
+ return it, otherwise leave it and return NULL.
+ */
+ static SkGlyphCache* VisitCache(SkTypeface*, const SkDescriptor* desc,
+ bool (*proc)(const SkGlyphCache*, void*),
+ void* context);
+
+ /** Given a strike that was returned by either VisitCache() or DetachCache()
+ add it back into the global cache list (after which the caller should
+ not reference it anymore.
+ */
+ static void AttachCache(SkGlyphCache*);
+
+ /** Detach a strike from the global cache matching the specified descriptor.
+ Once detached, it can be queried/modified by the current thread, and
+ when finished, be reattached to the global cache with AttachCache().
+ While detached, if another request is made with the same descriptor,
+ a different strike will be generated. This is fine. It does mean we
+ can have more than 1 strike for the same descriptor, but that will
+ eventually get purged, and the win is that different thread will never
+ block each other while a strike is being used.
+ */
+ static SkGlyphCache* DetachCache(SkTypeface* typeface,
+ const SkDescriptor* desc) {
+ return VisitCache(typeface, desc, DetachProc, NULL);
+ }
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+ class AutoValidate : SkNoncopyable {
+ public:
+ AutoValidate(const SkGlyphCache* cache) : fCache(cache) {
+ if (fCache) {
+ fCache->validate();
+ }
+ }
+ ~AutoValidate() {
+ if (fCache) {
+ fCache->validate();
+ }
+ }
+ void forget() {
+ fCache = NULL;
+ }
+ private:
+ const SkGlyphCache* fCache;
+ };
+
+private:
+ // we take ownership of the scalercontext
+ SkGlyphCache(SkTypeface*, const SkDescriptor*, SkScalerContext*);
+ ~SkGlyphCache();
+
+ enum MetricsType {
+ kJustAdvance_MetricsType,
+ kFull_MetricsType
+ };
+
+ SkGlyph* lookupMetrics(uint32_t id, MetricsType);
+ static bool DetachProc(const SkGlyphCache*, void*) { return true; }
+
+ void detach(SkGlyphCache** head) {
+ if (fPrev) {
+ fPrev->fNext = fNext;
+ } else {
+ *head = fNext;
+ }
+ if (fNext) {
+ fNext->fPrev = fPrev;
+ }
+ fPrev = fNext = NULL;
+ }
+
+ void attachToHead(SkGlyphCache** head) {
+ SkASSERT(NULL == fPrev && NULL == fNext);
+ if (*head) {
+ (*head)->fPrev = this;
+ fNext = *head;
+ }
+ *head = this;
+ }
+
+ SkGlyphCache* fNext, *fPrev;
+ SkDescriptor* fDesc;
+ SkScalerContext* fScalerContext;
+ SkPaint::FontMetrics fFontMetrics;
+
+ enum {
+ kHashBits = 8,
+ kHashCount = 1 << kHashBits,
+ kHashMask = kHashCount - 1
+ };
+ SkGlyph* fGlyphHash[kHashCount];
+ SkTDArray<SkGlyph*> fGlyphArray;
+ SkChunkAlloc fGlyphAlloc;
+
+ int fMetricsCount, fAdvanceCount;
+
+ struct CharGlyphRec {
+ uint32_t fID; // unichar + subpixel
+ SkGlyph* fGlyph;
+ };
+ // no reason to use the same kHashCount as fGlyphHash, but we do for now
+ CharGlyphRec fCharToGlyphHash[kHashCount];
+
+ static inline unsigned ID2HashIndex(uint32_t id) {
+ id ^= id >> 16;
+ id ^= id >> 8;
+ return id & kHashMask;
+ }
+
+ // used to track (approx) how much ram is tied-up in this cache
+ size_t fMemoryUsed;
+
+ struct AuxProcRec {
+ AuxProcRec* fNext;
+ void (*fProc)(void*);
+ void* fData;
+ };
+ AuxProcRec* fAuxProcList;
+ void invokeAndRemoveAuxProcs();
+
+ // This relies on the caller to have already acquired the mutex to access the global cache
+ static size_t InternalFreeCache(SkGlyphCache_Globals*, size_t bytesNeeded);
+
+ inline static SkGlyphCache* FindTail(SkGlyphCache* head);
+
+ friend class SkGlyphCache_Globals;
+};
+
+class SkAutoGlyphCache {
+public:
+ SkAutoGlyphCache(SkGlyphCache* cache) : fCache(cache) {}
+ SkAutoGlyphCache(SkTypeface* typeface, const SkDescriptor* desc) {
+ fCache = SkGlyphCache::DetachCache(typeface, desc);
+ }
+ SkAutoGlyphCache(const SkPaint& paint,
+ const SkDeviceProperties* deviceProperties,
+ const SkMatrix* matrix) {
+ fCache = paint.detachCache(deviceProperties, matrix);
+ }
+ ~SkAutoGlyphCache() {
+ if (fCache) {
+ SkGlyphCache::AttachCache(fCache);
+ }
+ }
+
+ SkGlyphCache* getCache() const { return fCache; }
+
+ void release() {
+ if (fCache) {
+ SkGlyphCache::AttachCache(fCache);
+ fCache = NULL;
+ }
+ }
+
+private:
+ SkGlyphCache* fCache;
+
+ static bool DetachProc(const SkGlyphCache*, void*);
+};
+
+#endif
diff --git a/core/SkGraphics.cpp b/core/SkGraphics.cpp
new file mode 100644
index 00000000..8e7c7cd3
--- /dev/null
+++ b/core/SkGraphics.cpp
@@ -0,0 +1,180 @@
+
+/*
+ * 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 "SkGraphics.h"
+
+#include "Sk64.h"
+#include "SkBlitter.h"
+#include "SkCanvas.h"
+#include "SkFloat.h"
+#include "SkGeometry.h"
+#include "SkMath.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkPixelRef.h"
+#include "SkRefCnt.h"
+#include "SkRTConf.h"
+#include "SkScalerContext.h"
+#include "SkShader.h"
+#include "SkStream.h"
+#include "SkTSearch.h"
+#include "SkTime.h"
+#include "SkUtils.h"
+#include "SkXfermode.h"
+
+void SkGraphics::GetVersion(int32_t* major, int32_t* minor, int32_t* patch) {
+ if (major) {
+ *major = SKIA_VERSION_MAJOR;
+ }
+ if (minor) {
+ *minor = SKIA_VERSION_MINOR;
+ }
+ if (patch) {
+ *patch = SKIA_VERSION_PATCH;
+ }
+}
+
+#define typesizeline(type) { #type , sizeof(type) }
+
+#ifdef BUILD_EMBOSS_TABLE
+ extern void SkEmbossMask_BuildTable();
+#endif
+
+#ifdef BUILD_RADIALGRADIENT_TABLE
+ extern void SkRadialGradient_BuildTable();
+#endif
+
+void SkGraphics::Init() {
+#ifdef SK_DEVELOPER
+ skRTConfRegistry().possiblyDumpFile();
+ skRTConfRegistry().validate();
+ SkDebugf("Non-default runtime configuration options:\n");
+ skRTConfRegistry().printNonDefault( );
+#endif
+
+ SkFlattenable::InitializeFlattenables();
+#ifdef BUILD_EMBOSS_TABLE
+ SkEmbossMask_BuildTable();
+#endif
+#ifdef BUILD_RADIALGRADIENT_TABLE
+ SkRadialGradient_BuildTable();
+#endif
+
+#ifdef SK_DEBUGx
+ int i;
+
+ static const struct {
+ const char* fTypeName;
+ size_t fSizeOf;
+ } gTypeSize[] = {
+ typesizeline(char),
+ typesizeline(short),
+ typesizeline(int),
+ typesizeline(long),
+ typesizeline(size_t),
+ typesizeline(void*),
+
+ typesizeline(S8CPU),
+ typesizeline(U8CPU),
+ typesizeline(S16CPU),
+ typesizeline(U16CPU),
+
+ typesizeline(SkPoint),
+ typesizeline(SkRect),
+ typesizeline(SkMatrix),
+ typesizeline(SkPath),
+ typesizeline(SkGlyph),
+ typesizeline(SkRefCnt),
+
+ typesizeline(SkPaint),
+ typesizeline(SkCanvas),
+ typesizeline(SkBlitter),
+ typesizeline(SkShader),
+ typesizeline(SkXfermode),
+ typesizeline(SkPathEffect)
+ };
+
+#ifdef SK_CPU_BENDIAN
+ SkDebugf("SkGraphics: big-endian\n");
+#else
+ SkDebugf("SkGraphics: little-endian\n");
+#endif
+
+ {
+ char test = 0xFF;
+ int itest = test; // promote to int, see if it sign-extended
+ if (itest < 0)
+ SkDebugf("SkGraphics: char is signed\n");
+ else
+ SkDebugf("SkGraphics: char is unsigned\n");
+ }
+ for (i = 0; i < (int)SK_ARRAY_COUNT(gTypeSize); i++) {
+ SkDebugf("SkGraphics: sizeof(%s) = %d\n",
+ gTypeSize[i].fTypeName, gTypeSize[i].fSizeOf);
+ }
+ SkDebugf("SkGraphics: font cache limit %dK\n",
+ GetFontCacheLimit() >> 10);
+
+#endif
+
+}
+
+void SkGraphics::Term() {
+ PurgeFontCache();
+ SkPaint::Term();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char kFontCacheLimitStr[] = "font-cache-limit";
+static const size_t kFontCacheLimitLen = sizeof(kFontCacheLimitStr) - 1;
+
+static const struct {
+ const char* fStr;
+ size_t fLen;
+ size_t (*fFunc)(size_t);
+} gFlags[] = {
+ { kFontCacheLimitStr, kFontCacheLimitLen, SkGraphics::SetFontCacheLimit }
+};
+
+/* flags are of the form param; or param=value; */
+void SkGraphics::SetFlags(const char* flags) {
+ if (!flags) {
+ return;
+ }
+ const char* nextSemi;
+ do {
+ size_t len = strlen(flags);
+ const char* paramEnd = flags + len;
+ const char* nextEqual = strchr(flags, '=');
+ if (nextEqual && paramEnd > nextEqual) {
+ paramEnd = nextEqual;
+ }
+ nextSemi = strchr(flags, ';');
+ if (nextSemi && paramEnd > nextSemi) {
+ paramEnd = nextSemi;
+ }
+ size_t paramLen = paramEnd - flags;
+ for (int i = 0; i < (int)SK_ARRAY_COUNT(gFlags); ++i) {
+ if (paramLen != gFlags[i].fLen) {
+ continue;
+ }
+ if (strncmp(flags, gFlags[i].fStr, paramLen) == 0) {
+ size_t val = 0;
+ if (nextEqual) {
+ val = (size_t) atoi(nextEqual + 1);
+ }
+ (gFlags[i].fFunc)(val);
+ break;
+ }
+ }
+ flags = nextSemi + 1;
+ } while (nextSemi);
+}
diff --git a/core/SkImageFilter.cpp b/core/SkImageFilter.cpp
new file mode 100644
index 00000000..222a0299
--- /dev/null
+++ b/core/SkImageFilter.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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 "SkImageFilter.h"
+
+#include "SkBitmap.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SK_DEFINE_INST_COUNT(SkImageFilter)
+
+SkImageFilter::SkImageFilter(int inputCount, SkImageFilter** inputs, const SkIRect* cropRect)
+ : fInputCount(inputCount),
+ fInputs(new SkImageFilter*[inputCount]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
+ for (int i = 0; i < inputCount; ++i) {
+ fInputs[i] = inputs[i];
+ SkSafeRef(fInputs[i]);
+ }
+}
+
+SkImageFilter::SkImageFilter(SkImageFilter* input, const SkIRect* cropRect)
+ : fInputCount(1),
+ fInputs(new SkImageFilter*[1]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
+ fInputs[0] = input;
+ SkSafeRef(fInputs[0]);
+}
+
+SkImageFilter::SkImageFilter(SkImageFilter* input1, SkImageFilter* input2, const SkIRect* cropRect)
+ : fInputCount(2), fInputs(new SkImageFilter*[2]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
+ fInputs[0] = input1;
+ fInputs[1] = input2;
+ SkSafeRef(fInputs[0]);
+ SkSafeRef(fInputs[1]);
+}
+
+SkImageFilter::~SkImageFilter() {
+ for (int i = 0; i < fInputCount; i++) {
+ SkSafeUnref(fInputs[i]);
+ }
+ delete[] fInputs;
+}
+
+SkImageFilter::SkImageFilter(SkFlattenableReadBuffer& buffer)
+ : fInputCount(buffer.readInt()), fInputs(new SkImageFilter*[fInputCount]) {
+ for (int i = 0; i < fInputCount; i++) {
+ if (buffer.readBool()) {
+ fInputs[i] = static_cast<SkImageFilter*>(buffer.readFlattenable());
+ } else {
+ fInputs[i] = NULL;
+ }
+ }
+ buffer.readIRect(&fCropRect);
+}
+
+void SkImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeInt(fInputCount);
+ for (int i = 0; i < fInputCount; i++) {
+ SkImageFilter* input = getInput(i);
+ buffer.writeBool(input != NULL);
+ if (input != NULL) {
+ buffer.writeFlattenable(input);
+ }
+ }
+ buffer.writeIRect(fCropRect);
+}
+
+bool SkImageFilter::filterImage(Proxy* proxy, const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* loc) {
+ SkASSERT(result);
+ SkASSERT(loc);
+ /*
+ * Give the proxy first shot at the filter. If it returns false, ask
+ * the filter to do it.
+ */
+ return (proxy && proxy->filterImage(this, src, ctm, result, loc)) ||
+ this->onFilterImage(proxy, src, ctm, result, loc);
+}
+
+bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ SkASSERT(&src);
+ SkASSERT(dst);
+ return this->onFilterBounds(src, ctm, dst);
+}
+
+bool SkImageFilter::onFilterImage(Proxy*, const SkBitmap&, const SkMatrix&,
+ SkBitmap*, SkIPoint*) {
+ return false;
+}
+
+bool SkImageFilter::canFilterImageGPU() const {
+ return this->asNewEffect(NULL, NULL, SkIPoint::Make(0, 0));
+}
+
+bool SkImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+#if SK_SUPPORT_GPU
+ SkBitmap input;
+ SkASSERT(fInputCount == 1);
+ if (!SkImageFilterUtils::GetInputResultGPU(this->getInput(0), proxy, src, ctm, &input, offset)) {
+ return false;
+ }
+ GrTexture* srcTexture = input.getTexture();
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+ SkRect srcRect = SkRect::Make(bounds);
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ GrContext* context = srcTexture->getContext();
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit,
+ desc.fWidth = bounds.width();
+ desc.fHeight = bounds.height();
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+
+ GrAutoScratchTexture dst(context, desc);
+ GrContext::AutoMatrix am;
+ am.setIdentity(context);
+ GrContext::AutoRenderTarget art(context, dst.texture()->asRenderTarget());
+ GrContext::AutoClip acs(context, dstRect);
+ GrEffectRef* effect;
+ this->asNewEffect(&effect, srcTexture, SkIPoint::Make(bounds.left(), bounds.top()));
+ SkASSERT(effect);
+ SkAutoUnref effectRef(effect);
+ GrPaint paint;
+ paint.addColorEffect(effect);
+ context->drawRectToRect(paint, dstRect, srcRect);
+
+ SkAutoTUnref<GrTexture> resultTex(dst.detach());
+ SkImageFilterUtils::WrapTexture(resultTex, bounds.width(), bounds.height(), result);
+ offset->fX += bounds.left();
+ offset->fY += bounds.top();
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool SkImageFilter::applyCropRect(SkIRect* rect) const {
+ return rect->intersect(fCropRect);
+}
+
+bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ *dst = src;
+ return true;
+}
+
+bool SkImageFilter::asNewEffect(GrEffectRef**, GrTexture*, const SkIPoint& offset) const {
+ return false;
+}
+
+bool SkImageFilter::asColorFilter(SkColorFilter**) const {
+ return false;
+}
diff --git a/core/SkImageFilterUtils.cpp b/core/SkImageFilterUtils.cpp
new file mode 100644
index 00000000..a4ce51eb
--- /dev/null
+++ b/core/SkImageFilterUtils.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 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 "SkMatrix.h"
+
+#if SK_SUPPORT_GPU
+#include "GrTexture.h"
+#include "SkImageFilterUtils.h"
+#include "SkBitmap.h"
+#include "SkGrPixelRef.h"
+#include "SkGr.h"
+
+bool SkImageFilterUtils::WrapTexture(GrTexture* texture, int width, int height, SkBitmap* result) {
+ result->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref();
+ return true;
+}
+
+bool SkImageFilterUtils::GetInputResultGPU(SkImageFilter* filter, SkImageFilter::Proxy* proxy,
+ const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ if (!filter) {
+ *result = src;
+ return true;
+ } else if (filter->canFilterImageGPU()) {
+ return filter->filterImageGPU(proxy, src, ctm, result, offset);
+ } else {
+ if (filter->filterImage(proxy, src, ctm, result, offset)) {
+ if (!result->getTexture()) {
+ GrContext* context = ((GrTexture *) src.getTexture())->getContext();
+ GrTexture* resultTex = GrLockAndRefCachedBitmapTexture(context,
+ *result, NULL);
+ result->setPixelRef(new SkGrPixelRef(resultTex))->unref();
+ GrUnlockAndUnrefCachedBitmapTexture(resultTex);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+#endif
diff --git a/core/SkInstCnt.cpp b/core/SkInstCnt.cpp
new file mode 100644
index 00000000..2f9a57dc
--- /dev/null
+++ b/core/SkInstCnt.cpp
@@ -0,0 +1,12 @@
+/*
+ * 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 "SkInstCnt.h"
+
+#if SK_ENABLE_INST_COUNT
+bool gPrintInstCount = false;
+#endif
diff --git a/core/SkLineClipper.cpp b/core/SkLineClipper.cpp
new file mode 100644
index 00000000..fc4e3d25
--- /dev/null
+++ b/core/SkLineClipper.cpp
@@ -0,0 +1,297 @@
+
+/*
+ * 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 "SkLineClipper.h"
+
+template <typename T> T pin_unsorted(T value, T limit0, T limit1) {
+ if (limit1 < limit0) {
+ SkTSwap(limit0, limit1);
+ }
+ // now the limits are sorted
+ SkASSERT(limit0 <= limit1);
+
+ if (value < limit0) {
+ value = limit0;
+ } else if (value > limit1) {
+ value = limit1;
+ }
+ return value;
+}
+
+// return X coordinate of intersection with horizontal line at Y
+static SkScalar sect_with_horizontal(const SkPoint src[2], SkScalar Y) {
+ SkScalar dy = src[1].fY - src[0].fY;
+ if (SkScalarNearlyZero(dy)) {
+ return SkScalarAve(src[0].fX, src[1].fX);
+ } else {
+#ifdef SK_SCALAR_IS_FLOAT
+ // need the extra precision so we don't compute a value that exceeds
+ // our original limits
+ double X0 = src[0].fX;
+ double Y0 = src[0].fY;
+ double X1 = src[1].fX;
+ double Y1 = src[1].fY;
+ double result = X0 + ((double)Y - Y0) * (X1 - X0) / (Y1 - Y0);
+
+ // The computed X value might still exceed [X0..X1] due to quantum flux
+ // when the doubles were added and subtracted, so we have to pin the
+ // answer :(
+ return (float)pin_unsorted(result, X0, X1);
+#else
+ return src[0].fX + SkScalarMulDiv(Y - src[0].fY, src[1].fX - src[0].fX,
+ dy);
+#endif
+ }
+}
+
+// return Y coordinate of intersection with vertical line at X
+static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) {
+ SkScalar dx = src[1].fX - src[0].fX;
+ if (SkScalarNearlyZero(dx)) {
+ return SkScalarAve(src[0].fY, src[1].fY);
+ } else {
+#ifdef SK_SCALAR_IS_FLOAT
+ // need the extra precision so we don't compute a value that exceeds
+ // our original limits
+ double X0 = src[0].fX;
+ double Y0 = src[0].fY;
+ double X1 = src[1].fX;
+ double Y1 = src[1].fY;
+ double result = Y0 + ((double)X - X0) * (Y1 - Y0) / (X1 - X0);
+ return (float)result;
+#else
+ return src[0].fY + SkScalarMulDiv(X - src[0].fX, src[1].fY - src[0].fY,
+ dx);
+#endif
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) {
+ return a <= b && (a < b || dim > 0);
+}
+
+// returns true if outer contains inner, even if inner is empty.
+// note: outer.contains(inner) always returns false if inner is empty.
+static inline bool containsNoEmptyCheck(const SkRect& outer,
+ const SkRect& inner) {
+ return outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop &&
+ outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom;
+}
+
+bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip,
+ SkPoint dst[2]) {
+ SkRect bounds;
+
+ bounds.set(src, 2);
+ if (containsNoEmptyCheck(clip, bounds)) {
+ if (src != dst) {
+ memcpy(dst, src, 2 * sizeof(SkPoint));
+ }
+ return true;
+ }
+ // check for no overlap, and only permit coincident edges if the line
+ // and the edge are colinear
+ if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) ||
+ nestedLT(clip.fRight, bounds.fLeft, bounds.width()) ||
+ nestedLT(bounds.fBottom, clip.fTop, bounds.height()) ||
+ nestedLT(clip.fBottom, bounds.fTop, bounds.height())) {
+ return false;
+ }
+
+ int index0, index1;
+
+ if (src[0].fY < src[1].fY) {
+ index0 = 0;
+ index1 = 1;
+ } else {
+ index0 = 1;
+ index1 = 0;
+ }
+
+ SkPoint tmp[2];
+ memcpy(tmp, src, sizeof(tmp));
+
+ // now compute Y intersections
+ if (tmp[index0].fY < clip.fTop) {
+ tmp[index0].set(sect_with_horizontal(src, clip.fTop), clip.fTop);
+ }
+ if (tmp[index1].fY > clip.fBottom) {
+ tmp[index1].set(sect_with_horizontal(src, clip.fBottom), clip.fBottom);
+ }
+
+ if (tmp[0].fX < tmp[1].fX) {
+ index0 = 0;
+ index1 = 1;
+ } else {
+ index0 = 1;
+ index1 = 0;
+ }
+
+ // check for quick-reject in X again, now that we may have been chopped
+ if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight) &&
+ tmp[index0].fX < tmp[index1].fX) {
+ // only reject if we have a non-zero width
+ return false;
+ }
+
+ if (tmp[index0].fX < clip.fLeft) {
+ tmp[index0].set(clip.fLeft, sect_with_vertical(src, clip.fLeft));
+ }
+ if (tmp[index1].fX > clip.fRight) {
+ tmp[index1].set(clip.fRight, sect_with_vertical(src, clip.fRight));
+ }
+#ifdef SK_DEBUG
+ bounds.set(tmp, 2);
+ SkASSERT(containsNoEmptyCheck(clip, bounds));
+#endif
+ memcpy(dst, tmp, sizeof(tmp));
+ return true;
+}
+
+#ifdef SK_DEBUG
+// return value between the two limits, where the limits are either ascending
+// or descending.
+static bool is_between_unsorted(SkScalar value,
+ SkScalar limit0, SkScalar limit1) {
+ if (limit0 < limit1) {
+ return limit0 <= value && value <= limit1;
+ } else {
+ return limit1 <= value && value <= limit0;
+ }
+}
+#endif
+
+#ifdef SK_SCALAR_IS_FLOAT
+#ifdef SK_DEBUG
+// This is an example of why we need to pin the result computed in
+// sect_with_horizontal. If we didn't explicitly pin, is_between_unsorted would
+// fail.
+//
+static void sect_with_horizontal_test_for_pin_results() {
+ const SkPoint pts[] = {
+ { -540000, -720000 },
+ { SkFloatToScalar(-9.10000017e-05f), SkFloatToScalar(9.99999996e-13f) }
+ };
+ float x = sect_with_horizontal(pts, 0);
+ SkASSERT(is_between_unsorted(x, pts[0].fX, pts[1].fX));
+}
+#endif
+#endif
+
+int SkLineClipper::ClipLine(const SkPoint pts[], const SkRect& clip,
+ SkPoint lines[]) {
+#ifdef SK_SCALAR_IS_FLOAT
+#ifdef SK_DEBUG
+ {
+ static bool gOnce;
+ if (!gOnce) {
+ sect_with_horizontal_test_for_pin_results();
+ gOnce = true;
+ }
+ }
+#endif
+#endif
+
+ int index0, index1;
+
+ if (pts[0].fY < pts[1].fY) {
+ index0 = 0;
+ index1 = 1;
+ } else {
+ index0 = 1;
+ index1 = 0;
+ }
+
+ // Check if we're completely clipped out in Y (above or below
+
+ if (pts[index1].fY <= clip.fTop) { // we're above the clip
+ return 0;
+ }
+ if (pts[index0].fY >= clip.fBottom) { // we're below the clip
+ return 0;
+ }
+
+ // Chop in Y to produce a single segment, stored in tmp[0..1]
+
+ SkPoint tmp[2];
+ memcpy(tmp, pts, sizeof(tmp));
+
+ // now compute intersections
+ if (pts[index0].fY < clip.fTop) {
+ tmp[index0].set(sect_with_horizontal(pts, clip.fTop), clip.fTop);
+ SkASSERT(is_between_unsorted(tmp[index0].fX, pts[0].fX, pts[1].fX));
+ }
+ if (tmp[index1].fY > clip.fBottom) {
+ tmp[index1].set(sect_with_horizontal(pts, clip.fBottom), clip.fBottom);
+ SkASSERT(is_between_unsorted(tmp[index1].fX, pts[0].fX, pts[1].fX));
+ }
+
+ // Chop it into 1..3 segments that are wholly within the clip in X.
+
+ // temp storage for up to 3 segments
+ SkPoint resultStorage[kMaxPoints];
+ SkPoint* result; // points to our results, either tmp or resultStorage
+ int lineCount = 1;
+ bool reverse;
+
+ if (pts[0].fX < pts[1].fX) {
+ index0 = 0;
+ index1 = 1;
+ reverse = false;
+ } else {
+ index0 = 1;
+ index1 = 0;
+ reverse = true;
+ }
+
+ if (tmp[index1].fX <= clip.fLeft) { // wholly to the left
+ tmp[0].fX = tmp[1].fX = clip.fLeft;
+ result = tmp;
+ reverse = false;
+ } else if (tmp[index0].fX >= clip.fRight) { // wholly to the right
+ tmp[0].fX = tmp[1].fX = clip.fRight;
+ result = tmp;
+ reverse = false;
+ } else {
+ result = resultStorage;
+ SkPoint* r = result;
+
+ if (tmp[index0].fX < clip.fLeft) {
+ r->set(clip.fLeft, tmp[index0].fY);
+ r += 1;
+ r->set(clip.fLeft, sect_with_vertical(tmp, clip.fLeft));
+ SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY));
+ } else {
+ *r = tmp[index0];
+ }
+ r += 1;
+
+ if (tmp[index1].fX > clip.fRight) {
+ r->set(clip.fRight, sect_with_vertical(tmp, clip.fRight));
+ SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY));
+ r += 1;
+ r->set(clip.fRight, tmp[index1].fY);
+ } else {
+ *r = tmp[index1];
+ }
+
+ lineCount = r - result;
+ }
+
+ // Now copy the results into the caller's lines[] parameter
+ if (reverse) {
+ // copy the pts in reverse order to maintain winding order
+ for (int i = 0; i <= lineCount; i++) {
+ lines[lineCount - i] = result[i];
+ }
+ } else {
+ memcpy(lines, result, (lineCount + 1) * sizeof(SkPoint));
+ }
+ return lineCount;
+}
diff --git a/core/SkMallocPixelRef.cpp b/core/SkMallocPixelRef.cpp
new file mode 100644
index 00000000..d7009839
--- /dev/null
+++ b/core/SkMallocPixelRef.cpp
@@ -0,0 +1,66 @@
+
+/*
+ * 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 "SkMallocPixelRef.h"
+#include "SkBitmap.h"
+#include "SkFlattenableBuffers.h"
+
+SkMallocPixelRef::SkMallocPixelRef(void* storage, size_t size,
+ SkColorTable* ctable, bool ownPixels) {
+ if (NULL == storage) {
+ SkASSERT(ownPixels);
+ storage = sk_malloc_throw(size);
+ }
+ fStorage = storage;
+ fSize = size;
+ fCTable = ctable;
+ SkSafeRef(ctable);
+ fOwnPixels = ownPixels;
+
+ this->setPreLocked(fStorage, fCTable);
+}
+
+SkMallocPixelRef::~SkMallocPixelRef() {
+ SkSafeUnref(fCTable);
+ if (fOwnPixels) {
+ sk_free(fStorage);
+ }
+}
+
+void* SkMallocPixelRef::onLockPixels(SkColorTable** ct) {
+ *ct = fCTable;
+ return fStorage;
+}
+
+void SkMallocPixelRef::onUnlockPixels() {
+ // nothing to do
+}
+
+void SkMallocPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeByteArray(fStorage, fSize);
+ buffer.writeBool(fCTable != NULL);
+ if (fCTable) {
+ buffer.writeFlattenable(fCTable);
+ }
+}
+
+SkMallocPixelRef::SkMallocPixelRef(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer, NULL) {
+ fSize = buffer.getArrayCount();
+ fStorage = sk_malloc_throw(fSize);
+ buffer.readByteArray(fStorage);
+ if (buffer.readBool()) {
+ fCTable = buffer.readFlattenableT<SkColorTable>();
+ } else {
+ fCTable = NULL;
+ }
+ fOwnPixels = true;
+
+ this->setPreLocked(fStorage, fCTable);
+}
diff --git a/core/SkMask.cpp b/core/SkMask.cpp
new file mode 100644
index 00000000..09744194
--- /dev/null
+++ b/core/SkMask.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007 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 "Sk64.h"
+#include "SkMask.h"
+
+/** returns the product if it is positive and fits in 31 bits. Otherwise this
+ returns 0.
+ */
+static int32_t safeMul32(int32_t a, int32_t b) {
+ Sk64 size;
+ size.setMul(a, b);
+ if (size.is32() && size.isPos()) {
+ return size.get32();
+ }
+ return 0;
+}
+
+size_t SkMask::computeImageSize() const {
+ return safeMul32(fBounds.height(), fRowBytes);
+}
+
+size_t SkMask::computeTotalImageSize() const {
+ size_t size = this->computeImageSize();
+ if (fFormat == SkMask::k3D_Format) {
+ size = safeMul32(size, 3);
+ }
+ return size;
+}
+
+/** We explicitly use this allocator for SkBimap pixels, so that we can
+ freely assign memory allocated by one class to the other.
+*/
+uint8_t* SkMask::AllocImage(size_t size) {
+ return (uint8_t*)sk_malloc_throw(SkAlign4(size));
+}
+
+/** We explicitly use this allocator for SkBimap pixels, so that we can
+ freely assign memory allocated by one class to the other.
+*/
+void SkMask::FreeImage(void* image) {
+ sk_free(image);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const int gMaskFormatToShift[] = {
+ ~0, // BW -- not supported
+ 0, // A8
+ 0, // 3D
+ 2, // ARGB32
+ 1, // LCD16
+ 2 // LCD32
+};
+
+static int maskFormatToShift(SkMask::Format format) {
+ SkASSERT((unsigned)format < SK_ARRAY_COUNT(gMaskFormatToShift));
+ SkASSERT(SkMask::kBW_Format != format);
+ return gMaskFormatToShift[format];
+}
+
+void* SkMask::getAddr(int x, int y) const {
+ SkASSERT(kBW_Format != fFormat);
+ SkASSERT(fBounds.contains(x, y));
+ SkASSERT(fImage);
+
+ char* addr = (char*)fImage;
+ addr += (y - fBounds.fTop) * fRowBytes;
+ addr += (x - fBounds.fLeft) << maskFormatToShift(fFormat);
+ return addr;
+}
diff --git a/core/SkMaskFilter.cpp b/core/SkMaskFilter.cpp
new file mode 100644
index 00000000..9c367f94
--- /dev/null
+++ b/core/SkMaskFilter.cpp
@@ -0,0 +1,356 @@
+
+/*
+ * 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 "SkMaskFilter.h"
+#include "SkBlitter.h"
+#include "SkBounder.h"
+#include "SkDraw.h"
+#include "SkRasterClip.h"
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+#include "GrTexture.h"
+#include "SkGr.h"
+#include "SkGrPixelRef.h"
+#endif
+
+SK_DEFINE_INST_COUNT(SkMaskFilter)
+
+bool SkMaskFilter::filterMask(SkMask*, const SkMask&, const SkMatrix&,
+ 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;
+ if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
+ 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;
+ if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
+ 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;
+ if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
+ 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;
+ if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
+ 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) 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,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode,
+ style)) {
+ return false;
+ }
+ SkAutoMaskFreeImage autoSrc(srcM.fImage);
+
+ if (!this->filterMask(&dstM, srcM, matrix, NULL)) {
+ return false;
+ }
+ SkAutoMaskFreeImage autoDst(dstM.fImage);
+
+ // 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(), dstM.fBounds);
+
+ if (!clipper.done() && (bounder == NULL || bounder->doIRect(dstM.fBounds))) {
+ const SkIRect& cr = clipper.rect();
+ do {
+ blitter->blitMask(dstM, cr);
+ clipper.next();
+ } while (!clipper.done());
+ }
+
+ return true;
+}
+
+SkMaskFilter::FilterReturn
+SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+ const SkIRect& clipBounds, NinePatch*) const {
+ return kUnimplemented_FilterReturn;
+}
+
+#if SK_SUPPORT_GPU
+bool SkMaskFilter::asNewEffect(GrEffectRef** effect, GrTexture*) const {
+ return false;
+}
+
+bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const {
+ return false;
+}
+
+bool SkMaskFilter::filterMaskGPU(GrContext* context,
+ const SkBitmap& srcBM,
+ const SkRect& maskRect,
+ SkBitmap* resultBM) const {
+ SkAutoTUnref<GrTexture> src;
+ bool canOverwriteSrc = false;
+ if (NULL == srcBM.getTexture()) {
+ GrTextureDesc desc;
+ // Needs to be a render target to be overwritten in filterMaskGPU
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(srcBM.config());
+ desc.fWidth = srcBM.width();
+ desc.fHeight = srcBM.height();
+
+ // TODO: right now this is exact to guard against out of bounds reads
+ // by the filter code. More thought needs to be devoted to the
+ // "filterMaskGPU" contract and then enforced (i.e., clamp the code
+ // in "filterMaskGPU" so it never samples beyond maskRect)
+ GrAutoScratchTexture ast(context, desc, GrContext::kExact_ScratchTexMatch);
+ if (NULL == ast.texture()) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(srcBM);
+ ast.texture()->writePixels(0, 0, srcBM.width(), srcBM.height(),
+ desc.fConfig,
+ srcBM.getPixels(), srcBM.rowBytes());
+
+ src.reset(ast.detach());
+ canOverwriteSrc = true;
+ } else {
+ src.reset((GrTexture*) srcBM.getTexture());
+ src.get()->ref();
+ }
+ GrTexture* dst;
+
+ bool result = this->filterMaskGPU(src, maskRect, &dst, canOverwriteSrc);
+ if (!result) {
+ return false;
+ }
+
+ resultBM->setConfig(srcBM.config(), dst->width(), dst->height());
+ resultBM->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (dst)))->unref();
+ dst->unref();
+ return true;
+}
+
+bool SkMaskFilter::filterMaskGPU(GrTexture* src,
+ const SkRect& maskRect,
+ GrTexture** result,
+ bool canOverwriteSrc) const {
+ return false;
+}
+#endif
+
+void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
+ SkMask srcM, dstM;
+
+ srcM.fImage = NULL;
+ src.roundOut(&srcM.fBounds);
+ srcM.fRowBytes = 0;
+ srcM.fFormat = SkMask::kA8_Format;
+
+ SkIPoint margin; // ignored
+ if (this->filterMask(&dstM, srcM, SkMatrix::I(), &margin)) {
+ dst->set(dstM.fBounds);
+ } else {
+ dst->set(srcM.fBounds);
+ }
+}
diff --git a/core/SkMaskGamma.cpp b/core/SkMaskGamma.cpp
new file mode 100644
index 00000000..9066fb7d
--- /dev/null
+++ b/core/SkMaskGamma.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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"
+
+#include "SkColor.h"
+#include "SkFloatingPoint.h"
+#include "SkMaskGamma.h"
+
+class SkLinearColorSpaceLuminance : public SkColorSpaceLuminance {
+ virtual SkScalar toLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luminance) const SK_OVERRIDE {
+ SkASSERT(SK_Scalar1 == gamma);
+ return luminance;
+ }
+ virtual SkScalar fromLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luma) const SK_OVERRIDE {
+ SkASSERT(SK_Scalar1 == gamma);
+ return luma;
+ }
+};
+
+class SkGammaColorSpaceLuminance : public SkColorSpaceLuminance {
+ virtual SkScalar toLuma(SkScalar gamma, SkScalar luminance) const SK_OVERRIDE {
+ return SkScalarPow(luminance, gamma);
+ }
+ virtual SkScalar fromLuma(SkScalar gamma, SkScalar luma) const SK_OVERRIDE {
+ return SkScalarPow(luma, SkScalarInvert(gamma));
+ }
+};
+
+class SkSRGBColorSpaceLuminance : public SkColorSpaceLuminance {
+ virtual SkScalar toLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luminance) const SK_OVERRIDE {
+ SkASSERT(0 == gamma);
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if (luminance <= SkFloatToScalar(0.04045f)) {
+ return luminance / SkFloatToScalar(12.92f);
+ }
+ return SkScalarPow((luminance + SkFloatToScalar(0.055f)) / SkFloatToScalar(1.055f),
+ SkFloatToScalar(2.4f));
+ }
+ virtual SkScalar fromLuma(SkScalar SkDEBUGCODE(gamma), SkScalar luma) const SK_OVERRIDE {
+ SkASSERT(0 == gamma);
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if (luma <= SkFloatToScalar(0.0031308f)) {
+ return luma * SkFloatToScalar(12.92f);
+ }
+ return SkFloatToScalar(1.055f) * SkScalarPow(luma, SkScalarInvert(SkFloatToScalar(2.4f)))
+ - SkFloatToScalar(0.055f);
+ }
+};
+
+/*static*/ const SkColorSpaceLuminance& SkColorSpaceLuminance::Fetch(SkScalar gamma) {
+ static SkLinearColorSpaceLuminance gSkLinearColorSpaceLuminance;
+ static SkGammaColorSpaceLuminance gSkGammaColorSpaceLuminance;
+ static SkSRGBColorSpaceLuminance gSkSRGBColorSpaceLuminance;
+
+ if (0 == gamma) {
+ return gSkSRGBColorSpaceLuminance;
+ } else if (SK_Scalar1 == gamma) {
+ return gSkLinearColorSpaceLuminance;
+ } else {
+ return gSkGammaColorSpaceLuminance;
+ }
+}
+
+static float apply_contrast(float srca, float contrast) {
+ return srca + ((1.0f - srca) * contrast * srca);
+}
+
+void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast,
+ const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma,
+ const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma) {
+ const float src = (float)srcI / 255.0f;
+ const float linSrc = srcConvert.toLuma(srcGamma, src);
+ //Guess at the dst. The perceptual inverse provides smaller visual
+ //discontinuities when slight changes to desaturated colors cause a channel
+ //to map to a different correcting lut with neighboring srcI.
+ //See https://code.google.com/p/chromium/issues/detail?id=141425#c59 .
+ const float dst = 1.0f - src;
+ const float linDst = dstConvert.toLuma(dstGamma, dst);
+
+ //Contrast value tapers off to 0 as the src luminance becomes white
+ const float adjustedContrast = SkScalarToFloat(contrast) * linDst;
+
+ //Remove discontinuity and instability when src is close to dst.
+ //The value 1/256 is arbitrary and appears to contain the instability.
+ if (fabs(src - dst) < (1.0f / 256.0f)) {
+ float ii = 0.0f;
+ for (int i = 0; i < 256; ++i, ii += 1.0f) {
+ float rawSrca = ii / 255.0f;
+ float srca = apply_contrast(rawSrca, adjustedContrast);
+ table[i] = SkToU8(sk_float_round2int(255.0f * srca));
+ }
+ } else {
+ // Avoid slow int to float conversion.
+ float ii = 0.0f;
+ for (int i = 0; i < 256; ++i, ii += 1.0f) {
+ // 'rawSrca += 1.0f / 255.0f' and even
+ // 'rawSrca = i * (1.0f / 255.0f)' can add up to more than 1.0f.
+ // When this happens the table[255] == 0x0 instead of 0xff.
+ // See http://code.google.com/p/chromium/issues/detail?id=146466
+ float rawSrca = ii / 255.0f;
+ float srca = apply_contrast(rawSrca, adjustedContrast);
+ SkASSERT(srca <= 1.0f);
+ float dsta = 1.0f - srca;
+
+ //Calculate the output we want.
+ float linOut = (linSrc * srca + dsta * linDst);
+ SkASSERT(linOut <= 1.0f);
+ float out = dstConvert.fromLuma(dstGamma, linOut);
+
+ //Undo what the blit blend will do.
+ float result = (out - dst) / (src - dst);
+ SkASSERT(sk_float_round2int(255.0f * result) <= 255);
+
+ table[i] = SkToU8(sk_float_round2int(255.0f * result));
+ }
+ }
+}
diff --git a/core/SkMaskGamma.h b/core/SkMaskGamma.h
new file mode 100644
index 00000000..fafe4ac7
--- /dev/null
+++ b/core/SkMaskGamma.h
@@ -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.
+ */
+
+#ifndef SkMaskGamma_DEFINED
+#define SkMaskGamma_DEFINED
+
+#include "SkTypes.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkRefCnt.h"
+
+/**
+ * SkColorSpaceLuminance is used to convert luminances to and from linear and
+ * perceptual color spaces.
+ *
+ * Luma is used to specify a linear luminance value [0.0, 1.0].
+ * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0].
+ */
+class SkColorSpaceLuminance : SkNoncopyable {
+public:
+ virtual ~SkColorSpaceLuminance() { }
+
+ /** Converts a color component luminance in the color space to a linear luma. */
+ virtual SkScalar toLuma(SkScalar gamma, SkScalar luminance) const = 0;
+ /** Converts a linear luma to a color component luminance in the color space. */
+ virtual SkScalar fromLuma(SkScalar gamma, SkScalar luma) const = 0;
+
+ /** Converts a color to a luminance value. */
+ static U8CPU computeLuminance(SkScalar gamma, SkColor c) {
+ const SkColorSpaceLuminance& luminance = Fetch(gamma);
+ SkScalar r = luminance.toLuma(gamma, SkIntToScalar(SkColorGetR(c)) / 255);
+ SkScalar g = luminance.toLuma(gamma, SkIntToScalar(SkColorGetG(c)) / 255);
+ SkScalar b = luminance.toLuma(gamma, SkIntToScalar(SkColorGetB(c)) / 255);
+ SkScalar luma = r * SkFloatToScalar(SK_LUM_COEFF_R) +
+ g * SkFloatToScalar(SK_LUM_COEFF_G) +
+ b * SkFloatToScalar(SK_LUM_COEFF_B);
+ SkASSERT(luma <= SK_Scalar1);
+ return SkScalarRoundToInt(luminance.fromLuma(gamma, luma) * 255);
+ }
+
+ /** Retrieves the SkColorSpaceLuminance for the given gamma. */
+ static const SkColorSpaceLuminance& Fetch(SkScalar gamma);
+};
+
+///@{
+/**
+ * Scales base <= 2^N-1 to 2^8-1
+ * @param N [1, 8] the number of bits used by base.
+ * @param base the number to be scaled to [0, 255].
+ */
+template<U8CPU N> static inline U8CPU sk_t_scale255(U8CPU base) {
+ base <<= (8 - N);
+ U8CPU lum = base;
+ for (unsigned int i = N; i < 8; i += N) {
+ lum |= base >> i;
+ }
+ return lum;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) {
+ return base * 0xFF;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) {
+ return base * 0x55;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) {
+ return base * 0x11;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) {
+ return base;
+}
+///@}
+
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend;
+
+void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast,
+ const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma,
+ const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma);
+
+/**
+ * A regular mask contains linear alpha values. A gamma correcting mask
+ * contains non-linear alpha values in an attempt to create gamma correct blits
+ * in the presence of a gamma incorrect (linear) blend in the blitter.
+ *
+ * SkMaskGamma creates and maintains tables which convert linear alpha values
+ * to gamma correcting alpha values.
+ * @param R The number of luminance bits to use [1, 8] from the red channel.
+ * @param G The number of luminance bits to use [1, 8] from the green channel.
+ * @param B The number of luminance bits to use [1, 8] from the blue channel.
+ */
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskGamma : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT_TEMPLATE(SkTMaskGamma)
+
+ /** Creates a linear SkTMaskGamma. */
+ SkTMaskGamma() : fIsLinear(true) { }
+
+ /**
+ * Creates tables to convert linear alpha values to gamma correcting alpha
+ * values.
+ *
+ * @param contrast A value in the range [0.0, 1.0] which indicates the
+ * amount of artificial contrast to add.
+ * @param paint The color space in which the paint color was chosen.
+ * @param device The color space of the target device.
+ */
+ 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 << 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);
+ }
+ }
+
+ /** Given a color, returns the closest canonical color. */
+ static SkColor CanonicalColor(SkColor color) {
+ return SkColorSetRGB(
+ 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). */
+ typedef SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> PreBlend;
+
+ /**
+ * Provides access to the tables appropriate for converting linear alpha
+ * values into gamma correcting alpha values when drawing the given color
+ * through the mask. The destination color will be approximated.
+ */
+ PreBlend preBlend(SkColor color) const;
+
+private:
+ 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;
+};
+
+
+#define MacroComma ,
+SK_DEFINE_INST_COUNT_TEMPLATE(
+ template <int R_LUM_BITS MacroComma int G_LUM_BITS MacroComma int B_LUM_BITS>,
+ SkTMaskGamma<R_LUM_BITS MacroComma G_LUM_BITS MacroComma B_LUM_BITS>);
+
+/**
+ * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to
+ * convert a linear alpha value for a given channel to a gamma correcting alpha
+ * 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. 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(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(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;
+};
+
+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) 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)]);
+}
+
+///@{
+/**
+ * If APPLY_LUT is false, returns component unchanged.
+ * If APPLY_LUT is true, returns lut[component].
+ * @param APPLY_LUT whether or not the look-up table should be applied to component.
+ * @component the initial component.
+ * @lut a look-up table which transforms the component.
+ */
+template<bool APPLY_LUT> static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) {
+ return component;
+}
+template<> /*static*/ inline U8CPU sk_apply_lut_if<true>(U8CPU component, const uint8_t* lut) {
+ return lut[component];
+}
+///@}
+
+#endif
diff --git a/core/SkMath.cpp b/core/SkMath.cpp
new file mode 100644
index 00000000..2693e5c1
--- /dev/null
+++ b/core/SkMath.cpp
@@ -0,0 +1,517 @@
+/*
+ * 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.
+ */
+
+#include "SkMathPriv.h"
+#include "SkCordic.h"
+#include "SkFloatBits.h"
+#include "SkFloatingPoint.h"
+#include "Sk64.h"
+#include "SkScalar.h"
+
+#ifdef SK_SCALAR_IS_FLOAT
+ const uint32_t gIEEENotANumber = 0x7FFFFFFF;
+ const uint32_t gIEEEInfinity = 0x7F800000;
+ const uint32_t gIEEENegativeInfinity = 0xFF800000;
+#endif
+
+#define sub_shift(zeros, x, n) \
+ zeros -= n; \
+ x >>= n
+
+int SkCLZ_portable(uint32_t x) {
+ if (x == 0) {
+ return 32;
+ }
+
+ int zeros = 31;
+ if (x & 0xFFFF0000) {
+ sub_shift(zeros, x, 16);
+ }
+ if (x & 0xFF00) {
+ sub_shift(zeros, x, 8);
+ }
+ if (x & 0xF0) {
+ sub_shift(zeros, x, 4);
+ }
+ if (x & 0xC) {
+ sub_shift(zeros, x, 2);
+ }
+ if (x & 0x2) {
+ sub_shift(zeros, x, 1);
+ }
+
+ return zeros;
+}
+
+int32_t SkMulDiv(int32_t numer1, int32_t numer2, int32_t denom) {
+ SkASSERT(denom);
+
+ Sk64 tmp;
+ tmp.setMul(numer1, numer2);
+ tmp.div(denom, Sk64::kTrunc_DivOption);
+ return tmp.get32();
+}
+
+int32_t SkMulShift(int32_t a, int32_t b, unsigned shift) {
+ int sign = SkExtractSign(a ^ b);
+
+ if (shift > 63) {
+ return sign;
+ }
+
+ a = SkAbs32(a);
+ b = SkAbs32(b);
+
+ uint32_t ah = a >> 16;
+ uint32_t al = a & 0xFFFF;
+ uint32_t bh = b >> 16;
+ uint32_t bl = b & 0xFFFF;
+
+ uint32_t A = ah * bh;
+ uint32_t B = ah * bl + al * bh;
+ uint32_t C = al * bl;
+
+ /* [ A ]
+ [ B ]
+ [ C ]
+ */
+ uint32_t lo = C + (B << 16);
+ int32_t hi = A + (B >> 16) + (lo < C);
+
+ if (sign < 0) {
+ hi = -hi - Sk32ToBool(lo);
+ lo = 0 - lo;
+ }
+
+ if (shift == 0) {
+#ifdef SK_DEBUGx
+ SkASSERT(((int32_t)lo >> 31) == hi);
+#endif
+ return lo;
+ } else if (shift >= 32) {
+ return hi >> (shift - 32);
+ } else {
+#ifdef SK_DEBUGx
+ int32_t tmp = hi >> shift;
+ SkASSERT(tmp == 0 || tmp == -1);
+#endif
+ // we want (hi << (32 - shift)) | (lo >> shift) but rounded
+ int roundBit = (lo >> (shift - 1)) & 1;
+ return ((hi << (32 - shift)) | (lo >> shift)) + roundBit;
+ }
+}
+
+SkFixed SkFixedMul_portable(SkFixed a, SkFixed b) {
+#if 0
+ Sk64 tmp;
+
+ tmp.setMul(a, b);
+ tmp.shiftRight(16);
+ return tmp.fLo;
+#elif defined(SkLONGLONG)
+ return static_cast<SkFixed>((SkLONGLONG)a * b >> 16);
+#else
+ int sa = SkExtractSign(a);
+ int sb = SkExtractSign(b);
+ // now make them positive
+ a = SkApplySign(a, sa);
+ b = SkApplySign(b, sb);
+
+ uint32_t ah = a >> 16;
+ uint32_t al = a & 0xFFFF;
+ uint32_t bh = b >> 16;
+ uint32_t bl = b & 0xFFFF;
+
+ uint32_t R = ah * b + al * bh + (al * bl >> 16);
+
+ return SkApplySign(R, sa ^ sb);
+#endif
+}
+
+SkFract SkFractMul_portable(SkFract a, SkFract b) {
+#if 0
+ Sk64 tmp;
+ tmp.setMul(a, b);
+ return tmp.getFract();
+#elif defined(SkLONGLONG)
+ return static_cast<SkFract>((SkLONGLONG)a * b >> 30);
+#else
+ int sa = SkExtractSign(a);
+ int sb = SkExtractSign(b);
+ // now make them positive
+ a = SkApplySign(a, sa);
+ b = SkApplySign(b, sb);
+
+ uint32_t ah = a >> 16;
+ uint32_t al = a & 0xFFFF;
+ uint32_t bh = b >> 16;
+ uint32_t bl = b & 0xFFFF;
+
+ uint32_t A = ah * bh;
+ uint32_t B = ah * bl + al * bh;
+ uint32_t C = al * bl;
+
+ /* [ A ]
+ [ B ]
+ [ C ]
+ */
+ uint32_t Lo = C + (B << 16);
+ uint32_t Hi = A + (B >>16) + (Lo < C);
+
+ SkASSERT((Hi >> 29) == 0); // else overflow
+
+ int32_t R = (Hi << 2) + (Lo >> 30);
+
+ return SkApplySign(R, sa ^ sb);
+#endif
+}
+
+int SkFixedMulCommon(SkFixed a, int b, int bias) {
+ // this function only works if b is 16bits
+ SkASSERT(b == (int16_t)b);
+ SkASSERT(b >= 0);
+
+ int sa = SkExtractSign(a);
+ a = SkApplySign(a, sa);
+ uint32_t ah = a >> 16;
+ uint32_t al = a & 0xFFFF;
+ uint32_t R = ah * b + ((al * b + bias) >> 16);
+ return SkApplySign(R, sa);
+}
+
+#ifdef SK_DEBUGx
+ #define TEST_FASTINVERT
+#endif
+
+SkFixed SkFixedFastInvert(SkFixed x) {
+/* Adapted (stolen) from gglRecip()
+*/
+
+ if (x == SK_Fixed1) {
+ return SK_Fixed1;
+ }
+
+ int sign = SkExtractSign(x);
+ uint32_t a = SkApplySign(x, sign);
+
+ if (a <= 2) {
+ return SkApplySign(SK_MaxS32, sign);
+ }
+
+#ifdef TEST_FASTINVERT
+ SkFixed orig = a;
+ uint32_t slow = SkFixedDiv(SK_Fixed1, a);
+#endif
+
+ // normalize a
+ int lz = SkCLZ(a);
+ a = a << lz >> 16;
+
+ // compute 1/a approximation (0.5 <= a < 1.0)
+ uint32_t r = 0x17400 - a; // (2.90625 (~2.914) - 2*a) >> 1
+
+ // Newton-Raphson iteration:
+ // x = r*(2 - a*r) = ((r/2)*(1 - a*r/2))*4
+ r = ( (0x10000 - ((a*r)>>16)) * r ) >> 15;
+ r = ( (0x10000 - ((a*r)>>16)) * r ) >> (30 - lz);
+
+#ifdef TEST_FASTINVERT
+ SkDebugf("SkFixedFastInvert(%x %g) = %x %g Slow[%x %g]\n",
+ orig, orig/65536.,
+ r, r/65536.,
+ slow, slow/65536.);
+#endif
+
+ return SkApplySign(r, sign);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DIVBITS_ITER(n) \
+ case n: \
+ if ((numer = (numer << 1) - denom) >= 0) \
+ result |= 1 << (n - 1); else numer += denom
+
+int32_t SkDivBits(int32_t numer, int32_t denom, int shift_bias) {
+ SkASSERT(denom != 0);
+ if (numer == 0) {
+ return 0;
+ }
+
+ // make numer and denom positive, and sign hold the resulting sign
+ int32_t sign = SkExtractSign(numer ^ denom);
+ numer = SkAbs32(numer);
+ denom = SkAbs32(denom);
+
+ int nbits = SkCLZ(numer) - 1;
+ int dbits = SkCLZ(denom) - 1;
+ int bits = shift_bias - nbits + dbits;
+
+ if (bits < 0) { // answer will underflow
+ return 0;
+ }
+ if (bits > 31) { // answer will overflow
+ return SkApplySign(SK_MaxS32, sign);
+ }
+
+ denom <<= dbits;
+ numer <<= nbits;
+
+ SkFixed result = 0;
+
+ // do the first one
+ if ((numer -= denom) >= 0) {
+ result = 1;
+ } else {
+ numer += denom;
+ }
+
+ // Now fall into our switch statement if there are more bits to compute
+ if (bits > 0) {
+ // make room for the rest of the answer bits
+ result <<= bits;
+ switch (bits) {
+ DIVBITS_ITER(31); DIVBITS_ITER(30); DIVBITS_ITER(29);
+ DIVBITS_ITER(28); DIVBITS_ITER(27); DIVBITS_ITER(26);
+ DIVBITS_ITER(25); DIVBITS_ITER(24); DIVBITS_ITER(23);
+ DIVBITS_ITER(22); DIVBITS_ITER(21); DIVBITS_ITER(20);
+ DIVBITS_ITER(19); DIVBITS_ITER(18); DIVBITS_ITER(17);
+ DIVBITS_ITER(16); DIVBITS_ITER(15); DIVBITS_ITER(14);
+ DIVBITS_ITER(13); DIVBITS_ITER(12); DIVBITS_ITER(11);
+ DIVBITS_ITER(10); DIVBITS_ITER( 9); DIVBITS_ITER( 8);
+ DIVBITS_ITER( 7); DIVBITS_ITER( 6); DIVBITS_ITER( 5);
+ DIVBITS_ITER( 4); DIVBITS_ITER( 3); DIVBITS_ITER( 2);
+ // we merge these last two together, makes GCC make better ARM
+ default:
+ DIVBITS_ITER( 1);
+ }
+ }
+
+ if (result < 0) {
+ result = SK_MaxS32;
+ }
+ return SkApplySign(result, sign);
+}
+
+/* mod(float numer, float denom) seems to always return the sign
+ of the numer, so that's what we do too
+*/
+SkFixed SkFixedMod(SkFixed numer, SkFixed denom) {
+ int sn = SkExtractSign(numer);
+ int sd = SkExtractSign(denom);
+
+ numer = SkApplySign(numer, sn);
+ denom = SkApplySign(denom, sd);
+
+ if (numer < denom) {
+ return SkApplySign(numer, sn);
+ } else if (numer == denom) {
+ return 0;
+ } else {
+ SkFixed div = SkFixedDiv(numer, denom);
+ return SkApplySign(SkFixedMul(denom, div & 0xFFFF), sn);
+ }
+}
+
+/* www.worldserver.com/turk/computergraphics/FixedSqrt.pdf
+*/
+int32_t SkSqrtBits(int32_t x, int count) {
+ SkASSERT(x >= 0 && count > 0 && (unsigned)count <= 30);
+
+ uint32_t root = 0;
+ uint32_t remHi = 0;
+ uint32_t remLo = x;
+
+ do {
+ root <<= 1;
+
+ remHi = (remHi<<2) | (remLo>>30);
+ remLo <<= 2;
+
+ uint32_t testDiv = (root << 1) + 1;
+ if (remHi >= testDiv) {
+ remHi -= testDiv;
+ root++;
+ }
+ } while (--count >= 0);
+
+ return root;
+}
+
+int32_t SkCubeRootBits(int32_t value, int bits) {
+ SkASSERT(bits > 0);
+
+ int sign = SkExtractSign(value);
+ value = SkApplySign(value, sign);
+
+ uint32_t root = 0;
+ uint32_t curr = (uint32_t)value >> 30;
+ value <<= 2;
+
+ do {
+ root <<= 1;
+ uint32_t guess = root * root + root;
+ guess = (guess << 1) + guess; // guess *= 3
+ if (guess < curr) {
+ curr -= guess + 1;
+ root |= 1;
+ }
+ curr = (curr << 3) | ((uint32_t)value >> 29);
+ value <<= 3;
+ } while (--bits);
+
+ return SkApplySign(root, sign);
+}
+
+SkFixed SkFixedMean(SkFixed a, SkFixed b) {
+ Sk64 tmp;
+
+ tmp.setMul(a, b);
+ return tmp.getSqrt();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_SCALAR_IS_FLOAT
+float SkScalarSinCos(float radians, float* cosValue) {
+ float sinValue = sk_float_sin(radians);
+
+ if (cosValue) {
+ *cosValue = sk_float_cos(radians);
+ if (SkScalarNearlyZero(*cosValue)) {
+ *cosValue = 0;
+ }
+ }
+
+ if (SkScalarNearlyZero(sinValue)) {
+ sinValue = 0;
+ }
+ return sinValue;
+}
+#endif
+
+#define INTERP_SINTABLE
+#define BUILD_TABLE_AT_RUNTIMEx
+
+#define kTableSize 256
+
+#ifdef BUILD_TABLE_AT_RUNTIME
+ static uint16_t gSkSinTable[kTableSize];
+
+ static void build_sintable(uint16_t table[]) {
+ for (int i = 0; i < kTableSize; i++) {
+ double rad = i * 3.141592653589793 / (2*kTableSize);
+ double val = sin(rad);
+ int ival = (int)(val * SK_Fixed1);
+ table[i] = SkToU16(ival);
+ }
+ }
+#else
+ #include "SkSinTable.h"
+#endif
+
+#define SK_Fract1024SizeOver2PI 0x28BE60 /* floatToFract(1024 / 2PI) */
+
+#ifdef INTERP_SINTABLE
+static SkFixed interp_table(const uint16_t table[], int index, int partial255) {
+ SkASSERT((unsigned)index < kTableSize);
+ SkASSERT((unsigned)partial255 <= 255);
+
+ SkFixed lower = table[index];
+ SkFixed upper = (index == kTableSize - 1) ? SK_Fixed1 : table[index + 1];
+
+ SkASSERT(lower < upper);
+ SkASSERT(lower >= 0);
+ SkASSERT(upper <= SK_Fixed1);
+
+ partial255 += (partial255 >> 7);
+ return lower + ((upper - lower) * partial255 >> 8);
+}
+#endif
+
+SkFixed SkFixedSinCos(SkFixed radians, SkFixed* cosValuePtr) {
+ SkASSERT(SK_ARRAY_COUNT(gSkSinTable) == kTableSize);
+
+#ifdef BUILD_TABLE_AT_RUNTIME
+ static bool gFirstTime = true;
+ if (gFirstTime) {
+ build_sintable(gSinTable);
+ gFirstTime = false;
+ }
+#endif
+
+ // make radians positive
+ SkFixed sinValue, cosValue;
+ int32_t cosSign = 0;
+ int32_t sinSign = SkExtractSign(radians);
+ radians = SkApplySign(radians, sinSign);
+ // scale it to 0...1023 ...
+
+#ifdef INTERP_SINTABLE
+ radians = SkMulDiv(radians, 2 * kTableSize * 256, SK_FixedPI);
+ int findex = radians & (kTableSize * 256 - 1);
+ int index = findex >> 8;
+ int partial = findex & 255;
+ sinValue = interp_table(gSkSinTable, index, partial);
+
+ findex = kTableSize * 256 - findex - 1;
+ index = findex >> 8;
+ partial = findex & 255;
+ cosValue = interp_table(gSkSinTable, index, partial);
+
+ int quad = ((unsigned)radians / (kTableSize * 256)) & 3;
+#else
+ radians = SkMulDiv(radians, 2 * kTableSize, SK_FixedPI);
+ int index = radians & (kTableSize - 1);
+
+ if (index == 0) {
+ sinValue = 0;
+ cosValue = SK_Fixed1;
+ } else {
+ sinValue = gSkSinTable[index];
+ cosValue = gSkSinTable[kTableSize - index];
+ }
+ int quad = ((unsigned)radians / kTableSize) & 3;
+#endif
+
+ if (quad & 1) {
+ SkTSwap<SkFixed>(sinValue, cosValue);
+ }
+ if (quad & 2) {
+ sinSign = ~sinSign;
+ }
+ if (((quad - 1) & 2) == 0) {
+ cosSign = ~cosSign;
+ }
+
+ // restore the sign for negative angles
+ sinValue = SkApplySign(sinValue, sinSign);
+ cosValue = SkApplySign(cosValue, cosSign);
+
+#ifdef SK_DEBUG
+ if (1) {
+ SkFixed sin2 = SkFixedMul(sinValue, sinValue);
+ SkFixed cos2 = SkFixedMul(cosValue, cosValue);
+ int diff = cos2 + sin2 - SK_Fixed1;
+ SkASSERT(SkAbs32(diff) <= 7);
+ }
+#endif
+
+ if (cosValuePtr) {
+ *cosValuePtr = cosValue;
+ }
+ return sinValue;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFixed SkFixedTan(SkFixed radians) { return SkCordicTan(radians); }
+SkFixed SkFixedASin(SkFixed x) { return SkCordicASin(x); }
+SkFixed SkFixedACos(SkFixed x) { return SkCordicACos(x); }
+SkFixed SkFixedATan2(SkFixed y, SkFixed x) { return SkCordicATan2(y, x); }
+SkFixed SkFixedExp(SkFixed x) { return SkCordicExp(x); }
+SkFixed SkFixedLog(SkFixed x) { return SkCordicLog(x); }
diff --git a/core/SkMathPriv.h b/core/SkMathPriv.h
new file mode 100644
index 00000000..4eaad8b9
--- /dev/null
+++ b/core/SkMathPriv.h
@@ -0,0 +1,81 @@
+/*
+ * 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 SkMathPriv_DEFINED
+#define SkMathPriv_DEFINED
+
+#include "SkMath.h"
+
+/** Returns -1 if n < 0, else returns 0
+ */
+#define SkExtractSign(n) ((int32_t)(n) >> 31)
+
+/** If sign == -1, returns -n, else sign must be 0, and returns n.
+ Typically used in conjunction with SkExtractSign().
+ */
+static inline int32_t SkApplySign(int32_t n, int32_t sign) {
+ SkASSERT(sign == 0 || sign == -1);
+ return (n ^ sign) - sign;
+}
+
+/** Return x with the sign of y */
+static inline int32_t SkCopySign32(int32_t x, int32_t y) {
+ return SkApplySign(x, SkExtractSign(x ^ y));
+}
+
+/** Given a positive value and a positive max, return the value
+ pinned against max.
+ Note: only works as long as max - value doesn't wrap around
+ @return max if value >= max, else value
+ */
+static inline unsigned SkClampUMax(unsigned value, unsigned max) {
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+/** Computes the 64bit product of a * b, and then shifts the answer down by
+ shift bits, returning the low 32bits. shift must be [0..63]
+ e.g. to perform a fixedmul, call SkMulShift(a, b, 16)
+ */
+int32_t SkMulShift(int32_t a, int32_t b, unsigned shift);
+
+/** Return the integer cube root of value, with a bias of bitBias
+ */
+int32_t SkCubeRootBits(int32_t value, int bitBias);
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** Return a*b/255, truncating away any fractional bits. Only valid if both
+ a and b are 0..255
+ */
+static inline U8CPU SkMulDiv255Trunc(U8CPU a, U8CPU b) {
+ SkASSERT((uint8_t)a == a);
+ SkASSERT((uint8_t)b == b);
+ unsigned prod = SkMulS16(a, b) + 1;
+ return (prod + (prod >> 8)) >> 8;
+}
+
+/** Return (a*b)/255, taking the ceiling of any fractional bits. Only valid if
+ both a and b are 0..255. The expected result equals (a * b + 254) / 255.
+ */
+static inline U8CPU SkMulDiv255Ceiling(U8CPU a, U8CPU b) {
+ SkASSERT((uint8_t)a == a);
+ SkASSERT((uint8_t)b == b);
+ unsigned prod = SkMulS16(a, b) + 255;
+ return (prod + (prod >> 8)) >> 8;
+}
+
+/** Just the rounding step in SkDiv255Round: round(value / 255)
+ */
+static inline unsigned SkDiv255Round(unsigned prod) {
+ prod += 128;
+ return (prod + (prod >> 8)) >> 8;
+}
+
+#endif
diff --git a/core/SkMatrix.cpp b/core/SkMatrix.cpp
new file mode 100644
index 00000000..36e73530
--- /dev/null
+++ b/core/SkMatrix.cpp
@@ -0,0 +1,2057 @@
+/*
+ * 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 "SkMatrix.h"
+#include "Sk64.h"
+#include "SkFloatBits.h"
+#include "SkScalarCompare.h"
+#include "SkString.h"
+
+#ifdef SK_SCALAR_IS_FLOAT
+ #define kMatrix22Elem SK_Scalar1
+
+ static inline float SkDoubleToFloat(double x) {
+ return static_cast<float>(x);
+ }
+#else
+ #define kMatrix22Elem SK_Fract1
+#endif
+
+/* [scale-x skew-x trans-x] [X] [X']
+ [skew-y scale-y trans-y] * [Y] = [Y']
+ [persp-0 persp-1 persp-2] [1] [1 ]
+*/
+
+void SkMatrix::reset() {
+ fMat[kMScaleX] = fMat[kMScaleY] = SK_Scalar1;
+ fMat[kMSkewX] = fMat[kMSkewY] =
+ fMat[kMTransX] = fMat[kMTransY] =
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kIdentity_Mask | kRectStaysRect_Mask);
+}
+
+// this guy aligns with the masks, so we can compute a mask from a varaible 0/1
+enum {
+ kTranslate_Shift,
+ kScale_Shift,
+ kAffine_Shift,
+ kPerspective_Shift,
+ kRectStaysRect_Shift
+};
+
+#ifdef SK_SCALAR_IS_FLOAT
+ static const int32_t kScalar1Int = 0x3f800000;
+ static const int32_t kPersp1Int = 0x3f800000;
+#else
+ #define scalarAsInt(x) (x)
+ static const int32_t kScalar1Int = (1 << 16);
+ static const int32_t kPersp1Int = (1 << 30);
+#endif
+
+uint8_t SkMatrix::computePerspectiveTypeMask() const {
+#ifdef SK_SCALAR_SLOW_COMPARES
+ if (SkScalarAs2sCompliment(fMat[kMPersp0]) |
+ SkScalarAs2sCompliment(fMat[kMPersp1]) |
+ (SkScalarAs2sCompliment(fMat[kMPersp2]) - kPersp1Int)) {
+ return SkToU8(kORableMasks);
+ }
+#else
+ // Benchmarking suggests that replacing this set of SkScalarAs2sCompliment
+ // is a win, but replacing those below is not. We don't yet understand
+ // that result.
+ if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 ||
+ fMat[kMPersp2] != kMatrix22Elem) {
+ // If this is a perspective transform, we return true for all other
+ // transform flags - this does not disable any optimizations, respects
+ // the rule that the type mask must be conservative, and speeds up
+ // type mask computation.
+ return SkToU8(kORableMasks);
+ }
+#endif
+
+ return SkToU8(kOnlyPerspectiveValid_Mask | kUnknown_Mask);
+}
+
+uint8_t SkMatrix::computeTypeMask() const {
+ unsigned mask = 0;
+
+#ifdef SK_SCALAR_SLOW_COMPARES
+ if (SkScalarAs2sCompliment(fMat[kMPersp0]) |
+ SkScalarAs2sCompliment(fMat[kMPersp1]) |
+ (SkScalarAs2sCompliment(fMat[kMPersp2]) - kPersp1Int)) {
+ return SkToU8(kORableMasks);
+ }
+
+ if (SkScalarAs2sCompliment(fMat[kMTransX]) |
+ SkScalarAs2sCompliment(fMat[kMTransY])) {
+ mask |= kTranslate_Mask;
+ }
+#else
+ if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 ||
+ fMat[kMPersp2] != kMatrix22Elem) {
+ // Once it is determined that that this is a perspective transform,
+ // all other flags are moot as far as optimizations are concerned.
+ return SkToU8(kORableMasks);
+ }
+
+ if (fMat[kMTransX] != 0 || fMat[kMTransY] != 0) {
+ mask |= kTranslate_Mask;
+ }
+#endif
+
+ int m00 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleX]);
+ int m01 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewX]);
+ int m10 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewY]);
+ int m11 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleY]);
+
+ if (m01 | m10) {
+ // The skew components may be scale-inducing, unless we are dealing
+ // with a pure rotation. Testing for a pure rotation is expensive,
+ // so we opt for being conservative by always setting the scale bit.
+ // along with affine.
+ // By doing this, we are also ensuring that matrices have the same
+ // type masks as their inverses.
+ mask |= kAffine_Mask | kScale_Mask;
+
+ // For rectStaysRect, in the affine case, we only need check that
+ // the primary diagonal is all zeros and that the secondary diagonal
+ // is all non-zero.
+
+ // map non-zero to 1
+ m01 = m01 != 0;
+ m10 = m10 != 0;
+
+ int dp0 = 0 == (m00 | m11) ; // true if both are 0
+ int ds1 = m01 & m10; // true if both are 1
+
+ mask |= (dp0 & ds1) << kRectStaysRect_Shift;
+ } else {
+ // Only test for scale explicitly if not affine, since affine sets the
+ // scale bit.
+ if ((m00 - kScalar1Int) | (m11 - kScalar1Int)) {
+ mask |= kScale_Mask;
+ }
+
+ // Not affine, therefore we already know secondary diagonal is
+ // all zeros, so we just need to check that primary diagonal is
+ // all non-zero.
+
+ // map non-zero to 1
+ m00 = m00 != 0;
+ m11 = m11 != 0;
+
+ // record if the (p)rimary diagonal is all non-zero
+ mask |= (m00 & m11) << kRectStaysRect_Shift;
+ }
+
+ return SkToU8(mask);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+bool operator==(const SkMatrix& a, const SkMatrix& b) {
+ const SkScalar* SK_RESTRICT ma = a.fMat;
+ const SkScalar* SK_RESTRICT mb = b.fMat;
+
+ return ma[0] == mb[0] && ma[1] == mb[1] && ma[2] == mb[2] &&
+ ma[3] == mb[3] && ma[4] == mb[4] && ma[5] == mb[5] &&
+ ma[6] == mb[6] && ma[7] == mb[7] && ma[8] == mb[8];
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// helper function to determine if upper-left 2x2 of matrix is degenerate
+static inline bool is_degenerate_2x2(SkScalar scaleX, SkScalar skewX,
+ SkScalar skewY, SkScalar scaleY) {
+ SkScalar perp_dot = scaleX*scaleY - skewX*skewY;
+ return SkScalarNearlyZero(perp_dot, SK_ScalarNearlyZero*SK_ScalarNearlyZero);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkMatrix::isSimilarity(SkScalar tol) const {
+ // if identity or translate matrix
+ TypeMask mask = this->getType();
+ if (mask <= kTranslate_Mask) {
+ return true;
+ }
+ if (mask & kPerspective_Mask) {
+ return false;
+ }
+
+ SkScalar mx = fMat[kMScaleX];
+ SkScalar my = fMat[kMScaleY];
+ // if no skew, can just compare scale factors
+ if (!(mask & kAffine_Mask)) {
+ return !SkScalarNearlyZero(mx) && SkScalarNearlyEqual(SkScalarAbs(mx), SkScalarAbs(my));
+ }
+ SkScalar sx = fMat[kMSkewX];
+ SkScalar sy = fMat[kMSkewY];
+
+ if (is_degenerate_2x2(mx, sx, sy, my)) {
+ return false;
+ }
+
+ // it has scales and skews, but it could also be rotation, check it out.
+ SkVector vec[2];
+ vec[0].set(mx, sx);
+ vec[1].set(sy, my);
+
+ return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
+ SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
+ SkScalarSquare(tol));
+}
+
+bool SkMatrix::preservesRightAngles(SkScalar tol) const {
+ TypeMask mask = this->getType();
+
+ if (mask <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) {
+ // identity, translate and/or scale
+ return true;
+ }
+ if (mask & kPerspective_Mask) {
+ return false;
+ }
+
+ SkASSERT(mask & kAffine_Mask);
+
+ SkScalar mx = fMat[kMScaleX];
+ SkScalar my = fMat[kMScaleY];
+ SkScalar sx = fMat[kMSkewX];
+ SkScalar sy = fMat[kMSkewY];
+
+ if (is_degenerate_2x2(mx, sx, sy, my)) {
+ return false;
+ }
+
+ // it has scales and skews, but it could also be rotation, check it out.
+ SkVector vec[2];
+ vec[0].set(mx, sx);
+ vec[1].set(sy, my);
+
+ return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
+ SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
+ SkScalarSquare(tol));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::setTranslate(SkScalar dx, SkScalar dy) {
+ if (SkScalarToCompareType(dx) || SkScalarToCompareType(dy)) {
+ fMat[kMTransX] = dx;
+ fMat[kMTransY] = dy;
+
+ fMat[kMScaleX] = fMat[kMScaleY] = SK_Scalar1;
+ fMat[kMSkewX] = fMat[kMSkewY] =
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kTranslate_Mask | kRectStaysRect_Mask);
+ } else {
+ this->reset();
+ }
+}
+
+bool SkMatrix::preTranslate(SkScalar dx, SkScalar dy) {
+ if (this->hasPerspective()) {
+ SkMatrix m;
+ m.setTranslate(dx, dy);
+ return this->preConcat(m);
+ }
+
+ if (SkScalarToCompareType(dx) || SkScalarToCompareType(dy)) {
+ fMat[kMTransX] += SkScalarMul(fMat[kMScaleX], dx) +
+ SkScalarMul(fMat[kMSkewX], dy);
+ fMat[kMTransY] += SkScalarMul(fMat[kMSkewY], dx) +
+ SkScalarMul(fMat[kMScaleY], dy);
+
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+ }
+ return true;
+}
+
+bool SkMatrix::postTranslate(SkScalar dx, SkScalar dy) {
+ if (this->hasPerspective()) {
+ SkMatrix m;
+ m.setTranslate(dx, dy);
+ return this->postConcat(m);
+ }
+
+ if (SkScalarToCompareType(dx) || SkScalarToCompareType(dy)) {
+ fMat[kMTransX] += dx;
+ fMat[kMTransY] += dy;
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ this->reset();
+ } else {
+ fMat[kMScaleX] = sx;
+ fMat[kMScaleY] = sy;
+ fMat[kMTransX] = px - SkScalarMul(sx, px);
+ fMat[kMTransY] = py - SkScalarMul(sy, py);
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ fMat[kMSkewX] = fMat[kMSkewY] =
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+
+ this->setTypeMask(kScale_Mask | kTranslate_Mask | kRectStaysRect_Mask);
+ }
+}
+
+void SkMatrix::setScale(SkScalar sx, SkScalar sy) {
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ this->reset();
+ } else {
+ fMat[kMScaleX] = sx;
+ fMat[kMScaleY] = sy;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ fMat[kMTransX] = fMat[kMTransY] =
+ fMat[kMSkewX] = fMat[kMSkewY] =
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+
+ this->setTypeMask(kScale_Mask | kRectStaysRect_Mask);
+ }
+}
+
+bool SkMatrix::setIDiv(int divx, int divy) {
+ if (!divx || !divy) {
+ return false;
+ }
+ this->setScale(SK_Scalar1 / divx, SK_Scalar1 / divy);
+ return true;
+}
+
+bool SkMatrix::preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ SkMatrix m;
+ m.setScale(sx, sy, px, py);
+ return this->preConcat(m);
+}
+
+bool SkMatrix::preScale(SkScalar sx, SkScalar sy) {
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ return true;
+ }
+
+#ifdef SK_SCALAR_IS_FIXED
+ SkMatrix m;
+ m.setScale(sx, sy);
+ return this->preConcat(m);
+#else
+ // the assumption is that these multiplies are very cheap, and that
+ // a full concat and/or just computing the matrix type is more expensive.
+ // Also, the fixed-point case checks for overflow, but the float doesn't,
+ // so we can get away with these blind multiplies.
+
+ fMat[kMScaleX] = SkScalarMul(fMat[kMScaleX], sx);
+ fMat[kMSkewY] = SkScalarMul(fMat[kMSkewY], sx);
+ fMat[kMPersp0] = SkScalarMul(fMat[kMPersp0], sx);
+
+ fMat[kMSkewX] = SkScalarMul(fMat[kMSkewX], sy);
+ fMat[kMScaleY] = SkScalarMul(fMat[kMScaleY], sy);
+ fMat[kMPersp1] = SkScalarMul(fMat[kMPersp1], sy);
+
+ this->orTypeMask(kScale_Mask);
+ return true;
+#endif
+}
+
+bool SkMatrix::postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ return true;
+ }
+ SkMatrix m;
+ m.setScale(sx, sy, px, py);
+ return this->postConcat(m);
+}
+
+bool SkMatrix::postScale(SkScalar sx, SkScalar sy) {
+ if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
+ return true;
+ }
+ SkMatrix m;
+ m.setScale(sx, sy);
+ return this->postConcat(m);
+}
+
+#ifdef SK_SCALAR_IS_FIXED
+ static inline SkFixed roundidiv(SkFixed numer, int denom) {
+ int ns = numer >> 31;
+ int ds = denom >> 31;
+ numer = (numer ^ ns) - ns;
+ denom = (denom ^ ds) - ds;
+
+ SkFixed answer = (numer + (denom >> 1)) / denom;
+ int as = ns ^ ds;
+ return (answer ^ as) - as;
+ }
+#endif
+
+// this guy perhaps can go away, if we have a fract/high-precision way to
+// scale matrices
+bool SkMatrix::postIDiv(int divx, int divy) {
+ if (divx == 0 || divy == 0) {
+ return false;
+ }
+
+#ifdef SK_SCALAR_IS_FIXED
+ fMat[kMScaleX] = roundidiv(fMat[kMScaleX], divx);
+ fMat[kMSkewX] = roundidiv(fMat[kMSkewX], divx);
+ fMat[kMTransX] = roundidiv(fMat[kMTransX], divx);
+
+ fMat[kMScaleY] = roundidiv(fMat[kMScaleY], divy);
+ fMat[kMSkewY] = roundidiv(fMat[kMSkewY], divy);
+ fMat[kMTransY] = roundidiv(fMat[kMTransY], divy);
+#else
+ const float invX = 1.f / divx;
+ const float invY = 1.f / divy;
+
+ fMat[kMScaleX] *= invX;
+ fMat[kMSkewX] *= invX;
+ fMat[kMTransX] *= invX;
+
+ fMat[kMScaleY] *= invY;
+ fMat[kMSkewY] *= invY;
+ fMat[kMTransY] *= invY;
+#endif
+
+ this->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV,
+ SkScalar px, SkScalar py) {
+ const SkScalar oneMinusCosV = SK_Scalar1 - cosV;
+
+ fMat[kMScaleX] = cosV;
+ fMat[kMSkewX] = -sinV;
+ fMat[kMTransX] = SkScalarMul(sinV, py) + SkScalarMul(oneMinusCosV, px);
+
+ fMat[kMSkewY] = sinV;
+ fMat[kMScaleY] = cosV;
+ fMat[kMTransY] = SkScalarMul(-sinV, px) + SkScalarMul(oneMinusCosV, py);
+
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+}
+
+void SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV) {
+ fMat[kMScaleX] = cosV;
+ fMat[kMSkewX] = -sinV;
+ fMat[kMTransX] = 0;
+
+ fMat[kMSkewY] = sinV;
+ fMat[kMScaleY] = cosV;
+ fMat[kMTransY] = 0;
+
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+}
+
+void SkMatrix::setRotate(SkScalar degrees, SkScalar px, SkScalar py) {
+ SkScalar sinV, cosV;
+ sinV = SkScalarSinCos(SkDegreesToRadians(degrees), &cosV);
+ this->setSinCos(sinV, cosV, px, py);
+}
+
+void SkMatrix::setRotate(SkScalar degrees) {
+ SkScalar sinV, cosV;
+ sinV = SkScalarSinCos(SkDegreesToRadians(degrees), &cosV);
+ this->setSinCos(sinV, cosV);
+}
+
+bool SkMatrix::preRotate(SkScalar degrees, SkScalar px, SkScalar py) {
+ SkMatrix m;
+ m.setRotate(degrees, px, py);
+ return this->preConcat(m);
+}
+
+bool SkMatrix::preRotate(SkScalar degrees) {
+ SkMatrix m;
+ m.setRotate(degrees);
+ return this->preConcat(m);
+}
+
+bool SkMatrix::postRotate(SkScalar degrees, SkScalar px, SkScalar py) {
+ SkMatrix m;
+ m.setRotate(degrees, px, py);
+ return this->postConcat(m);
+}
+
+bool SkMatrix::postRotate(SkScalar degrees) {
+ SkMatrix m;
+ m.setRotate(degrees);
+ return this->postConcat(m);
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::setSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ fMat[kMScaleX] = SK_Scalar1;
+ fMat[kMSkewX] = sx;
+ fMat[kMTransX] = SkScalarMul(-sx, py);
+
+ fMat[kMSkewY] = sy;
+ fMat[kMScaleY] = SK_Scalar1;
+ fMat[kMTransY] = SkScalarMul(-sy, px);
+
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+}
+
+void SkMatrix::setSkew(SkScalar sx, SkScalar sy) {
+ fMat[kMScaleX] = SK_Scalar1;
+ fMat[kMSkewX] = sx;
+ fMat[kMTransX] = 0;
+
+ fMat[kMSkewY] = sy;
+ fMat[kMScaleY] = SK_Scalar1;
+ fMat[kMTransY] = 0;
+
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+ fMat[kMPersp2] = kMatrix22Elem;
+
+ this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+}
+
+bool SkMatrix::preSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ SkMatrix m;
+ m.setSkew(sx, sy, px, py);
+ return this->preConcat(m);
+}
+
+bool SkMatrix::preSkew(SkScalar sx, SkScalar sy) {
+ SkMatrix m;
+ m.setSkew(sx, sy);
+ return this->preConcat(m);
+}
+
+bool SkMatrix::postSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
+ SkMatrix m;
+ m.setSkew(sx, sy, px, py);
+ return this->postConcat(m);
+}
+
+bool SkMatrix::postSkew(SkScalar sx, SkScalar sy) {
+ SkMatrix m;
+ m.setSkew(sx, sy);
+ return this->postConcat(m);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkMatrix::setRectToRect(const SkRect& src, const SkRect& dst,
+ ScaleToFit align)
+{
+ if (src.isEmpty()) {
+ this->reset();
+ return false;
+ }
+
+ if (dst.isEmpty()) {
+ sk_bzero(fMat, 8 * sizeof(SkScalar));
+ this->setTypeMask(kScale_Mask | kRectStaysRect_Mask);
+ } else {
+ SkScalar tx, sx = SkScalarDiv(dst.width(), src.width());
+ SkScalar ty, sy = SkScalarDiv(dst.height(), src.height());
+ bool xLarger = false;
+
+ if (align != kFill_ScaleToFit) {
+ if (sx > sy) {
+ xLarger = true;
+ sx = sy;
+ } else {
+ sy = sx;
+ }
+ }
+
+ tx = dst.fLeft - SkScalarMul(src.fLeft, sx);
+ ty = dst.fTop - SkScalarMul(src.fTop, sy);
+ if (align == kCenter_ScaleToFit || align == kEnd_ScaleToFit) {
+ SkScalar diff;
+
+ if (xLarger) {
+ diff = dst.width() - SkScalarMul(src.width(), sy);
+ } else {
+ diff = dst.height() - SkScalarMul(src.height(), sy);
+ }
+
+ if (align == kCenter_ScaleToFit) {
+ diff = SkScalarHalf(diff);
+ }
+
+ if (xLarger) {
+ tx += diff;
+ } else {
+ ty += diff;
+ }
+ }
+
+ fMat[kMScaleX] = sx;
+ fMat[kMScaleY] = sy;
+ fMat[kMTransX] = tx;
+ fMat[kMTransY] = ty;
+ fMat[kMSkewX] = fMat[kMSkewY] =
+ fMat[kMPersp0] = fMat[kMPersp1] = 0;
+
+ unsigned mask = kRectStaysRect_Mask;
+ if (sx != SK_Scalar1 || sy != SK_Scalar1) {
+ mask |= kScale_Mask;
+ }
+ if (tx || ty) {
+ mask |= kTranslate_Mask;
+ }
+ this->setTypeMask(mask);
+ }
+ // shared cleanup
+ fMat[kMPersp2] = kMatrix22Elem;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_SCALAR_IS_FLOAT
+ static inline int fixmuladdmul(float a, float b, float c, float d,
+ float* result) {
+ *result = SkDoubleToFloat((double)a * b + (double)c * d);
+ return true;
+ }
+
+ static inline bool rowcol3(const float row[], const float col[],
+ float* result) {
+ *result = row[0] * col[0] + row[1] * col[3] + row[2] * col[6];
+ return true;
+ }
+
+ static inline int negifaddoverflows(float& result, float a, float b) {
+ result = a + b;
+ return 0;
+ }
+#else
+ static inline bool fixmuladdmul(SkFixed a, SkFixed b, SkFixed c, SkFixed d,
+ SkFixed* result) {
+ Sk64 tmp1, tmp2;
+ tmp1.setMul(a, b);
+ tmp2.setMul(c, d);
+ tmp1.add(tmp2);
+ if (tmp1.isFixed()) {
+ *result = tmp1.getFixed();
+ return true;
+ }
+ return false;
+ }
+
+ static inline SkFixed fracmuladdmul(SkFixed a, SkFract b, SkFixed c,
+ SkFract d) {
+ Sk64 tmp1, tmp2;
+ tmp1.setMul(a, b);
+ tmp2.setMul(c, d);
+ tmp1.add(tmp2);
+ return tmp1.getFract();
+ }
+
+ static inline bool rowcol3(const SkFixed row[], const SkFixed col[],
+ SkFixed* result) {
+ Sk64 tmp1, tmp2;
+
+ tmp1.setMul(row[0], col[0]); // N * fixed
+ tmp2.setMul(row[1], col[3]); // N * fixed
+ tmp1.add(tmp2);
+
+ tmp2.setMul(row[2], col[6]); // N * fract
+ tmp2.roundRight(14); // make it fixed
+ tmp1.add(tmp2);
+
+ if (tmp1.isFixed()) {
+ *result = tmp1.getFixed();
+ return true;
+ }
+ return false;
+ }
+
+ static inline int negifaddoverflows(SkFixed& result, SkFixed a, SkFixed b) {
+ SkFixed c = a + b;
+ result = c;
+ return (c ^ a) & (c ^ b);
+ }
+#endif
+
+static void normalize_perspective(SkScalar mat[9]) {
+ if (SkScalarAbs(mat[SkMatrix::kMPersp2]) > kMatrix22Elem) {
+ for (int i = 0; i < 9; i++)
+ mat[i] = SkScalarHalf(mat[i]);
+ }
+}
+
+bool SkMatrix::setConcat(const SkMatrix& a, const SkMatrix& b) {
+ TypeMask aType = a.getPerspectiveTypeMaskOnly();
+ TypeMask bType = b.getPerspectiveTypeMaskOnly();
+
+ if (a.isTriviallyIdentity()) {
+ *this = b;
+ } else if (b.isTriviallyIdentity()) {
+ *this = a;
+ } else {
+ SkMatrix tmp;
+
+ if ((aType | bType) & kPerspective_Mask) {
+ if (!rowcol3(&a.fMat[0], &b.fMat[0], &tmp.fMat[kMScaleX])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[0], &b.fMat[1], &tmp.fMat[kMSkewX])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[0], &b.fMat[2], &tmp.fMat[kMTransX])) {
+ return false;
+ }
+
+ if (!rowcol3(&a.fMat[3], &b.fMat[0], &tmp.fMat[kMSkewY])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[3], &b.fMat[1], &tmp.fMat[kMScaleY])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[3], &b.fMat[2], &tmp.fMat[kMTransY])) {
+ return false;
+ }
+
+ if (!rowcol3(&a.fMat[6], &b.fMat[0], &tmp.fMat[kMPersp0])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[6], &b.fMat[1], &tmp.fMat[kMPersp1])) {
+ return false;
+ }
+ if (!rowcol3(&a.fMat[6], &b.fMat[2], &tmp.fMat[kMPersp2])) {
+ return false;
+ }
+
+ normalize_perspective(tmp.fMat);
+ tmp.setTypeMask(kUnknown_Mask);
+ } else { // not perspective
+ if (!fixmuladdmul(a.fMat[kMScaleX], b.fMat[kMScaleX],
+ a.fMat[kMSkewX], b.fMat[kMSkewY], &tmp.fMat[kMScaleX])) {
+ return false;
+ }
+ if (!fixmuladdmul(a.fMat[kMScaleX], b.fMat[kMSkewX],
+ a.fMat[kMSkewX], b.fMat[kMScaleY], &tmp.fMat[kMSkewX])) {
+ return false;
+ }
+ if (!fixmuladdmul(a.fMat[kMScaleX], b.fMat[kMTransX],
+ a.fMat[kMSkewX], b.fMat[kMTransY], &tmp.fMat[kMTransX])) {
+ return false;
+ }
+ if (negifaddoverflows(tmp.fMat[kMTransX], tmp.fMat[kMTransX],
+ a.fMat[kMTransX]) < 0) {
+ return false;
+ }
+
+ if (!fixmuladdmul(a.fMat[kMSkewY], b.fMat[kMScaleX],
+ a.fMat[kMScaleY], b.fMat[kMSkewY], &tmp.fMat[kMSkewY])) {
+ return false;
+ }
+ if (!fixmuladdmul(a.fMat[kMSkewY], b.fMat[kMSkewX],
+ a.fMat[kMScaleY], b.fMat[kMScaleY], &tmp.fMat[kMScaleY])) {
+ return false;
+ }
+ if (!fixmuladdmul(a.fMat[kMSkewY], b.fMat[kMTransX],
+ a.fMat[kMScaleY], b.fMat[kMTransY], &tmp.fMat[kMTransY])) {
+ return false;
+ }
+ if (negifaddoverflows(tmp.fMat[kMTransY], tmp.fMat[kMTransY],
+ a.fMat[kMTransY]) < 0) {
+ return false;
+ }
+
+ tmp.fMat[kMPersp0] = tmp.fMat[kMPersp1] = 0;
+ tmp.fMat[kMPersp2] = kMatrix22Elem;
+ //SkDebugf("Concat mat non-persp type: %d\n", tmp.getType());
+ //SkASSERT(!(tmp.getType() & kPerspective_Mask));
+ tmp.setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask);
+ }
+ *this = tmp;
+ }
+ return true;
+}
+
+bool SkMatrix::preConcat(const SkMatrix& mat) {
+ // check for identity first, so we don't do a needless copy of ourselves
+ // to ourselves inside setConcat()
+ return mat.isIdentity() || this->setConcat(*this, mat);
+}
+
+bool SkMatrix::postConcat(const SkMatrix& mat) {
+ // check for identity first, so we don't do a needless copy of ourselves
+ // to ourselves inside setConcat()
+ return mat.isIdentity() || this->setConcat(mat, *this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* Matrix inversion is very expensive, but also the place where keeping
+ precision may be most important (here and matrix concat). Hence to avoid
+ bitmap blitting artifacts when walking the inverse, we use doubles for
+ the intermediate math, even though we know that is more expensive.
+ The fixed counter part is us using Sk64 for temp calculations.
+ */
+
+#ifdef SK_SCALAR_IS_FLOAT
+ typedef double SkDetScalar;
+ #define SkPerspMul(a, b) SkScalarMul(a, b)
+ #define SkScalarMulShift(a, b, s) SkDoubleToFloat((a) * (b))
+ static double sk_inv_determinant(const float mat[9], int isPerspective,
+ int* /* (only used in Fixed case) */) {
+ double det;
+
+ if (isPerspective) {
+ det = mat[SkMatrix::kMScaleX] * ((double)mat[SkMatrix::kMScaleY] * mat[SkMatrix::kMPersp2] - (double)mat[SkMatrix::kMTransY] * mat[SkMatrix::kMPersp1]) +
+ mat[SkMatrix::kMSkewX] * ((double)mat[SkMatrix::kMTransY] * mat[SkMatrix::kMPersp0] - (double)mat[SkMatrix::kMSkewY] * mat[SkMatrix::kMPersp2]) +
+ mat[SkMatrix::kMTransX] * ((double)mat[SkMatrix::kMSkewY] * mat[SkMatrix::kMPersp1] - (double)mat[SkMatrix::kMScaleY] * mat[SkMatrix::kMPersp0]);
+ } else {
+ det = (double)mat[SkMatrix::kMScaleX] * mat[SkMatrix::kMScaleY] - (double)mat[SkMatrix::kMSkewX] * mat[SkMatrix::kMSkewY];
+ }
+
+ // Since the determinant is on the order of the cube of the matrix members,
+ // compare to the cube of the default nearly-zero constant (although an
+ // estimate of the condition number would be better if it wasn't so expensive).
+ if (SkScalarNearlyZero((float)det, SK_ScalarNearlyZero * SK_ScalarNearlyZero * SK_ScalarNearlyZero)) {
+ return 0;
+ }
+ return 1.0 / det;
+ }
+ // we declar a,b,c,d to all be doubles, because we want to perform
+ // double-precision muls and subtract, even though the original values are
+ // from the matrix, which are floats.
+ static float inline mul_diff_scale(double a, double b, double c, double d,
+ double scale) {
+ return SkDoubleToFloat((a * b - c * d) * scale);
+ }
+#else
+ typedef SkFixed SkDetScalar;
+ #define SkPerspMul(a, b) SkFractMul(a, b)
+ #define SkScalarMulShift(a, b, s) SkMulShift(a, b, s)
+ static void set_muladdmul(Sk64* dst, int32_t a, int32_t b, int32_t c,
+ int32_t d) {
+ Sk64 tmp;
+ dst->setMul(a, b);
+ tmp.setMul(c, d);
+ dst->add(tmp);
+ }
+
+ static SkFixed sk_inv_determinant(const SkFixed mat[9], int isPerspective,
+ int* shift) {
+ Sk64 tmp1, tmp2;
+
+ if (isPerspective) {
+ tmp1.setMul(mat[SkMatrix::kMScaleX], fracmuladdmul(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2], -mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1]));
+ tmp2.setMul(mat[SkMatrix::kMSkewX], fracmuladdmul(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0], -mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2]));
+ tmp1.add(tmp2);
+ tmp2.setMul(mat[SkMatrix::kMTransX], fracmuladdmul(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1], -mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]));
+ tmp1.add(tmp2);
+ } else {
+ tmp1.setMul(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY]);
+ tmp2.setMul(mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]);
+ tmp1.sub(tmp2);
+ }
+
+ int s = tmp1.getClzAbs();
+ *shift = s;
+
+ SkFixed denom;
+ if (s <= 32) {
+ denom = tmp1.getShiftRight(33 - s);
+ } else {
+ denom = (int32_t)tmp1.fLo << (s - 33);
+ }
+
+ if (denom == 0) {
+ return 0;
+ }
+ /** This could perhaps be a special fractdiv function, since both of its
+ arguments are known to have bit 31 clear and bit 30 set (when they
+ are made positive), thus eliminating the need for calling clz()
+ */
+ return SkFractDiv(SK_Fract1, denom);
+ }
+#endif
+
+void SkMatrix::SetAffineIdentity(SkScalar affine[6]) {
+ affine[kAScaleX] = SK_Scalar1;
+ affine[kASkewY] = 0;
+ affine[kASkewX] = 0;
+ affine[kAScaleY] = SK_Scalar1;
+ affine[kATransX] = 0;
+ affine[kATransY] = 0;
+}
+
+bool SkMatrix::asAffine(SkScalar affine[6]) const {
+ if (this->hasPerspective()) {
+ return false;
+ }
+ if (affine) {
+ affine[kAScaleX] = this->fMat[kMScaleX];
+ affine[kASkewY] = this->fMat[kMSkewY];
+ affine[kASkewX] = this->fMat[kMSkewX];
+ affine[kAScaleY] = this->fMat[kMScaleY];
+ affine[kATransX] = this->fMat[kMTransX];
+ affine[kATransY] = this->fMat[kMTransY];
+ }
+ return true;
+}
+
+bool SkMatrix::invertNonIdentity(SkMatrix* inv) const {
+ SkASSERT(!this->isIdentity());
+
+ TypeMask mask = this->getType();
+
+ if (0 == (mask & ~(kScale_Mask | kTranslate_Mask))) {
+ bool invertible = true;
+ if (inv) {
+ if (mask & kScale_Mask) {
+ SkScalar invX = fMat[kMScaleX];
+ SkScalar invY = fMat[kMScaleY];
+ if (0 == invX || 0 == invY) {
+ return false;
+ }
+ invX = SkScalarInvert(invX);
+ invY = SkScalarInvert(invY);
+
+ // Must be careful when writing to inv, since it may be the
+ // same memory as this.
+
+ inv->fMat[kMSkewX] = inv->fMat[kMSkewY] =
+ inv->fMat[kMPersp0] = inv->fMat[kMPersp1] = 0;
+
+ inv->fMat[kMScaleX] = invX;
+ inv->fMat[kMScaleY] = invY;
+ inv->fMat[kMPersp2] = kMatrix22Elem;
+ inv->fMat[kMTransX] = -SkScalarMul(fMat[kMTransX], invX);
+ inv->fMat[kMTransY] = -SkScalarMul(fMat[kMTransY], invY);
+
+ inv->setTypeMask(mask | kRectStaysRect_Mask);
+ } else {
+ // translate only
+ inv->setTranslate(-fMat[kMTransX], -fMat[kMTransY]);
+ }
+ } else { // inv is NULL, just check if we're invertible
+ if (!fMat[kMScaleX] || !fMat[kMScaleY]) {
+ invertible = false;
+ }
+ }
+ return invertible;
+ }
+
+ int isPersp = mask & kPerspective_Mask;
+ int shift;
+ SkDetScalar scale = sk_inv_determinant(fMat, isPersp, &shift);
+
+ if (scale == 0) { // underflow
+ return false;
+ }
+
+ if (inv) {
+ SkMatrix tmp;
+ if (inv == this) {
+ inv = &tmp;
+ }
+
+ if (isPersp) {
+ shift = 61 - shift;
+ inv->fMat[kMScaleX] = SkScalarMulShift(SkPerspMul(fMat[kMScaleY], fMat[kMPersp2]) - SkPerspMul(fMat[kMTransY], fMat[kMPersp1]), scale, shift);
+ inv->fMat[kMSkewX] = SkScalarMulShift(SkPerspMul(fMat[kMTransX], fMat[kMPersp1]) - SkPerspMul(fMat[kMSkewX], fMat[kMPersp2]), scale, shift);
+ inv->fMat[kMTransX] = SkScalarMulShift(SkScalarMul(fMat[kMSkewX], fMat[kMTransY]) - SkScalarMul(fMat[kMTransX], fMat[kMScaleY]), scale, shift);
+
+ inv->fMat[kMSkewY] = SkScalarMulShift(SkPerspMul(fMat[kMTransY], fMat[kMPersp0]) - SkPerspMul(fMat[kMSkewY], fMat[kMPersp2]), scale, shift);
+ inv->fMat[kMScaleY] = SkScalarMulShift(SkPerspMul(fMat[kMScaleX], fMat[kMPersp2]) - SkPerspMul(fMat[kMTransX], fMat[kMPersp0]), scale, shift);
+ inv->fMat[kMTransY] = SkScalarMulShift(SkScalarMul(fMat[kMTransX], fMat[kMSkewY]) - SkScalarMul(fMat[kMScaleX], fMat[kMTransY]), scale, shift);
+
+ inv->fMat[kMPersp0] = SkScalarMulShift(SkScalarMul(fMat[kMSkewY], fMat[kMPersp1]) - SkScalarMul(fMat[kMScaleY], fMat[kMPersp0]), scale, shift);
+ inv->fMat[kMPersp1] = SkScalarMulShift(SkScalarMul(fMat[kMSkewX], fMat[kMPersp0]) - SkScalarMul(fMat[kMScaleX], fMat[kMPersp1]), scale, shift);
+ inv->fMat[kMPersp2] = SkScalarMulShift(SkScalarMul(fMat[kMScaleX], fMat[kMScaleY]) - SkScalarMul(fMat[kMSkewX], fMat[kMSkewY]), scale, shift);
+#ifdef SK_SCALAR_IS_FIXED
+ if (SkAbs32(inv->fMat[kMPersp2]) > SK_Fixed1) {
+ Sk64 tmp;
+
+ tmp.set(SK_Fract1);
+ tmp.shiftLeft(16);
+ tmp.div(inv->fMat[kMPersp2], Sk64::kRound_DivOption);
+
+ SkFract scale = tmp.get32();
+
+ for (int i = 0; i < 9; i++) {
+ inv->fMat[i] = SkFractMul(inv->fMat[i], scale);
+ }
+ }
+ inv->fMat[kMPersp2] = SkFixedToFract(inv->fMat[kMPersp2]);
+#endif
+ } else { // not perspective
+#ifdef SK_SCALAR_IS_FIXED
+ Sk64 tx, ty;
+ int clzNumer;
+
+ // check the 2x2 for overflow
+ {
+ int32_t value = SkAbs32(fMat[kMScaleY]);
+ value |= SkAbs32(fMat[kMSkewX]);
+ value |= SkAbs32(fMat[kMScaleX]);
+ value |= SkAbs32(fMat[kMSkewY]);
+ clzNumer = SkCLZ(value);
+ if (shift - clzNumer > 31)
+ return false; // overflow
+ }
+
+ set_muladdmul(&tx, fMat[kMSkewX], fMat[kMTransY], -fMat[kMScaleY], fMat[kMTransX]);
+ set_muladdmul(&ty, fMat[kMSkewY], fMat[kMTransX], -fMat[kMScaleX], fMat[kMTransY]);
+ // check tx,ty for overflow
+ clzNumer = SkCLZ(SkAbs32(tx.fHi) | SkAbs32(ty.fHi));
+ if (shift - clzNumer > 14) {
+ return false; // overflow
+ }
+
+ int fixedShift = 61 - shift;
+ int sk64shift = 44 - shift + clzNumer;
+
+ inv->fMat[kMScaleX] = SkMulShift(fMat[kMScaleY], scale, fixedShift);
+ inv->fMat[kMSkewX] = SkMulShift(-fMat[kMSkewX], scale, fixedShift);
+ inv->fMat[kMTransX] = SkMulShift(tx.getShiftRight(33 - clzNumer), scale, sk64shift);
+
+ inv->fMat[kMSkewY] = SkMulShift(-fMat[kMSkewY], scale, fixedShift);
+ inv->fMat[kMScaleY] = SkMulShift(fMat[kMScaleX], scale, fixedShift);
+ inv->fMat[kMTransY] = SkMulShift(ty.getShiftRight(33 - clzNumer), scale, sk64shift);
+#else
+ inv->fMat[kMScaleX] = SkDoubleToFloat(fMat[kMScaleY] * scale);
+ inv->fMat[kMSkewX] = SkDoubleToFloat(-fMat[kMSkewX] * scale);
+ inv->fMat[kMTransX] = mul_diff_scale(fMat[kMSkewX], fMat[kMTransY],
+ fMat[kMScaleY], fMat[kMTransX], scale);
+
+ inv->fMat[kMSkewY] = SkDoubleToFloat(-fMat[kMSkewY] * scale);
+ inv->fMat[kMScaleY] = SkDoubleToFloat(fMat[kMScaleX] * scale);
+ inv->fMat[kMTransY] = mul_diff_scale(fMat[kMSkewY], fMat[kMTransX],
+ fMat[kMScaleX], fMat[kMTransY], scale);
+#endif
+ inv->fMat[kMPersp0] = 0;
+ inv->fMat[kMPersp1] = 0;
+ inv->fMat[kMPersp2] = kMatrix22Elem;
+
+ }
+
+ inv->setTypeMask(fTypeMask);
+
+ if (inv == &tmp) {
+ *(SkMatrix*)this = tmp;
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::Identity_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(m.getType() == 0);
+
+ if (dst != src && count > 0)
+ memcpy(dst, src, count * sizeof(SkPoint));
+}
+
+void SkMatrix::Trans_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(m.getType() == kTranslate_Mask);
+
+ if (count > 0) {
+ SkScalar tx = m.fMat[kMTransX];
+ SkScalar ty = m.fMat[kMTransY];
+ do {
+ dst->fY = src->fY + ty;
+ dst->fX = src->fX + tx;
+ src += 1;
+ dst += 1;
+ } while (--count);
+ }
+}
+
+void SkMatrix::Scale_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(m.getType() == kScale_Mask);
+
+ if (count > 0) {
+ SkScalar mx = m.fMat[kMScaleX];
+ SkScalar my = m.fMat[kMScaleY];
+ do {
+ dst->fY = SkScalarMul(src->fY, my);
+ dst->fX = SkScalarMul(src->fX, mx);
+ src += 1;
+ dst += 1;
+ } while (--count);
+ }
+}
+
+void SkMatrix::ScaleTrans_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(m.getType() == (kScale_Mask | kTranslate_Mask));
+
+ if (count > 0) {
+ SkScalar mx = m.fMat[kMScaleX];
+ SkScalar my = m.fMat[kMScaleY];
+ SkScalar tx = m.fMat[kMTransX];
+ SkScalar ty = m.fMat[kMTransY];
+ do {
+ dst->fY = SkScalarMulAdd(src->fY, my, ty);
+ dst->fX = SkScalarMulAdd(src->fX, mx, tx);
+ src += 1;
+ dst += 1;
+ } while (--count);
+ }
+}
+
+void SkMatrix::Rot_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT((m.getType() & (kPerspective_Mask | kTranslate_Mask)) == 0);
+
+ if (count > 0) {
+ SkScalar mx = m.fMat[kMScaleX];
+ SkScalar my = m.fMat[kMScaleY];
+ SkScalar kx = m.fMat[kMSkewX];
+ SkScalar ky = m.fMat[kMSkewY];
+ do {
+ SkScalar sy = src->fY;
+ SkScalar sx = src->fX;
+ src += 1;
+ dst->fY = SkScalarMul(sx, ky) + SkScalarMul(sy, my);
+ dst->fX = SkScalarMul(sx, mx) + SkScalarMul(sy, kx);
+ dst += 1;
+ } while (--count);
+ }
+}
+
+void SkMatrix::RotTrans_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(!m.hasPerspective());
+
+ if (count > 0) {
+ SkScalar mx = m.fMat[kMScaleX];
+ SkScalar my = m.fMat[kMScaleY];
+ SkScalar kx = m.fMat[kMSkewX];
+ SkScalar ky = m.fMat[kMSkewY];
+ SkScalar tx = m.fMat[kMTransX];
+ SkScalar ty = m.fMat[kMTransY];
+ do {
+ SkScalar sy = src->fY;
+ SkScalar sx = src->fX;
+ src += 1;
+ dst->fY = SkScalarMul(sx, ky) + SkScalarMulAdd(sy, my, ty);
+ dst->fX = SkScalarMul(sx, mx) + SkScalarMulAdd(sy, kx, tx);
+ dst += 1;
+ } while (--count);
+ }
+}
+
+void SkMatrix::Persp_pts(const SkMatrix& m, SkPoint dst[],
+ const SkPoint src[], int count) {
+ SkASSERT(m.hasPerspective());
+
+#ifdef SK_SCALAR_IS_FIXED
+ SkFixed persp2 = SkFractToFixed(m.fMat[kMPersp2]);
+#endif
+
+ if (count > 0) {
+ do {
+ SkScalar sy = src->fY;
+ SkScalar sx = src->fX;
+ src += 1;
+
+ SkScalar x = SkScalarMul(sx, m.fMat[kMScaleX]) +
+ SkScalarMul(sy, m.fMat[kMSkewX]) + m.fMat[kMTransX];
+ SkScalar y = SkScalarMul(sx, m.fMat[kMSkewY]) +
+ SkScalarMul(sy, m.fMat[kMScaleY]) + m.fMat[kMTransY];
+#ifdef SK_SCALAR_IS_FIXED
+ SkFixed z = SkFractMul(sx, m.fMat[kMPersp0]) +
+ SkFractMul(sy, m.fMat[kMPersp1]) + persp2;
+#else
+ float z = SkScalarMul(sx, m.fMat[kMPersp0]) +
+ SkScalarMulAdd(sy, m.fMat[kMPersp1], m.fMat[kMPersp2]);
+#endif
+ if (z) {
+ z = SkScalarFastInvert(z);
+ }
+
+ dst->fY = SkScalarMul(y, z);
+ dst->fX = SkScalarMul(x, z);
+ dst += 1;
+ } while (--count);
+ }
+}
+
+const SkMatrix::MapPtsProc SkMatrix::gMapPtsProcs[] = {
+ SkMatrix::Identity_pts, SkMatrix::Trans_pts,
+ SkMatrix::Scale_pts, SkMatrix::ScaleTrans_pts,
+ SkMatrix::Rot_pts, SkMatrix::RotTrans_pts,
+ SkMatrix::Rot_pts, SkMatrix::RotTrans_pts,
+ // repeat the persp proc 8 times
+ SkMatrix::Persp_pts, SkMatrix::Persp_pts,
+ SkMatrix::Persp_pts, SkMatrix::Persp_pts,
+ SkMatrix::Persp_pts, SkMatrix::Persp_pts,
+ SkMatrix::Persp_pts, SkMatrix::Persp_pts
+};
+
+void SkMatrix::mapPoints(SkPoint dst[], const SkPoint src[], int count) const {
+ SkASSERT((dst && src && count > 0) || count == 0);
+ // no partial overlap
+ SkASSERT(src == dst || SkAbs32((int32_t)(src - dst)) >= count);
+
+ this->getMapPtsProc()(*this, dst, src, count);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::mapVectors(SkPoint dst[], const SkPoint src[], int count) const {
+ if (this->hasPerspective()) {
+ SkPoint origin;
+
+ MapXYProc proc = this->getMapXYProc();
+ proc(*this, 0, 0, &origin);
+
+ for (int i = count - 1; i >= 0; --i) {
+ SkPoint tmp;
+
+ proc(*this, src[i].fX, src[i].fY, &tmp);
+ dst[i].set(tmp.fX - origin.fX, tmp.fY - origin.fY);
+ }
+ } else {
+ SkMatrix tmp = *this;
+
+ tmp.fMat[kMTransX] = tmp.fMat[kMTransY] = 0;
+ tmp.clearTypeMask(kTranslate_Mask);
+ tmp.mapPoints(dst, src, count);
+ }
+}
+
+bool SkMatrix::mapRect(SkRect* dst, const SkRect& src) const {
+ SkASSERT(dst && &src);
+
+ if (this->rectStaysRect()) {
+ this->mapPoints((SkPoint*)dst, (const SkPoint*)&src, 2);
+ dst->sort();
+ return true;
+ } else {
+ SkPoint quad[4];
+
+ src.toQuad(quad);
+ this->mapPoints(quad, quad, 4);
+ dst->set(quad, 4);
+ return false;
+ }
+}
+
+SkScalar SkMatrix::mapRadius(SkScalar radius) const {
+ SkVector vec[2];
+
+ vec[0].set(radius, 0);
+ vec[1].set(0, radius);
+ this->mapVectors(vec, 2);
+
+ SkScalar d0 = vec[0].length();
+ SkScalar d1 = vec[1].length();
+
+ return SkScalarMean(d0, d1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix::Persp_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT(m.hasPerspective());
+
+ SkScalar x = SkScalarMul(sx, m.fMat[kMScaleX]) +
+ SkScalarMul(sy, m.fMat[kMSkewX]) + m.fMat[kMTransX];
+ SkScalar y = SkScalarMul(sx, m.fMat[kMSkewY]) +
+ SkScalarMul(sy, m.fMat[kMScaleY]) + m.fMat[kMTransY];
+#ifdef SK_SCALAR_IS_FIXED
+ SkFixed z = SkFractMul(sx, m.fMat[kMPersp0]) +
+ SkFractMul(sy, m.fMat[kMPersp1]) +
+ SkFractToFixed(m.fMat[kMPersp2]);
+#else
+ float z = SkScalarMul(sx, m.fMat[kMPersp0]) +
+ SkScalarMul(sy, m.fMat[kMPersp1]) + m.fMat[kMPersp2];
+#endif
+ if (z) {
+ z = SkScalarFastInvert(z);
+ }
+ pt->fX = SkScalarMul(x, z);
+ pt->fY = SkScalarMul(y, z);
+}
+
+#ifdef SK_SCALAR_IS_FIXED
+static SkFixed fixmuladdmul(SkFixed a, SkFixed b, SkFixed c, SkFixed d) {
+ Sk64 tmp, tmp1;
+
+ tmp.setMul(a, b);
+ tmp1.setMul(c, d);
+ return tmp.addGetFixed(tmp1);
+// tmp.add(tmp1);
+// return tmp.getFixed();
+}
+#endif
+
+void SkMatrix::RotTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask)) == kAffine_Mask);
+
+#ifdef SK_SCALAR_IS_FIXED
+ pt->fX = fixmuladdmul(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) +
+ m.fMat[kMTransX];
+ pt->fY = fixmuladdmul(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) +
+ m.fMat[kMTransY];
+#else
+ pt->fX = SkScalarMul(sx, m.fMat[kMScaleX]) +
+ SkScalarMulAdd(sy, m.fMat[kMSkewX], m.fMat[kMTransX]);
+ pt->fY = SkScalarMul(sx, m.fMat[kMSkewY]) +
+ SkScalarMulAdd(sy, m.fMat[kMScaleY], m.fMat[kMTransY]);
+#endif
+}
+
+void SkMatrix::Rot_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask))== kAffine_Mask);
+ SkASSERT(0 == m.fMat[kMTransX]);
+ SkASSERT(0 == m.fMat[kMTransY]);
+
+#ifdef SK_SCALAR_IS_FIXED
+ pt->fX = fixmuladdmul(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]);
+ pt->fY = fixmuladdmul(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]);
+#else
+ pt->fX = SkScalarMul(sx, m.fMat[kMScaleX]) +
+ SkScalarMulAdd(sy, m.fMat[kMSkewX], m.fMat[kMTransX]);
+ pt->fY = SkScalarMul(sx, m.fMat[kMSkewY]) +
+ SkScalarMulAdd(sy, m.fMat[kMScaleY], m.fMat[kMTransY]);
+#endif
+}
+
+void SkMatrix::ScaleTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask))
+ == kScale_Mask);
+
+ pt->fX = SkScalarMulAdd(sx, m.fMat[kMScaleX], m.fMat[kMTransX]);
+ pt->fY = SkScalarMulAdd(sy, m.fMat[kMScaleY], m.fMat[kMTransY]);
+}
+
+void SkMatrix::Scale_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask))
+ == kScale_Mask);
+ SkASSERT(0 == m.fMat[kMTransX]);
+ SkASSERT(0 == m.fMat[kMTransY]);
+
+ pt->fX = SkScalarMul(sx, m.fMat[kMScaleX]);
+ pt->fY = SkScalarMul(sy, m.fMat[kMScaleY]);
+}
+
+void SkMatrix::Trans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT(m.getType() == kTranslate_Mask);
+
+ pt->fX = sx + m.fMat[kMTransX];
+ pt->fY = sy + m.fMat[kMTransY];
+}
+
+void SkMatrix::Identity_xy(const SkMatrix& m, SkScalar sx, SkScalar sy,
+ SkPoint* pt) {
+ SkASSERT(0 == m.getType());
+
+ pt->fX = sx;
+ pt->fY = sy;
+}
+
+const SkMatrix::MapXYProc SkMatrix::gMapXYProcs[] = {
+ SkMatrix::Identity_xy, SkMatrix::Trans_xy,
+ SkMatrix::Scale_xy, SkMatrix::ScaleTrans_xy,
+ SkMatrix::Rot_xy, SkMatrix::RotTrans_xy,
+ SkMatrix::Rot_xy, SkMatrix::RotTrans_xy,
+ // repeat the persp proc 8 times
+ SkMatrix::Persp_xy, SkMatrix::Persp_xy,
+ SkMatrix::Persp_xy, SkMatrix::Persp_xy,
+ SkMatrix::Persp_xy, SkMatrix::Persp_xy,
+ SkMatrix::Persp_xy, SkMatrix::Persp_xy
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// if its nearly zero (just made up 26, perhaps it should be bigger or smaller)
+#ifdef SK_SCALAR_IS_FIXED
+ typedef SkFract SkPerspElemType;
+ #define PerspNearlyZero(x) (SkAbs32(x) < (SK_Fract1 >> 26))
+#else
+ typedef float SkPerspElemType;
+ #define PerspNearlyZero(x) SkScalarNearlyZero(x, (1.0f / (1 << 26)))
+#endif
+
+bool SkMatrix::fixedStepInX(SkScalar y, SkFixed* stepX, SkFixed* stepY) const {
+ if (PerspNearlyZero(fMat[kMPersp0])) {
+ if (stepX || stepY) {
+ if (PerspNearlyZero(fMat[kMPersp1]) &&
+ PerspNearlyZero(fMat[kMPersp2] - kMatrix22Elem)) {
+ if (stepX) {
+ *stepX = SkScalarToFixed(fMat[kMScaleX]);
+ }
+ if (stepY) {
+ *stepY = SkScalarToFixed(fMat[kMSkewY]);
+ }
+ } else {
+#ifdef SK_SCALAR_IS_FIXED
+ SkFixed z = SkFractMul(y, fMat[kMPersp1]) +
+ SkFractToFixed(fMat[kMPersp2]);
+#else
+ float z = y * fMat[kMPersp1] + fMat[kMPersp2];
+#endif
+ if (stepX) {
+ *stepX = SkScalarToFixed(SkScalarDiv(fMat[kMScaleX], z));
+ }
+ if (stepY) {
+ *stepY = SkScalarToFixed(SkScalarDiv(fMat[kMSkewY], z));
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkPerspIter.h"
+
+SkPerspIter::SkPerspIter(const SkMatrix& m, SkScalar x0, SkScalar y0, int count)
+ : fMatrix(m), fSX(x0), fSY(y0), fCount(count) {
+ SkPoint pt;
+
+ SkMatrix::Persp_xy(m, x0, y0, &pt);
+ fX = SkScalarToFixed(pt.fX);
+ fY = SkScalarToFixed(pt.fY);
+}
+
+int SkPerspIter::next() {
+ int n = fCount;
+
+ if (0 == n) {
+ return 0;
+ }
+ SkPoint pt;
+ SkFixed x = fX;
+ SkFixed y = fY;
+ SkFixed dx, dy;
+
+ if (n >= kCount) {
+ n = kCount;
+ fSX += SkIntToScalar(kCount);
+ SkMatrix::Persp_xy(fMatrix, fSX, fSY, &pt);
+ fX = SkScalarToFixed(pt.fX);
+ fY = SkScalarToFixed(pt.fY);
+ dx = (fX - x) >> kShift;
+ dy = (fY - y) >> kShift;
+ } else {
+ fSX += SkIntToScalar(n);
+ SkMatrix::Persp_xy(fMatrix, fSX, fSY, &pt);
+ fX = SkScalarToFixed(pt.fX);
+ fY = SkScalarToFixed(pt.fY);
+ dx = (fX - x) / n;
+ dy = (fY - y) / n;
+ }
+
+ SkFixed* p = fStorage;
+ for (int i = 0; i < n; i++) {
+ *p++ = x; x += dx;
+ *p++ = y; y += dy;
+ }
+
+ fCount -= n;
+ return n;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_SCALAR_IS_FIXED
+
+static inline bool poly_to_point(SkPoint* pt, const SkPoint poly[], int count) {
+ SkFixed x = SK_Fixed1, y = SK_Fixed1;
+ SkPoint pt1, pt2;
+ Sk64 w1, w2;
+
+ if (count > 1) {
+ pt1.fX = poly[1].fX - poly[0].fX;
+ pt1.fY = poly[1].fY - poly[0].fY;
+ y = SkPoint::Length(pt1.fX, pt1.fY);
+ if (y == 0) {
+ return false;
+ }
+ switch (count) {
+ case 2:
+ break;
+ case 3:
+ pt2.fX = poly[0].fY - poly[2].fY;
+ pt2.fY = poly[2].fX - poly[0].fX;
+ goto CALC_X;
+ default:
+ pt2.fX = poly[0].fY - poly[3].fY;
+ pt2.fY = poly[3].fX - poly[0].fX;
+ CALC_X:
+ w1.setMul(pt1.fX, pt2.fX);
+ w2.setMul(pt1.fY, pt2.fY);
+ w1.add(w2);
+ w1.div(y, Sk64::kRound_DivOption);
+ if (!w1.is32()) {
+ return false;
+ }
+ x = w1.get32();
+ break;
+ }
+ }
+ pt->set(x, y);
+ return true;
+}
+
+bool SkMatrix::Poly2Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scalePt) {
+ // need to check if SkFixedDiv overflows...
+
+ const SkFixed scale = scalePt.fY;
+ dst->fMat[kMScaleX] = SkFixedDiv(srcPt[1].fY - srcPt[0].fY, scale);
+ dst->fMat[kMSkewY] = SkFixedDiv(srcPt[0].fX - srcPt[1].fX, scale);
+ dst->fMat[kMPersp0] = 0;
+ dst->fMat[kMSkewX] = SkFixedDiv(srcPt[1].fX - srcPt[0].fX, scale);
+ dst->fMat[kMScaleY] = SkFixedDiv(srcPt[1].fY - srcPt[0].fY, scale);
+ dst->fMat[kMPersp1] = 0;
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = SK_Fract1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+bool SkMatrix::Poly3Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scale) {
+ // really, need to check if SkFixedDiv overflow'd
+
+ dst->fMat[kMScaleX] = SkFixedDiv(srcPt[2].fX - srcPt[0].fX, scale.fX);
+ dst->fMat[kMSkewY] = SkFixedDiv(srcPt[2].fY - srcPt[0].fY, scale.fX);
+ dst->fMat[kMPersp0] = 0;
+ dst->fMat[kMSkewX] = SkFixedDiv(srcPt[1].fX - srcPt[0].fX, scale.fY);
+ dst->fMat[kMScaleY] = SkFixedDiv(srcPt[1].fY - srcPt[0].fY, scale.fY);
+ dst->fMat[kMPersp1] = 0;
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = SK_Fract1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+bool SkMatrix::Poly4Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scale) {
+ SkFract a1, a2;
+ SkFixed x0, y0, x1, y1, x2, y2;
+
+ x0 = srcPt[2].fX - srcPt[0].fX;
+ y0 = srcPt[2].fY - srcPt[0].fY;
+ x1 = srcPt[2].fX - srcPt[1].fX;
+ y1 = srcPt[2].fY - srcPt[1].fY;
+ x2 = srcPt[2].fX - srcPt[3].fX;
+ y2 = srcPt[2].fY - srcPt[3].fY;
+
+ /* check if abs(x2) > abs(y2) */
+ if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) {
+ SkFixed denom = SkMulDiv(x1, y2, x2) - y1;
+ if (0 == denom) {
+ return false;
+ }
+ a1 = SkFractDiv(SkMulDiv(x0 - x1, y2, x2) - y0 + y1, denom);
+ } else {
+ SkFixed denom = x1 - SkMulDiv(y1, x2, y2);
+ if (0 == denom) {
+ return false;
+ }
+ a1 = SkFractDiv(x0 - x1 - SkMulDiv(y0 - y1, x2, y2), denom);
+ }
+
+ /* check if abs(x1) > abs(y1) */
+ if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) {
+ SkFixed denom = y2 - SkMulDiv(x2, y1, x1);
+ if (0 == denom) {
+ return false;
+ }
+ a2 = SkFractDiv(y0 - y2 - SkMulDiv(x0 - x2, y1, x1), denom);
+ } else {
+ SkFixed denom = SkMulDiv(y2, x1, y1) - x2;
+ if (0 == denom) {
+ return false;
+ }
+ a2 = SkFractDiv(SkMulDiv(y0 - y2, x1, y1) - x0 + x2, denom);
+ }
+
+ // need to check if SkFixedDiv overflows...
+ dst->fMat[kMScaleX] = SkFixedDiv(SkFractMul(a2, srcPt[3].fX) +
+ srcPt[3].fX - srcPt[0].fX, scale.fX);
+ dst->fMat[kMSkewY] = SkFixedDiv(SkFractMul(a2, srcPt[3].fY) +
+ srcPt[3].fY - srcPt[0].fY, scale.fX);
+ dst->fMat[kMPersp0] = SkFixedDiv(a2, scale.fX);
+ dst->fMat[kMSkewX] = SkFixedDiv(SkFractMul(a1, srcPt[1].fX) +
+ srcPt[1].fX - srcPt[0].fX, scale.fY);
+ dst->fMat[kMScaleY] = SkFixedDiv(SkFractMul(a1, srcPt[1].fY) +
+ srcPt[1].fY - srcPt[0].fY, scale.fY);
+ dst->fMat[kMPersp1] = SkFixedDiv(a1, scale.fY);
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = SK_Fract1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+#else /* Scalar is float */
+
+static inline bool checkForZero(float x) {
+ return x*x == 0;
+}
+
+static inline bool poly_to_point(SkPoint* pt, const SkPoint poly[], int count) {
+ float x = 1, y = 1;
+ SkPoint pt1, pt2;
+
+ if (count > 1) {
+ pt1.fX = poly[1].fX - poly[0].fX;
+ pt1.fY = poly[1].fY - poly[0].fY;
+ y = SkPoint::Length(pt1.fX, pt1.fY);
+ if (checkForZero(y)) {
+ return false;
+ }
+ switch (count) {
+ case 2:
+ break;
+ case 3:
+ pt2.fX = poly[0].fY - poly[2].fY;
+ pt2.fY = poly[2].fX - poly[0].fX;
+ goto CALC_X;
+ default:
+ pt2.fX = poly[0].fY - poly[3].fY;
+ pt2.fY = poly[3].fX - poly[0].fX;
+ CALC_X:
+ x = SkScalarDiv(SkScalarMul(pt1.fX, pt2.fX) +
+ SkScalarMul(pt1.fY, pt2.fY), y);
+ break;
+ }
+ }
+ pt->set(x, y);
+ return true;
+}
+
+bool SkMatrix::Poly2Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scale) {
+ float invScale = 1 / scale.fY;
+
+ dst->fMat[kMScaleX] = (srcPt[1].fY - srcPt[0].fY) * invScale;
+ dst->fMat[kMSkewY] = (srcPt[0].fX - srcPt[1].fX) * invScale;
+ dst->fMat[kMPersp0] = 0;
+ dst->fMat[kMSkewX] = (srcPt[1].fX - srcPt[0].fX) * invScale;
+ dst->fMat[kMScaleY] = (srcPt[1].fY - srcPt[0].fY) * invScale;
+ dst->fMat[kMPersp1] = 0;
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = 1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+bool SkMatrix::Poly3Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scale) {
+ float invScale = 1 / scale.fX;
+ dst->fMat[kMScaleX] = (srcPt[2].fX - srcPt[0].fX) * invScale;
+ dst->fMat[kMSkewY] = (srcPt[2].fY - srcPt[0].fY) * invScale;
+ dst->fMat[kMPersp0] = 0;
+
+ invScale = 1 / scale.fY;
+ dst->fMat[kMSkewX] = (srcPt[1].fX - srcPt[0].fX) * invScale;
+ dst->fMat[kMScaleY] = (srcPt[1].fY - srcPt[0].fY) * invScale;
+ dst->fMat[kMPersp1] = 0;
+
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = 1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+bool SkMatrix::Poly4Proc(const SkPoint srcPt[], SkMatrix* dst,
+ const SkPoint& scale) {
+ float a1, a2;
+ float x0, y0, x1, y1, x2, y2;
+
+ x0 = srcPt[2].fX - srcPt[0].fX;
+ y0 = srcPt[2].fY - srcPt[0].fY;
+ x1 = srcPt[2].fX - srcPt[1].fX;
+ y1 = srcPt[2].fY - srcPt[1].fY;
+ x2 = srcPt[2].fX - srcPt[3].fX;
+ y2 = srcPt[2].fY - srcPt[3].fY;
+
+ /* check if abs(x2) > abs(y2) */
+ if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) {
+ float denom = SkScalarMulDiv(x1, y2, x2) - y1;
+ if (checkForZero(denom)) {
+ return false;
+ }
+ a1 = SkScalarDiv(SkScalarMulDiv(x0 - x1, y2, x2) - y0 + y1, denom);
+ } else {
+ float denom = x1 - SkScalarMulDiv(y1, x2, y2);
+ if (checkForZero(denom)) {
+ return false;
+ }
+ a1 = SkScalarDiv(x0 - x1 - SkScalarMulDiv(y0 - y1, x2, y2), denom);
+ }
+
+ /* check if abs(x1) > abs(y1) */
+ if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) {
+ float denom = y2 - SkScalarMulDiv(x2, y1, x1);
+ if (checkForZero(denom)) {
+ return false;
+ }
+ a2 = SkScalarDiv(y0 - y2 - SkScalarMulDiv(x0 - x2, y1, x1), denom);
+ } else {
+ float denom = SkScalarMulDiv(y2, x1, y1) - x2;
+ if (checkForZero(denom)) {
+ return false;
+ }
+ a2 = SkScalarDiv(SkScalarMulDiv(y0 - y2, x1, y1) - x0 + x2, denom);
+ }
+
+ float invScale = 1 / scale.fX;
+ dst->fMat[kMScaleX] = SkScalarMul(SkScalarMul(a2, srcPt[3].fX) +
+ srcPt[3].fX - srcPt[0].fX, invScale);
+ dst->fMat[kMSkewY] = SkScalarMul(SkScalarMul(a2, srcPt[3].fY) +
+ srcPt[3].fY - srcPt[0].fY, invScale);
+ dst->fMat[kMPersp0] = SkScalarMul(a2, invScale);
+ invScale = 1 / scale.fY;
+ dst->fMat[kMSkewX] = SkScalarMul(SkScalarMul(a1, srcPt[1].fX) +
+ srcPt[1].fX - srcPt[0].fX, invScale);
+ dst->fMat[kMScaleY] = SkScalarMul(SkScalarMul(a1, srcPt[1].fY) +
+ srcPt[1].fY - srcPt[0].fY, invScale);
+ dst->fMat[kMPersp1] = SkScalarMul(a1, invScale);
+ dst->fMat[kMTransX] = srcPt[0].fX;
+ dst->fMat[kMTransY] = srcPt[0].fY;
+ dst->fMat[kMPersp2] = 1;
+ dst->setTypeMask(kUnknown_Mask);
+ return true;
+}
+
+#endif
+
+typedef bool (*PolyMapProc)(const SkPoint[], SkMatrix*, const SkPoint&);
+
+/* Taken from Rob Johnson's original sample code in QuickDraw GX
+*/
+bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
+ int count) {
+ if ((unsigned)count > 4) {
+ SkDebugf("--- SkMatrix::setPolyToPoly count out of range %d\n", count);
+ return false;
+ }
+
+ if (0 == count) {
+ this->reset();
+ return true;
+ }
+ if (1 == count) {
+ this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY);
+ return true;
+ }
+
+ SkPoint scale;
+ if (!poly_to_point(&scale, src, count) ||
+ SkScalarNearlyZero(scale.fX) ||
+ SkScalarNearlyZero(scale.fY)) {
+ return false;
+ }
+
+ static const PolyMapProc gPolyMapProcs[] = {
+ SkMatrix::Poly2Proc, SkMatrix::Poly3Proc, SkMatrix::Poly4Proc
+ };
+ PolyMapProc proc = gPolyMapProcs[count - 2];
+
+ SkMatrix tempMap, result;
+ tempMap.setTypeMask(kUnknown_Mask);
+
+ if (!proc(src, &tempMap, scale)) {
+ return false;
+ }
+ if (!tempMap.invert(&result)) {
+ return false;
+ }
+ if (!proc(dst, &tempMap, scale)) {
+ return false;
+ }
+ if (!result.setConcat(tempMap, result)) {
+ return false;
+ }
+ *this = result;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkScalar SkMatrix::getMaxStretch() const {
+ TypeMask mask = this->getType();
+
+ if (this->hasPerspective()) {
+ return -SK_Scalar1;
+ }
+ if (this->isIdentity()) {
+ return SK_Scalar1;
+ }
+ if (!(mask & kAffine_Mask)) {
+ return SkMaxScalar(SkScalarAbs(fMat[kMScaleX]),
+ SkScalarAbs(fMat[kMScaleY]));
+ }
+ // 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
+ SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) +
+ SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]);
+ SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) +
+ SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]);
+ SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) +
+ SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]);
+ // 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).
+ SkScalar largerRoot;
+ SkScalar bSqd = SkScalarMul(b,b);
+ // if upper left 2x2 is orthogonal save some math
+ if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) {
+ largerRoot = SkMaxScalar(a, c);
+ } else {
+ SkScalar aminusc = a - c;
+ SkScalar apluscdiv2 = SkScalarHalf(a + c);
+ SkScalar x = SkScalarHalf(SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd));
+ largerRoot = apluscdiv2 + x;
+ }
+ return SkScalarSqrt(largerRoot);
+}
+
+const SkMatrix& SkMatrix::I() {
+ static SkMatrix gIdentity;
+ static bool gOnce;
+ if (!gOnce) {
+ gIdentity.reset();
+ gOnce = true;
+ }
+ return gIdentity;
+}
+
+const SkMatrix& SkMatrix::InvalidMatrix() {
+ static SkMatrix gInvalid;
+ static bool gOnce;
+ if (!gOnce) {
+ gInvalid.setAll(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax,
+ SK_ScalarMax, SK_ScalarMax, SK_ScalarMax,
+ SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ gInvalid.getType(); // force the type to be computed
+ gOnce = true;
+ }
+ return gInvalid;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t SkMatrix::writeToMemory(void* buffer) const {
+ // TODO write less for simple matrices
+ if (buffer) {
+ memcpy(buffer, fMat, 9 * sizeof(SkScalar));
+ }
+ return 9 * sizeof(SkScalar);
+}
+
+uint32_t SkMatrix::readFromMemory(const void* buffer) {
+ if (buffer) {
+ memcpy(fMat, buffer, 9 * sizeof(SkScalar));
+ this->setTypeMask(kUnknown_Mask);
+ }
+ return 9 * sizeof(SkScalar);
+}
+
+#ifdef SK_DEVELOPER
+void SkMatrix::dump() const {
+ SkString str;
+ this->toString(&str);
+ SkDebugf("%s\n", str.c_str());
+}
+
+void SkMatrix::toString(SkString* str) const {
+ str->appendf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]",
+#ifdef SK_SCALAR_IS_FLOAT
+ fMat[0], fMat[1], fMat[2], fMat[3], fMat[4], fMat[5],
+ fMat[6], fMat[7], fMat[8]);
+#else
+ SkFixedToFloat(fMat[0]), SkFixedToFloat(fMat[1]), SkFixedToFloat(fMat[2]),
+ SkFixedToFloat(fMat[3]), SkFixedToFloat(fMat[4]), SkFixedToFloat(fMat[5]),
+ SkFractToFloat(fMat[6]), SkFractToFloat(fMat[7]), SkFractToFloat(fMat[8]));
+#endif
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkMatrixUtils.h"
+
+bool SkTreatAsSprite(const SkMatrix& mat, int width, int height,
+ unsigned subpixelBits) {
+ // quick reject on affine or perspective
+ if (mat.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
+ return false;
+ }
+
+ // quick success check
+ if (!subpixelBits && !(mat.getType() & ~SkMatrix::kTranslate_Mask)) {
+ return true;
+ }
+
+ // mapRect supports negative scales, so we eliminate those first
+ if (mat.getScaleX() < 0 || mat.getScaleY() < 0) {
+ return false;
+ }
+
+ SkRect dst;
+ SkIRect isrc = { 0, 0, width, height };
+
+ {
+ SkRect src;
+ src.set(isrc);
+ mat.mapRect(&dst, src);
+ }
+
+ // just apply the translate to isrc
+ isrc.offset(SkScalarRoundToInt(mat.getTranslateX()),
+ SkScalarRoundToInt(mat.getTranslateY()));
+
+ if (subpixelBits) {
+ isrc.fLeft <<= subpixelBits;
+ isrc.fTop <<= subpixelBits;
+ isrc.fRight <<= subpixelBits;
+ isrc.fBottom <<= subpixelBits;
+
+ const float scale = 1 << subpixelBits;
+ dst.fLeft *= scale;
+ dst.fTop *= scale;
+ dst.fRight *= scale;
+ dst.fBottom *= scale;
+ }
+
+ SkIRect idst;
+ dst.round(&idst);
+ return isrc == idst;
+}
+
+bool SkDecomposeUpper2x2(const SkMatrix& matrix,
+ SkScalar* rotation0,
+ SkScalar* xScale, SkScalar* yScale,
+ SkScalar* rotation1) {
+
+ // borrowed from Jim Blinn's article "Consider the Lowly 2x2 Matrix"
+ // Note: he uses row vectors, so we have to do some swapping of terms
+ SkScalar A = matrix[SkMatrix::kMScaleX];
+ SkScalar B = matrix[SkMatrix::kMSkewX];
+ SkScalar C = matrix[SkMatrix::kMSkewY];
+ SkScalar D = matrix[SkMatrix::kMScaleY];
+
+ if (is_degenerate_2x2(A, B, C, D)) {
+ return false;
+ }
+
+ SkScalar E = SK_ScalarHalf*(A + D);
+ SkScalar F = SK_ScalarHalf*(A - D);
+ SkScalar G = SK_ScalarHalf*(C + B);
+ SkScalar H = SK_ScalarHalf*(C - B);
+
+ SkScalar sqrt0 = SkScalarSqrt(E*E + H*H);
+ SkScalar sqrt1 = SkScalarSqrt(F*F + G*G);
+
+ SkScalar xs, ys, r0, r1;
+
+ xs = sqrt0 + sqrt1;
+ ys = sqrt0 - sqrt1;
+ // can't have zero yScale, must be degenerate
+ SkASSERT(!SkScalarNearlyZero(ys));
+
+ // uniformly scaled rotation
+ if (SkScalarNearlyZero(F) && SkScalarNearlyZero(G)) {
+ SkASSERT(!SkScalarNearlyZero(E) || !SkScalarNearlyZero(H));
+ r0 = SkScalarATan2(H, E);
+ r1 = 0;
+ // uniformly scaled reflection
+ } else if (SkScalarNearlyZero(E) && SkScalarNearlyZero(H)) {
+ SkASSERT(!SkScalarNearlyZero(F) || !SkScalarNearlyZero(G));
+ r0 = -SkScalarATan2(G, F);
+ r1 = 0;
+ } else {
+ SkASSERT(!SkScalarNearlyZero(E) || !SkScalarNearlyZero(H));
+ SkASSERT(!SkScalarNearlyZero(F) || !SkScalarNearlyZero(G));
+
+ SkScalar arctan0 = SkScalarATan2(H, E);
+ SkScalar arctan1 = SkScalarATan2(G, F);
+ r0 = SK_ScalarHalf*(arctan0 - arctan1);
+ r1 = SK_ScalarHalf*(arctan0 + arctan1);
+
+ // simplify the results
+ const SkScalar kHalfPI = SK_ScalarHalf*SK_ScalarPI;
+ if (SkScalarNearlyEqual(SkScalarAbs(r0), kHalfPI)) {
+ SkScalar tmp = xs;
+ xs = ys;
+ ys = tmp;
+
+ r1 += r0;
+ r0 = 0;
+ } else if (SkScalarNearlyEqual(SkScalarAbs(r1), kHalfPI)) {
+ SkScalar tmp = xs;
+ xs = ys;
+ ys = tmp;
+
+ r0 += r1;
+ r1 = 0;
+ }
+ }
+
+ if (NULL != xScale) {
+ *xScale = xs;
+ }
+ if (NULL != yScale) {
+ *yScale = ys;
+ }
+ if (NULL != rotation0) {
+ *rotation0 = r0;
+ }
+ if (NULL != rotation1) {
+ *rotation1 = r1;
+ }
+
+ return true;
+}
diff --git a/core/SkMatrixUtils.h b/core/SkMatrixUtils.h
new file mode 100644
index 00000000..37341f28
--- /dev/null
+++ b/core/SkMatrixUtils.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMatrixUtils_DEFINED
+#define SkMatrixUtils_DEFINED
+
+#include "SkMatrix.h"
+
+/**
+ * Number of subpixel bits used in skia's bilerp.
+ * See SkBitmapProcState_procs.h and SkBitmapProcState_filter.h
+ */
+#define kSkSubPixelBitsForBilerp 4
+
+/**
+ * Given a matrix and width/height, return true if the computed dst-rect would
+ * align such that there is a 1-to-1 coorspondence between src and dst pixels.
+ * This can be called by drawing code to see if drawBitmap can be turned into
+ * drawSprite (which is faster).
+ *
+ * The src-rect is defined to be { 0, 0, width, height }
+ *
+ * The "closeness" test is based on the subpixelBits parameter. Pass 0 for
+ * round-to-nearest behavior (e.g. nearest neighbor sampling). Pass the number
+ * of subpixel-bits to simulate filtering.
+ */
+bool SkTreatAsSprite(const SkMatrix&, int width, int height,
+ unsigned subpixelBits);
+
+/**
+ * Calls SkTreatAsSprite() with default subpixelBits value to match Skia's
+ * filter-bitmap implementation (i.e. kSkSubPixelBitsForBilerp).
+ */
+static inline bool SkTreatAsSpriteFilter(const SkMatrix& matrix,
+ int width, int height) {
+ return SkTreatAsSprite(matrix, width, height, kSkSubPixelBitsForBilerp);
+}
+
+/** Decomposes the upper-left 2x2 of the matrix into a rotation, followed by a non-uniform scale,
+ followed by another rotation. Returns true if successful.
+ If the scale factors are uniform, then rotation1 will be 0.
+ If there is a reflection, yScale will be negative.
+ Returns false if the matrix is degenerate.
+ */
+bool SkDecomposeUpper2x2(const SkMatrix& matrix,
+ SkScalar* rotation0,
+ SkScalar* xScale, SkScalar* yScale,
+ SkScalar* rotation1);
+
+#endif
diff --git a/core/SkMemory_stdlib.cpp b/core/SkMemory_stdlib.cpp
new file mode 100644
index 00000000..fa13e9cb
--- /dev/null
+++ b/core/SkMemory_stdlib.cpp
@@ -0,0 +1,267 @@
+
+/*
+ * 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 "SkTypes.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef SK_DEBUG
+ #define SK_TAG_BLOCKS
+ // #define SK_TRACK_ALLOC // enable to see a printf for every alloc/free
+ // #define SK_CHECK_TAGS // enable to double-check debugging link list
+#endif
+
+#ifdef SK_TAG_BLOCKS
+
+#include "SkThread.h"
+
+// size this (as a multiple of 4) so that the total offset to the internal data
+// is at least a multiple of 8 (since some clients of our malloc may require
+// that.
+static const char kBlockHeaderTag[] = { 's', 'k', 'i', 'a', '1', '2', '3', '4' };
+static const char kBlockTrailerTag[] = { 'a', 'i', 'k', 's' };
+#define kByteFill 0xCD
+#define kDeleteFill 0xEF
+
+static SkBaseMutex& get_block_mutex() {
+ static SkMutex* gBlockMutex;
+ if (NULL == gBlockMutex) {
+ gBlockMutex = new SkMutex;
+ }
+ return *gBlockMutex;
+}
+
+static struct SkBlockHeader* gHeader;
+
+struct SkBlockHeader {
+ SkBlockHeader* fNext;
+#ifdef SK_CHECK_TAGS
+ SkBlockHeader** fTop; // set to verify in debugger that block was alloc'd / freed with same gHeader
+ SkBlockHeader* fPrevious; // set to see in debugger previous block when corruption happens
+#endif
+ size_t fSize;
+ char fHeader[sizeof(kBlockHeaderTag)];
+ // data goes here. The offset to this point must be a multiple of 8
+ char fTrailer[sizeof(kBlockTrailerTag)];
+
+ void* add(size_t realSize)
+ {
+ SkAutoMutexAcquire ac(get_block_mutex());
+ InMutexValidate();
+ fNext = gHeader;
+#ifdef SK_CHECK_TAGS
+ fTop = &gHeader;
+ fPrevious = NULL;
+ if (fNext != NULL)
+ fNext->fPrevious = this;
+#endif
+ gHeader = this;
+ fSize = realSize;
+ memcpy(fHeader, kBlockHeaderTag, sizeof(kBlockHeaderTag));
+ void* result = fTrailer;
+ void* trailer = (char*)result + realSize;
+ memcpy(trailer, kBlockTrailerTag, sizeof(kBlockTrailerTag));
+ return result;
+ }
+
+ static void Dump()
+ {
+ SkAutoMutexAcquire ac(get_block_mutex());
+ InMutexValidate();
+ SkBlockHeader* header = gHeader;
+ int count = 0;
+ size_t size = 0;
+ while (header != NULL) {
+ char scratch[256];
+ char* pos = scratch;
+ size_t size = header->fSize;
+ int* data = (int*)(void*)header->fTrailer;
+ pos += sprintf(pos, "%p 0x%08zx (%7zd) ",
+ data, size, size);
+ size >>= 2;
+ size_t ints = size > 4 ? 4 : size;
+ size_t index;
+ for (index = 0; index < ints; index++)
+ pos += sprintf(pos, "0x%08x ", data[index]);
+ pos += sprintf(pos, " (");
+ for (index = 0; index < ints; index++)
+ pos += sprintf(pos, "%g ", data[index] / 65536.0f);
+ if (ints > 0)
+ --pos;
+ pos += sprintf(pos, ") \"");
+ size_t chars = size > 16 ? 16 : size;
+ char* chPtr = (char*) data;
+ for (index = 0; index < chars; index++) {
+ char ch = chPtr[index];
+ pos += sprintf(pos, "%c", ch >= ' ' && ch < 0x7f ? ch : '?');
+ }
+ pos += sprintf(pos, "\"");
+ SkDebugf("%s\n", scratch);
+ count++;
+ size += header->fSize;
+ header = header->fNext;
+ }
+ SkDebugf("--- count %d size 0x%08x (%zd) ---\n", count, size, size);
+ }
+
+ void remove() const
+ {
+ SkAutoMutexAcquire ac(get_block_mutex());
+ SkBlockHeader** findPtr = &gHeader;
+ do {
+ SkBlockHeader* find = *findPtr;
+ SkASSERT(find != NULL);
+ if (find == this) {
+ *findPtr = fNext;
+ break;
+ }
+ findPtr = &find->fNext;
+ } while (true);
+ InMutexValidate();
+ SkASSERT(memcmp(fHeader, kBlockHeaderTag, sizeof(kBlockHeaderTag)) == 0);
+ const char* trailer = fTrailer + fSize;
+ SkASSERT(memcmp(trailer, kBlockTrailerTag, sizeof(kBlockTrailerTag)) == 0);
+ }
+
+ static void Validate()
+ {
+ SkAutoMutexAcquire ac(get_block_mutex());
+ InMutexValidate();
+ }
+
+private:
+ static void InMutexValidate()
+ {
+ SkBlockHeader* header = gHeader;
+ while (header != NULL) {
+ SkASSERT(memcmp(header->fHeader, kBlockHeaderTag, sizeof(kBlockHeaderTag)) == 0);
+ char* trailer = header->fTrailer + header->fSize;
+ SkASSERT(memcmp(trailer, kBlockTrailerTag, sizeof(kBlockTrailerTag)) == 0);
+ header = header->fNext;
+ }
+ }
+};
+
+void ValidateHeap();
+void ValidateHeap()
+{
+ SkBlockHeader::Validate();
+}
+#else
+void ValidateHeap() {}
+#endif
+
+void sk_throw()
+{
+ SkDEBUGFAIL("sk_throw");
+ abort();
+}
+
+void sk_out_of_memory(void)
+{
+ SkDEBUGFAIL("sk_out_of_memory");
+ abort();
+}
+
+void* sk_malloc_throw(size_t size)
+{
+ return sk_malloc_flags(size, SK_MALLOC_THROW);
+}
+
+void* sk_realloc_throw(void* addr, size_t size)
+{
+#ifdef SK_TAG_BLOCKS
+ ValidateHeap();
+ if (addr != NULL) {
+ SkBlockHeader* header = (SkBlockHeader*)
+ ((char*)addr - SK_OFFSETOF(SkBlockHeader, fTrailer));
+ header->remove();
+#ifdef SK_TRACK_ALLOC
+ printf("sk_realloc_throw %p oldSize=%zd\n", addr, header->fSize);
+#endif
+ addr = header;
+ }
+ size_t realSize = size;
+ if (size)
+ size += sizeof(SkBlockHeader);
+#endif
+
+ void* p = realloc(addr, size);
+ if (size == 0)
+ {
+ ValidateHeap();
+ return p;
+ }
+
+ if (p == NULL)
+ sk_throw();
+#ifdef SK_TAG_BLOCKS
+ else
+ {
+ SkBlockHeader* header = (SkBlockHeader*) p;
+ p = header->add(realSize);
+#ifdef SK_TRACK_ALLOC
+ printf("sk_realloc_throw %p size=%zd\n", p, realSize);
+#endif
+ }
+#endif
+ ValidateHeap();
+ return p;
+}
+
+void sk_free(void* p)
+{
+ if (p)
+ {
+ ValidateHeap();
+#ifdef SK_TAG_BLOCKS
+ SkBlockHeader* header = (SkBlockHeader*)
+ ((char*)p - SK_OFFSETOF(SkBlockHeader, fTrailer));
+ header->remove();
+#ifdef SK_TRACK_ALLOC
+ printf("sk_free %p size=%zd\n", p, header->fSize);
+#endif
+ size_t size = header->fSize + sizeof(SkBlockHeader);
+ memset(header, kDeleteFill, size);
+ p = header;
+#endif
+ ValidateHeap();
+ free(p);
+ ValidateHeap();
+ }
+}
+
+void* sk_malloc_flags(size_t size, unsigned flags)
+{
+ ValidateHeap();
+#ifdef SK_TAG_BLOCKS
+ size_t realSize = size;
+ size += sizeof(SkBlockHeader);
+#endif
+
+ void* p = malloc(size);
+ if (p == NULL)
+ {
+ if (flags & SK_MALLOC_THROW)
+ sk_throw();
+ }
+#ifdef SK_TAG_BLOCKS
+ else
+ {
+ SkBlockHeader* header = (SkBlockHeader*) p;
+ p = header->add(realSize);
+ memset(p, kByteFill, realSize);
+#ifdef SK_TRACK_ALLOC
+ printf("sk_malloc_flags %p size=%zd\n", p, realSize);
+#endif
+ }
+#endif
+ ValidateHeap();
+ return p;
+}
diff --git a/core/SkMetaData.cpp b/core/SkMetaData.cpp
new file mode 100644
index 00000000..5a494b35
--- /dev/null
+++ b/core/SkMetaData.cpp
@@ -0,0 +1,336 @@
+
+/*
+ * 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 "SkMetaData.h"
+#include "SkRefCnt.h"
+
+struct PtrPair {
+ void* fPtr;
+ SkMetaData::PtrProc fProc;
+};
+
+void* SkMetaData::RefCntProc(void* ptr, bool doRef) {
+ SkASSERT(ptr);
+ SkRefCnt* refcnt = reinterpret_cast<SkRefCnt*>(ptr);
+
+ if (doRef) {
+ refcnt->ref();
+ } else {
+ refcnt->unref();
+ }
+ return ptr;
+}
+
+SkMetaData::SkMetaData() : fRec(NULL)
+{
+}
+
+SkMetaData::SkMetaData(const SkMetaData& src) : fRec(NULL)
+{
+ *this = src;
+}
+
+SkMetaData::~SkMetaData()
+{
+ this->reset();
+}
+
+void SkMetaData::reset()
+{
+ Rec* rec = fRec;
+ while (rec) {
+ if (kPtr_Type == rec->fType) {
+ PtrPair* pair = (PtrPair*)rec->data();
+ if (pair->fProc && pair->fPtr) {
+ pair->fPtr = pair->fProc(pair->fPtr, false);
+ }
+ }
+ Rec* next = rec->fNext;
+ Rec::Free(rec);
+ rec = next;
+ }
+ fRec = NULL;
+}
+
+SkMetaData& SkMetaData::operator=(const SkMetaData& src)
+{
+ this->reset();
+
+ const Rec* rec = src.fRec;
+ while (rec)
+ {
+ this->set(rec->name(), rec->data(), rec->fDataLen, (Type)rec->fType, rec->fDataCount);
+ rec = rec->fNext;
+ }
+ return *this;
+}
+
+void SkMetaData::setS32(const char name[], int32_t value)
+{
+ (void)this->set(name, &value, sizeof(int32_t), kS32_Type, 1);
+}
+
+void SkMetaData::setScalar(const char name[], SkScalar value)
+{
+ (void)this->set(name, &value, sizeof(SkScalar), kScalar_Type, 1);
+}
+
+SkScalar* SkMetaData::setScalars(const char name[], int count, const SkScalar values[])
+{
+ SkASSERT(count > 0);
+ if (count > 0)
+ return (SkScalar*)this->set(name, values, sizeof(SkScalar), kScalar_Type, count);
+ return NULL;
+}
+
+void SkMetaData::setString(const char name[], const char value[])
+{
+ (void)this->set(name, value, sizeof(char), kString_Type, strlen(value) + 1);
+}
+
+void SkMetaData::setPtr(const char name[], void* ptr, PtrProc proc) {
+ PtrPair pair = { ptr, proc };
+ (void)this->set(name, &pair, sizeof(PtrPair), kPtr_Type, 1);
+}
+
+void SkMetaData::setBool(const char name[], bool value)
+{
+ (void)this->set(name, &value, sizeof(bool), kBool_Type, 1);
+}
+
+void SkMetaData::setData(const char name[], const void* data, size_t byteCount) {
+ (void)this->set(name, data, sizeof(char), kData_Type, byteCount);
+}
+
+void* SkMetaData::set(const char name[], const void* data, size_t dataSize, Type type, int count)
+{
+ SkASSERT(name);
+ SkASSERT(dataSize);
+ SkASSERT(count > 0);
+
+ (void)this->remove(name, type);
+
+ size_t len = strlen(name);
+ Rec* rec = Rec::Alloc(sizeof(Rec) + dataSize * count + len + 1);
+
+#ifndef SK_DEBUG
+ rec->fType = SkToU8(type);
+#else
+ rec->fType = type;
+#endif
+ rec->fDataLen = SkToU8(dataSize);
+ rec->fDataCount = SkToU16(count);
+ if (data)
+ memcpy(rec->data(), data, dataSize * count);
+ memcpy(rec->name(), name, len + 1);
+
+ if (kPtr_Type == type) {
+ PtrPair* pair = (PtrPair*)rec->data();
+ if (pair->fProc && pair->fPtr) {
+ pair->fPtr = pair->fProc(pair->fPtr, true);
+ }
+ }
+
+ rec->fNext = fRec;
+ fRec = rec;
+ return rec->data();
+}
+
+bool SkMetaData::findS32(const char name[], int32_t* value) const
+{
+ const Rec* rec = this->find(name, kS32_Type);
+ if (rec)
+ {
+ SkASSERT(rec->fDataCount == 1);
+ if (value)
+ *value = *(const int32_t*)rec->data();
+ return true;
+ }
+ return false;
+}
+
+bool SkMetaData::findScalar(const char name[], SkScalar* value) const
+{
+ const Rec* rec = this->find(name, kScalar_Type);
+ if (rec)
+ {
+ SkASSERT(rec->fDataCount == 1);
+ if (value)
+ *value = *(const SkScalar*)rec->data();
+ return true;
+ }
+ return false;
+}
+
+const SkScalar* SkMetaData::findScalars(const char name[], int* count, SkScalar values[]) const
+{
+ const Rec* rec = this->find(name, kScalar_Type);
+ if (rec)
+ {
+ if (count)
+ *count = rec->fDataCount;
+ if (values)
+ memcpy(values, rec->data(), rec->fDataCount * rec->fDataLen);
+ return (const SkScalar*)rec->data();
+ }
+ return NULL;
+}
+
+bool SkMetaData::findPtr(const char name[], void** ptr, PtrProc* proc) const {
+ const Rec* rec = this->find(name, kPtr_Type);
+ if (rec) {
+ SkASSERT(rec->fDataCount == 1);
+ const PtrPair* pair = (const PtrPair*)rec->data();
+ if (ptr) {
+ *ptr = pair->fPtr;
+ }
+ if (proc) {
+ *proc = pair->fProc;
+ }
+ return true;
+ }
+ return false;
+}
+
+const char* SkMetaData::findString(const char name[]) const
+{
+ const Rec* rec = this->find(name, kString_Type);
+ SkASSERT(rec == NULL || rec->fDataLen == sizeof(char));
+ return rec ? (const char*)rec->data() : NULL;
+}
+
+bool SkMetaData::findBool(const char name[], bool* value) const
+{
+ const Rec* rec = this->find(name, kBool_Type);
+ if (rec)
+ {
+ SkASSERT(rec->fDataCount == 1);
+ if (value)
+ *value = *(const bool*)rec->data();
+ return true;
+ }
+ return false;
+}
+
+const void* SkMetaData::findData(const char name[], size_t* length) const {
+ const Rec* rec = this->find(name, kData_Type);
+ if (rec) {
+ SkASSERT(rec->fDataLen == sizeof(char));
+ if (length) {
+ *length = rec->fDataCount;
+ }
+ return rec->data();
+ }
+ return NULL;
+}
+
+const SkMetaData::Rec* SkMetaData::find(const char name[], Type type) const
+{
+ const Rec* rec = fRec;
+ while (rec)
+ {
+ if (rec->fType == type && !strcmp(rec->name(), name))
+ return rec;
+ rec = rec->fNext;
+ }
+ return NULL;
+}
+
+bool SkMetaData::remove(const char name[], Type type) {
+ Rec* rec = fRec;
+ Rec* prev = NULL;
+ while (rec) {
+ Rec* next = rec->fNext;
+ if (rec->fType == type && !strcmp(rec->name(), name)) {
+ if (prev) {
+ prev->fNext = next;
+ } else {
+ fRec = next;
+ }
+
+ if (kPtr_Type == type) {
+ PtrPair* pair = (PtrPair*)rec->data();
+ if (pair->fProc && pair->fPtr) {
+ (void)pair->fProc(pair->fPtr, false);
+ }
+ }
+ Rec::Free(rec);
+ return true;
+ }
+ prev = rec;
+ rec = next;
+ }
+ return false;
+}
+
+bool SkMetaData::removeS32(const char name[])
+{
+ return this->remove(name, kS32_Type);
+}
+
+bool SkMetaData::removeScalar(const char name[])
+{
+ return this->remove(name, kScalar_Type);
+}
+
+bool SkMetaData::removeString(const char name[])
+{
+ return this->remove(name, kString_Type);
+}
+
+bool SkMetaData::removePtr(const char name[])
+{
+ return this->remove(name, kPtr_Type);
+}
+
+bool SkMetaData::removeBool(const char name[])
+{
+ return this->remove(name, kBool_Type);
+}
+
+bool SkMetaData::removeData(const char name[]) {
+ return this->remove(name, kData_Type);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkMetaData::Iter::Iter(const SkMetaData& metadata) {
+ fRec = metadata.fRec;
+}
+
+void SkMetaData::Iter::reset(const SkMetaData& metadata) {
+ fRec = metadata.fRec;
+}
+
+const char* SkMetaData::Iter::next(SkMetaData::Type* t, int* count) {
+ const char* name = NULL;
+
+ if (fRec) {
+ if (t) {
+ *t = (SkMetaData::Type)fRec->fType;
+ }
+ if (count) {
+ *count = fRec->fDataCount;
+ }
+ name = fRec->name();
+
+ fRec = fRec->fNext;
+ }
+ return name;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkMetaData::Rec* SkMetaData::Rec::Alloc(size_t size) {
+ return (Rec*)sk_malloc_throw(size);
+}
+
+void SkMetaData::Rec::Free(Rec* rec) {
+ sk_free(rec);
+}
diff --git a/core/SkMipMap.cpp b/core/SkMipMap.cpp
new file mode 100644
index 00000000..0673c7e0
--- /dev/null
+++ b/core/SkMipMap.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMipMap.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+
+static void downsampleby2_proc32(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const SkPMColor* p = src.getAddr32(x, y);
+ const SkPMColor* baseP = p;
+ SkPMColor c, ag, rb;
+
+ c = *p; ag = (c >> 8) & 0xFF00FF; rb = c & 0xFF00FF;
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 2;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF;
+
+ *dst->getAddr32(x >> 1, y >> 1) =
+ ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00);
+}
+
+static inline uint32_t expand16(U16CPU c) {
+ return (c & ~SK_G16_MASK_IN_PLACE) | ((c & SK_G16_MASK_IN_PLACE) << 16);
+}
+
+// returns dirt in the top 16bits, but we don't care, since we only
+// store the low 16bits.
+static inline U16CPU pack16(uint32_t c) {
+ return (c & ~SK_G16_MASK_IN_PLACE) | ((c >> 16) & SK_G16_MASK_IN_PLACE);
+}
+
+static void downsampleby2_proc16(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const uint16_t* p = src.getAddr16(x, y);
+ const uint16_t* baseP = p;
+ SkPMColor c;
+
+ c = expand16(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand16(*p);
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 1;
+ }
+ c += expand16(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand16(*p);
+
+ *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)pack16(c >> 2);
+}
+
+static uint32_t expand4444(U16CPU c) {
+ return (c & 0xF0F) | ((c & ~0xF0F) << 12);
+}
+
+static U16CPU collaps4444(uint32_t c) {
+ return (c & 0xF0F) | ((c >> 12) & ~0xF0F);
+}
+
+static void downsampleby2_proc4444(SkBitmap* dst, int x, int y,
+ const SkBitmap& src) {
+ x <<= 1;
+ y <<= 1;
+ const uint16_t* p = src.getAddr16(x, y);
+ const uint16_t* baseP = p;
+ uint32_t c;
+
+ c = expand4444(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand4444(*p);
+
+ p = baseP;
+ if (y < src.height() - 1) {
+ p += src.rowBytes() >> 1;
+ }
+ c += expand4444(*p);
+ if (x < src.width() - 1) {
+ p += 1;
+ }
+ c += expand4444(*p);
+
+ *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)collaps4444(c >> 2);
+}
+
+static bool isPos32Bits(const Sk64& value) {
+ return !value.isNeg() && value.is32();
+}
+
+SkMipMap::Level* SkMipMap::AllocLevels(int levelCount, size_t pixelSize) {
+ if (levelCount < 0) {
+ return NULL;
+ }
+ Sk64 size;
+ size.setMul(levelCount + 1, sizeof(Level));
+ size.add(SkToS32(pixelSize));
+ if (!isPos32Bits(size)) {
+ return NULL;
+ }
+ return (Level*)sk_malloc_throw(size.get32());
+}
+
+SkMipMap* SkMipMap::Build(const SkBitmap& src) {
+ void (*proc)(SkBitmap* dst, int x, int y, const SkBitmap& src);
+
+ const SkBitmap::Config config = src.getConfig();
+ switch (config) {
+ case SkBitmap::kARGB_8888_Config:
+ proc = downsampleby2_proc32;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ proc = downsampleby2_proc16;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ proc = downsampleby2_proc4444;
+ break;
+ case SkBitmap::kIndex8_Config:
+ case SkBitmap::kA8_Config:
+ default:
+ return NULL; // don't build mipmaps for these configs
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.readyToDraw()) {
+ return NULL;
+ }
+
+ // whip through our loop to compute the exact size needed
+ size_t size = 0;
+ int countLevels = 0;
+ {
+ int width = src.width();
+ int height = src.height();
+ for (;;) {
+ width >>= 1;
+ height >>= 1;
+ if (0 == width || 0 == height) {
+ break;
+ }
+ size += SkBitmap::ComputeRowBytes(config, width) * height;
+ countLevels += 1;
+ }
+ }
+ if (0 == countLevels) {
+ return NULL;
+ }
+
+ Level* levels = SkMipMap::AllocLevels(countLevels, size);
+ if (NULL == levels) {
+ return NULL;
+ }
+
+ uint8_t* baseAddr = (uint8_t*)&levels[countLevels];
+ uint8_t* addr = baseAddr;
+ int width = src.width();
+ int height = src.height();
+ uint32_t rowBytes;
+ SkBitmap srcBM(src);
+
+ for (int i = 0; i < countLevels; ++i) {
+ width >>= 1;
+ height >>= 1;
+ rowBytes = SkToU32(SkBitmap::ComputeRowBytes(config, width));
+
+ levels[i].fPixels = addr;
+ levels[i].fWidth = width;
+ levels[i].fHeight = height;
+ levels[i].fRowBytes = rowBytes;
+ levels[i].fScale = (float)width / src.width();
+
+ SkBitmap dstBM;
+ dstBM.setConfig(config, width, height, rowBytes);
+ dstBM.setPixels(addr);
+
+ srcBM.lockPixels();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ proc(&dstBM, x, y, srcBM);
+ }
+ }
+ srcBM.unlockPixels();
+
+ srcBM = dstBM;
+ addr += height * rowBytes;
+ }
+ SkASSERT(addr == baseAddr + size);
+
+ return SkNEW_ARGS(SkMipMap, (levels, countLevels, size));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//static int gCounter;
+
+SkMipMap::SkMipMap(Level* levels, int count, size_t size)
+ : fSize(size), fLevels(levels), fCount(count) {
+ SkASSERT(levels);
+ SkASSERT(count > 0);
+// SkDebugf("mips %d\n", ++gCounter);
+}
+
+SkMipMap::~SkMipMap() {
+ sk_free(fLevels);
+// SkDebugf("mips %d\n", --gCounter);
+}
+
+static SkFixed compute_level(SkScalar scale) {
+ SkFixed s = SkAbs32(SkScalarToFixed(SkScalarInvert(scale)));
+
+ if (s < SK_Fixed1) {
+ return 0;
+ }
+ int clz = SkCLZ(s);
+ SkASSERT(clz >= 1 && clz <= 15);
+ return SkIntToFixed(15 - clz) + ((unsigned)(s << (clz + 1)) >> 16);
+}
+
+bool SkMipMap::extractLevel(SkScalar scale, Level* levelPtr) const {
+ if (scale >= SK_Scalar1) {
+ return false;
+ }
+
+ int level = compute_level(scale) >> 16;
+ SkASSERT(level >= 0);
+ if (level <= 0) {
+ return false;
+ }
+
+ if (level > fCount) {
+ level = fCount;
+ }
+ if (levelPtr) {
+ *levelPtr = fLevels[level - 1];
+ }
+ return true;
+}
diff --git a/core/SkMipMap.h b/core/SkMipMap.h
new file mode 100644
index 00000000..ed912ba9
--- /dev/null
+++ b/core/SkMipMap.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMipMap_DEFINED
+#define SkMipMap_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+
+class SkBitmap;
+
+class SkMipMap : public SkRefCnt {
+public:
+ static SkMipMap* Build(const SkBitmap& src);
+
+ struct Level {
+ void* fPixels;
+ uint32_t fRowBytes;
+ uint32_t fWidth, fHeight;
+ float fScale; // < 1.0
+ };
+
+ bool extractLevel(SkScalar scale, Level*) const;
+
+ size_t getSize() const { return fSize; }
+
+private:
+ size_t fSize;
+ Level* fLevels;
+ int fCount;
+
+ // we take ownership of levels, and will free it with sk_free()
+ SkMipMap(Level* levels, int count, size_t size);
+ virtual ~SkMipMap();
+
+ static Level* AllocLevels(int levelCount, size_t pixelSize);
+};
+
+#endif
diff --git a/core/SkOrderedReadBuffer.cpp b/core/SkOrderedReadBuffer.cpp
new file mode 100644
index 00000000..9d991949
--- /dev/null
+++ b/core/SkOrderedReadBuffer.cpp
@@ -0,0 +1,314 @@
+
+/*
+ * 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 "SkErrorInternals.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+
+SkOrderedReadBuffer::SkOrderedReadBuffer() : INHERITED() {
+ fMemoryPtr = NULL;
+
+ fBitmapStorage = NULL;
+ fTFArray = NULL;
+ fTFCount = 0;
+
+ fFactoryTDArray = NULL;
+ fFactoryArray = NULL;
+ fFactoryCount = 0;
+ fBitmapDecoder = NULL;
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ fDecodedBitmapIndex = -1;
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+}
+
+SkOrderedReadBuffer::SkOrderedReadBuffer(const void* data, size_t size) : INHERITED() {
+ fReader.setMemory(data, size);
+ fMemoryPtr = NULL;
+
+ fBitmapStorage = NULL;
+ fTFArray = NULL;
+ fTFCount = 0;
+
+ fFactoryTDArray = NULL;
+ fFactoryArray = NULL;
+ fFactoryCount = 0;
+ fBitmapDecoder = NULL;
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ fDecodedBitmapIndex = -1;
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+}
+
+SkOrderedReadBuffer::SkOrderedReadBuffer(SkStream* stream) {
+ const size_t length = stream->getLength();
+ fMemoryPtr = sk_malloc_throw(length);
+ stream->read(fMemoryPtr, length);
+ fReader.setMemory(fMemoryPtr, length);
+
+ fBitmapStorage = NULL;
+ fTFArray = NULL;
+ fTFCount = 0;
+
+ fFactoryTDArray = NULL;
+ fFactoryArray = NULL;
+ fFactoryCount = 0;
+ fBitmapDecoder = NULL;
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ fDecodedBitmapIndex = -1;
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+}
+
+SkOrderedReadBuffer::~SkOrderedReadBuffer() {
+ sk_free(fMemoryPtr);
+ SkSafeUnref(fBitmapStorage);
+}
+
+bool SkOrderedReadBuffer::readBool() {
+ return fReader.readBool();
+}
+
+SkColor SkOrderedReadBuffer::readColor() {
+ return fReader.readInt();
+}
+
+SkFixed SkOrderedReadBuffer::readFixed() {
+ return fReader.readS32();
+}
+
+int32_t SkOrderedReadBuffer::readInt() {
+ return fReader.readInt();
+}
+
+SkScalar SkOrderedReadBuffer::readScalar() {
+ return fReader.readScalar();
+}
+
+uint32_t SkOrderedReadBuffer::readUInt() {
+ return fReader.readU32();
+}
+
+int32_t SkOrderedReadBuffer::read32() {
+ return fReader.readInt();
+}
+
+void SkOrderedReadBuffer::readString(SkString* string) {
+ size_t len;
+ const char* strContents = fReader.readString(&len);
+ string->set(strContents, len);
+}
+
+void* SkOrderedReadBuffer::readEncodedString(size_t* length, SkPaint::TextEncoding encoding) {
+ SkDEBUGCODE(int32_t encodingType = ) fReader.readInt();
+ SkASSERT(encodingType == encoding);
+ *length = fReader.readInt();
+ void* data = sk_malloc_throw(*length);
+ memcpy(data, fReader.skip(SkAlign4(*length)), *length);
+ return data;
+}
+
+void SkOrderedReadBuffer::readPoint(SkPoint* point) {
+ point->fX = fReader.readScalar();
+ point->fY = fReader.readScalar();
+}
+
+void SkOrderedReadBuffer::readMatrix(SkMatrix* matrix) {
+ fReader.readMatrix(matrix);
+}
+
+void SkOrderedReadBuffer::readIRect(SkIRect* rect) {
+ memcpy(rect, fReader.skip(sizeof(SkIRect)), sizeof(SkIRect));
+}
+
+void SkOrderedReadBuffer::readRect(SkRect* rect) {
+ memcpy(rect, fReader.skip(sizeof(SkRect)), sizeof(SkRect));
+}
+
+void SkOrderedReadBuffer::readRegion(SkRegion* region) {
+ fReader.readRegion(region);
+}
+
+void SkOrderedReadBuffer::readPath(SkPath* path) {
+ fReader.readPath(path);
+}
+
+uint32_t SkOrderedReadBuffer::readByteArray(void* value) {
+ const uint32_t length = fReader.readU32();
+ memcpy(value, fReader.skip(SkAlign4(length)), length);
+ return length;
+}
+
+uint32_t SkOrderedReadBuffer::readColorArray(SkColor* colors) {
+ const uint32_t count = fReader.readU32();
+ const uint32_t byteLength = count * sizeof(SkColor);
+ memcpy(colors, fReader.skip(SkAlign4(byteLength)), byteLength);
+ return count;
+}
+
+uint32_t SkOrderedReadBuffer::readIntArray(int32_t* values) {
+ const uint32_t count = fReader.readU32();
+ const uint32_t byteLength = count * sizeof(int32_t);
+ memcpy(values, fReader.skip(SkAlign4(byteLength)), byteLength);
+ return count;
+}
+
+uint32_t SkOrderedReadBuffer::readPointArray(SkPoint* points) {
+ const uint32_t count = fReader.readU32();
+ const uint32_t byteLength = count * sizeof(SkPoint);
+ memcpy(points, fReader.skip(SkAlign4(byteLength)), byteLength);
+ return count;
+}
+
+uint32_t SkOrderedReadBuffer::readScalarArray(SkScalar* values) {
+ const uint32_t count = fReader.readU32();
+ const uint32_t byteLength = count * sizeof(SkScalar);
+ memcpy(values, fReader.skip(SkAlign4(byteLength)), byteLength);
+ return count;
+}
+
+uint32_t SkOrderedReadBuffer::getArrayCount() {
+ return *(uint32_t*)fReader.peek();
+}
+
+void SkOrderedReadBuffer::readBitmap(SkBitmap* bitmap) {
+ const int width = this->readInt();
+ const int height = this->readInt();
+ // The writer stored a boolean value to determine whether an SkBitmapHeap was used during
+ // writing.
+ if (this->readBool()) {
+ // An SkBitmapHeap was used for writing. Read the index from the stream and find the
+ // corresponding SkBitmap in fBitmapStorage.
+ const uint32_t index = fReader.readU32();
+ fReader.readU32(); // bitmap generation ID (see SkOrderedWriteBuffer::writeBitmap)
+ if (fBitmapStorage) {
+ *bitmap = *fBitmapStorage->getBitmap(index);
+ fBitmapStorage->releaseRef(index);
+ return;
+ } else {
+ // The bitmap was stored in a heap, but there is no way to access it. Set an error and
+ // fall through to use a place holder bitmap.
+ SkErrorInternals::SetError(kParseError_SkError, "SkOrderedWriteBuffer::writeBitmap "
+ "stored the SkBitmap in an SkBitmapHeap, but "
+ "SkOrderedReadBuffer has no SkBitmapHeapReader to "
+ "retrieve the SkBitmap.");
+ }
+ } else {
+ // The writer stored false, meaning the SkBitmap was not stored in an SkBitmapHeap.
+ const size_t length = this->readUInt();
+ if (length > 0) {
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ fDecodedBitmapIndex++;
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+ // A non-zero size means the SkBitmap was encoded. Read the data and pixel
+ // offset.
+ const void* data = this->skip(length);
+ const int32_t xOffset = fReader.readS32();
+ const int32_t yOffset = fReader.readS32();
+ if (fBitmapDecoder != NULL && fBitmapDecoder(data, length, bitmap)) {
+ if (bitmap->width() == width && bitmap->height() == height) {
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ if (0 != xOffset || 0 != yOffset) {
+ SkDebugf("SkOrderedReadBuffer::readBitmap: heights match,"
+ " but offset is not zero. \nInfo about the bitmap:"
+ "\n\tIndex: %d\n\tDimensions: [%d %d]\n\tEncoded"
+ " data size: %d\n\tOffset: (%d, %d)\n",
+ fDecodedBitmapIndex, width, height, length, xOffset,
+ yOffset);
+ }
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+ // If the width and height match, there should be no offset.
+ SkASSERT(0 == xOffset && 0 == yOffset);
+ return;
+ }
+
+ // This case can only be reached if extractSubset was called, so
+ // the recorded width and height must be smaller than (or equal to
+ // the encoded width and height.
+ SkASSERT(width <= bitmap->width() && height <= bitmap->height());
+
+ SkBitmap subsetBm;
+ SkIRect subset = SkIRect::MakeXYWH(xOffset, yOffset, width, height);
+ if (bitmap->extractSubset(&subsetBm, subset)) {
+ bitmap->swap(subsetBm);
+ return;
+ }
+ }
+ // This bitmap was encoded when written, but we are unable to decode, possibly due to
+ // not having a decoder.
+ SkErrorInternals::SetError(kParseError_SkError,
+ "Could not decode bitmap. Resulting bitmap will be red.");
+ } else {
+ // A size of zero means the SkBitmap was simply flattened.
+ bitmap->unflatten(*this);
+ return;
+ }
+ }
+ // Could not read the SkBitmap. Use a placeholder bitmap.
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap->allocPixels();
+ bitmap->eraseColor(SK_ColorRED);
+}
+
+SkTypeface* SkOrderedReadBuffer::readTypeface() {
+
+ uint32_t index = fReader.readU32();
+ if (0 == index || index > (unsigned)fTFCount) {
+ if (index) {
+ SkDebugf("====== typeface index %d\n", index);
+ }
+ return NULL;
+ } else {
+ SkASSERT(fTFArray);
+ return fTFArray[index - 1];
+ }
+}
+
+SkFlattenable* SkOrderedReadBuffer::readFlattenable() {
+ SkFlattenable::Factory factory = NULL;
+
+ if (fFactoryCount > 0) {
+ int32_t index = fReader.readU32();
+ if (0 == index) {
+ return NULL; // writer failed to give us the flattenable
+ }
+ index -= 1; // we stored the index-base-1
+ SkASSERT(index < fFactoryCount);
+ factory = fFactoryArray[index];
+ } else if (fFactoryTDArray) {
+ int32_t index = fReader.readU32();
+ if (0 == index) {
+ return NULL; // writer failed to give us the flattenable
+ }
+ index -= 1; // we stored the index-base-1
+ factory = (*fFactoryTDArray)[index];
+ } else {
+ factory = (SkFlattenable::Factory)readFunctionPtr();
+ if (NULL == factory) {
+ return NULL; // writer failed to give us the flattenable
+ }
+ }
+
+ // if we get here, factory may still be null, but if that is the case, the
+ // failure was ours, not the writer.
+ SkFlattenable* obj = NULL;
+ uint32_t sizeRecorded = fReader.readU32();
+ if (factory) {
+ uint32_t offset = fReader.offset();
+ obj = (*factory)(*this);
+ // check that we read the amount we expected
+ uint32_t sizeRead = fReader.offset() - offset;
+ if (sizeRecorded != sizeRead) {
+ // we could try to fix up the offset...
+ sk_throw();
+ }
+ } else {
+ // we must skip the remaining data
+ fReader.skip(sizeRecorded);
+ }
+ return obj;
+}
diff --git a/core/SkOrderedReadBuffer.h b/core/SkOrderedReadBuffer.h
new file mode 100644
index 00000000..556fce8b
--- /dev/null
+++ b/core/SkOrderedReadBuffer.h
@@ -0,0 +1,138 @@
+
+/*
+ * 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 SkOrderedReadBuffer_DEFINED
+#define SkOrderedReadBuffer_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkBitmapHeap.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPath.h"
+#include "SkPicture.h"
+#include "SkReader32.h"
+
+class SkBitmap;
+
+#if defined(SK_DEBUG) && defined(SK_BUILD_FOR_MAC)
+ #define DEBUG_NON_DETERMINISTIC_ASSERT
+#endif
+
+class SkOrderedReadBuffer : public SkFlattenableReadBuffer {
+public:
+ SkOrderedReadBuffer();
+ SkOrderedReadBuffer(const void* data, size_t size);
+ SkOrderedReadBuffer(SkStream* stream);
+ virtual ~SkOrderedReadBuffer();
+
+ virtual SkOrderedReadBuffer* getOrderedBinaryBuffer() SK_OVERRIDE { return this; }
+
+ SkReader32* getReader32() { return &fReader; }
+
+ uint32_t size() { return fReader.size(); }
+ uint32_t offset() { return fReader.offset(); }
+ bool eof() { return fReader.eof(); }
+ const void* skip(size_t size) { return fReader.skip(size); }
+
+ // primitives
+ virtual bool readBool() SK_OVERRIDE;
+ virtual SkColor readColor() SK_OVERRIDE;
+ virtual SkFixed readFixed() SK_OVERRIDE;
+ virtual int32_t readInt() SK_OVERRIDE;
+ virtual SkScalar readScalar() SK_OVERRIDE;
+ virtual uint32_t readUInt() SK_OVERRIDE;
+ virtual int32_t read32() SK_OVERRIDE;
+
+ // strings -- the caller is responsible for freeing the string contents
+ virtual void readString(SkString* string) SK_OVERRIDE;
+ virtual void* readEncodedString(size_t* length, SkPaint::TextEncoding encoding) SK_OVERRIDE;
+
+ // common data structures
+ virtual SkFlattenable* readFlattenable() SK_OVERRIDE;
+ virtual void readPoint(SkPoint* point) SK_OVERRIDE;
+ virtual void readMatrix(SkMatrix* matrix) SK_OVERRIDE;
+ virtual void readIRect(SkIRect* rect) SK_OVERRIDE;
+ virtual void readRect(SkRect* rect) SK_OVERRIDE;
+ virtual void readRegion(SkRegion* region) SK_OVERRIDE;
+ virtual void readPath(SkPath* path) SK_OVERRIDE;
+
+ // binary data and arrays
+ virtual uint32_t readByteArray(void* value) SK_OVERRIDE;
+ virtual uint32_t readColorArray(SkColor* colors) SK_OVERRIDE;
+ virtual uint32_t readIntArray(int32_t* values) SK_OVERRIDE;
+ virtual uint32_t readPointArray(SkPoint* points) SK_OVERRIDE;
+ virtual uint32_t readScalarArray(SkScalar* values) SK_OVERRIDE;
+
+ // helpers to get info about arrays and binary data
+ virtual uint32_t getArrayCount() SK_OVERRIDE;
+
+ virtual void readBitmap(SkBitmap* bitmap) SK_OVERRIDE;
+ virtual SkTypeface* readTypeface() SK_OVERRIDE;
+
+ void setBitmapStorage(SkBitmapHeapReader* bitmapStorage) {
+ SkRefCnt_SafeAssign(fBitmapStorage, bitmapStorage);
+ }
+
+ void setTypefaceArray(SkTypeface* array[], int count) {
+ fTFArray = array;
+ fTFCount = count;
+ }
+
+ /**
+ * Call this with a pre-loaded array of Factories, in the same order as
+ * were created/written by the writer. SkPicture uses this.
+ */
+ void setFactoryPlayback(SkFlattenable::Factory array[], int count) {
+ fFactoryTDArray = NULL;
+ fFactoryArray = array;
+ fFactoryCount = count;
+ }
+
+ /**
+ * Call this with an initially empty array, so the reader can cache each
+ * factory it sees by name. Used by the pipe code in conjunction with
+ * SkOrderedWriteBuffer::setNamedFactoryRecorder.
+ */
+ void setFactoryArray(SkTDArray<SkFlattenable::Factory>* array) {
+ fFactoryTDArray = array;
+ fFactoryArray = NULL;
+ fFactoryCount = 0;
+ }
+
+ /**
+ * Provide a function to decode an SkBitmap from encoded data. Only used if the writer
+ * encoded the SkBitmap. If the proper decoder cannot be used, a red bitmap with the
+ * appropriate size will be used.
+ */
+ void setBitmapDecoder(SkPicture::InstallPixelRefProc bitmapDecoder) {
+ fBitmapDecoder = bitmapDecoder;
+ }
+
+private:
+ SkReader32 fReader;
+ void* fMemoryPtr;
+
+ SkBitmapHeapReader* fBitmapStorage;
+ SkTypeface** fTFArray;
+ int fTFCount;
+
+ SkTDArray<SkFlattenable::Factory>* fFactoryTDArray;
+ SkFlattenable::Factory* fFactoryArray;
+ int fFactoryCount;
+
+ SkPicture::InstallPixelRefProc fBitmapDecoder;
+
+#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
+ // Debugging counter to keep track of how many bitmaps we
+ // have decoded.
+ int fDecodedBitmapIndex;
+#endif // DEBUG_NON_DETERMINISTIC_ASSERT
+
+ typedef SkFlattenableReadBuffer INHERITED;
+};
+
+#endif // SkOrderedReadBuffer_DEFINED
diff --git a/core/SkOrderedWriteBuffer.cpp b/core/SkOrderedWriteBuffer.cpp
new file mode 100644
index 00000000..64f52193
--- /dev/null
+++ b/core/SkOrderedWriteBuffer.cpp
@@ -0,0 +1,314 @@
+
+/*
+ * 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 "SkOrderedWriteBuffer.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkPtrRecorder.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+
+SkOrderedWriteBuffer::SkOrderedWriteBuffer(size_t minSize)
+ : INHERITED()
+ , fFactorySet(NULL)
+ , fNamedFactorySet(NULL)
+ , fWriter(minSize)
+ , fBitmapHeap(NULL)
+ , fTFSet(NULL)
+ , fBitmapEncoder(NULL) {
+}
+
+SkOrderedWriteBuffer::SkOrderedWriteBuffer(size_t minSize, void* storage, size_t storageSize)
+ : INHERITED()
+ , fFactorySet(NULL)
+ , fNamedFactorySet(NULL)
+ , fWriter(minSize, storage, storageSize)
+ , fBitmapHeap(NULL)
+ , fTFSet(NULL)
+ , fBitmapEncoder(NULL) {
+}
+
+SkOrderedWriteBuffer::~SkOrderedWriteBuffer() {
+ SkSafeUnref(fFactorySet);
+ SkSafeUnref(fNamedFactorySet);
+ SkSafeUnref(fBitmapHeap);
+ SkSafeUnref(fTFSet);
+}
+
+void SkOrderedWriteBuffer::writeByteArray(const void* data, size_t size) {
+ fWriter.write32(size);
+ fWriter.writePad(data, size);
+}
+
+void SkOrderedWriteBuffer::writeBool(bool value) {
+ fWriter.writeBool(value);
+}
+
+void SkOrderedWriteBuffer::writeFixed(SkFixed value) {
+ fWriter.write32(value);
+}
+
+void SkOrderedWriteBuffer::writeScalar(SkScalar value) {
+ fWriter.writeScalar(value);
+}
+
+void SkOrderedWriteBuffer::writeScalarArray(const SkScalar* value, uint32_t count) {
+ fWriter.write32(count);
+ fWriter.write(value, count * sizeof(SkScalar));
+}
+
+void SkOrderedWriteBuffer::writeInt(int32_t value) {
+ fWriter.write32(value);
+}
+
+void SkOrderedWriteBuffer::writeIntArray(const int32_t* value, uint32_t count) {
+ fWriter.write32(count);
+ fWriter.write(value, count * sizeof(int32_t));
+}
+
+void SkOrderedWriteBuffer::writeUInt(uint32_t value) {
+ fWriter.write32(value);
+}
+
+void SkOrderedWriteBuffer::write32(int32_t value) {
+ fWriter.write32(value);
+}
+
+void SkOrderedWriteBuffer::writeString(const char* value) {
+ fWriter.writeString(value);
+}
+
+void SkOrderedWriteBuffer::writeEncodedString(const void* value, size_t byteLength,
+ SkPaint::TextEncoding encoding) {
+ fWriter.writeInt(encoding);
+ fWriter.writeInt(byteLength);
+ fWriter.write(value, byteLength);
+}
+
+
+void SkOrderedWriteBuffer::writeColor(const SkColor& color) {
+ fWriter.write32(color);
+}
+
+void SkOrderedWriteBuffer::writeColorArray(const SkColor* color, uint32_t count) {
+ fWriter.write32(count);
+ fWriter.write(color, count * sizeof(SkColor));
+}
+
+void SkOrderedWriteBuffer::writePoint(const SkPoint& point) {
+ fWriter.writeScalar(point.fX);
+ fWriter.writeScalar(point.fY);
+}
+
+void SkOrderedWriteBuffer::writePointArray(const SkPoint* point, uint32_t count) {
+ fWriter.write32(count);
+ fWriter.write(point, count * sizeof(SkPoint));
+}
+
+void SkOrderedWriteBuffer::writeMatrix(const SkMatrix& matrix) {
+ fWriter.writeMatrix(matrix);
+}
+
+void SkOrderedWriteBuffer::writeIRect(const SkIRect& rect) {
+ fWriter.write(&rect, sizeof(SkIRect));
+}
+
+void SkOrderedWriteBuffer::writeRect(const SkRect& rect) {
+ fWriter.writeRect(rect);
+}
+
+void SkOrderedWriteBuffer::writeRegion(const SkRegion& region) {
+ fWriter.writeRegion(region);
+}
+
+void SkOrderedWriteBuffer::writePath(const SkPath& path) {
+ fWriter.writePath(path);
+}
+
+size_t SkOrderedWriteBuffer::writeStream(SkStream* stream, size_t length) {
+ fWriter.write32(length);
+ size_t bytesWritten = fWriter.readFromStream(stream, length);
+ if (bytesWritten < length) {
+ fWriter.reservePad(length - bytesWritten);
+ }
+ return bytesWritten;
+}
+
+bool SkOrderedWriteBuffer::writeToStream(SkWStream* stream) {
+ return fWriter.writeToStream(stream);
+}
+
+// Defined in SkBitmap.cpp
+bool get_upper_left_from_offset(SkBitmap::Config config, size_t offset, size_t rowBytes,
+ int32_t* x, int32_t* y);
+
+void SkOrderedWriteBuffer::writeBitmap(const SkBitmap& bitmap) {
+ // Record the width and height. This way if readBitmap fails a dummy bitmap can be drawn at the
+ // right size.
+ this->writeInt(bitmap.width());
+ this->writeInt(bitmap.height());
+
+ // 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. A boolean value of true will be written
+ // to the stream to signify that a heap was used.
+ // 2. If there is a function for encoding bitmaps, use it to write an encoded version of the
+ // bitmap. After writing a boolean value of false, signifying that a heap was not used, write
+ // the size of the encoded data. A non-zero size signifies that encoded data was written.
+ // 3. Call SkBitmap::flatten. After writing a boolean value of false, signifying that a heap was
+ // not used, write a zero to signify that the data was not encoded.
+ bool useBitmapHeap = fBitmapHeap != NULL;
+ // Write a bool: true if the SkBitmapHeap is to be used, in which case the reader must use an
+ // SkBitmapHeapReader to read the SkBitmap. False if the bitmap was serialized another way.
+ this->writeBool(useBitmapHeap);
+ if (useBitmapHeap) {
+ SkASSERT(NULL == fBitmapEncoder);
+ 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;
+ }
+ if (fBitmapEncoder != NULL) {
+ SkASSERT(NULL == fBitmapHeap);
+ size_t offset = 0;
+ SkAutoDataUnref data(fBitmapEncoder(&offset, bitmap));
+ if (data.get() != NULL) {
+ // Write the length to indicate that the bitmap was encoded successfully, followed
+ // by the actual data.
+ this->writeUInt(SkToU32(data->size()));
+ fWriter.writePad(data->data(), data->size());
+ // Store the coordinate of the offset, rather than fPixelRefOffset, which may be
+ // different depending on the decoder.
+ int32_t x, y;
+ if (0 == offset || !get_upper_left_from_offset(bitmap.config(), offset,
+ bitmap.rowBytes(), &x, &y)) {
+ x = y = 0;
+ }
+ this->write32(x);
+ this->write32(y);
+ return;
+ }
+ }
+ // Bitmap was not encoded. Record a zero, implying that the reader need not decode.
+ this->writeUInt(0);
+ bitmap.flatten(*this);
+}
+
+void SkOrderedWriteBuffer::writeTypeface(SkTypeface* obj) {
+ if (NULL == obj || NULL == fTFSet) {
+ fWriter.write32(0);
+ } else {
+ fWriter.write32(fTFSet->add(obj));
+ }
+}
+
+SkFactorySet* SkOrderedWriteBuffer::setFactoryRecorder(SkFactorySet* rec) {
+ SkRefCnt_SafeAssign(fFactorySet, rec);
+ if (fNamedFactorySet != NULL) {
+ fNamedFactorySet->unref();
+ fNamedFactorySet = NULL;
+ }
+ return rec;
+}
+
+SkNamedFactorySet* SkOrderedWriteBuffer::setNamedFactoryRecorder(SkNamedFactorySet* rec) {
+ SkRefCnt_SafeAssign(fNamedFactorySet, rec);
+ if (fFactorySet != NULL) {
+ fFactorySet->unref();
+ fFactorySet = NULL;
+ }
+ return rec;
+}
+
+SkRefCntSet* SkOrderedWriteBuffer::setTypefaceRecorder(SkRefCntSet* rec) {
+ SkRefCnt_SafeAssign(fTFSet, rec);
+ return rec;
+}
+
+void SkOrderedWriteBuffer::setBitmapHeap(SkBitmapHeap* bitmapHeap) {
+ SkRefCnt_SafeAssign(fBitmapHeap, bitmapHeap);
+ if (bitmapHeap != NULL) {
+ SkASSERT(NULL == fBitmapEncoder);
+ fBitmapEncoder = NULL;
+ }
+}
+
+void SkOrderedWriteBuffer::setBitmapEncoder(SkPicture::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...
+ * 0: failure to write the flattenable
+ * >0: (1-based) index into the SkFactorySet or SkNamedFactorySet
+ * If we don't have a factoryset, then the first "ptr" is either the
+ * factory, or null for failure.
+ *
+ * The distinction is important, since 0-index is 32bits (always), but a
+ * 0-functionptr might be 32 or 64 bits.
+ */
+
+ SkFlattenable::Factory factory = NULL;
+ if (flattenable) {
+ factory = flattenable->getFactory();
+ }
+ if (NULL == factory) {
+ if (fFactorySet != NULL || fNamedFactorySet != NULL) {
+ this->write32(0);
+ } else {
+ this->writeFunctionPtr(NULL);
+ }
+ return;
+ }
+
+ /*
+ * We can write 1 of 3 versions of the flattenable:
+ * 1. function-ptr : this is the fastest for the reader, but assumes that
+ * the writer and reader are in the same process.
+ * 2. index into fFactorySet : This is assumes the writer will later
+ * resolve the function-ptrs into strings for its reader. SkPicture
+ * does exactly this, by writing a table of names (matching the indices)
+ * up front in its serialized form.
+ * 3. index into fNamedFactorySet. fNamedFactorySet will also store the
+ * name. SkGPipe uses this technique so it can write the name to its
+ * stream before writing the flattenable.
+ */
+ if (fFactorySet) {
+ this->write32(fFactorySet->add(factory));
+ } else if (fNamedFactorySet) {
+ int32_t index = fNamedFactorySet->find(factory);
+ this->write32(index);
+ if (0 == index) {
+ return;
+ }
+ } else {
+ this->writeFunctionPtr((void*)factory);
+ }
+
+ // make room for the size of the flattened object
+ (void)fWriter.reserve(sizeof(uint32_t));
+ // record the current size, so we can subtract after the object writes.
+ uint32_t offset = fWriter.size();
+ // now flatten the object
+ flattenObject(flattenable, *this);
+ uint32_t objSize = fWriter.size() - offset;
+ // record the obj's size
+ *fWriter.peek32(offset - sizeof(uint32_t)) = objSize;
+}
diff --git a/core/SkOrderedWriteBuffer.h b/core/SkOrderedWriteBuffer.h
new file mode 100644
index 00000000..180f9a4d
--- /dev/null
+++ b/core/SkOrderedWriteBuffer.h
@@ -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.
+ */
+
+#ifndef SkOrderedWriteBuffer_DEFINED
+#define SkOrderedWriteBuffer_DEFINED
+
+#include "SkFlattenableBuffers.h"
+
+#include "SkRefCnt.h"
+#include "SkBitmapHeap.h"
+#include "SkPath.h"
+#include "SkPicture.h"
+#include "SkWriter32.h"
+
+class SkBitmap;
+class SkFlattenable;
+class SkFactorySet;
+class SkNamedFactorySet;
+class SkRefCntSet;
+
+class SkOrderedWriteBuffer : public SkFlattenableWriteBuffer {
+public:
+ SkOrderedWriteBuffer(size_t minSize);
+ SkOrderedWriteBuffer(size_t minSize, void* initialStorage, size_t storageSize);
+ virtual ~SkOrderedWriteBuffer();
+
+ virtual bool isOrderedBinaryBuffer() SK_OVERRIDE { return true; }
+ virtual SkOrderedWriteBuffer* getOrderedBinaryBuffer() SK_OVERRIDE { return this; }
+
+ SkWriter32* getWriter32() { return &fWriter; }
+ void reset(void* storage, size_t storageSize) { fWriter.reset(storage, storageSize); }
+
+ // Returns true if we've written only into the storage passed into constructor or reset.
+ // (You may be able to use this to avoid a call to writeToMemory.)
+ bool wroteOnlyToStorage() const { return fWriter.wroteOnlyToStorage(); }
+
+ void writeToMemory(void* dst) { fWriter.flatten(dst); }
+ uint32_t* reserve(size_t size) { return fWriter.reserve(size); }
+
+ uint32_t bytesWritten() const { return fWriter.bytesWritten(); }
+ // Deprecated. Please call bytesWritten instead. TODO(mtklein): clean up
+ uint32_t size() const { return this->bytesWritten(); }
+
+ virtual void writeByteArray(const void* data, size_t size) SK_OVERRIDE;
+ virtual void writeBool(bool value) SK_OVERRIDE;
+ virtual void writeFixed(SkFixed value) SK_OVERRIDE;
+ virtual void writeScalar(SkScalar value) SK_OVERRIDE;
+ virtual void writeScalarArray(const SkScalar* value, uint32_t count) SK_OVERRIDE;
+ virtual void writeInt(int32_t value) SK_OVERRIDE;
+ virtual void writeIntArray(const int32_t* value, uint32_t count) SK_OVERRIDE;
+ virtual void writeUInt(uint32_t value) SK_OVERRIDE;
+ virtual void write32(int32_t value) SK_OVERRIDE;
+ virtual void writeString(const char* value) SK_OVERRIDE;
+ virtual void writeEncodedString(const void* value, size_t byteLength,
+ SkPaint::TextEncoding encoding) SK_OVERRIDE;
+
+ virtual void writeFlattenable(SkFlattenable* flattenable) SK_OVERRIDE;
+ virtual void writeColor(const SkColor& color) SK_OVERRIDE;
+ virtual void writeColorArray(const SkColor* color, uint32_t count) SK_OVERRIDE;
+ virtual void writePoint(const SkPoint& point) SK_OVERRIDE;
+ virtual void writePointArray(const SkPoint* point, uint32_t count) SK_OVERRIDE;
+ virtual void writeMatrix(const SkMatrix& matrix) SK_OVERRIDE;
+ virtual void writeIRect(const SkIRect& rect)SK_OVERRIDE;
+ virtual void writeRect(const SkRect& rect) SK_OVERRIDE;
+ virtual void writeRegion(const SkRegion& region) SK_OVERRIDE;
+ virtual void writePath(const SkPath& path) SK_OVERRIDE;
+ virtual size_t writeStream(SkStream* stream, size_t length) SK_OVERRIDE;
+
+ virtual void writeBitmap(const SkBitmap& bitmap) SK_OVERRIDE;
+ virtual void writeTypeface(SkTypeface* typeface) SK_OVERRIDE;
+
+ virtual bool writeToStream(SkWStream*) SK_OVERRIDE;
+
+ SkFactorySet* setFactoryRecorder(SkFactorySet*);
+ SkNamedFactorySet* setNamedFactoryRecorder(SkNamedFactorySet*);
+
+ SkRefCntSet* getTypefaceRecorder() const { return fTFSet; }
+ SkRefCntSet* setTypefaceRecorder(SkRefCntSet*);
+
+ /**
+ * 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 SkData. writeBitmap will attempt to use
+ * 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.
+ * bitmapEncoder will never be called with a NULL pixelRefOffset.
+ *
+ * Incompatible with the SkBitmapHeap. If an encoder is set fBitmapHeap will be set to NULL in
+ * release and crash in debug.
+ */
+ void setBitmapEncoder(SkPicture::EncodeBitmap bitmapEncoder);
+
+private:
+ SkFactorySet* fFactorySet;
+ SkNamedFactorySet* fNamedFactorySet;
+ SkWriter32 fWriter;
+
+ SkBitmapHeap* fBitmapHeap;
+ SkRefCntSet* fTFSet;
+
+ SkPicture::EncodeBitmap fBitmapEncoder;
+
+ typedef SkFlattenableWriteBuffer INHERITED;
+};
+
+#endif // SkOrderedWriteBuffer_DEFINED
diff --git a/core/SkPackBits.cpp b/core/SkPackBits.cpp
new file mode 100644
index 00000000..7a1444b1
--- /dev/null
+++ b/core/SkPackBits.cpp
@@ -0,0 +1,411 @@
+
+/*
+ * 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 "SkPackBits.h"
+
+#define GATHER_STATSx
+
+static inline void small_memcpy(void* SK_RESTRICT dst,
+ const void* SK_RESTRICT src, int n) {
+ SkASSERT(n > 0 && n <= 15);
+ uint8_t* d = (uint8_t*)dst;
+ const uint8_t* s = (const uint8_t*)src;
+ switch (n) {
+ case 15: *d++ = *s++;
+ case 14: *d++ = *s++;
+ case 13: *d++ = *s++;
+ case 12: *d++ = *s++;
+ case 11: *d++ = *s++;
+ case 10: *d++ = *s++;
+ case 9: *d++ = *s++;
+ case 8: *d++ = *s++;
+ case 7: *d++ = *s++;
+ case 6: *d++ = *s++;
+ case 5: *d++ = *s++;
+ case 4: *d++ = *s++;
+ case 3: *d++ = *s++;
+ case 2: *d++ = *s++;
+ case 1: *d++ = *s++;
+ case 0: break;
+ }
+}
+
+static inline void small_memset(void* dst, uint8_t value, int n) {
+ SkASSERT(n > 0 && n <= 15);
+ uint8_t* d = (uint8_t*)dst;
+ switch (n) {
+ case 15: *d++ = value;
+ case 14: *d++ = value;
+ case 13: *d++ = value;
+ case 12: *d++ = value;
+ case 11: *d++ = value;
+ case 10: *d++ = value;
+ case 9: *d++ = value;
+ case 8: *d++ = value;
+ case 7: *d++ = value;
+ case 6: *d++ = value;
+ case 5: *d++ = value;
+ case 4: *d++ = value;
+ case 3: *d++ = value;
+ case 2: *d++ = value;
+ case 1: *d++ = value;
+ case 0: break;
+ }
+}
+
+// can we do better for small counts with our own inlined memcpy/memset?
+
+#define PB_MEMSET(addr, value, count) \
+do { \
+if ((count) > 15) { \
+memset(addr, value, count); \
+} else { \
+small_memset(addr, value, count); \
+} \
+} while (0)
+
+#define PB_MEMCPY(dst, src, count) \
+do { \
+ if ((count) > 15) { \
+ memcpy(dst, src, count); \
+ } else { \
+ small_memcpy(dst, src, count); \
+ } \
+} while (0)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef GATHER_STATS
+ static int gMemSetBuckets[129];
+ static int gMemCpyBuckets[129];
+ static int gCounter;
+
+static void register_memset_count(int n) {
+ SkASSERT((unsigned)n <= 128);
+ gMemSetBuckets[n] += 1;
+ gCounter += 1;
+
+ if ((gCounter & 0xFF) == 0) {
+ SkDebugf("----- packbits memset stats: ");
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gMemSetBuckets); i++) {
+ if (gMemSetBuckets[i]) {
+ SkDebugf(" %d:%d", i, gMemSetBuckets[i]);
+ }
+ }
+ }
+}
+static void register_memcpy_count(int n) {
+ SkASSERT((unsigned)n <= 128);
+ gMemCpyBuckets[n] += 1;
+ gCounter += 1;
+
+ if ((gCounter & 0x1FF) == 0) {
+ SkDebugf("----- packbits memcpy stats: ");
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gMemCpyBuckets); i++) {
+ if (gMemCpyBuckets[i]) {
+ SkDebugf(" %d:%d", i, gMemCpyBuckets[i]);
+ }
+ }
+ }
+}
+#else
+#define register_memset_count(n)
+#define register_memcpy_count(n)
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+size_t SkPackBits::ComputeMaxSize16(int count) {
+ // worst case is the number of 16bit values (times 2) +
+ // 1 byte per (up to) 128 entries.
+ return ((count + 127) >> 7) + (count << 1);
+}
+
+size_t SkPackBits::ComputeMaxSize8(int count) {
+ // worst case is the number of 8bit values + 1 byte per (up to) 128 entries.
+ return ((count + 127) >> 7) + count;
+}
+
+static uint8_t* flush_same16(uint8_t dst[], uint16_t value, int count) {
+ while (count > 0) {
+ int n = count;
+ if (n > 128) {
+ n = 128;
+ }
+ *dst++ = (uint8_t)(n - 1);
+ *dst++ = (uint8_t)(value >> 8);
+ *dst++ = (uint8_t)value;
+ count -= n;
+ }
+ return dst;
+}
+
+static uint8_t* flush_same8(uint8_t dst[], uint8_t value, int count) {
+ while (count > 0) {
+ int n = count;
+ if (n > 128) {
+ n = 128;
+ }
+ *dst++ = (uint8_t)(n - 1);
+ *dst++ = (uint8_t)value;
+ count -= n;
+ }
+ return dst;
+}
+
+static uint8_t* flush_diff16(uint8_t* SK_RESTRICT dst,
+ const uint16_t* SK_RESTRICT src, int count) {
+ while (count > 0) {
+ int n = count;
+ if (n > 128) {
+ n = 128;
+ }
+ *dst++ = (uint8_t)(n + 127);
+ PB_MEMCPY(dst, src, n * sizeof(uint16_t));
+ src += n;
+ dst += n * sizeof(uint16_t);
+ count -= n;
+ }
+ return dst;
+}
+
+static uint8_t* flush_diff8(uint8_t* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT src, int count) {
+ while (count > 0) {
+ int n = count;
+ if (n > 128) {
+ n = 128;
+ }
+ *dst++ = (uint8_t)(n + 127);
+ PB_MEMCPY(dst, src, n);
+ src += n;
+ dst += n;
+ count -= n;
+ }
+ return dst;
+}
+
+size_t SkPackBits::Pack16(const uint16_t* SK_RESTRICT src, int count,
+ uint8_t* SK_RESTRICT dst) {
+ uint8_t* origDst = dst;
+ const uint16_t* stop = src + count;
+
+ for (;;) {
+ count = stop - src;
+ SkASSERT(count >= 0);
+ if (count == 0) {
+ return dst - origDst;
+ }
+ if (1 == count) {
+ *dst++ = 0;
+ *dst++ = (uint8_t)(*src >> 8);
+ *dst++ = (uint8_t)*src;
+ return dst - origDst;
+ }
+
+ unsigned value = *src;
+ const uint16_t* s = src + 1;
+
+ if (*s == value) { // accumulate same values...
+ do {
+ s++;
+ if (s == stop) {
+ break;
+ }
+ } while (*s == value);
+ dst = flush_same16(dst, value, s - src);
+ } else { // accumulate diff values...
+ do {
+ if (++s == stop) {
+ goto FLUSH_DIFF;
+ }
+ } while (*s != s[-1]);
+ s -= 1; // back up so we don't grab one of the "same" values that follow
+ FLUSH_DIFF:
+ dst = flush_diff16(dst, src, s - src);
+ }
+ src = s;
+ }
+}
+
+size_t SkPackBits::Pack8(const uint8_t* SK_RESTRICT src, int count,
+ uint8_t* SK_RESTRICT dst) {
+ uint8_t* origDst = dst;
+ const uint8_t* stop = src + count;
+
+ for (;;) {
+ count = stop - src;
+ SkASSERT(count >= 0);
+ if (count == 0) {
+ return dst - origDst;
+ }
+ if (1 == count) {
+ *dst++ = 0;
+ *dst++ = *src;
+ return dst - origDst;
+ }
+
+ unsigned value = *src;
+ const uint8_t* s = src + 1;
+
+ if (*s == value) { // accumulate same values...
+ do {
+ s++;
+ if (s == stop) {
+ break;
+ }
+ } while (*s == value);
+ dst = flush_same8(dst, value, s - src);
+ } else { // accumulate diff values...
+ do {
+ if (++s == stop) {
+ goto FLUSH_DIFF;
+ }
+ // only stop if we hit 3 in a row,
+ // otherwise we get bigger than compuatemax
+ } while (*s != s[-1] || s[-1] != s[-2]);
+ s -= 2; // back up so we don't grab the "same" values that follow
+ FLUSH_DIFF:
+ dst = flush_diff8(dst, src, s - src);
+ }
+ src = s;
+ }
+}
+
+#include "SkUtils.h"
+
+int SkPackBits::Unpack16(const uint8_t* SK_RESTRICT src, size_t srcSize,
+ uint16_t* SK_RESTRICT dst) {
+ uint16_t* origDst = dst;
+ const uint8_t* stop = src + srcSize;
+
+ while (src < stop) {
+ unsigned n = *src++;
+ if (n <= 127) { // repeat count (n + 1)
+ n += 1;
+ sk_memset16(dst, (src[0] << 8) | src[1], n);
+ src += 2;
+ } else { // same count (n - 127)
+ n -= 127;
+ PB_MEMCPY(dst, src, n * sizeof(uint16_t));
+ src += n * sizeof(uint16_t);
+ }
+ dst += n;
+ }
+ SkASSERT(src == stop);
+ return dst - origDst;
+}
+
+int SkPackBits::Unpack8(const uint8_t* SK_RESTRICT src, size_t srcSize,
+ uint8_t* SK_RESTRICT dst) {
+ uint8_t* origDst = dst;
+ const uint8_t* stop = src + srcSize;
+
+ while (src < stop) {
+ unsigned n = *src++;
+ if (n <= 127) { // repeat count (n + 1)
+ n += 1;
+ PB_MEMSET(dst, *src++, n);
+ } else { // same count (n - 127)
+ n -= 127;
+ PB_MEMCPY(dst, src, n);
+ src += n;
+ }
+ dst += n;
+ }
+ SkASSERT(src == stop);
+ return dst - origDst;
+}
+
+enum UnpackState {
+ CLEAN_STATE,
+ REPEAT_BYTE_STATE,
+ COPY_SRC_STATE
+};
+
+void SkPackBits::Unpack8(uint8_t* SK_RESTRICT dst, size_t dstSkip,
+ size_t dstWrite, const uint8_t* SK_RESTRICT src) {
+ if (dstWrite == 0) {
+ return;
+ }
+
+ UnpackState state = CLEAN_STATE;
+ size_t stateCount = 0;
+
+ // state 1: do the skip-loop
+ while (dstSkip > 0) {
+ unsigned n = *src++;
+ if (n <= 127) { // repeat count (n + 1)
+ n += 1;
+ if (n > dstSkip) {
+ state = REPEAT_BYTE_STATE;
+ stateCount = n - dstSkip;
+ n = dstSkip;
+ // we don't increment src here, since its needed in stage 2
+ } else {
+ src++; // skip the src byte
+ }
+ } else { // same count (n - 127)
+ n -= 127;
+ if (n > dstSkip) {
+ state = COPY_SRC_STATE;
+ stateCount = n - dstSkip;
+ n = dstSkip;
+ }
+ src += n;
+ }
+ dstSkip -= n;
+ }
+
+ // stage 2: perform any catchup from the skip-stage
+ if (stateCount > dstWrite) {
+ stateCount = dstWrite;
+ }
+ switch (state) {
+ case REPEAT_BYTE_STATE:
+ SkASSERT(stateCount > 0);
+ register_memset_count(stateCount);
+ PB_MEMSET(dst, *src++, stateCount);
+ break;
+ case COPY_SRC_STATE:
+ SkASSERT(stateCount > 0);
+ register_memcpy_count(stateCount);
+ PB_MEMCPY(dst, src, stateCount);
+ src += stateCount;
+ break;
+ default:
+ SkASSERT(stateCount == 0);
+ break;
+ }
+ dst += stateCount;
+ dstWrite -= stateCount;
+
+ // copy at most dstWrite bytes into dst[]
+ while (dstWrite > 0) {
+ unsigned n = *src++;
+ if (n <= 127) { // repeat count (n + 1)
+ n += 1;
+ if (n > dstWrite) {
+ n = dstWrite;
+ }
+ register_memset_count(n);
+ PB_MEMSET(dst, *src++, n);
+ } else { // same count (n - 127)
+ n -= 127;
+ if (n > dstWrite) {
+ n = dstWrite;
+ }
+ register_memcpy_count(n);
+ PB_MEMCPY(dst, src, n);
+ src += n;
+ }
+ dst += n;
+ dstWrite -= n;
+ }
+ SkASSERT(0 == dstWrite);
+}
diff --git a/core/SkPaint.cpp b/core/SkPaint.cpp
new file mode 100644
index 00000000..372e6805
--- /dev/null
+++ b/core/SkPaint.cpp
@@ -0,0 +1,2611 @@
+
+/*
+ * 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 "SkPaint.h"
+#include "SkAnnotation.h"
+#include "SkAutoKern.h"
+#include "SkColorFilter.h"
+#include "SkData.h"
+#include "SkDeviceProperties.h"
+#include "SkFontDescriptor.h"
+#include "SkFontHost.h"
+#include "SkGlyphCache.h"
+#include "SkImageFilter.h"
+#include "SkMaskFilter.h"
+#include "SkMaskGamma.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPaintDefaults.h"
+#include "SkPathEffect.h"
+#include "SkRasterizer.h"
+#include "SkScalar.h"
+#include "SkScalerContext.h"
+#include "SkShader.h"
+#include "SkStringUtils.h"
+#include "SkStroke.h"
+#include "SkTextFormatParams.h"
+#include "SkTextToPathIter.h"
+#include "SkTLazy.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+
+// define this to get a printf for out-of-range parameter in setters
+// e.g. setTextSize(-1)
+//#define SK_REPORT_API_RANGE_CHECK
+
+#ifdef SK_BUILD_FOR_ANDROID
+#define GEN_ID_INC fGenerationID++
+#define GEN_ID_INC_EVAL(expression) if (expression) { fGenerationID++; }
+#else
+#define GEN_ID_INC
+#define GEN_ID_INC_EVAL(expression)
+#endif
+
+SkPaint::SkPaint() {
+ // since we may have padding, we zero everything so that our memcmp() call
+ // in operator== will work correctly.
+ // with this, we can skip 0 and null individual initializations
+ sk_bzero(this, sizeof(*this));
+
+#if 0 // not needed with the bzero call above
+ fTypeface = NULL;
+ fTextSkewX = 0;
+ fPathEffect = NULL;
+ fShader = NULL;
+ fXfermode = NULL;
+ fMaskFilter = NULL;
+ fColorFilter = NULL;
+ fRasterizer = NULL;
+ fLooper = NULL;
+ fImageFilter = NULL;
+ fAnnotation = NULL;
+ fWidth = 0;
+#endif
+
+ fTextSize = SkPaintDefaults_TextSize;
+ fTextScaleX = SK_Scalar1;
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ fHintingScaleFactor = SK_Scalar1;
+#endif
+ fColor = SK_ColorBLACK;
+ fMiterLimit = SkPaintDefaults_MiterLimit;
+ fFlags = SkPaintDefaults_Flags;
+ fCapType = kDefault_Cap;
+ fJoinType = kDefault_Join;
+ fTextAlign = kLeft_Align;
+ fStyle = kFill_Style;
+ fTextEncoding = kUTF8_TextEncoding;
+ fHinting = SkPaintDefaults_Hinting;
+ fPrivFlags = 0;
+#ifdef SK_BUILD_FOR_ANDROID
+ new (&fPaintOptionsAndroid) SkPaintOptionsAndroid;
+ fGenerationID = 0;
+#endif
+}
+
+SkPaint::SkPaint(const SkPaint& src) {
+ memcpy(this, &src, sizeof(src));
+
+ SkSafeRef(fTypeface);
+ SkSafeRef(fPathEffect);
+ SkSafeRef(fShader);
+ SkSafeRef(fXfermode);
+ SkSafeRef(fMaskFilter);
+ SkSafeRef(fColorFilter);
+ SkSafeRef(fRasterizer);
+ SkSafeRef(fLooper);
+ SkSafeRef(fImageFilter);
+ SkSafeRef(fAnnotation);
+
+#ifdef SK_BUILD_FOR_ANDROID
+ new (&fPaintOptionsAndroid) SkPaintOptionsAndroid(src.fPaintOptionsAndroid);
+#endif
+}
+
+SkPaint::~SkPaint() {
+ SkSafeUnref(fTypeface);
+ SkSafeUnref(fPathEffect);
+ SkSafeUnref(fShader);
+ SkSafeUnref(fXfermode);
+ SkSafeUnref(fMaskFilter);
+ SkSafeUnref(fColorFilter);
+ SkSafeUnref(fRasterizer);
+ SkSafeUnref(fLooper);
+ SkSafeUnref(fImageFilter);
+ SkSafeUnref(fAnnotation);
+}
+
+SkPaint& SkPaint::operator=(const SkPaint& src) {
+ SkASSERT(&src);
+
+ SkSafeRef(src.fTypeface);
+ SkSafeRef(src.fPathEffect);
+ SkSafeRef(src.fShader);
+ SkSafeRef(src.fXfermode);
+ SkSafeRef(src.fMaskFilter);
+ SkSafeRef(src.fColorFilter);
+ SkSafeRef(src.fRasterizer);
+ SkSafeRef(src.fLooper);
+ SkSafeRef(src.fImageFilter);
+ SkSafeRef(src.fAnnotation);
+
+ SkSafeUnref(fTypeface);
+ SkSafeUnref(fPathEffect);
+ SkSafeUnref(fShader);
+ SkSafeUnref(fXfermode);
+ SkSafeUnref(fMaskFilter);
+ SkSafeUnref(fColorFilter);
+ SkSafeUnref(fRasterizer);
+ SkSafeUnref(fLooper);
+ SkSafeUnref(fImageFilter);
+ SkSafeUnref(fAnnotation);
+
+#ifdef SK_BUILD_FOR_ANDROID
+ fPaintOptionsAndroid.~SkPaintOptionsAndroid();
+
+ uint32_t oldGenerationID = fGenerationID;
+#endif
+ memcpy(this, &src, sizeof(src));
+#ifdef SK_BUILD_FOR_ANDROID
+ fGenerationID = oldGenerationID + 1;
+
+ new (&fPaintOptionsAndroid) SkPaintOptionsAndroid(src.fPaintOptionsAndroid);
+#endif
+
+ return *this;
+}
+
+bool operator==(const SkPaint& a, const SkPaint& b) {
+#ifdef SK_BUILD_FOR_ANDROID
+ //assumes that fGenerationID is the last field in the struct
+ return !memcmp(&a, &b, SK_OFFSETOF(SkPaint, fGenerationID));
+#else
+ return !memcmp(&a, &b, sizeof(a));
+#endif
+}
+
+void SkPaint::reset() {
+ SkPaint init;
+
+#ifdef SK_BUILD_FOR_ANDROID
+ uint32_t oldGenerationID = fGenerationID;
+#endif
+ *this = init;
+#ifdef SK_BUILD_FOR_ANDROID
+ fGenerationID = oldGenerationID + 1;
+#endif
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+uint32_t SkPaint::getGenerationID() const {
+ return fGenerationID;
+}
+
+void SkPaint::setGenerationID(uint32_t generationID) {
+ fGenerationID = generationID;
+}
+
+unsigned SkPaint::getBaseGlyphCount(SkUnichar text) const {
+ SkAutoGlyphCache autoCache(*this, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+ return cache->getBaseGlyphCount(text);
+}
+
+void SkPaint::setPaintOptionsAndroid(const SkPaintOptionsAndroid& options) {
+ if (options != fPaintOptionsAndroid) {
+ fPaintOptionsAndroid = options;
+ GEN_ID_INC;
+ }
+}
+#endif
+
+SkPaint::FilterLevel SkPaint::getFilterLevel() const {
+ int level = 0;
+ if (fFlags & kFilterBitmap_Flag) {
+ level |= 1;
+ }
+ if (fFlags & kHighQualityFilterBitmap_Flag) {
+ level |= 2;
+ }
+ return (FilterLevel)level;
+}
+
+void SkPaint::setFilterLevel(FilterLevel level) {
+ unsigned mask = kFilterBitmap_Flag | kHighQualityFilterBitmap_Flag;
+ unsigned flags = 0;
+ if (level & 1) {
+ flags |= kFilterBitmap_Flag;
+ }
+ if (level & 2) {
+ flags |= kHighQualityFilterBitmap_Flag;
+ }
+ this->setFlags((fFlags & ~mask) | flags);
+}
+
+void SkPaint::setHinting(Hinting hintingLevel) {
+ GEN_ID_INC_EVAL((unsigned) hintingLevel != fHinting);
+ fHinting = hintingLevel;
+}
+
+void SkPaint::setFlags(uint32_t flags) {
+ GEN_ID_INC_EVAL(fFlags != flags);
+ fFlags = flags;
+}
+
+void SkPaint::setAntiAlias(bool doAA) {
+ this->setFlags(SkSetClearMask(fFlags, doAA, kAntiAlias_Flag));
+}
+
+void SkPaint::setDither(bool doDither) {
+ this->setFlags(SkSetClearMask(fFlags, doDither, kDither_Flag));
+}
+
+void SkPaint::setSubpixelText(bool doSubpixel) {
+ this->setFlags(SkSetClearMask(fFlags, doSubpixel, kSubpixelText_Flag));
+}
+
+void SkPaint::setLCDRenderText(bool doLCDRender) {
+ this->setFlags(SkSetClearMask(fFlags, doLCDRender, kLCDRenderText_Flag));
+}
+
+void SkPaint::setEmbeddedBitmapText(bool doEmbeddedBitmapText) {
+ this->setFlags(SkSetClearMask(fFlags, doEmbeddedBitmapText, kEmbeddedBitmapText_Flag));
+}
+
+void SkPaint::setAutohinted(bool useAutohinter) {
+ this->setFlags(SkSetClearMask(fFlags, useAutohinter, kAutoHinting_Flag));
+}
+
+void SkPaint::setLinearText(bool doLinearText) {
+ this->setFlags(SkSetClearMask(fFlags, doLinearText, kLinearText_Flag));
+}
+
+void SkPaint::setVerticalText(bool doVertical) {
+ this->setFlags(SkSetClearMask(fFlags, doVertical, kVerticalText_Flag));
+}
+
+void SkPaint::setUnderlineText(bool doUnderline) {
+ this->setFlags(SkSetClearMask(fFlags, doUnderline, kUnderlineText_Flag));
+}
+
+void SkPaint::setStrikeThruText(bool doStrikeThru) {
+ this->setFlags(SkSetClearMask(fFlags, doStrikeThru, kStrikeThruText_Flag));
+}
+
+void SkPaint::setFakeBoldText(bool doFakeBold) {
+ this->setFlags(SkSetClearMask(fFlags, doFakeBold, kFakeBoldText_Flag));
+}
+
+void SkPaint::setDevKernText(bool doDevKern) {
+ this->setFlags(SkSetClearMask(fFlags, doDevKern, kDevKernText_Flag));
+}
+
+void SkPaint::setStyle(Style style) {
+ if ((unsigned)style < kStyleCount) {
+ GEN_ID_INC_EVAL((unsigned)style != fStyle);
+ fStyle = style;
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setStyle(%d) out of range\n", style);
+#endif
+ }
+}
+
+void SkPaint::setColor(SkColor color) {
+ GEN_ID_INC_EVAL(color != fColor);
+ fColor = color;
+}
+
+void SkPaint::setAlpha(U8CPU a) {
+ this->setColor(SkColorSetARGB(a, SkColorGetR(fColor),
+ SkColorGetG(fColor), SkColorGetB(fColor)));
+}
+
+void SkPaint::setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
+ this->setColor(SkColorSetARGB(a, r, g, b));
+}
+
+void SkPaint::setStrokeWidth(SkScalar width) {
+ if (width >= 0) {
+ GEN_ID_INC_EVAL(width != fWidth);
+ fWidth = width;
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setStrokeWidth() called with negative value\n");
+#endif
+ }
+}
+
+void SkPaint::setStrokeMiter(SkScalar limit) {
+ if (limit >= 0) {
+ GEN_ID_INC_EVAL(limit != fMiterLimit);
+ fMiterLimit = limit;
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setStrokeMiter() called with negative value\n");
+#endif
+ }
+}
+
+void SkPaint::setStrokeCap(Cap ct) {
+ if ((unsigned)ct < kCapCount) {
+ GEN_ID_INC_EVAL((unsigned)ct != fCapType);
+ fCapType = SkToU8(ct);
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setStrokeCap(%d) out of range\n", ct);
+#endif
+ }
+}
+
+void SkPaint::setStrokeJoin(Join jt) {
+ if ((unsigned)jt < kJoinCount) {
+ GEN_ID_INC_EVAL((unsigned)jt != fJoinType);
+ fJoinType = SkToU8(jt);
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setStrokeJoin(%d) out of range\n", jt);
+#endif
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPaint::setTextAlign(Align align) {
+ if ((unsigned)align < kAlignCount) {
+ GEN_ID_INC_EVAL((unsigned)align != fTextAlign);
+ fTextAlign = SkToU8(align);
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setTextAlign(%d) out of range\n", align);
+#endif
+ }
+}
+
+void SkPaint::setTextSize(SkScalar ts) {
+ if (ts >= 0) {
+ GEN_ID_INC_EVAL(ts != fTextSize);
+ fTextSize = ts;
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setTextSize() called with negative value\n");
+#endif
+ }
+}
+
+void SkPaint::setTextScaleX(SkScalar scaleX) {
+ GEN_ID_INC_EVAL(scaleX != fTextScaleX);
+ fTextScaleX = scaleX;
+}
+
+void SkPaint::setTextSkewX(SkScalar skewX) {
+ GEN_ID_INC_EVAL(skewX != fTextSkewX);
+ fTextSkewX = skewX;
+}
+
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+void SkPaint::setHintingScaleFactor(SkScalar hintingScaleFactor) {
+ GEN_ID_INC_EVAL(hintingScaleFactor != fHintingScaleFactor);
+ fHintingScaleFactor = hintingScaleFactor;
+}
+#endif
+
+void SkPaint::setTextEncoding(TextEncoding encoding) {
+ if ((unsigned)encoding <= kGlyphID_TextEncoding) {
+ GEN_ID_INC_EVAL((unsigned)encoding != fTextEncoding);
+ fTextEncoding = encoding;
+ } else {
+#ifdef SK_REPORT_API_RANGE_CHECK
+ SkDebugf("SkPaint::setTextEncoding(%d) out of range\n", encoding);
+#endif
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTypeface* SkPaint::setTypeface(SkTypeface* font) {
+ SkRefCnt_SafeAssign(fTypeface, font);
+ GEN_ID_INC;
+ return font;
+}
+
+SkRasterizer* SkPaint::setRasterizer(SkRasterizer* r) {
+ SkRefCnt_SafeAssign(fRasterizer, r);
+ GEN_ID_INC;
+ return r;
+}
+
+SkDrawLooper* SkPaint::setLooper(SkDrawLooper* looper) {
+ SkRefCnt_SafeAssign(fLooper, looper);
+ GEN_ID_INC;
+ return looper;
+}
+
+SkImageFilter* SkPaint::setImageFilter(SkImageFilter* imageFilter) {
+ SkRefCnt_SafeAssign(fImageFilter, imageFilter);
+ GEN_ID_INC;
+ return imageFilter;
+}
+
+SkAnnotation* SkPaint::setAnnotation(SkAnnotation* annotation) {
+ SkRefCnt_SafeAssign(fAnnotation, annotation);
+ GEN_ID_INC;
+
+ bool isNoDraw = annotation && annotation->isNoDraw();
+ fPrivFlags = SkSetClearMask(fPrivFlags, isNoDraw, kNoDrawAnnotation_PrivFlag);
+
+ return annotation;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkScalar mag2(SkScalar x, SkScalar y) {
+ return x * x + y * y;
+}
+
+static bool tooBig(const SkMatrix& m, SkScalar ma2max) {
+ return mag2(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewY]) > ma2max
+ ||
+ mag2(m[SkMatrix::kMSkewX], m[SkMatrix::kMScaleY]) > ma2max;
+}
+
+bool SkPaint::TooBigToUseCache(const SkMatrix& ctm, const SkMatrix& textM) {
+ SkASSERT(!ctm.hasPerspective());
+ SkASSERT(!textM.hasPerspective());
+
+ SkMatrix matrix;
+ matrix.setConcat(ctm, textM);
+ return tooBig(matrix, MaxCacheSize2());
+}
+
+bool SkPaint::tooBigToUseCache(const SkMatrix& ctm) const {
+ SkMatrix textM;
+ return TooBigToUseCache(ctm, *this->setTextMatrix(&textM));
+}
+
+bool SkPaint::tooBigToUseCache() const {
+ SkMatrix textM;
+ return tooBig(*this->setTextMatrix(&textM), MaxCacheSize2());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGlyphCache.h"
+#include "SkUtils.h"
+
+static void DetachDescProc(SkTypeface* typeface, const SkDescriptor* desc,
+ void* context) {
+ *((SkGlyphCache**)context) = SkGlyphCache::DetachCache(typeface, desc);
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+const SkGlyph& SkPaint::getUnicharMetrics(SkUnichar text,
+ const SkMatrix* deviceMatrix) {
+ SkGlyphCache* cache;
+ descriptorProc(NULL, deviceMatrix, DetachDescProc, &cache, true);
+
+ const SkGlyph& glyph = cache->getUnicharMetrics(text);
+
+ SkGlyphCache::AttachCache(cache);
+ return glyph;
+}
+
+const SkGlyph& SkPaint::getGlyphMetrics(uint16_t glyphId,
+ const SkMatrix* deviceMatrix) {
+ SkGlyphCache* cache;
+ descriptorProc(NULL, deviceMatrix, DetachDescProc, &cache, true);
+
+ const SkGlyph& glyph = cache->getGlyphIDMetrics(glyphId);
+
+ SkGlyphCache::AttachCache(cache);
+ return glyph;
+}
+
+const void* SkPaint::findImage(const SkGlyph& glyph,
+ const SkMatrix* deviceMatrix) {
+ // See ::detachCache()
+ SkGlyphCache* cache;
+ descriptorProc(NULL, deviceMatrix, DetachDescProc, &cache, true);
+
+ const void* image = cache->findImage(glyph);
+
+ SkGlyphCache::AttachCache(cache);
+ return image;
+}
+#endif
+
+int SkPaint::textToGlyphs(const void* textData, size_t byteLength,
+ uint16_t glyphs[]) const {
+ if (byteLength == 0) {
+ return 0;
+ }
+
+ SkASSERT(textData != NULL);
+
+ if (NULL == glyphs) {
+ switch (this->getTextEncoding()) {
+ case kUTF8_TextEncoding:
+ return SkUTF8_CountUnichars((const char*)textData, byteLength);
+ case kUTF16_TextEncoding:
+ return SkUTF16_CountUnichars((const uint16_t*)textData,
+ byteLength >> 1);
+ case kUTF32_TextEncoding:
+ return byteLength >> 2;
+ case kGlyphID_TextEncoding:
+ return byteLength >> 1;
+ default:
+ SkDEBUGFAIL("unknown text encoding");
+ }
+ return 0;
+ }
+
+ // if we get here, we have a valid glyphs[] array, so time to fill it in
+
+ // handle this encoding before the setup for the glyphcache
+ if (this->getTextEncoding() == kGlyphID_TextEncoding) {
+ // we want to ignore the low bit of byteLength
+ memcpy(glyphs, textData, byteLength >> 1 << 1);
+ return byteLength >> 1;
+ }
+
+ SkAutoGlyphCache autoCache(*this, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ const char* text = (const char*)textData;
+ const char* stop = text + byteLength;
+ uint16_t* gptr = glyphs;
+
+ switch (this->getTextEncoding()) {
+ case SkPaint::kUTF8_TextEncoding:
+ while (text < stop) {
+ *gptr++ = cache->unicharToGlyph(SkUTF8_NextUnichar(&text));
+ }
+ break;
+ case SkPaint::kUTF16_TextEncoding: {
+ const uint16_t* text16 = (const uint16_t*)text;
+ const uint16_t* stop16 = (const uint16_t*)stop;
+ while (text16 < stop16) {
+ *gptr++ = cache->unicharToGlyph(SkUTF16_NextUnichar(&text16));
+ }
+ break;
+ }
+ case kUTF32_TextEncoding: {
+ const int32_t* text32 = (const int32_t*)text;
+ const int32_t* stop32 = (const int32_t*)stop;
+ while (text32 < stop32) {
+ *gptr++ = cache->unicharToGlyph(*text32++);
+ }
+ break;
+ }
+ default:
+ SkDEBUGFAIL("unknown text encoding");
+ }
+ return gptr - glyphs;
+}
+
+bool SkPaint::containsText(const void* textData, size_t byteLength) const {
+ if (0 == byteLength) {
+ return true;
+ }
+
+ SkASSERT(textData != NULL);
+
+ // handle this encoding before the setup for the glyphcache
+ if (this->getTextEncoding() == kGlyphID_TextEncoding) {
+ const uint16_t* glyphID = static_cast<const uint16_t*>(textData);
+ size_t count = byteLength >> 1;
+ for (size_t i = 0; i < count; i++) {
+ if (0 == glyphID[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ SkAutoGlyphCache autoCache(*this, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ switch (this->getTextEncoding()) {
+ case SkPaint::kUTF8_TextEncoding: {
+ const char* text = static_cast<const char*>(textData);
+ const char* stop = text + byteLength;
+ while (text < stop) {
+ if (0 == cache->unicharToGlyph(SkUTF8_NextUnichar(&text))) {
+ return false;
+ }
+ }
+ break;
+ }
+ case SkPaint::kUTF16_TextEncoding: {
+ const uint16_t* text = static_cast<const uint16_t*>(textData);
+ const uint16_t* stop = text + (byteLength >> 1);
+ while (text < stop) {
+ if (0 == cache->unicharToGlyph(SkUTF16_NextUnichar(&text))) {
+ return false;
+ }
+ }
+ break;
+ }
+ case SkPaint::kUTF32_TextEncoding: {
+ const int32_t* text = static_cast<const int32_t*>(textData);
+ const int32_t* stop = text + (byteLength >> 2);
+ while (text < stop) {
+ if (0 == cache->unicharToGlyph(*text++)) {
+ return false;
+ }
+ }
+ break;
+ }
+ default:
+ SkDEBUGFAIL("unknown text encoding");
+ return false;
+ }
+ return true;
+}
+
+void SkPaint::glyphsToUnichars(const uint16_t glyphs[], int count,
+ SkUnichar textData[]) const {
+ if (count <= 0) {
+ return;
+ }
+
+ SkASSERT(glyphs != NULL);
+ SkASSERT(textData != NULL);
+
+ SkAutoGlyphCache autoCache(*this, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ for (int index = 0; index < count; index++) {
+ textData[index] = cache->glyphToUnichar(glyphs[index]);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkGlyph& sk_getMetrics_utf8_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF8_NextUnichar(text));
+}
+
+static const SkGlyph& sk_getMetrics_utf8_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF8_PrevUnichar(text));
+}
+
+static const SkGlyph& sk_getMetrics_utf16_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF16_NextUnichar((const uint16_t**)text));
+}
+
+static const SkGlyph& sk_getMetrics_utf16_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF16_PrevUnichar((const uint16_t**)text));
+}
+
+static const SkGlyph& sk_getMetrics_utf32_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *ptr++;
+ *text = (const char*)ptr;
+ return cache->getUnicharMetrics(uni);
+}
+
+static const SkGlyph& sk_getMetrics_utf32_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *--ptr;
+ *text = (const char*)ptr;
+ return cache->getUnicharMetrics(uni);
+}
+
+static const SkGlyph& sk_getMetrics_glyph_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ unsigned glyphID = *ptr;
+ ptr += 1;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDMetrics(glyphID);
+}
+
+static const SkGlyph& sk_getMetrics_glyph_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ ptr -= 1;
+ unsigned glyphID = *ptr;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDMetrics(glyphID);
+}
+
+static const SkGlyph& sk_getAdvance_utf8_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharAdvance(SkUTF8_NextUnichar(text));
+}
+
+static const SkGlyph& sk_getAdvance_utf8_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharAdvance(SkUTF8_PrevUnichar(text));
+}
+
+static const SkGlyph& sk_getAdvance_utf16_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharAdvance(SkUTF16_NextUnichar((const uint16_t**)text));
+}
+
+static const SkGlyph& sk_getAdvance_utf16_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharAdvance(SkUTF16_PrevUnichar((const uint16_t**)text));
+}
+
+static const SkGlyph& sk_getAdvance_utf32_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *ptr++;
+ *text = (const char*)ptr;
+ return cache->getUnicharAdvance(uni);
+}
+
+static const SkGlyph& sk_getAdvance_utf32_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *--ptr;
+ *text = (const char*)ptr;
+ return cache->getUnicharAdvance(uni);
+}
+
+static const SkGlyph& sk_getAdvance_glyph_next(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ unsigned glyphID = *ptr;
+ ptr += 1;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDAdvance(glyphID);
+}
+
+static const SkGlyph& sk_getAdvance_glyph_prev(SkGlyphCache* cache,
+ const char** text) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ ptr -= 1;
+ unsigned glyphID = *ptr;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDAdvance(glyphID);
+}
+
+SkMeasureCacheProc SkPaint::getMeasureCacheProc(TextBufferDirection tbd,
+ bool needFullMetrics) const {
+ static const SkMeasureCacheProc gMeasureCacheProcs[] = {
+ sk_getMetrics_utf8_next,
+ sk_getMetrics_utf16_next,
+ sk_getMetrics_utf32_next,
+ sk_getMetrics_glyph_next,
+
+ sk_getMetrics_utf8_prev,
+ sk_getMetrics_utf16_prev,
+ sk_getMetrics_utf32_prev,
+ sk_getMetrics_glyph_prev,
+
+ sk_getAdvance_utf8_next,
+ sk_getAdvance_utf16_next,
+ sk_getAdvance_utf32_next,
+ sk_getAdvance_glyph_next,
+
+ sk_getAdvance_utf8_prev,
+ sk_getAdvance_utf16_prev,
+ sk_getAdvance_utf32_prev,
+ sk_getAdvance_glyph_prev
+ };
+
+ unsigned index = this->getTextEncoding();
+
+ if (kBackward_TextBufferDirection == tbd) {
+ index += 4;
+ }
+ if (!needFullMetrics && !this->isDevKernText()) {
+ index += 8;
+ }
+
+ SkASSERT(index < SK_ARRAY_COUNT(gMeasureCacheProcs));
+ return gMeasureCacheProcs[index];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkGlyph& sk_getMetrics_utf8_00(SkGlyphCache* cache,
+ const char** text, SkFixed, SkFixed) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF8_NextUnichar(text));
+}
+
+static const SkGlyph& sk_getMetrics_utf8_xy(SkGlyphCache* cache,
+ const char** text, SkFixed x, SkFixed y) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF8_NextUnichar(text), x, y);
+}
+
+static const SkGlyph& sk_getMetrics_utf16_00(SkGlyphCache* cache,
+ const char** text, SkFixed, SkFixed) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF16_NextUnichar((const uint16_t**)text));
+}
+
+static const SkGlyph& sk_getMetrics_utf16_xy(SkGlyphCache* cache,
+ const char** text, SkFixed x, SkFixed y) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ return cache->getUnicharMetrics(SkUTF16_NextUnichar((const uint16_t**)text),
+ x, y);
+}
+
+static const SkGlyph& sk_getMetrics_utf32_00(SkGlyphCache* cache,
+ const char** text, SkFixed, SkFixed) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *ptr++;
+ *text = (const char*)ptr;
+ return cache->getUnicharMetrics(uni);
+}
+
+static const SkGlyph& sk_getMetrics_utf32_xy(SkGlyphCache* cache,
+ const char** text, SkFixed x, SkFixed y) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const int32_t* ptr = *(const int32_t**)text;
+ SkUnichar uni = *--ptr;
+ *text = (const char*)ptr;
+ return cache->getUnicharMetrics(uni, x, y);
+}
+
+static const SkGlyph& sk_getMetrics_glyph_00(SkGlyphCache* cache,
+ const char** text, SkFixed, SkFixed) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ unsigned glyphID = *ptr;
+ ptr += 1;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDMetrics(glyphID);
+}
+
+static const SkGlyph& sk_getMetrics_glyph_xy(SkGlyphCache* cache,
+ const char** text, SkFixed x, SkFixed y) {
+ SkASSERT(cache != NULL);
+ SkASSERT(text != NULL);
+
+ const uint16_t* ptr = *(const uint16_t**)text;
+ unsigned glyphID = *ptr;
+ ptr += 1;
+ *text = (const char*)ptr;
+ return cache->getGlyphIDMetrics(glyphID, x, y);
+}
+
+SkDrawCacheProc SkPaint::getDrawCacheProc() const {
+ static const SkDrawCacheProc gDrawCacheProcs[] = {
+ sk_getMetrics_utf8_00,
+ sk_getMetrics_utf16_00,
+ sk_getMetrics_utf32_00,
+ sk_getMetrics_glyph_00,
+
+ sk_getMetrics_utf8_xy,
+ sk_getMetrics_utf16_xy,
+ sk_getMetrics_utf32_xy,
+ sk_getMetrics_glyph_xy
+ };
+
+ unsigned index = this->getTextEncoding();
+ if (fFlags & kSubpixelText_Flag) {
+ index += 4;
+ }
+
+ SkASSERT(index < SK_ARRAY_COUNT(gDrawCacheProcs));
+ return gDrawCacheProcs[index];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define TEXT_AS_PATHS_PAINT_FLAGS_TO_IGNORE ( \
+SkPaint::kDevKernText_Flag | \
+SkPaint::kLinearText_Flag | \
+SkPaint::kLCDRenderText_Flag | \
+SkPaint::kEmbeddedBitmapText_Flag | \
+SkPaint::kAutoHinting_Flag | \
+SkPaint::kGenA8FromLCD_Flag )
+
+SkScalar SkPaint::setupForAsPaths() {
+ uint32_t flags = this->getFlags();
+ // clear the flags we don't care about
+ flags &= ~TEXT_AS_PATHS_PAINT_FLAGS_TO_IGNORE;
+ // set the flags we do care about
+ flags |= SkPaint::kSubpixelText_Flag;
+
+ this->setFlags(flags);
+ this->setHinting(SkPaint::kNo_Hinting);
+
+ SkScalar textSize = fTextSize;
+ this->setTextSize(kCanonicalTextSizeForPaths);
+ return textSize / kCanonicalTextSizeForPaths;
+}
+
+class SkCanonicalizePaint {
+public:
+ SkCanonicalizePaint(const SkPaint& paint) : fPaint(&paint), fScale(0) {
+ if (paint.isLinearText() || paint.tooBigToUseCache()) {
+ SkPaint* p = fLazy.set(paint);
+ fScale = p->setupForAsPaths();
+ fPaint = p;
+ }
+ }
+
+ const SkPaint& getPaint() const { return *fPaint; }
+
+ /**
+ * Returns 0 if the paint was unmodified, or the scale factor need to
+ * the original textSize
+ */
+ SkScalar getScale() const { return fScale; }
+
+private:
+ const SkPaint* fPaint;
+ SkScalar fScale;
+ SkTLazy<SkPaint> fLazy;
+};
+
+static void set_bounds(const SkGlyph& g, SkRect* bounds) {
+ bounds->set(SkIntToScalar(g.fLeft),
+ SkIntToScalar(g.fTop),
+ SkIntToScalar(g.fLeft + g.fWidth),
+ SkIntToScalar(g.fTop + g.fHeight));
+}
+
+// 64bits wide, with a 16bit bias. Useful when accumulating lots of 16.16 so
+// we don't overflow along the way
+typedef int64_t Sk48Dot16;
+
+#ifdef SK_SCALAR_IS_FLOAT
+ static inline float Sk48Dot16ToScalar(Sk48Dot16 x) {
+ return (float) (x * 1.5258789e-5); // x * (1 / 65536.0f)
+ }
+#else
+ static inline SkFixed Sk48Dot16ToScalar(Sk48Dot16 x) {
+ // just return the low 32bits
+ return static_cast<SkFixed>(x);
+ }
+#endif
+
+static void join_bounds_x(const SkGlyph& g, SkRect* bounds, Sk48Dot16 dx) {
+ SkScalar sx = Sk48Dot16ToScalar(dx);
+ bounds->join(SkIntToScalar(g.fLeft) + sx,
+ SkIntToScalar(g.fTop),
+ SkIntToScalar(g.fLeft + g.fWidth) + sx,
+ SkIntToScalar(g.fTop + g.fHeight));
+}
+
+static void join_bounds_y(const SkGlyph& g, SkRect* bounds, Sk48Dot16 dy) {
+ SkScalar sy = Sk48Dot16ToScalar(dy);
+ bounds->join(SkIntToScalar(g.fLeft),
+ SkIntToScalar(g.fTop) + sy,
+ SkIntToScalar(g.fLeft + g.fWidth),
+ SkIntToScalar(g.fTop + g.fHeight) + sy);
+}
+
+typedef void (*JoinBoundsProc)(const SkGlyph&, SkRect*, Sk48Dot16);
+
+// xyIndex is 0 for fAdvanceX or 1 for fAdvanceY
+static SkFixed advance(const SkGlyph& glyph, int xyIndex) {
+ SkASSERT(0 == xyIndex || 1 == xyIndex);
+ return (&glyph.fAdvanceX)[xyIndex];
+}
+
+SkScalar SkPaint::measure_text(SkGlyphCache* cache,
+ const char* text, size_t byteLength,
+ int* count, SkRect* bounds) const {
+ SkASSERT(count);
+ if (byteLength == 0) {
+ *count = 0;
+ if (bounds) {
+ bounds->setEmpty();
+ }
+ return 0;
+ }
+
+ SkMeasureCacheProc glyphCacheProc;
+ glyphCacheProc = this->getMeasureCacheProc(kForward_TextBufferDirection,
+ NULL != bounds);
+
+ int xyIndex;
+ JoinBoundsProc joinBoundsProc;
+ if (this->isVerticalText()) {
+ xyIndex = 1;
+ joinBoundsProc = join_bounds_y;
+ } else {
+ xyIndex = 0;
+ joinBoundsProc = join_bounds_x;
+ }
+
+ int n = 1;
+ const char* stop = (const char*)text + byteLength;
+ const SkGlyph* g = &glyphCacheProc(cache, &text);
+ // our accumulated fixed-point advances might overflow 16.16, so we use
+ // a 48.16 (64bit) accumulator, and then convert that to scalar at the
+ // very end.
+ Sk48Dot16 x = advance(*g, xyIndex);
+
+ SkAutoKern autokern;
+
+ if (NULL == bounds) {
+ if (this->isDevKernText()) {
+ int rsb;
+ for (; text < stop; n++) {
+ rsb = g->fRsbDelta;
+ g = &glyphCacheProc(cache, &text);
+ x += SkAutoKern_AdjustF(rsb, g->fLsbDelta) + advance(*g, xyIndex);
+ }
+ } else {
+ for (; text < stop; n++) {
+ x += advance(glyphCacheProc(cache, &text), xyIndex);
+ }
+ }
+ } else {
+ set_bounds(*g, bounds);
+ if (this->isDevKernText()) {
+ int rsb;
+ for (; text < stop; n++) {
+ rsb = g->fRsbDelta;
+ g = &glyphCacheProc(cache, &text);
+ x += SkAutoKern_AdjustF(rsb, g->fLsbDelta);
+ joinBoundsProc(*g, bounds, x);
+ x += advance(*g, xyIndex);
+ }
+ } else {
+ for (; text < stop; n++) {
+ g = &glyphCacheProc(cache, &text);
+ joinBoundsProc(*g, bounds, x);
+ x += advance(*g, xyIndex);
+ }
+ }
+ }
+ SkASSERT(text == stop);
+
+ *count = n;
+ return Sk48Dot16ToScalar(x);
+}
+
+SkScalar SkPaint::measureText(const void* textData, size_t length,
+ SkRect* bounds, SkScalar zoom) const {
+ const char* text = (const char*)textData;
+ SkASSERT(text != NULL || length == 0);
+
+ SkCanonicalizePaint canon(*this);
+ const SkPaint& paint = canon.getPaint();
+ SkScalar scale = canon.getScale();
+
+ SkMatrix zoomMatrix, *zoomPtr = NULL;
+ if (zoom) {
+ zoomMatrix.setScale(zoom, zoom);
+ zoomPtr = &zoomMatrix;
+ }
+
+ SkAutoGlyphCache autoCache(paint, NULL, zoomPtr);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ SkScalar width = 0;
+
+ if (length > 0) {
+ int tempCount;
+
+ width = paint.measure_text(cache, text, length, &tempCount, bounds);
+ if (scale) {
+ width = SkScalarMul(width, scale);
+ if (bounds) {
+ bounds->fLeft = SkScalarMul(bounds->fLeft, scale);
+ bounds->fTop = SkScalarMul(bounds->fTop, scale);
+ bounds->fRight = SkScalarMul(bounds->fRight, scale);
+ bounds->fBottom = SkScalarMul(bounds->fBottom, scale);
+ }
+ }
+ } else if (bounds) {
+ // ensure that even if we don't measure_text we still update the bounds
+ bounds->setEmpty();
+ }
+ return width;
+}
+
+typedef bool (*SkTextBufferPred)(const char* text, const char* stop);
+
+static bool forward_textBufferPred(const char* text, const char* stop) {
+ return text < stop;
+}
+
+static bool backward_textBufferPred(const char* text, const char* stop) {
+ return text > stop;
+}
+
+static SkTextBufferPred chooseTextBufferPred(SkPaint::TextBufferDirection tbd,
+ const char** text, size_t length,
+ const char** stop) {
+ if (SkPaint::kForward_TextBufferDirection == tbd) {
+ *stop = *text + length;
+ return forward_textBufferPred;
+ } else {
+ // text should point to the end of the buffer, and stop to the beginning
+ *stop = *text;
+ *text += length;
+ return backward_textBufferPred;
+ }
+}
+
+size_t SkPaint::breakText(const void* textD, size_t length, SkScalar maxWidth,
+ SkScalar* measuredWidth,
+ TextBufferDirection tbd) const {
+ if (0 == length || 0 >= maxWidth) {
+ if (measuredWidth) {
+ *measuredWidth = 0;
+ }
+ return 0;
+ }
+
+ if (0 == fTextSize) {
+ if (measuredWidth) {
+ *measuredWidth = 0;
+ }
+ return length;
+ }
+
+ SkASSERT(textD != NULL);
+ const char* text = (const char*)textD;
+
+ SkCanonicalizePaint canon(*this);
+ const SkPaint& paint = canon.getPaint();
+ SkScalar scale = canon.getScale();
+
+ // adjust max in case we changed the textSize in paint
+ if (scale) {
+ maxWidth /= scale;
+ }
+
+ SkAutoGlyphCache autoCache(paint, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ SkMeasureCacheProc glyphCacheProc = paint.getMeasureCacheProc(tbd, false);
+ const char* stop;
+ SkTextBufferPred pred = chooseTextBufferPred(tbd, &text, length, &stop);
+ const int xyIndex = paint.isVerticalText() ? 1 : 0;
+ // use 64bits for our accumulator, to avoid overflowing 16.16
+ Sk48Dot16 max = SkScalarToFixed(maxWidth);
+ Sk48Dot16 width = 0;
+
+ SkAutoKern autokern;
+
+ if (this->isDevKernText()) {
+ int rsb = 0;
+ while (pred(text, stop)) {
+ const char* curr = text;
+ const SkGlyph& g = glyphCacheProc(cache, &text);
+ SkFixed x = SkAutoKern_AdjustF(rsb, g.fLsbDelta) + advance(g, xyIndex);
+ if ((width += x) > max) {
+ width -= x;
+ text = curr;
+ break;
+ }
+ rsb = g.fRsbDelta;
+ }
+ } else {
+ while (pred(text, stop)) {
+ const char* curr = text;
+ SkFixed x = advance(glyphCacheProc(cache, &text), xyIndex);
+ if ((width += x) > max) {
+ width -= x;
+ text = curr;
+ break;
+ }
+ }
+ }
+
+ if (measuredWidth) {
+ SkScalar scalarWidth = Sk48Dot16ToScalar(width);
+ if (scale) {
+ scalarWidth = SkScalarMul(scalarWidth, scale);
+ }
+ *measuredWidth = scalarWidth;
+ }
+
+ // return the number of bytes measured
+ return (kForward_TextBufferDirection == tbd) ?
+ text - stop + length : stop - text + length;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool FontMetricsCacheProc(const SkGlyphCache* cache, void* context) {
+ *(SkPaint::FontMetrics*)context = cache->getFontMetrics();
+ return false; // don't detach the cache
+}
+
+static void FontMetricsDescProc(SkTypeface* typeface, const SkDescriptor* desc,
+ void* context) {
+ SkGlyphCache::VisitCache(typeface, desc, FontMetricsCacheProc, context);
+}
+
+SkScalar SkPaint::getFontMetrics(FontMetrics* metrics, SkScalar zoom) const {
+ SkCanonicalizePaint canon(*this);
+ const SkPaint& paint = canon.getPaint();
+ SkScalar scale = canon.getScale();
+
+ SkMatrix zoomMatrix, *zoomPtr = NULL;
+ if (zoom) {
+ zoomMatrix.setScale(zoom, zoom);
+ zoomPtr = &zoomMatrix;
+ }
+
+ FontMetrics storage;
+ if (NULL == metrics) {
+ metrics = &storage;
+ }
+
+ paint.descriptorProc(NULL, zoomPtr, FontMetricsDescProc, metrics, true);
+
+ if (scale) {
+ metrics->fTop = SkScalarMul(metrics->fTop, scale);
+ metrics->fAscent = SkScalarMul(metrics->fAscent, scale);
+ metrics->fDescent = SkScalarMul(metrics->fDescent, scale);
+ metrics->fBottom = SkScalarMul(metrics->fBottom, scale);
+ metrics->fLeading = SkScalarMul(metrics->fLeading, scale);
+ metrics->fAvgCharWidth = SkScalarMul(metrics->fAvgCharWidth, scale);
+ metrics->fXMin = SkScalarMul(metrics->fXMin, scale);
+ metrics->fXMax = SkScalarMul(metrics->fXMax, scale);
+ metrics->fXHeight = SkScalarMul(metrics->fXHeight, scale);
+ }
+ return metrics->fDescent - metrics->fAscent + metrics->fLeading;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void set_bounds(const SkGlyph& g, SkRect* bounds, SkScalar scale) {
+ bounds->set(g.fLeft * scale,
+ g.fTop * scale,
+ (g.fLeft + g.fWidth) * scale,
+ (g.fTop + g.fHeight) * scale);
+}
+
+int SkPaint::getTextWidths(const void* textData, size_t byteLength,
+ SkScalar widths[], SkRect bounds[]) const {
+ if (0 == byteLength) {
+ return 0;
+ }
+
+ SkASSERT(NULL != textData);
+
+ if (NULL == widths && NULL == bounds) {
+ return this->countText(textData, byteLength);
+ }
+
+ SkCanonicalizePaint canon(*this);
+ const SkPaint& paint = canon.getPaint();
+ SkScalar scale = canon.getScale();
+
+ SkAutoGlyphCache autoCache(paint, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+ SkMeasureCacheProc glyphCacheProc;
+ glyphCacheProc = paint.getMeasureCacheProc(kForward_TextBufferDirection,
+ NULL != bounds);
+
+ const char* text = (const char*)textData;
+ const char* stop = text + byteLength;
+ int count = 0;
+ const int xyIndex = paint.isVerticalText() ? 1 : 0;
+
+ if (this->isDevKernText()) {
+ // we adjust the widths returned here through auto-kerning
+ SkAutoKern autokern;
+ SkFixed prevWidth = 0;
+
+ if (scale) {
+ while (text < stop) {
+ const SkGlyph& g = glyphCacheProc(cache, &text);
+ if (widths) {
+ SkFixed adjust = autokern.adjust(g);
+
+ if (count > 0) {
+ SkScalar w = SkFixedToScalar(prevWidth + adjust);
+ *widths++ = SkScalarMul(w, scale);
+ }
+ prevWidth = advance(g, xyIndex);
+ }
+ if (bounds) {
+ set_bounds(g, bounds++, scale);
+ }
+ ++count;
+ }
+ if (count > 0 && widths) {
+ *widths = SkScalarMul(SkFixedToScalar(prevWidth), scale);
+ }
+ } else {
+ while (text < stop) {
+ const SkGlyph& g = glyphCacheProc(cache, &text);
+ if (widths) {
+ SkFixed adjust = autokern.adjust(g);
+
+ if (count > 0) {
+ *widths++ = SkFixedToScalar(prevWidth + adjust);
+ }
+ prevWidth = advance(g, xyIndex);
+ }
+ if (bounds) {
+ set_bounds(g, bounds++);
+ }
+ ++count;
+ }
+ if (count > 0 && widths) {
+ *widths = SkFixedToScalar(prevWidth);
+ }
+ }
+ } else { // no devkern
+ if (scale) {
+ while (text < stop) {
+ const SkGlyph& g = glyphCacheProc(cache, &text);
+ if (widths) {
+ *widths++ = SkScalarMul(SkFixedToScalar(advance(g, xyIndex)),
+ scale);
+ }
+ if (bounds) {
+ set_bounds(g, bounds++, scale);
+ }
+ ++count;
+ }
+ } else {
+ while (text < stop) {
+ const SkGlyph& g = glyphCacheProc(cache, &text);
+ if (widths) {
+ *widths++ = SkFixedToScalar(advance(g, xyIndex));
+ }
+ if (bounds) {
+ set_bounds(g, bounds++);
+ }
+ ++count;
+ }
+ }
+ }
+
+ SkASSERT(text == stop);
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkDraw.h"
+
+void SkPaint::getTextPath(const void* textData, size_t length,
+ SkScalar x, SkScalar y, SkPath* path) const {
+ SkASSERT(length == 0 || textData != NULL);
+
+ const char* text = (const char*)textData;
+ if (text == NULL || length == 0 || path == NULL) {
+ return;
+ }
+
+ SkTextToPathIter iter(text, length, *this, false);
+ SkMatrix matrix;
+ SkScalar prevXPos = 0;
+
+ matrix.setScale(iter.getPathScale(), iter.getPathScale());
+ matrix.postTranslate(x, y);
+ path->reset();
+
+ SkScalar xpos;
+ const SkPath* iterPath;
+ while (iter.next(&iterPath, &xpos)) {
+ matrix.postTranslate(xpos - prevXPos, 0);
+ if (iterPath) {
+ path->addPath(*iterPath, matrix);
+ }
+ prevXPos = xpos;
+ }
+}
+
+void SkPaint::getPosTextPath(const void* textData, size_t length,
+ const SkPoint pos[], SkPath* path) const {
+ SkASSERT(length == 0 || textData != NULL);
+
+ const char* text = (const char*)textData;
+ if (text == NULL || length == 0 || path == NULL) {
+ return;
+ }
+
+ SkTextToPathIter iter(text, length, *this, false);
+ SkMatrix matrix;
+ SkPoint prevPos;
+ prevPos.set(0, 0);
+
+ matrix.setScale(iter.getPathScale(), iter.getPathScale());
+ path->reset();
+
+ unsigned int i = 0;
+ const SkPath* iterPath;
+ while (iter.next(&iterPath, NULL)) {
+ matrix.postTranslate(pos[i].fX - prevPos.fX, pos[i].fY - prevPos.fY);
+ if (iterPath) {
+ path->addPath(*iterPath, matrix);
+ }
+ prevPos = pos[i];
+ i++;
+ }
+}
+
+static void add_flattenable(SkDescriptor* desc, uint32_t tag,
+ SkOrderedWriteBuffer* buffer) {
+ buffer->writeToMemory(desc->addEntry(tag, buffer->size(), NULL));
+}
+
+// SkFontHost can override this choice in FilterRec()
+static SkMask::Format computeMaskFormat(const SkPaint& paint) {
+ uint32_t flags = paint.getFlags();
+
+ // Antialiasing being disabled trumps all other settings.
+ if (!(flags & SkPaint::kAntiAlias_Flag)) {
+ return SkMask::kBW_Format;
+ }
+
+ if (flags & SkPaint::kLCDRenderText_Flag) {
+ return SkMask::kLCD16_Format;
+ }
+
+ return SkMask::kA8_Format;
+}
+
+// if linear-text is on, then we force hinting to be off (since that's sort of
+// the point of linear-text.
+static SkPaint::Hinting computeHinting(const SkPaint& paint) {
+ SkPaint::Hinting h = paint.getHinting();
+ if (paint.isLinearText()) {
+ h = SkPaint::kNo_Hinting;
+ }
+ return h;
+}
+
+// return true if the paint is just a single color (i.e. not a shader). If its
+// a shader, then we can't compute a const luminance for it :(
+static bool justAColor(const SkPaint& paint, SkColor* color) {
+ if (paint.getShader()) {
+ return false;
+ }
+ SkColor c = paint.getColor();
+ if (paint.getColorFilter()) {
+ c = paint.getColorFilter()->filterColor(c);
+ }
+ if (color) {
+ *color = c;
+ }
+ return true;
+}
+
+static SkColor computeLuminanceColor(const SkPaint& paint) {
+ SkColor c;
+ if (!justAColor(paint, &c)) {
+ c = SkColorSetRGB(0x7F, 0x80, 0x7F);
+ }
+ return c;
+}
+
+#define assert_byte(x) SkASSERT(0 == ((x) >> 8))
+
+// Beyond this size, LCD doesn't appreciably improve quality, but it always
+// cost more RAM and draws slower, so we set a cap.
+#ifndef SK_MAX_SIZE_FOR_LCDTEXT
+ #define SK_MAX_SIZE_FOR_LCDTEXT 48
+#endif
+
+static bool tooBigForLCD(const SkScalerContext::Rec& rec) {
+ SkScalar area = SkScalarMul(rec.fPost2x2[0][0], rec.fPost2x2[1][1]) -
+ SkScalarMul(rec.fPost2x2[1][0], rec.fPost2x2[0][1]);
+ SkScalar size = SkScalarMul(area, rec.fTextSize);
+ return SkScalarAbs(size) > SkIntToScalar(SK_MAX_SIZE_FOR_LCDTEXT);
+}
+
+/*
+ * Return the scalar with only limited fractional precision. Used to consolidate matrices
+ * that vary only slightly when we create our key into the font cache, since the font scaler
+ * typically returns the same looking resuts for tiny changes in the matrix.
+ */
+static SkScalar sk_relax(SkScalar x) {
+#ifdef SK_SCALAR_IS_FLOAT
+ int n = sk_float_round2int(x * 1024);
+ return n / 1024.0f;
+#else
+ // round to the nearest 10 fractional bits
+ return (x + (1 << 5)) & ~(1024 - 1);
+#endif
+}
+
+void SkScalerContext::MakeRec(const SkPaint& paint,
+ const SkDeviceProperties* deviceProperties,
+ const SkMatrix* deviceMatrix,
+ Rec* rec) {
+ SkASSERT(deviceMatrix == NULL || !deviceMatrix->hasPerspective());
+
+ SkTypeface* typeface = paint.getTypeface();
+ if (NULL == typeface) {
+ typeface = SkTypeface::GetDefaultTypeface();
+ }
+ rec->fOrigFontID = typeface->uniqueID();
+ rec->fFontID = rec->fOrigFontID;
+ rec->fTextSize = paint.getTextSize();
+ rec->fPreScaleX = paint.getTextScaleX();
+ rec->fPreSkewX = paint.getTextSkewX();
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ rec->fHintingScaleFactor = paint.getHintingScaleFactor();
+#endif
+
+ if (deviceMatrix) {
+ rec->fPost2x2[0][0] = sk_relax(deviceMatrix->getScaleX());
+ rec->fPost2x2[0][1] = sk_relax(deviceMatrix->getSkewX());
+ rec->fPost2x2[1][0] = sk_relax(deviceMatrix->getSkewY());
+ rec->fPost2x2[1][1] = sk_relax(deviceMatrix->getScaleY());
+ } else {
+ rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1;
+ rec->fPost2x2[0][1] = rec->fPost2x2[1][0] = 0;
+ }
+
+ SkPaint::Style style = paint.getStyle();
+ SkScalar strokeWidth = paint.getStrokeWidth();
+
+ unsigned flags = 0;
+
+ if (paint.isFakeBoldText()) {
+#ifdef SK_USE_FREETYPE_EMBOLDEN
+ flags |= SkScalerContext::kEmbolden_Flag;
+#else
+ SkScalar fakeBoldScale = SkScalarInterpFunc(paint.getTextSize(),
+ kStdFakeBoldInterpKeys,
+ kStdFakeBoldInterpValues,
+ kStdFakeBoldInterpLength);
+ SkScalar extra = SkScalarMul(paint.getTextSize(), fakeBoldScale);
+
+ if (style == SkPaint::kFill_Style) {
+ style = SkPaint::kStrokeAndFill_Style;
+ strokeWidth = extra; // ignore paint's strokeWidth if it was "fill"
+ } else {
+ strokeWidth += extra;
+ }
+#endif
+ }
+
+ if (paint.isDevKernText()) {
+ flags |= SkScalerContext::kDevKernText_Flag;
+ }
+
+ if (style != SkPaint::kFill_Style && strokeWidth > 0) {
+ rec->fFrameWidth = strokeWidth;
+ rec->fMiterLimit = paint.getStrokeMiter();
+ rec->fStrokeJoin = SkToU8(paint.getStrokeJoin());
+
+ if (style == SkPaint::kStrokeAndFill_Style) {
+ flags |= SkScalerContext::kFrameAndFill_Flag;
+ }
+ } else {
+ rec->fFrameWidth = 0;
+ rec->fMiterLimit = 0;
+ rec->fStrokeJoin = 0;
+ }
+
+ rec->fMaskFormat = SkToU8(computeMaskFormat(paint));
+
+ SkDeviceProperties::Geometry geometry = deviceProperties
+ ? deviceProperties->fGeometry
+ : SkDeviceProperties::Geometry::MakeDefault();
+ if (SkMask::kLCD16_Format == rec->fMaskFormat || SkMask::kLCD32_Format == rec->fMaskFormat) {
+ if (!geometry.isOrientationKnown() || !geometry.isLayoutKnown() || tooBigForLCD(*rec)) {
+ // eeek, can't support LCD
+ rec->fMaskFormat = SkMask::kA8_Format;
+ } else {
+ if (SkDeviceProperties::Geometry::kVertical_Orientation == geometry.getOrientation()) {
+ flags |= SkScalerContext::kLCD_Vertical_Flag;
+ }
+ if (SkDeviceProperties::Geometry::kBGR_Layout == geometry.getLayout()) {
+ flags |= SkScalerContext::kLCD_BGROrder_Flag;
+ }
+ }
+ }
+
+ if (paint.isEmbeddedBitmapText()) {
+ flags |= SkScalerContext::kEmbeddedBitmapText_Flag;
+ }
+ if (paint.isSubpixelText()) {
+ flags |= SkScalerContext::kSubpixelPositioning_Flag;
+ }
+ if (paint.isAutohinted()) {
+ flags |= SkScalerContext::kAutohinting_Flag;
+ }
+ if (paint.isVerticalText()) {
+ flags |= SkScalerContext::kVertical_Flag;
+ }
+ if (paint.getFlags() & SkPaint::kGenA8FromLCD_Flag) {
+ flags |= SkScalerContext::kGenA8FromLCD_Flag;
+ }
+ rec->fFlags = SkToU16(flags);
+
+ // these modify fFlags, so do them after assigning fFlags
+ rec->setHinting(computeHinting(paint));
+
+ rec->setLuminanceColor(computeLuminanceColor(paint));
+
+ if (NULL == deviceProperties) {
+ rec->setDeviceGamma(SK_GAMMA_EXPONENT);
+ rec->setPaintGamma(SK_GAMMA_EXPONENT);
+ } else {
+ rec->setDeviceGamma(deviceProperties->fGamma);
+
+ //For now always set the paint gamma equal to the device gamma.
+ //The math in SkMaskGamma can handle them being different,
+ //but it requires superluminous masks when
+ //Ex : deviceGamma(x) < paintGamma(x) and x is sufficiently large.
+ rec->setPaintGamma(deviceProperties->fGamma);
+ }
+
+#ifdef SK_GAMMA_CONTRAST
+ rec->setContrast(SK_GAMMA_CONTRAST);
+#else
+ /**
+ * A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise.
+ * With lower values small text appears washed out (though correctly so).
+ * With higher values lcd fringing is worse and the smoothing effect of
+ * partial coverage is diminished.
+ */
+ rec->setContrast(SkFloatToScalar(0.5f));
+#endif
+
+ rec->fReservedAlign = 0;
+
+ /* Allow the fonthost to modify our rec before we use it as a key into the
+ cache. This way if we're asking for something that they will ignore,
+ they can modify our rec up front, so we don't create duplicate cache
+ entries.
+ */
+ typeface->onFilterRec(rec);
+
+ // be sure to call PostMakeRec(rec) before you actually use it!
+}
+
+/**
+ * In order to call cachedDeviceLuminance, cachedPaintLuminance, or
+ * cachedMaskGamma the caller must hold the gMaskGammaCacheMutex and continue
+ * to hold it until the returned pointer is refed or forgotten.
+ */
+SK_DECLARE_STATIC_MUTEX(gMaskGammaCacheMutex);
+
+static SkMaskGamma* gLinearMaskGamma = NULL;
+static SkMaskGamma* gMaskGamma = NULL;
+static SkScalar gContrast = SK_ScalarMin;
+static SkScalar gPaintGamma = SK_ScalarMin;
+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 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;
+ }
+ if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) {
+ SkSafeUnref(gMaskGamma);
+ gMaskGamma = SkNEW_ARGS(SkMaskGamma, (contrast, paintGamma, deviceGamma));
+ gContrast = contrast;
+ gPaintGamma = paintGamma;
+ gDeviceGamma = deviceGamma;
+ }
+ return *gMaskGamma;
+}
+
+/*static*/ void SkPaint::Term() {
+ SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+
+ SkSafeUnref(gLinearMaskGamma);
+ gLinearMaskGamma = NULL;
+ SkSafeUnref(gMaskGamma);
+ gMaskGamma = NULL;
+ SkDEBUGCODE(gContrast = SK_ScalarMin;)
+ SkDEBUGCODE(gPaintGamma = SK_ScalarMin;)
+ SkDEBUGCODE(gDeviceGamma = SK_ScalarMin;)
+}
+
+/**
+ * We ensure that the rec is self-consistent and efficient (where possible)
+ */
+void SkScalerContext::PostMakeRec(const SkPaint&, SkScalerContext::Rec* rec) {
+ /**
+ * If we're asking for A8, we force the colorlum to be gray, since that
+ * limits the number of unique entries, and the scaler will only look at
+ * the lum of one of them.
+ */
+ switch (rec->fMaskFormat) {
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format: {
+ // filter down the luminance color to a finite number of bits
+ SkColor color = rec->getLuminanceColor();
+ rec->setLuminanceColor(SkMaskGamma::CanonicalColor(color));
+ break;
+ }
+ case SkMask::kA8_Format: {
+ // filter down the luminance to a single component, since A8 can't
+ // use per-component information
+
+ SkColor color = rec->getLuminanceColor();
+ U8CPU lum = SkColorSpaceLuminance::computeLuminance(rec->getPaintGamma(), color);
+ //If we are asked to look like LCD, look like LCD.
+ if (!(rec->fFlags & SkScalerContext::kGenA8FromLCD_Flag)) {
+ // HACK: Prevents green from being pre-blended as white.
+ lum -= ((255 - lum) * lum) / 255;
+ }
+
+ // reduce to our finite number of bits
+ color = SkColorSetRGB(lum, lum, lum);
+ rec->setLuminanceColor(SkMaskGamma::CanonicalColor(color));
+ break;
+ }
+ case SkMask::kBW_Format:
+ // No need to differentiate gamma if we're BW
+ rec->ignorePreBlend();
+ break;
+ }
+}
+
+#define MIN_SIZE_FOR_EFFECT_BUFFER 1024
+
+#ifdef SK_DEBUG
+ #define TEST_DESC
+#endif
+
+/*
+ * ignoreGamma tells us that the caller just wants metrics that are unaffected
+ * by gamma correction, so we jam the luminance field to 0 (most common value
+ * for black text) in hopes that we get a cache hit easier. A better solution
+ * would be for the fontcache lookup to know to ignore the luminance field
+ * entirely, but not sure how to do that and keep it fast.
+ */
+void SkPaint::descriptorProc(const SkDeviceProperties* deviceProperties,
+ const SkMatrix* deviceMatrix,
+ void (*proc)(SkTypeface*, const SkDescriptor*, void*),
+ void* context, bool ignoreGamma) const {
+ SkScalerContext::Rec rec;
+
+ SkScalerContext::MakeRec(*this, deviceProperties, deviceMatrix, &rec);
+ if (ignoreGamma) {
+ rec.setLuminanceColor(0);
+ }
+
+ size_t descSize = sizeof(rec);
+ int entryCount = 1;
+ SkPathEffect* pe = this->getPathEffect();
+ SkMaskFilter* mf = this->getMaskFilter();
+ SkRasterizer* ra = this->getRasterizer();
+
+ SkOrderedWriteBuffer peBuffer(MIN_SIZE_FOR_EFFECT_BUFFER);
+ SkOrderedWriteBuffer mfBuffer(MIN_SIZE_FOR_EFFECT_BUFFER);
+ SkOrderedWriteBuffer raBuffer(MIN_SIZE_FOR_EFFECT_BUFFER);
+
+ if (pe) {
+ peBuffer.writeFlattenable(pe);
+ descSize += peBuffer.size();
+ entryCount += 1;
+ rec.fMaskFormat = SkMask::kA8_Format; // force antialiasing when we do the scan conversion
+ // seems like we could support kLCD as well at this point...
+ }
+ if (mf) {
+ mfBuffer.writeFlattenable(mf);
+ 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);
+ descSize += raBuffer.size();
+ entryCount += 1;
+ rec.fMaskFormat = SkMask::kA8_Format; // force antialiasing when we do the scan conversion
+ }
+
+#ifdef SK_BUILD_FOR_ANDROID
+ SkOrderedWriteBuffer androidBuffer(128);
+ fPaintOptionsAndroid.flatten(androidBuffer);
+ descSize += androidBuffer.size();
+ entryCount += 1;
+#endif
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Now that we're done tweaking the rec, call the PostMakeRec cleanup
+ SkScalerContext::PostMakeRec(*this, &rec);
+
+ descSize += SkDescriptor::ComputeOverhead(entryCount);
+
+ SkAutoDescriptor ad(descSize);
+ SkDescriptor* desc = ad.getDesc();
+
+ desc->init();
+ desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+
+#ifdef SK_BUILD_FOR_ANDROID
+ add_flattenable(desc, kAndroidOpts_SkDescriptorTag, &androidBuffer);
+#endif
+
+ if (pe) {
+ add_flattenable(desc, kPathEffect_SkDescriptorTag, &peBuffer);
+ }
+ if (mf) {
+ add_flattenable(desc, kMaskFilter_SkDescriptorTag, &mfBuffer);
+ }
+ if (ra) {
+ add_flattenable(desc, kRasterizer_SkDescriptorTag, &raBuffer);
+ }
+
+ SkASSERT(descSize == desc->getLength());
+ desc->computeChecksum();
+
+#ifdef TEST_DESC
+ {
+ // Check that we completely write the bytes in desc (our key), and that
+ // there are no uninitialized bytes. If there were, then we would get
+ // false-misses (or worse, false-hits) in our fontcache.
+ //
+ // We do this buy filling 2 others, one with 0s and the other with 1s
+ // and create those, and then check that all 3 are identical.
+ SkAutoDescriptor ad1(descSize);
+ SkAutoDescriptor ad2(descSize);
+ SkDescriptor* desc1 = ad1.getDesc();
+ SkDescriptor* desc2 = ad2.getDesc();
+
+ memset(desc1, 0x00, descSize);
+ memset(desc2, 0xFF, descSize);
+
+ desc1->init();
+ desc2->init();
+ desc1->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+ desc2->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
+
+#ifdef SK_BUILD_FOR_ANDROID
+ add_flattenable(desc1, kAndroidOpts_SkDescriptorTag, &androidBuffer);
+ add_flattenable(desc2, kAndroidOpts_SkDescriptorTag, &androidBuffer);
+#endif
+
+ if (pe) {
+ add_flattenable(desc1, kPathEffect_SkDescriptorTag, &peBuffer);
+ add_flattenable(desc2, kPathEffect_SkDescriptorTag, &peBuffer);
+ }
+ if (mf) {
+ add_flattenable(desc1, kMaskFilter_SkDescriptorTag, &mfBuffer);
+ add_flattenable(desc2, kMaskFilter_SkDescriptorTag, &mfBuffer);
+ }
+ if (ra) {
+ add_flattenable(desc1, kRasterizer_SkDescriptorTag, &raBuffer);
+ add_flattenable(desc2, kRasterizer_SkDescriptorTag, &raBuffer);
+ }
+
+ SkASSERT(descSize == desc1->getLength());
+ SkASSERT(descSize == desc2->getLength());
+ desc1->computeChecksum();
+ desc2->computeChecksum();
+ SkASSERT(!memcmp(desc, desc1, descSize));
+ SkASSERT(!memcmp(desc, desc2, descSize));
+ }
+#endif
+
+ proc(fTypeface, desc, context);
+}
+
+SkGlyphCache* SkPaint::detachCache(const SkDeviceProperties* deviceProperties,
+ const SkMatrix* deviceMatrix) const {
+ SkGlyphCache* cache;
+ this->descriptorProc(deviceProperties, deviceMatrix, DetachDescProc, &cache, false);
+ return cache;
+}
+
+/**
+ * Expands fDeviceGamma, fPaintGamma, fContrast, and fLumBits into a mask pre-blend.
+ */
+//static
+SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContext::Rec& rec) {
+ SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+ const SkMaskGamma& maskGamma = cachedMaskGamma(rec.getContrast(),
+ rec.getPaintGamma(),
+ rec.getDeviceGamma());
+ return maskGamma.preBlend(rec.getLuminanceColor());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkStream.h"
+
+static uintptr_t asint(const void* p) {
+ return reinterpret_cast<uintptr_t>(p);
+}
+
+union Scalar32 {
+ SkScalar fScalar;
+ uint32_t f32;
+};
+
+static uint32_t* write_scalar(uint32_t* ptr, SkScalar value) {
+ SkASSERT(sizeof(SkScalar) == sizeof(uint32_t));
+ Scalar32 tmp;
+ tmp.fScalar = value;
+ *ptr = tmp.f32;
+ return ptr + 1;
+}
+
+static SkScalar read_scalar(const uint32_t*& ptr) {
+ SkASSERT(sizeof(SkScalar) == sizeof(uint32_t));
+ Scalar32 tmp;
+ tmp.f32 = *ptr++;
+ return tmp.fScalar;
+}
+
+static uint32_t pack_4(unsigned a, unsigned b, unsigned c, unsigned d) {
+ SkASSERT(a == (uint8_t)a);
+ SkASSERT(b == (uint8_t)b);
+ SkASSERT(c == (uint8_t)c);
+ SkASSERT(d == (uint8_t)d);
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+enum FlatFlags {
+ kHasTypeface_FlatFlag = 0x01,
+ kHasEffects_FlatFlag = 0x02,
+};
+
+// The size of a flat paint's POD fields
+// Include an SkScalar for hinting scale factor whether it is
+// supported or not so that an SKP is valid whether it was
+// created with support or not.
+
+static const uint32_t kPODPaintSize = 6 * sizeof(SkScalar) +
+ 1 * sizeof(SkColor) +
+ 1 * sizeof(uint16_t) +
+ 6 * sizeof(uint8_t);
+
+/* To save space/time, we analyze the paint, and write a truncated version of
+ it if there are not tricky elements like shaders, etc.
+ */
+void SkPaint::flatten(SkFlattenableWriteBuffer& buffer) const {
+ uint8_t flatFlags = 0;
+ if (this->getTypeface()) {
+ flatFlags |= kHasTypeface_FlatFlag;
+ }
+ if (asint(this->getPathEffect()) |
+ asint(this->getShader()) |
+ asint(this->getXfermode()) |
+ asint(this->getMaskFilter()) |
+ asint(this->getColorFilter()) |
+ asint(this->getRasterizer()) |
+ asint(this->getLooper()) |
+ asint(this->getAnnotation()) |
+ asint(this->getImageFilter())) {
+ flatFlags |= kHasEffects_FlatFlag;
+ }
+
+
+ if (buffer.isOrderedBinaryBuffer()) {
+ SkASSERT(SkAlign4(kPODPaintSize) == kPODPaintSize);
+ uint32_t* ptr = buffer.getOrderedBinaryBuffer()->reserve(kPODPaintSize);
+
+ ptr = write_scalar(ptr, this->getTextSize());
+ ptr = write_scalar(ptr, this->getTextScaleX());
+ ptr = write_scalar(ptr, this->getTextSkewX());
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ ptr = write_scalar(ptr, this->getHintingScaleFactor());
+#else
+ // Dummy value.
+ ptr = write_scalar(ptr, SK_Scalar1);
+#endif
+ ptr = write_scalar(ptr, this->getStrokeWidth());
+ ptr = write_scalar(ptr, this->getStrokeMiter());
+ *ptr++ = this->getColor();
+ // previously flags:16, textAlign:8, flatFlags:8
+ // now flags:16, hinting:4, textAlign:4, flatFlags:8
+ *ptr++ = (this->getFlags() << 16) |
+ // hinting added later. 0 in this nibble means use the default.
+ ((this->getHinting()+1) << 12) |
+ (this->getTextAlign() << 8) |
+ flatFlags;
+ *ptr++ = pack_4(this->getStrokeCap(), this->getStrokeJoin(),
+ this->getStyle(), this->getTextEncoding());
+ } else {
+ buffer.writeScalar(fTextSize);
+ buffer.writeScalar(fTextScaleX);
+ buffer.writeScalar(fTextSkewX);
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ buffer.writeScalar(fHintingScaleFactor);
+#else
+ // Dummy value.
+ buffer.writeScalar(SK_Scalar1);
+#endif
+ buffer.writeScalar(fWidth);
+ buffer.writeScalar(fMiterLimit);
+ buffer.writeColor(fColor);
+ buffer.writeUInt(fFlags);
+ buffer.writeUInt(fHinting);
+ buffer.writeUInt(fTextAlign);
+ buffer.writeUInt(flatFlags);
+
+ buffer.writeUInt(fCapType);
+ buffer.writeUInt(fJoinType);
+ buffer.writeUInt(fStyle);
+ buffer.writeUInt(fTextEncoding);
+ }
+
+ // now we're done with ptr and the (pre)reserved space. If we need to write
+ // additional fields, use the buffer directly
+ if (flatFlags & kHasTypeface_FlatFlag) {
+ buffer.writeTypeface(this->getTypeface());
+ }
+ if (flatFlags & kHasEffects_FlatFlag) {
+ buffer.writeFlattenable(this->getPathEffect());
+ buffer.writeFlattenable(this->getShader());
+ buffer.writeFlattenable(this->getXfermode());
+ buffer.writeFlattenable(this->getMaskFilter());
+ buffer.writeFlattenable(this->getColorFilter());
+ buffer.writeFlattenable(this->getRasterizer());
+ buffer.writeFlattenable(this->getLooper());
+ buffer.writeFlattenable(this->getImageFilter());
+ buffer.writeFlattenable(this->getAnnotation());
+ }
+}
+
+void SkPaint::unflatten(SkFlattenableReadBuffer& buffer) {
+ fPrivFlags = 0;
+
+ uint8_t flatFlags = 0;
+ if (buffer.isOrderedBinaryBuffer()) {
+ SkASSERT(SkAlign4(kPODPaintSize) == kPODPaintSize);
+ const void* podData = buffer.getOrderedBinaryBuffer()->skip(kPODPaintSize);
+ const uint32_t* pod = reinterpret_cast<const uint32_t*>(podData);
+
+ // the order we read must match the order we wrote in flatten()
+ this->setTextSize(read_scalar(pod));
+ this->setTextScaleX(read_scalar(pod));
+ this->setTextSkewX(read_scalar(pod));
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ this->setHintingScaleFactor(read_scalar(pod));
+#else
+ // Skip the hinting scalar factor, which is not supported.
+ read_scalar(pod);
+#endif
+ this->setStrokeWidth(read_scalar(pod));
+ this->setStrokeMiter(read_scalar(pod));
+ this->setColor(*pod++);
+
+ // previously flags:16, textAlign:8, flatFlags:8
+ // now flags:16, hinting:4, textAlign:4, flatFlags:8
+ uint32_t tmp = *pod++;
+ this->setFlags(tmp >> 16);
+
+ // hinting added later. 0 in this nibble means use the default.
+ uint32_t hinting = (tmp >> 12) & 0xF;
+ this->setHinting(0 == hinting ? kNormal_Hinting : static_cast<Hinting>(hinting-1));
+
+ this->setTextAlign(static_cast<Align>((tmp >> 8) & 0xF));
+
+ flatFlags = tmp & 0xFF;
+
+ tmp = *pod++;
+ this->setStrokeCap(static_cast<Cap>((tmp >> 24) & 0xFF));
+ this->setStrokeJoin(static_cast<Join>((tmp >> 16) & 0xFF));
+ this->setStyle(static_cast<Style>((tmp >> 8) & 0xFF));
+ this->setTextEncoding(static_cast<TextEncoding>((tmp >> 0) & 0xFF));
+ } else {
+ this->setTextSize(buffer.readScalar());
+ this->setTextScaleX(buffer.readScalar());
+ this->setTextSkewX(buffer.readScalar());
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ this->setHintingScaleFactor(buffer.readScalar());
+#else
+ // Skip the hinting scalar factor, which is not supported.
+ buffer.readScalar();
+#endif
+ this->setStrokeWidth(buffer.readScalar());
+ this->setStrokeMiter(buffer.readScalar());
+ this->setColor(buffer.readColor());
+ this->setFlags(buffer.readUInt());
+ this->setHinting(static_cast<SkPaint::Hinting>(buffer.readUInt()));
+ this->setTextAlign(static_cast<SkPaint::Align>(buffer.readUInt()));
+ flatFlags = buffer.readUInt();
+
+ this->setStrokeCap(static_cast<SkPaint::Cap>(buffer.readUInt()));
+ this->setStrokeJoin(static_cast<SkPaint::Join>(buffer.readUInt()));
+ this->setStyle(static_cast<SkPaint::Style>(buffer.readUInt()));
+ this->setTextEncoding(static_cast<SkPaint::TextEncoding>(buffer.readUInt()));
+ }
+
+ if (flatFlags & kHasTypeface_FlatFlag) {
+ this->setTypeface(buffer.readTypeface());
+ } else {
+ this->setTypeface(NULL);
+ }
+
+ if (flatFlags & kHasEffects_FlatFlag) {
+ SkSafeUnref(this->setPathEffect(buffer.readFlattenableT<SkPathEffect>()));
+ SkSafeUnref(this->setShader(buffer.readFlattenableT<SkShader>()));
+ SkSafeUnref(this->setXfermode(buffer.readFlattenableT<SkXfermode>()));
+ SkSafeUnref(this->setMaskFilter(buffer.readFlattenableT<SkMaskFilter>()));
+ SkSafeUnref(this->setColorFilter(buffer.readFlattenableT<SkColorFilter>()));
+ SkSafeUnref(this->setRasterizer(buffer.readFlattenableT<SkRasterizer>()));
+ SkSafeUnref(this->setLooper(buffer.readFlattenableT<SkDrawLooper>()));
+ SkSafeUnref(this->setImageFilter(buffer.readFlattenableT<SkImageFilter>()));
+ SkSafeUnref(this->setAnnotation(buffer.readFlattenableT<SkAnnotation>()));
+ } else {
+ this->setPathEffect(NULL);
+ this->setShader(NULL);
+ this->setXfermode(NULL);
+ this->setMaskFilter(NULL);
+ this->setColorFilter(NULL);
+ this->setRasterizer(NULL);
+ this->setLooper(NULL);
+ this->setImageFilter(NULL);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkShader* SkPaint::setShader(SkShader* shader) {
+ GEN_ID_INC_EVAL(shader != fShader);
+ SkRefCnt_SafeAssign(fShader, shader);
+ return shader;
+}
+
+SkColorFilter* SkPaint::setColorFilter(SkColorFilter* filter) {
+ GEN_ID_INC_EVAL(filter != fColorFilter);
+ SkRefCnt_SafeAssign(fColorFilter, filter);
+ return filter;
+}
+
+SkXfermode* SkPaint::setXfermode(SkXfermode* mode) {
+ GEN_ID_INC_EVAL(mode != fXfermode);
+ SkRefCnt_SafeAssign(fXfermode, mode);
+ return mode;
+}
+
+SkXfermode* SkPaint::setXfermodeMode(SkXfermode::Mode mode) {
+ SkSafeUnref(fXfermode);
+ fXfermode = SkXfermode::Create(mode);
+ GEN_ID_INC;
+ return fXfermode;
+}
+
+SkPathEffect* SkPaint::setPathEffect(SkPathEffect* effect) {
+ GEN_ID_INC_EVAL(effect != fPathEffect);
+ SkRefCnt_SafeAssign(fPathEffect, effect);
+ return effect;
+}
+
+SkMaskFilter* SkPaint::setMaskFilter(SkMaskFilter* filter) {
+ GEN_ID_INC_EVAL(filter != fMaskFilter);
+ SkRefCnt_SafeAssign(fMaskFilter, filter);
+ return filter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkPaint::getFillPath(const SkPath& src, SkPath* dst,
+ const SkRect* cullRect) const {
+ SkStrokeRec rec(*this);
+
+ const SkPath* srcPtr = &src;
+ SkPath tmpPath;
+
+ if (fPathEffect && fPathEffect->filterPath(&tmpPath, src, &rec, cullRect)) {
+ srcPtr = &tmpPath;
+ }
+
+ if (!rec.applyToPath(dst, *srcPtr)) {
+ if (srcPtr == &tmpPath) {
+ // If path's were copy-on-write, this trick would not be needed.
+ // As it is, we want to save making a deep-copy from tmpPath -> dst
+ // since we know we're just going to delete tmpPath when we return,
+ // so the swap saves that copy.
+ dst->swap(tmpPath);
+ } else {
+ *dst = *srcPtr;
+ }
+ }
+ return !rec.isHairlineStyle();
+}
+
+const SkRect& SkPaint::doComputeFastBounds(const SkRect& origSrc,
+ SkRect* storage,
+ Style style) const {
+ SkASSERT(storage);
+
+ const SkRect* src = &origSrc;
+
+ if (this->getLooper()) {
+ SkASSERT(this->getLooper()->canComputeFastBounds(*this));
+ this->getLooper()->computeFastBounds(*this, *src, storage);
+ return *storage;
+ }
+
+ SkRect tmpSrc;
+ if (this->getPathEffect()) {
+ this->getPathEffect()->computeFastBounds(&tmpSrc, origSrc);
+ src = &tmpSrc;
+ }
+
+ if (kFill_Style != style) {
+ // since we're stroked, outset the rect by the radius (and join type)
+ SkScalar radius = SkScalarHalf(this->getStrokeWidth());
+ if (0 == radius) { // hairline
+ radius = SK_Scalar1;
+ } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) {
+ SkScalar scale = this->getStrokeMiter();
+ if (scale > SK_Scalar1) {
+ radius = SkScalarMul(radius, scale);
+ }
+ }
+ storage->set(src->fLeft - radius, src->fTop - radius,
+ src->fRight + radius, src->fBottom + radius);
+ } else {
+ *storage = *src;
+ }
+
+ if (this->getMaskFilter()) {
+ this->getMaskFilter()->computeFastBounds(*storage, storage);
+ }
+
+ return *storage;
+}
+
+#ifdef SK_DEVELOPER
+void SkPaint::toString(SkString* str) const {
+ str->append("<dl><dt>SkPaint:</dt><dd><dl>");
+
+ SkTypeface* typeface = this->getTypeface();
+ if (NULL != typeface) {
+ SkDynamicMemoryWStream ostream;
+ typeface->serialize(&ostream);
+ SkAutoTUnref<SkData> data(ostream.copyToData());
+
+ SkMemoryStream stream(data);
+ SkFontDescriptor descriptor(&stream);
+
+ str->append("<dt>Font Family Name:</dt><dd>");
+ str->append(descriptor.getFamilyName());
+ str->append("</dd><dt>Font Full Name:</dt><dd>");
+ str->append(descriptor.getFullName());
+ str->append("</dd><dt>Font PS Name:</dt><dd>");
+ str->append(descriptor.getPostscriptName());
+ str->append("</dd><dt>Font File Name:</dt><dd>");
+ str->append(descriptor.getFontFileName());
+ str->append("</dd>");
+ }
+
+ str->append("<dt>TextSize:</dt><dd>");
+ str->appendScalar(this->getTextSize());
+ str->append("</dd>");
+
+ str->append("<dt>TextScaleX:</dt><dd>");
+ str->appendScalar(this->getTextScaleX());
+ str->append("</dd>");
+
+ str->append("<dt>TextSkewX:</dt><dd>");
+ str->appendScalar(this->getTextSkewX());
+ str->append("</dd>");
+
+ SkPathEffect* pathEffect = this->getPathEffect();
+ if (NULL != pathEffect) {
+ str->append("<dt>PathEffect:</dt><dd>");
+ str->append("</dd>");
+ }
+
+ SkShader* shader = this->getShader();
+ if (NULL != shader) {
+ str->append("<dt>Shader:</dt><dd>");
+ shader->toString(str);
+ str->append("</dd>");
+ }
+
+ SkXfermode* xfer = this->getXfermode();
+ if (NULL != xfer) {
+ str->append("<dt>Xfermode:</dt><dd>");
+ xfer->toString(str);
+ str->append("</dd>");
+ }
+
+ SkMaskFilter* maskFilter = this->getMaskFilter();
+ if (NULL != maskFilter) {
+ str->append("<dt>MaskFilter:</dt><dd>");
+ maskFilter->toString(str);
+ str->append("</dd>");
+ }
+
+ SkColorFilter* colorFilter = this->getColorFilter();
+ if (NULL != colorFilter) {
+ str->append("<dt>ColorFilter:</dt><dd>");
+ colorFilter->toString(str);
+ str->append("</dd>");
+ }
+
+ SkRasterizer* rasterizer = this->getRasterizer();
+ if (NULL != rasterizer) {
+ str->append("<dt>Rasterizer:</dt><dd>");
+ str->append("</dd>");
+ }
+
+ SkDrawLooper* looper = this->getLooper();
+ if (NULL != looper) {
+ str->append("<dt>DrawLooper:</dt><dd>");
+ looper->toString(str);
+ str->append("</dd>");
+ }
+
+ SkImageFilter* imageFilter = this->getImageFilter();
+ if (NULL != imageFilter) {
+ str->append("<dt>ImageFilter:</dt><dd>");
+ str->append("</dd>");
+ }
+
+ SkAnnotation* annotation = this->getAnnotation();
+ if (NULL != annotation) {
+ str->append("<dt>Annotation:</dt><dd>");
+ str->append("</dd>");
+ }
+
+ str->append("<dt>Color:</dt><dd>0x");
+ SkColor color = this->getColor();
+ str->appendHex(color);
+ str->append("</dd>");
+
+ str->append("<dt>Stroke Width:</dt><dd>");
+ str->appendScalar(this->getStrokeWidth());
+ str->append("</dd>");
+
+ str->append("<dt>Stroke Miter:</dt><dd>");
+ str->appendScalar(this->getStrokeMiter());
+ str->append("</dd>");
+
+ str->append("<dt>Flags:</dt><dd>(");
+ if (this->getFlags()) {
+ bool needSeparator = false;
+ SkAddFlagToString(str, this->isAntiAlias(), "AntiAlias", &needSeparator);
+ SkAddFlagToString(str, this->isFilterBitmap(), "FilterBitmap", &needSeparator);
+ SkAddFlagToString(str, this->isDither(), "Dither", &needSeparator);
+ SkAddFlagToString(str, this->isUnderlineText(), "UnderlineText", &needSeparator);
+ SkAddFlagToString(str, this->isStrikeThruText(), "StrikeThruText", &needSeparator);
+ SkAddFlagToString(str, this->isFakeBoldText(), "FakeBoldText", &needSeparator);
+ SkAddFlagToString(str, this->isLinearText(), "LinearText", &needSeparator);
+ SkAddFlagToString(str, this->isSubpixelText(), "SubpixelText", &needSeparator);
+ SkAddFlagToString(str, this->isDevKernText(), "DevKernText", &needSeparator);
+ SkAddFlagToString(str, this->isLCDRenderText(), "LCDRenderText", &needSeparator);
+ SkAddFlagToString(str, this->isEmbeddedBitmapText(),
+ "EmbeddedBitmapText", &needSeparator);
+ SkAddFlagToString(str, this->isAutohinted(), "Autohinted", &needSeparator);
+ SkAddFlagToString(str, this->isVerticalText(), "VerticalText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(this->getFlags() & SkPaint::kGenA8FromLCD_Flag),
+ "GenA8FromLCD", &needSeparator);
+ } else {
+ str->append("None");
+ }
+ str->append(")</dd>");
+
+ str->append("<dt>TextAlign:</dt><dd>");
+ static const char* gTextAlignStrings[SkPaint::kAlignCount] = { "Left", "Center", "Right" };
+ str->append(gTextAlignStrings[this->getTextAlign()]);
+ str->append("</dd>");
+
+ str->append("<dt>CapType:</dt><dd>");
+ static const char* gStrokeCapStrings[SkPaint::kCapCount] = { "Butt", "Round", "Square" };
+ str->append(gStrokeCapStrings[this->getStrokeCap()]);
+ str->append("</dd>");
+
+ str->append("<dt>JoinType:</dt><dd>");
+ static const char* gJoinStrings[SkPaint::kJoinCount] = { "Miter", "Round", "Bevel" };
+ str->append(gJoinStrings[this->getStrokeJoin()]);
+ str->append("</dd>");
+
+ str->append("<dt>Style:</dt><dd>");
+ static const char* gStyleStrings[SkPaint::kStyleCount] = { "Fill", "Stroke", "StrokeAndFill" };
+ str->append(gStyleStrings[this->getStyle()]);
+ str->append("</dd>");
+
+ str->append("<dt>TextEncoding:</dt><dd>");
+ static const char* gTextEncodingStrings[] = { "UTF8", "UTF16", "UTF32", "GlyphID" };
+ str->append(gTextEncodingStrings[this->getTextEncoding()]);
+ str->append("</dd>");
+
+ str->append("<dt>Hinting:</dt><dd>");
+ static const char* gHintingStrings[] = { "None", "Slight", "Normal", "Full" };
+ str->append(gHintingStrings[this->getHinting()]);
+ str->append("</dd>");
+
+ str->append("</dd></dl></dl>");
+}
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool has_thick_frame(const SkPaint& paint) {
+ return paint.getStrokeWidth() > 0 &&
+ paint.getStyle() != SkPaint::kFill_Style;
+}
+
+SkTextToPathIter::SkTextToPathIter( const char text[], size_t length,
+ const SkPaint& paint,
+ bool applyStrokeAndPathEffects)
+ : fPaint(paint) {
+ fGlyphCacheProc = paint.getMeasureCacheProc(SkPaint::kForward_TextBufferDirection,
+ true);
+
+ fPaint.setLinearText(true);
+ fPaint.setMaskFilter(NULL); // don't want this affecting our path-cache lookup
+
+ if (fPaint.getPathEffect() == NULL && !has_thick_frame(fPaint)) {
+ applyStrokeAndPathEffects = false;
+ }
+
+ // can't use our canonical size if we need to apply patheffects
+ if (fPaint.getPathEffect() == NULL) {
+ fPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
+ fScale = paint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
+ if (has_thick_frame(fPaint)) {
+ fPaint.setStrokeWidth(SkScalarDiv(fPaint.getStrokeWidth(), fScale));
+ }
+ } else {
+ fScale = SK_Scalar1;
+ }
+
+ if (!applyStrokeAndPathEffects) {
+ fPaint.setStyle(SkPaint::kFill_Style);
+ fPaint.setPathEffect(NULL);
+ }
+
+ fCache = fPaint.detachCache(NULL, NULL);
+
+ SkPaint::Style style = SkPaint::kFill_Style;
+ SkPathEffect* pe = NULL;
+
+ if (!applyStrokeAndPathEffects) {
+ style = paint.getStyle(); // restore
+ pe = paint.getPathEffect(); // restore
+ }
+ fPaint.setStyle(style);
+ fPaint.setPathEffect(pe);
+ fPaint.setMaskFilter(paint.getMaskFilter()); // restore
+
+ // now compute fXOffset if needed
+
+ SkScalar xOffset = 0;
+ if (paint.getTextAlign() != SkPaint::kLeft_Align) { // need to measure first
+ int count;
+ SkScalar width = SkScalarMul(fPaint.measure_text(fCache, text, length,
+ &count, NULL), fScale);
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ width = SkScalarHalf(width);
+ }
+ xOffset = -width;
+ }
+ fXPos = xOffset;
+ fPrevAdvance = 0;
+
+ fText = text;
+ fStop = text + length;
+
+ fXYIndex = paint.isVerticalText() ? 1 : 0;
+}
+
+SkTextToPathIter::~SkTextToPathIter() {
+ SkGlyphCache::AttachCache(fCache);
+}
+
+bool SkTextToPathIter::next(const SkPath** path, SkScalar* xpos) {
+ if (fText < fStop) {
+ const SkGlyph& glyph = fGlyphCacheProc(fCache, &fText);
+
+ fXPos += SkScalarMul(SkFixedToScalar(fPrevAdvance + fAutoKern.adjust(glyph)), fScale);
+ fPrevAdvance = advance(glyph, fXYIndex); // + fPaint.getTextTracking();
+
+ if (glyph.fWidth) {
+ if (path) {
+ *path = fCache->findPath(glyph);
+ }
+ } else {
+ if (path) {
+ *path = NULL;
+ }
+ }
+ if (xpos) {
+ *xpos = fXPos;
+ }
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkPaint::nothingToDraw() const {
+ if (fLooper) {
+ return false;
+ }
+ SkXfermode::Mode mode;
+ if (SkXfermode::AsMode(fXfermode, &mode)) {
+ switch (mode) {
+ case SkXfermode::kSrcOver_Mode:
+ case SkXfermode::kSrcATop_Mode:
+ case SkXfermode::kDstOut_Mode:
+ case SkXfermode::kDstOver_Mode:
+ case SkXfermode::kPlus_Mode:
+ return 0 == this->getAlpha();
+ case SkXfermode::kDst_Mode:
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
diff --git a/core/SkPaintDefaults.h b/core/SkPaintDefaults.h
new file mode 100644
index 00000000..3ea1cd30
--- /dev/null
+++ b/core/SkPaintDefaults.h
@@ -0,0 +1,35 @@
+/*
+ * 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 SkPaintDefaults_DEFINED
+#define SkPaintDefaults_DEFINED
+
+#include "SkPaint.h"
+
+/**
+ * Any of these can be specified by the build system (or SkUserConfig.h)
+ * to change the default values for a SkPaint. This file should not be
+ * edited directly.
+ */
+
+#ifndef SkPaintDefaults_Flags
+ #define SkPaintDefaults_Flags 0
+#endif
+
+#ifndef SkPaintDefaults_TextSize
+ #define SkPaintDefaults_TextSize SkIntToScalar(12)
+#endif
+
+#ifndef SkPaintDefaults_Hinting
+ #define SkPaintDefaults_Hinting SkPaint::kNormal_Hinting
+#endif
+
+#ifndef SkPaintDefaults_MiterLimit
+ #define SkPaintDefaults_MiterLimit SkIntToScalar(4)
+#endif
+
+#endif
diff --git a/core/SkPaintOptionsAndroid.cpp b/core/SkPaintOptionsAndroid.cpp
new file mode 100644
index 00000000..31f489cb
--- /dev/null
+++ b/core/SkPaintOptionsAndroid.cpp
@@ -0,0 +1,44 @@
+
+/*
+ * 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 "SkPaintOptionsAndroid.h"
+#include "SkFlattenableBuffers.h"
+#include "SkTDict.h"
+#include "SkThread.h"
+#include <cstring>
+
+#ifdef SK_BUILD_FOR_ANDROID
+
+SkLanguage SkLanguage::getParent() const {
+ SkASSERT(!fTag.isEmpty());
+ const char* tag = fTag.c_str();
+
+ // strip off the rightmost "-.*"
+ char* parentTagEnd = strrchr(tag, '-');
+ if (parentTagEnd == NULL) {
+ return SkLanguage();
+ }
+ size_t parentTagLen = parentTagEnd - tag;
+ return SkLanguage(tag, parentTagLen);
+}
+
+void SkPaintOptionsAndroid::flatten(SkFlattenableWriteBuffer& buffer) const {
+ buffer.writeUInt(fFontVariant);
+ buffer.writeString(fLanguage.getTag().c_str());
+ buffer.writeBool(fUseFontFallbacks);
+}
+
+void SkPaintOptionsAndroid::unflatten(SkFlattenableReadBuffer& buffer) {
+ fFontVariant = (FontVariant)buffer.readUInt();
+ SkString tag;
+ buffer.readString(&tag);
+ fLanguage = SkLanguage(tag);
+ fUseFontFallbacks = buffer.readBool();
+}
+
+#endif
diff --git a/core/SkPaintPriv.cpp b/core/SkPaintPriv.cpp
new file mode 100644
index 00000000..ce053890
--- /dev/null
+++ b/core/SkPaintPriv.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPaintPriv.h"
+
+#include "SkBitmap.h"
+#include "SkColorFilter.h"
+#include "SkPaint.h"
+#include "SkShader.h"
+
+bool isPaintOpaque(const SkPaint* paint,
+ const SkBitmap* bmpReplacesShader) {
+ // TODO: SkXfermode should have a virtual isOpaque method, which would
+ // make it possible to test modes that do not have a Coeff representation.
+
+ if (!paint) {
+ return bmpReplacesShader ? bmpReplacesShader->isOpaque() : true;
+ }
+
+ SkXfermode::Coeff srcCoeff, dstCoeff;
+ if (SkXfermode::AsCoeff(paint->getXfermode(), &srcCoeff, &dstCoeff)){
+ if (SkXfermode::kDA_Coeff == srcCoeff || SkXfermode::kDC_Coeff == srcCoeff ||
+ SkXfermode::kIDA_Coeff == srcCoeff || SkXfermode::kIDC_Coeff == srcCoeff) {
+ return false;
+ }
+ switch (dstCoeff) {
+ case SkXfermode::kZero_Coeff:
+ return true;
+ case SkXfermode::kISA_Coeff:
+ if (paint->getAlpha() != 255) {
+ break;
+ }
+ if (bmpReplacesShader) {
+ if (!bmpReplacesShader->isOpaque()) {
+ break;
+ }
+ } else if (paint->getShader() && !paint->getShader()->isOpaque()) {
+ break;
+ }
+ if (paint->getColorFilter() &&
+ ((paint->getColorFilter()->getFlags() &
+ SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+ break;
+ }
+ return true;
+ case SkXfermode::kSA_Coeff:
+ if (paint->getAlpha() != 0) {
+ break;
+ }
+ if (paint->getColorFilter() &&
+ ((paint->getColorFilter()->getFlags() &
+ SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+ break;
+ }
+ return true;
+ case SkXfermode::kSC_Coeff:
+ if (paint->getColor() != 0) { // all components must be 0
+ break;
+ }
+ if (bmpReplacesShader || paint->getShader()) {
+ break;
+ }
+ if (paint->getColorFilter() && (
+ (paint->getColorFilter()->getFlags() &
+ SkColorFilter::kAlphaUnchanged_Flag) == 0)) {
+ break;
+ }
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
diff --git a/core/SkPaintPriv.h b/core/SkPaintPriv.h
new file mode 100644
index 00000000..38c9063e
--- /dev/null
+++ b/core/SkPaintPriv.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPaintPriv_DEFINED
+#define SkPaintPriv_DEFINED
+
+class SkBitmap;
+class SkPaint;
+
+#include "SkTypes.h"
+/** Returns true if draw calls that use the paint will completely occlude
+ canvas contents that are covered by the draw.
+ @param paint The paint to be analyzed, NULL is equivalent to
+ the default paint.
+ @param bmpReplacesShader a bitmap to be used in place of the paint's
+ shader.
+ @return true if paint is opaque
+*/
+bool isPaintOpaque(const SkPaint* paint,
+ const SkBitmap* bmpReplacesShader = NULL);
+#endif
diff --git a/core/SkPath.cpp b/core/SkPath.cpp
new file mode 100644
index 00000000..ed2f555c
--- /dev/null
+++ b/core/SkPath.cpp
@@ -0,0 +1,3013 @@
+
+/*
+ * 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 "SkBuffer.h"
+#include "SkErrorInternals.h"
+#include "SkMath.h"
+#include "SkPath.h"
+#include "SkPathRef.h"
+#include "SkRRect.h"
+#include "SkThread.h"
+
+SK_DEFINE_INST_COUNT(SkPath);
+
+// This value is just made-up for now. When count is 4, calling memset was much
+// slower than just writing the loop. This seems odd, and hopefully in the
+// future this we appear to have been a fluke...
+#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
+
+////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Path.bounds is defined to be the bounds of all the control points.
+ * If we called bounds.join(r) we would skip r if r was empty, which breaks
+ * our promise. Hence we have a custom joiner that doesn't look at emptiness
+ */
+static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
+ dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
+ dst->fTop = SkMinScalar(dst->fTop, src.fTop);
+ dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
+ dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
+}
+
+static bool is_degenerate(const SkPath& path) {
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ return SkPath::kDone_Verb == iter.next(pts);
+}
+
+class SkAutoDisableOvalCheck {
+public:
+ SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
+ fSaved = fPath->fIsOval;
+ }
+
+ ~SkAutoDisableOvalCheck() {
+ fPath->fIsOval = fSaved;
+ }
+
+private:
+ SkPath* fPath;
+ 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).
+
+ It captures some state about the path up front (i.e. if it already has a
+ cached bounds), and the if it can, it updates the cache bounds explicitly,
+ avoiding the need to revisit all of the points in getBounds().
+
+ It also notes if the path was originally degenerate, and if so, sets
+ isConvex to true. Thus it can only be used if the contour being added is
+ convex.
+ */
+class SkAutoPathBoundsUpdate {
+public:
+ SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
+ this->init(path);
+ }
+
+ SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
+ SkScalar right, SkScalar bottom) {
+ fRect.set(left, top, right, bottom);
+ this->init(path);
+ }
+
+ ~SkAutoPathBoundsUpdate() {
+ fPath->setIsConvex(fDegenerate);
+ if (fEmpty) {
+ fPath->fBounds = fRect;
+ fPath->fBoundsIsDirty = false;
+ fPath->fIsFinite = fPath->fBounds.isFinite();
+ } else if (!fDirty) {
+ joinNoEmptyChecks(&fPath->fBounds, fRect);
+ fPath->fBoundsIsDirty = false;
+ fPath->fIsFinite = fPath->fBounds.isFinite();
+ }
+ }
+
+private:
+ SkPath* fPath;
+ SkRect fRect;
+ bool fDirty;
+ bool fDegenerate;
+ bool fEmpty;
+
+ // returns true if we should proceed
+ void init(SkPath* path) {
+ fPath = path;
+ // 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
+ fRect.sort();
+ }
+};
+
+// Return true if the computed bounds are finite.
+static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
+ int count = ref.countPoints();
+ if (count <= 1) { // we ignore just 1 point (moveto)
+ bounds->setEmpty();
+ return count ? ref.points()->isFinite() : true;
+ } else {
+ return bounds->setBoundsCheck(ref.points(), count);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/*
+ Stores the verbs and points as they are given to us, with exceptions:
+ - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
+ - we insert a Move(0,0) if Line | Quad | Cubic is our first command
+
+ The iterator does more cleanup, especially if forceClose == true
+ 1. If we encounter degenerate segments, remove them
+ 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
+ 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
+ 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
+*/
+
+////////////////////////////////////////////////////////////////////////////
+
+// flag to require a moveTo if we begin with something else, like lineTo etc.
+#define INITIAL_LASTMOVETOINDEX_VALUE ~0
+
+SkPath::SkPath()
+ : fPathRef(SkPathRef::CreateEmpty())
+#ifdef SK_BUILD_FOR_ANDROID
+ , fGenerationID(0)
+#endif
+{
+ this->resetFields();
+}
+
+void SkPath::resetFields() {
+ //fPathRef is assumed to have been emptied by the caller.
+ fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
+ fFillType = kWinding_FillType;
+ fSegmentMask = 0;
+ fBoundsIsDirty = true;
+ fConvexity = kUnknown_Convexity;
+ fDirection = kUnknown_Direction;
+ fIsFinite = false;
+ fIsOval = false;
+#ifdef SK_BUILD_FOR_ANDROID
+ GEN_ID_INC;
+ fSourcePath = NULL;
+#endif
+}
+
+SkPath::SkPath(const SkPath& that)
+ : fPathRef(SkRef(that.fPathRef.get())) {
+ this->copyFields(that);
+#ifdef SK_BUILD_FOR_ANDROID
+ fGenerationID = that.fGenerationID;
+ fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
+#endif
+ SkDEBUGCODE(that.validate();)
+}
+
+SkPath::~SkPath() {
+ SkDEBUGCODE(this->validate();)
+}
+
+SkPath& SkPath::operator=(const SkPath& that) {
+ SkDEBUGCODE(that.validate();)
+
+ if (this != &that) {
+ fPathRef.reset(SkRef(that.fPathRef.get()));
+ this->copyFields(that);
+#ifdef SK_BUILD_FOR_ANDROID
+ GEN_ID_INC; // Similar to swap, we can't just copy this or it could go back in time.
+ fSourcePath = NULL; // TODO(mtklein): follow up with Android: do we want to copy this too?
+#endif
+ }
+ SkDEBUGCODE(this->validate();)
+ return *this;
+}
+
+void SkPath::copyFields(const SkPath& that) {
+ //fPathRef is assumed to have been set by the caller.
+ fBounds = that.fBounds;
+ fLastMoveToIndex = that.fLastMoveToIndex;
+ fFillType = that.fFillType;
+ fSegmentMask = that.fSegmentMask;
+ fBoundsIsDirty = that.fBoundsIsDirty;
+ fConvexity = that.fConvexity;
+ fDirection = that.fDirection;
+ fIsFinite = that.fIsFinite;
+ fIsOval = that.fIsOval;
+}
+
+bool operator==(const SkPath& a, const SkPath& b) {
+ // note: don't need to look at isConvex or bounds, since just comparing the
+ // raw data is sufficient.
+
+ // We explicitly check fSegmentMask as a quick-reject. We could skip it,
+ // since it is only a cache of info in the fVerbs, but its a fast way to
+ // notice a difference
+
+ return &a == &b ||
+ (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
+ *a.fPathRef.get() == *b.fPathRef.get());
+}
+
+void SkPath::swap(SkPath& that) {
+ SkASSERT(&that != NULL);
+
+ if (this != &that) {
+ fPathRef.swap(&that.fPathRef);
+ SkTSwap<SkRect>(fBounds, that.fBounds);
+ SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
+ SkTSwap<uint8_t>(fFillType, that.fFillType);
+ SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
+ SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
+ SkTSwap<uint8_t>(fConvexity, that.fConvexity);
+ SkTSwap<uint8_t>(fDirection, that.fDirection);
+ SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
+ SkTSwap<SkBool8>(fIsOval, that.fIsOval);
+#ifdef SK_BUILD_FOR_ANDROID
+ // It doesn't really make sense to swap the generation IDs here, because they might go
+ // backwards. To be safe we increment both to mark them both as changed.
+ GEN_ID_INC;
+ GEN_ID_PTR_INC(&that);
+ SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
+#endif
+ }
+}
+
+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;)
+ SkDEBUGCODE(int segmentCount = 0;)
+ SkDEBUGCODE(int closeCount = 0;)
+
+ while ((verb = iter.next(pts)) != kDone_Verb) {
+ int nextPt = -1;
+ switch (verb) {
+ case kMove_Verb:
+ SkASSERT(!segmentCount && !closeCount);
+ SkDEBUGCODE(++moveCnt);
+ firstPt = prevPt = pts[0];
+ break;
+ case kLine_Verb:
+ nextPt = 1;
+ SkASSERT(moveCnt && !closeCount);
+ SkDEBUGCODE(++segmentCount);
+ break;
+ case kQuad_Verb:
+ case kConic_Verb:
+ SkASSERT(moveCnt && !closeCount);
+ SkDEBUGCODE(++segmentCount);
+ nextPt = 2;
+ break;
+ case kCubic_Verb:
+ SkASSERT(moveCnt && !closeCount);
+ SkDEBUGCODE(++segmentCount);
+ nextPt = 3;
+ break;
+ case kClose_Verb:
+ SkDEBUGCODE(++closeCount;)
+ 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;
+}
+
+const SkPath* SkPath::getSourcePath() const {
+ return fSourcePath;
+}
+
+void SkPath::setSourcePath(const SkPath* path) {
+ fSourcePath = path;
+}
+#endif
+
+void SkPath::reset() {
+ SkDEBUGCODE(this->validate();)
+
+ fPathRef.reset(SkPathRef::CreateEmpty());
+ this->resetFields();
+}
+
+void SkPath::rewind() {
+ SkDEBUGCODE(this->validate();)
+
+ SkPathRef::Rewind(&fPathRef);
+ this->resetFields();
+}
+
+bool SkPath::isEmpty() const {
+ SkDEBUGCODE(this->validate();)
+ return 0 == fPathRef->countVerbs();
+}
+
+bool SkPath::isLine(SkPoint line[2]) const {
+ int verbCount = fPathRef->countVerbs();
+
+ if (2 == verbCount) {
+ SkASSERT(kMove_Verb == fPathRef->atVerb(0));
+ if (kLine_Verb == fPathRef->atVerb(1)) {
+ SkASSERT(2 == fPathRef->countPoints());
+ if (line) {
+ const SkPoint* pts = fPathRef->points();
+ line[0] = pts[0];
+ line[1] = pts[1];
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ Determines if path is a rect by keeping track of changes in direction
+ and looking for a loop either clockwise or counterclockwise.
+
+ The direction is computed such that:
+ 0: vertical up
+ 1: horizontal left
+ 2: vertical down
+ 3: horizontal right
+
+A rectangle cycles up/right/down/left or up/left/down/right.
+
+The test fails if:
+ The path is closed, and followed by a line.
+ A second move creates a new endpoint.
+ A diagonal line is parsed.
+ There's more than four changes of direction.
+ There's a discontinuity on the line (e.g., a move in the middle)
+ The line reverses direction.
+ The rectangle doesn't complete a cycle.
+ The path contains a quadratic or cubic.
+ The path contains fewer than four points.
+ The final point isn't equal to the first point.
+
+It's OK if the path has:
+ Several colinear line segments composing a rectangle side.
+ Single points on the rectangle side.
+
+The direction takes advantage of the corners found since opposite sides
+must travel in opposite directions.
+
+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::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;
+ int lastDirection = 0;
+ int nextDirection = 0;
+ bool closedOrMoved = false;
+ bool autoClose = false;
+ int verbCnt = fPathRef->countVerbs();
+ while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
+ switch (fPathRef->atVerb(*currVerb)) {
+ case kClose_Verb:
+ savePts = pts;
+ pts = *ptsPtr;
+ autoClose = true;
+ case kLine_Verb: {
+ SkScalar left = last.fX;
+ SkScalar top = last.fY;
+ SkScalar right = pts->fX;
+ SkScalar bottom = pts->fY;
+ ++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 kQuad_Verb:
+ case kConic_Verb:
+ case kCubic_Verb:
+ return false; // quadratic, cubic not allowed
+ case kMove_Verb:
+ last = *pts++;
+ closedOrMoved = true;
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ break;
+ }
+ *currVerb += 1;
+ lastDirection = nextDirection;
+ }
+ // Success if 4 corners and first point equals 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], Direction dirs[2]) const {
+ SkDEBUGCODE(this->validate();)
+ int currVerb = 0;
+ const SkPoint* pts = fPathRef->points();
+ const SkPoint* first = pts;
+ Direction testDirs[2];
+ if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
+ return false;
+ }
+ const SkPoint* last = pts;
+ SkRect testRects[2];
+ if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
+ testRects[0].set(first, SkToS32(last - first));
+ testRects[1].set(last, SkToS32(pts - last));
+ if (testRects[0].contains(testRects[1])) {
+ if (rects) {
+ rects[0] = testRects[0];
+ rects[1] = testRects[1];
+ }
+ if (dirs) {
+ dirs[0] = testDirs[0];
+ dirs[1] = testDirs[1];
+ }
+ return true;
+ }
+ if (testRects[1].contains(testRects[0])) {
+ if (rects) {
+ rects[0] = testRects[1];
+ rects[1] = testRects[0];
+ }
+ if (dirs) {
+ dirs[0] = testDirs[1];
+ dirs[1] = testDirs[0];
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+int SkPath::countPoints() const {
+ return fPathRef->countPoints();
+}
+
+int SkPath::getPoints(SkPoint dst[], int max) const {
+ SkDEBUGCODE(this->validate();)
+
+ SkASSERT(max >= 0);
+ SkASSERT(!max || dst);
+ int count = SkMin32(max, fPathRef->countPoints());
+ memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
+ return fPathRef->countPoints();
+}
+
+SkPoint SkPath::getPoint(int index) const {
+ if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
+ return fPathRef->atPoint(index);
+ }
+ return SkPoint::Make(0, 0);
+}
+
+int SkPath::countVerbs() const {
+ return fPathRef->countVerbs();
+}
+
+static inline void copy_verbs_reverse(uint8_t* inorderDst,
+ const uint8_t* reversedSrc,
+ int count) {
+ for (int i = 0; i < count; ++i) {
+ inorderDst[i] = reversedSrc[~i];
+ }
+}
+
+int SkPath::getVerbs(uint8_t dst[], int max) const {
+ SkDEBUGCODE(this->validate();)
+
+ SkASSERT(max >= 0);
+ SkASSERT(!max || dst);
+ int count = SkMin32(max, fPathRef->countVerbs());
+ copy_verbs_reverse(dst, fPathRef->verbs(), count);
+ return fPathRef->countVerbs();
+}
+
+bool SkPath::getLastPt(SkPoint* lastPt) const {
+ SkDEBUGCODE(this->validate();)
+
+ int count = fPathRef->countPoints();
+ if (count > 0) {
+ if (lastPt) {
+ *lastPt = fPathRef->atPoint(count - 1);
+ }
+ return true;
+ }
+ if (lastPt) {
+ lastPt->set(0, 0);
+ }
+ return false;
+}
+
+void SkPath::setLastPt(SkScalar x, SkScalar y) {
+ SkDEBUGCODE(this->validate();)
+
+ int count = fPathRef->countPoints();
+ if (count == 0) {
+ this->moveTo(x, y);
+ } else {
+ fIsOval = false;
+ SkPathRef::Editor ed(&fPathRef);
+ ed.atPoint(count-1)->set(x, y);
+ GEN_ID_INC;
+ }
+}
+
+void SkPath::computeBounds() const {
+ SkDEBUGCODE(this->validate();)
+ SkASSERT(fBoundsIsDirty);
+
+ fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
+ fBoundsIsDirty = false;
+}
+
+void SkPath::setConvexity(Convexity c) {
+ if (fConvexity != c) {
+ fConvexity = c;
+ GEN_ID_INC;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Construction methods
+
+#define DIRTY_AFTER_EDIT \
+ do { \
+ fBoundsIsDirty = true; \
+ fConvexity = kUnknown_Convexity; \
+ fDirection = kUnknown_Direction; \
+ fIsOval = false; \
+ } while (0)
+
+#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
+ do { \
+ fBoundsIsDirty = true; \
+ } while (0)
+
+void SkPath::incReserve(U16CPU inc) {
+ SkDEBUGCODE(this->validate();)
+ SkPathRef::Editor(&fPathRef, inc, inc);
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkPath::moveTo(SkScalar x, SkScalar y) {
+ SkDEBUGCODE(this->validate();)
+
+ SkPathRef::Editor ed(&fPathRef);
+
+ // remember our index
+ fLastMoveToIndex = ed.pathRef()->countPoints();
+
+ ed.growForVerb(kMove_Verb)->set(x, y);
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
+}
+
+void SkPath::rMoveTo(SkScalar x, SkScalar y) {
+ SkPoint pt;
+ this->getLastPt(&pt);
+ this->moveTo(pt.fX + x, pt.fY + y);
+}
+
+void SkPath::injectMoveToIfNeeded() {
+ if (fLastMoveToIndex < 0) {
+ SkScalar x, y;
+ if (fPathRef->countVerbs() == 0) {
+ x = y = 0;
+ } else {
+ const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
+ x = pt.fX;
+ y = pt.fY;
+ }
+ this->moveTo(x, y);
+ }
+}
+
+void SkPath::lineTo(SkScalar x, SkScalar y) {
+ SkDEBUGCODE(this->validate();)
+
+ this->injectMoveToIfNeeded();
+
+ SkPathRef::Editor ed(&fPathRef);
+ ed.growForVerb(kLine_Verb)->set(x, y);
+ fSegmentMask |= kLine_SegmentMask;
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT;
+}
+
+void SkPath::rLineTo(SkScalar x, SkScalar y) {
+ this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
+ SkPoint pt;
+ this->getLastPt(&pt);
+ this->lineTo(pt.fX + x, pt.fY + y);
+}
+
+void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+ SkDEBUGCODE(this->validate();)
+
+ this->injectMoveToIfNeeded();
+
+ SkPathRef::Editor ed(&fPathRef);
+ SkPoint* pts = ed.growForVerb(kQuad_Verb);
+ pts[0].set(x1, y1);
+ pts[1].set(x2, y2);
+ fSegmentMask |= kQuad_SegmentMask;
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT;
+}
+
+void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+ this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
+ SkPoint pt;
+ this->getLastPt(&pt);
+ this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
+}
+
+void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar w) {
+ // check for <= 0 or NaN with this test
+ if (!(w > 0)) {
+ this->lineTo(x2, y2);
+ } else if (!SkScalarIsFinite(w)) {
+ this->lineTo(x1, y1);
+ this->lineTo(x2, y2);
+ } else if (SK_Scalar1 == w) {
+ this->quadTo(x1, y1, x2, y2);
+ } else {
+ SkDEBUGCODE(this->validate();)
+
+ this->injectMoveToIfNeeded();
+
+ SkPathRef::Editor ed(&fPathRef);
+ SkPoint* pts = ed.growForConic(w);
+ pts[0].set(x1, y1);
+ pts[1].set(x2, y2);
+ fSegmentMask |= kConic_SegmentMask;
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT;
+ }
+}
+
+void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
+ SkScalar w) {
+ this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
+ SkPoint pt;
+ this->getLastPt(&pt);
+ this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
+}
+
+void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar x3, SkScalar y3) {
+ SkDEBUGCODE(this->validate();)
+
+ this->injectMoveToIfNeeded();
+
+ SkPathRef::Editor ed(&fPathRef);
+ SkPoint* pts = ed.growForVerb(kCubic_Verb);
+ pts[0].set(x1, y1);
+ pts[1].set(x2, y2);
+ pts[2].set(x3, y3);
+ fSegmentMask |= kCubic_SegmentMask;
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT;
+}
+
+void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar x3, SkScalar y3) {
+ this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
+ SkPoint pt;
+ this->getLastPt(&pt);
+ this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
+ pt.fX + x3, pt.fY + y3);
+}
+
+void SkPath::close() {
+ SkDEBUGCODE(this->validate();)
+
+ int count = fPathRef->countVerbs();
+ if (count > 0) {
+ switch (fPathRef->atVerb(count - 1)) {
+ case kLine_Verb:
+ case kQuad_Verb:
+ case kConic_Verb:
+ case kCubic_Verb:
+ case kMove_Verb: {
+ SkPathRef::Editor ed(&fPathRef);
+ ed.growForVerb(kClose_Verb);
+ GEN_ID_INC;
+ break;
+ }
+ case kClose_Verb:
+ // don't add a close if it's the first verb or a repeat
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ break;
+ }
+ }
+
+ // signal that we need a moveTo to follow us (unless we're done)
+#if 0
+ if (fLastMoveToIndex >= 0) {
+ fLastMoveToIndex = ~fLastMoveToIndex;
+ }
+#else
+ fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+
+ this->moveTo(left, top);
+ if (dir == kCCW_Direction) {
+ this->lineTo(left, bottom);
+ this->lineTo(right, bottom);
+ this->lineTo(right, top);
+ } else {
+ this->lineTo(right, top);
+ this->lineTo(right, bottom);
+ this->lineTo(left, bottom);
+ }
+ this->close();
+}
+
+void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
+ SkDEBUGCODE(this->validate();)
+ if (count <= 0) {
+ return;
+ }
+
+ SkPathRef::Editor ed(&fPathRef);
+ fLastMoveToIndex = ed.pathRef()->countPoints();
+ uint8_t* vb;
+ SkPoint* p;
+ // +close makes room for the extra kClose_Verb
+ ed.grow(count + close, count, &vb, &p);
+
+ memcpy(p, pts, count * sizeof(SkPoint));
+ vb[~0] = kMove_Verb;
+ if (count > 1) {
+ // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
+ // be 0, the compiler will remove the test/branch entirely.
+ if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
+ memset(vb - count, kLine_Verb, count - 1);
+ } else {
+ for (int i = 1; i < count; ++i) {
+ vb[~i] = kLine_Verb;
+ }
+ }
+ fSegmentMask |= kLine_SegmentMask;
+ }
+ if (close) {
+ vb[~count] = kClose_Verb;
+ }
+
+ GEN_ID_INC;
+ DIRTY_AFTER_EDIT;
+ SkDEBUGCODE(this->validate();)
+}
+
+static void add_corner_arc(SkPath* path, const SkRect& rect,
+ SkScalar rx, SkScalar ry, int startAngle,
+ SkPath::Direction dir, bool forceMoveTo) {
+ // 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);
+
+ switch (startAngle) {
+ case 0:
+ r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
+ break;
+ case 90:
+ r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
+ break;
+ case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
+ case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
+ default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
+ }
+
+ SkScalar start = SkIntToScalar(startAngle);
+ SkScalar sweep = SkIntToScalar(90);
+ if (SkPath::kCCW_Direction == dir) {
+ start += sweep;
+ sweep = -sweep;
+ }
+
+ path->arcTo(r, start, sweep, forceMoveTo);
+}
+
+void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
+ Direction dir) {
+ 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;
+ }
+
+ const SkRect& bounds = rrect.getBounds();
+
+ 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 {
+ 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();
+ }
+}
+
+bool SkPath::hasOnlyMoveTos() const {
+ int count = fPathRef->countVerbs();
+ const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
+ for (int i = 0; i < count; ++i) {
+ if (*verbs == kLine_Verb ||
+ *verbs == kQuad_Verb ||
+ *verbs == kCubic_Verb) {
+ return false;
+ }
+ ++verbs;
+ }
+ return true;
+}
+
+#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
+
+void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
+ Direction dir) {
+ assert_known_direction(dir);
+
+ if (rx < 0 || ry < 0) {
+ SkErrorInternals::SetError( kInvalidArgument_SkError,
+ "I got %f and %f as radii to SkPath::AddRoundRect, "
+ "but negative radii are not allowed.",
+ SkScalarToDouble(rx), SkScalarToDouble(ry) );
+ return;
+ }
+
+ SkScalar w = rect.width();
+ SkScalar halfW = SkScalarHalf(w);
+ SkScalar h = rect.height();
+ SkScalar halfH = SkScalarHalf(h);
+
+ if (halfW <= 0 || halfH <= 0) {
+ return;
+ }
+
+ bool skip_hori = rx >= halfW;
+ bool skip_vert = ry >= halfH;
+
+ if (skip_hori && skip_vert) {
+ this->addOval(rect, dir);
+ return;
+ }
+
+ fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+
+ SkAutoPathBoundsUpdate apbu(this, rect);
+ SkAutoDisableDirectionCheck(this);
+
+ if (skip_hori) {
+ rx = halfW;
+ } else if (skip_vert) {
+ ry = halfH;
+ }
+
+ SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
+ SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
+
+ this->incReserve(17);
+ this->moveTo(rect.fRight - rx, rect.fTop);
+ if (dir == kCCW_Direction) {
+ if (!skip_hori) {
+ this->lineTo(rect.fLeft + rx, rect.fTop); // top
+ }
+ this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
+ rect.fLeft, rect.fTop + ry - sy,
+ rect.fLeft, rect.fTop + ry); // top-left
+ if (!skip_vert) {
+ this->lineTo(rect.fLeft, rect.fBottom - ry); // left
+ }
+ this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
+ rect.fLeft + rx - sx, rect.fBottom,
+ rect.fLeft + rx, rect.fBottom); // bot-left
+ if (!skip_hori) {
+ this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
+ }
+ this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
+ rect.fRight, rect.fBottom - ry + sy,
+ rect.fRight, rect.fBottom - ry); // bot-right
+ if (!skip_vert) {
+ this->lineTo(rect.fRight, rect.fTop + ry);
+ }
+ this->cubicTo(rect.fRight, rect.fTop + ry - sy,
+ rect.fRight - rx + sx, rect.fTop,
+ rect.fRight - rx, rect.fTop); // top-right
+ } else {
+ this->cubicTo(rect.fRight - rx + sx, rect.fTop,
+ rect.fRight, rect.fTop + ry - sy,
+ rect.fRight, rect.fTop + ry); // top-right
+ if (!skip_vert) {
+ this->lineTo(rect.fRight, rect.fBottom - ry);
+ }
+ this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
+ rect.fRight - rx + sx, rect.fBottom,
+ rect.fRight - rx, rect.fBottom); // bot-right
+ if (!skip_hori) {
+ this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
+ }
+ this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
+ rect.fLeft, rect.fBottom - ry + sy,
+ rect.fLeft, rect.fBottom - ry); // bot-left
+ if (!skip_vert) {
+ this->lineTo(rect.fLeft, rect.fTop + ry); // left
+ }
+ this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
+ rect.fLeft + rx - sx, rect.fTop,
+ rect.fLeft + rx, rect.fTop); // top-left
+ if (!skip_hori) {
+ this->lineTo(rect.fRight - rx, rect.fTop); // top
+ }
+ }
+ this->close();
+}
+
+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.
+ We can't simply check isEmpty() in this case, as additional
+ 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);
+
+ SkScalar cx = oval.centerX();
+ SkScalar cy = oval.centerY();
+ SkScalar rx = SkScalarHalf(oval.width());
+ SkScalar ry = SkScalarHalf(oval.height());
+
+ SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
+ SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
+ SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
+ SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
+
+ /*
+ To handle imprecision in computing the center and radii, we revert to
+ the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
+ to ensure that we don't exceed the oval's bounds *ever*, since we want
+ to use oval for our fast-bounds, rather than have to recompute it.
+ */
+ const SkScalar L = oval.fLeft; // cx - rx
+ const SkScalar T = oval.fTop; // cy - ry
+ const SkScalar R = oval.fRight; // cx + rx
+ const SkScalar B = oval.fBottom; // cy + ry
+
+ this->incReserve(17); // 8 quads + close
+ this->moveTo(R, cy);
+ if (dir == kCCW_Direction) {
+ this->quadTo( R, cy - sy, cx + mx, cy - my);
+ this->quadTo(cx + sx, T, cx , T);
+ this->quadTo(cx - sx, T, cx - mx, cy - my);
+ this->quadTo( L, cy - sy, L, cy );
+ this->quadTo( L, cy + sy, cx - mx, cy + my);
+ this->quadTo(cx - sx, B, cx , B);
+ this->quadTo(cx + sx, B, cx + mx, cy + my);
+ this->quadTo( R, cy + sy, R, cy );
+ } else {
+ this->quadTo( R, cy + sy, cx + mx, cy + my);
+ this->quadTo(cx + sx, B, cx , B);
+ this->quadTo(cx - sx, B, cx - mx, cy + my);
+ this->quadTo( L, cy + sy, L, cy );
+ this->quadTo( L, cy - sy, cx - mx, cy - my);
+ this->quadTo(cx - sx, T, cx , T);
+ this->quadTo(cx + sx, T, cx + mx, cy - my);
+ this->quadTo( R, cy - sy, R, cy );
+ }
+ this->close();
+}
+
+bool SkPath::isOval(SkRect* rect) const {
+ if (fIsOval && rect) {
+ *rect = getBounds();
+ }
+
+ return fIsOval;
+}
+
+void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
+ if (r > 0) {
+ SkRect rect;
+ rect.set(x - r, y - r, x + r, y + r);
+ this->addOval(rect, dir);
+ }
+}
+
+#include "SkGeometry.h"
+
+static int build_arc_points(const SkRect& oval, SkScalar startAngle,
+ SkScalar sweepAngle,
+ SkPoint pts[kSkBuildQuadArcStorage]) {
+
+ if (0 == sweepAngle &&
+ (0 == startAngle || SkIntToScalar(360) == startAngle)) {
+ // Chrome uses this path to move into and out of ovals. If not
+ // treated as a special case the moves can distort the oval's
+ // 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;
+
+ start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
+ stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
+ &stop.fX);
+
+ /* If the sweep angle is nearly (but less than) 360, then due to precision
+ loss in radians-conversion and/or sin/cos, we may end up with coincident
+ vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
+ of drawing a nearly complete circle (good).
+ e.g. canvas.drawArc(0, 359.99, ...)
+ -vs- canvas.drawArc(0, 359.9, ...)
+ We try to detect this edge case, and tweak the stop vector
+ */
+ if (start == stop) {
+ SkScalar sw = SkScalarAbs(sweepAngle);
+ if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
+ SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
+ // make a guess at a tiny angle (in radians) to tweak by
+ SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
+ // not sure how much will be enough, so we use a loop
+ do {
+ stopRad -= deltaRad;
+ stop.fY = SkScalarSinCos(stopRad, &stop.fX);
+ } while (start == stop);
+ }
+ }
+
+ SkMatrix matrix;
+
+ matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
+ matrix.postTranslate(oval.centerX(), oval.centerY());
+
+ return SkBuildQuadArc(start, stop,
+ sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
+ &matrix, pts);
+}
+
+void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+ bool forceMoveTo) {
+ if (oval.width() < 0 || oval.height() < 0) {
+ return;
+ }
+
+ SkPoint pts[kSkBuildQuadArcStorage];
+ int count = build_arc_points(oval, startAngle, sweepAngle, pts);
+ SkASSERT((count & 1) == 1);
+
+ if (fPathRef->countVerbs() == 0) {
+ forceMoveTo = true;
+ }
+ this->incReserve(count);
+ forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
+ for (int i = 1; i < count; i += 2) {
+ this->quadTo(pts[i], pts[i+1]);
+ }
+}
+
+void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
+ SkScalar sweepAngle) {
+ if (oval.isEmpty() || 0 == sweepAngle) {
+ return;
+ }
+
+ const SkScalar kFullCircleAngle = SkIntToScalar(360);
+
+ if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
+ this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
+ return;
+ }
+
+ SkPoint pts[kSkBuildQuadArcStorage];
+ int count = build_arc_points(oval, startAngle, sweepAngle, pts);
+
+ this->incReserve(count);
+ this->moveTo(pts[0]);
+ for (int i = 1; i < count; i += 2) {
+ this->quadTo(pts[i], pts[i+1]);
+ }
+}
+
+/*
+ Need to handle the case when the angle is sharp, and our computed end-points
+ for the arc go behind pt1 and/or p2...
+*/
+void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar radius) {
+ SkVector before, after;
+
+ // need to know our prev pt so we can construct tangent vectors
+ {
+ SkPoint start;
+ this->getLastPt(&start);
+ // Handle degenerate cases by adding a line to the first point and
+ // bailing out.
+ if ((x1 == start.fX && y1 == start.fY) ||
+ (x1 == x2 && y1 == y2) ||
+ radius == 0) {
+ this->lineTo(x1, y1);
+ return;
+ }
+ before.setNormalize(x1 - start.fX, y1 - start.fY);
+ after.setNormalize(x2 - x1, y2 - y1);
+ }
+
+ SkScalar cosh = SkPoint::DotProduct(before, after);
+ SkScalar sinh = SkPoint::CrossProduct(before, after);
+
+ if (SkScalarNearlyZero(sinh)) { // angle is too tight
+ this->lineTo(x1, y1);
+ return;
+ }
+
+ SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
+ if (dist < 0) {
+ dist = -dist;
+ }
+
+ SkScalar xx = x1 - SkScalarMul(dist, before.fX);
+ SkScalar yy = y1 - SkScalarMul(dist, before.fY);
+ SkRotationDirection arcDir;
+
+ // now turn before/after into normals
+ if (sinh > 0) {
+ before.rotateCCW();
+ after.rotateCCW();
+ arcDir = kCW_SkRotationDirection;
+ } else {
+ before.rotateCW();
+ after.rotateCW();
+ arcDir = kCCW_SkRotationDirection;
+ }
+
+ SkMatrix matrix;
+ SkPoint pts[kSkBuildQuadArcStorage];
+
+ matrix.setScale(radius, radius);
+ matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
+ yy - SkScalarMul(radius, before.fY));
+
+ int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
+
+ this->incReserve(count);
+ // [xx,yy] == pts[0]
+ this->lineTo(xx, yy);
+ for (int i = 1; i < count; i += 2) {
+ this->quadTo(pts[i], pts[i+1]);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
+ SkMatrix matrix;
+
+ matrix.setTranslate(dx, dy);
+ this->addPath(path, matrix);
+}
+
+void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
+ SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
+
+ fIsOval = false;
+
+ RawIter iter(path);
+ SkPoint pts[4];
+ Verb verb;
+
+ SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
+
+ while ((verb = iter.next(pts)) != kDone_Verb) {
+ switch (verb) {
+ case kMove_Verb:
+ proc(matrix, &pts[0], &pts[0], 1);
+ this->moveTo(pts[0]);
+ break;
+ case kLine_Verb:
+ proc(matrix, &pts[1], &pts[1], 1);
+ this->lineTo(pts[1]);
+ break;
+ case kQuad_Verb:
+ proc(matrix, &pts[1], &pts[1], 2);
+ this->quadTo(pts[1], pts[2]);
+ break;
+ case kConic_Verb:
+ proc(matrix, &pts[1], &pts[1], 2);
+ this->conicTo(pts[1], pts[2], iter.conicWeight());
+ break;
+ case kCubic_Verb:
+ proc(matrix, &pts[1], &pts[1], 3);
+ this->cubicTo(pts[1], pts[2], pts[3]);
+ break;
+ case kClose_Verb:
+ this->close();
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int pts_in_verb(unsigned verb) {
+ static const uint8_t gPtsInVerb[] = {
+ 1, // kMove
+ 1, // kLine
+ 2, // kQuad
+ 2, // kConic
+ 3, // kCubic
+ 0, // kClose
+ 0 // kDone
+ };
+
+ SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
+ return gPtsInVerb[verb];
+}
+
+// ignore the initial moveto, and stop when the 1st contour ends
+void SkPath::pathTo(const SkPath& path) {
+ int i, vcount = path.fPathRef->countVerbs();
+ // exit early if the path is empty, or just has a moveTo.
+ if (vcount < 2) {
+ return;
+ }
+
+ SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
+
+ fIsOval = false;
+
+ const uint8_t* verbs = path.fPathRef->verbs();
+ // skip the initial moveTo
+ const SkPoint* pts = path.fPathRef->points() + 1;
+ const SkScalar* conicWeight = path.fPathRef->conicWeights();
+
+ SkASSERT(verbs[~0] == kMove_Verb);
+ for (i = 1; i < vcount; i++) {
+ switch (verbs[~i]) {
+ case kLine_Verb:
+ this->lineTo(pts[0].fX, pts[0].fY);
+ break;
+ case kQuad_Verb:
+ this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
+ break;
+ case kConic_Verb:
+ this->conicTo(pts[0], pts[1], *conicWeight++);
+ break;
+ case kCubic_Verb:
+ this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+ break;
+ case kClose_Verb:
+ return;
+ }
+ pts += pts_in_verb(verbs[~i]);
+ }
+}
+
+// ignore the last point of the 1st contour
+void SkPath::reversePathTo(const SkPath& path) {
+ int i, vcount = path.fPathRef->countVerbs();
+ // exit early if the path is empty, or just has a moveTo.
+ if (vcount < 2) {
+ return;
+ }
+
+ SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
+
+ fIsOval = false;
+
+ const uint8_t* verbs = path.fPathRef->verbs();
+ const SkPoint* pts = path.fPathRef->points();
+ const SkScalar* conicWeights = path.fPathRef->conicWeights();
+
+ SkASSERT(verbs[~0] == kMove_Verb);
+ for (i = 1; i < vcount; ++i) {
+ unsigned v = verbs[~i];
+ int n = pts_in_verb(v);
+ if (n == 0) {
+ break;
+ }
+ pts += n;
+ conicWeights += (SkPath::kConic_Verb == v);
+ }
+
+ while (--i > 0) {
+ switch (verbs[~i]) {
+ case kLine_Verb:
+ this->lineTo(pts[-1].fX, pts[-1].fY);
+ break;
+ case kQuad_Verb:
+ this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
+ break;
+ case kConic_Verb:
+ this->conicTo(pts[-1], pts[-2], *--conicWeights);
+ break;
+ case kCubic_Verb:
+ this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
+ pts[-3].fX, pts[-3].fY);
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ break;
+ }
+ pts -= pts_in_verb(verbs[~i]);
+ }
+}
+
+void SkPath::reverseAddPath(const SkPath& src) {
+ SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
+
+ const SkPoint* pts = src.fPathRef->pointsEnd();
+ // we will iterator through src's verbs backwards
+ const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
+ const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
+ const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
+
+ fIsOval = false;
+
+ bool needMove = true;
+ bool needClose = false;
+ while (verbs < verbsEnd) {
+ uint8_t v = *(verbs++);
+ int n = pts_in_verb(v);
+
+ if (needMove) {
+ --pts;
+ this->moveTo(pts->fX, pts->fY);
+ needMove = false;
+ }
+ pts -= n;
+ switch (v) {
+ case kMove_Verb:
+ if (needClose) {
+ this->close();
+ needClose = false;
+ }
+ needMove = true;
+ pts += 1; // so we see the point in "if (needMove)" above
+ break;
+ case kLine_Verb:
+ this->lineTo(pts[0]);
+ break;
+ case kQuad_Verb:
+ this->quadTo(pts[1], pts[0]);
+ break;
+ case kConic_Verb:
+ this->conicTo(pts[1], pts[0], *--conicWeights);
+ break;
+ case kCubic_Verb:
+ this->cubicTo(pts[2], pts[1], pts[0]);
+ break;
+ case kClose_Verb:
+ needClose = true;
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
+ SkMatrix matrix;
+
+ matrix.setTranslate(dx, dy);
+ this->transform(matrix, dst);
+}
+
+#include "SkGeometry.h"
+
+static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
+ int level = 2) {
+ if (--level >= 0) {
+ SkPoint tmp[5];
+
+ SkChopQuadAtHalf(pts, tmp);
+ subdivide_quad_to(path, &tmp[0], level);
+ subdivide_quad_to(path, &tmp[2], level);
+ } else {
+ path->quadTo(pts[1], pts[2]);
+ }
+}
+
+static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
+ int level = 2) {
+ if (--level >= 0) {
+ SkPoint tmp[7];
+
+ SkChopCubicAtHalf(pts, tmp);
+ subdivide_cubic_to(path, &tmp[0], level);
+ subdivide_cubic_to(path, &tmp[3], level);
+ } else {
+ path->cubicTo(pts[1], pts[2], pts[3]);
+ }
+}
+
+void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
+ SkDEBUGCODE(this->validate();)
+ if (dst == NULL) {
+ dst = (SkPath*)this;
+ }
+
+ if (matrix.hasPerspective()) {
+ SkPath tmp;
+ tmp.fFillType = fFillType;
+
+ SkPath::Iter iter(*this, false);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+
+ while ((verb = iter.next(pts, false)) != kDone_Verb) {
+ switch (verb) {
+ case kMove_Verb:
+ tmp.moveTo(pts[0]);
+ break;
+ case kLine_Verb:
+ tmp.lineTo(pts[1]);
+ break;
+ case kQuad_Verb:
+ subdivide_quad_to(&tmp, pts);
+ break;
+ case kConic_Verb:
+ SkASSERT(!"TODO: compute new weight");
+ tmp.conicTo(pts[1], pts[2], iter.conicWeight());
+ break;
+ case kCubic_Verb:
+ subdivide_cubic_to(&tmp, pts);
+ break;
+ case kClose_Verb:
+ tmp.close();
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ break;
+ }
+ }
+
+ 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
+ * once.
+ *
+ * Here we also want to optimize bounds, by noting if the bounds are
+ * already known, and if so, we just transform those as well and mark
+ * them as "known", rather than force the transformed path to have to
+ * recompute them.
+ *
+ * Special gotchas if the path is effectively empty (<= 1 point) or
+ * if it is non-finite. In those cases bounds need to stay empty,
+ * regardless of the matrix.
+ */
+ if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
+ dst->fBoundsIsDirty = false;
+ if (fIsFinite) {
+ matrix.mapRect(&dst->fBounds, fBounds);
+ if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
+ dst->fBounds.setEmpty();
+ }
+ } else {
+ dst->fIsFinite = false;
+ dst->fBounds.setEmpty();
+ }
+ } else {
+ GEN_ID_PTR_INC(dst);
+ dst->fBoundsIsDirty = true;
+ }
+
+ SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
+
+ if (this != dst) {
+ dst->fFillType = fFillType;
+ dst->fSegmentMask = fSegmentMask;
+ dst->fConvexity = fConvexity;
+ }
+
+#ifdef SK_BUILD_FOR_ANDROID
+ if (!matrix.isIdentity()) {
+ GEN_ID_PTR_INC(dst);
+ }
+#endif
+
+ 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();
+
+ SkDEBUGCODE(dst->validate();)
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enum SegmentState {
+ kEmptyContour_SegmentState, // The current contour is empty. We may be
+ // starting processing or we may have just
+ // closed a contour.
+ kAfterMove_SegmentState, // We have seen a move, but nothing else.
+ kAfterPrimitive_SegmentState // We have seen a primitive but not yet
+ // closed the path. Also the initial state.
+};
+
+SkPath::Iter::Iter() {
+#ifdef SK_DEBUG
+ fPts = NULL;
+ fConicWeights = NULL;
+ fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
+ fForceClose = fCloseLine = false;
+ fSegmentState = kEmptyContour_SegmentState;
+#endif
+ // need to init enough to make next() harmlessly return kDone_Verb
+ fVerbs = NULL;
+ fVerbStop = NULL;
+ fNeedClose = false;
+}
+
+SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
+ this->setPath(path, forceClose);
+}
+
+void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
+ fPts = path.fPathRef->points();
+ fVerbs = path.fPathRef->verbs();
+ fVerbStop = path.fPathRef->verbsMemBegin();
+ fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
+ fLastPt.fX = fLastPt.fY = 0;
+ fMoveTo.fX = fMoveTo.fY = 0;
+ fForceClose = SkToU8(forceClose);
+ fNeedClose = false;
+ fSegmentState = kEmptyContour_SegmentState;
+}
+
+bool SkPath::Iter::isClosedContour() const {
+ if (fVerbs == NULL || fVerbs == fVerbStop) {
+ return false;
+ }
+ if (fForceClose) {
+ return true;
+ }
+
+ const uint8_t* verbs = fVerbs;
+ const uint8_t* stop = fVerbStop;
+
+ if (kMove_Verb == *(verbs - 1)) {
+ verbs -= 1; // skip the initial moveto
+ }
+
+ while (verbs > stop) {
+ // verbs points one beyond the current verb, decrement first.
+ unsigned v = *(--verbs);
+ if (kMove_Verb == v) {
+ break;
+ }
+ if (kClose_Verb == v) {
+ return true;
+ }
+ }
+ return false;
+}
+
+SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
+ SkASSERT(pts);
+ if (fLastPt != fMoveTo) {
+ // A special case: if both points are NaN, SkPoint::operation== returns
+ // false, but the iterator expects that they are treated as the same.
+ // (consider SkPoint is a 2-dimension float point).
+ if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
+ SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
+ return kClose_Verb;
+ }
+
+ pts[0] = fLastPt;
+ pts[1] = fMoveTo;
+ fLastPt = fMoveTo;
+ fCloseLine = true;
+ return kLine_Verb;
+ } else {
+ pts[0] = fMoveTo;
+ return kClose_Verb;
+ }
+}
+
+const SkPoint& SkPath::Iter::cons_moveTo() {
+ if (fSegmentState == kAfterMove_SegmentState) {
+ // Set the first return pt to the move pt
+ fSegmentState = kAfterPrimitive_SegmentState;
+ return fMoveTo;
+ } else {
+ SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
+ // Set the first return pt to the last pt of the previous primitive.
+ return fPts[-1];
+ }
+}
+
+void SkPath::Iter::consumeDegenerateSegments() {
+ // We need to step over anything that will not move the current draw point
+ // forward before the next move is seen
+ const uint8_t* lastMoveVerb = 0;
+ const SkPoint* lastMovePt = 0;
+ SkPoint lastPt = fLastPt;
+ while (fVerbs != fVerbStop) {
+ unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
+ switch (verb) {
+ case kMove_Verb:
+ // Keep a record of this most recent move
+ lastMoveVerb = fVerbs;
+ lastMovePt = fPts;
+ lastPt = fPts[0];
+ fVerbs--;
+ fPts++;
+ break;
+
+ case kClose_Verb:
+ // A close when we are in a segment is always valid except when it
+ // follows a move which follows a segment.
+ if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
+ return;
+ }
+ // A close at any other time must be ignored
+ fVerbs--;
+ break;
+
+ case kLine_Verb:
+ if (!IsLineDegenerate(lastPt, fPts[0])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs--;
+ fPts++;
+ break;
+
+ case kConic_Verb:
+ case kQuad_Verb:
+ if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs--;
+ fPts += 2;
+ fConicWeights += (kConic_Verb == verb);
+ break;
+
+ case kCubic_Verb:
+ if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs--;
+ fPts += 3;
+ break;
+
+ default:
+ SkDEBUGFAIL("Should never see kDone_Verb");
+ }
+ }
+}
+
+SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
+ SkASSERT(ptsParam);
+
+ if (fVerbs == fVerbStop) {
+ // Close the curve if requested and if there is some curve to close
+ if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
+ if (kLine_Verb == this->autoClose(ptsParam)) {
+ return kLine_Verb;
+ }
+ fNeedClose = false;
+ return kClose_Verb;
+ }
+ return kDone_Verb;
+ }
+
+ // fVerbs is one beyond the current verb, decrement first
+ unsigned verb = *(--fVerbs);
+ const SkPoint* SK_RESTRICT srcPts = fPts;
+ SkPoint* SK_RESTRICT pts = ptsParam;
+
+ switch (verb) {
+ case kMove_Verb:
+ if (fNeedClose) {
+ fVerbs++; // move back one verb
+ verb = this->autoClose(pts);
+ if (verb == kClose_Verb) {
+ fNeedClose = false;
+ }
+ return (Verb)verb;
+ }
+ if (fVerbs == fVerbStop) { // might be a trailing moveto
+ return kDone_Verb;
+ }
+ fMoveTo = *srcPts;
+ pts[0] = *srcPts;
+ srcPts += 1;
+ fSegmentState = kAfterMove_SegmentState;
+ fLastPt = fMoveTo;
+ fNeedClose = fForceClose;
+ break;
+ case kLine_Verb:
+ pts[0] = this->cons_moveTo();
+ pts[1] = srcPts[0];
+ fLastPt = srcPts[0];
+ fCloseLine = false;
+ srcPts += 1;
+ break;
+ case kConic_Verb:
+ fConicWeights += 1;
+ // fall-through
+ case kQuad_Verb:
+ pts[0] = this->cons_moveTo();
+ memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
+ fLastPt = srcPts[1];
+ srcPts += 2;
+ break;
+ case kCubic_Verb:
+ pts[0] = this->cons_moveTo();
+ memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
+ fLastPt = srcPts[2];
+ srcPts += 3;
+ break;
+ case kClose_Verb:
+ verb = this->autoClose(pts);
+ if (verb == kLine_Verb) {
+ fVerbs++; // move back one verb
+ } else {
+ fNeedClose = false;
+ fSegmentState = kEmptyContour_SegmentState;
+ }
+ fLastPt = fMoveTo;
+ break;
+ }
+ fPts = srcPts;
+ return (Verb)verb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPath::RawIter::RawIter() {
+#ifdef SK_DEBUG
+ fPts = NULL;
+ fConicWeights = NULL;
+ fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
+#endif
+ // need to init enough to make next() harmlessly return kDone_Verb
+ fVerbs = NULL;
+ fVerbStop = NULL;
+}
+
+SkPath::RawIter::RawIter(const SkPath& path) {
+ this->setPath(path);
+}
+
+void SkPath::RawIter::setPath(const SkPath& path) {
+ fPts = path.fPathRef->points();
+ fVerbs = path.fPathRef->verbs();
+ fVerbStop = path.fPathRef->verbsMemBegin();
+ fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
+ fMoveTo.fX = fMoveTo.fY = 0;
+ fLastPt.fX = fLastPt.fY = 0;
+}
+
+SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
+ SkASSERT(NULL != pts);
+ if (fVerbs == fVerbStop) {
+ return kDone_Verb;
+ }
+
+ // fVerbs points one beyond next verb so decrement first.
+ unsigned verb = *(--fVerbs);
+ const SkPoint* srcPts = fPts;
+
+ switch (verb) {
+ case kMove_Verb:
+ pts[0] = *srcPts;
+ fMoveTo = srcPts[0];
+ fLastPt = fMoveTo;
+ srcPts += 1;
+ break;
+ case kLine_Verb:
+ pts[0] = fLastPt;
+ pts[1] = srcPts[0];
+ fLastPt = srcPts[0];
+ srcPts += 1;
+ break;
+ case kConic_Verb:
+ fConicWeights += 1;
+ // fall-through
+ case kQuad_Verb:
+ pts[0] = fLastPt;
+ memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
+ fLastPt = srcPts[1];
+ srcPts += 2;
+ break;
+ case kCubic_Verb:
+ pts[0] = fLastPt;
+ memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
+ fLastPt = srcPts[2];
+ srcPts += 3;
+ break;
+ case kClose_Verb:
+ fLastPt = fMoveTo;
+ pts[0] = fMoveTo;
+ break;
+ }
+ fPts = srcPts;
+ return (Verb)verb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
+*/
+
+uint32_t SkPath::writeToMemory(void* storage) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (NULL == storage) {
+ const int byteCount = sizeof(int32_t)
+ + fPathRef->writeSize()
+ + sizeof(SkRect);
+ return SkAlign4(byteCount);
+ }
+
+ SkWBuffer buffer(storage);
+
+ // Call getBounds() to ensure (as a side-effect) that fBounds
+ // and fIsFinite are computed.
+ const SkRect& bounds = this->getBounds();
+ SkASSERT(!fBoundsIsDirty);
+
+ int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
+ ((fIsOval & 1) << kIsOval_SerializationShift) |
+ (fConvexity << kConvexity_SerializationShift) |
+ (fFillType << kFillType_SerializationShift) |
+ (fSegmentMask << kSegmentMask_SerializationShift) |
+ (fDirection << kDirection_SerializationShift);
+
+ buffer.write32(packed);
+
+ fPathRef->writeToBuffer(&buffer);
+
+ buffer.write(&bounds, sizeof(bounds));
+
+ buffer.padToAlign4();
+ return SkToU32(buffer.pos());
+}
+
+uint32_t SkPath::readFromMemory(const void* storage) {
+ SkRBuffer buffer(storage);
+
+ uint32_t packed = buffer.readS32();
+ fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
+ fIsOval = (packed >> kIsOval_SerializationShift) & 1;
+ fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
+ fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
+ fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
+ fDirection = (packed >> kDirection_SerializationShift) & 0x3;
+
+ fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
+
+ buffer.read(&fBounds, sizeof(fBounds));
+ fBoundsIsDirty = false;
+
+ buffer.skipToAlign4();
+
+ GEN_ID_INC;
+
+ SkDEBUGCODE(this->validate();)
+ return SkToU32(buffer.pos());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkString.h"
+
+static void append_scalar(SkString* str, SkScalar value) {
+ SkString tmp;
+ tmp.printf("%g", value);
+ if (tmp.contains('.')) {
+ tmp.appendUnichar('f');
+ }
+ str->append(tmp);
+}
+
+static void append_params(SkString* str, const char label[], const SkPoint pts[],
+ int count, SkScalar conicWeight = -1) {
+ str->append(label);
+ str->append("(");
+
+ const SkScalar* values = &pts[0].fX;
+ count *= 2;
+
+ for (int i = 0; i < count; ++i) {
+ append_scalar(str, values[i]);
+ if (i < count - 1) {
+ str->append(", ");
+ }
+ }
+ if (conicWeight >= 0) {
+ str->append(", ");
+ append_scalar(str, conicWeight);
+ }
+ str->append(");\n");
+}
+
+void SkPath::dump(bool forceClose, const char title[]) const {
+ Iter iter(*this, forceClose);
+ SkPoint pts[4];
+ Verb verb;
+
+ SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
+ title ? title : "");
+
+ SkString builder;
+
+ while ((verb = iter.next(pts, false)) != kDone_Verb) {
+ switch (verb) {
+ case kMove_Verb:
+ append_params(&builder, "path.moveTo", &pts[0], 1);
+ break;
+ case kLine_Verb:
+ append_params(&builder, "path.lineTo", &pts[1], 1);
+ break;
+ case kQuad_Verb:
+ append_params(&builder, "path.quadTo", &pts[1], 2);
+ break;
+ case kConic_Verb:
+ append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
+ break;
+ case kCubic_Verb:
+ append_params(&builder, "path.cubicTo", &pts[1], 3);
+ break;
+ case kClose_Verb:
+ builder.append("path.close();\n");
+ break;
+ default:
+ SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
+ verb = kDone_Verb; // stop the loop
+ break;
+ }
+ }
+ SkDebugf("%s\n", builder.c_str());
+}
+
+void SkPath::dump() const {
+ this->dump(false);
+}
+
+#ifdef SK_DEBUG
+void SkPath::validate() const {
+ SkASSERT(this != NULL);
+ SkASSERT((fFillType & ~3) == 0);
+
+#ifdef SK_DEBUG_PATH
+ if (!fBoundsIsDirty) {
+ SkRect bounds;
+
+ bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
+ SkASSERT(SkToBool(fIsFinite) == isFinite);
+
+ if (fPathRef->countPoints() <= 1) {
+ // if we're empty, fBounds may be empty but translated, so we can't
+ // necessarily compare to bounds directly
+ // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
+ // be [2, 2, 2, 2]
+ SkASSERT(bounds.isEmpty());
+ SkASSERT(fBounds.isEmpty());
+ } else {
+ if (bounds.isEmpty()) {
+ SkASSERT(fBounds.isEmpty());
+ } else {
+ if (!fBounds.isEmpty()) {
+ SkASSERT(fBounds.contains(bounds));
+ }
+ }
+ }
+ }
+
+ uint32_t mask = 0;
+ const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
+ for (int i = 0; i < fPathRef->countVerbs(); i++) {
+ switch (verbs[~i]) {
+ case kLine_Verb:
+ mask |= kLine_SegmentMask;
+ break;
+ case kQuad_Verb:
+ mask |= kQuad_SegmentMask;
+ break;
+ case kConic_Verb:
+ mask |= kConic_SegmentMask;
+ break;
+ case kCubic_Verb:
+ mask |= kCubic_SegmentMask;
+ case kMove_Verb: // these verbs aren't included in the segment mask.
+ case kClose_Verb:
+ break;
+ case kDone_Verb:
+ SkDEBUGFAIL("Done verb shouldn't be recorded.");
+ break;
+ default:
+ SkDEBUGFAIL("Unknown Verb");
+ break;
+ }
+ }
+ SkASSERT(mask == fSegmentMask);
+#endif // SK_DEBUG_PATH
+}
+#endif // SK_DEBUG
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int sign(SkScalar x) { return x < 0; }
+#define kValueNeverReturnedBySign 2
+
+static int CrossProductSign(const SkVector& a, const SkVector& b) {
+ return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
+}
+
+// only valid for a single contour
+struct Convexicator {
+ Convexicator()
+ : fPtCount(0)
+ , fConvexity(SkPath::kConvex_Convexity)
+ , fDirection(SkPath::kUnknown_Direction) {
+ fSign = 0;
+ // warnings
+ fCurrPt.set(0, 0);
+ fVec0.set(0, 0);
+ fVec1.set(0, 0);
+ fFirstVec.set(0, 0);
+
+ fDx = fDy = 0;
+ fSx = fSy = kValueNeverReturnedBySign;
+ }
+
+ 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;
+ }
+
+ if (0 == fPtCount) {
+ fCurrPt = pt;
+ ++fPtCount;
+ } else {
+ SkVector vec = pt - fCurrPt;
+ if (vec.fX || vec.fY) {
+ fCurrPt = pt;
+ if (++fPtCount == 2) {
+ fFirstVec = fVec1 = vec;
+ } else {
+ SkASSERT(fPtCount > 2);
+ this->addVec(vec);
+ }
+
+ int sx = sign(vec.fX);
+ int sy = sign(vec.fY);
+ fDx += (sx != fSx);
+ fDy += (sy != fSy);
+ fSx = sx;
+ fSy = sy;
+
+ if (fDx > 3 || fDy > 3) {
+ fConvexity = SkPath::kConcave_Convexity;
+ }
+ }
+ }
+ }
+
+ void close() {
+ if (fPtCount > 2) {
+ this->addVec(fFirstVec);
+ }
+ }
+
+private:
+ void addVec(const SkVector& vec) {
+ SkASSERT(vec.fX || vec.fY);
+ fVec0 = fVec1;
+ fVec1 = vec;
+ 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;
+ }
+ }
+ }
+
+ SkPoint fCurrPt;
+ SkVector fVec0, fVec1, fFirstVec;
+ int fPtCount; // non-degenerate points
+ int fSign;
+ SkPath::Convexity fConvexity;
+ SkPath::Direction fDirection;
+ int fDx, fDy, fSx, fSy;
+};
+
+SkPath::Convexity SkPath::internalGetConvexity() const {
+ SkASSERT(kUnknown_Convexity == fConvexity);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ SkPath::Iter iter(*this, true);
+
+ int contourCount = 0;
+ int count;
+ Convexicator state;
+
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case kMove_Verb:
+ if (++contourCount > 1) {
+ fConvexity = kConcave_Convexity;
+ return kConcave_Convexity;
+ }
+ pts[1] = pts[0];
+ count = 1;
+ break;
+ case kLine_Verb: count = 1; break;
+ case kQuad_Verb: count = 2; break;
+ case kConic_Verb: count = 2; break;
+ case kCubic_Verb: count = 3; break;
+ case kClose_Verb:
+ state.close();
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ fConvexity = kConcave_Convexity;
+ return kConcave_Convexity;
+ }
+
+ for (int i = 1; i <= count; i++) {
+ state.addPt(pts[i]);
+ }
+ // early exit
+ if (kConcave_Convexity == state.getConvexity()) {
+ fConvexity = kConcave_Convexity;
+ return kConcave_Convexity;
+ }
+ }
+ fConvexity = state.getConvexity();
+ if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
+ fDirection = state.getDirection();
+ }
+ return static_cast<Convexity>(fConvexity);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ContourIter {
+public:
+ ContourIter(const SkPathRef& pathRef);
+
+ bool done() const { return fDone; }
+ // if !done() then these may be called
+ int count() const { return fCurrPtCount; }
+ const SkPoint* pts() const { return fCurrPt; }
+ void next();
+
+private:
+ int fCurrPtCount;
+ const SkPoint* fCurrPt;
+ const uint8_t* fCurrVerb;
+ const uint8_t* fStopVerbs;
+ const SkScalar* fCurrConicWeight;
+ bool fDone;
+ SkDEBUGCODE(int fContourCounter;)
+};
+
+ContourIter::ContourIter(const SkPathRef& pathRef) {
+ fStopVerbs = pathRef.verbsMemBegin();
+ fDone = false;
+ fCurrPt = pathRef.points();
+ fCurrVerb = pathRef.verbs();
+ fCurrConicWeight = pathRef.conicWeights();
+ fCurrPtCount = 0;
+ SkDEBUGCODE(fContourCounter = 0;)
+ this->next();
+}
+
+void ContourIter::next() {
+ if (fCurrVerb <= fStopVerbs) {
+ fDone = true;
+ }
+ if (fDone) {
+ return;
+ }
+
+ // skip pts of prev contour
+ fCurrPt += fCurrPtCount;
+
+ SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
+ int ptCount = 1; // moveTo
+ const uint8_t* verbs = fCurrVerb;
+
+ for (--verbs; verbs > fStopVerbs; --verbs) {
+ switch (verbs[~0]) {
+ case SkPath::kMove_Verb:
+ goto CONTOUR_END;
+ case SkPath::kLine_Verb:
+ ptCount += 1;
+ break;
+ case SkPath::kConic_Verb:
+ fCurrConicWeight += 1;
+ // fall-through
+ case SkPath::kQuad_Verb:
+ ptCount += 2;
+ break;
+ case SkPath::kCubic_Verb:
+ ptCount += 3;
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ break;
+ }
+ }
+CONTOUR_END:
+ fCurrPtCount = ptCount;
+ fCurrVerb = verbs;
+ SkDEBUGCODE(++fContourCounter;)
+}
+
+// returns cross product of (p1 - p0) and (p2 - p0)
+static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
+ SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
+ // We may get 0 when the above subtracts underflow. We expect this to be
+ // very rare and lazily promote to double.
+ if (0 == cross) {
+ double p0x = SkScalarToDouble(p0.fX);
+ double p0y = SkScalarToDouble(p0.fY);
+
+ double p1x = SkScalarToDouble(p1.fX);
+ double p1y = SkScalarToDouble(p1.fY);
+
+ double p2x = SkScalarToDouble(p2.fX);
+ double p2y = SkScalarToDouble(p2.fY);
+
+ cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
+ (p1y - p0y) * (p2x - p0x));
+
+ }
+ return cross;
+}
+
+// Returns the first pt with the maximum Y coordinate
+static int find_max_y(const SkPoint pts[], int count) {
+ SkASSERT(count > 0);
+ SkScalar max = pts[0].fY;
+ int firstIndex = 0;
+ for (int i = 1; i < count; ++i) {
+ SkScalar y = pts[i].fY;
+ if (y > max) {
+ max = y;
+ firstIndex = i;
+ }
+ }
+ return firstIndex;
+}
+
+static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
+ int i = index;
+ for (;;) {
+ i = (i + inc) % n;
+ if (i == index) { // we wrapped around, so abort
+ break;
+ }
+ if (pts[index] != pts[i]) { // found a different point, success!
+ break;
+ }
+ }
+ return i;
+}
+
+/**
+ * Starting at index, and moving forward (incrementing), find the xmin and
+ * xmax of the contiguous points that have the same Y.
+ */
+static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
+ int* maxIndexPtr) {
+ const SkScalar y = pts[index].fY;
+ SkScalar min = pts[index].fX;
+ SkScalar max = min;
+ int minIndex = index;
+ int maxIndex = index;
+ for (int i = index + 1; i < n; ++i) {
+ if (pts[i].fY != y) {
+ break;
+ }
+ SkScalar x = pts[i].fX;
+ if (x < min) {
+ min = x;
+ minIndex = i;
+ } else if (x > max) {
+ max = x;
+ maxIndex = i;
+ }
+ }
+ *maxIndexPtr = maxIndex;
+ return minIndex;
+}
+
+static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
+ if (dir) {
+ *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+ }
+}
+
+#if 0
+#include "SkString.h"
+#include "../utils/SkParsePath.h"
+static void dumpPath(const SkPath& path) {
+ SkString str;
+ SkParsePath::ToSVGString(path, &str);
+ SkDebugf("%s\n", str.c_str());
+}
+#endif
+
+namespace {
+// for use with convex_dir_test
+double mul(double a, double b) { return a * b; }
+SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
+double toDouble(SkScalar a) { return SkScalarToDouble(a); }
+SkScalar toScalar(SkScalar a) { return a; }
+
+// determines the winding direction of a convex polygon with the precision
+// of T. CAST_SCALAR casts an SkScalar to T.
+template <typename T, T (CAST_SCALAR)(SkScalar)>
+bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
+ // we find the first three points that form a non-degenerate
+ // triangle. If there are no such points then the path is
+ // degenerate. The first is always point 0. Now we find the second
+ // point.
+ int i = 0;
+ enum { kX = 0, kY = 1 };
+ T v0[2];
+ while (1) {
+ v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
+ v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
+ if (v0[kX] || v0[kY]) {
+ break;
+ }
+ if (++i == n - 1) {
+ return false;
+ }
+ }
+ // now find a third point that is not colinear with the first two
+ // points and check the orientation of the triangle (which will be
+ // the same as the orientation of the path).
+ for (++i; i < n; ++i) {
+ T v1[2];
+ v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
+ v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
+ T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
+ if (0 != cross) {
+ *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
+ return true;
+ }
+ }
+ return false;
+}
+}
+
+/*
+ * We loop through all contours, and keep the computed cross-product of the
+ * contour that contained the global y-max. If we just look at the first
+ * contour, we may find one that is wound the opposite way (correctly) since
+ * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
+ * that is outer most (or at least has the global y-max) before we can consider
+ * its cross product.
+ */
+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());
+
+ // initialize with our logical y-min
+ SkScalar ymax = this->getBounds().fTop;
+ SkScalar ymaxCross = 0;
+
+ for (; !iter.done(); iter.next()) {
+ int n = iter.count();
+ if (n < 3) {
+ continue;
+ }
+
+ const SkPoint* pts = iter.pts();
+ SkScalar cross = 0;
+ if (kConvex_Convexity == conv) {
+ // We try first at scalar precision, and then again at double
+ // 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;
+ }
+ } else {
+ int index = find_max_y(pts, n);
+ if (pts[index].fY < ymax) {
+ continue;
+ }
+
+ // If there is more than 1 distinct point at the y-max, we take the
+ // x-min and x-max of them and just subtract to compute the dir.
+ if (pts[(index + 1) % n].fY == pts[index].fY) {
+ int maxIndex;
+ int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
+ if (minIndex == maxIndex) {
+ goto TRY_CROSSPROD;
+ }
+ SkASSERT(pts[minIndex].fY == pts[index].fY);
+ SkASSERT(pts[maxIndex].fY == pts[index].fY);
+ SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
+ // we just subtract the indices, and let that auto-convert to
+ // SkScalar, since we just want - or + to signal the direction.
+ cross = minIndex - maxIndex;
+ } else {
+ TRY_CROSSPROD:
+ // Find a next and prev index to use for the cross-product test,
+ // but we try to find pts that form non-zero vectors from pts[index]
+ //
+ // Its possible that we can't find two non-degenerate vectors, so
+ // we have to guard our search (e.g. all the pts could be in the
+ // same place).
+
+ // we pass n - 1 instead of -1 so we don't foul up % operator by
+ // passing it a negative LH argument.
+ int prev = find_diff_pt(pts, index, n, n - 1);
+ if (prev == index) {
+ // completely degenerate, skip to next contour
+ continue;
+ }
+ 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 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;
+ }
+ }
+
+ if (cross) {
+ // record our best guess so far
+ ymax = pts[index].fY;
+ ymaxCross = cross;
+ }
+ }
+ }
+ if (ymaxCross) {
+ crossToDir(ymaxCross, dir);
+ fDirection = *dir;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
+ SkScalar D, SkScalar t) {
+ return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
+}
+
+static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
+ SkScalar t) {
+ SkScalar A = c3 + 3*(c1 - c2) - c0;
+ SkScalar B = 3*(c2 - c1 - c1 + c0);
+ SkScalar C = 3*(c1 - c0);
+ SkScalar D = c0;
+ return eval_cubic_coeff(A, B, C, D, t);
+}
+
+/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
+ t value such that cubic(t) = target
+ */
+static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
+ SkScalar target, SkScalar* t) {
+ // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
+ SkASSERT(c0 < target && target < c3);
+
+ SkScalar D = c0 - target;
+ SkScalar A = c3 + 3*(c1 - c2) - c0;
+ SkScalar B = 3*(c2 - c1 - c1 + c0);
+ SkScalar C = 3*(c1 - c0);
+
+ const SkScalar TOLERANCE = SK_Scalar1 / 4096;
+ SkScalar minT = 0;
+ SkScalar maxT = SK_Scalar1;
+ SkScalar mid;
+ int i;
+ for (i = 0; i < 16; i++) {
+ mid = SkScalarAve(minT, maxT);
+ SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
+ if (delta < 0) {
+ minT = mid;
+ delta = -delta;
+ } else {
+ maxT = mid;
+ }
+ if (delta < TOLERANCE) {
+ break;
+ }
+ }
+ *t = mid;
+ return true;
+}
+
+template <size_t N> static void find_minmax(const SkPoint pts[],
+ SkScalar* minPtr, SkScalar* maxPtr) {
+ SkScalar min, max;
+ min = max = pts[0].fX;
+ for (size_t i = 1; i < N; ++i) {
+ min = SkMinScalar(min, pts[i].fX);
+ max = SkMaxScalar(max, pts[i].fX);
+ }
+ *minPtr = min;
+ *maxPtr = max;
+}
+
+static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
+ SkPoint storage[4];
+
+ int dir = 1;
+ if (pts[0].fY > pts[3].fY) {
+ storage[0] = pts[3];
+ storage[1] = pts[2];
+ storage[2] = pts[1];
+ storage[3] = pts[0];
+ pts = storage;
+ dir = -1;
+ }
+ if (y < pts[0].fY || y >= pts[3].fY) {
+ return 0;
+ }
+
+ // quickreject or quickaccept
+ SkScalar min, max;
+ find_minmax<4>(pts, &min, &max);
+ if (x < min) {
+ return 0;
+ }
+ if (x > max) {
+ return dir;
+ }
+
+ // compute the actual x(t) value
+ SkScalar t, xt;
+ if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
+ xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
+ } else {
+ SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
+ xt = y < mid ? pts[0].fX : pts[3].fX;
+ }
+ return xt < x ? dir : 0;
+}
+
+static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
+ SkPoint dst[10];
+ int n = SkChopCubicAtYExtrema(pts, dst);
+ int w = 0;
+ for (int i = 0; i <= n; ++i) {
+ w += winding_mono_cubic(&dst[i * 3], x, y);
+ }
+ return w;
+}
+
+static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
+ SkScalar y0 = pts[0].fY;
+ SkScalar y2 = pts[2].fY;
+
+ int dir = 1;
+ if (y0 > y2) {
+ SkTSwap(y0, y2);
+ dir = -1;
+ }
+ if (y < y0 || y >= y2) {
+ return 0;
+ }
+
+ // bounds check on X (not required. is it faster?)
+#if 0
+ if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
+ return 0;
+ }
+#endif
+
+ SkScalar roots[2];
+ int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
+ 2 * (pts[1].fY - pts[0].fY),
+ pts[0].fY - y,
+ roots);
+ SkASSERT(n <= 1);
+ SkScalar xt;
+ if (0 == n) {
+ SkScalar mid = SkScalarAve(y0, y2);
+ // Need [0] and [2] if dir == 1
+ // and [2] and [0] if dir == -1
+ xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
+ } else {
+ SkScalar t = roots[0];
+ SkScalar C = pts[0].fX;
+ SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
+ SkScalar B = 2 * (pts[1].fX - C);
+ xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
+ }
+ return xt < x ? dir : 0;
+}
+
+static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
+ // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
+ if (y0 == y1) {
+ return true;
+ }
+ if (y0 < y1) {
+ return y1 <= y2;
+ } else {
+ return y1 >= y2;
+ }
+}
+
+static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
+ SkPoint dst[5];
+ int n = 0;
+
+ if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
+ n = SkChopQuadAtYExtrema(pts, dst);
+ pts = dst;
+ }
+ int w = winding_mono_quad(pts, x, y);
+ if (n > 0) {
+ w += winding_mono_quad(&pts[2], x, y);
+ }
+ return w;
+}
+
+static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
+ SkScalar x0 = pts[0].fX;
+ SkScalar y0 = pts[0].fY;
+ SkScalar x1 = pts[1].fX;
+ SkScalar y1 = pts[1].fY;
+
+ SkScalar dy = y1 - y0;
+
+ int dir = 1;
+ if (y0 > y1) {
+ SkTSwap(y0, y1);
+ dir = -1;
+ }
+ if (y < y0 || y >= y1) {
+ return 0;
+ }
+
+ SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
+ SkScalarMul(dy, x - pts[0].fX);
+
+ if (SkScalarSignAsInt(cross) == dir) {
+ dir = 0;
+ }
+ return dir;
+}
+
+bool SkPath::contains(SkScalar x, SkScalar y) const {
+ bool isInverse = this->isInverseFillType();
+ if (this->isEmpty()) {
+ return isInverse;
+ }
+
+ const SkRect& bounds = this->getBounds();
+ if (!bounds.contains(x, y)) {
+ return isInverse;
+ }
+
+ SkPath::Iter iter(*this, true);
+ bool done = false;
+ int w = 0;
+ do {
+ SkPoint pts[4];
+ switch (iter.next(pts, false)) {
+ case SkPath::kMove_Verb:
+ case SkPath::kClose_Verb:
+ break;
+ case SkPath::kLine_Verb:
+ w += winding_line(pts, x, y);
+ break;
+ case SkPath::kQuad_Verb:
+ w += winding_quad(pts, x, y);
+ break;
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kCubic_Verb:
+ w += winding_cubic(pts, x, y);
+ break;
+ case SkPath::kDone_Verb:
+ done = true;
+ break;
+ }
+ } while (!done);
+
+ switch (this->getFillType()) {
+ case SkPath::kEvenOdd_FillType:
+ case SkPath::kInverseEvenOdd_FillType:
+ w &= 1;
+ break;
+ default:
+ break;
+ }
+ return SkToBool(w);
+}
diff --git a/core/SkPathEffect.cpp b/core/SkPathEffect.cpp
new file mode 100644
index 00000000..8306d7aa
--- /dev/null
+++ b/core/SkPathEffect.cpp
@@ -0,0 +1,81 @@
+
+/*
+ * 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 "SkPathEffect.h"
+#include "SkPath.h"
+#include "SkFlattenableBuffers.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_INST_COUNT(SkPathEffect)
+
+void SkPathEffect::computeFastBounds(SkRect* dst, const SkRect& src) const {
+ *dst = src;
+}
+
+bool SkPathEffect::asPoints(PointData* results, const SkPath& src,
+ const SkStrokeRec&, const SkMatrix&, const SkRect*) const {
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPairPathEffect::SkPairPathEffect(SkPathEffect* pe0, SkPathEffect* pe1)
+ : fPE0(pe0), fPE1(pe1) {
+ SkASSERT(pe0);
+ SkASSERT(pe1);
+ fPE0->ref();
+ fPE1->ref();
+}
+
+SkPairPathEffect::~SkPairPathEffect() {
+ SkSafeUnref(fPE0);
+ SkSafeUnref(fPE1);
+}
+
+/*
+ Format: [oe0-factory][pe1-factory][pe0-size][pe0-data][pe1-data]
+*/
+void SkPairPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fPE0);
+ buffer.writeFlattenable(fPE1);
+}
+
+SkPairPathEffect::SkPairPathEffect(SkFlattenableReadBuffer& buffer) {
+ fPE0 = buffer.readFlattenableT<SkPathEffect>();
+ fPE1 = buffer.readFlattenableT<SkPathEffect>();
+ // either of these may fail, so we have to check for nulls later on
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkComposePathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ // we may have failed to unflatten these, so we have to check
+ if (!fPE0 || !fPE1) {
+ return false;
+ }
+
+ SkPath tmp;
+ const SkPath* ptr = &src;
+
+ if (fPE1->filterPath(&tmp, src, rec, cullRect)) {
+ ptr = &tmp;
+ }
+ return fPE0->filterPath(dst, *ptr, rec, cullRect);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkSumPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ // use bit-or so that we always call both, even if the first one succeeds
+ return fPE0->filterPath(dst, src, rec, cullRect) |
+ fPE1->filterPath(dst, src, rec, cullRect);
+}
diff --git a/core/SkPathHeap.cpp b/core/SkPathHeap.cpp
new file mode 100644
index 00000000..12db3c48
--- /dev/null
+++ b/core/SkPathHeap.cpp
@@ -0,0 +1,63 @@
+
+/*
+ * 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 "SkPathHeap.h"
+#include "SkPath.h"
+#include "SkStream.h"
+#include "SkFlattenableBuffers.h"
+#include <new>
+
+SK_DEFINE_INST_COUNT(SkPathHeap)
+
+#define kPathCount 64
+
+SkPathHeap::SkPathHeap() : fHeap(kPathCount * sizeof(SkPath)) {
+}
+
+SkPathHeap::SkPathHeap(SkFlattenableReadBuffer& buffer)
+ : fHeap(kPathCount * sizeof(SkPath)) {
+ const int count = buffer.readInt();
+
+ fPaths.setCount(count);
+ SkPath** ptr = fPaths.begin();
+ SkPath* p = (SkPath*)fHeap.allocThrow(count * sizeof(SkPath));
+
+ for (int i = 0; i < count; i++) {
+ new (p) SkPath;
+ buffer.readPath(p);
+ *ptr++ = p; // record the pointer
+ p++; // move to the next storage location
+ }
+}
+
+SkPathHeap::~SkPathHeap() {
+ SkPath** iter = fPaths.begin();
+ SkPath** stop = fPaths.end();
+ while (iter < stop) {
+ (*iter)->~SkPath();
+ iter++;
+ }
+}
+
+int SkPathHeap::append(const SkPath& path) {
+ SkPath* p = (SkPath*)fHeap.allocThrow(sizeof(SkPath));
+ new (p) SkPath(path);
+ *fPaths.append() = p;
+ return fPaths.count();
+}
+
+void SkPathHeap::flatten(SkFlattenableWriteBuffer& buffer) const {
+ int count = fPaths.count();
+
+ buffer.writeInt(count);
+ SkPath* const* iter = fPaths.begin();
+ SkPath* const* stop = fPaths.end();
+ while (iter < stop) {
+ buffer.writePath(**iter);
+ iter++;
+ }
+}
diff --git a/core/SkPathHeap.h b/core/SkPathHeap.h
new file mode 100644
index 00000000..095e84a2
--- /dev/null
+++ b/core/SkPathHeap.h
@@ -0,0 +1,50 @@
+
+/*
+ * 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 SkPathHeap_DEFINED
+#define SkPathHeap_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkChunkAlloc.h"
+#include "SkTDArray.h"
+
+class SkPath;
+class SkFlattenableReadBuffer;
+class SkFlattenableWriteBuffer;
+
+class SkPathHeap : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkPathHeap)
+
+ SkPathHeap();
+ SkPathHeap(SkFlattenableReadBuffer&);
+ virtual ~SkPathHeap();
+
+ /** Copy the path into the heap, and return the new total number of paths.
+ Thus, the returned value will be index+1, where index is the index of
+ this newly added (copied) path.
+ */
+ int append(const SkPath&);
+
+ // called during picture-playback
+ int count() const { return fPaths.count(); }
+ const SkPath& operator[](int index) const {
+ return *fPaths[index];
+ }
+
+ void flatten(SkFlattenableWriteBuffer&) const;
+
+private:
+ // we store the paths in the heap (placement new)
+ SkChunkAlloc fHeap;
+ // we just store ptrs into fHeap here
+ SkTDArray<SkPath*> fPaths;
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkPathMeasure.cpp b/core/SkPathMeasure.cpp
new file mode 100644
index 00000000..c519f93d
--- /dev/null
+++ b/core/SkPathMeasure.cpp
@@ -0,0 +1,542 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkPathMeasure.h"
+#include "SkGeometry.h"
+#include "SkPath.h"
+#include "SkTSearch.h"
+
+// these must be 0,1,2 since they are in our 2-bit field
+enum {
+ kLine_SegType,
+ kQuad_SegType,
+ kCubic_SegType
+};
+
+#define kMaxTValue 32767
+
+static inline SkScalar tValue2Scalar(int t) {
+ SkASSERT((unsigned)t <= kMaxTValue);
+
+#ifdef SK_SCALAR_IS_FLOAT
+ return t * 3.05185e-5f; // t / 32767
+#else
+ return (t + (t >> 14)) << 1;
+#endif
+}
+
+SkScalar SkPathMeasure::Segment::getScalarT() const {
+ return tValue2Scalar(fTValue);
+}
+
+const SkPathMeasure::Segment* SkPathMeasure::NextSegment(const Segment* seg) {
+ unsigned ptIndex = seg->fPtIndex;
+
+ do {
+ ++seg;
+ } while (seg->fPtIndex == ptIndex);
+ return seg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline int tspan_big_enough(int tspan) {
+ SkASSERT((unsigned)tspan <= kMaxTValue);
+ return tspan >> 10;
+}
+
+// can't use tangents, since we need [0..1..................2] to be seen
+// as definitely not a line (it is when drawn, but not parametrically)
+// so we compare midpoints
+#define CHEAP_DIST_LIMIT (SK_Scalar1/2) // just made this value up
+
+static bool quad_too_curvy(const SkPoint pts[3]) {
+ // diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
+ // diff = -a/4 + b/2 - c/4
+ SkScalar dx = SkScalarHalf(pts[1].fX) -
+ SkScalarHalf(SkScalarHalf(pts[0].fX + pts[2].fX));
+ SkScalar dy = SkScalarHalf(pts[1].fY) -
+ SkScalarHalf(SkScalarHalf(pts[0].fY + pts[2].fY));
+
+ SkScalar dist = SkMaxScalar(SkScalarAbs(dx), SkScalarAbs(dy));
+ return dist > CHEAP_DIST_LIMIT;
+}
+
+static bool cheap_dist_exceeds_limit(const SkPoint& pt,
+ SkScalar x, SkScalar y) {
+ SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
+ // just made up the 1/2
+ return dist > CHEAP_DIST_LIMIT;
+}
+
+static bool cubic_too_curvy(const SkPoint pts[4]) {
+ return cheap_dist_exceeds_limit(pts[1],
+ SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1/3),
+ SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1/3))
+ ||
+ cheap_dist_exceeds_limit(pts[2],
+ SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1*2/3),
+ SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3));
+}
+
+SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
+ SkScalar distance, int mint, int maxt, int ptIndex) {
+ if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts)) {
+ SkPoint tmp[5];
+ int halft = (mint + maxt) >> 1;
+
+ SkChopQuadAtHalf(pts, tmp);
+ distance = this->compute_quad_segs(tmp, distance, mint, halft, ptIndex);
+ distance = this->compute_quad_segs(&tmp[2], distance, halft, maxt, ptIndex);
+ } else {
+ SkScalar d = SkPoint::Distance(pts[0], pts[2]);
+ SkScalar prevD = distance;
+ distance += d;
+ if (distance > prevD) {
+ Segment* seg = fSegments.append();
+ seg->fDistance = distance;
+ seg->fPtIndex = ptIndex;
+ seg->fType = kQuad_SegType;
+ seg->fTValue = maxt;
+ }
+ }
+ return distance;
+}
+
+SkScalar SkPathMeasure::compute_cubic_segs(const SkPoint pts[4],
+ SkScalar distance, int mint, int maxt, int ptIndex) {
+ if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts)) {
+ SkPoint tmp[7];
+ int halft = (mint + maxt) >> 1;
+
+ SkChopCubicAtHalf(pts, tmp);
+ distance = this->compute_cubic_segs(tmp, distance, mint, halft, ptIndex);
+ distance = this->compute_cubic_segs(&tmp[3], distance, halft, maxt, ptIndex);
+ } else {
+ SkScalar d = SkPoint::Distance(pts[0], pts[3]);
+ SkScalar prevD = distance;
+ distance += d;
+ if (distance > prevD) {
+ Segment* seg = fSegments.append();
+ seg->fDistance = distance;
+ seg->fPtIndex = ptIndex;
+ seg->fType = kCubic_SegType;
+ seg->fTValue = maxt;
+ }
+ }
+ return distance;
+}
+
+void SkPathMeasure::buildSegments() {
+ SkPoint pts[4];
+ int ptIndex = fFirstPtIndex;
+ SkScalar distance = 0;
+ bool isClosed = fForceClosed;
+ bool firstMoveTo = ptIndex < 0;
+ Segment* seg;
+
+ /* Note:
+ * as we accumulate distance, we have to check that the result of +=
+ * actually made it larger, since a very small delta might be > 0, but
+ * still have no effect on distance (if distance >>> delta).
+ *
+ * We do this check below, and in compute_quad_segs and compute_cubic_segs
+ */
+ fSegments.reset();
+ bool done = false;
+ do {
+ switch (fIter.next(pts)) {
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kMove_Verb:
+ ptIndex += 1;
+ fPts.append(1, pts);
+ if (!firstMoveTo) {
+ done = true;
+ break;
+ }
+ firstMoveTo = false;
+ break;
+
+ case SkPath::kLine_Verb: {
+ SkScalar d = SkPoint::Distance(pts[0], pts[1]);
+ SkASSERT(d >= 0);
+ SkScalar prevD = distance;
+ distance += d;
+ if (distance > prevD) {
+ seg = fSegments.append();
+ seg->fDistance = distance;
+ seg->fPtIndex = ptIndex;
+ seg->fType = kLine_SegType;
+ seg->fTValue = kMaxTValue;
+ fPts.append(1, pts + 1);
+ ptIndex++;
+ }
+ } break;
+
+ case SkPath::kQuad_Verb: {
+ SkScalar prevD = distance;
+ distance = this->compute_quad_segs(pts, distance, 0,
+ kMaxTValue, ptIndex);
+ if (distance > prevD) {
+ fPts.append(2, pts + 1);
+ ptIndex += 2;
+ }
+ } break;
+
+ case SkPath::kCubic_Verb: {
+ SkScalar prevD = distance;
+ distance = this->compute_cubic_segs(pts, distance, 0,
+ kMaxTValue, ptIndex);
+ if (distance > prevD) {
+ fPts.append(3, pts + 1);
+ ptIndex += 3;
+ }
+ } break;
+
+ case SkPath::kClose_Verb:
+ isClosed = true;
+ break;
+
+ case SkPath::kDone_Verb:
+ done = true;
+ break;
+ }
+ } while (!done);
+
+ fLength = distance;
+ fIsClosed = isClosed;
+ fFirstPtIndex = ptIndex;
+
+#ifdef SK_DEBUG
+ {
+ const Segment* seg = fSegments.begin();
+ const Segment* stop = fSegments.end();
+ unsigned ptIndex = 0;
+ SkScalar distance = 0;
+
+ while (seg < stop) {
+ SkASSERT(seg->fDistance > distance);
+ SkASSERT(seg->fPtIndex >= ptIndex);
+ SkASSERT(seg->fTValue > 0);
+
+ const Segment* s = seg;
+ while (s < stop - 1 && s[0].fPtIndex == s[1].fPtIndex) {
+ SkASSERT(s[0].fType == s[1].fType);
+ SkASSERT(s[0].fTValue < s[1].fTValue);
+ s += 1;
+ }
+
+ distance = seg->fDistance;
+ ptIndex = seg->fPtIndex;
+ seg += 1;
+ }
+ // SkDebugf("\n");
+ }
+#endif
+}
+
+static void compute_pos_tan(const SkPoint pts[], int segType,
+ SkScalar t, SkPoint* pos, SkVector* tangent) {
+ switch (segType) {
+ case kLine_SegType:
+ if (pos) {
+ pos->set(SkScalarInterp(pts[0].fX, pts[1].fX, t),
+ SkScalarInterp(pts[0].fY, pts[1].fY, t));
+ }
+ if (tangent) {
+ tangent->setNormalize(pts[1].fX - pts[0].fX, pts[1].fY - pts[0].fY);
+ }
+ break;
+ case kQuad_SegType:
+ SkEvalQuadAt(pts, t, pos, tangent);
+ if (tangent) {
+ tangent->normalize();
+ }
+ break;
+ case kCubic_SegType:
+ SkEvalCubicAt(pts, t, pos, tangent, NULL);
+ if (tangent) {
+ tangent->normalize();
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unknown segType");
+ }
+}
+
+static void seg_to(const SkPoint pts[], int segType,
+ SkScalar startT, SkScalar stopT, SkPath* dst) {
+ SkASSERT(startT >= 0 && startT <= SK_Scalar1);
+ SkASSERT(stopT >= 0 && stopT <= SK_Scalar1);
+ SkASSERT(startT <= stopT);
+
+ if (startT == stopT) {
+ return; // should we report this, to undo a moveTo?
+ }
+
+ SkPoint tmp0[7], tmp1[7];
+
+ switch (segType) {
+ case kLine_SegType:
+ if (SK_Scalar1 == stopT) {
+ dst->lineTo(pts[1]);
+ } else {
+ dst->lineTo(SkScalarInterp(pts[0].fX, pts[1].fX, stopT),
+ SkScalarInterp(pts[0].fY, pts[1].fY, stopT));
+ }
+ break;
+ case kQuad_SegType:
+ if (0 == startT) {
+ if (SK_Scalar1 == stopT) {
+ dst->quadTo(pts[1], pts[2]);
+ } else {
+ SkChopQuadAt(pts, tmp0, stopT);
+ dst->quadTo(tmp0[1], tmp0[2]);
+ }
+ } else {
+ SkChopQuadAt(pts, tmp0, startT);
+ if (SK_Scalar1 == stopT) {
+ dst->quadTo(tmp0[3], tmp0[4]);
+ } else {
+ SkChopQuadAt(&tmp0[2], tmp1, SkScalarDiv(stopT - startT,
+ SK_Scalar1 - startT));
+ dst->quadTo(tmp1[1], tmp1[2]);
+ }
+ }
+ break;
+ case kCubic_SegType:
+ if (0 == startT) {
+ if (SK_Scalar1 == stopT) {
+ dst->cubicTo(pts[1], pts[2], pts[3]);
+ } else {
+ SkChopCubicAt(pts, tmp0, stopT);
+ dst->cubicTo(tmp0[1], tmp0[2], tmp0[3]);
+ }
+ } else {
+ SkChopCubicAt(pts, tmp0, startT);
+ if (SK_Scalar1 == stopT) {
+ dst->cubicTo(tmp0[4], tmp0[5], tmp0[6]);
+ } else {
+ SkChopCubicAt(&tmp0[3], tmp1, SkScalarDiv(stopT - startT,
+ SK_Scalar1 - startT));
+ dst->cubicTo(tmp1[1], tmp1[2], tmp1[3]);
+ }
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unknown segType");
+ sk_throw();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+SkPathMeasure::SkPathMeasure() {
+ fPath = NULL;
+ fLength = -1; // signal we need to compute it
+ fForceClosed = false;
+ fFirstPtIndex = -1;
+}
+
+SkPathMeasure::SkPathMeasure(const SkPath& path, bool forceClosed) {
+ fPath = &path;
+ fLength = -1; // signal we need to compute it
+ fForceClosed = forceClosed;
+ fFirstPtIndex = -1;
+
+ fIter.setPath(path, forceClosed);
+}
+
+SkPathMeasure::~SkPathMeasure() {}
+
+/** Assign a new path, or null to have none.
+*/
+void SkPathMeasure::setPath(const SkPath* path, bool forceClosed) {
+ fPath = path;
+ fLength = -1; // signal we need to compute it
+ fForceClosed = forceClosed;
+ fFirstPtIndex = -1;
+
+ if (path) {
+ fIter.setPath(*path, forceClosed);
+ }
+ fSegments.reset();
+ fPts.reset();
+}
+
+SkScalar SkPathMeasure::getLength() {
+ if (fPath == NULL) {
+ return 0;
+ }
+ if (fLength < 0) {
+ this->buildSegments();
+ }
+ SkASSERT(fLength >= 0);
+ return fLength;
+}
+
+const SkPathMeasure::Segment* SkPathMeasure::distanceToSegment(
+ SkScalar distance, SkScalar* t) {
+ SkDEBUGCODE(SkScalar length = ) this->getLength();
+ SkASSERT(distance >= 0 && distance <= length);
+
+ const Segment* seg = fSegments.begin();
+ int count = fSegments.count();
+
+ int index = SkTSearch<SkScalar>(&seg->fDistance, count, distance, sizeof(Segment));
+ // don't care if we hit an exact match or not, so we xor index if it is negative
+ index ^= (index >> 31);
+ seg = &seg[index];
+
+ // now interpolate t-values with the prev segment (if possible)
+ SkScalar startT = 0, startD = 0;
+ // check if the prev segment is legal, and references the same set of points
+ if (index > 0) {
+ startD = seg[-1].fDistance;
+ if (seg[-1].fPtIndex == seg->fPtIndex) {
+ SkASSERT(seg[-1].fType == seg->fType);
+ startT = seg[-1].getScalarT();
+ }
+ }
+
+ SkASSERT(seg->getScalarT() > startT);
+ SkASSERT(distance >= startD);
+ SkASSERT(seg->fDistance > startD);
+
+ *t = startT + SkScalarMulDiv(seg->getScalarT() - startT,
+ distance - startD,
+ seg->fDistance - startD);
+ return seg;
+}
+
+bool SkPathMeasure::getPosTan(SkScalar distance, SkPoint* pos,
+ SkVector* tangent) {
+ if (NULL == fPath) {
+ return false;
+ }
+
+ SkScalar length = this->getLength(); // call this to force computing it
+ int count = fSegments.count();
+
+ if (count == 0 || length == 0) {
+ return false;
+ }
+
+ // pin the distance to a legal range
+ if (distance < 0) {
+ distance = 0;
+ } else if (distance > length) {
+ distance = length;
+ }
+
+ SkScalar t;
+ const Segment* seg = this->distanceToSegment(distance, &t);
+
+ compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
+ return true;
+}
+
+bool SkPathMeasure::getMatrix(SkScalar distance, SkMatrix* matrix,
+ MatrixFlags flags) {
+ if (NULL == fPath) {
+ return false;
+ }
+
+ SkPoint position;
+ SkVector tangent;
+
+ if (this->getPosTan(distance, &position, &tangent)) {
+ if (matrix) {
+ if (flags & kGetTangent_MatrixFlag) {
+ matrix->setSinCos(tangent.fY, tangent.fX, 0, 0);
+ } else {
+ matrix->reset();
+ }
+ if (flags & kGetPosition_MatrixFlag) {
+ matrix->postTranslate(position.fX, position.fY);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkPathMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst,
+ bool startWithMoveTo) {
+ SkASSERT(dst);
+
+ SkScalar length = this->getLength(); // ensure we have built our segments
+
+ if (startD < 0) {
+ startD = 0;
+ }
+ if (stopD > length) {
+ stopD = length;
+ }
+ if (startD >= stopD) {
+ return false;
+ }
+
+ SkPoint p;
+ SkScalar startT, stopT;
+ const Segment* seg = this->distanceToSegment(startD, &startT);
+ const Segment* stopSeg = this->distanceToSegment(stopD, &stopT);
+ SkASSERT(seg <= stopSeg);
+
+ if (startWithMoveTo) {
+ compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, startT, &p, NULL);
+ dst->moveTo(p);
+ }
+
+ if (seg->fPtIndex == stopSeg->fPtIndex) {
+ seg_to(&fPts[seg->fPtIndex], seg->fType, startT, stopT, dst);
+ } else {
+ do {
+ seg_to(&fPts[seg->fPtIndex], seg->fType, startT, SK_Scalar1, dst);
+ seg = SkPathMeasure::NextSegment(seg);
+ startT = 0;
+ } while (seg->fPtIndex < stopSeg->fPtIndex);
+ seg_to(&fPts[seg->fPtIndex], seg->fType, 0, stopT, dst);
+ }
+ return true;
+}
+
+bool SkPathMeasure::isClosed() {
+ (void)this->getLength();
+ return fIsClosed;
+}
+
+/** Move to the next contour in the path. Return true if one exists, or false if
+ we're done with the path.
+*/
+bool SkPathMeasure::nextContour() {
+ fLength = -1;
+ return this->getLength() > 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkPathMeasure::dump() {
+ SkDebugf("pathmeas: length=%g, segs=%d\n", fLength, fSegments.count());
+
+ for (int i = 0; i < fSegments.count(); i++) {
+ const Segment* seg = &fSegments[i];
+ SkDebugf("pathmeas: seg[%d] distance=%g, point=%d, t=%g, type=%d\n",
+ i, seg->fDistance, seg->fPtIndex, seg->getScalarT(),
+ seg->fType);
+ }
+}
+
+#endif
diff --git a/core/SkPathRef.h b/core/SkPathRef.h
new file mode 100644
index 00000000..cff75b64
--- /dev/null
+++ b/core/SkPathRef.h
@@ -0,0 +1,551 @@
+
+/*
+ * 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 SkPathRef_DEFINED
+#define SkPathRef_DEFINED
+
+#include "SkRefCnt.h"
+#include <stddef.h> // ptrdiff_t
+
+/**
+ * Holds the path verbs and points. It is versioned by a generation ID. None of its public methods
+ * modify the contents. To modify or append to the verbs/points wrap the SkPathRef in an
+ * SkPathRef::Editor object. Installing the editor resets the generation ID. It also performs
+ * copy-on-write if the SkPathRef is shared by multipls SkPaths. The caller passes the Editor's
+ * constructor a SkAutoTUnref, which may be updated to point to a new SkPathRef after the editor's
+ * constructor returns.
+ *
+ * The points and verbs are stored in a single allocation. The points are at the begining of the
+ * allocation while the verbs are stored at end of the allocation, in reverse order. Thus the points
+ * and verbs both grow into the middle of the allocation until the meet. To access verb i in the
+ * verb array use ref.verbs()[~i] (because verbs() returns a pointer just beyond the first
+ * logical verb or the last verb in memory).
+ */
+
+class SkPathRef;
+
+class SkPathRef : public ::SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkPathRef);
+
+ class Editor {
+ public:
+ Editor(SkAutoTUnref<SkPathRef>* pathRef,
+ int incReserveVerbs = 0,
+ int incReservePoints = 0)
+ {
+ if ((*pathRef)->unique()) {
+ (*pathRef)->incReserve(incReserveVerbs, incReservePoints);
+ } else {
+ SkPathRef* copy = SkNEW(SkPathRef);
+ copy->copy(**pathRef, incReserveVerbs, incReservePoints);
+ pathRef->reset(copy);
+ }
+ fPathRef = *pathRef;
+ fPathRef->fGenerationID = 0;
+ SkDEBUGCODE(sk_atomic_inc(&fPathRef->fEditorsAttached);)
+ }
+
+ ~Editor() { SkDEBUGCODE(sk_atomic_dec(&fPathRef->fEditorsAttached);) }
+
+ /**
+ * Returns the array of points.
+ */
+ SkPoint* points() { return fPathRef->fPoints; }
+
+ /**
+ * Gets the ith point. Shortcut for this->points() + i
+ */
+ SkPoint* atPoint(int i) {
+ SkASSERT((unsigned) i < (unsigned) fPathRef->fPointCnt);
+ return this->points() + i;
+ };
+
+ /**
+ * Adds the verb and allocates space for the number of points indicated by the verb. The
+ * return value is a pointer to where the points for the verb should be written.
+ */
+ SkPoint* growForVerb(SkPath::Verb verb) {
+ fPathRef->validate();
+ return fPathRef->growForVerb(verb);
+ }
+
+ SkPoint* growForConic(SkScalar w) {
+ fPathRef->validate();
+ SkPoint* pts = fPathRef->growForVerb(SkPath::kConic_Verb);
+ *fPathRef->fConicWeights.append() = w;
+ return pts;
+ }
+
+ /**
+ * Allocates space for additional verbs and points and returns pointers to the new verbs and
+ * points. verbs will point one beyond the first new verb (index it using [~<i>]). pts points
+ * at the first new point (indexed normally [<i>]).
+ */
+ void grow(int newVerbs, int newPts, uint8_t** verbs, SkPoint** pts) {
+ SkASSERT(NULL != verbs);
+ SkASSERT(NULL != pts);
+ fPathRef->validate();
+ int oldVerbCnt = fPathRef->fVerbCnt;
+ int oldPointCnt = fPathRef->fPointCnt;
+ SkASSERT(verbs && pts);
+ fPathRef->grow(newVerbs, newPts);
+ *verbs = fPathRef->fVerbs - oldVerbCnt;
+ *pts = fPathRef->fPoints + oldPointCnt;
+ fPathRef->validate();
+ }
+
+ /**
+ * Resets the path ref to a new verb and point count. The new verbs and points are
+ * uninitialized.
+ */
+ void resetToSize(int newVerbCnt, int newPointCnt, int newConicCount) {
+ fPathRef->resetToSize(newVerbCnt, newPointCnt, newConicCount);
+ }
+ /**
+ * Gets the path ref that is wrapped in the Editor.
+ */
+ SkPathRef* pathRef() { return fPathRef; }
+
+ private:
+ SkPathRef* fPathRef;
+ };
+
+public:
+ /**
+ * Gets a path ref with no verbs or points.
+ */
+ static SkPathRef* CreateEmpty() {
+ static SkPathRef* gEmptyPathRef;
+ if (!gEmptyPathRef) {
+ gEmptyPathRef = SkNEW(SkPathRef); // leak!
+ }
+ return SkRef(gEmptyPathRef);
+ }
+
+ /**
+ * Transforms a path ref by a matrix, allocating a new one only if necessary.
+ */
+ static void CreateTransformedCopy(SkAutoTUnref<SkPathRef>* dst,
+ const SkPathRef& src,
+ const SkMatrix& matrix) {
+ src.validate();
+ if (matrix.isIdentity()) {
+ if (*dst != &src) {
+ src.ref();
+ dst->reset(const_cast<SkPathRef*>(&src));
+ (*dst)->validate();
+ }
+ return;
+ }
+ bool dstUnique = (*dst)->unique();
+ if (&src == *dst && dstUnique) {
+ matrix.mapPoints((*dst)->fPoints, (*dst)->fPointCnt);
+ return;
+ } else if (!dstUnique) {
+ dst->reset(SkNEW(SkPathRef));
+ }
+ (*dst)->resetToSize(src.fVerbCnt, src.fPointCnt, src.fConicWeights.count());
+ memcpy((*dst)->verbsMemWritable(), src.verbsMemBegin(), src.fVerbCnt * sizeof(uint8_t));
+ matrix.mapPoints((*dst)->fPoints, src.points(), src.fPointCnt);
+ (*dst)->fConicWeights = src.fConicWeights;
+ (*dst)->validate();
+ }
+
+ static SkPathRef* CreateFromBuffer(SkRBuffer* buffer) {
+ SkPathRef* ref = SkNEW(SkPathRef);
+ ref->fGenerationID = buffer->readU32();
+ int32_t verbCount = buffer->readS32();
+ int32_t pointCount = buffer->readS32();
+ int32_t conicCount = buffer->readS32();
+ ref->resetToSize(verbCount, pointCount, conicCount);
+
+ SkASSERT(verbCount == ref->countVerbs());
+ SkASSERT(pointCount == ref->countPoints());
+ SkASSERT(conicCount == ref->fConicWeights.count());
+ buffer->read(ref->verbsMemWritable(), verbCount * sizeof(uint8_t));
+ buffer->read(ref->fPoints, pointCount * sizeof(SkPoint));
+ buffer->read(ref->fConicWeights.begin(), conicCount * sizeof(SkScalar));
+ return ref;
+ }
+
+ /**
+ * Rollsback a path ref to zero verbs and points with the assumption that the path ref will be
+ * repopulated with approximately the same number of verbs and points. A new path ref is created
+ * only if necessary.
+ */
+ static void Rewind(SkAutoTUnref<SkPathRef>* pathRef) {
+ if ((*pathRef)->unique()) {
+ (*pathRef)->validate();
+ (*pathRef)->fVerbCnt = 0;
+ (*pathRef)->fPointCnt = 0;
+ (*pathRef)->fFreeSpace = (*pathRef)->currSize();
+ (*pathRef)->fGenerationID = 0;
+ (*pathRef)->fConicWeights.rewind();
+ (*pathRef)->validate();
+ } else {
+ int oldVCnt = (*pathRef)->countVerbs();
+ int oldPCnt = (*pathRef)->countPoints();
+ pathRef->reset(SkNEW(SkPathRef));
+ (*pathRef)->resetToSize(0, 0, 0, oldVCnt, oldPCnt);
+ }
+ }
+
+ virtual ~SkPathRef() {
+ this->validate();
+ sk_free(fPoints);
+
+ SkDEBUGCODE(fPoints = NULL;)
+ SkDEBUGCODE(fVerbs = NULL;)
+ SkDEBUGCODE(fVerbCnt = 0x9999999;)
+ SkDEBUGCODE(fPointCnt = 0xAAAAAAA;)
+ SkDEBUGCODE(fPointCnt = 0xBBBBBBB;)
+ SkDEBUGCODE(fGenerationID = 0xEEEEEEEE;)
+ SkDEBUGCODE(fEditorsAttached = 0x7777777;)
+ }
+
+ int countPoints() const { this->validate(); return fPointCnt; }
+ int countVerbs() const { this->validate(); return fVerbCnt; }
+
+ /**
+ * Returns a pointer one beyond the first logical verb (last verb in memory order).
+ */
+ const uint8_t* verbs() const { this->validate(); return fVerbs; }
+
+ /**
+ * Returns a const pointer to the first verb in memory (which is the last logical verb).
+ */
+ const uint8_t* verbsMemBegin() const { return this->verbs() - fVerbCnt; }
+
+ /**
+ * Returns a const pointer to the first point.
+ */
+ const SkPoint* points() const { this->validate(); return fPoints; }
+
+ /**
+ * Shortcut for this->points() + this->countPoints()
+ */
+ const SkPoint* pointsEnd() const { return this->points() + this->countPoints(); }
+
+ const SkScalar* conicWeights() const { this->validate(); return fConicWeights.begin(); }
+ const SkScalar* conicWeightsEnd() const { this->validate(); return fConicWeights.end(); }
+
+ /**
+ * Convenience methods for getting to a verb or point by index.
+ */
+ uint8_t atVerb(int index) {
+ SkASSERT((unsigned) index < (unsigned) fVerbCnt);
+ return this->verbs()[~index];
+ }
+ const SkPoint& atPoint(int index) const {
+ SkASSERT((unsigned) index < (unsigned) fPointCnt);
+ return this->points()[index];
+ }
+
+ bool operator== (const SkPathRef& ref) const {
+ this->validate();
+ ref.validate();
+ bool genIDMatch = fGenerationID && fGenerationID == ref.fGenerationID;
+#ifdef SK_RELEASE
+ if (genIDMatch) {
+ return true;
+ }
+#endif
+ if (fPointCnt != ref.fPointCnt ||
+ fVerbCnt != ref.fVerbCnt) {
+ SkASSERT(!genIDMatch);
+ return false;
+ }
+ if (0 != memcmp(this->verbsMemBegin(),
+ ref.verbsMemBegin(),
+ ref.fVerbCnt * sizeof(uint8_t))) {
+ SkASSERT(!genIDMatch);
+ return false;
+ }
+ if (0 != memcmp(this->points(),
+ ref.points(),
+ ref.fPointCnt * sizeof(SkPoint))) {
+ SkASSERT(!genIDMatch);
+ return false;
+ }
+ if (fConicWeights != ref.fConicWeights) {
+ SkASSERT(!genIDMatch);
+ return false;
+ }
+ // We've done the work to determine that these are equal. If either has a zero genID, copy
+ // the other's. If both are 0 then genID() will compute the next ID.
+ if (0 == fGenerationID) {
+ fGenerationID = ref.genID();
+ } else if (0 == ref.fGenerationID) {
+ ref.fGenerationID = this->genID();
+ }
+ return true;
+ }
+
+ /**
+ * Writes the path points and verbs to a buffer.
+ */
+ void writeToBuffer(SkWBuffer* buffer) {
+ this->validate();
+ SkDEBUGCODE(size_t beforePos = buffer->pos();)
+
+ // TODO: write gen ID here. Problem: We don't know if we're cross process or not from
+ // SkWBuffer. Until this is fixed we write 0.
+ buffer->write32(0);
+ buffer->write32(fVerbCnt);
+ buffer->write32(fPointCnt);
+ buffer->write32(fConicWeights.count());
+ buffer->write(verbsMemBegin(), fVerbCnt * sizeof(uint8_t));
+ buffer->write(fPoints, fPointCnt * sizeof(SkPoint));
+ buffer->write(fConicWeights.begin(), fConicWeights.bytes());
+
+ SkASSERT(buffer->pos() - beforePos == (size_t) this->writeSize());
+ }
+
+ /**
+ * Gets the number of bytes that would be written in writeBuffer()
+ */
+ uint32_t writeSize() {
+ return 4 * sizeof(uint32_t) +
+ fVerbCnt * sizeof(uint8_t) +
+ fPointCnt * sizeof(SkPoint) +
+ fConicWeights.bytes();
+ }
+
+private:
+ SkPathRef() {
+ fPointCnt = 0;
+ fVerbCnt = 0;
+ fVerbs = NULL;
+ fPoints = NULL;
+ fFreeSpace = 0;
+ fGenerationID = kEmptyGenID;
+ SkDEBUGCODE(fEditorsAttached = 0;)
+ this->validate();
+ }
+
+ void copy(const SkPathRef& ref, int additionalReserveVerbs, int additionalReservePoints) {
+ this->validate();
+ this->resetToSize(ref.fVerbCnt, ref.fPointCnt, ref.fConicWeights.count(),
+ additionalReserveVerbs, additionalReservePoints);
+ memcpy(this->verbsMemWritable(), ref.verbsMemBegin(), ref.fVerbCnt * sizeof(uint8_t));
+ memcpy(this->fPoints, ref.fPoints, ref.fPointCnt * sizeof(SkPoint));
+ fConicWeights = ref.fConicWeights;
+ // We could call genID() here to force a real ID (instead of 0). However, if we're making
+ // a copy then presumably we intend to make a modification immediately afterwards.
+ fGenerationID = ref.fGenerationID;
+ this->validate();
+ }
+
+ /** Makes additional room but does not change the counts or change the genID */
+ void incReserve(int additionalVerbs, int additionalPoints) {
+ this->validate();
+ size_t space = additionalVerbs * sizeof(uint8_t) + additionalPoints * sizeof (SkPoint);
+ this->makeSpace(space);
+ this->validate();
+ }
+
+ /** Resets the path ref with verbCount verbs and pointCount points, all unitialized. Also
+ * allocates space for reserveVerb additional verbs and reservePoints additional points.*/
+ void resetToSize(int verbCount, int pointCount, int conicCount,
+ int reserveVerbs = 0, int reservePoints = 0) {
+ this->validate();
+ fGenerationID = 0;
+
+ size_t newSize = sizeof(uint8_t) * verbCount + sizeof(SkPoint) * pointCount;
+ size_t newReserve = sizeof(uint8_t) * reserveVerbs + sizeof(SkPoint) * reservePoints;
+ size_t minSize = newSize + newReserve;
+
+ ptrdiff_t sizeDelta = this->currSize() - minSize;
+
+ if (sizeDelta < 0 || static_cast<size_t>(sizeDelta) >= 3 * minSize) {
+ sk_free(fPoints);
+ fPoints = NULL;
+ fVerbs = NULL;
+ fFreeSpace = 0;
+ fVerbCnt = 0;
+ fPointCnt = 0;
+ this->makeSpace(minSize);
+ fVerbCnt = verbCount;
+ fPointCnt = pointCount;
+ fFreeSpace -= newSize;
+ } else {
+ fPointCnt = pointCount;
+ fVerbCnt = verbCount;
+ fFreeSpace = this->currSize() - minSize;
+ }
+ fConicWeights.setCount(conicCount);
+ this->validate();
+ }
+
+ /**
+ * Increases the verb count by newVerbs and the point count be newPoints. New verbs and points
+ * are uninitialized.
+ */
+ void grow(int newVerbs, int newPoints) {
+ this->validate();
+ size_t space = newVerbs * sizeof(uint8_t) + newPoints * sizeof (SkPoint);
+ this->makeSpace(space);
+ fVerbCnt += newVerbs;
+ fPointCnt += newPoints;
+ fFreeSpace -= space;
+ this->validate();
+ }
+
+ /**
+ * Increases the verb count 1, records the new verb, and creates room for the requisite number
+ * of additional points. A pointer to the first point is returned. Any new points are
+ * uninitialized.
+ */
+ SkPoint* growForVerb(SkPath::Verb verb) {
+ this->validate();
+ int pCnt;
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ pCnt = 1;
+ break;
+ case SkPath::kLine_Verb:
+ pCnt = 1;
+ break;
+ case SkPath::kQuad_Verb:
+ // fall through
+ case SkPath::kConic_Verb:
+ pCnt = 2;
+ break;
+ case SkPath::kCubic_Verb:
+ pCnt = 3;
+ break;
+ case SkPath::kClose_Verb:
+ pCnt = 0;
+ break;
+ case SkPath::kDone_Verb:
+ SkASSERT(!"growForVerb called for kDone");
+ // fall through
+ default:
+ SkASSERT(!"default is not reached");
+ pCnt = 0;
+ }
+ size_t space = sizeof(uint8_t) + pCnt * sizeof (SkPoint);
+ this->makeSpace(space);
+ this->fVerbs[~fVerbCnt] = verb;
+ SkPoint* ret = fPoints + fPointCnt;
+ fVerbCnt += 1;
+ fPointCnt += pCnt;
+ fFreeSpace -= space;
+ this->validate();
+ return ret;
+ }
+
+ /**
+ * Ensures that the free space available in the path ref is >= size. The verb and point counts
+ * are not changed.
+ */
+ void makeSpace(size_t size) {
+ this->validate();
+ ptrdiff_t growSize = size - fFreeSpace;
+ if (growSize <= 0) {
+ return;
+ }
+ size_t oldSize = this->currSize();
+ // round to next multiple of 8 bytes
+ growSize = (growSize + 7) & ~static_cast<size_t>(7);
+ // we always at least double the allocation
+ if (static_cast<size_t>(growSize) < oldSize) {
+ growSize = oldSize;
+ }
+ if (growSize < kMinSize) {
+ growSize = kMinSize;
+ }
+ size_t newSize = oldSize + growSize;
+ // Note that realloc could memcpy more than we need. It seems to be a win anyway. TODO:
+ // encapsulate this.
+ fPoints = reinterpret_cast<SkPoint*>(sk_realloc_throw(fPoints, newSize));
+ size_t oldVerbSize = fVerbCnt * sizeof(uint8_t);
+ void* newVerbsDst = reinterpret_cast<void*>(
+ reinterpret_cast<intptr_t>(fPoints) + newSize - oldVerbSize);
+ void* oldVerbsSrc = reinterpret_cast<void*>(
+ reinterpret_cast<intptr_t>(fPoints) + oldSize - oldVerbSize);
+ memmove(newVerbsDst, oldVerbsSrc, oldVerbSize);
+ fVerbs = reinterpret_cast<uint8_t*>(reinterpret_cast<intptr_t>(fPoints) + newSize);
+ fFreeSpace += growSize;
+ this->validate();
+ }
+
+ /**
+ * Private, non-const-ptr version of the public function verbsMemBegin().
+ */
+ uint8_t* verbsMemWritable() {
+ this->validate();
+ return fVerbs - fVerbCnt;
+ }
+
+ /**
+ * Gets the total amount of space allocated for verbs, points, and reserve.
+ */
+ size_t currSize() const {
+ return reinterpret_cast<intptr_t>(fVerbs) - reinterpret_cast<intptr_t>(fPoints);
+ }
+
+ /**
+ * Gets an ID that uniquely identifies the contents of the path ref. If two path refs have the
+ * same ID then they have the same verbs and points. However, two path refs may have the same
+ * contents but different genIDs. Zero is reserved and means an ID has not yet been determined
+ * for the path ref.
+ */
+ int32_t genID() const {
+ SkASSERT(!fEditorsAttached);
+ if (!fGenerationID) {
+ if (0 == fPointCnt && 0 == fVerbCnt) {
+ fGenerationID = kEmptyGenID;
+ } else {
+ static int32_t gPathRefGenerationID;
+ // do a loop in case our global wraps around, as we never want to return a 0 or the
+ // empty ID
+ do {
+ fGenerationID = sk_atomic_inc(&gPathRefGenerationID) + 1;
+ } while (fGenerationID <= kEmptyGenID);
+ }
+ }
+ return fGenerationID;
+ }
+
+ void validate() const {
+ SkASSERT(static_cast<ptrdiff_t>(fFreeSpace) >= 0);
+ SkASSERT(reinterpret_cast<intptr_t>(fVerbs) - reinterpret_cast<intptr_t>(fPoints) >= 0);
+ SkASSERT((NULL == fPoints) == (NULL == fVerbs));
+ SkASSERT(!(NULL == fPoints && 0 != fFreeSpace));
+ SkASSERT(!(NULL == fPoints && 0 != fFreeSpace));
+ SkASSERT(!(NULL == fPoints && fPointCnt));
+ SkASSERT(!(NULL == fVerbs && fVerbCnt));
+ SkASSERT(this->currSize() ==
+ fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt);
+ }
+
+ enum {
+ kMinSize = 256,
+ };
+
+ SkPoint* fPoints; // points to begining of the allocation
+ uint8_t* fVerbs; // points just past the end of the allocation (verbs grow backwards)
+ int fVerbCnt;
+ int fPointCnt;
+ size_t fFreeSpace; // redundant but saves computation
+ SkTDArray<SkScalar> fConicWeights;
+
+ enum {
+ kEmptyGenID = 1, // GenID reserved for path ref with zero points and zero verbs.
+ };
+ mutable int32_t fGenerationID;
+ SkDEBUGCODE(int32_t fEditorsAttached;) // assert that only one editor in use at any time.
+
+ typedef SkRefCnt INHERITED;
+};
+
+SK_DEFINE_INST_COUNT(SkPathRef);
+
+#endif
diff --git a/core/SkPerspIter.h b/core/SkPerspIter.h
new file mode 100644
index 00000000..821f1534
--- /dev/null
+++ b/core/SkPerspIter.h
@@ -0,0 +1,48 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkPerspIter_DEFINED
+#define SkPerspIter_DEFINED
+
+#include "SkMatrix.h"
+
+class SkPerspIter {
+public:
+ /** Iterate a line through the matrix [x,y] ... [x+count-1, y].
+ @param m The matrix we will be iterating a line through
+ @param x The initial X coordinate to be mapped through the matrix
+ @param y The initial Y coordinate to be mapped through the matrix
+ @param count The number of points (x,y) (x+1,y) (x+2,y) ... we will eventually map
+ */
+ SkPerspIter(const SkMatrix& m, SkScalar x, SkScalar y, int count);
+
+ /** Return the buffer of [x,y] fixed point values we will be filling.
+ This always returns the same value, so it can be saved across calls to
+ next().
+ */
+ const SkFixed* getXY() const { return fStorage; }
+
+ /** Return the number of [x,y] pairs that have been filled in the getXY() buffer.
+ When this returns 0, the iterator is finished.
+ */
+ int next();
+
+private:
+ enum {
+ kShift = 4,
+ kCount = (1 << kShift)
+ };
+ const SkMatrix& fMatrix;
+ SkFixed fStorage[kCount * 2];
+ SkFixed fX, fY;
+ SkScalar fSX, fSY;
+ int fCount;
+};
+
+#endif
diff --git a/core/SkPicture.cpp b/core/SkPicture.cpp
new file mode 100644
index 00000000..d8ad80dd
--- /dev/null
+++ b/core/SkPicture.cpp
@@ -0,0 +1,350 @@
+
+/*
+ * Copyright 2007 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 "SkPictureFlat.h"
+#include "SkPicturePlayback.h"
+#include "SkPictureRecord.h"
+
+#include "SkCanvas.h"
+#include "SkChunkAlloc.h"
+#include "SkDevice.h"
+#include "SkPicture.h"
+#include "SkRegion.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTSearch.h"
+#include "SkTime.h"
+
+#include "SkReader32.h"
+#include "SkWriter32.h"
+#include "SkRTree.h"
+#include "SkBBoxHierarchyRecord.h"
+
+SK_DEFINE_INST_COUNT(SkPicture)
+
+#define DUMP_BUFFER_SIZE 65536
+
+//#define ENABLE_TIME_DRAW // dumps milliseconds for each draw
+
+
+#ifdef SK_DEBUG
+// enable SK_DEBUG_TRACE to trace DrawType elements when
+// recorded and played back
+// #define SK_DEBUG_TRACE
+// enable SK_DEBUG_SIZE to see the size of picture components
+// #define SK_DEBUG_SIZE
+// enable SK_DEBUG_DUMP to see the contents of recorded elements
+// #define SK_DEBUG_DUMP
+// enable SK_DEBUG_VALIDATE to check internal structures for consistency
+// #define SK_DEBUG_VALIDATE
+#endif
+
+#if defined SK_DEBUG_TRACE || defined SK_DEBUG_DUMP
+const char* DrawTypeToString(DrawType drawType) {
+ switch (drawType) {
+ case UNUSED: SkDebugf("DrawType UNUSED\n"); SkASSERT(0); break;
+ case CLIP_PATH: return "CLIP_PATH";
+ case CLIP_REGION: return "CLIP_REGION";
+ case CLIP_RECT: return "CLIP_RECT";
+ case CLIP_RRECT: return "CLIP_RRECT";
+ case CONCAT: return "CONCAT";
+ case DRAW_BITMAP: return "DRAW_BITMAP";
+ case DRAW_BITMAP_MATRIX: return "DRAW_BITMAP_MATRIX";
+ case DRAW_BITMAP_NINE: return "DRAW_BITMAP_NINE";
+ case DRAW_BITMAP_RECT_TO_RECT: return "DRAW_BITMAP_RECT_TO_RECT";
+ case DRAW_CLEAR: return "DRAW_CLEAR";
+ case DRAW_DATA: return "DRAW_DATA";
+ case DRAW_OVAL: return "DRAW_OVAL";
+ case DRAW_PAINT: return "DRAW_PAINT";
+ case DRAW_PATH: return "DRAW_PATH";
+ case DRAW_PICTURE: return "DRAW_PICTURE";
+ case DRAW_POINTS: return "DRAW_POINTS";
+ case DRAW_POS_TEXT: return "DRAW_POS_TEXT";
+ case DRAW_POS_TEXT_TOP_BOTTOM: return "DRAW_POS_TEXT_TOP_BOTTOM";
+ case DRAW_POS_TEXT_H: return "DRAW_POS_TEXT_H";
+ case DRAW_POS_TEXT_H_TOP_BOTTOM: return "DRAW_POS_TEXT_H_TOP_BOTTOM";
+ case DRAW_RECT: return "DRAW_RECT";
+ case DRAW_RRECT: return "DRAW_RRECT";
+ case DRAW_SPRITE: return "DRAW_SPRITE";
+ case DRAW_TEXT: return "DRAW_TEXT";
+ case DRAW_TEXT_ON_PATH: return "DRAW_TEXT_ON_PATH";
+ case DRAW_TEXT_TOP_BOTTOM: return "DRAW_TEXT_TOP_BOTTOM";
+ case DRAW_VERTICES: return "DRAW_VERTICES";
+ case RESTORE: return "RESTORE";
+ case ROTATE: return "ROTATE";
+ case SAVE: return "SAVE";
+ case SAVE_LAYER: return "SAVE_LAYER";
+ case SCALE: return "SCALE";
+ case SET_MATRIX: return "SET_MATRIX";
+ case SKEW: return "SKEW";
+ case TRANSLATE: return "TRANSLATE";
+ case NOOP: return "NOOP";
+ default:
+ SkDebugf("DrawType error 0x%08x\n", drawType);
+ SkASSERT(0);
+ break;
+ }
+ SkASSERT(0);
+ return NULL;
+}
+#endif
+
+#ifdef SK_DEBUG_VALIDATE
+static void validateMatrix(const SkMatrix* matrix) {
+ SkScalar scaleX = matrix->getScaleX();
+ SkScalar scaleY = matrix->getScaleY();
+ SkScalar skewX = matrix->getSkewX();
+ SkScalar skewY = matrix->getSkewY();
+ SkScalar perspX = matrix->getPerspX();
+ SkScalar perspY = matrix->getPerspY();
+ if (scaleX != 0 && skewX != 0)
+ SkDebugf("scaleX != 0 && skewX != 0\n");
+ SkASSERT(scaleX == 0 || skewX == 0);
+ SkASSERT(scaleY == 0 || skewY == 0);
+ SkASSERT(perspX == 0);
+ SkASSERT(perspY == 0);
+}
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPicture::SkPicture() {
+ fRecord = NULL;
+ fPlayback = NULL;
+ fWidth = fHeight = 0;
+}
+
+SkPicture::SkPicture(const SkPicture& src) : INHERITED() {
+ fWidth = src.fWidth;
+ fHeight = src.fHeight;
+ fRecord = NULL;
+
+ /* 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).
+ */
+ if (src.fPlayback) {
+ fPlayback = SkNEW_ARGS(SkPicturePlayback, (*src.fPlayback));
+ } else if (src.fRecord) {
+ // here we do a fake src.endRecording()
+ fPlayback = SkNEW_ARGS(SkPicturePlayback, (*src.fRecord));
+ } else {
+ fPlayback = NULL;
+ }
+}
+
+SkPicture::~SkPicture() {
+ SkSafeUnref(fRecord);
+ SkDELETE(fPlayback);
+}
+
+void SkPicture::swap(SkPicture& other) {
+ SkTSwap(fRecord, other.fRecord);
+ SkTSwap(fPlayback, other.fPlayback);
+ SkTSwap(fWidth, other.fWidth);
+ SkTSwap(fHeight, other.fHeight);
+}
+
+SkPicture* SkPicture::clone() const {
+ SkPicture* clonedPicture = SkNEW(SkPicture);
+ clone(clonedPicture, 1);
+ return clonedPicture;
+}
+
+void SkPicture::clone(SkPicture* pictures, int count) const {
+ SkPictCopyInfo copyInfo;
+
+ for (int i = 0; i < count; i++) {
+ SkPicture* clone = &pictures[i];
+
+ clone->fWidth = fWidth;
+ 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).
+ */
+ if (fPlayback) {
+ clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (*fPlayback, &copyInfo));
+ } else if (fRecord) {
+ // here we do a fake src.endRecording()
+ clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (*fRecord, true));
+ } else {
+ clone->fPlayback = NULL;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkCanvas* SkPicture::beginRecording(int width, int height,
+ uint32_t recordingFlags) {
+ if (fPlayback) {
+ SkDELETE(fPlayback);
+ fPlayback = NULL;
+ }
+
+ if (NULL != fRecord) {
+ fRecord->unref();
+ fRecord = NULL;
+ }
+
+ SkBitmap bm;
+ 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) {
+ SkBBoxHierarchy* tree = this->createBBoxHierarchy();
+ SkASSERT(NULL != tree);
+ fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (recordingFlags, tree, dev));
+ tree->unref();
+ } else {
+ fRecord = SkNEW_ARGS(SkPictureRecord, (recordingFlags, dev));
+ }
+ fRecord->beginRecording();
+
+ 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;
+}
+
+void SkPicture::endRecording() {
+ if (NULL == fPlayback) {
+ if (NULL != fRecord) {
+ fRecord->endRecording();
+ fPlayback = SkNEW_ARGS(SkPicturePlayback, (*fRecord));
+ fRecord->unref();
+ fRecord = NULL;
+ }
+ }
+ SkASSERT(NULL == fRecord);
+}
+
+void SkPicture::draw(SkCanvas* surface, SkDrawPictureCallback* callback) {
+ this->endRecording();
+ if (fPlayback) {
+ fPlayback->draw(*surface, callback);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkStream.h"
+
+bool SkPicture::StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) {
+ if (NULL == stream) {
+ return false;
+ }
+
+ SkPictInfo info;
+ if (!stream->read(&info, sizeof(SkPictInfo))) {
+ return false;
+ }
+ if (PICTURE_VERSION != info.fVersion) {
+ return false;
+ }
+
+ if (pInfo != NULL) {
+ *pInfo = info;
+ }
+ return true;
+}
+
+SkPicture::SkPicture(SkPicturePlayback* playback, int width, int height)
+ : fPlayback(playback)
+ , fRecord(NULL)
+ , fWidth(width)
+ , fHeight(height) {}
+
+SkPicture* SkPicture::CreateFromStream(SkStream* stream, InstallPixelRefProc proc) {
+ SkPictInfo info;
+
+ if (!StreamIsSKP(stream, &info)) {
+ return NULL;
+ }
+
+ SkPicturePlayback* playback;
+ // Check to see if there is a playback to recreate.
+ if (stream->readBool()) {
+ playback = SkNEW_ARGS(SkPicturePlayback, (stream, info, proc));
+ } else {
+ playback = NULL;
+ }
+
+ return SkNEW_ARGS(SkPicture, (playback, info.fWidth, info.fHeight));
+}
+
+void SkPicture::serialize(SkWStream* stream, EncodeBitmap encoder) const {
+ SkPicturePlayback* playback = fPlayback;
+
+ if (NULL == playback && fRecord) {
+ playback = SkNEW_ARGS(SkPicturePlayback, (*fRecord));
+ }
+
+ SkPictInfo info;
+
+ info.fVersion = PICTURE_VERSION;
+ info.fWidth = fWidth;
+ info.fHeight = fHeight;
+ info.fFlags = SkPictInfo::kCrossProcess_Flag;
+#ifdef SK_SCALAR_IS_FLOAT
+ info.fFlags |= SkPictInfo::kScalarIsFloat_Flag;
+#endif
+ if (8 == sizeof(void*)) {
+ info.fFlags |= SkPictInfo::kPtrIs64Bit_Flag;
+ }
+
+ stream->write(&info, sizeof(info));
+ if (playback) {
+ stream->writeBool(true);
+ playback->serialize(stream, encoder);
+ // delete playback if it is a local version (i.e. cons'd up just now)
+ if (playback != fPlayback) {
+ SkDELETE(playback);
+ }
+ } else {
+ stream->writeBool(false);
+ }
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+void SkPicture::abortPlayback() {
+ if (NULL == fPlayback) {
+ return;
+ }
+ fPlayback->abort();
+}
+#endif
diff --git a/core/SkPictureFlat.cpp b/core/SkPictureFlat.cpp
new file mode 100644
index 00000000..2c6efa2a
--- /dev/null
+++ b/core/SkPictureFlat.cpp
@@ -0,0 +1,152 @@
+
+/*
+ * 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 "SkPictureFlat.h"
+
+#include "SkChecksum.h"
+#include "SkColorFilter.h"
+#include "SkDrawLooper.h"
+#include "SkMaskFilter.h"
+#include "SkRasterizer.h"
+#include "SkShader.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+SK_DEFINE_INST_COUNT(SkFlatController)
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTypefacePlayback::SkTypefacePlayback() : fCount(0), fArray(NULL) {}
+
+SkTypefacePlayback::~SkTypefacePlayback() {
+ this->reset(NULL);
+}
+
+void SkTypefacePlayback::reset(const SkRefCntSet* rec) {
+ for (int i = 0; i < fCount; i++) {
+ SkASSERT(fArray[i]);
+ fArray[i]->unref();
+ }
+ SkDELETE_ARRAY(fArray);
+
+ if (rec!= NULL && rec->count() > 0) {
+ fCount = rec->count();
+ fArray = SkNEW_ARRAY(SkRefCnt*, fCount);
+ rec->copyToArray(fArray);
+ for (int i = 0; i < fCount; i++) {
+ fArray[i]->ref();
+ }
+ } else {
+ fCount = 0;
+ fArray = NULL;
+ }
+}
+
+void SkTypefacePlayback::setCount(int count) {
+ this->reset(NULL);
+
+ fCount = count;
+ fArray = SkNEW_ARRAY(SkRefCnt*, count);
+ sk_bzero(fArray, count * sizeof(SkRefCnt*));
+}
+
+SkRefCnt* SkTypefacePlayback::set(int index, SkRefCnt* obj) {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ SkRefCnt_SafeAssign(fArray[index], obj);
+ return obj;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFlatController::SkFlatController()
+: fBitmapHeap(NULL)
+, fTypefaceSet(NULL)
+, fTypefacePlayback(NULL)
+, fFactorySet(NULL)
+, fWriteBufferFlags(0) {}
+
+SkFlatController::~SkFlatController() {
+ SkSafeUnref(fBitmapHeap);
+ SkSafeUnref(fTypefaceSet);
+ SkSafeUnref(fFactorySet);
+}
+
+void SkFlatController::setBitmapHeap(SkBitmapHeap* heap) {
+ SkRefCnt_SafeAssign(fBitmapHeap, heap);
+}
+
+void SkFlatController::setTypefaceSet(SkRefCntSet *set) {
+ SkRefCnt_SafeAssign(fTypefaceSet, set);
+}
+
+void SkFlatController::setTypefacePlayback(SkTypefacePlayback* playback) {
+ fTypefacePlayback = playback;
+}
+
+SkNamedFactorySet* SkFlatController::setNamedFactorySet(SkNamedFactorySet* set) {
+ SkRefCnt_SafeAssign(fFactorySet, set);
+ return set;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkFlatData::stampHeaderAndSentinel(int index, int32_t size) {
+ fIndex = index;
+ fFlatSize = size;
+ fChecksum = SkChecksum::Compute(this->data32(), size);
+ this->setTopBotUnwritten();
+ this->setSentinelAsCandidate();
+}
+
+SkFlatData* SkFlatData::Create(SkFlatController* controller, const void* obj,
+ int index, void (*flattenProc)(SkOrderedWriteBuffer&, const void*)) {
+ // a buffer of 256 bytes should be sufficient for most paints, regions,
+ // and matrices.
+ intptr_t storage[256];
+ SkOrderedWriteBuffer buffer(256, storage, sizeof(storage));
+
+ buffer.setBitmapHeap(controller->getBitmapHeap());
+ buffer.setTypefaceRecorder(controller->getTypefaceSet());
+ buffer.setNamedFactoryRecorder(controller->getNamedFactorySet());
+ buffer.setFlags(controller->getWriteBufferFlags());
+
+ flattenProc(buffer, obj);
+ uint32_t size = buffer.size();
+ SkASSERT(SkIsAlign4(size));
+
+ /**
+ * Allocate enough memory to hold
+ * 1. SkFlatData struct
+ * 2. flattenProc's data (4-byte aligned)
+ * 3. 4-byte sentinel
+ */
+ size_t allocSize = sizeof(SkFlatData) + size + sizeof(uint32_t);
+ SkFlatData* result = (SkFlatData*) controller->allocThrow(allocSize);
+
+ // put the serialized contents into the data section of the new allocation
+ buffer.writeToMemory(result->data());
+ result->stampHeaderAndSentinel(index, size);
+ return result;
+}
+
+void SkFlatData::unflatten(void* result,
+ void (*unflattenProc)(SkOrderedReadBuffer&, void*),
+ SkBitmapHeap* bitmapHeap,
+ SkTypefacePlayback* facePlayback) const {
+
+ SkOrderedReadBuffer buffer(this->data(), fFlatSize);
+
+ if (bitmapHeap) {
+ buffer.setBitmapStorage(bitmapHeap);
+ }
+ if (facePlayback) {
+ facePlayback->setupBuffer(buffer);
+ }
+
+ unflattenProc(buffer, result);
+ SkASSERT(fFlatSize == (int32_t)buffer.offset());
+}
diff --git a/core/SkPictureFlat.h b/core/SkPictureFlat.h
new file mode 100644
index 00000000..76120f79
--- /dev/null
+++ b/core/SkPictureFlat.h
@@ -0,0 +1,807 @@
+
+/*
+ * 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 SkPictureFlat_DEFINED
+#define SkPictureFlat_DEFINED
+
+//#define SK_DEBUG_SIZE
+
+#include "SkChunkAlloc.h"
+#include "SkBitmap.h"
+#include "SkBitmapHeap.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPicture.h"
+#include "SkPtrRecorder.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkTRefArray.h"
+#include "SkTSearch.h"
+
+enum DrawType {
+ UNUSED,
+ CLIP_PATH,
+ CLIP_REGION,
+ CLIP_RECT,
+ CLIP_RRECT,
+ CONCAT,
+ DRAW_BITMAP,
+ DRAW_BITMAP_MATRIX,
+ DRAW_BITMAP_NINE,
+ DRAW_BITMAP_RECT_TO_RECT,
+ DRAW_CLEAR,
+ DRAW_DATA,
+ DRAW_OVAL,
+ DRAW_PAINT,
+ DRAW_PATH,
+ DRAW_PICTURE,
+ DRAW_POINTS,
+ DRAW_POS_TEXT,
+ DRAW_POS_TEXT_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT
+ 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,
+ DRAW_TEXT_TOP_BOTTOM, // fast variant of DRAW_TEXT
+ DRAW_VERTICES,
+ RESTORE,
+ ROTATE,
+ SAVE,
+ SAVE_LAYER,
+ SCALE,
+ SET_MATRIX,
+ SKEW,
+ TRANSLATE,
+ NOOP,
+ BEGIN_COMMENT_GROUP,
+ COMMENT,
+ END_COMMENT_GROUP,
+
+ LAST_DRAWTYPE_ENUM = END_COMMENT_GROUP
+};
+
+// In the 'match' method, this constant will match any flavor of DRAW_BITMAP*
+static const int kDRAW_BITMAP_FLAVOR = LAST_DRAWTYPE_ENUM+1;
+
+enum DrawVertexFlags {
+ DRAW_VERTICES_HAS_TEXS = 0x01,
+ DRAW_VERTICES_HAS_COLORS = 0x02,
+ DRAW_VERTICES_HAS_INDICES = 0x04
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// clipparams are packed in 5 bits
+// doAA:1 | regionOp:4
+
+static inline uint32_t ClipParams_pack(SkRegion::Op op, bool doAA) {
+ unsigned doAABit = doAA ? 1 : 0;
+ return (doAABit << 4) | op;
+}
+
+static inline SkRegion::Op ClipParams_unpackRegionOp(uint32_t packed) {
+ return (SkRegion::Op)(packed & 0xF);
+}
+
+static inline bool ClipParams_unpackDoAA(uint32_t packed) {
+ return SkToBool((packed >> 4) & 1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkTypefacePlayback {
+public:
+ SkTypefacePlayback();
+ virtual ~SkTypefacePlayback();
+
+ int count() const { return fCount; }
+
+ void reset(const SkRefCntSet*);
+
+ void setCount(int count);
+ SkRefCnt* set(int index, SkRefCnt*);
+
+ void setupBuffer(SkOrderedReadBuffer& buffer) const {
+ buffer.setTypefaceArray((SkTypeface**)fArray, fCount);
+ }
+
+protected:
+ int fCount;
+ SkRefCnt** fArray;
+};
+
+class SkFactoryPlayback {
+public:
+ SkFactoryPlayback(int count) : fCount(count) {
+ fArray = SkNEW_ARRAY(SkFlattenable::Factory, count);
+ }
+
+ ~SkFactoryPlayback() {
+ SkDELETE_ARRAY(fArray);
+ }
+
+ SkFlattenable::Factory* base() const { return fArray; }
+
+ void setupBuffer(SkOrderedReadBuffer& buffer) const {
+ buffer.setFactoryPlayback(fArray, fCount);
+ }
+
+private:
+ int fCount;
+ SkFlattenable::Factory* fArray;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+//
+// The following templated classes provide an efficient way to store and compare
+// objects that have been flattened (i.e. serialized in an ordered binary
+// format).
+//
+// SkFlatData: is a simple indexable container for the flattened data
+// which is agnostic to the type of data is is indexing. It is
+// also responsible for flattening/unflattening objects but
+// details of that operation are hidden in the provided procs
+// SkFlatDictionary: is an abstract templated dictionary that maintains a
+// searchable set of SkFlatData objects of type T.
+// SkFlatController: is an interface provided to SkFlatDictionary which handles
+// allocation (and unallocation in some cases). It also holds
+// ref count recorders and the like.
+//
+// NOTE: any class that wishes to be used in conjunction with SkFlatDictionary
+// must subclass the dictionary and provide the necessary flattening procs.
+// The end of this header contains dictionary subclasses for some common classes
+// like SkBitmap, SkMatrix, SkPaint, and SkRegion. SkFlatController must also
+// be implemented, or SkChunkFlatController can be used to use an
+// SkChunkAllocator and never do replacements.
+//
+//
+///////////////////////////////////////////////////////////////////////////////
+
+class SkFlatData;
+
+class SkFlatController : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkFlatController)
+
+ SkFlatController();
+ virtual ~SkFlatController();
+ /**
+ * Return a new block of memory for the SkFlatDictionary to use.
+ * This memory is owned by the controller and has the same lifetime unless you
+ * call unalloc(), in which case it may be freed early.
+ */
+ virtual void* allocThrow(size_t bytes) = 0;
+
+ /**
+ * Hint that this block, which was allocated with allocThrow, is no longer needed.
+ * The implementation may choose to free this memory any time beteween now and destruction.
+ */
+ virtual void unalloc(void* ptr) = 0;
+
+ /**
+ * Used during creation and unflattening of SkFlatData objects. If the
+ * objects being flattened contain bitmaps they are stored in this heap
+ * and the flattenable stores the index to the bitmap on the heap.
+ * This should be set by the protected setBitmapHeap.
+ */
+ SkBitmapHeap* getBitmapHeap() { return fBitmapHeap; }
+
+ /**
+ * Used during creation of SkFlatData objects. If a typeface recorder is
+ * required to flatten the objects being flattened (i.e. for SkPaints), this
+ * should be set by the protected setTypefaceSet.
+ */
+ SkRefCntSet* getTypefaceSet() { return fTypefaceSet; }
+
+ /**
+ * Used during unflattening of the SkFlatData objects in the
+ * SkFlatDictionary. Needs to be set by the protected setTypefacePlayback
+ * and needs to be reset to the SkRefCntSet passed to setTypefaceSet.
+ */
+ SkTypefacePlayback* getTypefacePlayback() { return fTypefacePlayback; }
+
+ /**
+ * Optional factory recorder used during creation of SkFlatData objects. Set
+ * using the protected method setNamedFactorySet.
+ */
+ SkNamedFactorySet* getNamedFactorySet() { return fFactorySet; }
+
+ /**
+ * Flags to use during creation of SkFlatData objects. Defaults to zero.
+ */
+ uint32_t getWriteBufferFlags() { return fWriteBufferFlags; }
+
+protected:
+ /**
+ * Set an SkBitmapHeap to be used to store/read SkBitmaps. Ref counted.
+ */
+ void setBitmapHeap(SkBitmapHeap*);
+
+ /**
+ * Set an SkRefCntSet to be used to store SkTypefaces during flattening. Ref
+ * counted.
+ */
+ void setTypefaceSet(SkRefCntSet*);
+
+ /**
+ * Set an SkTypefacePlayback to be used to find references to SkTypefaces
+ * during unflattening. Should be reset to the set provided to
+ * setTypefaceSet.
+ */
+ void setTypefacePlayback(SkTypefacePlayback*);
+
+ /**
+ * Set an SkNamedFactorySet to be used to store Factorys and their
+ * corresponding names during flattening. Ref counted. Returns the same
+ * set as a convenience.
+ */
+ SkNamedFactorySet* setNamedFactorySet(SkNamedFactorySet*);
+
+ /**
+ * Set the flags to be used during flattening.
+ */
+ void setWriteBufferFlags(uint32_t flags) { fWriteBufferFlags = flags; }
+
+private:
+ SkBitmapHeap* fBitmapHeap;
+ SkRefCntSet* fTypefaceSet;
+ SkTypefacePlayback* fTypefacePlayback;
+ SkNamedFactorySet* fFactorySet;
+ uint32_t fWriteBufferFlags;
+
+ typedef SkRefCnt INHERITED;
+};
+
+class SkFlatData {
+public:
+ /**
+ * Compare two SkFlatData ptrs, returning -1, 0, 1 to allow them to be
+ * sorted.
+ *
+ * Note: this assumes that a and b have different sentinel values, either
+ * InCache or AsCandidate, otherwise the loop will go beyond the end of
+ * the buffers.
+ *
+ * dataToCompare() returns 2 fields before the flattened data:
+ * - checksum
+ * - size
+ * This ensures that if we see two blocks of different length, we will
+ * notice that right away, and not read any further. It also ensures that
+ * we see the checksum right away, so that most of the time it is enough
+ * to short-circuit our comparison.
+ */
+ static int Compare(const SkFlatData& a, const SkFlatData& b) {
+ const uint32_t* stop = a.dataStop();
+ const uint32_t* a_ptr = a.dataToCompare() - 1;
+ const uint32_t* b_ptr = b.dataToCompare() - 1;
+ // We use -1 above, so we can pre-increment our pointers in the loop
+ while (*++a_ptr == *++b_ptr) {}
+
+ if (a_ptr == stop) { // sentinel
+ SkASSERT(b.dataStop() == b_ptr);
+ return 0;
+ }
+ SkASSERT(a_ptr < a.dataStop());
+ SkASSERT(b_ptr < b.dataStop());
+ return (*a_ptr < *b_ptr) ? -1 : 1;
+ }
+
+ // Adapts Compare to be used with SkTSearch
+ static bool Less(const SkFlatData& a, const SkFlatData& b) {
+ return Compare(a, b) < 0;
+ }
+
+ int index() const { return fIndex; }
+ const void* data() const { return (const char*)this + sizeof(*this); }
+ void* data() { return (char*)this + sizeof(*this); }
+ // Our data is always 32bit aligned, so we can offer this accessor
+ uint32_t* data32() { return (uint32_t*)this->data(); }
+ // Returns the size of the flattened data.
+ size_t flatSize() const { return fFlatSize; }
+
+ void setSentinelInCache() {
+ this->setSentinel(kInCache_Sentinel);
+ }
+ void setSentinelAsCandidate() {
+ this->setSentinel(kCandidate_Sentinel);
+ }
+
+ uint32_t checksum() const { return fChecksum; }
+
+#ifdef SK_DEBUG_SIZE
+ // returns the logical size of our data. Does not return any sentinel or
+ // padding we might have.
+ size_t size() const {
+ return sizeof(SkFlatData) + fFlatSize;
+ }
+#endif
+
+ static SkFlatData* Create(SkFlatController* controller, const void* obj, int index,
+ void (*flattenProc)(SkOrderedWriteBuffer&, const void*));
+
+ void unflatten(void* result,
+ void (*unflattenProc)(SkOrderedReadBuffer&, void*),
+ SkBitmapHeap* bitmapHeap = NULL,
+ SkTypefacePlayback* facePlayback = NULL) const;
+
+ // When we purge an entry, we want to reuse an old index for the new entry,
+ // so we expose this setter.
+ void setIndex(int index) { fIndex = index; }
+
+ // for unittesting
+ friend bool operator==(const SkFlatData& a, const SkFlatData& b) {
+ size_t N = (const char*)a.dataStop() - (const char*)a.dataToCompare();
+ return !memcmp(a.dataToCompare(), b.dataToCompare(), N);
+ }
+
+ // returns true if fTopBot[] has been recorded
+ bool isTopBotWritten() const {
+ return !SkScalarIsNaN(fTopBot[0]);
+ }
+
+ // 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() const {
+ SkASSERT(!this->isTopBotWritten());
+ return fTopBot;
+ }
+
+ // return the topbot[] after it has been recorded
+ const SkScalar* topBot() const {
+ SkASSERT(this->isTopBotWritten());
+ return fTopBot;
+ }
+
+private:
+ // This is *not* part of the key for search/sort
+ int fIndex;
+
+ // Cache of paint's FontMetrics fTop,fBottom
+ // initialied to [NaN,NaN] as a sentinel that they have not been recorded yet
+ //
+ // This is *not* part of the key for search/sort
+ mutable SkScalar fTopBot[2];
+
+ // marks fTopBot[] as unrecorded
+ void setTopBotUnwritten() {
+ this->fTopBot[0] = SK_ScalarNaN; // initial to sentinel values
+ }
+
+ // 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;
+ int32_t fFlatSize; // size of flattened data
+ // uint32_t flattenedData[]
+ // uint32_t sentinelValue
+
+ const uint32_t* dataToCompare() const {
+ return (const uint32_t*)&fChecksum;
+ }
+ const uint32_t* dataStop() const {
+ SkASSERT(SkIsAlign4(fFlatSize));
+ return (const uint32_t*)((const char*)this->data() + fFlatSize);
+ }
+
+ enum {
+ kInCache_Sentinel = 0,
+ kCandidate_Sentinel = ~0U,
+ };
+ void setSentinel(uint32_t value) {
+ SkASSERT(SkIsAlign4(fFlatSize));
+ this->data32()[fFlatSize >> 2] = value;
+ }
+
+ // This does not modify the payload flat data, in case it's already been written.
+ void stampHeaderAndSentinel(int index, int32_t size);
+ template <class T> friend class SkFlatDictionary; // For stampHeaderAndSentinel().
+};
+
+template <class T>
+class SkFlatDictionary {
+ static const size_t kWriteBufferGrowthBytes = 1024;
+
+public:
+ SkFlatDictionary(SkFlatController* controller, size_t scratchSizeGuess = 0)
+ : fFlattenProc(NULL)
+ , fUnflattenProc(NULL)
+ , fController(SkRef(controller))
+ , fScratchSize(scratchSizeGuess)
+ , fScratch(AllocScratch(fScratchSize))
+ , fWriteBuffer(kWriteBufferGrowthBytes)
+ , fWriteBufferReady(false)
+ , fNextIndex(1) { // set to 1 since returning a zero from find() indicates failure
+ sk_bzero(fHash, sizeof(fHash));
+ // index 0 is always empty since it is used as a signal that find failed
+ fIndexedData.push(NULL);
+ }
+
+ ~SkFlatDictionary() {
+ sk_free(fScratch);
+ }
+
+ int count() const {
+ SkASSERT(fIndexedData.count() == fSortedData.count()+1);
+ return fSortedData.count();
+ }
+
+ const SkFlatData* operator[](int index) const {
+ SkASSERT(index >= 0 && index < fSortedData.count());
+ return fSortedData[index];
+ }
+
+ /**
+ * Clears the dictionary of all entries. However, it does NOT free the
+ * memory that was allocated for each entry.
+ */
+ void reset() {
+ fSortedData.reset();
+ fIndexedData.rewind();
+ // index 0 is always empty since it is used as a signal that find failed
+ fIndexedData.push(NULL);
+ fNextIndex = 1;
+ sk_bzero(fHash, sizeof(fHash));
+ }
+
+ /**
+ * Similar to find. Allows the caller to specify an SkFlatData to replace in
+ * the case of an add. Also tells the caller whether a new SkFlatData was
+ * added and whether the old one was replaced. The parameters added and
+ * replaced are required to be non-NULL. Rather than returning the index of
+ * the entry in the dictionary, it returns the actual SkFlatData.
+ */
+ const SkFlatData* findAndReplace(const T& element,
+ const SkFlatData* toReplace, bool* added,
+ bool* replaced) {
+ SkASSERT(added != NULL && replaced != NULL);
+ int oldCount = fSortedData.count();
+ const SkFlatData* flat = this->findAndReturnFlat(element);
+ *added = fSortedData.count() == oldCount + 1;
+ *replaced = false;
+ if (*added && toReplace != NULL) {
+ // First, find the index of the one to replace
+ int indexToReplace = fSortedData.find(toReplace);
+ if (indexToReplace >= 0) {
+ // findAndReturnFlat set the index to fNextIndex and increased
+ // fNextIndex by one. Reuse the index from the one being
+ // replaced and reset fNextIndex to the proper value.
+ int oldIndex = flat->index();
+ const_cast<SkFlatData*>(flat)->setIndex(toReplace->index());
+ fIndexedData[toReplace->index()] = flat;
+ fNextIndex--;
+ // Remove from the arrays.
+ fSortedData.remove(indexToReplace);
+ fIndexedData.remove(oldIndex);
+ // Remove from the hash table.
+ int oldHash = ChecksumToHashIndex(toReplace->checksum());
+ if (fHash[oldHash] == toReplace) {
+ fHash[oldHash] = NULL;
+ }
+ // Delete the actual object.
+ fController->unalloc((void*)toReplace);
+ *replaced = true;
+ SkASSERT(fIndexedData.count() == fSortedData.count()+1);
+ }
+ }
+ return flat;
+ }
+
+ /**
+ * Given an element of type T return its 1-based index in the dictionary. If
+ * the element wasn't previously in the dictionary it is automatically
+ * added.
+ *
+ * To make the Compare function fast, we write a sentinel value at the end
+ * of each block. The blocks in our fSortedData[] all have a 0 sentinel. The
+ * newly created block we're comparing against has a -1 in the sentinel.
+ *
+ * This trick allows Compare to always loop until failure. If it fails on
+ * the sentinal value, we know the blocks are equal.
+ */
+ int find(const T& element) {
+ return this->findAndReturnFlat(element)->index();
+ }
+
+ /**
+ * Unflatten the objects and return them in SkTRefArray, or return NULL
+ * if there no objects (instead of an empty array).
+ */
+ SkTRefArray<T>* unflattenToArray() const {
+ int count = fSortedData.count();
+ SkTRefArray<T>* array = NULL;
+ if (count > 0) {
+ array = SkTRefArray<T>::Create(count);
+ this->unflattenIntoArray(&array->writableAt(0));
+ }
+ return array;
+ }
+
+ /**
+ * Unflatten the specific object at the given index
+ */
+ T* unflatten(int index) const {
+ SkASSERT(fIndexedData.count() == fSortedData.count()+1);
+ const SkFlatData* element = fIndexedData[index];
+ SkASSERT(index == element->index());
+
+ T* dst = new T;
+ this->unflatten(dst, element);
+ return dst;
+ }
+
+ const SkFlatData* findAndReturnFlat(const T& element) {
+ // Only valid until the next call to resetScratch().
+ const SkFlatData& scratch = this->resetScratch(element, fNextIndex);
+
+ // See if we have it in the hash?
+ const int hashIndex = ChecksumToHashIndex(scratch.checksum());
+ const SkFlatData* candidate = fHash[hashIndex];
+ if (candidate != NULL && SkFlatData::Compare(scratch, *candidate) == 0) {
+ return candidate;
+ }
+
+ // See if we have it at all?
+ const int index = SkTSearch<const SkFlatData, SkFlatData::Less>(fSortedData.begin(),
+ fSortedData.count(),
+ &scratch,
+ sizeof(&scratch));
+ if (index >= 0) {
+ // Found. Update hash before we return.
+ fHash[hashIndex] = fSortedData[index];
+ return fSortedData[index];
+ }
+
+ // We don't have it. Add it.
+ SkFlatData* detached = this->detachScratch();
+ // detached will live beyond the next call to resetScratch(), but is owned by fController.
+ *fSortedData.insert(~index) = detached; // SkTSearch returned bit-not of where to insert.
+ *fIndexedData.insert(detached->index()) = detached;
+ fHash[hashIndex] = detached;
+
+ SkASSERT(detached->index() == fNextIndex);
+ SkASSERT(fSortedData.count() == fNextIndex);
+ SkASSERT(fIndexedData.count() == fNextIndex+1);
+ fNextIndex++;
+
+ return detached;
+ }
+
+protected:
+ void (*fFlattenProc)(SkOrderedWriteBuffer&, const void*);
+ void (*fUnflattenProc)(SkOrderedReadBuffer&, void*);
+
+private:
+ // Layout: [ SkFlatData header, 20 bytes ] [ data ..., 4-byte aligned ] [ sentinel, 4 bytes]
+ static size_t SizeWithPadding(size_t flatDataSize) {
+ SkASSERT(SkIsAlign4(flatDataSize));
+ return sizeof(SkFlatData) + flatDataSize + sizeof(uint32_t);
+ }
+
+ // Allocate a new scratch SkFlatData. Must be sk_freed.
+ static SkFlatData* AllocScratch(size_t scratchSize) {
+ return (SkFlatData*) sk_malloc_throw(SizeWithPadding(scratchSize));
+ }
+
+ // We have to delay fWriteBuffer's initialization until its first use; fController might not
+ // be fully set up by the time we get it in the constructor.
+ void lazyWriteBufferInit() {
+ if (fWriteBufferReady) {
+ return;
+ }
+ // Without a bitmap heap, we'll flatten bitmaps into paints. That's never what you want.
+ SkASSERT(fController->getBitmapHeap() != NULL);
+ fWriteBuffer.setBitmapHeap(fController->getBitmapHeap());
+ fWriteBuffer.setTypefaceRecorder(fController->getTypefaceSet());
+ fWriteBuffer.setNamedFactoryRecorder(fController->getNamedFactorySet());
+ fWriteBuffer.setFlags(fController->getWriteBufferFlags());
+ fWriteBufferReady = true;
+ }
+
+ // This reference is valid only until the next call to resetScratch() or detachScratch().
+ const SkFlatData& resetScratch(const T& element, int index) {
+ this->lazyWriteBufferInit();
+
+ // Flatten element into fWriteBuffer (using fScratch as storage).
+ fWriteBuffer.reset(fScratch->data(), fScratchSize);
+ fFlattenProc(fWriteBuffer, &element);
+ const size_t bytesWritten = fWriteBuffer.bytesWritten();
+
+ // If all the flattened bytes fit into fScratch, we can skip a call to writeToMemory.
+ if (!fWriteBuffer.wroteOnlyToStorage()) {
+ SkASSERT(bytesWritten > fScratchSize);
+ // It didn't all fit. Copy into a larger replacement SkFlatData.
+ // We can't just realloc because it might move the pointer and confuse writeToMemory.
+ SkFlatData* larger = AllocScratch(bytesWritten);
+ fWriteBuffer.writeToMemory(larger->data());
+
+ // Carry on with this larger scratch to minimize the likelihood of future resizing.
+ sk_free(fScratch);
+ fScratchSize = bytesWritten;
+ fScratch = larger;
+ }
+
+ // The data is in fScratch now, but we need to stamp its header and trailing sentinel.
+ fScratch->stampHeaderAndSentinel(index, bytesWritten);
+ return *fScratch;
+ }
+
+ // This result is owned by fController and lives as long as it does (unless unalloc'd).
+ SkFlatData* detachScratch() {
+ // Allocate a new SkFlatData exactly big enough to hold our current scratch.
+ // We use the controller for this allocation to extend the allocation's lifetime and allow
+ // the controller to do whatever memory management it wants.
+ const size_t paddedSize = SizeWithPadding(fScratch->flatSize());
+ SkFlatData* detached = (SkFlatData*)fController->allocThrow(paddedSize);
+
+ // Copy scratch into the new SkFlatData, setting the sentinel for cache storage.
+ memcpy(detached, fScratch, paddedSize);
+ detached->setSentinelInCache();
+
+ // We can now reuse fScratch, and detached will live until fController dies.
+ return detached;
+ }
+
+ void unflatten(T* dst, const SkFlatData* element) const {
+ element->unflatten(dst, fUnflattenProc,
+ fController->getBitmapHeap(),
+ fController->getTypefacePlayback());
+ }
+
+ void unflattenIntoArray(T* array) const {
+ const int count = fSortedData.count();
+ SkASSERT(fIndexedData.count() == fSortedData.count()+1);
+ const SkFlatData* const* iter = fSortedData.begin();
+ for (int i = 0; i < count; ++i) {
+ const SkFlatData* element = iter[i];
+ int index = element->index() - 1;
+ SkASSERT((unsigned)index < (unsigned)count);
+ unflatten(&array[index], element);
+ }
+ }
+
+ SkAutoTUnref<SkFlatController> fController;
+ size_t fScratchSize; // How many bytes fScratch has allocated for data itself.
+ SkFlatData* fScratch; // Owned, must be freed with sk_free.
+ SkOrderedWriteBuffer fWriteBuffer;
+ bool fWriteBufferReady;
+
+ // SkFlatDictionary has two copies of the data one indexed by the
+ // SkFlatData's index and the other sorted. The sorted data is used
+ // for finding and uniquification while the indexed copy is used
+ // for standard array-style lookups based on the SkFlatData's index
+ // (as in 'unflatten').
+ int fNextIndex;
+ SkTDArray<const SkFlatData*> fIndexedData;
+ // fSortedData is sorted by checksum/size/data.
+ SkTDArray<const SkFlatData*> fSortedData;
+
+ enum {
+ // Determined by trying diff values on picture-recording benchmarks
+ // (e.g. PictureRecordBench.cpp), choosing the smallest value that
+ // showed a big improvement. Even better would be to benchmark diff
+ // values on recording representative web-pages or other "real" content.
+ HASH_BITS = 7,
+ HASH_MASK = (1 << HASH_BITS) - 1,
+ HASH_COUNT = 1 << HASH_BITS
+ };
+ const SkFlatData* fHash[HASH_COUNT];
+
+ static int ChecksumToHashIndex(uint32_t checksum) {
+ int n = checksum;
+ if (HASH_BITS < 32) {
+ n ^= n >> 16;
+ }
+ if (HASH_BITS < 16) {
+ n ^= n >> 8;
+ }
+ if (HASH_BITS < 8) {
+ n ^= n >> 4;
+ }
+ return n & HASH_MASK;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Some common dictionaries are defined here for both reference and convenience
+///////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+static void SkFlattenObjectProc(SkOrderedWriteBuffer& buffer, const void* obj) {
+ ((T*)obj)->flatten(buffer);
+}
+
+template <class T>
+static void SkUnflattenObjectProc(SkOrderedReadBuffer& buffer, void* obj) {
+ ((T*)obj)->unflatten(buffer);
+}
+
+class SkChunkFlatController : public SkFlatController {
+public:
+ SkChunkFlatController(size_t minSize)
+ : fHeap(minSize)
+ , fTypefaceSet(SkNEW(SkRefCntSet))
+ , fLastAllocated(NULL) {
+ this->setTypefaceSet(fTypefaceSet);
+ this->setTypefacePlayback(&fTypefacePlayback);
+ }
+
+ virtual void* allocThrow(size_t bytes) SK_OVERRIDE {
+ fLastAllocated = fHeap.allocThrow(bytes);
+ return fLastAllocated;
+ }
+
+ virtual void unalloc(void* ptr) SK_OVERRIDE {
+ // fHeap can only free a pointer if it was the last one allocated. Otherwise, we'll just
+ // have to wait until fHeap is destroyed.
+ if (ptr == fLastAllocated) (void)fHeap.unalloc(ptr);
+ }
+
+ void setupPlaybacks() const {
+ fTypefacePlayback.reset(fTypefaceSet.get());
+ }
+
+ void setBitmapStorage(SkBitmapHeap* heap) {
+ this->setBitmapHeap(heap);
+ }
+
+private:
+ SkChunkAlloc fHeap;
+ SkAutoTUnref<SkRefCntSet> fTypefaceSet;
+ void* fLastAllocated;
+ mutable SkTypefacePlayback fTypefacePlayback;
+};
+
+class SkMatrixDictionary : public SkFlatDictionary<SkMatrix> {
+ public:
+ // All matrices fit in 36 bytes.
+ SkMatrixDictionary(SkFlatController* controller)
+ : SkFlatDictionary<SkMatrix>(controller, 36) {
+ fFlattenProc = &flattenMatrix;
+ fUnflattenProc = &unflattenMatrix;
+ }
+
+ static void flattenMatrix(SkOrderedWriteBuffer& buffer, const void* obj) {
+ buffer.getWriter32()->writeMatrix(*(SkMatrix*)obj);
+ }
+
+ static void unflattenMatrix(SkOrderedReadBuffer& buffer, void* obj) {
+ buffer.getReader32()->readMatrix((SkMatrix*)obj);
+ }
+};
+
+class SkPaintDictionary : public SkFlatDictionary<SkPaint> {
+ public:
+ // The largest paint across ~60 .skps was 500 bytes.
+ SkPaintDictionary(SkFlatController* controller)
+ : SkFlatDictionary<SkPaint>(controller, 512) {
+ fFlattenProc = &SkFlattenObjectProc<SkPaint>;
+ fUnflattenProc = &SkUnflattenObjectProc<SkPaint>;
+ }
+};
+
+class SkRegionDictionary : public SkFlatDictionary<SkRegion> {
+ public:
+ SkRegionDictionary(SkFlatController* controller)
+ : SkFlatDictionary<SkRegion>(controller) {
+ fFlattenProc = &flattenRegion;
+ fUnflattenProc = &unflattenRegion;
+ }
+
+ static void flattenRegion(SkOrderedWriteBuffer& buffer, const void* obj) {
+ buffer.getWriter32()->writeRegion(*(SkRegion*)obj);
+ }
+
+ static void unflattenRegion(SkOrderedReadBuffer& buffer, void* obj) {
+ buffer.getReader32()->readRegion((SkRegion*)obj);
+ }
+};
+
+#endif
diff --git a/core/SkPicturePlayback.cpp b/core/SkPicturePlayback.cpp
new file mode 100644
index 00000000..fe1a037c
--- /dev/null
+++ b/core/SkPicturePlayback.cpp
@@ -0,0 +1,1618 @@
+
+/*
+ * 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 "SkPicturePlayback.h"
+#include "SkPictureRecord.h"
+#include "SkTypeface.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include <new>
+#include "SkBBoxHierarchy.h"
+#include "SkPictureStateTree.h"
+#include "SkTSort.h"
+
+template <typename T> int SafeCount(const T* obj) {
+ return obj ? obj->count() : 0;
+}
+
+/* Define this to spew out a debug statement whenever we skip the remainder of
+ a save/restore block because a clip... command returned false (empty).
+ */
+#define SPEW_CLIP_SKIPPINGx
+
+SkPicturePlayback::SkPicturePlayback() {
+ this->init();
+}
+
+SkPicturePlayback::SkPicturePlayback(const SkPictureRecord& record, bool deepCopy) {
+#ifdef SK_DEBUG_SIZE
+ size_t overallBytes, bitmapBytes, matricesBytes,
+ paintBytes, pathBytes, pictureBytes, regionBytes;
+ int bitmaps = record.bitmaps(&bitmapBytes);
+ int matrices = record.matrices(&matricesBytes);
+ int paints = record.paints(&paintBytes);
+ int paths = record.paths(&pathBytes);
+ int pictures = record.pictures(&pictureBytes);
+ int regions = record.regions(&regionBytes);
+ SkDebugf("picture record mem used %zd (stream %zd) ", record.size(),
+ record.streamlen());
+ if (bitmaps != 0)
+ SkDebugf("bitmaps size %zd (bitmaps:%d) ", bitmapBytes, bitmaps);
+ if (matrices != 0)
+ SkDebugf("matrices size %zd (matrices:%d) ", matricesBytes, matrices);
+ if (paints != 0)
+ SkDebugf("paints size %zd (paints:%d) ", paintBytes, paints);
+ if (paths != 0)
+ SkDebugf("paths size %zd (paths:%d) ", pathBytes, paths);
+ if (pictures != 0)
+ SkDebugf("pictures size %zd (pictures:%d) ", pictureBytes, pictures);
+ if (regions != 0)
+ SkDebugf("regions size %zd (regions:%d) ", regionBytes, regions);
+ if (record.fPointWrites != 0)
+ SkDebugf("points size %zd (points:%d) ", record.fPointBytes, record.fPointWrites);
+ if (record.fRectWrites != 0)
+ SkDebugf("rects size %zd (rects:%d) ", record.fRectBytes, record.fRectWrites);
+ if (record.fTextWrites != 0)
+ SkDebugf("text size %zd (text strings:%d) ", record.fTextBytes, record.fTextWrites);
+
+ SkDebugf("\n");
+#endif
+#ifdef SK_DEBUG_DUMP
+ record.dumpMatrices();
+ record.dumpPaints();
+#endif
+
+ record.validate(record.writeStream().size(), 0);
+ const SkWriter32& writer = record.writeStream();
+ init();
+ if (writer.size() == 0) {
+ fOpData = SkData::NewEmpty();
+ return;
+ }
+
+ fBoundingHierarchy = record.fBoundingHierarchy;
+ fStateTree = record.fStateTree;
+
+ SkSafeRef(fBoundingHierarchy);
+ SkSafeRef(fStateTree);
+
+ if (NULL != fBoundingHierarchy) {
+ fBoundingHierarchy->flushDeferredInserts();
+ }
+
+ {
+ size_t size = writer.size();
+ void* buffer = sk_malloc_throw(size);
+ writer.flatten(buffer);
+ SkASSERT(!fOpData);
+ fOpData = SkData::NewFromMalloc(buffer, size);
+ }
+
+ // copy over the refcnt dictionary to our reader
+ record.fFlattenableHeap.setupPlaybacks();
+
+ fBitmaps = record.fBitmapHeap->extractBitmaps();
+ fMatrices = record.fMatrices.unflattenToArray();
+ fPaints = record.fPaints.unflattenToArray();
+ fRegions = record.fRegions.unflattenToArray();
+
+ fBitmapHeap.reset(SkSafeRef(record.fBitmapHeap));
+ fPathHeap.reset(SkSafeRef(record.fPathHeap));
+
+ // ensure that the paths bounds are pre-computed
+ if (fPathHeap.get()) {
+ for (int i = 0; i < fPathHeap->count(); i++) {
+ (*fPathHeap)[i].updateBoundsCache();
+ }
+ }
+
+ const SkTDArray<SkPicture* >& pictures = record.getPictureRefs();
+ fPictureCount = pictures.count();
+ if (fPictureCount > 0) {
+ fPictureRefs = SkNEW_ARRAY(SkPicture*, fPictureCount);
+ for (int i = 0; i < fPictureCount; i++) {
+ if (deepCopy) {
+ fPictureRefs[i] = pictures[i]->clone();
+ } else {
+ fPictureRefs[i] = pictures[i];
+ fPictureRefs[i]->ref();
+ }
+ }
+ }
+
+#ifdef SK_DEBUG_SIZE
+ int overall = fPlayback->size(&overallBytes);
+ bitmaps = fPlayback->bitmaps(&bitmapBytes);
+ paints = fPlayback->paints(&paintBytes);
+ paths = fPlayback->paths(&pathBytes);
+ pictures = fPlayback->pictures(&pictureBytes);
+ regions = fPlayback->regions(&regionBytes);
+ SkDebugf("playback size %zd (objects:%d) ", overallBytes, overall);
+ if (bitmaps != 0)
+ SkDebugf("bitmaps size %zd (bitmaps:%d) ", bitmapBytes, bitmaps);
+ if (paints != 0)
+ SkDebugf("paints size %zd (paints:%d) ", paintBytes, paints);
+ if (paths != 0)
+ SkDebugf("paths size %zd (paths:%d) ", pathBytes, paths);
+ if (pictures != 0)
+ SkDebugf("pictures size %zd (pictures:%d) ", pictureBytes, pictures);
+ if (regions != 0)
+ SkDebugf("regions size %zd (regions:%d) ", regionBytes, regions);
+ SkDebugf("\n");
+#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();
+
+ fBitmapHeap.reset(SkSafeRef(src.fBitmapHeap.get()));
+ fPathHeap.reset(SkSafeRef(src.fPathHeap.get()));
+
+ fMatrices = SkSafeRef(src.fMatrices);
+ fRegions = SkSafeRef(src.fRegions);
+ fOpData = SkSafeRef(src.fOpData);
+
+ fBoundingHierarchy = src.fBoundingHierarchy;
+ fStateTree = src.fStateTree;
+
+ SkSafeRef(fBoundingHierarchy);
+ SkSafeRef(fStateTree);
+
+ if (deepCopyInfo) {
+ int paintCount = SafeCount(src.fPaints);
+
+ if (src.fBitmaps) {
+ fBitmaps = SkTRefArray<SkBitmap>::Create(src.fBitmaps->begin(), src.fBitmaps->count());
+ }
+
+ if (!deepCopyInfo->initialized) {
+ /* The alternative to doing this is to have a clone method on the paint and have it make
+ * the deep copy of its internal structures as needed. The holdup to doing that is at
+ * this point we would need to pass the SkBitmapHeap so that we don't unnecessarily
+ * flatten the pixels in a bitmap shader.
+ */
+ deepCopyInfo->paintData.setCount(paintCount);
+
+ /* 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 < paintCount; i++) {
+ 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);
+
+ // needed to create typeface playback
+ deepCopyInfo->controller.setupPlaybacks();
+ deepCopyInfo->initialized = true;
+ }
+
+ fPaints = SkTRefArray<SkPaint>::Create(paintCount);
+ SkASSERT(deepCopyInfo->paintData.count() == paintCount);
+ SkBitmapHeap* bmHeap = deepCopyInfo->controller.getBitmapHeap();
+ SkTypefacePlayback* tfPlayback = deepCopyInfo->controller.getTypefacePlayback();
+ for (int i = 0; i < paintCount; i++) {
+ 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 {
+ fBitmaps = SkSafeRef(src.fBitmaps);
+ fPaints = SkSafeRef(src.fPaints);
+ }
+
+ fPictureCount = src.fPictureCount;
+ fPictureRefs = SkNEW_ARRAY(SkPicture*, fPictureCount);
+ for (int i = 0; i < fPictureCount; i++) {
+ if (deepCopyInfo) {
+ fPictureRefs[i] = src.fPictureRefs[i]->clone();
+ } else {
+ fPictureRefs[i] = src.fPictureRefs[i];
+ fPictureRefs[i]->ref();
+ }
+ }
+}
+
+void SkPicturePlayback::init() {
+ fBitmaps = NULL;
+ fMatrices = NULL;
+ fPaints = NULL;
+ fPictureRefs = NULL;
+ fRegions = NULL;
+ fPictureCount = 0;
+ fOpData = NULL;
+ fFactoryPlayback = NULL;
+ fBoundingHierarchy = NULL;
+ fStateTree = NULL;
+}
+
+SkPicturePlayback::~SkPicturePlayback() {
+ fOpData->unref();
+
+ SkSafeUnref(fBitmaps);
+ SkSafeUnref(fMatrices);
+ SkSafeUnref(fPaints);
+ SkSafeUnref(fRegions);
+ SkSafeUnref(fBoundingHierarchy);
+ SkSafeUnref(fStateTree);
+
+ for (int i = 0; i < fPictureCount; i++) {
+ fPictureRefs[i]->unref();
+ }
+ SkDELETE_ARRAY(fPictureRefs);
+
+ SkDELETE(fFactoryPlayback);
+}
+
+void SkPicturePlayback::dumpSize() const {
+ SkDebugf("--- picture size: ops=%d bitmaps=%d [%d] matrices=%d [%d] paints=%d [%d] paths=%d regions=%d\n",
+ fOpData->size(),
+ SafeCount(fBitmaps), SafeCount(fBitmaps) * sizeof(SkBitmap),
+ SafeCount(fMatrices), SafeCount(fMatrices) * sizeof(SkMatrix),
+ SafeCount(fPaints), SafeCount(fPaints) * sizeof(SkPaint),
+ SafeCount(fPathHeap.get()),
+ SafeCount(fRegions));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define PICT_READER_TAG SkSetFourByteTag('r', 'e', 'a', 'd')
+#define PICT_FACTORY_TAG SkSetFourByteTag('f', 'a', 'c', 't')
+#define PICT_TYPEFACE_TAG SkSetFourByteTag('t', 'p', 'f', 'c')
+#define PICT_PICTURE_TAG SkSetFourByteTag('p', 'c', 't', 'r')
+
+// This tag specifies the size of the ReadBuffer, needed for the following tags
+#define PICT_BUFFER_SIZE_TAG SkSetFourByteTag('a', 'r', 'a', 'y')
+// these are all inside the ARRAYS tag
+#define PICT_BITMAP_BUFFER_TAG SkSetFourByteTag('b', 't', 'm', 'p')
+#define PICT_MATRIX_BUFFER_TAG SkSetFourByteTag('m', 't', 'r', 'x')
+#define PICT_PAINT_BUFFER_TAG SkSetFourByteTag('p', 'n', 't', ' ')
+#define PICT_PATH_BUFFER_TAG SkSetFourByteTag('p', 't', 'h', ' ')
+#define PICT_REGION_BUFFER_TAG SkSetFourByteTag('r', 'g', 'n', ' ')
+
+// Always write this guy last (with no length field afterwards)
+#define PICT_EOF_TAG SkSetFourByteTag('e', 'o', 'f', ' ')
+
+#include "SkStream.h"
+
+static void writeTagSize(SkOrderedWriteBuffer& buffer, uint32_t tag,
+ uint32_t size) {
+ buffer.writeUInt(tag);
+ buffer.writeUInt(size);
+}
+
+static void writeTagSize(SkWStream* stream, uint32_t tag,
+ uint32_t size) {
+ stream->write32(tag);
+ stream->write32(size);
+}
+
+static void writeFactories(SkWStream* stream, const SkFactorySet& rec) {
+ int count = rec.count();
+
+ writeTagSize(stream, PICT_FACTORY_TAG, count);
+
+ SkAutoSTMalloc<16, SkFlattenable::Factory> storage(count);
+ SkFlattenable::Factory* array = (SkFlattenable::Factory*)storage.get();
+ rec.copyToArray(array);
+
+ for (int i = 0; i < count; i++) {
+ const char* name = SkFlattenable::FactoryToName(array[i]);
+// SkDebugf("---- write factories [%d] %p <%s>\n", i, array[i], name);
+ if (NULL == name || 0 == *name) {
+ stream->writePackedUInt(0);
+ } else {
+ uint32_t len = strlen(name);
+ stream->writePackedUInt(len);
+ stream->write(name, len);
+ }
+ }
+}
+
+static void writeTypefaces(SkWStream* stream, const SkRefCntSet& rec) {
+ int count = rec.count();
+
+ writeTagSize(stream, PICT_TYPEFACE_TAG, count);
+
+ SkAutoSTMalloc<16, SkTypeface*> storage(count);
+ SkTypeface** array = (SkTypeface**)storage.get();
+ rec.copyToArray((SkRefCnt**)array);
+
+ for (int i = 0; i < count; i++) {
+ array[i]->serialize(stream);
+ }
+}
+
+void SkPicturePlayback::flattenToBuffer(SkOrderedWriteBuffer& buffer) const {
+ int i, n;
+
+ if ((n = SafeCount(fBitmaps)) > 0) {
+ writeTagSize(buffer, PICT_BITMAP_BUFFER_TAG, n);
+ for (i = 0; i < n; i++) {
+ buffer.writeBitmap((*fBitmaps)[i]);
+ }
+ }
+
+ if ((n = SafeCount(fMatrices)) > 0) {
+ writeTagSize(buffer, PICT_MATRIX_BUFFER_TAG, n);
+ for (i = 0; i < n; i++) {
+ buffer.writeMatrix((*fMatrices)[i]);
+ }
+
+ }
+
+ if ((n = SafeCount(fPaints)) > 0) {
+ writeTagSize(buffer, PICT_PAINT_BUFFER_TAG, n);
+ for (i = 0; i < n; i++) {
+ buffer.writePaint((*fPaints)[i]);
+ }
+ }
+
+ if ((n = SafeCount(fPathHeap.get())) > 0) {
+ writeTagSize(buffer, PICT_PATH_BUFFER_TAG, n);
+ fPathHeap->flatten(buffer);
+ }
+
+ if ((n = SafeCount(fRegions)) > 0) {
+ writeTagSize(buffer, PICT_REGION_BUFFER_TAG, n);
+ for (i = 0; i < n; i++) {
+ buffer.writeRegion((*fRegions)[i]);
+ }
+ }
+}
+
+void SkPicturePlayback::serialize(SkWStream* stream,
+ SkPicture::EncodeBitmap encoder) const {
+ writeTagSize(stream, PICT_READER_TAG, fOpData->size());
+ stream->write(fOpData->bytes(), fOpData->size());
+
+ if (fPictureCount > 0) {
+ writeTagSize(stream, PICT_PICTURE_TAG, fPictureCount);
+ for (int i = 0; i < fPictureCount; i++) {
+ fPictureRefs[i]->serialize(stream, encoder);
+ }
+ }
+
+ // Write some of our data into a writebuffer, and then serialize that
+ // into our stream
+ {
+ SkRefCntSet typefaceSet;
+ SkFactorySet factSet;
+
+ SkOrderedWriteBuffer buffer(1024);
+
+ buffer.setFlags(SkFlattenableWriteBuffer::kCrossProcess_Flag);
+ buffer.setTypefaceRecorder(&typefaceSet);
+ buffer.setFactoryRecorder(&factSet);
+ buffer.setBitmapEncoder(encoder);
+
+ this->flattenToBuffer(buffer);
+
+ // We have to write these to sets into the stream *before* we write
+ // the buffer, since parsing that buffer will require that we already
+ // have these sets available to use.
+ writeFactories(stream, factSet);
+ writeTypefaces(stream, typefaceSet);
+
+ writeTagSize(stream, PICT_BUFFER_SIZE_TAG, buffer.size());
+ buffer.writeToStream(stream);
+ }
+
+ stream->write32(PICT_EOF_TAG);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Return the corresponding SkFlattenableReadBuffer flags, given a set of
+ * SkPictInfo flags.
+ */
+static uint32_t pictInfoFlagsToReadBufferFlags(uint32_t pictInfoFlags) {
+ static const struct {
+ uint32_t fSrc;
+ uint32_t fDst;
+ } gSD[] = {
+ { SkPictInfo::kCrossProcess_Flag, SkFlattenableReadBuffer::kCrossProcess_Flag },
+ { SkPictInfo::kScalarIsFloat_Flag, SkFlattenableReadBuffer::kScalarIsFloat_Flag },
+ { SkPictInfo::kPtrIs64Bit_Flag, SkFlattenableReadBuffer::kPtrIs64Bit_Flag },
+ };
+
+ uint32_t rbMask = 0;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gSD); ++i) {
+ if (pictInfoFlags & gSD[i].fSrc) {
+ rbMask |= gSD[i].fDst;
+ }
+ }
+ return rbMask;
+}
+
+void SkPicturePlayback::parseStreamTag(SkStream* stream, const SkPictInfo& info, uint32_t tag,
+ size_t size, SkPicture::InstallPixelRefProc proc) {
+ /*
+ * By the time we encounter BUFFER_SIZE_TAG, we need to have already seen
+ * its dependents: FACTORY_TAG and TYPEFACE_TAG. These two are not required
+ * but if they are present, they need to have been seen before the buffer.
+ *
+ * We assert that if/when we see either of these, that we have not yet seen
+ * the buffer tag, because if we have, then its too-late to deal with the
+ * factories or typefaces.
+ */
+ SkDEBUGCODE(bool haveBuffer = false;)
+
+ switch (tag) {
+ case PICT_READER_TAG: {
+ void* storage = sk_malloc_throw(size);
+ stream->read(storage, size);
+ SkASSERT(NULL == fOpData);
+ fOpData = SkData::NewFromMalloc(storage, size);
+ } break;
+ case PICT_FACTORY_TAG: {
+ SkASSERT(!haveBuffer);
+ fFactoryPlayback = SkNEW_ARGS(SkFactoryPlayback, (size));
+ for (size_t i = 0; i < size; i++) {
+ SkString str;
+ int len = stream->readPackedUInt();
+ str.resize(len);
+ stream->read(str.writable_str(), len);
+ fFactoryPlayback->base()[i] = SkFlattenable::NameToFactory(str.c_str());
+ }
+ } break;
+ case PICT_TYPEFACE_TAG: {
+ SkASSERT(!haveBuffer);
+ fTFPlayback.setCount(size);
+ for (size_t i = 0; i < size; i++) {
+ SkAutoTUnref<SkTypeface> tf(SkTypeface::Deserialize(stream));
+ if (!tf.get()) { // failed to deserialize
+ // fTFPlayback asserts it never has a null, so we plop in
+ // the default here.
+ tf.reset(SkTypeface::RefDefault());
+ }
+ fTFPlayback.set(i, tf);
+ }
+ } break;
+ case PICT_PICTURE_TAG: {
+ fPictureCount = size;
+ fPictureRefs = SkNEW_ARRAY(SkPicture*, fPictureCount);
+ for (int i = 0; i < fPictureCount; i++) {
+ fPictureRefs[i] = SkPicture::CreateFromStream(stream, proc);
+ // CreateFromStream can only fail if PICTURE_VERSION does not match
+ // (which should never happen from here, since a sub picture will
+ // have the same PICTURE_VERSION as its parent) or if stream->read
+ // returns 0. In the latter case, we have a bug when writing the
+ // picture to begin with, which will be alerted to here.
+ SkASSERT(fPictureRefs[i] != NULL);
+ }
+ } break;
+ case PICT_BUFFER_SIZE_TAG: {
+ SkAutoMalloc storage(size);
+ stream->read(storage.get(), size);
+
+ SkOrderedReadBuffer buffer(storage.get(), size);
+ buffer.setFlags(pictInfoFlagsToReadBufferFlags(info.fFlags));
+
+ fFactoryPlayback->setupBuffer(buffer);
+ fTFPlayback.setupBuffer(buffer);
+ buffer.setBitmapDecoder(proc);
+
+ while (!buffer.eof()) {
+ tag = buffer.readUInt();
+ size = buffer.readUInt();
+ this->parseBufferTag(buffer, tag, size);
+ }
+ SkDEBUGCODE(haveBuffer = true;)
+ } break;
+ }
+}
+
+void SkPicturePlayback::parseBufferTag(SkOrderedReadBuffer& buffer,
+ uint32_t tag, size_t size) {
+ switch (tag) {
+ case PICT_BITMAP_BUFFER_TAG: {
+ fBitmaps = SkTRefArray<SkBitmap>::Create(size);
+ for (size_t i = 0; i < size; ++i) {
+ SkBitmap* bm = &fBitmaps->writableAt(i);
+ buffer.readBitmap(bm);
+ bm->setImmutable();
+ }
+ } break;
+ case PICT_MATRIX_BUFFER_TAG:
+ fMatrices = SkTRefArray<SkMatrix>::Create(size);
+ for (size_t i = 0; i < size; ++i) {
+ buffer.readMatrix(&fMatrices->writableAt(i));
+ }
+ break;
+ case PICT_PAINT_BUFFER_TAG: {
+ fPaints = SkTRefArray<SkPaint>::Create(size);
+ for (size_t i = 0; i < size; ++i) {
+ buffer.readPaint(&fPaints->writableAt(i));
+ }
+ } break;
+ case PICT_PATH_BUFFER_TAG:
+ if (size > 0) {
+ fPathHeap.reset(SkNEW_ARGS(SkPathHeap, (buffer)));
+ }
+ break;
+ case PICT_REGION_BUFFER_TAG: {
+ fRegions = SkTRefArray<SkRegion>::Create(size);
+ for (size_t i = 0; i < size; ++i) {
+ buffer.readRegion(&fRegions->writableAt(i));
+ }
+ } break;
+ }
+}
+
+SkPicturePlayback::SkPicturePlayback(SkStream* stream, const SkPictInfo& info,
+ SkPicture::InstallPixelRefProc proc) {
+ this->init();
+
+ for (;;) {
+ uint32_t tag = stream->readU32();
+ if (PICT_EOF_TAG == tag) {
+ break;
+ }
+
+ uint32_t size = stream->readU32();
+ this->parseStreamTag(stream, info, tag, size, proc);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SPEW_CLIP_SKIPPING
+struct SkipClipRec {
+ int fCount;
+ size_t fSize;
+
+ SkipClipRec() {
+ fCount = 0;
+ fSize = 0;
+ }
+
+ void recordSkip(size_t bytes) {
+ fCount += 1;
+ fSize += bytes;
+ }
+};
+#endif
+
+#ifdef SK_DEVELOPER
+bool SkPicturePlayback::preDraw(int opIndex, int type) {
+ return false;
+}
+
+void SkPicturePlayback::postDraw(int opIndex) {
+}
+#endif
+
+/*
+ * Read the next op code and chunk size from 'reader'. The returned size
+ * is the entire size of the chunk (including the opcode). Thus, the
+ * offset just prior to calling read_op_and_size + 'size' is the offset
+ * to the next chunk's op code. This also means that the size of a chunk
+ * with no arguments (just an opcode) will be 4.
+ */
+static DrawType read_op_and_size(SkReader32* reader, uint32_t* size) {
+ uint32_t temp = reader->readInt();
+ uint32_t op;
+ if (((uint8_t) temp) == temp) {
+ // old skp file - no size information
+ op = temp;
+ *size = 0;
+ } else {
+ UNPACK_8_24(temp, op, *size);
+ if (MASK_24 == *size) {
+ *size = reader->readInt();
+ }
+ }
+ return (DrawType) op;
+}
+
+void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback) {
+#ifdef ENABLE_TIME_DRAW
+ SkAutoTime at("SkPicture::draw", 50);
+#endif
+
+#ifdef SPEW_CLIP_SKIPPING
+ 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;
+
+ if (NULL != fStateTree && NULL != fBoundingHierarchy) {
+ SkRect clipBounds;
+ if (canvas.getClipBounds(&clipBounds)) {
+ SkIRect query;
+ clipBounds.roundOut(&query);
+ fBoundingHierarchy->search(query, &results);
+ if (results.count() == 0) {
+ return;
+ }
+ SkTQSort<SkPictureStateTree::Draw>(
+ reinterpret_cast<SkPictureStateTree::Draw**>(results.begin()),
+ reinterpret_cast<SkPictureStateTree::Draw**>(results.end()-1));
+ }
+ }
+
+ SkPictureStateTree::Iterator it = (NULL == fStateTree) ?
+ SkPictureStateTree::Iterator() :
+ fStateTree->getIterator(results, &canvas);
+
+ if (it.isValid()) {
+ uint32_t skipTo = it.draw();
+ if (kDrawComplete == skipTo) {
+ return;
+ }
+ reader.setOffset(skipTo);
+ }
+
+ // Record this, so we can concat w/ it if we encounter a setMatrix()
+ SkMatrix initialMatrix = canvas.getTotalMatrix();
+ int originalSaveCount = canvas.getSaveCount();
+
+#ifdef SK_BUILD_FOR_ANDROID
+ fAbortCurrentPlayback = false;
+#endif
+
+#ifdef SK_DEVELOPER
+ int opIndex = -1;
+#endif
+
+ while (!reader.eof()) {
+ if (callback && callback->abortDrawing()) {
+ canvas.restoreToCount(originalSaveCount);
+ return;
+ }
+#ifdef SK_BUILD_FOR_ANDROID
+ if (fAbortCurrentPlayback) {
+ return;
+ }
+#endif
+
+ size_t curOffset = reader.offset();
+ uint32_t size;
+ DrawType op = read_op_and_size(&reader, &size);
+ size_t skipTo = 0;
+ if (NOOP == op) {
+ // NOOPs are to be ignored - do not propagate them any further
+ skipTo = curOffset + size;
+#ifdef SK_DEVELOPER
+ } else {
+ opIndex++;
+ if (this->preDraw(opIndex, op)) {
+ skipTo = curOffset + size;
+ }
+#endif
+ }
+
+ if (0 != skipTo) {
+ if (it.isValid()) {
+ // If using a bounding box hierarchy, advance the state tree
+ // iterator until at or after skipTo
+ uint32_t adjustedSkipTo;
+ do {
+ adjustedSkipTo = it.draw();
+ } while (adjustedSkipTo < skipTo);
+ skipTo = adjustedSkipTo;
+ }
+ if (kDrawComplete == skipTo) {
+ break;
+ }
+ reader.setOffset(skipTo);
+ continue;
+ }
+
+ switch (op) {
+ case CLIP_PATH: {
+ const SkPath& path = getPath(reader);
+ uint32_t packed = reader.readInt();
+ SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
+ bool doAA = ClipParams_unpackDoAA(packed);
+ size_t offsetToRestore = reader.readInt();
+ SkASSERT(!offsetToRestore || \
+ offsetToRestore >= reader.offset());
+ if (!canvas.clipPath(path, regionOp, doAA) && offsetToRestore) {
+#ifdef SPEW_CLIP_SKIPPING
+ skipPath.recordSkip(offsetToRestore - reader.offset());
+#endif
+ reader.setOffset(offsetToRestore);
+ }
+ } break;
+ case CLIP_REGION: {
+ const SkRegion& region = getRegion(reader);
+ uint32_t packed = reader.readInt();
+ SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
+ size_t offsetToRestore = reader.readInt();
+ SkASSERT(!offsetToRestore || \
+ offsetToRestore >= reader.offset());
+ if (!canvas.clipRegion(region, regionOp) && offsetToRestore) {
+#ifdef SPEW_CLIP_SKIPPING
+ skipRegion.recordSkip(offsetToRestore - reader.offset());
+#endif
+ reader.setOffset(offsetToRestore);
+ }
+ } break;
+ case CLIP_RECT: {
+ const SkRect& rect = reader.skipT<SkRect>();
+ uint32_t packed = reader.readInt();
+ SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
+ bool doAA = ClipParams_unpackDoAA(packed);
+ size_t offsetToRestore = reader.readInt();
+ SkASSERT(!offsetToRestore || \
+ offsetToRestore >= reader.offset());
+ if (!canvas.clipRect(rect, regionOp, doAA) && offsetToRestore) {
+#ifdef SPEW_CLIP_SKIPPING
+ skipRect.recordSkip(offsetToRestore - reader.offset());
+#endif
+ reader.setOffset(offsetToRestore);
+ }
+ } break;
+ case CLIP_RRECT: {
+ SkRRect rrect;
+ reader.readRRect(&rrect);
+ uint32_t packed = reader.readInt();
+ SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
+ bool doAA = ClipParams_unpackDoAA(packed);
+ size_t offsetToRestore = reader.readInt();
+ SkASSERT(!offsetToRestore || \
+ offsetToRestore >= reader.offset());
+ if (!canvas.clipRRect(rrect, regionOp, doAA) && offsetToRestore) {
+#ifdef SPEW_CLIP_SKIPPING
+ skipRRect.recordSkip(offsetToRestore - reader.offset());
+#endif
+ reader.setOffset(offsetToRestore);
+ }
+ } break;
+ case CONCAT:
+ canvas.concat(*getMatrix(reader));
+ break;
+ case DRAW_BITMAP: {
+ const SkPaint* paint = getPaint(reader);
+ const SkBitmap& bitmap = getBitmap(reader);
+ const SkPoint& loc = reader.skipT<SkPoint>();
+ canvas.drawBitmap(bitmap, loc.fX, loc.fY, paint);
+ } break;
+ case DRAW_BITMAP_RECT_TO_RECT: {
+ const SkPaint* paint = getPaint(reader);
+ const SkBitmap& bitmap = getBitmap(reader);
+ const SkRect* src = this->getRectPtr(reader); // may be null
+ const SkRect& dst = reader.skipT<SkRect>(); // required
+ canvas.drawBitmapRectToRect(bitmap, src, dst, paint);
+ } break;
+ case DRAW_BITMAP_MATRIX: {
+ const SkPaint* paint = getPaint(reader);
+ const SkBitmap& bitmap = getBitmap(reader);
+ const SkMatrix* matrix = getMatrix(reader);
+ canvas.drawBitmapMatrix(bitmap, *matrix, paint);
+ } break;
+ case DRAW_BITMAP_NINE: {
+ const SkPaint* paint = getPaint(reader);
+ const SkBitmap& bitmap = getBitmap(reader);
+ const SkIRect& src = reader.skipT<SkIRect>();
+ const SkRect& dst = reader.skipT<SkRect>();
+ canvas.drawBitmapNine(bitmap, src, dst, paint);
+ } break;
+ case DRAW_CLEAR:
+ canvas.clear(reader.readInt());
+ break;
+ case DRAW_DATA: {
+ size_t length = reader.readInt();
+ canvas.drawData(reader.skip(length), length);
+ // skip handles padding the read out to a multiple of 4
+ } break;
+ case BEGIN_COMMENT_GROUP: {
+ const char* desc = reader.readString();
+ canvas.beginCommentGroup(desc);
+ } break;
+ case COMMENT: {
+ const char* kywd = reader.readString();
+ const char* value = reader.readString();
+ canvas.addComment(kywd, value);
+ } break;
+ case END_COMMENT_GROUP: {
+ canvas.endCommentGroup();
+ } break;
+ case DRAW_OVAL: {
+ const SkPaint& paint = *getPaint(reader);
+ canvas.drawOval(reader.skipT<SkRect>(), paint);
+ } break;
+ case DRAW_PAINT:
+ canvas.drawPaint(*getPaint(reader));
+ break;
+ case DRAW_PATH: {
+ const SkPaint& paint = *getPaint(reader);
+ canvas.drawPath(getPath(reader), paint);
+ } break;
+ case DRAW_PICTURE:
+ canvas.drawPicture(getPicture(reader));
+ break;
+ case DRAW_POINTS: {
+ const SkPaint& paint = *getPaint(reader);
+ SkCanvas::PointMode mode = (SkCanvas::PointMode)reader.readInt();
+ size_t count = reader.readInt();
+ const SkPoint* pts = (const SkPoint*)reader.skip(sizeof(SkPoint) * count);
+ canvas.drawPoints(mode, count, pts, paint);
+ } break;
+ case DRAW_POS_TEXT: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ size_t points = reader.readInt();
+ const SkPoint* pos = (const SkPoint*)reader.skip(points * sizeof(SkPoint));
+ canvas.drawPosText(text.text(), text.length(), pos, paint);
+ } break;
+ case DRAW_POS_TEXT_TOP_BOTTOM: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ size_t points = reader.readInt();
+ const SkPoint* pos = (const SkPoint*)reader.skip(points * sizeof(SkPoint));
+ const SkScalar top = reader.readScalar();
+ const SkScalar bottom = reader.readScalar();
+ if (!canvas.quickRejectY(top, bottom)) {
+ canvas.drawPosText(text.text(), text.length(), pos, paint);
+ }
+ } break;
+ case DRAW_POS_TEXT_H: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ size_t xCount = reader.readInt();
+ const SkScalar constY = reader.readScalar();
+ const SkScalar* xpos = (const SkScalar*)reader.skip(xCount * sizeof(SkScalar));
+ canvas.drawPosTextH(text.text(), text.length(), xpos, constY,
+ paint);
+ } break;
+ case DRAW_POS_TEXT_H_TOP_BOTTOM: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ size_t xCount = reader.readInt();
+ const SkScalar* xpos = (const SkScalar*)reader.skip((3 + xCount) * sizeof(SkScalar));
+ const SkScalar top = *xpos++;
+ const SkScalar bottom = *xpos++;
+ const SkScalar constY = *xpos++;
+ if (!canvas.quickRejectY(top, bottom)) {
+ canvas.drawPosTextH(text.text(), text.length(), xpos,
+ constY, paint);
+ }
+ } break;
+ case DRAW_RECT: {
+ 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);
+ int left = reader.readInt();
+ int top = reader.readInt();
+ canvas.drawSprite(bitmap, left, top, paint);
+ } break;
+ case DRAW_TEXT: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ SkScalar x = reader.readScalar();
+ SkScalar y = reader.readScalar();
+ canvas.drawText(text.text(), text.length(), x, y, paint);
+ } break;
+ case DRAW_TEXT_TOP_BOTTOM: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ const SkScalar* ptr = (const SkScalar*)reader.skip(4 * sizeof(SkScalar));
+ // ptr[0] == x
+ // ptr[1] == y
+ // ptr[2] == top
+ // ptr[3] == bottom
+ if (!canvas.quickRejectY(ptr[2], ptr[3])) {
+ canvas.drawText(text.text(), text.length(), ptr[0], ptr[1],
+ paint);
+ }
+ } break;
+ case DRAW_TEXT_ON_PATH: {
+ const SkPaint& paint = *getPaint(reader);
+ getText(reader, &text);
+ const SkPath& path = getPath(reader);
+ const SkMatrix* matrix = getMatrix(reader);
+ canvas.drawTextOnPath(text.text(), text.length(), path,
+ matrix, paint);
+ } break;
+ case DRAW_VERTICES: {
+ const SkPaint& paint = *getPaint(reader);
+ DrawVertexFlags flags = (DrawVertexFlags)reader.readInt();
+ SkCanvas::VertexMode vmode = (SkCanvas::VertexMode)reader.readInt();
+ int vCount = reader.readInt();
+ const SkPoint* verts = (const SkPoint*)reader.skip(
+ vCount * sizeof(SkPoint));
+ const SkPoint* texs = NULL;
+ const SkColor* colors = NULL;
+ const uint16_t* indices = NULL;
+ int iCount = 0;
+ if (flags & DRAW_VERTICES_HAS_TEXS) {
+ texs = (const SkPoint*)reader.skip(
+ vCount * sizeof(SkPoint));
+ }
+ if (flags & DRAW_VERTICES_HAS_COLORS) {
+ colors = (const SkColor*)reader.skip(
+ vCount * sizeof(SkColor));
+ }
+ if (flags & DRAW_VERTICES_HAS_INDICES) {
+ iCount = reader.readInt();
+ indices = (const uint16_t*)reader.skip(
+ iCount * sizeof(uint16_t));
+ }
+ canvas.drawVertices(vmode, vCount, verts, texs, colors, NULL,
+ indices, iCount, paint);
+ } break;
+ case RESTORE:
+ canvas.restore();
+ break;
+ case ROTATE:
+ canvas.rotate(reader.readScalar());
+ break;
+ case SAVE:
+ canvas.save((SkCanvas::SaveFlags) reader.readInt());
+ break;
+ case SAVE_LAYER: {
+ const SkRect* boundsPtr = getRectPtr(reader);
+ const SkPaint* paint = getPaint(reader);
+ canvas.saveLayer(boundsPtr, paint, (SkCanvas::SaveFlags) reader.readInt());
+ } break;
+ case SCALE: {
+ SkScalar sx = reader.readScalar();
+ SkScalar sy = reader.readScalar();
+ canvas.scale(sx, sy);
+ } break;
+ case SET_MATRIX: {
+ SkMatrix matrix;
+ matrix.setConcat(initialMatrix, *getMatrix(reader));
+ canvas.setMatrix(matrix);
+ } break;
+ case SKEW: {
+ SkScalar sx = reader.readScalar();
+ SkScalar sy = reader.readScalar();
+ canvas.skew(sx, sy);
+ } break;
+ case TRANSLATE: {
+ SkScalar dx = reader.readScalar();
+ SkScalar dy = reader.readScalar();
+ canvas.translate(dx, dy);
+ } break;
+ default:
+ SkASSERT(0);
+ }
+
+#ifdef SK_DEVELOPER
+ this->postDraw(opIndex);
+#endif
+
+ if (it.isValid()) {
+ uint32_t skipTo = it.draw();
+ if (kDrawComplete == skipTo) {
+ break;
+ }
+ reader.setOffset(skipTo);
+ }
+ }
+
+#ifdef SPEW_CLIP_SKIPPING
+ {
+ 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();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG_SIZE
+int SkPicturePlayback::size(size_t* sizePtr) {
+ int objects = bitmaps(sizePtr);
+ objects += paints(sizePtr);
+ objects += paths(sizePtr);
+ objects += pictures(sizePtr);
+ objects += regions(sizePtr);
+ *sizePtr = fOpData.size();
+ return objects;
+}
+
+int SkPicturePlayback::bitmaps(size_t* size) {
+ size_t result = 0;
+ for (int index = 0; index < fBitmapCount; index++) {
+ // const SkBitmap& bitmap = fBitmaps[index];
+ result += sizeof(SkBitmap); // bitmap->size();
+ }
+ *size = result;
+ return fBitmapCount;
+}
+
+int SkPicturePlayback::paints(size_t* size) {
+ size_t result = 0;
+ for (int index = 0; index < fPaintCount; index++) {
+ // const SkPaint& paint = fPaints[index];
+ result += sizeof(SkPaint); // paint->size();
+ }
+ *size = result;
+ return fPaintCount;
+}
+
+int SkPicturePlayback::paths(size_t* size) {
+ size_t result = 0;
+ for (int index = 0; index < fPathCount; index++) {
+ const SkPath& path = fPaths[index];
+ result += path.flatten(NULL);
+ }
+ *size = result;
+ return fPathCount;
+}
+
+int SkPicturePlayback::regions(size_t* size) {
+ size_t result = 0;
+ for (int index = 0; index < fRegionCount; index++) {
+ // const SkRegion& region = fRegions[index];
+ result += sizeof(SkRegion); // region->size();
+ }
+ *size = result;
+ return fRegionCount;
+}
+#endif
+
+#ifdef SK_DEBUG_DUMP
+void SkPicturePlayback::dumpBitmap(const SkBitmap& bitmap) const {
+ char pBuffer[DUMP_BUFFER_SIZE];
+ char* bufferPtr = pBuffer;
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "BitmapData bitmap%p = {", &bitmap);
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kWidth, %d}, ", bitmap.width());
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kHeight, %d}, ", bitmap.height());
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kRowBytes, %d}, ", bitmap.rowBytes());
+// start here;
+ SkDebugf("%s{0}};\n", pBuffer);
+}
+
+void dumpMatrix(const SkMatrix& matrix) const {
+ SkMatrix defaultMatrix;
+ defaultMatrix.reset();
+ char pBuffer[DUMP_BUFFER_SIZE];
+ char* bufferPtr = pBuffer;
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "MatrixData matrix%p = {", &matrix);
+ SkScalar scaleX = matrix.getScaleX();
+ if (scaleX != defaultMatrix.getScaleX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kScaleX, %g}, ", SkScalarToFloat(scaleX));
+ SkScalar scaleY = matrix.getScaleY();
+ if (scaleY != defaultMatrix.getScaleY())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kScaleY, %g}, ", SkScalarToFloat(scaleY));
+ SkScalar skewX = matrix.getSkewX();
+ if (skewX != defaultMatrix.getSkewX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kSkewX, %g}, ", SkScalarToFloat(skewX));
+ SkScalar skewY = matrix.getSkewY();
+ if (skewY != defaultMatrix.getSkewY())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kSkewY, %g}, ", SkScalarToFloat(skewY));
+ SkScalar translateX = matrix.getTranslateX();
+ if (translateX != defaultMatrix.getTranslateX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTranslateX, %g}, ", SkScalarToFloat(translateX));
+ SkScalar translateY = matrix.getTranslateY();
+ if (translateY != defaultMatrix.getTranslateY())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTranslateY, %g}, ", SkScalarToFloat(translateY));
+ SkScalar perspX = matrix.getPerspX();
+ if (perspX != defaultMatrix.getPerspX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kPerspX, %g}, ", SkFractToFloat(perspX));
+ SkScalar perspY = matrix.getPerspY();
+ if (perspY != defaultMatrix.getPerspY())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kPerspY, %g}, ", SkFractToFloat(perspY));
+ SkDebugf("%s{0}};\n", pBuffer);
+}
+
+void dumpPaint(const SkPaint& paint) const {
+ SkPaint defaultPaint;
+ char pBuffer[DUMP_BUFFER_SIZE];
+ char* bufferPtr = pBuffer;
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "PaintPointers paintPtrs%p = {", &paint);
+ const SkTypeface* typeface = paint.getTypeface();
+ if (typeface != defaultPaint.getTypeface())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTypeface, %p}, ", typeface);
+ const SkPathEffect* pathEffect = paint.getPathEffect();
+ if (pathEffect != defaultPaint.getPathEffect())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kPathEffect, %p}, ", pathEffect);
+ const SkShader* shader = paint.getShader();
+ if (shader != defaultPaint.getShader())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kShader, %p}, ", shader);
+ const SkXfermode* xfermode = paint.getXfermode();
+ if (xfermode != defaultPaint.getXfermode())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kXfermode, %p}, ", xfermode);
+ const SkMaskFilter* maskFilter = paint.getMaskFilter();
+ if (maskFilter != defaultPaint.getMaskFilter())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kMaskFilter, %p}, ", maskFilter);
+ const SkColorFilter* colorFilter = paint.getColorFilter();
+ if (colorFilter != defaultPaint.getColorFilter())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kColorFilter, %p}, ", colorFilter);
+ const SkRasterizer* rasterizer = paint.getRasterizer();
+ if (rasterizer != defaultPaint.getRasterizer())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kRasterizer, %p}, ", rasterizer);
+ const SkDrawLooper* drawLooper = paint.getLooper();
+ if (drawLooper != defaultPaint.getLooper())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kDrawLooper, %p}, ", drawLooper);
+ SkDebugf("%s{0}};\n", pBuffer);
+ bufferPtr = pBuffer;
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "PaintScalars paintScalars%p = {", &paint);
+ SkScalar textSize = paint.getTextSize();
+ if (textSize != defaultPaint.getTextSize())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTextSize, %g}, ", SkScalarToFloat(textSize));
+ SkScalar textScaleX = paint.getTextScaleX();
+ if (textScaleX != defaultPaint.getTextScaleX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTextScaleX, %g}, ", SkScalarToFloat(textScaleX));
+ SkScalar textSkewX = paint.getTextSkewX();
+ if (textSkewX != defaultPaint.getTextSkewX())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTextSkewX, %g}, ", SkScalarToFloat(textSkewX));
+ SkScalar strokeWidth = paint.getStrokeWidth();
+ if (strokeWidth != defaultPaint.getStrokeWidth())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kStrokeWidth, %g}, ", SkScalarToFloat(strokeWidth));
+ SkScalar strokeMiter = paint.getStrokeMiter();
+ if (strokeMiter != defaultPaint.getStrokeMiter())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kStrokeMiter, %g}, ", SkScalarToFloat(strokeMiter));
+ SkDebugf("%s{0}};\n", pBuffer);
+ bufferPtr = pBuffer;
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "PaintInts = paintInts%p = {", &paint);
+ unsigned color = paint.getColor();
+ if (color != defaultPaint.getColor())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kColor, 0x%x}, ", color);
+ unsigned flags = paint.getFlags();
+ if (flags != defaultPaint.getFlags())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kFlags, 0x%x}, ", flags);
+ int align = paint.getTextAlign();
+ if (align != defaultPaint.getTextAlign())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kAlign, 0x%x}, ", align);
+ int strokeCap = paint.getStrokeCap();
+ if (strokeCap != defaultPaint.getStrokeCap())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kStrokeCap, 0x%x}, ", strokeCap);
+ int strokeJoin = paint.getStrokeJoin();
+ if (strokeJoin != defaultPaint.getStrokeJoin())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kAlign, 0x%x}, ", strokeJoin);
+ int style = paint.getStyle();
+ if (style != defaultPaint.getStyle())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kStyle, 0x%x}, ", style);
+ int textEncoding = paint.getTextEncoding();
+ if (textEncoding != defaultPaint.getTextEncoding())
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "{kTextEncoding, 0x%x}, ", textEncoding);
+ SkDebugf("%s{0}};\n", pBuffer);
+
+ SkDebugf("PaintData paint%p = {paintPtrs%p, paintScalars%p, paintInts%p};\n",
+ &paint, &paint, &paint, &paint);
+}
+
+void SkPicturePlayback::dumpPath(const SkPath& path) const {
+ SkDebugf("path dump unimplemented\n");
+}
+
+void SkPicturePlayback::dumpPicture(const SkPicture& picture) const {
+ SkDebugf("picture dump unimplemented\n");
+}
+
+void SkPicturePlayback::dumpRegion(const SkRegion& region) const {
+ SkDebugf("region dump unimplemented\n");
+}
+
+int SkPicturePlayback::dumpDrawType(char* bufferPtr, char* buffer, DrawType drawType) {
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "k%s, ", DrawTypeToString(drawType));
+}
+
+int SkPicturePlayback::dumpInt(char* bufferPtr, char* buffer, char* name) {
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:%d, ", name, getInt());
+}
+
+int SkPicturePlayback::dumpRect(char* bufferPtr, char* buffer, char* name) {
+ const SkRect* rect = fReader.skipRect();
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:{l:%g t:%g r:%g b:%g}, ", name, SkScalarToFloat(rect.fLeft),
+ SkScalarToFloat(rect.fTop),
+ SkScalarToFloat(rect.fRight), SkScalarToFloat(rect.fBottom));
+}
+
+int SkPicturePlayback::dumpPoint(char* bufferPtr, char* buffer, char* name) {
+ SkPoint pt;
+ getPoint(&pt);
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:{x:%g y:%g}, ", name, SkScalarToFloat(pt.fX),
+ SkScalarToFloat(pt.fY));
+}
+
+void SkPicturePlayback::dumpPointArray(char** bufferPtrPtr, char* buffer, int count) {
+ char* bufferPtr = *bufferPtrPtr;
+ const SkPoint* pts = (const SkPoint*)fReadStream.getAtPos();
+ fReadStream.skip(sizeof(SkPoint) * count);
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "count:%d {", count);
+ for (int index = 0; index < count; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "{x:%g y:%g}, ", SkScalarToFloat(pts[index].fX),
+ SkScalarToFloat(pts[index].fY));
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "} ");
+ *bufferPtrPtr = bufferPtr;
+}
+
+int SkPicturePlayback::dumpPtr(char* bufferPtr, char* buffer, char* name, void* ptr) {
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:%p, ", name, ptr);
+}
+
+int SkPicturePlayback::dumpRectPtr(char* bufferPtr, char* buffer, char* name) {
+ char result;
+ fReadStream.read(&result, sizeof(result));
+ if (result)
+ return dumpRect(bufferPtr, buffer, name);
+ else
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:NULL, ", name);
+}
+
+int SkPicturePlayback::dumpScalar(char* bufferPtr, char* buffer, char* name) {
+ return snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - buffer),
+ "%s:%d, ", name, getScalar());
+}
+
+void SkPicturePlayback::dumpText(char** bufferPtrPtr, char* buffer) {
+ char* bufferPtr = *bufferPtrPtr;
+ int length = getInt();
+ bufferPtr += dumpDrawType(bufferPtr, buffer);
+ fReadStream.skipToAlign4();
+ char* text = (char*) fReadStream.getAtPos();
+ fReadStream.skip(length);
+ bufferPtr += dumpInt(bufferPtr, buffer, "length");
+ int limit = DUMP_BUFFER_SIZE - (bufferPtr - buffer) - 2;
+ length >>= 1;
+ if (limit > length)
+ limit = length;
+ if (limit > 0) {
+ *bufferPtr++ = '"';
+ for (int index = 0; index < limit; index++) {
+ *bufferPtr++ = *(unsigned short*) text;
+ text += sizeof(unsigned short);
+ }
+ *bufferPtr++ = '"';
+ }
+ *bufferPtrPtr = bufferPtr;
+}
+
+#define DUMP_DRAWTYPE(drawType) \
+ bufferPtr += dumpDrawType(bufferPtr, buffer, drawType)
+
+#define DUMP_INT(name) \
+ bufferPtr += dumpInt(bufferPtr, buffer, #name)
+
+#define DUMP_RECT_PTR(name) \
+ bufferPtr += dumpRectPtr(bufferPtr, buffer, #name)
+
+#define DUMP_POINT(name) \
+ bufferPtr += dumpRect(bufferPtr, buffer, #name)
+
+#define DUMP_RECT(name) \
+ bufferPtr += dumpRect(bufferPtr, buffer, #name)
+
+#define DUMP_POINT_ARRAY(count) \
+ dumpPointArray(&bufferPtr, buffer, count)
+
+#define DUMP_PTR(name, ptr) \
+ bufferPtr += dumpPtr(bufferPtr, buffer, #name, (void*) ptr)
+
+#define DUMP_SCALAR(name) \
+ bufferPtr += dumpScalar(bufferPtr, buffer, #name)
+
+#define DUMP_TEXT() \
+ dumpText(&bufferPtr, buffer)
+
+void SkPicturePlayback::dumpStream() {
+ SkDebugf("RecordStream stream = {\n");
+ DrawType drawType;
+ TextContainer text;
+ fReadStream.rewind();
+ char buffer[DUMP_BUFFER_SIZE], * bufferPtr;
+ while (fReadStream.read(&drawType, sizeof(drawType))) {
+ bufferPtr = buffer;
+ DUMP_DRAWTYPE(drawType);
+ switch (drawType) {
+ case CLIP_PATH: {
+ DUMP_PTR(SkPath, &getPath());
+ DUMP_INT(SkRegion::Op);
+ DUMP_INT(offsetToRestore);
+ } break;
+ case CLIP_REGION: {
+ DUMP_PTR(SkRegion, &getRegion());
+ DUMP_INT(SkRegion::Op);
+ DUMP_INT(offsetToRestore);
+ } break;
+ case CLIP_RECT: {
+ DUMP_RECT(rect);
+ DUMP_INT(SkRegion::Op);
+ DUMP_INT(offsetToRestore);
+ } break;
+ case CONCAT:
+ DUMP_PTR(SkMatrix, getMatrix());
+ break;
+ case DRAW_BITMAP: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_PTR(SkBitmap, &getBitmap());
+ DUMP_SCALAR(left);
+ DUMP_SCALAR(top);
+ } break;
+ case DRAW_PAINT:
+ DUMP_PTR(SkPaint, getPaint());
+ break;
+ case DRAW_PATH: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_PTR(SkPath, &getPath());
+ } break;
+ case DRAW_PICTURE: {
+ DUMP_PTR(SkPicture, &getPicture());
+ } break;
+ case DRAW_POINTS: {
+ DUMP_PTR(SkPaint, getPaint());
+ (void)getInt(); // PointMode
+ size_t count = getInt();
+ fReadStream.skipToAlign4();
+ DUMP_POINT_ARRAY(count);
+ } break;
+ case DRAW_POS_TEXT: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_TEXT();
+ size_t points = getInt();
+ fReadStream.skipToAlign4();
+ DUMP_POINT_ARRAY(points);
+ } break;
+ case DRAW_POS_TEXT_H: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_TEXT();
+ size_t points = getInt();
+ fReadStream.skipToAlign4();
+ DUMP_SCALAR(top);
+ DUMP_SCALAR(bottom);
+ DUMP_SCALAR(constY);
+ DUMP_POINT_ARRAY(points);
+ } break;
+ case DRAW_RECT: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_RECT(rect);
+ } break;
+ case DRAW_SPRITE: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_PTR(SkBitmap, &getBitmap());
+ DUMP_SCALAR(left);
+ DUMP_SCALAR(top);
+ } break;
+ case DRAW_TEXT: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_TEXT();
+ DUMP_SCALAR(x);
+ DUMP_SCALAR(y);
+ } break;
+ case DRAW_TEXT_ON_PATH: {
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_TEXT();
+ DUMP_PTR(SkPath, &getPath());
+ DUMP_PTR(SkMatrix, getMatrix());
+ } break;
+ case RESTORE:
+ break;
+ case ROTATE:
+ DUMP_SCALAR(rotate);
+ break;
+ case SAVE:
+ DUMP_INT(SkCanvas::SaveFlags);
+ break;
+ case SAVE_LAYER: {
+ DUMP_RECT_PTR(layer);
+ DUMP_PTR(SkPaint, getPaint());
+ DUMP_INT(SkCanvas::SaveFlags);
+ } break;
+ case SCALE: {
+ DUMP_SCALAR(sx);
+ DUMP_SCALAR(sy);
+ } break;
+ case SKEW: {
+ DUMP_SCALAR(sx);
+ DUMP_SCALAR(sy);
+ } break;
+ case TRANSLATE: {
+ DUMP_SCALAR(dx);
+ DUMP_SCALAR(dy);
+ } break;
+ default:
+ SkASSERT(0);
+ }
+ SkDebugf("%s\n", buffer);
+ }
+}
+
+void SkPicturePlayback::dump() const {
+ char pBuffer[DUMP_BUFFER_SIZE];
+ char* bufferPtr = pBuffer;
+ int index;
+ if (fBitmapCount > 0)
+ SkDebugf("// bitmaps (%d)\n", fBitmapCount);
+ for (index = 0; index < fBitmapCount; index++) {
+ const SkBitmap& bitmap = fBitmaps[index];
+ dumpBitmap(bitmap);
+ }
+ if (fBitmapCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Bitmaps bitmaps = {");
+ for (index = 0; index < fBitmapCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "bitmap%p, ", &fBitmaps[index]);
+ if (fBitmapCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ if (fMatrixCount > 0)
+ SkDebugf("// matrices (%d)\n", fMatrixCount);
+ for (index = 0; index < fMatrixCount; index++) {
+ const SkMatrix& matrix = fMatrices[index];
+ dumpMatrix(matrix);
+ }
+ bufferPtr = pBuffer;
+ if (fMatrixCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Matrices matrices = {");
+ for (index = 0; index < fMatrixCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "matrix%p, ", &fMatrices[index]);
+ if (fMatrixCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ if (fPaintCount > 0)
+ SkDebugf("// paints (%d)\n", fPaintCount);
+ for (index = 0; index < fPaintCount; index++) {
+ const SkPaint& paint = fPaints[index];
+ dumpPaint(paint);
+ }
+ bufferPtr = pBuffer;
+ if (fPaintCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Paints paints = {");
+ for (index = 0; index < fPaintCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "paint%p, ", &fPaints[index]);
+ if (fPaintCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ for (index = 0; index < fPathCount; index++) {
+ const SkPath& path = fPaths[index];
+ dumpPath(path);
+ }
+ bufferPtr = pBuffer;
+ if (fPathCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Paths paths = {");
+ for (index = 0; index < fPathCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "path%p, ", &fPaths[index]);
+ if (fPathCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ for (index = 0; index < fPictureCount; index++) {
+ dumpPicture(*fPictureRefs[index]);
+ }
+ bufferPtr = pBuffer;
+ if (fPictureCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Pictures pictures = {");
+ for (index = 0; index < fPictureCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "picture%p, ", fPictureRefs[index]);
+ if (fPictureCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ for (index = 0; index < fRegionCount; index++) {
+ const SkRegion& region = fRegions[index];
+ dumpRegion(region);
+ }
+ bufferPtr = pBuffer;
+ if (fRegionCount > 0)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "Regions regions = {");
+ for (index = 0; index < fRegionCount; index++)
+ bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer),
+ "region%p, ", &fRegions[index]);
+ if (fRegionCount > 0)
+ SkDebugf("%s0};\n", pBuffer);
+
+ const_cast<SkPicturePlayback*>(this)->dumpStream();
+}
+
+#endif
diff --git a/core/SkPicturePlayback.h b/core/SkPicturePlayback.h
new file mode 100644
index 00000000..06d180fb
--- /dev/null
+++ b/core/SkPicturePlayback.h
@@ -0,0 +1,228 @@
+
+/*
+ * 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 SkPicturePlayback_DEFINED
+#define SkPicturePlayback_DEFINED
+
+#include "SkPicture.h"
+#include "SkReader32.h"
+
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkMatrix.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathHeap.h"
+#include "SkRegion.h"
+#include "SkRRect.h"
+#include "SkPictureFlat.h"
+
+#ifdef SK_BUILD_FOR_ANDROID
+#include "SkThread.h"
+#endif
+
+class SkPictureRecord;
+class SkStream;
+class SkWStream;
+class SkBBoxHierarchy;
+class SkPictureStateTree;
+
+struct SkPictInfo {
+ enum Flags {
+ kCrossProcess_Flag = 1 << 0,
+ kScalarIsFloat_Flag = 1 << 1,
+ kPtrIs64Bit_Flag = 1 << 2,
+ };
+
+ uint32_t fVersion;
+ uint32_t fWidth;
+ uint32_t fHeight;
+ uint32_t fFlags;
+};
+
+/**
+ * Container for data that is needed to deep copy a SkPicture. The container
+ * enables the data to be generated once and reused for subsequent copies.
+ */
+struct SkPictCopyInfo {
+ SkPictCopyInfo() : initialized(false), controller(1024) {}
+
+ bool initialized;
+ SkChunkFlatController controller;
+ SkTDArray<SkFlatData*> paintData;
+};
+
+class SkPicturePlayback {
+public:
+ SkPicturePlayback();
+ SkPicturePlayback(const SkPicturePlayback& src, SkPictCopyInfo* deepCopyInfo = NULL);
+ explicit SkPicturePlayback(const SkPictureRecord& record, bool deepCopy = false);
+ SkPicturePlayback(SkStream*, const SkPictInfo&, SkPicture::InstallPixelRefProc);
+
+ virtual ~SkPicturePlayback();
+
+ void draw(SkCanvas& canvas, SkDrawPictureCallback*);
+
+ void serialize(SkWStream*, SkPicture::EncodeBitmap) const;
+
+ void dumpSize() const;
+
+#ifdef SK_BUILD_FOR_ANDROID
+ // Can be called in the middle of playback (the draw() call). WIll abort the
+ // drawing and return from draw() after the "current" op code is done
+ void abort() { fAbortCurrentPlayback = true; }
+#endif
+
+protected:
+#ifdef SK_DEVELOPER
+ virtual bool preDraw(int opIndex, int type);
+ virtual void postDraw(int opIndex);
+#endif
+
+private:
+ class TextContainer {
+ public:
+ size_t length() { return fByteLength; }
+ const void* text() { return (const void*) fText; }
+ size_t fByteLength;
+ const char* fText;
+ };
+
+ const SkBitmap& getBitmap(SkReader32& reader) {
+ const int index = reader.readInt();
+ if (SkBitmapHeap::INVALID_SLOT == index) {
+#ifdef SK_DEBUG
+ SkDebugf("An invalid bitmap was recorded!\n");
+#endif
+ return fBadBitmap;
+ }
+ return (*fBitmaps)[index];
+ }
+
+ const SkMatrix* getMatrix(SkReader32& reader) {
+ int index = reader.readInt();
+ if (index == 0) {
+ return NULL;
+ }
+ return &(*fMatrices)[index - 1];
+ }
+
+ const SkPath& getPath(SkReader32& reader) {
+ return (*fPathHeap)[reader.readInt() - 1];
+ }
+
+ SkPicture& getPicture(SkReader32& reader) {
+ int index = reader.readInt();
+ SkASSERT(index > 0 && index <= fPictureCount);
+ return *fPictureRefs[index - 1];
+ }
+
+ const SkPaint* getPaint(SkReader32& reader) {
+ int index = reader.readInt();
+ if (index == 0) {
+ return NULL;
+ }
+ return &(*fPaints)[index - 1];
+ }
+
+ const SkRect* getRectPtr(SkReader32& reader) {
+ if (reader.readBool()) {
+ return &reader.skipT<SkRect>();
+ } else {
+ return NULL;
+ }
+ }
+
+ const SkIRect* getIRectPtr(SkReader32& reader) {
+ if (reader.readBool()) {
+ return &reader.skipT<SkIRect>();
+ } else {
+ return NULL;
+ }
+ }
+
+ const SkRegion& getRegion(SkReader32& reader) {
+ int index = reader.readInt();
+ return (*fRegions)[index - 1];
+ }
+
+ void getText(SkReader32& reader, TextContainer* text) {
+ size_t length = text->fByteLength = reader.readInt();
+ text->fText = (const char*)reader.skip(length);
+ }
+
+ void init();
+
+#ifdef SK_DEBUG_SIZE
+public:
+ int size(size_t* sizePtr);
+ int bitmaps(size_t* size);
+ int paints(size_t* size);
+ int paths(size_t* size);
+ int regions(size_t* size);
+#endif
+
+#ifdef SK_DEBUG_DUMP
+private:
+ void dumpBitmap(const SkBitmap& bitmap) const;
+ void dumpMatrix(const SkMatrix& matrix) const;
+ void dumpPaint(const SkPaint& paint) const;
+ void dumpPath(const SkPath& path) const;
+ void dumpPicture(const SkPicture& picture) const;
+ void dumpRegion(const SkRegion& region) const;
+ int dumpDrawType(char* bufferPtr, char* buffer, DrawType drawType);
+ int dumpInt(char* bufferPtr, char* buffer, char* name);
+ int dumpRect(char* bufferPtr, char* buffer, char* name);
+ int dumpPoint(char* bufferPtr, char* buffer, char* name);
+ void dumpPointArray(char** bufferPtrPtr, char* buffer, int count);
+ int dumpPtr(char* bufferPtr, char* buffer, char* name, void* ptr);
+ int dumpRectPtr(char* bufferPtr, char* buffer, char* name);
+ int dumpScalar(char* bufferPtr, char* buffer, char* name);
+ void dumpText(char** bufferPtrPtr, char* buffer);
+ void dumpStream();
+
+public:
+ void dump() const;
+#endif
+
+private: // these help us with reading/writing
+ void parseStreamTag(SkStream*, const SkPictInfo&, uint32_t tag, size_t size,
+ SkPicture::InstallPixelRefProc);
+ void parseBufferTag(SkOrderedReadBuffer&, uint32_t tag, size_t size);
+ 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;
+
+ SkTRefArray<SkBitmap>* fBitmaps;
+ SkTRefArray<SkMatrix>* fMatrices;
+ SkTRefArray<SkPaint>* fPaints;
+ SkTRefArray<SkRegion>* fRegions;
+
+ SkData* fOpData; // opcodes and parameters
+
+ SkPicture** fPictureRefs;
+ int fPictureCount;
+
+ SkBBoxHierarchy* fBoundingHierarchy;
+ SkPictureStateTree* fStateTree;
+
+ SkTypefacePlayback fTFPlayback;
+ SkFactoryPlayback* fFactoryPlayback;
+#ifdef SK_BUILD_FOR_ANDROID
+ SkMutex fDrawMutex;
+ bool fAbortCurrentPlayback;
+#endif
+};
+
+#endif
diff --git a/core/SkPictureRecord.cpp b/core/SkPictureRecord.cpp
new file mode 100644
index 00000000..91573637
--- /dev/null
+++ b/core/SkPictureRecord.cpp
@@ -0,0 +1,1481 @@
+
+/*
+ * 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 "SkPictureRecord.h"
+#include "SkTSearch.h"
+#include "SkPixelRef.h"
+#include "SkRRect.h"
+#include "SkBBoxHierarchy.h"
+#include "SkDevice.h"
+#include "SkPictureStateTree.h"
+
+#define MIN_WRITER_SIZE 16384
+#define HEAP_BLOCK_SIZE 4096
+
+enum {
+ // just need a value that save or getSaveCount would never return
+ kNoInitialSave = -1,
+};
+
+// A lot of basic types get stored as a uint32_t: bools, ints, paint indices, etc.
+static int const kUInt32Size = 4;
+
+static const uint32_t kSaveLayerNoBoundsSize = 4 * kUInt32Size;
+static const uint32_t kSaveLayerWithBoundsSize = 4 * kUInt32Size + sizeof(SkRect);
+
+SkPictureRecord::SkPictureRecord(uint32_t flags, SkDevice* device) :
+ INHERITED(device),
+ fBoundingHierarchy(NULL),
+ fStateTree(NULL),
+ fFlattenableHeap(HEAP_BLOCK_SIZE),
+ fMatrices(&fFlattenableHeap),
+ fPaints(&fFlattenableHeap),
+ fRegions(&fFlattenableHeap),
+ fWriter(MIN_WRITER_SIZE),
+ fRecordFlags(flags) {
+#ifdef SK_DEBUG_SIZE
+ fPointBytes = fRectBytes = fTextBytes = 0;
+ fPointWrites = fRectWrites = fTextWrites = 0;
+#endif
+
+ fRestoreOffsetStack.setReserve(32);
+
+ fBitmapHeap = SkNEW(SkBitmapHeap);
+ fFlattenableHeap.setBitmapStorage(fBitmapHeap);
+ fPathHeap = NULL; // lazy allocate
+ fFirstSavedLayerIndex = kNoSavedLayerIndex;
+
+ fInitialSaveCount = kNoInitialSave;
+}
+
+SkPictureRecord::~SkPictureRecord() {
+ SkSafeUnref(fBitmapHeap);
+ SkSafeUnref(fPathHeap);
+ SkSafeUnref(fBoundingHierarchy);
+ SkSafeUnref(fStateTree);
+ fFlattenableHeap.setBitmapStorage(NULL);
+ fPictureRefs.unrefAll();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Return the offset of the paint inside a given op's byte stream. A zero
+// return value means there is no paint (and you really shouldn't be calling
+// this method)
+static inline uint32_t getPaintOffset(DrawType op, uint32_t opSize) {
+ // These offsets are where the paint would be if the op size doesn't overflow
+ static const uint8_t gPaintOffsets[LAST_DRAWTYPE_ENUM + 1] = {
+ 0, // UNUSED - no paint
+ 0, // CLIP_PATH - no paint
+ 0, // CLIP_REGION - no paint
+ 0, // CLIP_RECT - no paint
+ 0, // CLIP_RRECT - no paint
+ 0, // CONCAT - no paint
+ 1, // DRAW_BITMAP - right after op code
+ 1, // DRAW_BITMAP_MATRIX - right after op code
+ 1, // DRAW_BITMAP_NINE - right after op code
+ 1, // DRAW_BITMAP_RECT_TO_RECT - right after op code
+ 0, // DRAW_CLEAR - no paint
+ 0, // DRAW_DATA - no paint
+ 1, // DRAW_OVAL - right after op code
+ 1, // DRAW_PAINT - right after op code
+ 1, // DRAW_PATH - right after op code
+ 0, // DRAW_PICTURE - no paint
+ 1, // DRAW_POINTS - right after op code
+ 1, // DRAW_POS_TEXT - right after op code
+ 1, // DRAW_POS_TEXT_TOP_BOTTOM - right after op code
+ 1, // DRAW_POS_TEXT_H - right after op code
+ 1, // DRAW_POS_TEXT_H_TOP_BOTTOM - right after op code
+ 1, // DRAW_RECT - right after op code
+ 1, // DRAW_RRECT - right after op code
+ 1, // DRAW_SPRITE - right after op code
+ 1, // DRAW_TEXT - right after op code
+ 1, // DRAW_TEXT_ON_PATH - right after op code
+ 1, // DRAW_TEXT_TOP_BOTTOM - right after op code
+ 1, // DRAW_VERTICES - right after op code
+ 0, // RESTORE - no paint
+ 0, // ROTATE - no paint
+ 0, // SAVE - no paint
+ 0, // SAVE_LAYER - see below - this paint's location varies
+ 0, // SCALE - no paint
+ 0, // SET_MATRIX - no paint
+ 0, // SKEW - no paint
+ 0, // TRANSLATE - no paint
+ 0, // NOOP - no paint
+ 0, // BEGIN_GROUP - no paint
+ 0, // COMMENT - no paint
+ 0, // END_GROUP - no paint
+ };
+
+ SkASSERT(sizeof(gPaintOffsets) == LAST_DRAWTYPE_ENUM + 1);
+ SkASSERT((unsigned)op <= (unsigned)LAST_DRAWTYPE_ENUM);
+
+ int overflow = 0;
+ if (0 != (opSize & ~MASK_24) || opSize == MASK_24) {
+ // This op's size overflows so an extra uint32_t will be written
+ // after the op code
+ overflow = sizeof(uint32_t);
+ }
+
+ if (SAVE_LAYER == op) {
+ static const uint32_t kSaveLayerNoBoundsPaintOffset = 2 * kUInt32Size;
+ static const uint32_t kSaveLayerWithBoundsPaintOffset = 2 * kUInt32Size + sizeof(SkRect);
+
+ if (kSaveLayerNoBoundsSize == opSize) {
+ return kSaveLayerNoBoundsPaintOffset + overflow;
+ } else {
+ SkASSERT(kSaveLayerWithBoundsSize == opSize);
+ return kSaveLayerWithBoundsPaintOffset + overflow;
+ }
+ }
+
+ SkASSERT(0 != gPaintOffsets[op]); // really shouldn't be calling this method
+ return gPaintOffsets[op] * sizeof(uint32_t) + overflow;
+}
+
+SkDevice* SkPictureRecord::setDevice(SkDevice* device) {
+ SkASSERT(!"eeek, don't try to change the device on a recording canvas");
+ return this->INHERITED::setDevice(device);
+}
+
+int SkPictureRecord::save(SaveFlags flags) {
+ // record the offset to us, making it non-positive to distinguish a save
+ // from a clip entry.
+ fRestoreOffsetStack.push(-(int32_t)fWriter.size());
+
+ // op + flags
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(SAVE, &size);
+ addInt(flags);
+
+ validate(initialOffset, size);
+ return this->INHERITED::save(flags);
+}
+
+int SkPictureRecord::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ // record the offset to us, making it non-positive to distinguish a save
+ // from a clip entry.
+ fRestoreOffsetStack.push(-(int32_t)fWriter.size());
+
+ // op + bool for 'bounds'
+ uint32_t size = 2 * kUInt32Size;
+ if (NULL != bounds) {
+ size += sizeof(*bounds); // + rect
+ }
+ // + paint index + flags
+ size += 2 * kUInt32Size;
+
+ SkASSERT(kSaveLayerNoBoundsSize == size || kSaveLayerWithBoundsSize == size);
+
+ uint32_t initialOffset = this->addDraw(SAVE_LAYER, &size);
+ addRectPtr(bounds);
+ SkASSERT(initialOffset+getPaintOffset(SAVE_LAYER, size) == fWriter.size());
+ addPaintPtr(paint);
+ addInt(flags);
+
+ if (kNoSavedLayerIndex == fFirstSavedLayerIndex) {
+ fFirstSavedLayerIndex = fRestoreOffsetStack.count();
+ }
+
+ validate(initialOffset, size);
+ /* Don't actually call saveLayer, because that will try to allocate an
+ offscreen device (potentially very big) which we don't actually need
+ at this time (and may not be able to afford since during record our
+ clip starts out the size of the picture, which is often much larger
+ than the size of the actual device we'll use during playback).
+ */
+ int count = this->INHERITED::save(flags);
+ this->clipRectBounds(bounds, flags, NULL);
+ return count;
+}
+
+bool SkPictureRecord::isDrawingToLayer() const {
+ return fFirstSavedLayerIndex != kNoSavedLayerIndex;
+}
+
+/*
+ * Read the op code from 'offset' in 'writer' and extract the size too.
+ */
+static DrawType peek_op_and_size(SkWriter32* writer, int32_t offset, uint32_t* size) {
+ uint32_t* peek = writer->peek32(offset);
+
+ uint32_t op;
+ UNPACK_8_24(*peek, op, *size);
+ if (MASK_24 == *size) {
+ // size required its own slot right after the op code
+ *size = *writer->peek32(offset+kUInt32Size);
+ }
+ return (DrawType) op;
+}
+
+#ifdef TRACK_COLLAPSE_STATS
+ static int gCollapseCount, gCollapseCalls;
+#endif
+
+// Is the supplied paint simply a color?
+static bool is_simple(const SkPaint& p) {
+ intptr_t orAccum = (intptr_t)p.getPathEffect() |
+ (intptr_t)p.getShader() |
+ (intptr_t)p.getXfermode() |
+ (intptr_t)p.getMaskFilter() |
+ (intptr_t)p.getColorFilter() |
+ (intptr_t)p.getRasterizer() |
+ (intptr_t)p.getLooper() |
+ (intptr_t)p.getImageFilter();
+ return 0 == orAccum;
+}
+
+// CommandInfos are fed to the 'match' method and filled in with command
+// information.
+struct CommandInfo {
+ DrawType fActualOp;
+ uint32_t fOffset;
+ uint32_t fSize;
+};
+
+/*
+ * Attempt to match the provided pattern of commands starting at 'offset'
+ * in the byte stream and stopping at the end of the stream. Upon success,
+ * return true with all the pattern information filled out in the result
+ * array (i.e., actual ops, offsets and sizes).
+ * Note this method skips any NOOPs seen in the stream
+ */
+static bool match(SkWriter32* writer, uint32_t offset,
+ int* pattern, CommandInfo* result, int numCommands) {
+ SkASSERT(offset < writer->size());
+
+ uint32_t curOffset = offset;
+ uint32_t curSize = 0;
+ int numMatched;
+ for (numMatched = 0; numMatched < numCommands && curOffset < writer->size(); ++numMatched) {
+ DrawType op = peek_op_and_size(writer, curOffset, &curSize);
+ while (NOOP == op && curOffset < writer->size()) {
+ curOffset += curSize;
+ op = peek_op_and_size(writer, curOffset, &curSize);
+ }
+
+ if (curOffset >= writer->size()) {
+ return false; // ran out of byte stream
+ }
+
+ if (kDRAW_BITMAP_FLAVOR == pattern[numMatched]) {
+ if (DRAW_BITMAP != op && DRAW_BITMAP_MATRIX != op &&
+ DRAW_BITMAP_NINE != op && DRAW_BITMAP_RECT_TO_RECT != op) {
+ return false;
+ }
+ } else if (op != pattern[numMatched]) {
+ return false;
+ }
+
+ result[numMatched].fActualOp = op;
+ result[numMatched].fOffset = curOffset;
+ result[numMatched].fSize = curSize;
+
+ curOffset += curSize;
+ }
+
+ if (numMatched != numCommands) {
+ return false;
+ }
+
+ curOffset += curSize;
+ if (curOffset < writer->size()) {
+ // Something else between the last command and the end of the stream
+ return false;
+ }
+
+ return true;
+}
+
+// temporarily here to make code review easier
+static bool merge_savelayer_paint_into_drawbitmp(SkWriter32* writer,
+ SkPaintDictionary* paintDict,
+ const CommandInfo& saveLayerInfo,
+ const CommandInfo& dbmInfo);
+
+/*
+ * Restore has just been called (but not recorded), look back at the
+ * matching save* and see if we are in the configuration:
+ * SAVE_LAYER
+ * DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
+ * RESTORE
+ * where the saveLayer's color can be moved into the drawBitmap*'s paint
+ */
+static bool remove_save_layer1(SkWriter32* writer, int32_t offset,
+ SkPaintDictionary* paintDict) {
+ // back up to the save block
+ // TODO: add a stack to track save*/restore offsets rather than searching backwards
+ while (offset > 0) {
+ offset = *writer->peek32(offset);
+ }
+
+ int pattern[] = { SAVE_LAYER, kDRAW_BITMAP_FLAVOR, /* RESTORE */ };
+ CommandInfo result[SK_ARRAY_COUNT(pattern)];
+
+ if (!match(writer, -offset, pattern, result, SK_ARRAY_COUNT(pattern))) {
+ return false;
+ }
+
+ if (kSaveLayerWithBoundsSize == result[0].fSize) {
+ // The saveLayer's bound can offset where the dbm is drawn
+ return false;
+ }
+
+
+ return merge_savelayer_paint_into_drawbitmp(writer, paintDict,
+ result[0], result[1]);
+}
+
+/*
+ * Convert the command code located at 'offset' to a NOOP. Leave the size
+ * field alone so the NOOP can be skipped later.
+ */
+static void convert_command_to_noop(SkWriter32* writer, uint32_t offset) {
+ uint32_t* ptr = writer->peek32(offset);
+ *ptr = (*ptr & MASK_24) | (NOOP << 24);
+}
+
+/*
+ * Attempt to merge the saveLayer's paint into the drawBitmap*'s paint.
+ * Return true on success; false otherwise.
+ */
+static bool merge_savelayer_paint_into_drawbitmp(SkWriter32* writer,
+ SkPaintDictionary* paintDict,
+ const CommandInfo& saveLayerInfo,
+ const CommandInfo& dbmInfo) {
+ SkASSERT(SAVE_LAYER == saveLayerInfo.fActualOp);
+ SkASSERT(DRAW_BITMAP == dbmInfo.fActualOp ||
+ DRAW_BITMAP_MATRIX == dbmInfo.fActualOp ||
+ DRAW_BITMAP_NINE == dbmInfo.fActualOp ||
+ DRAW_BITMAP_RECT_TO_RECT == dbmInfo.fActualOp);
+
+ uint32_t dbmPaintOffset = getPaintOffset(dbmInfo.fActualOp, dbmInfo.fSize);
+ uint32_t slPaintOffset = getPaintOffset(SAVE_LAYER, saveLayerInfo.fSize);
+
+ // we have a match, now we need to get the paints involved
+ uint32_t dbmPaintId = *writer->peek32(dbmInfo.fOffset+dbmPaintOffset);
+ uint32_t saveLayerPaintId = *writer->peek32(saveLayerInfo.fOffset+slPaintOffset);
+
+ if (0 == saveLayerPaintId) {
+ // In this case the saveLayer/restore isn't needed at all - just kill the saveLayer
+ // and signal the caller (by returning true) to not add the RESTORE op
+ convert_command_to_noop(writer, saveLayerInfo.fOffset);
+ return true;
+ }
+
+ if (0 == dbmPaintId) {
+ // In this case just make the DBM* use the saveLayer's paint, kill the saveLayer
+ // and signal the caller (by returning true) to not add the RESTORE op
+ convert_command_to_noop(writer, saveLayerInfo.fOffset);
+ uint32_t* ptr = writer->peek32(dbmInfo.fOffset+dbmPaintOffset);
+ SkASSERT(0 == *ptr);
+ *ptr = saveLayerPaintId;
+ return true;
+ }
+
+ SkAutoTDelete<SkPaint> saveLayerPaint(paintDict->unflatten(saveLayerPaintId));
+ if (NULL == saveLayerPaint.get() || !is_simple(*saveLayerPaint)) {
+ return false;
+ }
+
+ // For this optimization we only fold the saveLayer and drawBitmapRect
+ // together if the saveLayer's draw is simple (i.e., no fancy effects) and
+ // and the only difference in the colors is that the saveLayer's can have
+ // an alpha while the drawBitmapRect's is opaque.
+ // TODO: it should be possible to fold them together even if they both
+ // have different non-255 alphas
+ SkColor layerColor = saveLayerPaint->getColor() | 0xFF000000; // force opaque
+
+ SkAutoTDelete<SkPaint> dbmPaint(paintDict->unflatten(dbmPaintId));
+ if (NULL == dbmPaint.get() || dbmPaint->getColor() != layerColor) {
+ return false;
+ }
+
+ SkColor newColor = SkColorSetA(dbmPaint->getColor(),
+ SkColorGetA(saveLayerPaint->getColor()));
+ dbmPaint->setColor(newColor);
+
+ const SkFlatData* data = paintDict->findAndReturnFlat(*dbmPaint);
+ if (NULL == data) {
+ return false;
+ }
+
+ // kill the saveLayer and alter the DBMR2R's paint to be the modified one
+ convert_command_to_noop(writer, saveLayerInfo.fOffset);
+ uint32_t* ptr = writer->peek32(dbmInfo.fOffset+dbmPaintOffset);
+ SkASSERT(dbmPaintId == *ptr);
+ *ptr = data->index();
+ return true;
+}
+
+/*
+ * Restore has just been called (but not recorded), look back at the
+ * matching save* and see if we are in the configuration:
+ * SAVE_LAYER (with NULL == bounds)
+ * SAVE
+ * CLIP_RECT
+ * DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
+ * RESTORE
+ * RESTORE
+ * where the saveLayer's color can be moved into the drawBitmap*'s paint
+ */
+static bool remove_save_layer2(SkWriter32* writer, int32_t offset,
+ SkPaintDictionary* paintDict) {
+
+ // back up to the save block
+ // TODO: add a stack to track save*/restore offsets rather than searching backwards
+ while (offset > 0) {
+ offset = *writer->peek32(offset);
+ }
+
+ int pattern[] = { SAVE_LAYER, SAVE, CLIP_RECT, kDRAW_BITMAP_FLAVOR, RESTORE, /* RESTORE */ };
+ CommandInfo result[SK_ARRAY_COUNT(pattern)];
+
+ if (!match(writer, -offset, pattern, result, SK_ARRAY_COUNT(pattern))) {
+ return false;
+ }
+
+ if (kSaveLayerWithBoundsSize == result[0].fSize) {
+ // The saveLayer's bound can offset where the dbm is drawn
+ return false;
+ }
+
+ return merge_savelayer_paint_into_drawbitmp(writer, paintDict,
+ result[0], result[3]);
+}
+
+/*
+ * Restore has just been called (but not recorded), so look back at the
+ * matching save(), and see if we can eliminate the pair of them, due to no
+ * intervening matrix/clip calls.
+ *
+ * If so, update the writer and return true, in which case we won't even record
+ * the restore() call. If we still need the restore(), return false.
+ */
+static bool collapse_save_clip_restore(SkWriter32* writer, int32_t offset,
+ SkPaintDictionary* paintDict) {
+#ifdef TRACK_COLLAPSE_STATS
+ gCollapseCalls += 1;
+#endif
+
+ int32_t restoreOffset = (int32_t)writer->size();
+
+ // back up to the save block
+ while (offset > 0) {
+ offset = *writer->peek32(offset);
+ }
+
+ // now offset points to a save
+ offset = -offset;
+ uint32_t opSize;
+ DrawType op = peek_op_and_size(writer, offset, &opSize);
+ if (SAVE_LAYER == op) {
+ // not ready to cull these out yet (mrr)
+ return false;
+ }
+ SkASSERT(SAVE == op);
+
+ // Walk forward until we get back to either a draw-verb (abort) or we hit
+ // our restore (success).
+ int32_t saveOffset = offset;
+
+ offset += opSize;
+ while (offset < restoreOffset) {
+ op = peek_op_and_size(writer, offset, &opSize);
+ if ((op > CONCAT && op < ROTATE) || (SAVE_LAYER == op)) {
+ // drawing verb, abort
+ return false;
+ }
+ offset += opSize;
+ }
+
+#ifdef TRACK_COLLAPSE_STATS
+ gCollapseCount += 1;
+ SkDebugf("Collapse [%d out of %d] %g%spn", gCollapseCount, gCollapseCalls,
+ (double)gCollapseCount / gCollapseCalls, "%");
+#endif
+
+ writer->rewindToOffset(saveOffset);
+ return true;
+}
+
+typedef bool (*PictureRecordOptProc)(SkWriter32* writer, int32_t offset,
+ SkPaintDictionary* paintDict);
+enum PictureRecordOptType {
+ kRewind_OptType, // Optimization rewinds the command stream
+ kCollapseSaveLayer_OptType, // Optimization eliminates a save/restore pair
+};
+
+struct PictureRecordOpt {
+ PictureRecordOptProc fProc;
+ PictureRecordOptType fType;
+};
+/*
+ * A list of the optimizations that are tried upon seeing a restore
+ * TODO: add a real API for such optimizations
+ * Add the ability to fire optimizations on any op (not just RESTORE)
+ */
+static const PictureRecordOpt gPictureRecordOpts[] = {
+ { collapse_save_clip_restore, kRewind_OptType },
+ { remove_save_layer1, kCollapseSaveLayer_OptType },
+ { remove_save_layer2, kCollapseSaveLayer_OptType }
+};
+
+// This is called after an optimization has been applied to the command stream
+// in order to adjust the contents and state of the bounding box hierarchy and
+// state tree to reflect the optimization.
+static void apply_optimization_to_bbh(PictureRecordOptType opt, SkPictureStateTree* stateTree,
+ SkBBoxHierarchy* boundingHierarchy) {
+ switch (opt) {
+ case kCollapseSaveLayer_OptType:
+ if (NULL != stateTree) {
+ stateTree->saveCollapsed();
+ }
+ break;
+ case kRewind_OptType:
+ if (NULL != boundingHierarchy) {
+ boundingHierarchy->rewindInserts();
+ }
+ // Note: No need to touch the state tree for this to work correctly.
+ // Unused branches do not burden the playback, and pruning the tree
+ // would be O(N^2), so it is best to leave it alone.
+ break;
+ default:
+ SkASSERT(0);
+ }
+}
+
+void SkPictureRecord::restore() {
+ // FIXME: SkDeferredCanvas needs to be refactored to respect
+ // save/restore balancing so that the following test can be
+ // turned on permanently.
+#if 0
+ SkASSERT(fRestoreOffsetStack.count() > 1);
+#endif
+
+ // check for underflow
+ if (fRestoreOffsetStack.count() == 0) {
+ return;
+ }
+
+ if (fRestoreOffsetStack.count() == fFirstSavedLayerIndex) {
+ fFirstSavedLayerIndex = kNoSavedLayerIndex;
+ }
+
+ uint32_t initialOffset, size;
+ size_t opt = 0;
+ if (!(fRecordFlags & SkPicture::kDisableRecordOptimizations_RecordingFlag)) {
+ for (opt = 0; opt < SK_ARRAY_COUNT(gPictureRecordOpts); ++opt) {
+ if ((*gPictureRecordOpts[opt].fProc)(&fWriter, fRestoreOffsetStack.top(), &fPaints)) {
+ // Some optimization fired so don't add the RESTORE
+ size = 0;
+ initialOffset = fWriter.size();
+ apply_optimization_to_bbh(gPictureRecordOpts[opt].fType,
+ fStateTree, fBoundingHierarchy);
+ break;
+ }
+ }
+ }
+
+ if ((fRecordFlags & SkPicture::kDisableRecordOptimizations_RecordingFlag) ||
+ SK_ARRAY_COUNT(gPictureRecordOpts) == opt) {
+ // No optimization fired so add the RESTORE
+ fillRestoreOffsetPlaceholdersForCurrentStackLevel((uint32_t)fWriter.size());
+ size = 1 * kUInt32Size; // RESTORE consists solely of 1 op code
+ initialOffset = this->addDraw(RESTORE, &size);
+ }
+
+ fRestoreOffsetStack.pop();
+
+ validate(initialOffset, size);
+ return this->INHERITED::restore();
+}
+
+bool SkPictureRecord::translate(SkScalar dx, SkScalar dy) {
+ // op + dx + dy
+ uint32_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar);
+ uint32_t initialOffset = this->addDraw(TRANSLATE, &size);
+ addScalar(dx);
+ addScalar(dy);
+ validate(initialOffset, size);
+ return this->INHERITED::translate(dx, dy);
+}
+
+bool SkPictureRecord::scale(SkScalar sx, SkScalar sy) {
+ // op + sx + sy
+ uint32_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar);
+ uint32_t initialOffset = this->addDraw(SCALE, &size);
+ addScalar(sx);
+ addScalar(sy);
+ validate(initialOffset, size);
+ return this->INHERITED::scale(sx, sy);
+}
+
+bool SkPictureRecord::rotate(SkScalar degrees) {
+ // op + degrees
+ uint32_t size = 1 * kUInt32Size + sizeof(SkScalar);
+ uint32_t initialOffset = this->addDraw(ROTATE, &size);
+ addScalar(degrees);
+ validate(initialOffset, size);
+ return this->INHERITED::rotate(degrees);
+}
+
+bool SkPictureRecord::skew(SkScalar sx, SkScalar sy) {
+ // op + sx + sy
+ uint32_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar);
+ uint32_t initialOffset = this->addDraw(SKEW, &size);
+ addScalar(sx);
+ addScalar(sy);
+ validate(initialOffset, size);
+ return this->INHERITED::skew(sx, sy);
+}
+
+bool SkPictureRecord::concat(const SkMatrix& matrix) {
+ validate(fWriter.size(), 0);
+ // op + matrix index
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(CONCAT, &size);
+ addMatrix(matrix);
+ validate(initialOffset, size);
+ return this->INHERITED::concat(matrix);
+}
+
+void SkPictureRecord::setMatrix(const SkMatrix& matrix) {
+ validate(fWriter.size(), 0);
+ // op + matrix index
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(SET_MATRIX, &size);
+ addMatrix(matrix);
+ validate(initialOffset, size);
+ this->INHERITED::setMatrix(matrix);
+}
+
+static bool regionOpExpands(SkRegion::Op op) {
+ switch (op) {
+ case SkRegion::kUnion_Op:
+ case SkRegion::kXOR_Op:
+ case SkRegion::kReverseDifference_Op:
+ case SkRegion::kReplace_Op:
+ return true;
+ case SkRegion::kIntersect_Op:
+ case SkRegion::kDifference_Op:
+ return false;
+ default:
+ SkDEBUGFAIL("unknown region op");
+ return false;
+ }
+}
+
+void SkPictureRecord::fillRestoreOffsetPlaceholdersForCurrentStackLevel(uint32_t restoreOffset) {
+ int32_t offset = fRestoreOffsetStack.top();
+ while (offset > 0) {
+ uint32_t* peek = fWriter.peek32(offset);
+ offset = *peek;
+ *peek = restoreOffset;
+ }
+
+#ifdef SK_DEBUG
+ // assert that the final offset value points to a save verb
+ uint32_t opSize;
+ DrawType drawOp = peek_op_and_size(&fWriter, -offset, &opSize);
+ SkASSERT(SAVE == drawOp || SAVE_LAYER == drawOp);
+#endif
+}
+
+void SkPictureRecord::beginRecording() {
+ // we have to call this *after* our constructor, to ensure that it gets
+ // recorded. This is balanced by restoreToCount() call from endRecording,
+ // which in-turn calls our overridden restore(), so those get recorded too.
+ fInitialSaveCount = this->save(kMatrixClip_SaveFlag);
+}
+
+void SkPictureRecord::endRecording() {
+ SkASSERT(kNoInitialSave != fInitialSaveCount);
+ this->restoreToCount(fInitialSaveCount);
+}
+
+void SkPictureRecord::recordRestoreOffsetPlaceholder(SkRegion::Op op) {
+ if (fRestoreOffsetStack.isEmpty()) {
+ return;
+ }
+
+ if (regionOpExpands(op)) {
+ // Run back through any previous clip ops, and mark their offset to
+ // be 0, disabling their ability to trigger a jump-to-restore, otherwise
+ // they could hide this clips ability to expand the clip (i.e. go from
+ // empty to non-empty).
+ fillRestoreOffsetPlaceholdersForCurrentStackLevel(0);
+ }
+
+ size_t offset = fWriter.size();
+ // The RestoreOffset field is initially filled with a placeholder
+ // value that points to the offset of the previous RestoreOffset
+ // in the current stack level, thus forming a linked list so that
+ // the restore offsets can be filled in when the corresponding
+ // restore command is recorded.
+ addInt(fRestoreOffsetStack.top());
+ fRestoreOffsetStack.top() = offset;
+}
+
+bool SkPictureRecord::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ // id + rect + clip params
+ uint32_t size = 1 * kUInt32Size + sizeof(rect) + 1 * kUInt32Size;
+ // recordRestoreOffsetPlaceholder doesn't always write an offset
+ if (!fRestoreOffsetStack.isEmpty()) {
+ // + restore offset
+ size += kUInt32Size;
+ }
+ uint32_t initialOffset = this->addDraw(CLIP_RECT, &size);
+ addRect(rect);
+ addInt(ClipParams_pack(op, doAA));
+ recordRestoreOffsetPlaceholder(op);
+
+ validate(initialOffset, size);
+ 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);
+ }
+
+ // op + rrect + clip params
+ uint32_t size = 1 * kUInt32Size + SkRRect::kSizeInMemory + 1 * kUInt32Size;
+ // recordRestoreOffsetPlaceholder doesn't always write an offset
+ if (!fRestoreOffsetStack.isEmpty()) {
+ // + restore offset
+ size += kUInt32Size;
+ }
+ uint32_t initialOffset = this->addDraw(CLIP_RRECT, &size);
+ addRRect(rrect);
+ addInt(ClipParams_pack(op, doAA));
+ recordRestoreOffsetPlaceholder(op);
+
+ validate(initialOffset, size);
+
+ if (fRecordFlags & SkPicture::kUsePathBoundsForClip_RecordingFlag) {
+ return this->updateClipConservativelyUsingBounds(rrect.getBounds(), op, false);
+ } 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);
+ }
+
+ // op + path index + clip params
+ uint32_t size = 3 * kUInt32Size;
+ // recordRestoreOffsetPlaceholder doesn't always write an offset
+ if (!fRestoreOffsetStack.isEmpty()) {
+ // + restore offset
+ size += kUInt32Size;
+ }
+ uint32_t initialOffset = this->addDraw(CLIP_PATH, &size);
+ addPath(path);
+ addInt(ClipParams_pack(op, doAA));
+ recordRestoreOffsetPlaceholder(op);
+
+ validate(initialOffset, size);
+
+ if (fRecordFlags & SkPicture::kUsePathBoundsForClip_RecordingFlag) {
+ return this->updateClipConservativelyUsingBounds(path.getBounds(), op,
+ path.isInverseFillType());
+ } else {
+ return this->INHERITED::clipPath(path, op, doAA);
+ }
+}
+
+bool SkPictureRecord::clipRegion(const SkRegion& region, SkRegion::Op op) {
+ // op + region index + clip params
+ uint32_t size = 3 * kUInt32Size;
+ // recordRestoreOffsetPlaceholder doesn't always write an offset
+ if (!fRestoreOffsetStack.isEmpty()) {
+ // + restore offset
+ size += kUInt32Size;
+ }
+ uint32_t initialOffset = this->addDraw(CLIP_REGION, &size);
+ addRegion(region);
+ addInt(ClipParams_pack(op, false));
+ recordRestoreOffsetPlaceholder(op);
+
+ validate(initialOffset, size);
+ return this->INHERITED::clipRegion(region, op);
+}
+
+void SkPictureRecord::clear(SkColor color) {
+ // op + color
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_CLEAR, &size);
+ addInt(color);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawPaint(const SkPaint& paint) {
+ // op + paint index
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_PAINT, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_PAINT, size) == fWriter.size());
+ addPaint(paint);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
+ const SkPaint& paint) {
+ // op + paint index + mode + count + point data
+ uint32_t size = 4 * kUInt32Size + count * sizeof(SkPoint);
+ uint32_t initialOffset = this->addDraw(DRAW_POINTS, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_POINTS, size) == fWriter.size());
+ addPaint(paint);
+ addInt(mode);
+ addInt(count);
+ fWriter.writeMul4(pts, count * sizeof(SkPoint));
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawOval(const SkRect& oval, const SkPaint& paint) {
+ // op + paint index + rect
+ uint32_t size = 2 * kUInt32Size + sizeof(oval);
+ uint32_t initialOffset = this->addDraw(DRAW_OVAL, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_OVAL, size) == fWriter.size());
+ addPaint(paint);
+ addRect(oval);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
+ // op + paint index + rect
+ uint32_t size = 2 * kUInt32Size + sizeof(rect);
+ uint32_t initialOffset = this->addDraw(DRAW_RECT, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_RECT, size) == fWriter.size());
+ addPaint(paint);
+ addRect(rect);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ if (rrect.isRect()) {
+ this->SkPictureRecord::drawRect(rrect.getBounds(), paint);
+ } else if (rrect.isOval()) {
+ this->SkPictureRecord::drawOval(rrect.getBounds(), paint);
+ } else {
+ // op + paint index + rrect
+ uint32_t initialOffset, size;
+ size = 2 * kUInt32Size + SkRRect::kSizeInMemory;
+ initialOffset = this->addDraw(DRAW_RRECT, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_RRECT, size) == fWriter.size());
+ addPaint(paint);
+ addRRect(rrect);
+ validate(initialOffset, size);
+ }
+}
+
+void SkPictureRecord::drawPath(const SkPath& path, const SkPaint& paint) {
+ // op + paint index + path index
+ uint32_t size = 3 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_PATH, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_PATH, size) == fWriter.size());
+ addPaint(paint);
+ addPath(path);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+ const SkPaint* paint = NULL) {
+ // op + paint index + bitmap index + left + top
+ uint32_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar);
+ uint32_t initialOffset = this->addDraw(DRAW_BITMAP, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP, size) == fWriter.size());
+ addPaintPtr(paint);
+ addBitmap(bitmap);
+ addScalar(left);
+ addScalar(top);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ // id + paint index + bitmap index + bool for 'src'
+ uint32_t size = 4 * kUInt32Size;
+ if (NULL != src) {
+ size += sizeof(*src); // + rect
+ }
+ size += sizeof(dst); // + rect
+
+ uint32_t initialOffset = this->addDraw(DRAW_BITMAP_RECT_TO_RECT, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_RECT_TO_RECT, size) == fWriter.size());
+ addPaintPtr(paint);
+ addBitmap(bitmap);
+ addRectPtr(src); // may be null
+ addRect(dst);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ // id + paint index + bitmap index + matrix index
+ uint32_t size = 4 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_BITMAP_MATRIX, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_MATRIX, size) == fWriter.size());
+ addPaintPtr(paint);
+ addBitmap(bitmap);
+ addMatrix(matrix);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) {
+ // op + paint index + bitmap id + center + dst rect
+ uint32_t size = 3 * kUInt32Size + sizeof(center) + sizeof(dst);
+ uint32_t initialOffset = this->addDraw(DRAW_BITMAP_NINE, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_BITMAP_NINE, size) == fWriter.size());
+ addPaintPtr(paint);
+ addBitmap(bitmap);
+ addIRect(center);
+ addRect(dst);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint = NULL) {
+ // op + paint index + bitmap index + left + top
+ uint32_t size = 5 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_SPRITE, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_SPRITE, size) == fWriter.size());
+ addPaintPtr(paint);
+ addBitmap(bitmap);
+ addInt(left);
+ addInt(top);
+ validate(initialOffset, size);
+}
+
+// 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, SK_Scalar1, metrics.fBottom);
+ (void)paint.computeFastBounds(bounds, &bounds);
+ topbot[0] = bounds.fTop;
+ topbot[1] = bounds.fBottom;
+}
+
+void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint, const SkFlatData& flat,
+ SkScalar minY, SkScalar maxY) {
+ if (!flat.isTopBotWritten()) {
+ computeFontMetricsTopBottom(paint, flat.writableTopBot());
+ SkASSERT(flat.isTopBotWritten());
+ }
+ addScalar(flat.topBot()[0] + minY);
+ addScalar(flat.topBot()[1] + maxY);
+}
+
+void SkPictureRecord::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ bool fast = !paint.isVerticalText() && paint.canComputeFastBounds();
+
+ // op + paint index + length + 'length' worth of chars + x + y
+ uint32_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 2 * sizeof(SkScalar);
+ if (fast) {
+ size += 2 * sizeof(SkScalar); // + top & bottom
+ }
+
+ DrawType op = fast ? DRAW_TEXT_TOP_BOTTOM : DRAW_TEXT;
+ uint32_t initialOffset = this->addDraw(op, &size);
+ SkASSERT(initialOffset+getPaintOffset(op, size) == fWriter.size());
+ const SkFlatData* flatPaintData = addPaint(paint);
+ SkASSERT(flatPaintData);
+ addText(text, byteLength);
+ addScalar(x);
+ addScalar(y);
+ if (fast) {
+ addFontMetricsTopBottom(paint, *flatPaintData, y, y);
+ }
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ size_t points = paint.countText(text, byteLength);
+ if (0 == points)
+ return;
+
+ bool canUseDrawH = true;
+ SkScalar minY = pos[0].fY;
+ SkScalar maxY = pos[0].fY;
+ // check if the caller really should have used drawPosTextH()
+ {
+ const SkScalar firstY = pos[0].fY;
+ for (size_t index = 1; index < points; index++) {
+ if (pos[index].fY != firstY) {
+ canUseDrawH = false;
+ if (pos[index].fY < minY) {
+ minY = pos[index].fY;
+ } else if (pos[index].fY > maxY) {
+ maxY = pos[index].fY;
+ }
+ }
+ }
+ }
+
+ bool fastBounds = !paint.isVerticalText() && paint.canComputeFastBounds();
+ bool fast = canUseDrawH && fastBounds;
+
+ // op + paint index + length + 'length' worth of data + num points
+ uint32_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 1 * kUInt32Size;
+ if (canUseDrawH) {
+ if (fast) {
+ size += 2 * sizeof(SkScalar); // + top & bottom
+ }
+ // + y-pos + actual x-point data
+ size += sizeof(SkScalar) + points * sizeof(SkScalar);
+ } else {
+ // + x&y point data
+ size += points * sizeof(SkPoint);
+ if (fastBounds) {
+ size += 2 * sizeof(SkScalar); // + top & bottom
+ }
+ }
+
+ DrawType op;
+ if (fast) {
+ op = DRAW_POS_TEXT_H_TOP_BOTTOM;
+ } else if (canUseDrawH) {
+ op = DRAW_POS_TEXT_H;
+ } else if (fastBounds) {
+ op = DRAW_POS_TEXT_TOP_BOTTOM;
+ } else {
+ op = DRAW_POS_TEXT;
+ }
+ uint32_t initialOffset = this->addDraw(op, &size);
+ SkASSERT(initialOffset+getPaintOffset(op, size) == fWriter.size());
+ const SkFlatData* flatPaintData = addPaint(paint);
+ SkASSERT(flatPaintData);
+ addText(text, byteLength);
+ addInt(points);
+
+#ifdef SK_DEBUG_SIZE
+ size_t start = fWriter.size();
+#endif
+ if (canUseDrawH) {
+ if (fast) {
+ addFontMetricsTopBottom(paint, *flatPaintData, pos[0].fY, pos[0].fY);
+ }
+ addScalar(pos[0].fY);
+ SkScalar* xptr = (SkScalar*)fWriter.reserve(points * sizeof(SkScalar));
+ for (size_t index = 0; index < points; index++)
+ *xptr++ = pos[index].fX;
+ } else {
+ fWriter.writeMul4(pos, points * sizeof(SkPoint));
+ if (fastBounds) {
+ addFontMetricsTopBottom(paint, *flatPaintData, minY, maxY);
+ }
+ }
+#ifdef SK_DEBUG_SIZE
+ fPointBytes += fWriter.size() - start;
+ fPointWrites += points;
+#endif
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ size_t points = paint.countText(text, byteLength);
+ if (0 == points)
+ return;
+
+ bool fast = !paint.isVerticalText() && paint.canComputeFastBounds();
+
+ // op + paint index + length + 'length' worth of data + num points
+ uint32_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 1 * kUInt32Size;
+ if (fast) {
+ size += 2 * sizeof(SkScalar); // + top & bottom
+ }
+ // + y + the actual points
+ size += 1 * kUInt32Size + points * sizeof(SkScalar);
+
+ uint32_t initialOffset = this->addDraw(fast ? DRAW_POS_TEXT_H_TOP_BOTTOM : DRAW_POS_TEXT_H,
+ &size);
+ const SkFlatData* flatPaintData = addPaint(paint);
+ SkASSERT(flatPaintData);
+ addText(text, byteLength);
+ addInt(points);
+
+#ifdef SK_DEBUG_SIZE
+ size_t start = fWriter.size();
+#endif
+ if (fast) {
+ addFontMetricsTopBottom(paint, *flatPaintData, constY, constY);
+ }
+ addScalar(constY);
+ fWriter.writeMul4(xpos, points * sizeof(SkScalar));
+#ifdef SK_DEBUG_SIZE
+ fPointBytes += fWriter.size() - start;
+ fPointWrites += points;
+#endif
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ // op + paint index + length + 'length' worth of data + path index + matrix index
+ uint32_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_TEXT_ON_PATH, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_TEXT_ON_PATH, size) == fWriter.size());
+ addPaint(paint);
+ addText(text, byteLength);
+ addPath(path);
+ addMatrixPtr(matrix);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawPicture(SkPicture& picture) {
+ // op + picture index
+ uint32_t size = 2 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(DRAW_PICTURE, &size);
+ addPicture(picture);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode*,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ uint32_t flags = 0;
+ if (texs) {
+ flags |= DRAW_VERTICES_HAS_TEXS;
+ }
+ if (colors) {
+ flags |= DRAW_VERTICES_HAS_COLORS;
+ }
+ if (indexCount > 0) {
+ flags |= DRAW_VERTICES_HAS_INDICES;
+ }
+
+ // op + paint index + flags + vmode + vCount + vertices
+ uint32_t size = 5 * kUInt32Size + vertexCount * sizeof(SkPoint);
+ if (flags & DRAW_VERTICES_HAS_TEXS) {
+ size += vertexCount * sizeof(SkPoint); // + uvs
+ }
+ if (flags & DRAW_VERTICES_HAS_COLORS) {
+ size += vertexCount * sizeof(SkColor); // + vert colors
+ }
+ if (flags & DRAW_VERTICES_HAS_INDICES) {
+ // + num indices + indices
+ size += 1 * kUInt32Size + SkAlign4(indexCount * sizeof(uint16_t));
+ }
+
+ uint32_t initialOffset = this->addDraw(DRAW_VERTICES, &size);
+ SkASSERT(initialOffset+getPaintOffset(DRAW_VERTICES, size) == fWriter.size());
+ addPaint(paint);
+ addInt(flags);
+ addInt(vmode);
+ addInt(vertexCount);
+ addPoints(vertices, vertexCount);
+ if (flags & DRAW_VERTICES_HAS_TEXS) {
+ addPoints(texs, vertexCount);
+ }
+ if (flags & DRAW_VERTICES_HAS_COLORS) {
+ fWriter.writeMul4(colors, vertexCount * sizeof(SkColor));
+ }
+ if (flags & DRAW_VERTICES_HAS_INDICES) {
+ addInt(indexCount);
+ fWriter.writePad(indices, indexCount * sizeof(uint16_t));
+ }
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::drawData(const void* data, size_t length) {
+ // op + length + 'length' worth of data
+ uint32_t size = 2 * kUInt32Size + SkAlign4(length);
+ uint32_t initialOffset = this->addDraw(DRAW_DATA, &size);
+ addInt(length);
+ fWriter.writePad(data, length);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::beginCommentGroup(const char* description) {
+ // op/size + length of string + \0 terminated chars
+ int length = strlen(description);
+ uint32_t size = 2 * kUInt32Size + SkAlign4(length + 1);
+ uint32_t initialOffset = this->addDraw(BEGIN_COMMENT_GROUP, &size);
+ fWriter.writeString(description, length);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::addComment(const char* kywd, const char* value) {
+ // op/size + 2x length of string + 2x \0 terminated chars
+ int kywdLen = strlen(kywd);
+ int valueLen = strlen(value);
+ uint32_t size = 3 * kUInt32Size + SkAlign4(kywdLen + 1) + SkAlign4(valueLen + 1);
+ uint32_t initialOffset = this->addDraw(COMMENT, &size);
+ fWriter.writeString(kywd, kywdLen);
+ fWriter.writeString(value, valueLen);
+ validate(initialOffset, size);
+}
+
+void SkPictureRecord::endCommentGroup() {
+ // op/size
+ uint32_t size = 1 * kUInt32Size;
+ uint32_t initialOffset = this->addDraw(END_COMMENT_GROUP, &size);
+ validate(initialOffset, size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPictureRecord::addBitmap(const SkBitmap& 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) {
+ addMatrixPtr(&matrix);
+}
+
+void SkPictureRecord::addMatrixPtr(const SkMatrix* matrix) {
+ this->addInt(matrix ? fMatrices.find(*matrix) : 0);
+}
+
+const SkFlatData* SkPictureRecord::addPaintPtr(const SkPaint* paint) {
+ const SkFlatData* data = paint ? fPaints.findAndReturnFlat(*paint) : NULL;
+ int index = data ? data->index() : 0;
+ this->addInt(index);
+ return data;
+}
+
+void SkPictureRecord::addPath(const SkPath& path) {
+ if (NULL == fPathHeap) {
+ fPathHeap = SkNEW(SkPathHeap);
+ }
+ addInt(fPathHeap->append(path));
+}
+
+void SkPictureRecord::addPicture(SkPicture& picture) {
+ int index = fPictureRefs.find(&picture);
+ if (index < 0) { // not found
+ index = fPictureRefs.count();
+ *fPictureRefs.append() = &picture;
+ picture.ref();
+ }
+ // follow the convention of recording a 1-based index
+ addInt(index + 1);
+}
+
+void SkPictureRecord::addPoint(const SkPoint& point) {
+#ifdef SK_DEBUG_SIZE
+ size_t start = fWriter.size();
+#endif
+ fWriter.writePoint(point);
+#ifdef SK_DEBUG_SIZE
+ fPointBytes += fWriter.size() - start;
+ fPointWrites++;
+#endif
+}
+
+void SkPictureRecord::addPoints(const SkPoint pts[], int count) {
+ fWriter.writeMul4(pts, count * sizeof(SkPoint));
+#ifdef SK_DEBUG_SIZE
+ fPointBytes += count * sizeof(SkPoint);
+ fPointWrites++;
+#endif
+}
+
+void SkPictureRecord::addRect(const SkRect& rect) {
+#ifdef SK_DEBUG_SIZE
+ size_t start = fWriter.size();
+#endif
+ fWriter.writeRect(rect);
+#ifdef SK_DEBUG_SIZE
+ fRectBytes += fWriter.size() - start;
+ fRectWrites++;
+#endif
+}
+
+void SkPictureRecord::addRectPtr(const SkRect* rect) {
+ if (fWriter.writeBool(rect != NULL)) {
+ fWriter.writeRect(*rect);
+ }
+}
+
+void SkPictureRecord::addIRect(const SkIRect& rect) {
+ fWriter.write(&rect, sizeof(rect));
+}
+
+void SkPictureRecord::addIRectPtr(const SkIRect* rect) {
+ if (fWriter.writeBool(rect != NULL)) {
+ *(SkIRect*)fWriter.reserve(sizeof(SkIRect)) = *rect;
+ }
+}
+
+void SkPictureRecord::addRRect(const SkRRect& rrect) {
+ fWriter.writeRRect(rrect);
+}
+
+void SkPictureRecord::addRegion(const SkRegion& region) {
+ addInt(fRegions.find(region));
+}
+
+void SkPictureRecord::addText(const void* text, size_t byteLength) {
+#ifdef SK_DEBUG_SIZE
+ size_t start = fWriter.size();
+#endif
+ addInt(byteLength);
+ fWriter.writePad(text, byteLength);
+#ifdef SK_DEBUG_SIZE
+ fTextBytes += fWriter.size() - start;
+ fTextWrites++;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG_SIZE
+size_t SkPictureRecord::size() const {
+ size_t result = 0;
+ size_t sizeData;
+ bitmaps(&sizeData);
+ result += sizeData;
+ matrices(&sizeData);
+ result += sizeData;
+ paints(&sizeData);
+ result += sizeData;
+ paths(&sizeData);
+ result += sizeData;
+ pictures(&sizeData);
+ result += sizeData;
+ regions(&sizeData);
+ result += sizeData;
+ result += streamlen();
+ return result;
+}
+
+int SkPictureRecord::bitmaps(size_t* size) const {
+ size_t result = 0;
+ int count = fBitmaps.count();
+ for (int index = 0; index < count; index++)
+ result += sizeof(fBitmaps[index]) + fBitmaps[index]->size();
+ *size = result;
+ return count;
+}
+
+int SkPictureRecord::matrices(size_t* size) const {
+ int count = fMatrices.count();
+ *size = sizeof(fMatrices[0]) * count;
+ return count;
+}
+
+int SkPictureRecord::paints(size_t* size) const {
+ size_t result = 0;
+ int count = fPaints.count();
+ for (int index = 0; index < count; index++)
+ result += sizeof(fPaints[index]) + fPaints[index]->size();
+ *size = result;
+ return count;
+}
+
+int SkPictureRecord::paths(size_t* size) const {
+ size_t result = 0;
+ int count = fPaths.count();
+ for (int index = 0; index < count; index++)
+ result += sizeof(fPaths[index]) + fPaths[index]->size();
+ *size = result;
+ return count;
+}
+
+int SkPictureRecord::regions(size_t* size) const {
+ size_t result = 0;
+ int count = fRegions.count();
+ for (int index = 0; index < count; index++)
+ result += sizeof(fRegions[index]) + fRegions[index]->size();
+ *size = result;
+ return count;
+}
+
+size_t SkPictureRecord::streamlen() const {
+ return fWriter.size();
+}
+#endif
+
+#ifdef SK_DEBUG_VALIDATE
+void SkPictureRecord::validate(uint32_t initialOffset, uint32_t size) const {
+ SkASSERT(fWriter.size() == initialOffset + size);
+
+ validateBitmaps();
+ validateMatrices();
+ validatePaints();
+ validatePaths();
+ validateRegions();
+}
+
+void SkPictureRecord::validateBitmaps() const {
+ int count = fBitmapHeap->count();
+ SkASSERT((unsigned) count < 0x1000);
+ for (int index = 0; index < count; index++) {
+ const SkBitmap* bitPtr = fBitmapHeap->getBitmap(index);
+ SkASSERT(bitPtr);
+ bitPtr->validate();
+ }
+}
+
+void SkPictureRecord::validateMatrices() const {
+ int count = fMatrices.count();
+ SkASSERT((unsigned) count < 0x1000);
+ for (int index = 0; index < count; index++) {
+ const SkFlatData* matrix = fMatrices[index];
+ SkASSERT(matrix);
+// matrix->validate();
+ }
+}
+
+void SkPictureRecord::validatePaints() const {
+ int count = fPaints.count();
+ SkASSERT((unsigned) count < 0x1000);
+ for (int index = 0; index < count; index++) {
+ const SkFlatData* paint = fPaints[index];
+ SkASSERT(paint);
+// paint->validate();
+ }
+}
+
+void SkPictureRecord::validatePaths() const {
+ if (NULL == fPathHeap) {
+ return;
+ }
+
+ int count = fPathHeap->count();
+ SkASSERT((unsigned) count < 0x1000);
+ for (int index = 0; index < count; index++) {
+ const SkPath& path = (*fPathHeap)[index];
+ path.validate();
+ }
+}
+
+void SkPictureRecord::validateRegions() const {
+ int count = fRegions.count();
+ SkASSERT((unsigned) count < 0x1000);
+ for (int index = 0; index < count; index++) {
+ const SkFlatData* region = fRegions[index];
+ SkASSERT(region);
+// region->validate();
+ }
+}
+#endif
diff --git a/core/SkPictureRecord.h b/core/SkPictureRecord.h
new file mode 100644
index 00000000..0ea857d4
--- /dev/null
+++ b/core/SkPictureRecord.h
@@ -0,0 +1,249 @@
+
+/*
+ * 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 SkPictureRecord_DEFINED
+#define SkPictureRecord_DEFINED
+
+#include "SkCanvas.h"
+#include "SkFlattenable.h"
+#include "SkPathHeap.h"
+#include "SkPicture.h"
+#include "SkPictureFlat.h"
+#include "SkTemplates.h"
+#include "SkWriter32.h"
+
+class SkPictureStateTree;
+class SkBBoxHierarchy;
+
+// These macros help with packing and unpacking a single byte value and
+// a 3 byte value into/out of a uint32_t
+#define MASK_24 0x00FFFFFF
+#define UNPACK_8_24(combined, small, large) \
+ small = (combined >> 24) & 0xFF; \
+ large = combined & MASK_24;
+#define PACK_8_24(small, large) ((small << 24) | large)
+
+
+class SkPictureRecord : public SkCanvas {
+public:
+ SkPictureRecord(uint32_t recordFlags, SkDevice*);
+ virtual ~SkPictureRecord();
+
+ virtual SkDevice* setDevice(SkDevice* device) SK_OVERRIDE;
+
+ virtual int save(SaveFlags) SK_OVERRIDE;
+ virtual int saveLayer(const SkRect* bounds, const SkPaint*, SaveFlags) SK_OVERRIDE;
+ virtual void restore() SK_OVERRIDE;
+ virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE;
+ virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE;
+ virtual bool rotate(SkScalar degrees) SK_OVERRIDE;
+ 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&, 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 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;
+ virtual void drawBitmapRectToRect(const SkBitmap&, const SkRect* src,
+ const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+ virtual void drawBitmapMatrix(const SkBitmap&, const SkMatrix&,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+ virtual void drawSprite(const SkBitmap&, int left, int top,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint&) SK_OVERRIDE;
+ virtual void drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint&) SK_OVERRIDE;
+ virtual void drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY, const SkPaint&) SK_OVERRIDE;
+ virtual void drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint&) SK_OVERRIDE;
+ virtual void drawPicture(SkPicture& picture) SK_OVERRIDE;
+ virtual void drawVertices(VertexMode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode*,
+ const uint16_t indices[], int indexCount,
+ const SkPaint&) SK_OVERRIDE;
+ virtual void drawData(const void*, size_t) SK_OVERRIDE;
+ virtual void beginCommentGroup(const char* description) SK_OVERRIDE;
+ virtual void addComment(const char* kywd, const char* value) SK_OVERRIDE;
+ virtual void endCommentGroup() SK_OVERRIDE;
+ virtual bool isDrawingToLayer() const SK_OVERRIDE;
+
+ void addFontMetricsTopBottom(const SkPaint& paint, const SkFlatData&,
+ SkScalar minY, SkScalar maxY);
+
+ const SkTDArray<SkPicture* >& getPictureRefs() const {
+ return fPictureRefs;
+ }
+
+ void setFlags(uint32_t recordFlags) {
+ fRecordFlags = recordFlags;
+ }
+
+ const SkWriter32& writeStream() const {
+ return fWriter;
+ }
+
+ void beginRecording();
+ void endRecording();
+
+private:
+ void handleOptimization(int opt);
+ void recordRestoreOffsetPlaceholder(SkRegion::Op);
+ void fillRestoreOffsetPlaceholdersForCurrentStackLevel(
+ uint32_t restoreOffset);
+
+ SkTDArray<int32_t> fRestoreOffsetStack;
+ int fFirstSavedLayerIndex;
+ enum {
+ kNoSavedLayerIndex = -1
+ };
+
+ /*
+ * Write the 'drawType' operation and chunk size to the skp. 'size'
+ * can potentially be increased if the chunk size needs its own storage
+ * location (i.e., it overflows 24 bits).
+ * Returns the start offset of the chunk. This is the location at which
+ * the opcode & size are stored.
+ * TODO: since we are handing the size into here we could call reserve
+ * and then return a pointer to the memory storage. This could decrease
+ * allocation overhead but could lead to more wasted space (the tail
+ * end of blocks could go unused). Possibly add a second addDraw that
+ * operates in this manner.
+ */
+ uint32_t addDraw(DrawType drawType, uint32_t* size) {
+ uint32_t offset = fWriter.size();
+
+ this->predrawNotify();
+
+ #ifdef SK_DEBUG_TRACE
+ SkDebugf("add %s\n", DrawTypeToString(drawType));
+ #endif
+
+ SkASSERT(0 != *size);
+ SkASSERT(((uint8_t) drawType) == drawType);
+
+ if (0 != (*size & ~MASK_24) || *size == MASK_24) {
+ fWriter.writeInt(PACK_8_24(drawType, MASK_24));
+ *size += 1;
+ fWriter.writeInt(*size);
+ } else {
+ fWriter.writeInt(PACK_8_24(drawType, *size));
+ }
+
+ return offset;
+ }
+
+ void addInt(int value) {
+ fWriter.writeInt(value);
+ }
+ void addScalar(SkScalar scalar) {
+ fWriter.writeScalar(scalar);
+ }
+
+ void addBitmap(const SkBitmap& bitmap);
+ void addMatrix(const SkMatrix& matrix);
+ void addMatrixPtr(const SkMatrix* matrix);
+ const SkFlatData* addPaint(const SkPaint& paint) { return this->addPaintPtr(&paint); }
+ const SkFlatData* addPaintPtr(const SkPaint* paint);
+ void addPath(const SkPath& path);
+ void addPicture(SkPicture& picture);
+ void addPoint(const SkPoint& point);
+ void addPoints(const SkPoint pts[], int count);
+ void addRect(const SkRect& rect);
+ 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);
+
+ int find(const SkBitmap& bitmap);
+
+#ifdef SK_DEBUG_DUMP
+public:
+ void dumpMatrices();
+ void dumpPaints();
+#endif
+
+#ifdef SK_DEBUG_SIZE
+public:
+ size_t size() const;
+ int bitmaps(size_t* size) const;
+ int matrices(size_t* size) const;
+ int paints(size_t* size) const;
+ int paths(size_t* size) const;
+ int regions(size_t* size) const;
+ size_t streamlen() const;
+
+ size_t fPointBytes, fRectBytes, fTextBytes;
+ int fPointWrites, fRectWrites, fTextWrites;
+#endif
+
+#ifdef SK_DEBUG_VALIDATE
+public:
+ void validate(uint32_t initialOffset, uint32_t size) const;
+private:
+ void validateBitmaps() const;
+ void validateMatrices() const;
+ void validatePaints() const;
+ void validatePaths() const;
+ void validateRegions() const;
+#else
+public:
+ void validate(uint32_t initialOffset, uint32_t size) const {
+ SkASSERT(fWriter.size() == initialOffset + size);
+ }
+#endif
+
+protected:
+
+ // These are set to NULL in our constructor, but may be changed by
+ // subclasses, in which case they will be SkSafeUnref'd in our destructor.
+ SkBBoxHierarchy* fBoundingHierarchy;
+ SkPictureStateTree* fStateTree;
+
+ // Allocated in the constructor and managed by this class.
+ SkBitmapHeap* fBitmapHeap;
+
+private:
+ SkChunkFlatController fFlattenableHeap;
+
+ SkMatrixDictionary fMatrices;
+ SkPaintDictionary fPaints;
+ SkRegionDictionary fRegions;
+
+ SkPathHeap* fPathHeap; // reference counted
+ SkWriter32 fWriter;
+
+ // we ref each item in these arrays
+ SkTDArray<SkPicture*> fPictureRefs;
+
+ uint32_t fRecordFlags;
+ int fInitialSaveCount;
+
+ friend class SkPicturePlayback;
+ friend class SkPictureTester; // for unit testing
+
+ typedef SkCanvas INHERITED;
+};
+
+#endif
diff --git a/core/SkPictureStateTree.cpp b/core/SkPictureStateTree.cpp
new file mode 100644
index 00000000..9f2db258
--- /dev/null
+++ b/core/SkPictureStateTree.cpp
@@ -0,0 +1,191 @@
+
+/*
+ * 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 "SkPictureStateTree.h"
+#include "SkCanvas.h"
+
+SK_DEFINE_INST_COUNT(SkPictureStateTree)
+
+SkPictureStateTree::SkPictureStateTree()
+ : fAlloc(2048)
+ , fRoot(NULL)
+ , fLastRestoredNode(NULL)
+ , fStateStack(sizeof(Draw), 16) {
+ SkMatrix* identity = static_cast<SkMatrix*>(fAlloc.allocThrow(sizeof(SkMatrix)));
+ identity->reset();
+ fRoot = static_cast<Node*>(fAlloc.allocThrow(sizeof(Node)));
+ fRoot->fParent = NULL;
+ fRoot->fMatrix = identity;
+ fRoot->fFlags = Node::kSave_Flag;
+ fRoot->fOffset = 0;
+ fRoot->fLevel = 0;
+ fCurrentState.fNode = fRoot;
+ fCurrentState.fMatrix = identity;
+ *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
+}
+
+SkPictureStateTree::~SkPictureStateTree() {
+}
+
+SkPictureStateTree::Draw* SkPictureStateTree::appendDraw(uint32_t offset) {
+ Draw* draw = static_cast<Draw*>(fAlloc.allocThrow(sizeof(Draw)));
+ *draw = fCurrentState;
+ draw->fOffset = offset;
+ return draw;
+}
+
+void SkPictureStateTree::appendSave() {
+ *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
+ fCurrentState.fNode->fFlags |= Node::kSave_Flag;
+}
+
+void SkPictureStateTree::appendSaveLayer(uint32_t offset) {
+ *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
+ this->appendNode(offset);
+ fCurrentState.fNode->fFlags |= Node::kSaveLayer_Flag;
+}
+
+void SkPictureStateTree::saveCollapsed() {
+ SkASSERT(NULL != fLastRestoredNode);
+ SkASSERT(SkToBool(fLastRestoredNode->fFlags & \
+ (Node::kSaveLayer_Flag | Node::kSave_Flag)));
+ SkASSERT(fLastRestoredNode->fParent == fCurrentState.fNode);
+ // The structure of the tree is not modified here. We just turn off
+ // the save or saveLayer flag to prevent the iterator from making state
+ // changing calls on the playback canvas when traversing a save or
+ // saveLayerNode node.
+ fLastRestoredNode->fFlags = 0;
+}
+
+void SkPictureStateTree::appendRestore() {
+ fLastRestoredNode = fCurrentState.fNode;
+ fCurrentState = *static_cast<Draw*>(fStateStack.back());
+ fStateStack.pop_back();
+}
+
+void SkPictureStateTree::appendTransform(const SkMatrix& trans) {
+ SkMatrix* m = static_cast<SkMatrix*>(fAlloc.allocThrow(sizeof(SkMatrix)));
+ *m = trans;
+ fCurrentState.fMatrix = m;
+}
+
+void SkPictureStateTree::appendClip(uint32_t offset) {
+ this->appendNode(offset);
+}
+
+SkPictureStateTree::Iterator SkPictureStateTree::getIterator(const SkTDArray<void*>& draws,
+ SkCanvas* canvas) {
+ return Iterator(draws, canvas, fRoot);
+}
+
+void SkPictureStateTree::appendNode(uint32_t offset) {
+ Node* n = static_cast<Node*>(fAlloc.allocThrow(sizeof(Node)));
+ n->fOffset = offset;
+ n->fFlags = 0;
+ n->fParent = fCurrentState.fNode;
+ n->fLevel = fCurrentState.fNode->fLevel + 1;
+ n->fMatrix = fCurrentState.fMatrix;
+ fCurrentState.fNode = n;
+}
+
+SkPictureStateTree::Iterator::Iterator(const SkTDArray<void*>& draws, SkCanvas* canvas, Node* root)
+ : fDraws(&draws)
+ , fCanvas(canvas)
+ , fCurrentNode(root)
+ , fPlaybackMatrix(canvas->getTotalMatrix())
+ , fCurrentMatrix(NULL)
+ , fPlaybackIndex(0)
+ , fSave(false)
+ , fValid(true) {
+}
+
+uint32_t SkPictureStateTree::Iterator::draw() {
+ SkASSERT(this->isValid());
+ if (fPlaybackIndex >= fDraws->count()) {
+ // restore back to where we started
+ if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
+ fCurrentNode = fCurrentNode->fParent;
+ while (NULL != fCurrentNode) {
+ if (fCurrentNode->fFlags & Node::kSave_Flag) { fCanvas->restore(); }
+ if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
+ fCurrentNode = fCurrentNode->fParent;
+ }
+ fCanvas->setMatrix(fPlaybackMatrix);
+ return kDrawComplete;
+ }
+
+ Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
+ Node* targetNode = draw->fNode;
+
+ if (fSave) {
+ fCanvas->save(SkCanvas::kClip_SaveFlag);
+ fSave = false;
+ }
+
+ if (fCurrentNode != targetNode) {
+ // If we're not at the target and we don't have a list of nodes to get there, we need to
+ // figure out the path from our current node, to the target
+ if (fNodes.count() == 0) {
+ // Trace back up to a common ancestor, restoring to get our current state to match that
+ // of the ancestor, and saving a list of nodes whose state we need to apply to get to
+ // the target (we can restore up to the ancestor immediately, but we'll need to return
+ // an offset for each node on the way down to the target, to apply the desired clips and
+ // saveLayers, so it may take several draw() calls before the next draw actually occurs)
+ Node* tmp = fCurrentNode;
+ Node* ancestor = targetNode;
+ while (tmp != ancestor) {
+ uint16_t currentLevel = tmp->fLevel;
+ uint16_t targetLevel = ancestor->fLevel;
+ if (currentLevel >= targetLevel) {
+ if (tmp != fCurrentNode && tmp->fFlags & Node::kSave_Flag) { fCanvas->restore(); }
+ if (tmp->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
+ tmp = tmp->fParent;
+ }
+ if (currentLevel <= targetLevel) {
+ fNodes.push(ancestor);
+ ancestor = ancestor->fParent;
+ }
+ }
+
+ if (ancestor->fFlags & Node::kSave_Flag) {
+ if (fCurrentNode != ancestor) { fCanvas->restore(); }
+ if (targetNode != ancestor) { fCanvas->save(SkCanvas::kClip_SaveFlag); }
+ }
+ fCurrentNode = ancestor;
+ }
+
+ // If we're not at the target node yet, we'll need to return an offset to make the caller
+ // apply the next clip or saveLayer.
+ if (fCurrentNode != targetNode) {
+ if (fCurrentMatrix != fNodes.top()->fMatrix) {
+ fCurrentMatrix = fNodes.top()->fMatrix;
+ SkMatrix tmp = *fNodes.top()->fMatrix;
+ tmp.postConcat(fPlaybackMatrix);
+ fCanvas->setMatrix(tmp);
+ }
+ uint32_t offset = fNodes.top()->fOffset;
+ fCurrentNode = fNodes.top();
+ fSave = fCurrentNode != targetNode && fCurrentNode->fFlags & Node::kSave_Flag;
+ fNodes.pop();
+ return offset;
+ }
+ }
+
+ // If we got this far, the clip/saveLayer state is all set, so we can proceed to set the matrix
+ // for the draw, and return its offset.
+
+ if (fCurrentMatrix != draw->fMatrix) {
+ SkMatrix tmp = *draw->fMatrix;
+ tmp.postConcat(fPlaybackMatrix);
+ fCanvas->setMatrix(tmp);
+ fCurrentMatrix = draw->fMatrix;
+ }
+
+ ++fPlaybackIndex;
+ return draw->fOffset;
+}
diff --git a/core/SkPictureStateTree.h b/core/SkPictureStateTree.h
new file mode 100644
index 00000000..9d8bcf86
--- /dev/null
+++ b/core/SkPictureStateTree.h
@@ -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.
+ */
+
+#ifndef SkPictureStateTree_DEFINED
+#define SkPictureStateTree_DEFINED
+
+#include "SkTDArray.h"
+#include "SkChunkAlloc.h"
+#include "SkDeque.h"
+#include "SkMatrix.h"
+#include "SkRefCnt.h"
+
+class SkCanvas;
+
+/**
+ * Provides an interface that, given a sequence of draws into an SkPicture with corresponding
+ * offsets, allows for playback of an arbitrary subset of the draws (note that Z-order is only
+ * guaranteed if the draws are explicitly sorted).
+ */
+class SkPictureStateTree : public SkRefCnt {
+private:
+ struct Node;
+public:
+ SK_DECLARE_INST_COUNT(SkPictureStateTree)
+
+ /**
+ * A draw call, stores offset into command buffer, a pointer to the matrix, and a pointer to
+ * the node in the tree that corresponds to its clip/layer state
+ */
+ struct Draw {
+ SkMatrix* fMatrix;
+ Node* fNode;
+ uint32_t fOffset;
+ bool operator<(const Draw& other) const { return fOffset < other.fOffset; }
+ };
+
+ class Iterator;
+
+ SkPictureStateTree();
+ ~SkPictureStateTree();
+
+ /**
+ * Creates and returns a struct representing a draw at the given offset.
+ */
+ Draw* appendDraw(uint32_t offset);
+
+ /**
+ * Given a list of draws, and a canvas, returns an iterator that produces the correct sequence
+ * of offsets into the command buffer to carry out those calls with correct matrix/clip state.
+ * This handles saves/restores, and does all necessary matrix setup.
+ */
+ Iterator getIterator(const SkTDArray<void*>& draws, SkCanvas* canvas);
+
+ void appendSave();
+ void appendSaveLayer(uint32_t offset);
+ void appendRestore();
+ void appendTransform(const SkMatrix& trans);
+ void appendClip(uint32_t offset);
+
+ /**
+ * Call this immediately after an appendRestore call that is associated
+ * a save or saveLayer that was removed from the command stream
+ * due to a command pattern optimization in SkPicture.
+ */
+ void saveCollapsed();
+
+ /**
+ * Playback helper
+ */
+ class Iterator {
+ public:
+ /** Returns the next offset into the picture stream, or kDrawComplete if complete. */
+ uint32_t draw();
+ static const uint32_t kDrawComplete = SK_MaxU32;
+ Iterator() : fPlaybackMatrix(), fValid(false) { }
+ bool isValid() const { return fValid; }
+ private:
+ Iterator(const SkTDArray<void*>& draws, SkCanvas* canvas, Node* root);
+ // The draws this iterator is associated with
+ const SkTDArray<void*>* fDraws;
+
+ // canvas this is playing into (so we can insert saves/restores as necessary)
+ SkCanvas* fCanvas;
+
+ // current state node
+ Node* fCurrentNode;
+
+ // List of nodes whose state we need to apply to reach TargetNode
+ SkTDArray<Node*> fNodes;
+
+ // The matrix of the canvas we're playing back into
+ const SkMatrix fPlaybackMatrix;
+
+ // Cache of current matrix, so we can avoid redundantly setting it
+ SkMatrix* fCurrentMatrix;
+
+ // current position in the array of draws
+ int fPlaybackIndex;
+ // Whether or not we need to do a save next iteration
+ bool fSave;
+
+ // Whether or not this is a valid iterator (the default public constructor sets this false)
+ bool fValid;
+
+ friend class SkPictureStateTree;
+ };
+
+private:
+
+ void appendNode(uint32_t offset);
+
+ SkChunkAlloc fAlloc;
+ Node* fRoot;
+ // Needed by saveCollapsed() because nodes do not currently store
+ // references to their children. If they did, we could just retrieve the
+ // last added child.
+ Node* fLastRestoredNode;
+
+ // The currently active state
+ Draw fCurrentState;
+ // A stack of states for tracking save/restores
+ SkDeque fStateStack;
+
+ // Represents a notable piece of state that requires an offset into the command buffer,
+ // corresponding to a clip/saveLayer/etc call, to apply.
+ struct Node {
+ Node* fParent;
+ uint32_t fOffset;
+ uint16_t fLevel;
+ uint16_t fFlags;
+ SkMatrix* fMatrix;
+ enum Flags {
+ kSave_Flag = 0x1,
+ kSaveLayer_Flag = 0x2
+ };
+ };
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkPixelRef.cpp b/core/SkPixelRef.cpp
new file mode 100644
index 00000000..bc548a22
--- /dev/null
+++ b/core/SkPixelRef.cpp
@@ -0,0 +1,213 @@
+
+/*
+ * 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 "SkPixelRef.h"
+#include "SkFlattenableBuffers.h"
+#include "SkThread.h"
+
+SK_DEFINE_INST_COUNT(SkPixelRef)
+
+
+#ifdef SK_USE_POSIX_THREADS
+
+ static SkBaseMutex gPixelRefMutexRing[] = {
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ { PTHREAD_MUTEX_INITIALIZER }, { PTHREAD_MUTEX_INITIALIZER },
+ };
+
+ // must be a power-of-2. undef to just use 1 mutex
+ #define PIXELREF_MUTEX_RING_COUNT SK_ARRAY_COUNT(gPixelRefMutexRing)
+
+#else // not pthreads
+
+ // must be a power-of-2. undef to just use 1 mutex
+ #define PIXELREF_MUTEX_RING_COUNT 32
+ static SkBaseMutex gPixelRefMutexRing[PIXELREF_MUTEX_RING_COUNT];
+
+#endif
+
+static SkBaseMutex* get_default_mutex() {
+ static int32_t gPixelRefMutexRingIndex;
+
+ SkASSERT(SkIsPow2(PIXELREF_MUTEX_RING_COUNT));
+
+ // atomic_inc might be overkill here. It may be fine if once in a while
+ // we hit a race-condition and two subsequent calls get the same index...
+ int index = sk_atomic_inc(&gPixelRefMutexRingIndex);
+ return &gPixelRefMutexRing[index & (PIXELREF_MUTEX_RING_COUNT - 1)];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int32_t SkNextPixelRefGenerationID();
+
+int32_t SkNextPixelRefGenerationID() {
+ static int32_t gPixelRefGenerationID;
+ // do a loop in case our global wraps around, as we never want to
+ // return a 0
+ int32_t genID;
+ do {
+ genID = sk_atomic_inc(&gPixelRefGenerationID) + 1;
+ } while (0 == genID);
+ return genID;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPixelRef::setMutex(SkBaseMutex* mutex) {
+ if (NULL == mutex) {
+ mutex = get_default_mutex();
+ }
+ fMutex = mutex;
+}
+
+// just need a > 0 value, so pick a funny one to aid in debugging
+#define SKPIXELREF_PRELOCKED_LOCKCOUNT 123456789
+
+SkPixelRef::SkPixelRef(SkBaseMutex* mutex) : fPreLocked(false) {
+ this->setMutex(mutex);
+ fPixels = NULL;
+ fColorTable = NULL; // we do not track ownership of this
+ fLockCount = 0;
+ fGenerationID = 0; // signal to rebuild
+ fIsImmutable = false;
+ fPreLocked = false;
+}
+
+SkPixelRef::SkPixelRef(SkFlattenableReadBuffer& buffer, SkBaseMutex* mutex)
+ : INHERITED(buffer) {
+ this->setMutex(mutex);
+ fPixels = NULL;
+ fColorTable = NULL; // we do not track ownership of this
+ fLockCount = 0;
+ fIsImmutable = buffer.readBool();
+ fGenerationID = buffer.readUInt();
+ fPreLocked = false;
+}
+
+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 {
+ this->INHERITED::flatten(buffer);
+ buffer.writeBool(fIsImmutable);
+ // We write the gen ID into the picture for within-process recording. This
+ // is safe since the same genID will never refer to two different sets of
+ // pixels (barring overflow). However, each process has its own "namespace"
+ // of genIDs. So for cross-process recording we write a zero which will
+ // trigger assignment of a new genID in playback.
+ if (buffer.isCrossProcess()) {
+ buffer.writeUInt(0);
+ } else {
+ buffer.writeUInt(fGenerationID);
+ }
+}
+
+void SkPixelRef::lockPixels() {
+ SkASSERT(!fPreLocked || SKPIXELREF_PRELOCKED_LOCKCOUNT == fLockCount);
+
+ if (!fPreLocked) {
+ SkAutoMutexAcquire ac(*fMutex);
+
+ if (1 == ++fLockCount) {
+ fPixels = this->onLockPixels(&fColorTable);
+ }
+ }
+}
+
+void SkPixelRef::unlockPixels() {
+ SkASSERT(!fPreLocked || SKPIXELREF_PRELOCKED_LOCKCOUNT == fLockCount);
+
+ if (!fPreLocked) {
+ SkAutoMutexAcquire ac(*fMutex);
+
+ SkASSERT(fLockCount > 0);
+ if (0 == --fLockCount) {
+ this->onUnlockPixels();
+ fPixels = NULL;
+ fColorTable = NULL;
+ }
+ }
+}
+
+bool SkPixelRef::lockPixelsAreWritable() const {
+ return this->onLockPixelsAreWritable();
+}
+
+bool SkPixelRef::onLockPixelsAreWritable() const {
+ return true;
+}
+
+uint32_t SkPixelRef::getGenerationID() const {
+ if (0 == fGenerationID) {
+ fGenerationID = SkNextPixelRefGenerationID();
+ }
+ return fGenerationID;
+}
+
+void SkPixelRef::notifyPixelsChanged() {
+#ifdef SK_DEBUG
+ if (fIsImmutable) {
+ SkDebugf("========== notifyPixelsChanged called on immutable pixelref");
+ }
+#endif
+ // this signals us to recompute this next time around
+ fGenerationID = 0;
+}
+
+void SkPixelRef::setImmutable() {
+ fIsImmutable = true;
+}
+
+bool SkPixelRef::readPixels(SkBitmap* dst, const SkIRect* subset) {
+ return this->onReadPixels(dst, subset);
+}
+
+bool SkPixelRef::onReadPixels(SkBitmap* dst, const SkIRect* subset) {
+ return false;
+}
+
+SkData* SkPixelRef::onRefEncodedData() {
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_BUILD_FOR_ANDROID
+void SkPixelRef::globalRef(void* data) {
+ this->ref();
+}
+
+void SkPixelRef::globalUnref() {
+ this->unref();
+}
+#endif
diff --git a/core/SkPoint.cpp b/core/SkPoint.cpp
new file mode 100644
index 00000000..bf3affaa
--- /dev/null
+++ b/core/SkPoint.cpp
@@ -0,0 +1,517 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkPoint.h"
+
+void SkIPoint::rotateCW(SkIPoint* dst) const {
+ SkASSERT(dst);
+
+ // use a tmp in case this == dst
+ int32_t tmp = fX;
+ dst->fX = -fY;
+ dst->fY = tmp;
+}
+
+void SkIPoint::rotateCCW(SkIPoint* dst) const {
+ SkASSERT(dst);
+
+ // use a tmp in case this == dst
+ int32_t tmp = fX;
+ dst->fX = fY;
+ dst->fY = -tmp;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPoint::setIRectFan(int l, int t, int r, int b, size_t stride) {
+ SkASSERT(stride >= sizeof(SkPoint));
+
+ ((SkPoint*)((intptr_t)this + 0 * stride))->set(SkIntToScalar(l),
+ SkIntToScalar(t));
+ ((SkPoint*)((intptr_t)this + 1 * stride))->set(SkIntToScalar(l),
+ SkIntToScalar(b));
+ ((SkPoint*)((intptr_t)this + 2 * stride))->set(SkIntToScalar(r),
+ SkIntToScalar(b));
+ ((SkPoint*)((intptr_t)this + 3 * stride))->set(SkIntToScalar(r),
+ SkIntToScalar(t));
+}
+
+void SkPoint::setRectFan(SkScalar l, SkScalar t, SkScalar r, SkScalar b,
+ size_t stride) {
+ SkASSERT(stride >= sizeof(SkPoint));
+
+ ((SkPoint*)((intptr_t)this + 0 * stride))->set(l, t);
+ ((SkPoint*)((intptr_t)this + 1 * stride))->set(l, b);
+ ((SkPoint*)((intptr_t)this + 2 * stride))->set(r, b);
+ ((SkPoint*)((intptr_t)this + 3 * stride))->set(r, t);
+}
+
+void SkPoint::rotateCW(SkPoint* dst) const {
+ SkASSERT(dst);
+
+ // use a tmp in case this == dst
+ SkScalar tmp = fX;
+ dst->fX = -fY;
+ dst->fY = tmp;
+}
+
+void SkPoint::rotateCCW(SkPoint* dst) const {
+ SkASSERT(dst);
+
+ // use a tmp in case this == dst
+ SkScalar tmp = fX;
+ dst->fX = fY;
+ dst->fY = -tmp;
+}
+
+void SkPoint::scale(SkScalar scale, SkPoint* dst) const {
+ SkASSERT(dst);
+ dst->set(SkScalarMul(fX, scale), SkScalarMul(fY, scale));
+}
+
+bool SkPoint::normalize() {
+ return this->setLength(fX, fY, SK_Scalar1);
+}
+
+bool SkPoint::setNormalize(SkScalar x, SkScalar y) {
+ return this->setLength(x, y, SK_Scalar1);
+}
+
+bool SkPoint::setLength(SkScalar length) {
+ return this->setLength(fX, fY, length);
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+// Returns the square of the Euclidian distance to (dx,dy).
+static inline float getLengthSquared(float dx, float dy) {
+ return dx * dx + dy * dy;
+}
+
+// Calculates the square of the Euclidian distance to (dx,dy) and stores it in
+// *lengthSquared. Returns true if the distance is judged to be "nearly zero".
+//
+// This logic is encapsulated in a helper method to make it explicit that we
+// always perform this check in the same manner, to avoid inconsistencies
+// (see http://code.google.com/p/skia/issues/detail?id=560 ).
+static inline bool isLengthNearlyZero(float dx, float dy,
+ float *lengthSquared) {
+ *lengthSquared = getLengthSquared(dx, dy);
+ return *lengthSquared <= (SK_ScalarNearlyZero * SK_ScalarNearlyZero);
+}
+
+SkScalar SkPoint::Normalize(SkPoint* pt) {
+ float x = pt->fX;
+ float y = pt->fY;
+ float mag2;
+ if (isLengthNearlyZero(x, y, &mag2)) {
+ return 0;
+ }
+
+ float mag, scale;
+ if (SkScalarIsFinite(mag2)) {
+ mag = sk_float_sqrt(mag2);
+ scale = 1 / mag;
+ } else {
+ // our mag2 step overflowed to infinity, so use doubles instead.
+ // much slower, but needed when x or y are very large, other wise we
+ // divide by inf. and return (0,0) vector.
+ double xx = x;
+ double yy = y;
+ double magmag = sqrt(xx * xx + yy * yy);
+ mag = (float)magmag;
+ // we perform the divide with the double magmag, to stay exactly the
+ // same as setLength. It would be faster to perform the divide with
+ // mag, but it is possible that mag has overflowed to inf. but still
+ // have a non-zero value for scale (thanks to denormalized numbers).
+ scale = (float)(1 / magmag);
+ }
+ pt->set(x * scale, y * scale);
+ return mag;
+}
+
+SkScalar SkPoint::Length(SkScalar dx, SkScalar dy) {
+ float mag2 = dx * dx + dy * dy;
+ if (SkScalarIsFinite(mag2)) {
+ return sk_float_sqrt(mag2);
+ } else {
+ double xx = dx;
+ double yy = dy;
+ return (float)sqrt(xx * xx + yy * yy);
+ }
+}
+
+/*
+ * We have to worry about 2 tricky conditions:
+ * 1. underflow of mag2 (compared against nearlyzero^2)
+ * 2. overflow of mag2 (compared w/ isfinite)
+ *
+ * If we underflow, we return false. If we overflow, we compute again using
+ * doubles, which is much slower (3x in a desktop test) but will not overflow.
+ */
+bool SkPoint::setLength(float x, float y, float length) {
+ float mag2;
+ if (isLengthNearlyZero(x, y, &mag2)) {
+ return false;
+ }
+
+ float scale;
+ if (SkScalarIsFinite(mag2)) {
+ scale = length / sk_float_sqrt(mag2);
+ } else {
+ // our mag2 step overflowed to infinity, so use doubles instead.
+ // much slower, but needed when x or y are very large, other wise we
+ // divide by inf. and return (0,0) vector.
+ double xx = x;
+ double yy = y;
+ scale = (float)(length / sqrt(xx * xx + yy * yy));
+ }
+ fX = x * scale;
+ fY = y * scale;
+ return true;
+}
+
+#else
+
+#include "Sk64.h"
+
+// Returns the square of the Euclidian distance to (dx,dy) in *result.
+static inline void getLengthSquared(SkScalar dx, SkScalar dy, Sk64 *result) {
+ Sk64 dySqr;
+
+ result->setMul(dx, dx);
+ dySqr.setMul(dy, dy);
+ result->add(dySqr);
+}
+
+// Calculates the square of the Euclidian distance to (dx,dy) and stores it in
+// *lengthSquared. Returns true if the distance is judged to be "nearly zero".
+//
+// This logic is encapsulated in a helper method to make it explicit that we
+// always perform this check in the same manner, to avoid inconsistencies
+// (see http://code.google.com/p/skia/issues/detail?id=560 ).
+static inline bool isLengthNearlyZero(SkScalar dx, SkScalar dy,
+ Sk64 *lengthSquared) {
+ Sk64 tolSqr;
+ getLengthSquared(dx, dy, lengthSquared);
+
+ // we want nearlyzero^2, but to compute it fast we want to just do a
+ // 32bit multiply, so we require that it not exceed 31bits. That is true
+ // if nearlyzero is <= 0xB504, which should be trivial, since usually
+ // nearlyzero is a very small fixed-point value.
+ SkASSERT(SK_ScalarNearlyZero <= 0xB504);
+
+ tolSqr.set(0, SK_ScalarNearlyZero * SK_ScalarNearlyZero);
+ return *lengthSquared <= tolSqr;
+}
+
+SkScalar SkPoint::Normalize(SkPoint* pt) {
+ Sk64 mag2;
+ if (!isLengthNearlyZero(pt->fX, pt->fY, &mag2)) {
+ SkScalar mag = mag2.getSqrt();
+ SkScalar scale = SkScalarInvert(mag);
+ pt->fX = SkScalarMul(pt->fX, scale);
+ pt->fY = SkScalarMul(pt->fY, scale);
+ return mag;
+ }
+ return 0;
+}
+
+bool SkPoint::CanNormalize(SkScalar dx, SkScalar dy) {
+ Sk64 mag2_unused;
+ return !isLengthNearlyZero(dx, dy, &mag2_unused);
+}
+
+SkScalar SkPoint::Length(SkScalar dx, SkScalar dy) {
+ Sk64 tmp;
+ getLengthSquared(dx, dy, &tmp);
+ return tmp.getSqrt();
+}
+
+#ifdef SK_DEBUGx
+static SkFixed fixlen(SkFixed x, SkFixed y) {
+ float fx = (float)x;
+ float fy = (float)y;
+
+ return (int)floorf(sqrtf(fx*fx + fy*fy) + 0.5f);
+}
+#endif
+
+static inline uint32_t squarefixed(unsigned x) {
+ x >>= 16;
+ return x*x;
+}
+
+#if 1 // Newton iter for setLength
+
+static inline unsigned invsqrt_iter(unsigned V, unsigned U) {
+ unsigned x = V * U >> 14;
+ x = x * U >> 14;
+ x = (3 << 14) - x;
+ x = (U >> 1) * x >> 14;
+ return x;
+}
+
+static const uint16_t gInvSqrt14GuessTable[] = {
+ 0x4000, 0x3c57, 0x393e, 0x3695, 0x3441, 0x3235, 0x3061,
+ 0x2ebd, 0x2d41, 0x2be7, 0x2aaa, 0x2987, 0x287a, 0x2780,
+ 0x2698, 0x25be, 0x24f3, 0x2434, 0x2380, 0x22d6, 0x2235,
+ 0x219d, 0x210c, 0x2083, 0x2000, 0x1f82, 0x1f0b, 0x1e99,
+ 0x1e2b, 0x1dc2, 0x1d5d, 0x1cfc, 0x1c9f, 0x1c45, 0x1bee,
+ 0x1b9b, 0x1b4a, 0x1afc, 0x1ab0, 0x1a67, 0x1a20, 0x19dc,
+ 0x1999, 0x1959, 0x191a, 0x18dd, 0x18a2, 0x1868, 0x1830,
+ 0x17fa, 0x17c4, 0x1791, 0x175e, 0x172d, 0x16fd, 0x16ce
+};
+
+#define BUILD_INVSQRT_TABLEx
+#ifdef BUILD_INVSQRT_TABLE
+static void build_invsqrt14_guess_table() {
+ for (int i = 8; i <= 63; i++) {
+ unsigned x = SkToU16((1 << 28) / SkSqrt32(i << 25));
+ printf("0x%x, ", x);
+ }
+ printf("\n");
+}
+#endif
+
+static unsigned fast_invsqrt(uint32_t x) {
+#ifdef BUILD_INVSQRT_TABLE
+ unsigned top2 = x >> 25;
+ SkASSERT(top2 >= 8 && top2 <= 63);
+
+ static bool gOnce;
+ if (!gOnce) {
+ build_invsqrt14_guess_table();
+ gOnce = true;
+ }
+#endif
+
+ unsigned V = x >> 14; // make V .14
+
+ unsigned top = x >> 25;
+ SkASSERT(top >= 8 && top <= 63);
+ SkASSERT(top - 8 < SK_ARRAY_COUNT(gInvSqrt14GuessTable));
+ unsigned U = gInvSqrt14GuessTable[top - 8];
+
+ U = invsqrt_iter(V, U);
+ return invsqrt_iter(V, U);
+}
+
+/* We "normalize" x,y to be .14 values (so we can square them and stay 32bits.
+ Then we Newton-iterate this in .14 space to compute the invser-sqrt, and
+ scale by it at the end. The .14 space means we can execute our iterations
+ and stay in 32bits as well, making the multiplies much cheaper than calling
+ SkFixedMul.
+*/
+bool SkPoint::setLength(SkFixed ox, SkFixed oy, SkFixed length) {
+ if (ox == 0) {
+ if (oy == 0) {
+ return false;
+ }
+ this->set(0, SkApplySign(length, SkExtractSign(oy)));
+ return true;
+ }
+ if (oy == 0) {
+ this->set(SkApplySign(length, SkExtractSign(ox)), 0);
+ return true;
+ }
+
+ unsigned x = SkAbs32(ox);
+ unsigned y = SkAbs32(oy);
+ int zeros = SkCLZ(x | y);
+
+ // make x,y 1.14 values so our fast sqr won't overflow
+ if (zeros > 17) {
+ x <<= zeros - 17;
+ y <<= zeros - 17;
+ } else {
+ x >>= 17 - zeros;
+ y >>= 17 - zeros;
+ }
+ SkASSERT((x | y) <= 0x7FFF);
+
+ unsigned invrt = fast_invsqrt(x*x + y*y);
+
+ x = x * invrt >> 12;
+ y = y * invrt >> 12;
+
+ if (length != SK_Fixed1) {
+ x = SkFixedMul(x, length);
+ y = SkFixedMul(y, length);
+ }
+ this->set(SkApplySign(x, SkExtractSign(ox)),
+ SkApplySign(y, SkExtractSign(oy)));
+ return true;
+}
+#else
+/*
+ Normalize x,y, and then scale them by length.
+
+ The obvious way to do this would be the following:
+ S64 tmp1, tmp2;
+ tmp1.setMul(x,x);
+ tmp2.setMul(y,y);
+ tmp1.add(tmp2);
+ len = tmp1.getSqrt();
+ x' = SkFixedDiv(x, len);
+ y' = SkFixedDiv(y, len);
+ This is fine, but slower than what we do below.
+
+ The present technique does not compute the starting length, but
+ rather fiddles with x,y iteratively, all the while checking its
+ magnitude^2 (avoiding a sqrt).
+
+ We normalize by first shifting x,y so that at least one of them
+ has bit 31 set (after taking the abs of them).
+ Then we loop, refining x,y by squaring them and comparing
+ against a very large 1.0 (1 << 28), and then adding or subtracting
+ a delta (which itself is reduced by half each time through the loop).
+ For speed we want the squaring to be with a simple integer mul. To keep
+ that from overflowing we shift our coordinates down until we are dealing
+ with at most 15 bits (2^15-1)^2 * 2 says withing 32 bits)
+ When our square is close to 1.0, we shift x,y down into fixed range.
+*/
+bool SkPoint::setLength(SkFixed ox, SkFixed oy, SkFixed length) {
+ if (ox == 0) {
+ if (oy == 0)
+ return false;
+ this->set(0, SkApplySign(length, SkExtractSign(oy)));
+ return true;
+ }
+ if (oy == 0) {
+ this->set(SkApplySign(length, SkExtractSign(ox)), 0);
+ return true;
+ }
+
+ SkFixed x = SkAbs32(ox);
+ SkFixed y = SkAbs32(oy);
+
+ // shift x,y so that the greater of them is 15bits (1.14 fixed point)
+ {
+ int shift = SkCLZ(x | y);
+ // make them .30
+ x <<= shift - 1;
+ y <<= shift - 1;
+ }
+
+ SkFixed dx = x;
+ SkFixed dy = y;
+
+ for (int i = 0; i < 17; i++) {
+ dx >>= 1;
+ dy >>= 1;
+
+ U32 len2 = squarefixed(x) + squarefixed(y);
+ if (len2 >> 28) {
+ x -= dx;
+ y -= dy;
+ } else {
+ x += dx;
+ y += dy;
+ }
+ }
+ x >>= 14;
+ y >>= 14;
+
+#ifdef SK_DEBUGx // measure how far we are from unit-length
+ {
+ static int gMaxError;
+ static int gMaxDiff;
+
+ SkFixed len = fixlen(x, y);
+ int err = len - SK_Fixed1;
+ err = SkAbs32(err);
+
+ if (err > gMaxError) {
+ gMaxError = err;
+ SkDebugf("gMaxError %d\n", err);
+ }
+
+ float fx = SkAbs32(ox)/65536.0f;
+ float fy = SkAbs32(oy)/65536.0f;
+ float mag = sqrtf(fx*fx + fy*fy);
+ fx /= mag;
+ fy /= mag;
+ SkFixed xx = (int)floorf(fx * 65536 + 0.5f);
+ SkFixed yy = (int)floorf(fy * 65536 + 0.5f);
+ err = SkMax32(SkAbs32(xx-x), SkAbs32(yy-y));
+ if (err > gMaxDiff) {
+ gMaxDiff = err;
+ SkDebugf("gMaxDiff %d\n", err);
+ }
+ }
+#endif
+
+ x = SkApplySign(x, SkExtractSign(ox));
+ y = SkApplySign(y, SkExtractSign(oy));
+ if (length != SK_Fixed1) {
+ x = SkFixedMul(x, length);
+ y = SkFixedMul(y, length);
+ }
+
+ this->set(x, y);
+ return true;
+}
+#endif
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkScalar SkPoint::distanceToLineBetweenSqd(const SkPoint& a,
+ const SkPoint& b,
+ Side* side) const {
+
+ SkVector u = b - a;
+ SkVector v = *this - a;
+
+ SkScalar uLengthSqd = u.lengthSqd();
+ SkScalar det = u.cross(v);
+ if (NULL != side) {
+ SkASSERT(-1 == SkPoint::kLeft_Side &&
+ 0 == SkPoint::kOn_Side &&
+ 1 == kRight_Side);
+ *side = (Side) SkScalarSignAsInt(det);
+ }
+ return SkScalarMulDiv(det, det, uLengthSqd);
+}
+
+SkScalar SkPoint::distanceToLineSegmentBetweenSqd(const SkPoint& a,
+ const SkPoint& b) const {
+ // See comments to distanceToLineBetweenSqd. If the projection of c onto
+ // u is between a and b then this returns the same result as that
+ // function. Otherwise, it returns the distance to the closer of a and
+ // b. Let the projection of v onto u be v'. There are three cases:
+ // 1. v' points opposite to u. c is not between a and b and is closer
+ // to a than b.
+ // 2. v' points along u and has magnitude less than y. c is between
+ // a and b and the distance to the segment is the same as distance
+ // to the line ab.
+ // 3. v' points along u and has greater magnitude than u. c is not
+ // not between a and b and is closer to b than a.
+ // v' = (u dot v) * u / |u|. So if (u dot v)/|u| is less than zero we're
+ // in case 1. If (u dot v)/|u| is > |u| we are in case 3. Otherwise
+ // we're in case 2. We actually compare (u dot v) to 0 and |u|^2 to
+ // avoid a sqrt to compute |u|.
+
+ SkVector u = b - a;
+ SkVector v = *this - a;
+
+ SkScalar uLengthSqd = u.lengthSqd();
+ SkScalar uDotV = SkPoint::DotProduct(u, v);
+
+ if (uDotV <= 0) {
+ return v.lengthSqd();
+ } else if (uDotV > uLengthSqd) {
+ return b.distanceToSqd(*this);
+ } else {
+ SkScalar det = u.cross(v);
+ return SkScalarMulDiv(det, det, uLengthSqd);
+ }
+}
diff --git a/core/SkProcSpriteBlitter.cpp b/core/SkProcSpriteBlitter.cpp
new file mode 100644
index 00000000..a4819205
--- /dev/null
+++ b/core/SkProcSpriteBlitter.cpp
@@ -0,0 +1,47 @@
+
+/*
+ * 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.
+ */
+
+
+#if 0 // experimental
+
+class SkProcSpriteBlitter : public SkSpriteBlitter {
+public:
+ typedef void (*Proc)(void* dst, const void* src, int count, const SkPMColor ctable[]);
+
+ SkProcSpriteBlitter(const SkBitmap& source, Proc proc, unsigned srcShift, unsigned dstShift)
+ : SkSpriteBlitter(source), fProc(proc), fSrcShift(SkToU8(srcShift)), fDstShift(SkToU8(dstShift)) {}
+
+ virtual void blitRect(int x, int y, int width, int height)
+ {
+ size_t dstRB = fDevice.rowBytes();
+ size_t srcRB = fSource.rowBytes();
+ char* dst = (char*)fDevice.getPixels() + y * dstRB + (x << fDstShift);
+ const char* src = (const char*)fSource.getPixels() + (y - fTop) * srcRB + ((x - fLeft) << fSrcShift);
+ Proc proc = fProc;
+ const SkPMColor* ctable = NULL;
+
+ if fSource.getColorTable())
+ ctable = fSource.getColorTable()->lockColors();
+
+ while (--height >= 0)
+ {
+ proc(dst, src, width, ctable);
+ dst += dstRB;
+ src += srcRB;
+ }
+
+ if fSource.getColorTable())
+ fSource.getColorTable()->unlockColors(false);
+ }
+
+private:
+ Proc fProc;
+ uint8_t fSrcShift, fDstShift;
+};
+
+#endif
diff --git a/core/SkPtrRecorder.cpp b/core/SkPtrRecorder.cpp
new file mode 100644
index 00000000..2acb5af9
--- /dev/null
+++ b/core/SkPtrRecorder.cpp
@@ -0,0 +1,77 @@
+
+/*
+ * 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 "SkPtrRecorder.h"
+#include "SkTSearch.h"
+
+SK_DEFINE_INST_COUNT(SkPtrSet)
+SK_DEFINE_INST_COUNT(SkNamedFactorySet)
+
+void SkPtrSet::reset() {
+ Pair* p = fList.begin();
+ Pair* stop = fList.end();
+ while (p < stop) {
+ this->decPtr(p->fPtr);
+ p += 1;
+ }
+ fList.reset();
+}
+
+bool SkPtrSet::Less(const Pair& a, const Pair& b) {
+ return (char*)a.fPtr < (char*)b.fPtr;
+}
+
+uint32_t SkPtrSet::find(void* ptr) const {
+ if (NULL == ptr) {
+ return 0;
+ }
+
+ int count = fList.count();
+ Pair pair;
+ pair.fPtr = ptr;
+
+ int index = SkTSearch<Pair, Less>(fList.begin(), count, pair, sizeof(pair));
+ if (index < 0) {
+ return 0;
+ }
+ return fList[index].fIndex;
+}
+
+uint32_t SkPtrSet::add(void* ptr) {
+ if (NULL == ptr) {
+ return 0;
+ }
+
+ int count = fList.count();
+ Pair pair;
+ pair.fPtr = ptr;
+
+ int index = SkTSearch<Pair, Less>(fList.begin(), count, pair, sizeof(pair));
+ if (index < 0) {
+ index = ~index; // turn it back into an index for insertion
+ this->incPtr(ptr);
+ pair.fIndex = count + 1;
+ *fList.insert(index) = pair;
+ return count + 1;
+ } else {
+ return fList[index].fIndex;
+ }
+}
+
+void SkPtrSet::copyToArray(void* array[]) const {
+ int count = fList.count();
+ if (count > 0) {
+ SkASSERT(array);
+ const Pair* p = fList.begin();
+ // p->fIndex is base-1, so we need to subtract to find its slot
+ for (int i = 0; i < count; i++) {
+ int index = p[i].fIndex - 1;
+ SkASSERT((unsigned)index < (unsigned)count);
+ array[index] = p[i].fPtr;
+ }
+ }
+}
diff --git a/core/SkPtrRecorder.h b/core/SkPtrRecorder.h
new file mode 100644
index 00000000..06e14ab6
--- /dev/null
+++ b/core/SkPtrRecorder.h
@@ -0,0 +1,151 @@
+
+/*
+ * 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 SkPtrSet_DEFINED
+#define SkPtrSet_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkFlattenable.h"
+#include "SkTDArray.h"
+
+/**
+ * Maintains a set of ptrs, assigning each a unique ID [1...N]. Duplicate ptrs
+ * return the same ID (since its a set). Subclasses can override inPtr()
+ * and decPtr(). incPtr() is called each time a unique ptr is added ot the
+ * set. decPtr() is called on each ptr when the set is destroyed or reset.
+ */
+class SkPtrSet : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkPtrSet)
+
+ /**
+ * Search for the specified ptr in the set. If it is found, return its
+ * 32bit ID [1..N], or if not found, return 0. Always returns 0 for NULL.
+ */
+ uint32_t find(void*) const;
+
+ /**
+ * Add the specified ptr to the set, returning a unique 32bit ID for it
+ * [1...N]. Duplicate ptrs will return the same ID.
+ *
+ * If the ptr is NULL, it is not added, and 0 is returned.
+ */
+ uint32_t add(void*);
+
+ /**
+ * Return the number of (non-null) ptrs in the set.
+ */
+ int count() const { return fList.count(); }
+
+ /**
+ * Copy the ptrs in the set into the specified array (allocated by the
+ * caller). The ptrs are assgined to the array based on their corresponding
+ * ID. e.g. array[ptr.ID - 1] = ptr.
+ *
+ * incPtr() and decPtr() are not called during this operation.
+ */
+ void copyToArray(void* array[]) const;
+
+ /**
+ * Call decPtr() on each ptr in the set, and the reset the size of the set
+ * to 0.
+ */
+ void reset();
+
+protected:
+ virtual void incPtr(void*) {}
+ virtual void decPtr(void*) {}
+
+private:
+ struct Pair {
+ void* fPtr; // never NULL
+ uint32_t fIndex; // 1...N
+ };
+
+ // we store the ptrs in sorted-order (using Cmp) so that we can efficiently
+ // detect duplicates when add() is called. Hence we need to store the
+ // ptr and its ID/fIndex explicitly, since the ptr's position in the array
+ // is not related to its "index".
+ SkTDArray<Pair> fList;
+
+ static bool Less(const Pair& a, const Pair& b);
+
+ typedef SkRefCnt INHERITED;
+};
+
+/**
+ * Templated wrapper for SkPtrSet, just meant to automate typecasting
+ * parameters to and from void* (which the base class expects).
+ */
+template <typename T> class SkTPtrSet : public SkPtrSet {
+public:
+ uint32_t find(T ptr) {
+ return this->INHERITED::find((void*)ptr);
+ }
+ uint32_t add(T ptr) {
+ return this->INHERITED::add((void*)ptr);
+ }
+
+ void copyToArray(T* array) const {
+ this->INHERITED::copyToArray((void**)array);
+ }
+
+private:
+ typedef SkPtrSet INHERITED;
+};
+
+/**
+ * Subclass of SkTPtrSet specialed to call ref() and unref() when the
+ * base class's incPtr() and decPtr() are called. This makes it a valid owner
+ * of each ptr, which is released when the set is reset or destroyed.
+ */
+class SkRefCntSet : public SkTPtrSet<SkRefCnt*> {
+public:
+ virtual ~SkRefCntSet();
+
+protected:
+ // overrides
+ virtual void incPtr(void*);
+ virtual void decPtr(void*);
+};
+
+class SkFactorySet : public SkTPtrSet<SkFlattenable::Factory> {};
+
+/**
+ * Similar to SkFactorySet, but only allows Factorys that have registered names.
+ * Also has a function to return the next added Factory's name.
+ */
+class SkNamedFactorySet : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkNamedFactorySet)
+
+ SkNamedFactorySet();
+
+ /**
+ * Find the specified Factory in the set. If it is not already in the set,
+ * and has registered its name, add it to the set, and return its index.
+ * If the Factory has no registered name, return 0.
+ */
+ uint32_t find(SkFlattenable::Factory);
+
+ /**
+ * If new Factorys have been added to the set, return the name of the first
+ * Factory added after the Factory name returned by the last call to this
+ * function.
+ */
+ const char* getNextAddedFactoryName();
+private:
+ int fNextAddedFactory;
+ SkFactorySet fFactorySet;
+ SkTDArray<const char*> fNames;
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkQuadClipper.cpp b/core/SkQuadClipper.cpp
new file mode 100644
index 00000000..a67a23f9
--- /dev/null
+++ b/core/SkQuadClipper.cpp
@@ -0,0 +1,128 @@
+
+/*
+ * Copyright 2009 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 "SkQuadClipper.h"
+#include "SkGeometry.h"
+
+static inline void clamp_le(SkScalar& value, SkScalar max) {
+ if (value > max) {
+ value = max;
+ }
+}
+
+static inline void clamp_ge(SkScalar& value, SkScalar min) {
+ if (value < min) {
+ value = min;
+ }
+}
+
+SkQuadClipper::SkQuadClipper() {
+ fClip.setEmpty();
+}
+
+void SkQuadClipper::setClip(const SkIRect& clip) {
+ // conver to scalars, since that's where we'll see the points
+ fClip.set(clip);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
+ SkScalar target, SkScalar* t) {
+ /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
+ * We solve for t, using quadratic equation, hence we have to rearrange
+ * our cooefficents to look like At^2 + Bt + C
+ */
+ SkScalar A = c0 - c1 - c1 + c2;
+ SkScalar B = 2*(c1 - c0);
+ SkScalar C = c0 - target;
+
+ SkScalar roots[2]; // we only expect one, but make room for 2 for safety
+ int count = SkFindUnitQuadRoots(A, B, C, roots);
+ if (count) {
+ *t = roots[0];
+ return true;
+ }
+ return false;
+}
+
+static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
+ return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* If we somehow returned the fact that we had to flip the pts in Y, we could
+ communicate that to setQuadratic, and then avoid having to flip it back
+ here (only to have setQuadratic do the flip again)
+ */
+bool SkQuadClipper::clipQuad(const SkPoint srcPts[3], SkPoint dst[3]) {
+ bool reverse;
+
+ // we need the data to be monotonically increasing in Y
+ if (srcPts[0].fY > srcPts[2].fY) {
+ dst[0] = srcPts[2];
+ dst[1] = srcPts[1];
+ dst[2] = srcPts[0];
+ reverse = true;
+ } else {
+ memcpy(dst, srcPts, 3 * sizeof(SkPoint));
+ reverse = false;
+ }
+
+ // are we completely above or below
+ const SkScalar ctop = fClip.fTop;
+ const SkScalar cbot = fClip.fBottom;
+ if (dst[2].fY <= ctop || dst[0].fY >= cbot) {
+ return false;
+ }
+
+ SkScalar t;
+ SkPoint tmp[5]; // for SkChopQuadAt
+
+ // are we partially above
+ if (dst[0].fY < ctop) {
+ if (chopMonoQuadAtY(dst, ctop, &t)) {
+ // take the 2nd chopped quad
+ SkChopQuadAt(dst, tmp, t);
+ dst[0] = tmp[2];
+ dst[1] = tmp[3];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the top
+ for (int i = 0; i < 3; i++) {
+ if (dst[i].fY < ctop) {
+ dst[i].fY = ctop;
+ }
+ }
+ }
+ }
+
+ // are we partially below
+ if (dst[2].fY > cbot) {
+ if (chopMonoQuadAtY(dst, cbot, &t)) {
+ SkChopQuadAt(dst, tmp, t);
+ dst[1] = tmp[1];
+ dst[2] = tmp[2];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the bottom
+ for (int i = 0; i < 3; i++) {
+ if (dst[i].fY > cbot) {
+ dst[i].fY = cbot;
+ }
+ }
+ }
+ }
+
+ if (reverse) {
+ SkTSwap<SkPoint>(dst[0], dst[2]);
+ }
+ return true;
+}
diff --git a/core/SkQuadClipper.h b/core/SkQuadClipper.h
new file mode 100644
index 00000000..c0b8695c
--- /dev/null
+++ b/core/SkQuadClipper.h
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright 2009 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 SkQuadClipper_DEFINED
+#define SkQuadClipper_DEFINED
+
+#include "SkPath.h"
+
+/** This class is initialized with a clip rectangle, and then can be fed quads,
+ which must already be monotonic in Y.
+
+ In the future, it might return a series of segments, allowing it to clip
+ also in X, to ensure that all segments fit in a finite coordinate system.
+ */
+class SkQuadClipper {
+public:
+ SkQuadClipper();
+
+ void setClip(const SkIRect& clip);
+
+ bool clipQuad(const SkPoint src[3], SkPoint dst[3]);
+
+private:
+ SkRect fClip;
+};
+
+/** Iterator that returns the clipped segements of a quad clipped to a rect.
+ The segments will be either lines or quads (based on SkPath::Verb), and
+ will all be monotonic in Y
+ */
+class SkQuadClipper2 {
+public:
+ bool clipQuad(const SkPoint pts[3], const SkRect& clip);
+ bool clipCubic(const SkPoint pts[4], const SkRect& clip);
+
+ SkPath::Verb next(SkPoint pts[]);
+
+private:
+ SkPoint* fCurrPoint;
+ SkPath::Verb* fCurrVerb;
+
+ enum {
+ kMaxVerbs = 13,
+ kMaxPoints = 32
+ };
+ SkPoint fPoints[kMaxPoints];
+ SkPath::Verb fVerbs[kMaxVerbs];
+
+ void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip);
+ void clipMonoCubic(const SkPoint srcPts[4], const SkRect& clip);
+ void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse);
+ void appendQuad(const SkPoint pts[3], bool reverse);
+ void appendCubic(const SkPoint pts[4], bool reverse);
+};
+
+#ifdef SK_DEBUG
+ void sk_assert_monotonic_x(const SkPoint pts[], int count);
+ void sk_assert_monotonic_y(const SkPoint pts[], int count);
+#else
+ #define sk_assert_monotonic_x(pts, count)
+ #define sk_assert_monotonic_y(pts, count)
+#endif
+
+#endif
diff --git a/core/SkRRect.cpp b/core/SkRRect.cpp
new file mode 100644
index 00000000..75af106b
--- /dev/null
+++ b/core/SkRRect.cpp
@@ -0,0 +1,361 @@
+/*
+ * 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: assert that all the x&y radii are already W/2 & H/2
+ }
+
+ 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
+ return checkCornerContainment(x, y);
+}
+
+// This method determines if a point known to be inside the RRect's bounds is
+// inside all the corners.
+bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
+ 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
+ // or :
+ // b^2*x^2 + a^2*y^2 <= (ab)^2
+ SkScalar dist = SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
+ SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
+ return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
+}
+
+bool SkRRect::contains(const SkRect& rect) const {
+ if (!this->getBounds().contains(rect)) {
+ // If 'rect' isn't contained by the RR's bounds then the
+ // RR definitely doesn't contain it
+ return false;
+ }
+
+ if (this->isRect()) {
+ // the prior test was sufficient
+ return true;
+ }
+
+ // At this point we know all four corners of 'rect' are inside the
+ // bounds of of this RR. Check to make sure all the corners are inside
+ // all the curves
+ return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
+ this->checkCornerContainment(rect.fRight, rect.fTop) &&
+ this->checkCornerContainment(rect.fRight, rect.fBottom) &&
+ this->checkCornerContainment(rect.fLeft, rect.fBottom);
+}
+
+// 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;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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];
+ memcpy(radii, fRadii, sizeof(radii));
+ 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);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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/core/SkRTree.cpp b/core/SkRTree.cpp
new file mode 100644
index 00000000..d7a15d59
--- /dev/null
+++ b/core/SkRTree.cpp
@@ -0,0 +1,481 @@
+
+/*
+ * 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 "SkRTree.h"
+#include "SkTSort.h"
+
+static inline uint32_t get_area(const SkIRect& rect);
+static inline uint32_t get_overlap(const SkIRect& rect1, const SkIRect& rect2);
+static inline uint32_t get_margin(const SkIRect& rect);
+static inline uint32_t get_overlap_increase(const SkIRect& rect1, const SkIRect& rect2,
+ SkIRect expandBy);
+static inline uint32_t get_area_increase(const SkIRect& rect1, SkIRect rect2);
+static inline void join_no_empty_check(const SkIRect& joinWith, SkIRect* out);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_INST_COUNT(SkRTree)
+
+SkRTree* SkRTree::Create(int minChildren, int maxChildren, SkScalar aspectRatio) {
+ if (minChildren < maxChildren && (maxChildren + 1) / 2 >= minChildren &&
+ minChildren > 0 && maxChildren < static_cast<int>(SK_MaxU16)) {
+ return new SkRTree(minChildren, maxChildren, aspectRatio);
+ }
+ return NULL;
+}
+
+SkRTree::SkRTree(int minChildren, int maxChildren, SkScalar aspectRatio)
+ : fMinChildren(minChildren)
+ , fMaxChildren(maxChildren)
+ , fNodeSize(sizeof(Node) + sizeof(Branch) * maxChildren)
+ , fCount(0)
+ , fNodes(fNodeSize * 256)
+ , fAspectRatio(aspectRatio) {
+ SkASSERT(minChildren < maxChildren && minChildren > 0 && maxChildren <
+ static_cast<int>(SK_MaxU16));
+ SkASSERT((maxChildren + 1) / 2 >= minChildren);
+ this->validate();
+}
+
+SkRTree::~SkRTree() {
+ this->clear();
+}
+
+void SkRTree::insert(void* data, const SkIRect& bounds, bool defer) {
+ this->validate();
+ if (bounds.isEmpty()) {
+ SkASSERT(false);
+ return;
+ }
+ Branch newBranch;
+ newBranch.fBounds = bounds;
+ newBranch.fChild.data = data;
+ if (this->isEmpty()) {
+ // since a bulk-load into an existing tree is as of yet unimplemented (and arguably not
+ // of vital importance right now), we only batch up inserts if the tree is empty.
+ if (defer) {
+ fDeferredInserts.push(newBranch);
+ return;
+ } else {
+ fRoot.fChild.subtree = allocateNode(0);
+ fRoot.fChild.subtree->fNumChildren = 0;
+ }
+ }
+
+ Branch* newSibling = insert(fRoot.fChild.subtree, &newBranch);
+ fRoot.fBounds = this->computeBounds(fRoot.fChild.subtree);
+
+ if (NULL != newSibling) {
+ Node* oldRoot = fRoot.fChild.subtree;
+ Node* newRoot = this->allocateNode(oldRoot->fLevel + 1);
+ newRoot->fNumChildren = 2;
+ *newRoot->child(0) = fRoot;
+ *newRoot->child(1) = *newSibling;
+ fRoot.fChild.subtree = newRoot;
+ fRoot.fBounds = this->computeBounds(fRoot.fChild.subtree);
+ }
+
+ ++fCount;
+ this->validate();
+}
+
+void SkRTree::flushDeferredInserts() {
+ this->validate();
+ if (this->isEmpty() && fDeferredInserts.count() > 0) {
+ fCount = fDeferredInserts.count();
+ if (1 == fCount) {
+ fRoot.fChild.subtree = allocateNode(0);
+ fRoot.fChild.subtree->fNumChildren = 0;
+ this->insert(fRoot.fChild.subtree, &fDeferredInserts[0]);
+ fRoot.fBounds = fDeferredInserts[0].fBounds;
+ } else {
+ fRoot = this->bulkLoad(&fDeferredInserts);
+ }
+ } else {
+ // TODO: some algorithm for bulk loading into an already populated tree
+ SkASSERT(0 == fDeferredInserts.count());
+ }
+ fDeferredInserts.rewind();
+ this->validate();
+}
+
+void SkRTree::search(const SkIRect& query, SkTDArray<void*>* results) {
+ this->validate();
+ if (0 != fDeferredInserts.count()) {
+ this->flushDeferredInserts();
+ }
+ if (!this->isEmpty() && SkIRect::IntersectsNoEmptyCheck(fRoot.fBounds, query)) {
+ this->search(fRoot.fChild.subtree, query, results);
+ }
+ this->validate();
+}
+
+void SkRTree::clear() {
+ this->validate();
+ fNodes.reset();
+ fDeferredInserts.rewind();
+ fCount = 0;
+ this->validate();
+}
+
+SkRTree::Node* SkRTree::allocateNode(uint16_t level) {
+ Node* out = static_cast<Node*>(fNodes.allocThrow(fNodeSize));
+ out->fNumChildren = 0;
+ out->fLevel = level;
+ return out;
+}
+
+SkRTree::Branch* SkRTree::insert(Node* root, Branch* branch, uint16_t level) {
+ Branch* toInsert = branch;
+ if (root->fLevel != level) {
+ int childIndex = this->chooseSubtree(root, branch);
+ toInsert = this->insert(root->child(childIndex)->fChild.subtree, branch, level);
+ root->child(childIndex)->fBounds = this->computeBounds(
+ root->child(childIndex)->fChild.subtree);
+ }
+ if (NULL != toInsert) {
+ if (root->fNumChildren == fMaxChildren) {
+ // handle overflow by splitting. TODO: opportunistic reinsertion
+
+ // decide on a distribution to divide with
+ Node* newSibling = this->allocateNode(root->fLevel);
+ Branch* toDivide = SkNEW_ARRAY(Branch, fMaxChildren + 1);
+ for (int i = 0; i < fMaxChildren; ++i) {
+ toDivide[i] = *root->child(i);
+ }
+ toDivide[fMaxChildren] = *toInsert;
+ int splitIndex = this->distributeChildren(toDivide);
+
+ // divide up the branches
+ root->fNumChildren = splitIndex;
+ newSibling->fNumChildren = fMaxChildren + 1 - splitIndex;
+ for (int i = 0; i < splitIndex; ++i) {
+ *root->child(i) = toDivide[i];
+ }
+ for (int i = splitIndex; i < fMaxChildren + 1; ++i) {
+ *newSibling->child(i - splitIndex) = toDivide[i];
+ }
+ SkDELETE_ARRAY(toDivide);
+
+ // pass the new sibling branch up to the parent
+ branch->fChild.subtree = newSibling;
+ branch->fBounds = this->computeBounds(newSibling);
+ return branch;
+ } else {
+ *root->child(root->fNumChildren) = *toInsert;
+ ++root->fNumChildren;
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+int SkRTree::chooseSubtree(Node* root, Branch* branch) {
+ SkASSERT(!root->isLeaf());
+ if (1 < root->fLevel) {
+ // root's child pointers do not point to leaves, so minimize area increase
+ int32_t minAreaIncrease = SK_MaxS32;
+ int32_t minArea = SK_MaxS32;
+ int32_t bestSubtree = -1;
+ for (int i = 0; i < root->fNumChildren; ++i) {
+ const SkIRect& subtreeBounds = root->child(i)->fBounds;
+ int32_t areaIncrease = get_area_increase(subtreeBounds, branch->fBounds);
+ // break ties in favor of subtree with smallest area
+ if (areaIncrease < minAreaIncrease || (areaIncrease == minAreaIncrease &&
+ static_cast<int32_t>(get_area(subtreeBounds)) < minArea)) {
+ minAreaIncrease = areaIncrease;
+ minArea = get_area(subtreeBounds);
+ bestSubtree = i;
+ }
+ }
+ SkASSERT(-1 != bestSubtree);
+ return bestSubtree;
+ } else if (1 == root->fLevel) {
+ // root's child pointers do point to leaves, so minimize overlap increase
+ int32_t minOverlapIncrease = SK_MaxS32;
+ int32_t minAreaIncrease = SK_MaxS32;
+ int32_t bestSubtree = -1;
+ for (int32_t i = 0; i < root->fNumChildren; ++i) {
+ const SkIRect& subtreeBounds = root->child(i)->fBounds;
+ SkIRect expandedBounds = subtreeBounds;
+ join_no_empty_check(branch->fBounds, &expandedBounds);
+ int32_t overlap = 0;
+ for (int32_t j = 0; j < root->fNumChildren; ++j) {
+ if (j == i) continue;
+ // Note: this would be more correct if we subtracted the original pre-expanded
+ // overlap, but computing overlaps is expensive and omitting it doesn't seem to
+ // hurt query performance. See get_overlap_increase()
+ overlap += get_overlap(expandedBounds, root->child(j)->fBounds);
+ }
+ // break ties with lowest area increase
+ if (overlap < minOverlapIncrease || (overlap == minOverlapIncrease &&
+ static_cast<int32_t>(get_area_increase(branch->fBounds, subtreeBounds)) <
+ minAreaIncrease)) {
+ minOverlapIncrease = overlap;
+ minAreaIncrease = get_area_increase(branch->fBounds, subtreeBounds);
+ bestSubtree = i;
+ }
+ }
+ return bestSubtree;
+ } else {
+ SkASSERT(false);
+ return 0;
+ }
+}
+
+SkIRect SkRTree::computeBounds(Node* n) {
+ SkIRect r = n->child(0)->fBounds;
+ for (int i = 1; i < n->fNumChildren; ++i) {
+ join_no_empty_check(n->child(i)->fBounds, &r);
+ }
+ return r;
+}
+
+int SkRTree::distributeChildren(Branch* children) {
+ // We have two sides to sort by on each of two axes:
+ const static SortSide sorts[2][2] = {
+ {&SkIRect::fLeft, &SkIRect::fRight},
+ {&SkIRect::fTop, &SkIRect::fBottom}
+ };
+
+ // We want to choose an axis to split on, then a distribution along that axis; we'll need
+ // three pieces of info: the split axis, the side to sort by on that axis, and the index
+ // to split the sorted array on.
+ int32_t sortSide = -1;
+ int32_t k = -1;
+ int32_t axis = -1;
+ int32_t bestS = SK_MaxS32;
+
+ // Evaluate each axis, we want the min summed margin-value (s) over all distributions
+ for (int i = 0; i < 2; ++i) {
+ int32_t minOverlap = SK_MaxS32;
+ int32_t minArea = SK_MaxS32;
+ int32_t axisBestK = 0;
+ int32_t axisBestSide = 0;
+ int32_t s = 0;
+
+ // Evaluate each sort
+ for (int j = 0; j < 2; ++j) {
+ SkTQSort(children, children + fMaxChildren, RectLessThan(sorts[i][j]));
+
+ // Evaluate each split index
+ for (int32_t k = 1; k <= fMaxChildren - 2 * fMinChildren + 2; ++k) {
+ SkIRect r1 = children[0].fBounds;
+ SkIRect r2 = children[fMinChildren + k - 1].fBounds;
+ for (int32_t l = 1; l < fMinChildren - 1 + k; ++l) {
+ join_no_empty_check(children[l].fBounds, &r1);
+ }
+ for (int32_t l = fMinChildren + k; l < fMaxChildren + 1; ++l) {
+ join_no_empty_check(children[l].fBounds, &r2);
+ }
+
+ int32_t area = get_area(r1) + get_area(r2);
+ int32_t overlap = get_overlap(r1, r2);
+ s += get_margin(r1) + get_margin(r2);
+
+ if (overlap < minOverlap || (overlap == minOverlap && area < minArea)) {
+ minOverlap = overlap;
+ minArea = area;
+ axisBestSide = j;
+ axisBestK = k;
+ }
+ }
+ }
+
+ if (s < bestS) {
+ bestS = s;
+ axis = i;
+ sortSide = axisBestSide;
+ k = axisBestK;
+ }
+ }
+
+ // replicate the sort of the winning distribution, (we can skip this if the last
+ // sort ended up being best)
+ if (!(axis == 1 && sortSide == 1)) {
+ SkTQSort(children, children + fMaxChildren, RectLessThan(sorts[axis][sortSide]));
+ }
+
+ return fMinChildren - 1 + k;
+}
+
+void SkRTree::search(Node* root, const SkIRect query, SkTDArray<void*>* results) const {
+ for (int i = 0; i < root->fNumChildren; ++i) {
+ if (SkIRect::IntersectsNoEmptyCheck(root->child(i)->fBounds, query)) {
+ if (root->isLeaf()) {
+ results->push(root->child(i)->fChild.data);
+ } else {
+ this->search(root->child(i)->fChild.subtree, query, results);
+ }
+ }
+ }
+}
+
+SkRTree::Branch SkRTree::bulkLoad(SkTDArray<Branch>* branches, int level) {
+ if (branches->count() == 1) {
+ // Only one branch: it will be the root
+ Branch out = (*branches)[0];
+ branches->rewind();
+ return out;
+ } else {
+ // First we sort the whole list by y coordinates
+ SkTQSort(branches->begin(), branches->end() - 1, RectLessY());
+
+ int numBranches = branches->count() / fMaxChildren;
+ int remainder = branches->count() % fMaxChildren;
+ int newBranches = 0;
+
+ if (0 != remainder) {
+ ++numBranches;
+ // If the remainder isn't enough to fill a node, we'll need to add fewer nodes to
+ // some other branches to make up for it
+ if (remainder >= fMinChildren) {
+ remainder = 0;
+ } else {
+ remainder = fMinChildren - remainder;
+ }
+ }
+
+ int numStrips = SkScalarCeil(SkScalarSqrt(SkIntToScalar(numBranches) *
+ SkScalarInvert(fAspectRatio)));
+ int numTiles = SkScalarCeil(SkIntToScalar(numBranches) /
+ SkIntToScalar(numStrips));
+ int currentBranch = 0;
+
+ for (int i = 0; i < numStrips; ++i) {
+ int begin = currentBranch;
+ int end = currentBranch + numTiles * fMaxChildren - SkMin32(remainder,
+ (fMaxChildren - fMinChildren) * numTiles);
+ if (end > branches->count()) {
+ end = branches->count();
+ }
+
+ // Now we sort horizontal strips of rectangles by their x coords
+ SkTQSort(branches->begin() + begin, branches->begin() + end - 1, RectLessX());
+
+ for (int j = 0; j < numTiles && currentBranch < branches->count(); ++j) {
+ int incrementBy = fMaxChildren;
+ if (remainder != 0) {
+ // if need be, omit some nodes to make up for remainder
+ if (remainder <= fMaxChildren - fMinChildren) {
+ incrementBy -= remainder;
+ remainder = 0;
+ } else {
+ incrementBy = fMinChildren;
+ remainder -= fMaxChildren - fMinChildren;
+ }
+ }
+ Node* n = allocateNode(level);
+ n->fNumChildren = 1;
+ *n->child(0) = (*branches)[currentBranch];
+ Branch b;
+ b.fBounds = (*branches)[currentBranch].fBounds;
+ b.fChild.subtree = n;
+ ++currentBranch;
+ for (int k = 1; k < incrementBy && currentBranch < branches->count(); ++k) {
+ b.fBounds.join((*branches)[currentBranch].fBounds);
+ *n->child(k) = (*branches)[currentBranch];
+ ++n->fNumChildren;
+ ++currentBranch;
+ }
+ (*branches)[newBranches] = b;
+ ++newBranches;
+ }
+ }
+ branches->setCount(newBranches);
+ return this->bulkLoad(branches, level + 1);
+ }
+}
+
+void SkRTree::validate() {
+#ifdef SK_DEBUG
+ if (this->isEmpty()) {
+ return;
+ }
+ SkASSERT(fCount == (size_t)this->validateSubtree(fRoot.fChild.subtree, fRoot.fBounds, true));
+#endif
+}
+
+int SkRTree::validateSubtree(Node* root, SkIRect bounds, bool isRoot) {
+ // make sure the pointer is pointing to a valid place
+ SkASSERT(fNodes.contains(static_cast<void*>(root)));
+
+ if (isRoot) {
+ // If the root of this subtree is the overall root, we have looser standards:
+ if (root->isLeaf()) {
+ SkASSERT(root->fNumChildren >= 1 && root->fNumChildren <= fMaxChildren);
+ } else {
+ SkASSERT(root->fNumChildren >= 2 && root->fNumChildren <= fMaxChildren);
+ }
+ } else {
+ SkASSERT(root->fNumChildren >= fMinChildren && root->fNumChildren <= fMaxChildren);
+ }
+
+ for (int i = 0; i < root->fNumChildren; ++i) {
+ SkASSERT(bounds.contains(root->child(i)->fBounds));
+ }
+
+ if (root->isLeaf()) {
+ SkASSERT(0 == root->fLevel);
+ return root->fNumChildren;
+ } else {
+ int childCount = 0;
+ for (int i = 0; i < root->fNumChildren; ++i) {
+ SkASSERT(root->child(i)->fChild.subtree->fLevel == root->fLevel - 1);
+ childCount += this->validateSubtree(root->child(i)->fChild.subtree,
+ root->child(i)->fBounds);
+ }
+ return childCount;
+ }
+}
+
+void SkRTree::rewindInserts() {
+ SkASSERT(this->isEmpty()); // Currently only supports deferred inserts
+ while (!fDeferredInserts.isEmpty() &&
+ fClient->shouldRewind(fDeferredInserts.top().fChild.data)) {
+ fDeferredInserts.pop();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static inline uint32_t get_area(const SkIRect& rect) {
+ return rect.width() * rect.height();
+}
+
+static inline uint32_t get_overlap(const SkIRect& rect1, const SkIRect& rect2) {
+ // I suspect there's a more efficient way of computing this...
+ return SkMax32(0, SkMin32(rect1.fRight, rect2.fRight) - SkMax32(rect1.fLeft, rect2.fLeft)) *
+ SkMax32(0, SkMin32(rect1.fBottom, rect2.fBottom) - SkMax32(rect1.fTop, rect2.fTop));
+}
+
+// Get the margin (aka perimeter)
+static inline uint32_t get_margin(const SkIRect& rect) {
+ return 2 * (rect.width() + rect.height());
+}
+
+static inline uint32_t get_overlap_increase(const SkIRect& rect1, const SkIRect& rect2,
+ SkIRect expandBy) {
+ join_no_empty_check(rect1, &expandBy);
+ return get_overlap(expandBy, rect2) - get_overlap(rect1, rect2);
+}
+
+static inline uint32_t get_area_increase(const SkIRect& rect1, SkIRect rect2) {
+ join_no_empty_check(rect1, &rect2);
+ return get_area(rect2) - get_area(rect1);
+}
+
+// Expand 'out' to include 'joinWith'
+static inline void join_no_empty_check(const SkIRect& joinWith, SkIRect* out) {
+ // since we check for empty bounds on insert, we know we'll never have empty rects
+ // and we can save the empty check that SkIRect::join requires
+ if (joinWith.fLeft < out->fLeft) { out->fLeft = joinWith.fLeft; }
+ if (joinWith.fTop < out->fTop) { out->fTop = joinWith.fTop; }
+ if (joinWith.fRight > out->fRight) { out->fRight = joinWith.fRight; }
+ if (joinWith.fBottom > out->fBottom) { out->fBottom = joinWith.fBottom; }
+}
diff --git a/core/SkRTree.h b/core/SkRTree.h
new file mode 100644
index 00000000..2d11f28a
--- /dev/null
+++ b/core/SkRTree.h
@@ -0,0 +1,193 @@
+
+/*
+ * 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 SkRTree_DEFINED
+#define SkRTree_DEFINED
+
+#include "SkRect.h"
+#include "SkTDArray.h"
+#include "SkChunkAlloc.h"
+#include "SkBBoxHierarchy.h"
+
+/**
+ * An R-Tree implementation. In short, it is a balanced n-ary tree containing a hierarchy of
+ * bounding rectangles.
+ *
+ * Much like a B-Tree it maintains balance by enforcing minimum and maximum child counts, and
+ * splitting nodes when they become overfull. Unlike B-trees, however, we're using spatial data; so
+ * there isn't a canonical ordering to use when choosing insertion locations and splitting
+ * distributions. A variety of heuristics have been proposed for these problems; here, we're using
+ * something resembling an R*-tree, which attempts to minimize area and overlap during insertion,
+ * and aims to minimize a combination of margin, overlap, and area when splitting.
+ *
+ * One detail that is thus far unimplemented that may improve tree quality is attempting to remove
+ * and reinsert nodes when they become full, instead of immediately splitting (nodes that may have
+ * been placed well early on may hurt the tree later when more nodes have been added; removing
+ * and reinserting nodes generally helps reduce overlap and make a better tree). Deletion of nodes
+ * is also unimplemented.
+ *
+ * For more details see:
+ *
+ * Beckmann, N.; Kriegel, H. P.; Schneider, R.; Seeger, B. (1990). "The R*-tree:
+ * an efficient and robust access method for points and rectangles"
+ *
+ * It also supports bulk-loading from a batch of bounds and values; if you don't require the tree
+ * to be usable in its intermediate states while it is being constructed, this is significantly
+ * quicker than individual insertions and produces more consistent trees.
+ */
+class SkRTree : public SkBBoxHierarchy {
+public:
+ SK_DECLARE_INST_COUNT(SkRTree)
+
+ /**
+ * Create a new R-Tree with specified min/max child counts.
+ * The child counts are valid iff:
+ * - (max + 1) / 2 >= min (splitting an overfull node must be enough to populate 2 nodes)
+ * - min < max
+ * - min > 0
+ * - max < SK_MaxU16
+ * If you have some prior information about the distribution of bounds you're expecting, you
+ * can provide an optional aspect ratio parameter. This allows the bulk-load algorithm to create
+ * better proportioned tiles of rectangles.
+ */
+ static SkRTree* Create(int minChildren, int maxChildren, SkScalar aspectRatio = 1);
+ virtual ~SkRTree();
+
+ /**
+ * Insert a node, consisting of bounds and a data value into the tree, if we don't immediately
+ * need to use the tree; we may allow the insert to be deferred (this can allow us to bulk-load
+ * a large batch of nodes at once, which tends to be faster and produce a better tree).
+ * @param data The data value
+ * @param bounds The corresponding bounding box
+ * @param defer Can this insert be deferred? (this may be ignored)
+ */
+ virtual void insert(void* data, const SkIRect& bounds, bool defer = false);
+
+ /**
+ * If any inserts have been deferred, this will add them into the tree
+ */
+ virtual void flushDeferredInserts();
+
+ /**
+ * Given a query rectangle, populates the passed-in array with the elements it intersects
+ */
+ virtual void search(const SkIRect& query, SkTDArray<void*>* results);
+
+ virtual void clear();
+ bool isEmpty() const { return 0 == fCount; }
+ int getDepth() const { return this->isEmpty() ? 0 : fRoot.fChild.subtree->fLevel + 1; }
+
+ /**
+ * This gets the insertion count (rather than the node count)
+ */
+ virtual int getCount() const { return fCount; }
+
+ virtual void rewindInserts() SK_OVERRIDE;
+
+private:
+
+ struct Node;
+
+ /**
+ * A branch of the tree, this may contain a pointer to another interior node, or a data value
+ */
+ struct Branch {
+ union {
+ Node* subtree;
+ void* data;
+ } fChild;
+ SkIRect fBounds;
+ };
+
+ /**
+ * A node in the tree, has between fMinChildren and fMaxChildren (the root is a special case)
+ */
+ struct Node {
+ uint16_t fNumChildren;
+ uint16_t fLevel;
+ bool isLeaf() { return 0 == fLevel; }
+ // Since we want to be able to pick min/max child counts at runtime, we assume the creator
+ // has allocated sufficient space directly after us in memory, and index into that space
+ Branch* child(size_t index) {
+ return reinterpret_cast<Branch*>(this + 1) + index;
+ }
+ };
+
+ typedef int32_t SkIRect::*SortSide;
+
+ // Helper for sorting our children arrays by sides of their rects
+ struct RectLessThan {
+ RectLessThan(SkRTree::SortSide side) : fSide(side) { }
+ bool operator()(const SkRTree::Branch lhs, const SkRTree::Branch rhs) const {
+ return lhs.fBounds.*fSide < rhs.fBounds.*fSide;
+ }
+ private:
+ const SkRTree::SortSide fSide;
+ };
+
+ struct RectLessX {
+ bool operator()(const SkRTree::Branch lhs, const SkRTree::Branch rhs) {
+ return ((lhs.fBounds.fRight - lhs.fBounds.fLeft) >> 1) <
+ ((rhs.fBounds.fRight - lhs.fBounds.fLeft) >> 1);
+ }
+ };
+
+ struct RectLessY {
+ bool operator()(const SkRTree::Branch lhs, const SkRTree::Branch rhs) {
+ return ((lhs.fBounds.fBottom - lhs.fBounds.fTop) >> 1) <
+ ((rhs.fBounds.fBottom - lhs.fBounds.fTop) >> 1);
+ }
+ };
+
+ SkRTree(int minChildren, int maxChildren, SkScalar aspectRatio);
+
+ /**
+ * Recursively descend the tree to find an insertion position for 'branch', updates
+ * bounding boxes on the way up.
+ */
+ Branch* insert(Node* root, Branch* branch, uint16_t level = 0);
+
+ int chooseSubtree(Node* root, Branch* branch);
+ SkIRect computeBounds(Node* n);
+ int distributeChildren(Branch* children);
+ void search(Node* root, const SkIRect query, SkTDArray<void*>* results) const;
+
+ /**
+ * This performs a bottom-up bulk load using the STR (sort-tile-recursive) algorithm, this
+ * seems to generally produce better, more consistent trees at significantly lower cost than
+ * repeated insertions.
+ *
+ * This consumes the input array.
+ *
+ * TODO: Experiment with other bulk-load algorithms (in particular the Hilbert pack variant,
+ * which groups rects by position on the Hilbert curve, is probably worth a look). There also
+ * exist top-down bulk load variants (VAMSplit, TopDownGreedy, etc).
+ */
+ Branch bulkLoad(SkTDArray<Branch>* branches, int level = 0);
+
+ void validate();
+ int validateSubtree(Node* root, SkIRect bounds, bool isRoot = false);
+
+ const int fMinChildren;
+ const int fMaxChildren;
+ const size_t fNodeSize;
+
+ // This is the count of data elements (rather than total nodes in the tree)
+ size_t fCount;
+
+ Branch fRoot;
+ SkChunkAlloc fNodes;
+ SkTDArray<Branch> fDeferredInserts;
+ SkScalar fAspectRatio;
+
+ Node* allocateNode(uint16_t level);
+
+ typedef SkBBoxHierarchy INHERITED;
+};
+
+#endif
diff --git a/core/SkRasterClip.cpp b/core/SkRasterClip.cpp
new file mode 100644
index 00000000..664211f6
--- /dev/null
+++ b/core/SkRasterClip.cpp
@@ -0,0 +1,279 @@
+/*
+ * 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 "SkRasterClip.h"
+
+
+SkRasterClip::SkRasterClip() {
+ fIsBW = true;
+ fIsEmpty = true;
+ fIsRect = false;
+ SkDEBUGCODE(this->validate();)
+}
+
+SkRasterClip::SkRasterClip(const SkRasterClip& src) {
+ AUTO_RASTERCLIP_VALIDATE(src);
+
+ fIsBW = src.fIsBW;
+ if (fIsBW) {
+ fBW = src.fBW;
+ } else {
+ fAA = src.fAA;
+ }
+
+ fIsEmpty = src.isEmpty();
+ fIsRect = src.isRect();
+ SkDEBUGCODE(this->validate();)
+}
+
+SkRasterClip::SkRasterClip(const SkIRect& bounds) : fBW(bounds) {
+ fIsBW = true;
+ fIsEmpty = this->computeIsEmpty(); // bounds might be empty, so compute
+ fIsRect = !fIsEmpty;
+ SkDEBUGCODE(this->validate();)
+}
+
+SkRasterClip::~SkRasterClip() {
+ SkDEBUGCODE(this->validate();)
+}
+
+bool SkRasterClip::isComplex() const {
+ return fIsBW ? fBW.isComplex() : !fAA.isEmpty();
+}
+
+const SkIRect& SkRasterClip::getBounds() const {
+ return fIsBW ? fBW.getBounds() : fAA.getBounds();
+}
+
+bool SkRasterClip::setEmpty() {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ fIsBW = true;
+ fBW.setEmpty();
+ fAA.setEmpty();
+ fIsEmpty = true;
+ fIsRect = false;
+ return false;
+}
+
+bool SkRasterClip::setRect(const SkIRect& rect) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ fIsBW = true;
+ fAA.setEmpty();
+ fIsRect = fBW.setRect(rect);
+ fIsEmpty = !fIsRect;
+ return fIsRect;
+}
+
+bool SkRasterClip::setPath(const SkPath& path, const SkRegion& clip, bool doAA) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ if (this->isBW() && !doAA) {
+ (void)fBW.setPath(path, clip);
+ } else {
+ // TODO: since we are going to over-write fAA completely (aren't we?)
+ // we should just clear our BW data (if any) and set fIsAA=true
+ if (this->isBW()) {
+ this->convertToAA();
+ }
+ (void)fAA.setPath(path, &clip, doAA);
+ }
+ return this->updateCacheAndReturnNonEmpty();
+}
+
+bool SkRasterClip::setPath(const SkPath& path, const SkIRect& clip, bool doAA) {
+ SkRegion tmp;
+ tmp.setRect(clip);
+ return this->setPath(path, tmp, doAA);
+}
+
+bool SkRasterClip::op(const SkIRect& rect, SkRegion::Op op) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ fIsBW ? fBW.op(rect, op) : fAA.op(rect, op);
+ return this->updateCacheAndReturnNonEmpty();
+}
+
+bool SkRasterClip::op(const SkRegion& rgn, SkRegion::Op op) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ if (fIsBW) {
+ (void)fBW.op(rgn, op);
+ } else {
+ SkAAClip tmp;
+ tmp.setRegion(rgn);
+ (void)fAA.op(tmp, op);
+ }
+ return this->updateCacheAndReturnNonEmpty();
+}
+
+bool SkRasterClip::op(const SkRasterClip& clip, SkRegion::Op op) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+ clip.validate();
+
+ if (this->isBW() && clip.isBW()) {
+ (void)fBW.op(clip.fBW, op);
+ } else {
+ SkAAClip tmp;
+ const SkAAClip* other;
+
+ if (this->isBW()) {
+ this->convertToAA();
+ }
+ if (clip.isBW()) {
+ tmp.setRegion(clip.bwRgn());
+ other = &tmp;
+ } else {
+ other = &clip.aaRgn();
+ }
+ (void)fAA.op(*other, op);
+ }
+ return this->updateCacheAndReturnNonEmpty();
+}
+
+/**
+ * Our antialiasing currently has a granularity of 1/4 of a pixel along each
+ * axis. Thus we can treat an axis coordinate as an integer if it differs
+ * from its nearest int by < half of that value (1.8 in this case).
+ */
+static bool nearly_integral(SkScalar x) {
+ static const SkScalar domain = SK_Scalar1 / 4;
+ static const SkScalar halfDomain = domain / 2;
+
+ x += halfDomain;
+ return x - SkScalarFloorToScalar(x) < domain;
+}
+
+bool SkRasterClip::op(const SkRect& r, SkRegion::Op op, bool doAA) {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ if (fIsBW && doAA) {
+ // check that the rect really needs aa, or is it close enought to
+ // integer boundaries that we can just treat it as a BW rect?
+ if (nearly_integral(r.fLeft) && nearly_integral(r.fTop) &&
+ nearly_integral(r.fRight) && nearly_integral(r.fBottom)) {
+ doAA = false;
+ }
+ }
+
+ if (fIsBW && !doAA) {
+ SkIRect ir;
+ r.round(&ir);
+ (void)fBW.op(ir, op);
+ } else {
+ if (fIsBW) {
+ this->convertToAA();
+ }
+ (void)fAA.op(r, op, doAA);
+ }
+ return this->updateCacheAndReturnNonEmpty();
+}
+
+void SkRasterClip::translate(int dx, int dy, SkRasterClip* dst) const {
+ if (NULL == dst) {
+ return;
+ }
+
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ if (this->isEmpty()) {
+ dst->setEmpty();
+ return;
+ }
+ if (0 == (dx | dy)) {
+ *dst = *this;
+ return;
+ }
+
+ dst->fIsBW = fIsBW;
+ if (fIsBW) {
+ fBW.translate(dx, dy, &dst->fBW);
+ dst->fAA.setEmpty();
+ } else {
+ fAA.translate(dx, dy, &dst->fAA);
+ dst->fBW.setEmpty();
+ }
+ dst->updateCacheAndReturnNonEmpty();
+}
+
+bool SkRasterClip::quickContains(const SkIRect& ir) const {
+ return fIsBW ? fBW.quickContains(ir) : fAA.quickContains(ir);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkRegion& SkRasterClip::forceGetBW() {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ if (!fIsBW) {
+ fBW.setRect(fAA.getBounds());
+ }
+ return fBW;
+}
+
+void SkRasterClip::convertToAA() {
+ AUTO_RASTERCLIP_VALIDATE(*this);
+
+ SkASSERT(fIsBW);
+ fAA.setRegion(fBW);
+ fIsBW = false;
+ (void)this->updateCacheAndReturnNonEmpty();
+}
+
+#ifdef SK_DEBUG
+void SkRasterClip::validate() const {
+ // can't ever assert that fBW is empty, since we may have called forceGetBW
+ if (fIsBW) {
+ SkASSERT(fAA.isEmpty());
+ }
+
+ fBW.validate();
+ fAA.validate();
+
+ SkASSERT(this->computeIsEmpty() == fIsEmpty);
+ SkASSERT(this->computeIsRect() == fIsRect);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkAAClipBlitterWrapper::SkAAClipBlitterWrapper() {
+ SkDEBUGCODE(fClipRgn = NULL;)
+ SkDEBUGCODE(fBlitter = NULL;)
+}
+
+SkAAClipBlitterWrapper::SkAAClipBlitterWrapper(const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ this->init(clip, blitter);
+}
+
+SkAAClipBlitterWrapper::SkAAClipBlitterWrapper(const SkAAClip* aaclip,
+ SkBlitter* blitter) {
+ SkASSERT(blitter);
+ SkASSERT(aaclip);
+ fBWRgn.setRect(aaclip->getBounds());
+ fAABlitter.init(blitter, aaclip);
+ // now our return values
+ fClipRgn = &fBWRgn;
+ fBlitter = &fAABlitter;
+}
+
+void SkAAClipBlitterWrapper::init(const SkRasterClip& clip, SkBlitter* blitter) {
+ SkASSERT(blitter);
+ if (clip.isBW()) {
+ fClipRgn = &clip.bwRgn();
+ fBlitter = blitter;
+ } else {
+ const SkAAClip& aaclip = clip.aaRgn();
+ fBWRgn.setRect(aaclip.getBounds());
+ fAABlitter.init(blitter, &aaclip);
+ // now our return values
+ fClipRgn = &fBWRgn;
+ fBlitter = &fAABlitter;
+ }
+}
diff --git a/core/SkRasterClip.h b/core/SkRasterClip.h
new file mode 100644
index 00000000..e58a23b3
--- /dev/null
+++ b/core/SkRasterClip.h
@@ -0,0 +1,162 @@
+/*
+ * 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 SkRasterClip_DEFINED
+#define SkRasterClip_DEFINED
+
+#include "SkRegion.h"
+#include "SkAAClip.h"
+
+class SkRasterClip {
+public:
+ SkRasterClip();
+ SkRasterClip(const SkIRect&);
+ SkRasterClip(const SkRasterClip&);
+ ~SkRasterClip();
+
+ bool isBW() const { return fIsBW; }
+ bool isAA() const { return !fIsBW; }
+ const SkRegion& bwRgn() const { SkASSERT(fIsBW); return fBW; }
+ const SkAAClip& aaRgn() const { SkASSERT(!fIsBW); return fAA; }
+
+ bool isEmpty() const {
+ SkASSERT(this->computeIsEmpty() == fIsEmpty);
+ return fIsEmpty;
+ }
+
+ bool isRect() const {
+ SkASSERT(this->computeIsRect() == fIsRect);
+ return fIsRect;
+ }
+
+ bool isComplex() const;
+ const SkIRect& getBounds() const;
+
+ bool setEmpty();
+ bool setRect(const SkIRect&);
+
+ bool setPath(const SkPath& path, const SkRegion& clip, bool doAA);
+ bool setPath(const SkPath& path, const SkIRect& clip, bool doAA);
+
+ bool op(const SkIRect&, SkRegion::Op);
+ bool op(const SkRegion&, SkRegion::Op);
+ bool op(const SkRasterClip&, SkRegion::Op);
+ bool op(const SkRect&, SkRegion::Op, bool doAA);
+
+ void translate(int dx, int dy, SkRasterClip* dst) const;
+ void translate(int dx, int dy) {
+ this->translate(dx, dy, this);
+ }
+
+ bool quickContains(const SkIRect& rect) const;
+ bool quickContains(int left, int top, int right, int bottom) const {
+ return quickContains(SkIRect::MakeLTRB(left, top, right, bottom));
+ }
+
+ /**
+ * Return true if this region is empty, or if the specified rectangle does
+ * not intersect the region. Returning false is not a guarantee that they
+ * intersect, but returning true is a guarantee that they do not.
+ */
+ bool quickReject(const SkIRect& rect) const {
+ return this->isEmpty() || rect.isEmpty() ||
+ !SkIRect::Intersects(this->getBounds(), rect);
+ }
+
+ // hack for SkCanvas::getTotalClip
+ const SkRegion& forceGetBW();
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+private:
+ SkRegion fBW;
+ SkAAClip fAA;
+ bool fIsBW;
+ // these 2 are caches based on querying the right obj based on fIsBW
+ bool fIsEmpty;
+ bool fIsRect;
+
+ bool computeIsEmpty() const {
+ return fIsBW ? fBW.isEmpty() : fAA.isEmpty();
+ }
+
+ bool computeIsRect() const {
+ return fIsBW ? fBW.isRect() : false;
+ }
+
+ bool updateCacheAndReturnNonEmpty() {
+ fIsEmpty = this->computeIsEmpty();
+ fIsRect = this->computeIsRect();
+ return !fIsEmpty;
+ }
+
+ void convertToAA();
+};
+
+class SkAutoRasterClipValidate : SkNoncopyable {
+public:
+ SkAutoRasterClipValidate(const SkRasterClip& rc) : fRC(rc) {
+ fRC.validate();
+ }
+ ~SkAutoRasterClipValidate() {
+ fRC.validate();
+ }
+private:
+ const SkRasterClip& fRC;
+};
+
+#ifdef SK_DEBUG
+ #define AUTO_RASTERCLIP_VALIDATE(rc) SkAutoRasterClipValidate arcv(rc)
+#else
+ #define AUTO_RASTERCLIP_VALIDATE(rc)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Encapsulates the logic of deciding if we need to change/wrap the blitter
+ * for aaclipping. If so, getRgn and getBlitter return modified values. If
+ * not, they return the raw blitter and (bw) clip region.
+ *
+ * We need to keep the constructor/destructor cost as small as possible, so we
+ * can freely put this guy on the stack, and not pay too much for the case when
+ * we're really BW anyways.
+ */
+class SkAAClipBlitterWrapper {
+public:
+ SkAAClipBlitterWrapper();
+ SkAAClipBlitterWrapper(const SkRasterClip&, SkBlitter*);
+ SkAAClipBlitterWrapper(const SkAAClip*, SkBlitter*);
+
+ void init(const SkRasterClip&, SkBlitter*);
+
+ const SkIRect& getBounds() const {
+ SkASSERT(fClipRgn);
+ return fClipRgn->getBounds();
+ }
+ const SkRegion& getRgn() const {
+ SkASSERT(fClipRgn);
+ return *fClipRgn;
+ }
+ SkBlitter* getBlitter() {
+ SkASSERT(fBlitter);
+ return fBlitter;
+ }
+
+private:
+ SkRegion fBWRgn;
+ SkAAClipBlitter fAABlitter;
+ // what we return
+ const SkRegion* fClipRgn;
+ SkBlitter* fBlitter;
+};
+
+#endif
diff --git a/core/SkRasterizer.cpp b/core/SkRasterizer.cpp
new file mode 100644
index 00000000..a65d541a
--- /dev/null
+++ b/core/SkRasterizer.cpp
@@ -0,0 +1,50 @@
+
+/*
+ * 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 "SkRasterizer.h"
+#include "SkDraw.h"
+#include "SkMaskFilter.h"
+#include "SkPath.h"
+
+SK_DEFINE_INST_COUNT(SkRasterizer)
+
+bool SkRasterizer::rasterize(const SkPath& fillPath, const SkMatrix& matrix,
+ const SkIRect* clipBounds, SkMaskFilter* filter,
+ SkMask* mask, SkMask::CreateMode mode) const {
+ SkIRect storage;
+
+ if (clipBounds && filter && SkMask::kJustRenderImage_CreateMode != mode) {
+ SkIPoint margin;
+ SkMask srcM, dstM;
+
+ srcM.fFormat = SkMask::kA8_Format;
+ srcM.fBounds.set(0, 0, 1, 1);
+ srcM.fImage = NULL;
+ if (!filter->filterMask(&dstM, srcM, matrix, &margin)) {
+ return false;
+ }
+ storage = *clipBounds;
+ storage.inset(-margin.fX, -margin.fY);
+ clipBounds = &storage;
+ }
+
+ return this->onRasterize(fillPath, matrix, clipBounds, mask, mode);
+}
+
+/* Our default implementation of the virtual method just scan converts
+*/
+bool SkRasterizer::onRasterize(const SkPath& fillPath, const SkMatrix& matrix,
+ const SkIRect* clipBounds,
+ SkMask* mask, SkMask::CreateMode mode) const {
+ SkPath devPath;
+
+ fillPath.transform(matrix, &devPath);
+ return SkDraw::DrawToMask(devPath, clipBounds, NULL, NULL, mask, mode,
+ SkPaint::kFill_Style);
+}
diff --git a/core/SkRect.cpp b/core/SkRect.cpp
new file mode 100644
index 00000000..c62f2f3c
--- /dev/null
+++ b/core/SkRect.cpp
@@ -0,0 +1,183 @@
+
+/*
+ * 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 "SkRect.h"
+
+void SkIRect::join(int32_t left, int32_t top, int32_t right, int32_t bottom) {
+ // do nothing if the params are empty
+ if (left >= right || top >= bottom) {
+ return;
+ }
+
+ // if we are empty, just assign
+ if (fLeft >= fRight || fTop >= fBottom) {
+ this->set(left, top, right, bottom);
+ } else {
+ if (left < fLeft) fLeft = left;
+ if (top < fTop) fTop = top;
+ if (right > fRight) fRight = right;
+ if (bottom > fBottom) fBottom = bottom;
+ }
+}
+
+void SkIRect::sort() {
+ if (fLeft > fRight) {
+ SkTSwap<int32_t>(fLeft, fRight);
+ }
+ if (fTop > fBottom) {
+ SkTSwap<int32_t>(fTop, fBottom);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void SkRect::sort() {
+ if (fLeft > fRight) {
+ SkTSwap<SkScalar>(fLeft, fRight);
+ }
+ if (fTop > fBottom) {
+ SkTSwap<SkScalar>(fTop, fBottom);
+ }
+}
+
+void SkRect::toQuad(SkPoint quad[4]) const {
+ SkASSERT(quad);
+
+ quad[0].set(fLeft, fTop);
+ quad[1].set(fRight, fTop);
+ quad[2].set(fRight, fBottom);
+ quad[3].set(fLeft, fBottom);
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+ #define SkFLOATCODE(code) code
+#else
+ #define SkFLOATCODE(code)
+#endif
+
+// For float compares (at least on x86, by removing the else from the min/max
+// computation, we get MAXSS and MINSS instructions, and no branches.
+// Fixed point has no such opportunity (afaik), so we leave the else in that case
+#ifdef SK_SCALAR_IS_FLOAT
+ #define MINMAX_ELSE
+#else
+ #define MINMAX_ELSE else
+#endif
+
+bool SkRect::setBoundsCheck(const SkPoint pts[], int count) {
+ SkASSERT((pts && count > 0) || count == 0);
+
+ bool isFinite = true;
+
+ if (count <= 0) {
+ sk_bzero(this, sizeof(SkRect));
+ } else {
+#ifdef SK_SCALAR_SLOW_COMPARES
+ int32_t l, t, r, b;
+
+ l = r = SkScalarAs2sCompliment(pts[0].fX);
+ t = b = SkScalarAs2sCompliment(pts[0].fY);
+
+ for (int i = 1; i < count; i++) {
+ int32_t x = SkScalarAs2sCompliment(pts[i].fX);
+ int32_t y = SkScalarAs2sCompliment(pts[i].fY);
+
+ if (x < l) l = x; else if (x > r) r = x;
+ if (y < t) t = y; else if (y > b) b = y;
+ }
+ this->set(Sk2sComplimentAsScalar(l),
+ Sk2sComplimentAsScalar(t),
+ Sk2sComplimentAsScalar(r),
+ Sk2sComplimentAsScalar(b));
+#else
+ SkScalar l, t, r, b;
+
+ l = r = pts[0].fX;
+ t = b = pts[0].fY;
+
+ // If all of the points are finite, accum should stay 0. If we encounter
+ // a NaN or infinity, then accum should become NaN.
+ SkFLOATCODE(float accum = 0;)
+ SkFLOATCODE(accum *= l; accum *= t;)
+
+ for (int i = 1; i < count; i++) {
+ SkScalar x = pts[i].fX;
+ SkScalar y = pts[i].fY;
+
+ SkFLOATCODE(accum *= x; accum *= y;)
+
+ if (x < l) l = x; MINMAX_ELSE if (x > r) r = x;
+ if (y < t) t = y; MINMAX_ELSE if (y > b) b = y;
+ }
+
+#ifdef SK_SCALAR_IS_FLOAT
+ SkASSERT(!accum || !SkScalarIsFinite(accum));
+ if (accum) {
+ l = t = r = b = 0;
+ isFinite = false;
+ }
+#endif
+ this->set(l, t, r, b);
+#endif
+ }
+
+ return isFinite;
+}
+
+bool SkRect::intersect(SkScalar left, SkScalar top, SkScalar right,
+ SkScalar bottom) {
+ if (left < right && top < bottom && !this->isEmpty() && // check for empties
+ fLeft < right && left < fRight && fTop < bottom && top < fBottom)
+ {
+ if (fLeft < left) fLeft = left;
+ if (fTop < top) fTop = top;
+ if (fRight > right) fRight = right;
+ if (fBottom > bottom) fBottom = bottom;
+ return true;
+ }
+ return false;
+}
+
+bool SkRect::intersect(const SkRect& r) {
+ SkASSERT(&r);
+ return this->intersect(r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+
+bool SkRect::intersect(const SkRect& a, const SkRect& b) {
+ SkASSERT(&a && &b);
+
+ if (!a.isEmpty() && !b.isEmpty() &&
+ a.fLeft < b.fRight && b.fLeft < a.fRight &&
+ a.fTop < b.fBottom && b.fTop < a.fBottom) {
+ fLeft = SkMaxScalar(a.fLeft, b.fLeft);
+ fTop = SkMaxScalar(a.fTop, b.fTop);
+ fRight = SkMinScalar(a.fRight, b.fRight);
+ fBottom = SkMinScalar(a.fBottom, b.fBottom);
+ return true;
+ }
+ return false;
+}
+
+void SkRect::join(SkScalar left, SkScalar top, SkScalar right,
+ SkScalar bottom) {
+ // do nothing if the params are empty
+ if (left >= right || top >= bottom) {
+ return;
+ }
+
+ // if we are empty, just assign
+ if (fLeft >= fRight || fTop >= fBottom) {
+ this->set(left, top, right, bottom);
+ } else {
+ if (left < fLeft) fLeft = left;
+ if (top < fTop) fTop = top;
+ if (right > fRight) fRight = right;
+ if (bottom > fBottom) fBottom = bottom;
+ }
+}
diff --git a/core/SkRefCnt.cpp b/core/SkRefCnt.cpp
new file mode 100644
index 00000000..8d09065a
--- /dev/null
+++ b/core/SkRefCnt.cpp
@@ -0,0 +1,13 @@
+/*
+ * 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 "SkRefCnt.h"
+#include "SkWeakRefCnt.h"
+
+SK_DEFINE_INST_COUNT(SkRefCnt)
+SK_DEFINE_INST_COUNT(SkWeakRefCnt)
diff --git a/core/SkRefDict.cpp b/core/SkRefDict.cpp
new file mode 100644
index 00000000..44eddf01
--- /dev/null
+++ b/core/SkRefDict.cpp
@@ -0,0 +1,89 @@
+
+/*
+ * 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 "SkRefDict.h"
+#include "SkString.h"
+
+struct SkRefDict::Impl {
+ Impl* fNext;
+ SkString fName;
+ SkRefCnt* fData;
+};
+
+SkRefDict::SkRefDict() : fImpl(NULL) {}
+
+SkRefDict::~SkRefDict() {
+ this->removeAll();
+}
+
+SkRefCnt* SkRefDict::find(const char name[]) const {
+ if (NULL == name) {
+ return NULL;
+ }
+
+ Impl* rec = fImpl;
+ while (rec) {
+ if (rec->fName.equals(name)) {
+ return rec->fData;
+ }
+ rec = rec->fNext;
+ }
+ return NULL;
+}
+
+void SkRefDict::set(const char name[], SkRefCnt* data) {
+ if (NULL == name) {
+ return;
+ }
+
+ Impl* rec = fImpl;
+ Impl* prev = NULL;
+ while (rec) {
+ if (rec->fName.equals(name)) {
+ if (data) {
+ // replace
+ data->ref();
+ rec->fData->unref();
+ rec->fData = data;
+ } else {
+ // remove
+ rec->fData->unref();
+ if (prev) {
+ prev->fNext = rec->fNext;
+ } else {
+ fImpl = rec->fNext;
+ }
+ delete rec;
+ }
+ return;
+ }
+ prev = rec;
+ rec = rec->fNext;
+ }
+
+ // if get here, name was not found, so add it
+ data->ref();
+ rec = new Impl;
+ rec->fName.set(name);
+ rec->fData = data;
+ // prepend to the head of our list
+ rec->fNext = fImpl;
+ fImpl = rec;
+}
+
+void SkRefDict::removeAll() {
+ Impl* rec = fImpl;
+ while (rec) {
+ Impl* next = rec->fNext;
+ rec->fData->unref();
+ delete rec;
+ rec = next;
+ }
+ fImpl = NULL;
+}
diff --git a/core/SkRefDict.h b/core/SkRefDict.h
new file mode 100644
index 00000000..55b9bfe0
--- /dev/null
+++ b/core/SkRefDict.h
@@ -0,0 +1,54 @@
+
+/*
+ * 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 SkRefDict_DEFINED
+#define SkRefDict_DEFINED
+
+#include "SkRefCnt.h"
+
+/**
+ * A dictionary of string,refcnt pairs. The dictionary is also an owner of the
+ * refcnt objects while they are contained.
+ */
+class SK_API SkRefDict : SkNoncopyable {
+public:
+ SkRefDict();
+ ~SkRefDict();
+
+ /**
+ * Return the data associated with name[], or NULL if no matching entry
+ * is found. The reference-count of the entry is not affected.
+ */
+ SkRefCnt* find(const char name[]) const;
+
+ /**
+ * If data is NULL, remove (if present) the entry matching name and call
+ * prev_data->unref() on the data for the matching entry.
+ * If data is not-NULL, replace the existing entry matching name and
+ * call (prev_data->unref()), or add a new one. In either case,
+ * data->ref() is called.
+ */
+ void set(const char name[], SkRefCnt* data);
+
+ /**
+ * Remove the matching entry (if found) and unref its data.
+ */
+ void remove(const char name[]) { this->set(name, NULL); }
+
+ /**
+ * Remove all entries, and unref() their associated data.
+ */
+ void removeAll();
+
+private:
+ struct Impl;
+ Impl* fImpl;
+};
+
+#endif
diff --git a/core/SkRegion.cpp b/core/SkRegion.cpp
new file mode 100644
index 00000000..ca120236
--- /dev/null
+++ b/core/SkRegion.cpp
@@ -0,0 +1,1481 @@
+
+/*
+ * 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 "SkRegionPriv.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+#include "SkUtils.h"
+
+/* Region Layout
+ *
+ * TOP
+ *
+ * [ Bottom, X-Intervals, [Left, Right]..., X-Sentinel ]
+ * ...
+ *
+ * Y-Sentinel
+ */
+
+SkDEBUGCODE(int32_t gRgnAllocCounter;)
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+/* Pass in the beginning with the intervals.
+ * We back up 1 to read the interval-count.
+ * Return the beginning of the next scanline (i.e. the next Y-value)
+ */
+static SkRegion::RunType* skip_intervals(const SkRegion::RunType runs[]) {
+ int intervals = runs[-1];
+#ifdef SK_DEBUG
+ if (intervals > 0) {
+ SkASSERT(runs[0] < runs[1]);
+ SkASSERT(runs[1] < SkRegion::kRunTypeSentinel);
+ } else {
+ SkASSERT(0 == intervals);
+ SkASSERT(SkRegion::kRunTypeSentinel == runs[0]);
+ }
+#endif
+ runs += intervals * 2 + 1;
+ return const_cast<SkRegion::RunType*>(runs);
+}
+
+bool SkRegion::RunsAreARect(const SkRegion::RunType runs[], int count,
+ SkIRect* bounds) {
+ assert_sentinel(runs[0], false); // top
+ SkASSERT(count >= kRectRegionRuns);
+
+ if (count == kRectRegionRuns) {
+ assert_sentinel(runs[1], false); // bottom
+ SkASSERT(1 == runs[2]);
+ assert_sentinel(runs[3], false); // left
+ assert_sentinel(runs[4], false); // right
+ assert_sentinel(runs[5], true);
+ assert_sentinel(runs[6], true);
+
+ SkASSERT(runs[0] < runs[1]); // valid height
+ SkASSERT(runs[3] < runs[4]); // valid width
+
+ bounds->set(runs[3], runs[0], runs[4], runs[1]);
+ return true;
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+SkRegion::SkRegion() {
+ fBounds.set(0, 0, 0, 0);
+ fRunHead = SkRegion_gEmptyRunHeadPtr;
+}
+
+SkRegion::SkRegion(const SkRegion& src) {
+ fRunHead = SkRegion_gEmptyRunHeadPtr; // just need a value that won't trigger sk_free(fRunHead)
+ this->setRegion(src);
+}
+
+SkRegion::SkRegion(const SkIRect& rect) {
+ fRunHead = SkRegion_gEmptyRunHeadPtr; // just need a value that won't trigger sk_free(fRunHead)
+ this->setRect(rect);
+}
+
+SkRegion::~SkRegion() {
+ this->freeRuns();
+}
+
+void SkRegion::freeRuns() {
+ if (this->isComplex()) {
+ SkASSERT(fRunHead->fRefCnt >= 1);
+ if (sk_atomic_dec(&fRunHead->fRefCnt) == 1) {
+ //SkASSERT(gRgnAllocCounter > 0);
+ //SkDEBUGCODE(sk_atomic_dec(&gRgnAllocCounter));
+ //SkDEBUGF(("************** gRgnAllocCounter::free %d\n", gRgnAllocCounter));
+ sk_free(fRunHead);
+ }
+ }
+}
+
+void SkRegion::allocateRuns(int count, int ySpanCount, int intervalCount) {
+ fRunHead = RunHead::Alloc(count, ySpanCount, intervalCount);
+}
+
+void SkRegion::allocateRuns(int count) {
+ fRunHead = RunHead::Alloc(count);
+}
+
+void SkRegion::allocateRuns(const RunHead& head) {
+ fRunHead = RunHead::Alloc(head.fRunCount,
+ head.getYSpanCount(),
+ head.getIntervalCount());
+}
+
+SkRegion& SkRegion::operator=(const SkRegion& src) {
+ (void)this->setRegion(src);
+ return *this;
+}
+
+void SkRegion::swap(SkRegion& other) {
+ SkTSwap<SkIRect>(fBounds, other.fBounds);
+ SkTSwap<RunHead*>(fRunHead, other.fRunHead);
+}
+
+int SkRegion::computeRegionComplexity() const {
+ if (this->isEmpty()) {
+ return 0;
+ } else if (this->isRect()) {
+ return 1;
+ }
+ return fRunHead->getIntervalCount();
+}
+
+bool SkRegion::setEmpty() {
+ this->freeRuns();
+ fBounds.set(0, 0, 0, 0);
+ fRunHead = SkRegion_gEmptyRunHeadPtr;
+ return false;
+}
+
+bool SkRegion::setRect(int32_t left, int32_t top,
+ int32_t right, int32_t bottom) {
+ if (left >= right || top >= bottom) {
+ return this->setEmpty();
+ }
+ this->freeRuns();
+ fBounds.set(left, top, right, bottom);
+ fRunHead = SkRegion_gRectRunHeadPtr;
+ return true;
+}
+
+bool SkRegion::setRect(const SkIRect& r) {
+ return this->setRect(r.fLeft, r.fTop, r.fRight, r.fBottom);
+}
+
+bool SkRegion::setRegion(const SkRegion& src) {
+ if (this != &src) {
+ this->freeRuns();
+
+ fBounds = src.fBounds;
+ fRunHead = src.fRunHead;
+ if (this->isComplex()) {
+ sk_atomic_inc(&fRunHead->fRefCnt);
+ }
+ }
+ return fRunHead != SkRegion_gEmptyRunHeadPtr;
+}
+
+bool SkRegion::op(const SkIRect& rect, const SkRegion& rgn, Op op) {
+ SkRegion tmp(rect);
+
+ return this->op(tmp, rgn, op);
+}
+
+bool SkRegion::op(const SkRegion& rgn, const SkIRect& rect, Op op) {
+ SkRegion tmp(rect);
+
+ return this->op(rgn, tmp, op);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_BUILD_FOR_ANDROID
+char* SkRegion::toString() {
+ Iterator iter(*this);
+ int count = 0;
+ while (!iter.done()) {
+ count++;
+ iter.next();
+ }
+ // 4 ints, up to 10 digits each plus sign, 3 commas, '(', ')', SkRegion() and '\0'
+ const int max = (count*((11*4)+5))+11+1;
+ char* result = (char*)malloc(max);
+ if (result == NULL) {
+ return NULL;
+ }
+ count = sprintf(result, "SkRegion(");
+ iter.reset(*this);
+ while (!iter.done()) {
+ const SkIRect& r = iter.rect();
+ count += sprintf(result+count, "(%d,%d,%d,%d)", r.fLeft, r.fTop, r.fRight, r.fBottom);
+ iter.next();
+ }
+ count += sprintf(result+count, ")");
+ return result;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkRegion::count_runtype_values(int* itop, int* ibot) const {
+ if (this == NULL) {
+ *itop = SK_MinS32;
+ *ibot = SK_MaxS32;
+ return 0;
+ }
+
+ int maxT;
+
+ if (this->isRect()) {
+ maxT = 2;
+ } else {
+ SkASSERT(this->isComplex());
+ maxT = fRunHead->getIntervalCount() * 2;
+ }
+ *itop = fBounds.fTop;
+ *ibot = fBounds.fBottom;
+ return maxT;
+}
+
+static bool isRunCountEmpty(int count) {
+ return count <= 2;
+}
+
+bool SkRegion::setRuns(RunType runs[], int count) {
+ SkDEBUGCODE(this->validate();)
+ SkASSERT(count > 0);
+
+ if (isRunCountEmpty(count)) {
+ // SkDEBUGF(("setRuns: empty\n"));
+ assert_sentinel(runs[count-1], true);
+ return this->setEmpty();
+ }
+
+ // trim off any empty spans from the top and bottom
+ // weird I should need this, perhaps op() could be smarter...
+ if (count > kRectRegionRuns) {
+ RunType* stop = runs + count;
+ assert_sentinel(runs[0], false); // top
+ assert_sentinel(runs[1], false); // bottom
+ // runs[2] is uncomputed intervalCount
+
+ if (runs[3] == SkRegion::kRunTypeSentinel) { // should be first left...
+ runs += 3; // skip empty initial span
+ runs[0] = runs[-2]; // set new top to prev bottom
+ assert_sentinel(runs[1], false); // bot: a sentinal would mean two in a row
+ assert_sentinel(runs[2], false); // intervalcount
+ assert_sentinel(runs[3], false); // left
+ assert_sentinel(runs[4], false); // right
+ }
+
+ assert_sentinel(stop[-1], true);
+ assert_sentinel(stop[-2], true);
+
+ // now check for a trailing empty span
+ if (stop[-5] == SkRegion::kRunTypeSentinel) { // eek, stop[-4] was a bottom with no x-runs
+ stop[-4] = SkRegion::kRunTypeSentinel; // kill empty last span
+ stop -= 3;
+ assert_sentinel(stop[-1], true); // last y-sentinel
+ assert_sentinel(stop[-2], true); // last x-sentinel
+ assert_sentinel(stop[-3], false); // last right
+ assert_sentinel(stop[-4], false); // last left
+ assert_sentinel(stop[-5], false); // last interval-count
+ assert_sentinel(stop[-6], false); // last bottom
+ }
+ count = (int)(stop - runs);
+ }
+
+ SkASSERT(count >= kRectRegionRuns);
+
+ if (SkRegion::RunsAreARect(runs, count, &fBounds)) {
+ return this->setRect(fBounds);
+ }
+
+ // if we get here, we need to become a complex region
+
+ if (!this->isComplex() || fRunHead->fRunCount != count) {
+ this->freeRuns();
+ this->allocateRuns(count);
+ }
+
+ // must call this before we can write directly into runs()
+ // in case we are sharing the buffer with another region (copy on write)
+ fRunHead = fRunHead->ensureWritable();
+ memcpy(fRunHead->writable_runs(), runs, count * sizeof(RunType));
+ fRunHead->computeRunBounds(&fBounds);
+
+ SkDEBUGCODE(this->validate();)
+
+ return true;
+}
+
+void SkRegion::BuildRectRuns(const SkIRect& bounds,
+ RunType runs[kRectRegionRuns]) {
+ runs[0] = bounds.fTop;
+ runs[1] = bounds.fBottom;
+ runs[2] = 1; // 1 interval for this scanline
+ runs[3] = bounds.fLeft;
+ runs[4] = bounds.fRight;
+ runs[5] = kRunTypeSentinel;
+ runs[6] = kRunTypeSentinel;
+}
+
+bool SkRegion::contains(int32_t x, int32_t y) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (!fBounds.contains(x, y)) {
+ return false;
+ }
+ if (this->isRect()) {
+ return true;
+ }
+ SkASSERT(this->isComplex());
+
+ const RunType* runs = fRunHead->findScanline(y);
+
+ // Skip the Bottom and IntervalCount
+ runs += 2;
+
+ // Just walk this scanline, checking each interval. The X-sentinel will
+ // appear as a left-inteval (runs[0]) and should abort the search.
+ //
+ // We could do a bsearch, using interval-count (runs[1]), but need to time
+ // when that would be worthwhile.
+ //
+ for (;;) {
+ if (x < runs[0]) {
+ break;
+ }
+ if (x < runs[1]) {
+ return true;
+ }
+ runs += 2;
+ }
+ 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;
+}
+
+static bool scanline_contains(const SkRegion::RunType runs[],
+ SkRegion::RunType L, SkRegion::RunType R) {
+ runs += 2; // skip Bottom and IntervalCount
+ for (;;) {
+ if (L < runs[0]) {
+ break;
+ }
+ if (R <= runs[1]) {
+ return true;
+ }
+ runs += 2;
+ }
+ return false;
+}
+
+bool SkRegion::contains(const SkIRect& r) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (!fBounds.contains(r)) {
+ return false;
+ }
+ if (this->isRect()) {
+ return true;
+ }
+ SkASSERT(this->isComplex());
+
+ 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);
+ }
+ return true;
+}
+
+bool SkRegion::contains(const SkRegion& rgn) const {
+ SkDEBUGCODE(this->validate();)
+ SkDEBUGCODE(rgn.validate();)
+
+ if (this->isEmpty() || rgn.isEmpty() || !fBounds.contains(rgn.fBounds)) {
+ return false;
+ }
+ if (this->isRect()) {
+ return true;
+ }
+ if (rgn.isRect()) {
+ return this->contains(rgn.getBounds());
+ }
+
+ /*
+ * A contains B is equivalent to
+ * B - A == 0
+ */
+ return !Oper(rgn, *this, kDifference_Op, NULL);
+}
+
+const SkRegion::RunType* SkRegion::getRuns(RunType tmpStorage[],
+ int* intervals) const {
+ SkASSERT(tmpStorage && intervals);
+ const RunType* runs = tmpStorage;
+
+ if (this->isEmpty()) {
+ tmpStorage[0] = kRunTypeSentinel;
+ *intervals = 0;
+ } else if (this->isRect()) {
+ BuildRectRuns(fBounds, tmpStorage);
+ *intervals = 1;
+ } else {
+ runs = fRunHead->readonly_runs();
+ *intervals = fRunHead->getIntervalCount();
+ }
+ return runs;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool scanline_intersects(const SkRegion::RunType runs[],
+ SkRegion::RunType L, SkRegion::RunType R) {
+ runs += 2; // skip Bottom and IntervalCount
+ for (;;) {
+ if (R <= runs[0]) {
+ break;
+ }
+ if (L < runs[1]) {
+ return true;
+ }
+ runs += 2;
+ }
+ return false;
+}
+
+bool SkRegion::intersects(const SkIRect& r) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (this->isEmpty() || r.isEmpty()) {
+ return false;
+ }
+
+ SkIRect sect;
+ if (!sect.intersect(fBounds, r)) {
+ return false;
+ }
+ if (this->isRect()) {
+ return true;
+ }
+ SkASSERT(this->isComplex());
+
+ const RunType* scanline = fRunHead->findScanline(sect.fTop);
+ for (;;) {
+ if (scanline_intersects(scanline, sect.fLeft, sect.fRight)) {
+ return true;
+ }
+ if (sect.fBottom <= scanline_bottom(scanline)) {
+ break;
+ }
+ scanline = scanline_next(scanline);
+ }
+ return false;
+}
+
+bool SkRegion::intersects(const SkRegion& rgn) const {
+ if (this->isEmpty() || rgn.isEmpty()) {
+ return false;
+ }
+
+ if (!SkIRect::Intersects(fBounds, rgn.fBounds)) {
+ return false;
+ }
+
+ bool weAreARect = this->isRect();
+ bool theyAreARect = rgn.isRect();
+
+ if (weAreARect && theyAreARect) {
+ return true;
+ }
+ if (weAreARect) {
+ return rgn.intersects(this->getBounds());
+ }
+ if (theyAreARect) {
+ return this->intersects(rgn.getBounds());
+ }
+
+ // both of us are complex
+ return Oper(*this, rgn, kIntersect_Op, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkRegion::operator==(const SkRegion& b) const {
+ SkDEBUGCODE(validate();)
+ SkDEBUGCODE(b.validate();)
+
+ if (this == &b) {
+ return true;
+ }
+ if (fBounds != b.fBounds) {
+ return false;
+ }
+
+ const SkRegion::RunHead* ah = fRunHead;
+ const SkRegion::RunHead* bh = b.fRunHead;
+
+ // this catches empties and rects being equal
+ if (ah == bh) {
+ return true;
+ }
+ // now we insist that both are complex (but different ptrs)
+ if (!this->isComplex() || !b.isComplex()) {
+ return false;
+ }
+ return ah->fRunCount == bh->fRunCount &&
+ !memcmp(ah->readonly_runs(), bh->readonly_runs(),
+ ah->fRunCount * sizeof(SkRegion::RunType));
+}
+
+void SkRegion::translate(int dx, int dy, SkRegion* dst) const {
+ SkDEBUGCODE(this->validate();)
+
+ if (NULL == dst) {
+ return;
+ }
+ if (this->isEmpty()) {
+ dst->setEmpty();
+ } else if (this->isRect()) {
+ dst->setRect(fBounds.fLeft + dx, fBounds.fTop + dy,
+ fBounds.fRight + dx, fBounds.fBottom + dy);
+ } else {
+ if (this == dst) {
+ dst->fRunHead = dst->fRunHead->ensureWritable();
+ } else {
+ SkRegion tmp;
+ tmp.allocateRuns(*fRunHead);
+ tmp.fBounds = fBounds;
+ dst->swap(tmp);
+ }
+
+ dst->fBounds.offset(dx, dy);
+
+ const RunType* sruns = fRunHead->readonly_runs();
+ RunType* druns = dst->fRunHead->writable_runs();
+
+ *druns++ = (SkRegion::RunType)(*sruns++ + dy); // top
+ for (;;) {
+ int bottom = *sruns++;
+ if (bottom == kRunTypeSentinel) {
+ break;
+ }
+ *druns++ = (SkRegion::RunType)(bottom + dy); // bottom;
+ *druns++ = *sruns++; // copy intervalCount;
+ for (;;) {
+ int x = *sruns++;
+ if (x == kRunTypeSentinel) {
+ break;
+ }
+ *druns++ = (SkRegion::RunType)(x + dx);
+ *druns++ = (SkRegion::RunType)(*sruns++ + dx);
+ }
+ *druns++ = kRunTypeSentinel; // x sentinel
+ }
+ *druns++ = kRunTypeSentinel; // y sentinel
+
+ SkASSERT(sruns - fRunHead->readonly_runs() == fRunHead->fRunCount);
+ SkASSERT(druns - dst->fRunHead->readonly_runs() == dst->fRunHead->fRunCount);
+ }
+
+ SkDEBUGCODE(this->validate();)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkRegion::setRects(const SkIRect rects[], int count) {
+ if (0 == count) {
+ this->setEmpty();
+ } else {
+ this->setRect(rects[0]);
+ for (int i = 1; i < count; i++) {
+ this->op(rects[i], kUnion_Op);
+ }
+ }
+ return !this->isEmpty();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable warning : local variable used without having been initialized
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+#ifdef SK_DEBUG
+static void assert_valid_pair(int left, int rite)
+{
+ SkASSERT(left == SkRegion::kRunTypeSentinel || left < rite);
+}
+#else
+ #define assert_valid_pair(left, rite)
+#endif
+
+struct spanRec {
+ const SkRegion::RunType* fA_runs;
+ const SkRegion::RunType* fB_runs;
+ int fA_left, fA_rite, fB_left, fB_rite;
+ int fLeft, fRite, fInside;
+
+ void init(const SkRegion::RunType a_runs[],
+ const SkRegion::RunType b_runs[]) {
+ fA_left = *a_runs++;
+ fA_rite = *a_runs++;
+ fB_left = *b_runs++;
+ fB_rite = *b_runs++;
+
+ fA_runs = a_runs;
+ fB_runs = b_runs;
+ }
+
+ bool done() const {
+ SkASSERT(fA_left <= SkRegion::kRunTypeSentinel);
+ SkASSERT(fB_left <= SkRegion::kRunTypeSentinel);
+ return fA_left == SkRegion::kRunTypeSentinel &&
+ fB_left == SkRegion::kRunTypeSentinel;
+ }
+
+ void next() {
+ assert_valid_pair(fA_left, fA_rite);
+ assert_valid_pair(fB_left, fB_rite);
+
+ int inside, left, rite SK_INIT_TO_AVOID_WARNING;
+ bool a_flush = false;
+ bool b_flush = false;
+
+ int a_left = fA_left;
+ int a_rite = fA_rite;
+ int b_left = fB_left;
+ int b_rite = fB_rite;
+
+ if (a_left < b_left) {
+ inside = 1;
+ left = a_left;
+ if (a_rite <= b_left) { // [...] <...>
+ rite = a_rite;
+ a_flush = true;
+ } else { // [...<..]...> or [...<...>...]
+ rite = a_left = b_left;
+ }
+ } else if (b_left < a_left) {
+ inside = 2;
+ left = b_left;
+ if (b_rite <= a_left) { // [...] <...>
+ rite = b_rite;
+ b_flush = true;
+ } else { // [...<..]...> or [...<...>...]
+ rite = b_left = a_left;
+ }
+ } else { // a_left == b_left
+ inside = 3;
+ left = a_left; // or b_left
+ if (a_rite <= b_rite) {
+ rite = b_left = a_rite;
+ a_flush = true;
+ }
+ if (b_rite <= a_rite) {
+ rite = a_left = b_rite;
+ b_flush = true;
+ }
+ }
+
+ if (a_flush) {
+ a_left = *fA_runs++;
+ a_rite = *fA_runs++;
+ }
+ if (b_flush) {
+ b_left = *fB_runs++;
+ b_rite = *fB_runs++;
+ }
+
+ SkASSERT(left <= rite);
+
+ // now update our state
+ fA_left = a_left;
+ fA_rite = a_rite;
+ fB_left = b_left;
+ fB_rite = b_rite;
+
+ fLeft = left;
+ fRite = rite;
+ fInside = inside;
+ }
+};
+
+static SkRegion::RunType* operate_on_span(const SkRegion::RunType a_runs[],
+ const SkRegion::RunType b_runs[],
+ SkRegion::RunType dst[],
+ int min, int max) {
+ spanRec rec;
+ bool firstInterval = true;
+
+ rec.init(a_runs, b_runs);
+
+ while (!rec.done()) {
+ rec.next();
+
+ int left = rec.fLeft;
+ int rite = rec.fRite;
+
+ // add left,rite to our dst buffer (checking for coincidence
+ if ((unsigned)(rec.fInside - min) <= (unsigned)(max - min) &&
+ left < rite) { // skip if equal
+ if (firstInterval || dst[-1] < left) {
+ *dst++ = (SkRegion::RunType)(left);
+ *dst++ = (SkRegion::RunType)(rite);
+ firstInterval = false;
+ } else {
+ // update the right edge
+ dst[-1] = (SkRegion::RunType)(rite);
+ }
+ }
+ }
+
+ *dst++ = SkRegion::kRunTypeSentinel;
+ return dst;
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+static const struct {
+ uint8_t fMin;
+ uint8_t fMax;
+} gOpMinMax[] = {
+ { 1, 1 }, // Difference
+ { 3, 3 }, // Intersection
+ { 1, 3 }, // Union
+ { 1, 2 } // XOR
+};
+
+class RgnOper {
+public:
+ RgnOper(int top, SkRegion::RunType dst[], SkRegion::Op op) {
+ // need to ensure that the op enum lines up with our minmax array
+ SkASSERT(SkRegion::kDifference_Op == 0);
+ SkASSERT(SkRegion::kIntersect_Op == 1);
+ SkASSERT(SkRegion::kUnion_Op == 2);
+ SkASSERT(SkRegion::kXOR_Op == 3);
+ SkASSERT((unsigned)op <= 3);
+
+ fStartDst = dst;
+ fPrevDst = dst + 1;
+ fPrevLen = 0; // will never match a length from operate_on_span
+ fTop = (SkRegion::RunType)(top); // just a first guess, we might update this
+
+ fMin = gOpMinMax[op].fMin;
+ fMax = gOpMinMax[op].fMax;
+ }
+
+ void addSpan(int bottom, const SkRegion::RunType a_runs[],
+ const SkRegion::RunType b_runs[]) {
+ // skip X values and slots for the next Y+intervalCount
+ SkRegion::RunType* start = fPrevDst + fPrevLen + 2;
+ // start points to beginning of dst interval
+ SkRegion::RunType* stop = operate_on_span(a_runs, b_runs, start, fMin, fMax);
+ size_t len = stop - start;
+ SkASSERT(len >= 1 && (len & 1) == 1);
+ SkASSERT(SkRegion::kRunTypeSentinel == stop[-1]);
+
+ if (fPrevLen == len &&
+ (1 == len || !memcmp(fPrevDst, start,
+ (len - 1) * sizeof(SkRegion::RunType)))) {
+ // update Y value
+ fPrevDst[-2] = (SkRegion::RunType)(bottom);
+ } else { // accept the new span
+ if (len == 1 && fPrevLen == 0) {
+ fTop = (SkRegion::RunType)(bottom); // just update our bottom
+ } else {
+ start[-2] = (SkRegion::RunType)(bottom);
+ start[-1] = len >> 1;
+ fPrevDst = start;
+ fPrevLen = len;
+ }
+ }
+ }
+
+ int flush() {
+ fStartDst[0] = fTop;
+ fPrevDst[fPrevLen] = SkRegion::kRunTypeSentinel;
+ return (int)(fPrevDst - fStartDst + fPrevLen + 1);
+ }
+
+ bool isEmpty() const { return 0 == fPrevLen; }
+
+ uint8_t fMin, fMax;
+
+private:
+ SkRegion::RunType* fStartDst;
+ SkRegion::RunType* fPrevDst;
+ size_t fPrevLen;
+ SkRegion::RunType fTop;
+};
+
+// want a unique value to signal that we exited due to quickExit
+#define QUICK_EXIT_TRUE_COUNT (-1)
+
+static int operate(const SkRegion::RunType a_runs[],
+ const SkRegion::RunType b_runs[],
+ SkRegion::RunType dst[],
+ SkRegion::Op op,
+ bool quickExit) {
+ const SkRegion::RunType gEmptyScanline[] = {
+ 0, // dummy bottom value
+ 0, // zero intervals
+ SkRegion::kRunTypeSentinel,
+ // just need a 2nd value, since spanRec.init() reads 2 values, even
+ // though if the first value is the sentinel, it ignores the 2nd value.
+ // w/o the 2nd value here, we might read uninitialized memory.
+ // This happens when we are using gSentinel, which is pointing at
+ // our sentinel value.
+ 0
+ };
+ const SkRegion::RunType* const gSentinel = &gEmptyScanline[2];
+
+ int a_top = *a_runs++;
+ int a_bot = *a_runs++;
+ int b_top = *b_runs++;
+ int b_bot = *b_runs++;
+
+ a_runs += 1; // skip the intervalCount;
+ b_runs += 1; // skip the intervalCount;
+
+ // Now a_runs and b_runs to their intervals (or sentinel)
+
+ assert_sentinel(a_top, false);
+ assert_sentinel(a_bot, false);
+ assert_sentinel(b_top, false);
+ assert_sentinel(b_bot, false);
+
+ RgnOper oper(SkMin32(a_top, b_top), dst, op);
+
+ int prevBot = SkRegion::kRunTypeSentinel; // so we fail the first test
+
+ while (a_bot < SkRegion::kRunTypeSentinel ||
+ b_bot < SkRegion::kRunTypeSentinel) {
+ int top, bot SK_INIT_TO_AVOID_WARNING;
+ const SkRegion::RunType* run0 = gSentinel;
+ const SkRegion::RunType* run1 = gSentinel;
+ bool a_flush = false;
+ bool b_flush = false;
+
+ if (a_top < b_top) {
+ top = a_top;
+ run0 = a_runs;
+ if (a_bot <= b_top) { // [...] <...>
+ bot = a_bot;
+ a_flush = true;
+ } else { // [...<..]...> or [...<...>...]
+ bot = a_top = b_top;
+ }
+ } else if (b_top < a_top) {
+ top = b_top;
+ run1 = b_runs;
+ if (b_bot <= a_top) { // [...] <...>
+ bot = b_bot;
+ b_flush = true;
+ } else { // [...<..]...> or [...<...>...]
+ bot = b_top = a_top;
+ }
+ } else { // a_top == b_top
+ top = a_top; // or b_top
+ run0 = a_runs;
+ run1 = b_runs;
+ if (a_bot <= b_bot) {
+ bot = b_top = a_bot;
+ a_flush = true;
+ }
+ if (b_bot <= a_bot) {
+ bot = a_top = b_bot;
+ b_flush = true;
+ }
+ }
+
+ if (top > prevBot) {
+ oper.addSpan(top, gSentinel, gSentinel);
+ }
+ oper.addSpan(bot, run0, run1);
+
+ if (quickExit && !oper.isEmpty()) {
+ return QUICK_EXIT_TRUE_COUNT;
+ }
+
+ if (a_flush) {
+ a_runs = skip_intervals(a_runs);
+ a_top = a_bot;
+ a_bot = *a_runs++;
+ a_runs += 1; // skip uninitialized intervalCount
+ if (a_bot == SkRegion::kRunTypeSentinel) {
+ a_top = a_bot;
+ }
+ }
+ if (b_flush) {
+ b_runs = skip_intervals(b_runs);
+ b_top = b_bot;
+ b_bot = *b_runs++;
+ b_runs += 1; // skip uninitialized intervalCount
+ if (b_bot == SkRegion::kRunTypeSentinel) {
+ b_top = b_bot;
+ }
+ }
+
+ prevBot = bot;
+ }
+ return oper.flush();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* Given count RunTypes in a complex region, return the worst case number of
+ logical intervals that represents (i.e. number of rects that would be
+ returned from the iterator).
+
+ We could just return count/2, since there must be at least 2 values per
+ interval, but we can first trim off the const overhead of the initial TOP
+ value, plus the final BOTTOM + 2 sentinels.
+ */
+#if 0 // UNUSED
+static int count_to_intervals(int count) {
+ SkASSERT(count >= 6); // a single rect is 6 values
+ return (count - 4) >> 1;
+}
+#endif
+
+/* Given a number of intervals, what is the worst case representation of that
+ many intervals?
+
+ Worst case (from a storage perspective), is a vertical stack of single
+ intervals: TOP + N * (BOTTOM INTERVALCOUNT LEFT RIGHT SENTINEL) + SENTINEL
+ */
+static int intervals_to_count(int intervals) {
+ return 1 + intervals * 5 + 1;
+}
+
+/* Given the intervalCounts of RunTypes in two regions, return the worst-case number
+ of RunTypes need to store the result after a region-op.
+ */
+static int compute_worst_case_count(int a_intervals, int b_intervals) {
+ // Our heuristic worst case is ai * (bi + 1) + bi * (ai + 1)
+ int intervals = 2 * a_intervals * b_intervals + a_intervals + b_intervals;
+ // convert back to number of RunType values
+ return intervals_to_count(intervals);
+}
+
+static bool setEmptyCheck(SkRegion* result) {
+ return result ? result->setEmpty() : false;
+}
+
+static bool setRectCheck(SkRegion* result, const SkIRect& rect) {
+ return result ? result->setRect(rect) : !rect.isEmpty();
+}
+
+static bool setRegionCheck(SkRegion* result, const SkRegion& rgn) {
+ return result ? result->setRegion(rgn) : !rgn.isEmpty();
+}
+
+bool SkRegion::Oper(const SkRegion& rgnaOrig, const SkRegion& rgnbOrig, Op op,
+ SkRegion* result) {
+ SkASSERT((unsigned)op < kOpCount);
+
+ if (kReplace_Op == op) {
+ return setRegionCheck(result, rgnbOrig);
+ }
+
+ // swith to using pointers, so we can swap them as needed
+ const SkRegion* rgna = &rgnaOrig;
+ const SkRegion* rgnb = &rgnbOrig;
+ // after this point, do not refer to rgnaOrig or rgnbOrig!!!
+
+ // collaps difference and reverse-difference into just difference
+ if (kReverseDifference_Op == op) {
+ SkTSwap<const SkRegion*>(rgna, rgnb);
+ op = kDifference_Op;
+ }
+
+ SkIRect bounds;
+ bool a_empty = rgna->isEmpty();
+ bool b_empty = rgnb->isEmpty();
+ bool a_rect = rgna->isRect();
+ bool b_rect = rgnb->isRect();
+
+ switch (op) {
+ case kDifference_Op:
+ if (a_empty) {
+ return setEmptyCheck(result);
+ }
+ if (b_empty || !SkIRect::IntersectsNoEmptyCheck(rgna->fBounds,
+ rgnb->fBounds)) {
+ return setRegionCheck(result, *rgna);
+ }
+ if (b_rect && rgnb->fBounds.containsNoEmptyCheck(rgna->fBounds)) {
+ return setEmptyCheck(result);
+ }
+ break;
+
+ case kIntersect_Op:
+ if ((a_empty | b_empty)
+ || !bounds.intersect(rgna->fBounds, rgnb->fBounds)) {
+ return setEmptyCheck(result);
+ }
+ if (a_rect & b_rect) {
+ return setRectCheck(result, bounds);
+ }
+ if (a_rect && rgna->fBounds.contains(rgnb->fBounds)) {
+ return setRegionCheck(result, *rgnb);
+ }
+ if (b_rect && rgnb->fBounds.contains(rgna->fBounds)) {
+ return setRegionCheck(result, *rgna);
+ }
+ break;
+
+ case kUnion_Op:
+ if (a_empty) {
+ return setRegionCheck(result, *rgnb);
+ }
+ if (b_empty) {
+ return setRegionCheck(result, *rgna);
+ }
+ if (a_rect && rgna->fBounds.contains(rgnb->fBounds)) {
+ return setRegionCheck(result, *rgna);
+ }
+ if (b_rect && rgnb->fBounds.contains(rgna->fBounds)) {
+ return setRegionCheck(result, *rgnb);
+ }
+ break;
+
+ case kXOR_Op:
+ if (a_empty) {
+ return setRegionCheck(result, *rgnb);
+ }
+ if (b_empty) {
+ return setRegionCheck(result, *rgna);
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unknown region op");
+ return false;
+ }
+
+ RunType tmpA[kRectRegionRuns];
+ RunType tmpB[kRectRegionRuns];
+
+ int a_intervals, b_intervals;
+ const RunType* a_runs = rgna->getRuns(tmpA, &a_intervals);
+ const RunType* b_runs = rgnb->getRuns(tmpB, &b_intervals);
+
+ int dstCount = compute_worst_case_count(a_intervals, b_intervals);
+ SkAutoSTMalloc<256, RunType> array(dstCount);
+
+#ifdef SK_DEBUG
+// Sometimes helpful to seed everything with a known value when debugging
+// sk_memset32((uint32_t*)array.get(), 0x7FFFFFFF, dstCount);
+#endif
+
+ int count = operate(a_runs, b_runs, array.get(), op, NULL == result);
+ SkASSERT(count <= dstCount);
+
+ if (result) {
+ SkASSERT(count >= 0);
+ return result->setRuns(array.get(), count);
+ } else {
+ return (QUICK_EXIT_TRUE_COUNT == count) || !isRunCountEmpty(count);
+ }
+}
+
+bool SkRegion::op(const SkRegion& rgna, const SkRegion& rgnb, Op op) {
+ SkDEBUGCODE(this->validate();)
+ return SkRegion::Oper(rgna, rgnb, op, this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkBuffer.h"
+
+uint32_t SkRegion::writeToMemory(void* storage) const {
+ if (NULL == storage) {
+ uint32_t size = sizeof(int32_t); // -1 (empty), 0 (rect), runCount
+ if (!this->isEmpty()) {
+ size += sizeof(fBounds);
+ if (this->isComplex()) {
+ size += 2 * sizeof(int32_t); // ySpanCount + intervalCount
+ size += fRunHead->fRunCount * sizeof(RunType);
+ }
+ }
+ return size;
+ }
+
+ SkWBuffer buffer(storage);
+
+ if (this->isEmpty()) {
+ buffer.write32(-1);
+ } else {
+ bool isRect = this->isRect();
+
+ buffer.write32(isRect ? 0 : fRunHead->fRunCount);
+ buffer.write(&fBounds, sizeof(fBounds));
+
+ if (!isRect) {
+ buffer.write32(fRunHead->getYSpanCount());
+ buffer.write32(fRunHead->getIntervalCount());
+ buffer.write(fRunHead->readonly_runs(),
+ fRunHead->fRunCount * sizeof(RunType));
+ }
+ }
+ return buffer.pos();
+}
+
+uint32_t SkRegion::readFromMemory(const void* storage) {
+ SkRBuffer buffer(storage);
+ SkRegion tmp;
+ int32_t count;
+
+ count = buffer.readS32();
+ if (count >= 0) {
+ buffer.read(&tmp.fBounds, sizeof(tmp.fBounds));
+ if (count == 0) {
+ tmp.fRunHead = SkRegion_gRectRunHeadPtr;
+ } else {
+ int32_t ySpanCount = buffer.readS32();
+ int32_t intervalCount = buffer.readS32();
+ tmp.allocateRuns(count, ySpanCount, intervalCount);
+ buffer.read(tmp.fRunHead->writable_runs(), count * sizeof(RunType));
+ }
+ }
+ this->swap(tmp);
+ return buffer.pos();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkRegion& SkRegion::GetEmptyRegion() {
+ static SkRegion gEmpty;
+ return gEmpty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+// Starts with first X-interval, and returns a ptr to the X-sentinel
+static const SkRegion::RunType* skip_intervals_slow(const SkRegion::RunType runs[]) {
+ // want to track that our intevals are all disjoint, such that
+ // prev-right < next-left. We rely on this optimization in places such as
+ // contains().
+ //
+ SkRegion::RunType prevR = -SkRegion::kRunTypeSentinel;
+
+ while (runs[0] < SkRegion::kRunTypeSentinel) {
+ SkASSERT(prevR < runs[0]);
+ SkASSERT(runs[0] < runs[1]);
+ SkASSERT(runs[1] < SkRegion::kRunTypeSentinel);
+ prevR = runs[1];
+ runs += 2;
+ }
+ return runs;
+}
+
+static void compute_bounds(const SkRegion::RunType runs[],
+ SkIRect* bounds, int* ySpanCountPtr,
+ int* intervalCountPtr) {
+ assert_sentinel(runs[0], false); // top
+
+ int left = SK_MaxS32;
+ int rite = SK_MinS32;
+ int bot;
+ int ySpanCount = 0;
+ int intervalCount = 0;
+
+ bounds->fTop = *runs++;
+ do {
+ bot = *runs++;
+ SkASSERT(SkRegion::kRunTypeSentinel > bot);
+
+ ySpanCount += 1;
+
+ runs += 1; // skip intervalCount for now
+ if (*runs < SkRegion::kRunTypeSentinel) {
+ if (left > *runs) {
+ left = *runs;
+ }
+
+ const SkRegion::RunType* prev = runs;
+ runs = skip_intervals_slow(runs);
+ int intervals = (runs - prev) >> 1;
+ SkASSERT(prev[-1] == intervals);
+ intervalCount += intervals;
+
+ if (rite < runs[-1]) {
+ rite = runs[-1];
+ }
+ } else {
+ SkASSERT(0 == runs[-1]); // no intervals
+ }
+ SkASSERT(SkRegion::kRunTypeSentinel == *runs);
+ runs += 1;
+ } while (SkRegion::kRunTypeSentinel != *runs);
+
+ bounds->fLeft = left;
+ bounds->fRight = rite;
+ bounds->fBottom = bot;
+ *ySpanCountPtr = ySpanCount;
+ *intervalCountPtr = intervalCount;
+}
+
+void SkRegion::validate() const {
+ if (this->isEmpty()) {
+ // check for explicit empty (the zero rect), so we can compare rects to know when
+ // two regions are equal (i.e. emptyRectA == emptyRectB)
+ // this is stricter than just asserting fBounds.isEmpty()
+ SkASSERT(fBounds.fLeft == 0 && fBounds.fTop == 0 && fBounds.fRight == 0 && fBounds.fBottom == 0);
+ } else {
+ SkASSERT(!fBounds.isEmpty());
+ if (!this->isRect()) {
+ SkASSERT(fRunHead->fRefCnt >= 1);
+ SkASSERT(fRunHead->fRunCount > kRectRegionRuns);
+
+ const RunType* run = fRunHead->readonly_runs();
+
+ // check that our bounds match our runs
+ {
+ SkIRect bounds;
+ int ySpanCount, intervalCount;
+ compute_bounds(run, &bounds, &ySpanCount, &intervalCount);
+
+ SkASSERT(bounds == fBounds);
+ SkASSERT(ySpanCount > 0);
+ SkASSERT(fRunHead->getYSpanCount() == ySpanCount);
+ // SkASSERT(intervalCount > 1);
+ SkASSERT(fRunHead->getIntervalCount() == intervalCount);
+ }
+ }
+ }
+}
+
+void SkRegion::dump() const {
+ if (this->isEmpty()) {
+ SkDebugf(" rgn: empty\n");
+ } else {
+ SkDebugf(" rgn: [%d %d %d %d]", fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom);
+ if (this->isComplex()) {
+ const RunType* runs = fRunHead->readonly_runs();
+ for (int i = 0; i < fRunHead->fRunCount; i++)
+ SkDebugf(" %d", runs[i]);
+ }
+ SkDebugf("\n");
+ }
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRegion::Iterator::Iterator(const SkRegion& rgn) {
+ this->reset(rgn);
+}
+
+bool SkRegion::Iterator::rewind() {
+ if (fRgn) {
+ this->reset(*fRgn);
+ return true;
+ }
+ return false;
+}
+
+void SkRegion::Iterator::reset(const SkRegion& rgn) {
+ fRgn = &rgn;
+ if (rgn.isEmpty()) {
+ fDone = true;
+ } else {
+ fDone = false;
+ if (rgn.isRect()) {
+ fRect = rgn.fBounds;
+ fRuns = NULL;
+ } else {
+ fRuns = rgn.fRunHead->readonly_runs();
+ fRect.set(fRuns[3], fRuns[0], fRuns[4], fRuns[1]);
+ fRuns += 5;
+ // Now fRuns points to the 2nd interval (or x-sentinel)
+ }
+ }
+}
+
+void SkRegion::Iterator::next() {
+ if (fDone) {
+ return;
+ }
+
+ if (fRuns == NULL) { // rect case
+ fDone = true;
+ return;
+ }
+
+ const RunType* runs = fRuns;
+
+ if (runs[0] < kRunTypeSentinel) { // valid X value
+ fRect.fLeft = runs[0];
+ fRect.fRight = runs[1];
+ runs += 2;
+ } else { // we're at the end of a line
+ runs += 1;
+ if (runs[0] < kRunTypeSentinel) { // valid Y value
+ int intervals = runs[1];
+ if (0 == intervals) { // empty line
+ fRect.fTop = runs[0];
+ runs += 3;
+ } else {
+ fRect.fTop = fRect.fBottom;
+ }
+
+ fRect.fBottom = runs[0];
+ assert_sentinel(runs[2], false);
+ assert_sentinel(runs[3], false);
+ fRect.fLeft = runs[2];
+ fRect.fRight = runs[3];
+ runs += 4;
+ } else { // end of rgn
+ fDone = true;
+ }
+ }
+ fRuns = runs;
+}
+
+SkRegion::Cliperator::Cliperator(const SkRegion& rgn, const SkIRect& clip)
+ : fIter(rgn), fClip(clip), fDone(true) {
+ const SkIRect& r = fIter.rect();
+
+ while (!fIter.done()) {
+ if (r.fTop >= clip.fBottom) {
+ break;
+ }
+ if (fRect.intersect(clip, r)) {
+ fDone = false;
+ break;
+ }
+ fIter.next();
+ }
+}
+
+void SkRegion::Cliperator::next() {
+ if (fDone) {
+ return;
+ }
+
+ const SkIRect& r = fIter.rect();
+
+ fDone = true;
+ fIter.next();
+ while (!fIter.done()) {
+ if (r.fTop >= fClip.fBottom) {
+ break;
+ }
+ if (fRect.intersect(fClip, r)) {
+ fDone = false;
+ break;
+ }
+ fIter.next();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkRegion::Spanerator::Spanerator(const SkRegion& rgn, int y, int left,
+ int right) {
+ SkDEBUGCODE(rgn.validate();)
+
+ const SkIRect& r = rgn.getBounds();
+
+ fDone = true;
+ if (!rgn.isEmpty() && y >= r.fTop && y < r.fBottom &&
+ right > r.fLeft && left < r.fRight) {
+ if (rgn.isRect()) {
+ if (left < r.fLeft) {
+ left = r.fLeft;
+ }
+ if (right > r.fRight) {
+ right = r.fRight;
+ }
+ fLeft = left;
+ fRight = right;
+ fRuns = NULL; // means we're a rect, not a rgn
+ fDone = false;
+ } else {
+ const SkRegion::RunType* runs = rgn.fRunHead->findScanline(y);
+ runs += 2; // skip Bottom and IntervalCount
+ for (;;) {
+ // runs[0..1] is to the right of the span, so we're done
+ if (runs[0] >= right) {
+ break;
+ }
+ // runs[0..1] is to the left of the span, so continue
+ if (runs[1] <= left) {
+ runs += 2;
+ continue;
+ }
+ // runs[0..1] intersects the span
+ fRuns = runs;
+ fLeft = left;
+ fRight = right;
+ fDone = false;
+ break;
+ }
+ }
+ }
+}
+
+bool SkRegion::Spanerator::next(int* left, int* right) {
+ if (fDone) {
+ return false;
+ }
+
+ if (fRuns == NULL) { // we're a rect
+ fDone = true; // ok, now we're done
+ if (left) {
+ *left = fLeft;
+ }
+ if (right) {
+ *right = fRight;
+ }
+ return true; // this interval is legal
+ }
+
+ const SkRegion::RunType* runs = fRuns;
+
+ if (runs[0] >= fRight) {
+ fDone = true;
+ return false;
+ }
+
+ SkASSERT(runs[1] > fLeft);
+
+ if (left) {
+ *left = SkMax32(fLeft, runs[0]);
+ }
+ if (right) {
+ *right = SkMin32(fRight, runs[1]);
+ }
+ fRuns = runs + 2;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+bool SkRegion::debugSetRuns(const RunType runs[], int count) {
+ // we need to make a copy, since the real method may modify the array, and
+ // so it cannot be const.
+
+ SkAutoTArray<RunType> storage(count);
+ memcpy(storage.get(), runs, count * sizeof(RunType));
+ return this->setRuns(storage.get(), count);
+}
+
+#endif
diff --git a/core/SkRegionPriv.h b/core/SkRegionPriv.h
new file mode 100644
index 00000000..f299f3a9
--- /dev/null
+++ b/core/SkRegionPriv.h
@@ -0,0 +1,233 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkRegionPriv_DEFINED
+#define SkRegionPriv_DEFINED
+
+#include "SkRegion.h"
+#include "SkThread.h"
+
+#define assert_sentinel(value, isSentinel) \
+ SkASSERT(((value) == SkRegion::kRunTypeSentinel) == isSentinel)
+
+//SkDEBUGCODE(extern int32_t gRgnAllocCounter;)
+
+#ifdef SK_DEBUG
+// Given the first interval (just past the interval-count), compute the
+// interval count, by search for the x-sentinel
+//
+static int compute_intervalcount(const SkRegion::RunType runs[]) {
+ const SkRegion::RunType* curr = runs;
+ while (*curr < SkRegion::kRunTypeSentinel) {
+ SkASSERT(curr[0] < curr[1]);
+ SkASSERT(curr[1] < SkRegion::kRunTypeSentinel);
+ curr += 2;
+ }
+ return (curr - runs) >> 1;
+}
+#endif
+
+struct SkRegion::RunHead {
+private:
+
+public:
+ int32_t fRefCnt;
+ int32_t fRunCount;
+
+ /**
+ * Number of spans with different Y values. This does not count the initial
+ * Top value, nor does it count the final Y-Sentinel value. In the logical
+ * case of a rectangle, this would return 1, and an empty region would
+ * return 0.
+ */
+ int getYSpanCount() const {
+ return fYSpanCount;
+ }
+
+ /**
+ * Number of intervals in the entire region. This equals the number of
+ * rects that would be returned by the Iterator. In the logical case of
+ * a rect, this would return 1, and an empty region would return 0.
+ */
+ int getIntervalCount() const {
+ return fIntervalCount;
+ }
+
+ static RunHead* Alloc(int count) {
+ //SkDEBUGCODE(sk_atomic_inc(&gRgnAllocCounter);)
+ //SkDEBUGF(("************** gRgnAllocCounter::alloc %d\n", gRgnAllocCounter));
+
+ SkASSERT(count >= SkRegion::kRectRegionRuns);
+
+ RunHead* head = (RunHead*)sk_malloc_throw(sizeof(RunHead) + count * sizeof(RunType));
+ head->fRefCnt = 1;
+ head->fRunCount = count;
+ // these must be filled in later, otherwise we will be invalid
+ head->fYSpanCount = 0;
+ head->fIntervalCount = 0;
+ return head;
+ }
+
+ static RunHead* Alloc(int count, int yspancount, int intervalCount) {
+ SkASSERT(yspancount > 0);
+ SkASSERT(intervalCount > 1);
+
+ RunHead* head = Alloc(count);
+ head->fYSpanCount = yspancount;
+ head->fIntervalCount = intervalCount;
+ return head;
+ }
+
+ SkRegion::RunType* writable_runs() {
+ SkASSERT(fRefCnt == 1);
+ return (SkRegion::RunType*)(this + 1);
+ }
+
+ const SkRegion::RunType* readonly_runs() const {
+ return (const SkRegion::RunType*)(this + 1);
+ }
+
+ RunHead* ensureWritable() {
+ RunHead* writable = this;
+ if (fRefCnt > 1) {
+ // We need to alloc & copy the current region before we call
+ // sk_atomic_dec because it could be freed in the meantime,
+ // otherwise.
+ writable = Alloc(fRunCount, fYSpanCount, fIntervalCount);
+ memcpy(writable->writable_runs(), this->readonly_runs(),
+ fRunCount * sizeof(RunType));
+
+ // fRefCount might have changed since we last checked.
+ // If we own the last reference at this point, we need to
+ // free the memory.
+ if (sk_atomic_dec(&fRefCnt) == 1) {
+ sk_free(this);
+ }
+ }
+ return writable;
+ }
+
+ /**
+ * Given a scanline (including its Bottom value at runs[0]), return the next
+ * scanline. Asserts that there is one (i.e. runs[0] < Sentinel)
+ */
+ static SkRegion::RunType* SkipEntireScanline(const SkRegion::RunType runs[]) {
+ // we are not the Y Sentinel
+ SkASSERT(runs[0] < SkRegion::kRunTypeSentinel);
+
+ const int intervals = runs[1];
+ SkASSERT(runs[2 + intervals * 2] == SkRegion::kRunTypeSentinel);
+#ifdef SK_DEBUG
+ {
+ int n = compute_intervalcount(&runs[2]);
+ SkASSERT(n == intervals);
+ }
+#endif
+
+ // skip the entire line [B N [L R] S]
+ runs += 1 + 1 + intervals * 2 + 1;
+ return const_cast<SkRegion::RunType*>(runs);
+ }
+
+
+ /**
+ * Return the scanline that contains the Y value. This requires that the Y
+ * value is already known to be contained within the bounds of the region,
+ * and so this routine never returns NULL.
+ *
+ * It returns the beginning of the scanline, starting with its Bottom value.
+ */
+ SkRegion::RunType* findScanline(int y) const {
+ const RunType* runs = this->readonly_runs();
+
+ // if the top-check fails, we didn't do a quick check on the bounds
+ SkASSERT(y >= runs[0]);
+
+ runs += 1; // skip top-Y
+ for (;;) {
+ int bottom = runs[0];
+ // If we hit this, we've walked off the region, and our bounds check
+ // failed.
+ SkASSERT(bottom < SkRegion::kRunTypeSentinel);
+ if (y < bottom) {
+ break;
+ }
+ runs = SkipEntireScanline(runs);
+ }
+ return const_cast<SkRegion::RunType*>(runs);
+ }
+
+ // Copy src runs into us, computing interval counts and bounds along the way
+ void computeRunBounds(SkIRect* bounds) {
+ RunType* runs = this->writable_runs();
+ bounds->fTop = *runs++;
+
+ int bot;
+ int ySpanCount = 0;
+ int intervalCount = 0;
+ int left = SK_MaxS32;
+ int rite = SK_MinS32;
+
+ do {
+ bot = *runs++;
+ SkASSERT(bot < SkRegion::kRunTypeSentinel);
+ ySpanCount += 1;
+
+ const int intervals = *runs++;
+ SkASSERT(intervals >= 0);
+ SkASSERT(intervals < SkRegion::kRunTypeSentinel);
+
+ if (intervals > 0) {
+#ifdef SK_DEBUG
+ {
+ int n = compute_intervalcount(runs);
+ SkASSERT(n == intervals);
+ }
+#endif
+ RunType L = runs[0];
+ SkASSERT(L < SkRegion::kRunTypeSentinel);
+ if (left > L) {
+ left = L;
+ }
+
+ runs += intervals * 2;
+ RunType R = runs[-1];
+ SkASSERT(R < SkRegion::kRunTypeSentinel);
+ if (rite < R) {
+ rite = R;
+ }
+
+ intervalCount += intervals;
+ }
+ SkASSERT(SkRegion::kRunTypeSentinel == *runs);
+ runs += 1; // skip x-sentinel
+
+ // test Y-sentinel
+ } while (SkRegion::kRunTypeSentinel > *runs);
+
+#ifdef SK_DEBUG
+ // +1 to skip the last Y-sentinel
+ int runCount = runs - this->writable_runs() + 1;
+ SkASSERT(runCount == fRunCount);
+#endif
+
+ fYSpanCount = ySpanCount;
+ fIntervalCount = intervalCount;
+
+ bounds->fLeft = left;
+ bounds->fRight = rite;
+ bounds->fBottom = bot;
+ }
+
+private:
+ int32_t fYSpanCount;
+ int32_t fIntervalCount;
+};
+
+#endif
diff --git a/core/SkRegion_path.cpp b/core/SkRegion_path.cpp
new file mode 100644
index 00000000..0a5a4018
--- /dev/null
+++ b/core/SkRegion_path.cpp
@@ -0,0 +1,501 @@
+
+/*
+ * 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 "SkRegionPriv.h"
+#include "SkBlitter.h"
+#include "SkScan.h"
+#include "SkTDArray.h"
+#include "SkPath.h"
+
+class SkRgnBuilder : public SkBlitter {
+public:
+ virtual ~SkRgnBuilder();
+
+ // returns true if it could allocate the working storage needed
+ bool init(int maxHeight, int maxTransitions);
+
+ void done() {
+ if (fCurrScanline != NULL) {
+ fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX()));
+ if (!this->collapsWithPrev()) { // flush the last line
+ fCurrScanline = fCurrScanline->nextScanline();
+ }
+ }
+ }
+
+ int computeRunCount() const;
+ void copyToRect(SkIRect*) const;
+ void copyToRgn(SkRegion::RunType runs[]) const;
+
+ virtual void blitH(int x, int y, int width);
+
+#ifdef SK_DEBUG
+ void dump() const {
+ SkDebugf("SkRgnBuilder: Top = %d\n", fTop);
+ const Scanline* line = (Scanline*)fStorage;
+ while (line < fCurrScanline) {
+ SkDebugf("SkRgnBuilder::Scanline: LastY=%d, fXCount=%d", line->fLastY, line->fXCount);
+ for (int i = 0; i < line->fXCount; i++) {
+ SkDebugf(" %d", line->firstX()[i]);
+ }
+ SkDebugf("\n");
+
+ line = line->nextScanline();
+ }
+ }
+#endif
+private:
+ /*
+ * Scanline mimics a row in the region, nearly. A row in a region is:
+ * [Bottom IntervalCount [L R]... Sentinel]
+ * while a Scanline is
+ * [LastY XCount [L R]... uninitialized]
+ * The two are the same length (which is good), but we have to transmute
+ * the scanline a little when we convert it to a region-row.
+ *
+ * Potentially we could recode this to exactly match the row format, in
+ * which case copyToRgn() could be a single memcpy. Not sure that is worth
+ * the effort.
+ */
+ struct Scanline {
+ SkRegion::RunType fLastY;
+ SkRegion::RunType fXCount;
+
+ SkRegion::RunType* firstX() const { return (SkRegion::RunType*)(this + 1); }
+ Scanline* nextScanline() const {
+ // add final +1 for the x-sentinel
+ return (Scanline*)((SkRegion::RunType*)(this + 1) + fXCount + 1);
+ }
+ };
+ SkRegion::RunType* fStorage;
+ Scanline* fCurrScanline;
+ Scanline* fPrevScanline;
+ // points at next avialable x[] in fCurrScanline
+ SkRegion::RunType* fCurrXPtr;
+ SkRegion::RunType fTop; // first Y value
+
+ int fStorageCount;
+
+ bool collapsWithPrev() {
+ if (fPrevScanline != NULL &&
+ fPrevScanline->fLastY + 1 == fCurrScanline->fLastY &&
+ fPrevScanline->fXCount == fCurrScanline->fXCount &&
+ !memcmp(fPrevScanline->firstX(),
+ fCurrScanline->firstX(),
+ fCurrScanline->fXCount * sizeof(SkRegion::RunType)))
+ {
+ // update the height of fPrevScanline
+ fPrevScanline->fLastY = fCurrScanline->fLastY;
+ return true;
+ }
+ return false;
+ }
+};
+
+SkRgnBuilder::~SkRgnBuilder() {
+ sk_free(fStorage);
+}
+
+bool SkRgnBuilder::init(int maxHeight, int maxTransitions) {
+ if ((maxHeight | maxTransitions) < 0) {
+ return false;
+ }
+
+ Sk64 count, size;
+
+ // compute the count with +1 and +3 slop for the working buffer
+ count.setMul(maxHeight + 1, 3 + maxTransitions);
+ if (!count.is32() || count.isNeg()) {
+ return false;
+ }
+ fStorageCount = count.get32();
+
+ size.setMul(fStorageCount, sizeof(SkRegion::RunType));
+ if (!size.is32() || size.isNeg()) {
+ return false;
+ }
+
+ fStorage = (SkRegion::RunType*)sk_malloc_flags(size.get32(), 0);
+ if (NULL == fStorage) {
+ return false;
+ }
+
+ fCurrScanline = NULL; // signal empty collection
+ fPrevScanline = NULL; // signal first scanline
+ return true;
+}
+
+void SkRgnBuilder::blitH(int x, int y, int width) {
+ if (fCurrScanline == NULL) { // first time
+ fTop = (SkRegion::RunType)(y);
+ fCurrScanline = (Scanline*)fStorage;
+ fCurrScanline->fLastY = (SkRegion::RunType)(y);
+ fCurrXPtr = fCurrScanline->firstX();
+ } else {
+ SkASSERT(y >= fCurrScanline->fLastY);
+
+ if (y > fCurrScanline->fLastY) {
+ // if we get here, we're done with fCurrScanline
+ fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX()));
+
+ int prevLastY = fCurrScanline->fLastY;
+ if (!this->collapsWithPrev()) {
+ fPrevScanline = fCurrScanline;
+ fCurrScanline = fCurrScanline->nextScanline();
+
+ }
+ if (y - 1 > prevLastY) { // insert empty run
+ fCurrScanline->fLastY = (SkRegion::RunType)(y - 1);
+ fCurrScanline->fXCount = 0;
+ fCurrScanline = fCurrScanline->nextScanline();
+ }
+ // setup for the new curr line
+ fCurrScanline->fLastY = (SkRegion::RunType)(y);
+ fCurrXPtr = fCurrScanline->firstX();
+ }
+ }
+ // check if we should extend the current run, or add a new one
+ if (fCurrXPtr > fCurrScanline->firstX() && fCurrXPtr[-1] == x) {
+ fCurrXPtr[-1] = (SkRegion::RunType)(x + width);
+ } else {
+ fCurrXPtr[0] = (SkRegion::RunType)(x);
+ fCurrXPtr[1] = (SkRegion::RunType)(x + width);
+ fCurrXPtr += 2;
+ }
+ SkASSERT(fCurrXPtr - fStorage < fStorageCount);
+}
+
+int SkRgnBuilder::computeRunCount() const {
+ if (fCurrScanline == NULL) {
+ return 0;
+ }
+
+ const SkRegion::RunType* line = fStorage;
+ const SkRegion::RunType* stop = (const SkRegion::RunType*)fCurrScanline;
+
+ return 2 + (int)(stop - line);
+}
+
+void SkRgnBuilder::copyToRect(SkIRect* r) const {
+ SkASSERT(fCurrScanline != NULL);
+ // A rect's scanline is [bottom intervals left right sentinel] == 5
+ SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage == 5);
+
+ const Scanline* line = (const Scanline*)fStorage;
+ SkASSERT(line->fXCount == 2);
+
+ r->set(line->firstX()[0], fTop, line->firstX()[1], line->fLastY + 1);
+}
+
+void SkRgnBuilder::copyToRgn(SkRegion::RunType runs[]) const {
+ SkASSERT(fCurrScanline != NULL);
+ SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage > 4);
+
+ const Scanline* line = (const Scanline*)fStorage;
+ const Scanline* stop = fCurrScanline;
+
+ *runs++ = fTop;
+ do {
+ *runs++ = (SkRegion::RunType)(line->fLastY + 1);
+ int count = line->fXCount;
+ *runs++ = count >> 1; // intervalCount
+ if (count) {
+ memcpy(runs, line->firstX(), count * sizeof(SkRegion::RunType));
+ runs += count;
+ }
+ *runs++ = SkRegion::kRunTypeSentinel;
+ line = line->nextScanline();
+ } while (line < stop);
+ SkASSERT(line == stop);
+ *runs = SkRegion::kRunTypeSentinel;
+}
+
+static unsigned verb_to_initial_last_index(unsigned verb) {
+ static const uint8_t gPathVerbToInitialLastIndex[] = {
+ 0, // kMove_Verb
+ 1, // kLine_Verb
+ 2, // kQuad_Verb
+ 2, // kConic_Verb
+ 3, // kCubic_Verb
+ 0, // kClose_Verb
+ 0 // kDone_Verb
+ };
+ SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToInitialLastIndex));
+ return gPathVerbToInitialLastIndex[verb];
+}
+
+static unsigned verb_to_max_edges(unsigned verb) {
+ static const uint8_t gPathVerbToMaxEdges[] = {
+ 0, // kMove_Verb
+ 1, // kLine_Verb
+ 2, // kQuad_VerbB
+ 2, // kConic_VerbB
+ 3, // kCubic_Verb
+ 0, // kClose_Verb
+ 0 // kDone_Verb
+ };
+ SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToMaxEdges));
+ return gPathVerbToMaxEdges[verb];
+}
+
+
+static int count_path_runtype_values(const SkPath& path, int* itop, int* ibot) {
+ SkPath::Iter iter(path, true);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+
+ int maxEdges = 0;
+ SkScalar top = SkIntToScalar(SK_MaxS16);
+ SkScalar bot = SkIntToScalar(SK_MinS16);
+
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ maxEdges += verb_to_max_edges(verb);
+
+ int lastIndex = verb_to_initial_last_index(verb);
+ if (lastIndex > 0) {
+ for (int i = 1; i <= lastIndex; i++) {
+ if (top > pts[i].fY) {
+ top = pts[i].fY;
+ } else if (bot < pts[i].fY) {
+ bot = pts[i].fY;
+ }
+ }
+ } else if (SkPath::kMove_Verb == verb) {
+ if (top > pts[0].fY) {
+ top = pts[0].fY;
+ } else if (bot < pts[0].fY) {
+ bot = pts[0].fY;
+ }
+ }
+ }
+ SkASSERT(top <= bot);
+
+ *itop = SkScalarRound(top);
+ *ibot = SkScalarRound(bot);
+ return maxEdges;
+}
+
+bool SkRegion::setPath(const SkPath& path, const SkRegion& clip) {
+ SkDEBUGCODE(this->validate();)
+
+ if (clip.isEmpty()) {
+ return this->setEmpty();
+ }
+
+ if (path.isEmpty()) {
+ if (path.isInverseFillType()) {
+ return this->set(clip);
+ } else {
+ return this->setEmpty();
+ }
+ }
+
+ // compute worst-case rgn-size for the path
+ int pathTop, pathBot;
+ int pathTransitions = count_path_runtype_values(path, &pathTop, &pathBot);
+ int clipTop, clipBot;
+ int clipTransitions;
+
+ clipTransitions = clip.count_runtype_values(&clipTop, &clipBot);
+
+ int top = SkMax32(pathTop, clipTop);
+ int bot = SkMin32(pathBot, clipBot);
+
+ if (top >= bot)
+ return this->setEmpty();
+
+ SkRgnBuilder builder;
+
+ if (!builder.init(bot - top, SkMax32(pathTransitions, clipTransitions))) {
+ // can't allocate working space, so return false
+ return this->setEmpty();
+ }
+
+ SkScan::FillPath(path, clip, &builder);
+ builder.done();
+
+ int count = builder.computeRunCount();
+ if (count == 0) {
+ return this->setEmpty();
+ } else if (count == kRectRegionRuns) {
+ builder.copyToRect(&fBounds);
+ this->setRect(fBounds);
+ } else {
+ SkRegion tmp;
+
+ tmp.fRunHead = RunHead::Alloc(count);
+ builder.copyToRgn(tmp.fRunHead->writable_runs());
+ tmp.fRunHead->computeRunBounds(&tmp.fBounds);
+ this->swap(tmp);
+ }
+ SkDEBUGCODE(this->validate();)
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+struct Edge {
+ enum {
+ kY0Link = 0x01,
+ kY1Link = 0x02,
+
+ kCompleteLink = (kY0Link | kY1Link)
+ };
+
+ SkRegion::RunType fX;
+ SkRegion::RunType fY0, fY1;
+ uint8_t fFlags;
+ Edge* fNext;
+
+ void set(int x, int y0, int y1) {
+ SkASSERT(y0 != y1);
+
+ fX = (SkRegion::RunType)(x);
+ fY0 = (SkRegion::RunType)(y0);
+ fY1 = (SkRegion::RunType)(y1);
+ fFlags = 0;
+ SkDEBUGCODE(fNext = NULL;)
+ }
+
+ int top() const {
+ return SkFastMin32(fY0, fY1);
+ }
+};
+
+static void find_link(Edge* base, Edge* stop) {
+ SkASSERT(base < stop);
+
+ if (base->fFlags == Edge::kCompleteLink) {
+ SkASSERT(base->fNext);
+ return;
+ }
+
+ SkASSERT(base + 1 < stop);
+
+ int y0 = base->fY0;
+ int y1 = base->fY1;
+
+ Edge* e = base;
+ if ((base->fFlags & Edge::kY0Link) == 0) {
+ for (;;) {
+ e += 1;
+ if ((e->fFlags & Edge::kY1Link) == 0 && y0 == e->fY1) {
+ SkASSERT(NULL == e->fNext);
+ e->fNext = base;
+ e->fFlags = SkToU8(e->fFlags | Edge::kY1Link);
+ break;
+ }
+ }
+ }
+
+ e = base;
+ if ((base->fFlags & Edge::kY1Link) == 0) {
+ for (;;) {
+ e += 1;
+ if ((e->fFlags & Edge::kY0Link) == 0 && y1 == e->fY0) {
+ SkASSERT(NULL == base->fNext);
+ base->fNext = e;
+ e->fFlags = SkToU8(e->fFlags | Edge::kY0Link);
+ break;
+ }
+ }
+ }
+
+ base->fFlags = Edge::kCompleteLink;
+}
+
+static int extract_path(Edge* edge, Edge* stop, SkPath* path) {
+ while (0 == edge->fFlags) {
+ edge++; // skip over "used" edges
+ }
+
+ SkASSERT(edge < stop);
+
+ Edge* base = edge;
+ Edge* prev = edge;
+ edge = edge->fNext;
+ SkASSERT(edge != base);
+
+ int count = 1;
+ path->moveTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY0));
+ prev->fFlags = 0;
+ do {
+ if (prev->fX != edge->fX || prev->fY1 != edge->fY0) { // skip collinear
+ path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V
+ path->lineTo(SkIntToScalar(edge->fX), SkIntToScalar(edge->fY0)); // H
+ }
+ prev = edge;
+ edge = edge->fNext;
+ count += 1;
+ prev->fFlags = 0;
+ } while (edge != base);
+ path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1)); // V
+ path->close();
+ return count;
+}
+
+#include "SkTSearch.h"
+
+static int EdgeProc(const Edge* a, const Edge* b) {
+ return (a->fX == b->fX) ? a->top() - b->top() : a->fX - b->fX;
+}
+
+bool SkRegion::getBoundaryPath(SkPath* path) const {
+ // path could safely be NULL if we're empty, but the caller shouldn't
+ // *know* that
+ SkASSERT(path);
+
+ if (this->isEmpty()) {
+ return false;
+ }
+
+ const SkIRect& bounds = this->getBounds();
+
+ if (this->isRect()) {
+ SkRect r;
+ r.set(bounds); // this converts the ints to scalars
+ path->addRect(r);
+ return true;
+ }
+
+ SkRegion::Iterator iter(*this);
+ SkTDArray<Edge> edges;
+
+ for (const SkIRect& r = iter.rect(); !iter.done(); iter.next()) {
+ Edge* edge = edges.append(2);
+ edge[0].set(r.fLeft, r.fBottom, r.fTop);
+ edge[1].set(r.fRight, r.fTop, r.fBottom);
+ }
+ qsort(edges.begin(), edges.count(), sizeof(Edge), SkCastForQSort(EdgeProc));
+
+ int count = edges.count();
+ Edge* start = edges.begin();
+ Edge* stop = start + count;
+ Edge* e;
+
+ for (e = start; e != stop; e++) {
+ find_link(e, stop);
+ }
+
+#ifdef SK_DEBUG
+ for (e = start; e != stop; e++) {
+ SkASSERT(e->fNext != NULL);
+ SkASSERT(e->fFlags == Edge::kCompleteLink);
+ }
+#endif
+
+ path->incReserve(count << 1);
+ do {
+ SkASSERT(count > 1);
+ count -= extract_path(start, stop, path);
+ } while (count > 0);
+
+ return true;
+}
diff --git a/core/SkRegion_rects.cpp b/core/SkRegion_rects.cpp
new file mode 100644
index 00000000..4121080c
--- /dev/null
+++ b/core/SkRegion_rects.cpp
@@ -0,0 +1,290 @@
+
+/*
+ * 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 "SkRegion.h"
+#include "SkChunkAlloc.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+
+#if 0
+
+struct VEdge {
+ VEdge* fPrev;
+ VEdge* fNext;
+
+ SkRegion::RunType fX;
+ SkRegion::RunType fTop;
+ SkRegion::RunType fBottom;
+ int fWinding;
+
+ void removeFromList() {
+ fPrev->fNext = fNext;
+ fNext->fPrev = fPrev;
+ }
+
+ void backwardsInsert() {
+ while (fPrev->fX > fX) {
+ VEdge* prev = fPrev;
+ VEdge* next = this;
+
+ // 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 SetFromRect(VEdge edges[], const SkIRect& r) {
+ edges[0].fX = r.fLeft;
+ edges[0].fTop = r.fTop;
+ edges[0].fBottom = r.fBottom;
+ edges[0].fWinding = -1;
+
+ edges[1].fX = r.fRight;
+ edges[1].fTop = r.fTop;
+ edges[1].fBottom = r.fBottom;
+ edges[1].fWinding = 1;
+ }
+};
+
+class Accumulator {
+public:
+ Accumulator(SkRegion::RunType top, int numRects);
+ ~Accumulator() {}
+
+ SkRegion::RunType append(SkRegion::RunType top, const VEdge* edge);
+
+ int count() const { return fTotalCount; }
+ void copyTo(SkRegion::RunType dst[]);
+
+private:
+ struct Row {
+ SkRegion::RunType* fPtr;
+ SkRegion::RunType fBottom;
+ int fCount; // just [L R] count
+ };
+ SkChunkAlloc fAlloc;
+ SkTDArray<Row> fRows;
+ SkRegion::RunType fTop;
+ int fTotalCount;
+ int fRectCount;
+};
+
+Accumulator::Accumulator(SkRegion::RunType top, int numRects)
+ : fAlloc((1 + numRects * 2 + 1) * sizeof(int32_t)) {
+ fRectCount = numRects;
+ fTop = top;
+ fTotalCount = 2; // Top + final sentinel
+}
+
+//#define TRACE_ROW(code) code
+#define TRACE_ROW(code)
+
+SkRegion::RunType Accumulator::append(SkRegion::RunType currY, const VEdge* edge) {
+ // worst-case size
+ size_t size = fRectCount * 2 * sizeof(SkRegion::RunType);
+ SkRegion::RunType* row = (SkRegion::RunType*)fAlloc.allocThrow(size);
+ SkRegion::RunType* rowHead = row;
+
+ SkRegion::RunType nextY = SkRegion::kRunTypeSentinel;
+ int winding = edge->fWinding;
+
+ // record the L R values for this row
+
+ if (edge->fTop > currY) {
+ nextY = SkMin32(nextY, edge->fTop);
+ TRACE_ROW(SkDebugf("Y %d\n", currY);)
+ } else {
+ SkRegion::RunType currR;
+ *row++ = edge->fX;
+ TRACE_ROW(SkDebugf("Y %d [%d", currY, edge->fX);)
+ edge = edge->fNext;
+ for (;;) {
+ if (edge->fTop > currY) {
+ nextY = SkMin32(nextY, edge->fTop);
+ break;
+ }
+
+ int prevWinding = winding;
+ winding += edge->fWinding;
+ if (0 == winding) { // we finished an interval
+ currR = edge->fX;
+ } else if (0 == prevWinding && edge->fX > currR) {
+ *row++ = currR;
+ *row++ = edge->fX;
+ TRACE_ROW(SkDebugf(" %d] [%d", currR, edge->fX);)
+ }
+
+ nextY = SkMin32(nextY, edge->fBottom);
+ edge = edge->fNext;
+ }
+ SkASSERT(0 == winding);
+ *row++ = currR;
+ TRACE_ROW(SkDebugf(" %d]\n", currR);)
+ }
+ int rowCount = row - rowHead;
+
+ // now see if we have already seen this row, or if its unique
+
+ Row* r = fRows.count() ? &fRows[fRows.count() - 1] : NULL;
+ if (r && (r->fCount == rowCount) &&
+ !memcmp(r->fPtr, rowHead,
+ rowCount * sizeof(SkRegion::RunType))) {
+ r->fBottom = nextY; // update bottom
+ fAlloc.unalloc(rowHead);
+ } else {
+ Row* r = fRows.append();
+ r->fPtr = rowHead;
+ r->fBottom = nextY;
+ r->fCount = rowCount;
+ fTotalCount += 1 + rowCount + 1;
+ }
+
+ return nextY;
+}
+
+void Accumulator::copyTo(SkRegion::RunType dst[]) {
+ SkDEBUGCODE(SkRegion::RunType* startDst = dst;)
+
+ *dst++ = fTop;
+
+ const Row* curr = fRows.begin();
+ const Row* stop = fRows.end();
+ while (curr < stop) {
+ *dst++ = curr->fBottom;
+ memcpy(dst, curr->fPtr, curr->fCount * sizeof(SkRegion::RunType));
+ dst += curr->fCount;
+ *dst++ = SkRegion::kRunTypeSentinel;
+ curr += 1;
+ }
+ *dst++ = SkRegion::kRunTypeSentinel;
+ SkASSERT(dst - startDst == fTotalCount);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> int SkTCmp2Int(const T& a, const T& b) {
+ return (a < b) ? -1 : ((b < a) ? 1 : 0);
+}
+
+static inline int SkCmp32(int32_t a, int32_t b) {
+ return (a < b) ? -1 : ((b < a) ? 1 : 0);
+}
+
+static int compare_edgeptr(const void* p0, const void* p1) {
+ const VEdge* e0 = *static_cast<VEdge*const*>(p0);
+ const VEdge* e1 = *static_cast<VEdge*const*>(p1);
+
+ SkRegion::RunType v0 = e0->fTop;
+ SkRegion::RunType v1 = e1->fTop;
+
+ if (v0 == v1) {
+ v0 = e0->fX;
+ v1 = e1->fX;
+ }
+ return SkCmp32(v0, v1);
+}
+
+// fillout edge[] from rects[], sorted. Return the head, and set the tail
+//
+static VEdge* sort_edges(VEdge** edgePtr, VEdge edge[], const SkIRect rects[],
+ int rectCount, VEdge** edgeTail) {
+ int i;
+ VEdge** ptr = edgePtr;
+ for (int i = 0; i < rectCount; i++) {
+ if (!rects[i].isEmpty()) {
+ VEdge::SetFromRect(edge, rects[i]);
+ *ptr++ = edge++;
+ *ptr++ = edge++;
+ }
+ }
+
+ int edgeCount = ptr - edgePtr;
+ if (0 == edgeCount) {
+ // all the rects[] were empty
+ return NULL;
+ }
+
+ qsort(edgePtr, edgeCount, sizeof(*edgePtr), compare_edgeptr);
+ for (i = 1; i < edgeCount; i++) {
+ edgePtr[i - 1]->fNext = edgePtr[i];
+ edgePtr[i]->fPrev = edgePtr[i - 1];
+ }
+ *edgeTail = edgePtr[edgeCount - 1];
+ return edgePtr[0];
+}
+
+bool SkRegion::setRects(const SkIRect rects[], int rectCount) {
+ if (0 == rectCount) {
+ return this->setEmpty();
+ }
+ if (1 == rectCount) {
+ return this->setRect(rects[0]);
+ }
+
+ int edgeCount = rectCount * 2;
+ SkAutoMalloc memory((sizeof(VEdge) + sizeof(VEdge*)) * edgeCount);
+ VEdge** edgePtr = (VEdge**)memory.get();
+ VEdge* tail, *head = (VEdge*)(edgePtr + edgeCount);
+ head = sort_edges(edgePtr, head, rects, rectCount, &tail);
+ // check if we have no edges
+ if (NULL == head) {
+ return this->setEmpty();
+ }
+
+ // at this stage, we don't really care about edgeCount, or if rectCount is
+ // larger that it should be (since sort_edges might have skipped some
+ // empty rects[]). rectCount now is just used for worst-case allocations
+
+ VEdge headEdge, tailEdge;
+ headEdge.fPrev = NULL;
+ headEdge.fNext = head;
+ headEdge.fTop = SK_MinS32;
+ headEdge.fX = SK_MinS32;
+ head->fPrev = &headEdge;
+
+ tailEdge.fPrev = tail;
+ tailEdge.fNext = NULL;
+ tailEdge.fTop = SK_MaxS32;
+ tail->fNext = &tailEdge;
+
+ int32_t currY = head->fTop;
+ Accumulator accum(currY, rectCount);
+
+ while (head->fNext) {
+ VEdge* edge = head;
+ // accumulate the current
+ SkRegion::RunType nextY = accum.append(currY, edge);
+ // remove the old
+ while (edge->fTop <= currY) {
+ VEdge* next = edge->fNext;
+ if (edge->fBottom <= nextY) {
+ edge->removeFromList();
+ }
+ edge = next;
+ }
+ // insert (sorted) the new
+ while (edge->fTop == nextY) {
+ VEdge* next = edge->fNext;
+ edge->backwardsInsert();
+ edge = next;
+ }
+ currY = nextY;
+ head = headEdge.fNext;
+ }
+
+ SkAutoTArray<RunType> runs(accum.count());
+ accum.copyTo(runs.get());
+ return this->setRuns(runs.get(), accum.count());
+}
+
+#endif
diff --git a/core/SkScalar.cpp b/core/SkScalar.cpp
new file mode 100644
index 00000000..c48d3890
--- /dev/null
+++ b/core/SkScalar.cpp
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright 2010 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 "SkMath.h"
+#include "SkScalar.h"
+
+SkScalar SkScalarInterpFunc(SkScalar searchKey, const SkScalar keys[],
+ const SkScalar values[], int length) {
+ SkASSERT(length > 0);
+ SkASSERT(keys != NULL);
+ SkASSERT(values != NULL);
+#ifdef SK_DEBUG
+ for (int i = 1; i < length; i++)
+ SkASSERT(keys[i] >= keys[i-1]);
+#endif
+ int right = 0;
+ while (right < length && searchKey > keys[right])
+ right++;
+ // Could use sentinel values to eliminate conditionals, but since the
+ // tables are taken as input, a simpler format is better.
+ if (length == right)
+ return values[length-1];
+ if (0 == right)
+ return values[0];
+ // Otherwise, interpolate between right - 1 and right.
+ SkScalar rightKey = keys[right];
+ SkScalar leftKey = keys[right-1];
+ SkScalar fract = SkScalarDiv(searchKey-leftKey,rightKey-leftKey);
+ return SkScalarInterp(values[right-1], values[right], fract);
+}
diff --git a/core/SkScaledImageCache.cpp b/core/SkScaledImageCache.cpp
new file mode 100644
index 00000000..75dac785
--- /dev/null
+++ b/core/SkScaledImageCache.cpp
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkScaledImageCache.h"
+#include "SkMipMap.h"
+#include "SkPixelRef.h"
+#include "SkRect.h"
+
+#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
+ #define SK_DEFAULT_IMAGE_CACHE_LIMIT (2 * 1024 * 1024)
+#endif
+
+
+ // Implemented from en.wikipedia.org/wiki/MurmurHash.
+static uint32_t compute_hash(const uint32_t data[], int count) {
+ uint32_t hash = 0;
+
+ for (int i = 0; i < count; ++i) {
+ uint32_t k = data[i];
+ k *= 0xcc9e2d51;
+ k = (k << 15) | (k >> 17);
+ k *= 0x1b873593;
+
+ hash ^= k;
+ hash = (hash << 13) | (hash >> 19);
+ hash *= 5;
+ hash += 0xe6546b64;
+ }
+
+ // hash ^= size;
+ hash ^= hash >> 16;
+ hash *= 0x85ebca6b;
+ hash ^= hash >> 13;
+ hash *= 0xc2b2ae35;
+ hash ^= hash >> 16;
+
+ return hash;
+}
+
+struct Key {
+ bool init(const SkBitmap& bm, SkScalar scaleX, SkScalar scaleY) {
+ SkPixelRef* pr = bm.pixelRef();
+ if (!pr) {
+ return false;
+ }
+
+ size_t offset = bm.pixelRefOffset();
+ size_t rowBytes = bm.rowBytes();
+ int x = (offset % rowBytes) >> 2;
+ int y = offset / rowBytes;
+
+ fGenID = pr->getGenerationID();
+ fBounds.set(x, y, x + bm.width(), y + bm.height());
+ fScaleX = scaleX;
+ fScaleY = scaleY;
+
+ fHash = compute_hash(&fGenID, 7);
+ return true;
+ }
+
+ bool operator<(const Key& other) const {
+ const uint32_t* a = &fGenID;
+ const uint32_t* b = &other.fGenID;
+ for (int i = 0; i < 7; ++i) {
+ if (a[i] < b[i]) {
+ return true;
+ }
+ if (a[i] > b[i]) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ bool operator==(const Key& other) const {
+ const uint32_t* a = &fHash;
+ const uint32_t* b = &other.fHash;
+ for (int i = 0; i < 8; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ uint32_t fHash;
+ uint32_t fGenID;
+ float fScaleX;
+ float fScaleY;
+ SkIRect fBounds;
+};
+
+struct SkScaledImageCache::Rec {
+ Rec(const Key& key, const SkBitmap& bm) : fKey(key), fBitmap(bm) {
+ fLockCount = 1;
+ fMip = NULL;
+ }
+
+ Rec(const Key& key, const SkMipMap* mip) : fKey(key) {
+ fLockCount = 1;
+ fMip = mip;
+ mip->ref();
+ }
+
+ ~Rec() {
+ SkSafeUnref(fMip);
+ }
+
+ size_t bytesUsed() const {
+ return fMip ? fMip->getSize() : fBitmap.getSize();
+ }
+
+ Rec* fNext;
+ Rec* fPrev;
+
+ // this guy wants to be 64bit aligned
+ Key fKey;
+
+ int32_t fLockCount;
+
+ // we use either fBitmap or fMip, but not both
+ SkBitmap fBitmap;
+ const SkMipMap* fMip;
+};
+
+#include "SkTDynamicHash.h"
+
+namespace { // can't use static functions w/ template parameters
+const Key& key_from_rec(const SkScaledImageCache::Rec& rec) {
+ return rec.fKey;
+}
+
+uint32_t hash_from_key(const Key& key) {
+ return key.fHash;
+}
+
+bool eq_rec_key(const SkScaledImageCache::Rec& rec, const Key& key) {
+ return rec.fKey == key;
+}
+}
+
+class SkScaledImageCache::Hash : public SkTDynamicHash<SkScaledImageCache::Rec,
+ Key, key_from_rec, hash_from_key,
+ eq_rec_key> {};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// experimental hash to speed things up
+#define USE_HASH
+
+SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
+ fHead = NULL;
+ fTail = NULL;
+#ifdef USE_HASH
+ fHash = new Hash;
+#else
+ fHash = NULL;
+#endif
+ fBytesUsed = 0;
+ fByteLimit = byteLimit;
+ fCount = 0;
+}
+
+SkScaledImageCache::~SkScaledImageCache() {
+ Rec* rec = fHead;
+ while (rec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+ delete fHash;
+}
+
+SkScaledImageCache::Rec* SkScaledImageCache::findAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY) {
+ Key key;
+ if (!key.init(orig, scaleX, scaleY)) {
+ return NULL;
+ }
+
+#ifdef USE_HASH
+ Rec* rec = fHash->find(key);
+#else
+ Rec* rec = fHead;
+ while (rec != NULL) {
+ if (rec->fKey == key) {
+ break;
+ }
+ rec = rec->fNext;
+ }
+#endif
+
+ if (rec) {
+ this->moveToHead(rec); // for our LRU
+ rec->fLockCount += 1;
+ }
+ return rec;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::findAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ SkBitmap* scaled) {
+ if (0 == scaleX || 0 == scaleY) {
+ // degenerate, and the key we use for mipmaps
+ return NULL;
+ }
+
+ Rec* rec = this->findAndLock(orig, scaleX, scaleY);
+ if (rec) {
+ SkASSERT(NULL == rec->fMip);
+ SkASSERT(rec->fBitmap.pixelRef());
+ *scaled = rec->fBitmap;
+ }
+ return (ID*)rec;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::findAndLockMip(const SkBitmap& orig,
+ SkMipMap const ** mip) {
+ Rec* rec = this->findAndLock(orig, 0, 0);
+ if (rec) {
+ SkASSERT(rec->fMip);
+ SkASSERT(NULL == rec->fBitmap.pixelRef());
+ *mip = rec->fMip;
+ }
+ return (ID*)rec;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::addAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ const SkBitmap& scaled) {
+ if (0 == scaleX || 0 == scaleY) {
+ // degenerate, and the key we use for mipmaps
+ return NULL;
+ }
+
+ Key key;
+ if (!key.init(orig, scaleX, scaleY)) {
+ return NULL;
+ }
+
+ Rec* rec = SkNEW_ARGS(Rec, (key, scaled));
+ this->addToHead(rec);
+ SkASSERT(1 == rec->fLockCount);
+
+#ifdef USE_HASH
+ fHash->add(rec);
+#endif
+
+ // We may (now) be overbudget, so see if we need to purge something.
+ this->purgeAsNeeded();
+ return (ID*)rec;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::addAndLockMip(const SkBitmap& orig,
+ const SkMipMap* mip) {
+ Key key;
+ if (!key.init(orig, 0, 0)) {
+ return NULL;
+ }
+
+ Rec* rec = SkNEW_ARGS(Rec, (key, mip));
+ this->addToHead(rec);
+ SkASSERT(1 == rec->fLockCount);
+
+#ifdef USE_HASH
+ fHash->add(rec);
+#endif
+
+ // We may (now) be overbudget, so see if we need to purge something.
+ this->purgeAsNeeded();
+ return (ID*)rec;
+}
+
+void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) {
+ SkASSERT(id);
+
+#ifdef SK_DEBUG
+ {
+ bool found = false;
+ Rec* rec = fHead;
+ while (rec != NULL) {
+ if ((ID*)rec == id) {
+ found = true;
+ break;
+ }
+ rec = rec->fNext;
+ }
+ SkASSERT(found);
+ }
+#endif
+ Rec* rec = (Rec*)id;
+ SkASSERT(rec->fLockCount > 0);
+ rec->fLockCount -= 1;
+
+ // we may have been over-budget, but now have released something, so check
+ // if we should purge.
+ if (0 == rec->fLockCount) {
+ this->purgeAsNeeded();
+ }
+}
+
+void SkScaledImageCache::purgeAsNeeded() {
+ size_t byteLimit = fByteLimit;
+ size_t bytesUsed = fBytesUsed;
+
+ Rec* rec = fTail;
+ while (rec) {
+ if (bytesUsed < byteLimit) {
+ break;
+ }
+ Rec* prev = rec->fPrev;
+ if (0 == rec->fLockCount) {
+ size_t used = rec->bytesUsed();
+ SkASSERT(used <= bytesUsed);
+ bytesUsed -= used;
+ this->detach(rec);
+#ifdef USE_HASH
+ fHash->remove(rec->fKey);
+#endif
+
+ SkDELETE(rec);
+ fCount -= 1;
+ }
+ rec = prev;
+ }
+ fBytesUsed = bytesUsed;
+}
+
+size_t SkScaledImageCache::setByteLimit(size_t newLimit) {
+ size_t prevLimit = fByteLimit;
+ fByteLimit = newLimit;
+ if (newLimit < prevLimit) {
+ this->purgeAsNeeded();
+ }
+ return prevLimit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScaledImageCache::detach(Rec* rec) {
+ Rec* prev = rec->fPrev;
+ Rec* next = rec->fNext;
+
+ if (!prev) {
+ SkASSERT(fHead == rec);
+ fHead = next;
+ } else {
+ prev->fNext = next;
+ }
+
+ if (!next) {
+ fTail = prev;
+ } else {
+ next->fPrev = prev;
+ }
+
+ rec->fNext = rec->fPrev = NULL;
+}
+
+void SkScaledImageCache::moveToHead(Rec* rec) {
+ if (fHead == rec) {
+ return;
+ }
+
+ SkASSERT(fHead);
+ SkASSERT(fTail);
+
+ this->validate();
+
+ this->detach(rec);
+
+ fHead->fPrev = rec;
+ rec->fNext = fHead;
+ fHead = rec;
+
+ this->validate();
+}
+
+void SkScaledImageCache::addToHead(Rec* rec) {
+ this->validate();
+
+ rec->fPrev = NULL;
+ rec->fNext = fHead;
+ if (fHead) {
+ fHead->fPrev = rec;
+ }
+ fHead = rec;
+ if (!fTail) {
+ fTail = rec;
+ }
+ fBytesUsed += rec->bytesUsed();
+ fCount += 1;
+
+ this->validate();
+}
+
+#ifdef SK_DEBUG
+void SkScaledImageCache::validate() const {
+ if (NULL == fHead) {
+ SkASSERT(NULL == fTail);
+ SkASSERT(0 == fBytesUsed);
+ return;
+ }
+
+ if (fHead == fTail) {
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL == fHead->fNext);
+ SkASSERT(fHead->bytesUsed() == fBytesUsed);
+ return;
+ }
+
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL != fHead->fNext);
+ SkASSERT(NULL == fTail->fNext);
+ SkASSERT(NULL != fTail->fPrev);
+
+ size_t used = 0;
+ int count = 0;
+ const Rec* rec = fHead;
+ while (rec) {
+ count += 1;
+ used += rec->bytesUsed();
+ SkASSERT(used <= fBytesUsed);
+ rec = rec->fNext;
+ }
+ SkASSERT(fCount == count);
+
+ rec = fTail;
+ while (rec) {
+ SkASSERT(count > 0);
+ count -= 1;
+ SkASSERT(used >= rec->bytesUsed());
+ used -= rec->bytesUsed();
+ rec = rec->fPrev;
+ }
+
+ SkASSERT(0 == count);
+ SkASSERT(0 == used);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkThread.h"
+
+SK_DECLARE_STATIC_MUTEX(gMutex);
+
+static SkScaledImageCache* get_cache() {
+ static SkScaledImageCache* gCache;
+ if (!gCache) {
+ gCache = SkNEW_ARGS(SkScaledImageCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
+ }
+ return gCache;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::FindAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ SkBitmap* scaled) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->findAndLock(orig, scaleX, scaleY, scaled);
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::FindAndLockMip(const SkBitmap& orig,
+ SkMipMap const ** mip) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->findAndLockMip(orig, mip);
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::AddAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ const SkBitmap& scaled) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->addAndLock(orig, scaleX, scaleY, scaled);
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::AddAndLockMip(const SkBitmap& orig,
+ const SkMipMap* mip) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->addAndLockMip(orig, mip);
+}
+
+void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->unlock(id);
+}
+
+size_t SkScaledImageCache::GetBytesUsed() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getBytesUsed();
+}
+
+size_t SkScaledImageCache::GetByteLimit() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getByteLimit();
+}
+
+size_t SkScaledImageCache::SetByteLimit(size_t newLimit) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->setByteLimit(newLimit);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGraphics.h"
+
+size_t SkGraphics::GetImageCacheBytesUsed() {
+ return SkScaledImageCache::GetBytesUsed();
+}
+
+size_t SkGraphics::GetImageCacheByteLimit() {
+ return SkScaledImageCache::GetByteLimit();
+}
+
+size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) {
+ return SkScaledImageCache::SetByteLimit(newLimit);
+}
diff --git a/core/SkScaledImageCache.h b/core/SkScaledImageCache.h
new file mode 100644
index 00000000..32474b7f
--- /dev/null
+++ b/core/SkScaledImageCache.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkScaledImageCache_DEFINED
+#define SkScaledImageCache_DEFINED
+
+#include "SkBitmap.h"
+
+class SkMipMap;
+
+/**
+ * Cache object for bitmaps (with possible scale in X Y as part of the key).
+ *
+ * Multiple caches can be instantiated, but each instance is not implicitly
+ * thread-safe, so if a given instance is to be shared across threads, the
+ * caller must manage the access itself (e.g. via a mutex).
+ *
+ * As a convenience, a global instance is also defined, which can be safely
+ * access across threads via the static methods (e.g. FindAndLock, etc.).
+ */
+class SkScaledImageCache {
+public:
+ struct ID;
+
+ /*
+ * The following static methods are thread-safe wrappers around a global
+ * instance of this cache.
+ */
+
+ static ID* FindAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, SkBitmap* scaled);
+ static ID* FindAndLockMip(const SkBitmap& original, SkMipMap const**);
+
+ static ID* AddAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, const SkBitmap& scaled);
+ static ID* AddAndLockMip(const SkBitmap& original, const SkMipMap*);
+
+ static void Unlock(ID*);
+
+ static size_t GetBytesUsed();
+ static size_t GetByteLimit();
+ static size_t SetByteLimit(size_t newLimit);
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ SkScaledImageCache(size_t byteLimit);
+ ~SkScaledImageCache();
+
+ /**
+ * Search the cache for a scaled version of original. If found, return it
+ * in scaled, and return its ID pointer. Use the returned ptr to unlock
+ * the cache when you are done using scaled.
+ *
+ * If a match is not found, scaled will be unmodifed, and NULL will be
+ * returned.
+ */
+ ID* findAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, SkBitmap* scaled);
+ ID* findAndLockMip(const SkBitmap& original, SkMipMap const**);
+
+ /**
+ * To add a new (scaled) bitmap to the cache, call AddAndLock. Use the
+ * returned ptr to unlock the cache when you are done using scaled.
+ */
+ ID* addAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, const SkBitmap& scaled);
+ ID* addAndLockMip(const SkBitmap& original, const SkMipMap*);
+
+ /**
+ * Given a non-null ID ptr returned by either findAndLock or addAndLock,
+ * this releases the associated resources to be available to be purged
+ * if needed. After this, the cached bitmap should no longer be
+ * referenced by the caller.
+ */
+ void unlock(ID*);
+
+ size_t getBytesUsed() const { return fBytesUsed; }
+ size_t getByteLimit() const { return fByteLimit; }
+
+ /**
+ * Set the maximum number of bytes available to this cache. If the current
+ * cache exceeds this new value, it will be purged to try to fit within
+ * this new limit.
+ */
+ size_t setByteLimit(size_t newLimit);
+
+public:
+ struct Rec;
+private:
+ Rec* fHead;
+ Rec* fTail;
+
+ class Hash;
+ Hash* fHash;
+
+ size_t fBytesUsed;
+ size_t fByteLimit;
+ int fCount;
+
+ Rec* findAndLock(const SkBitmap& original, SkScalar sx, SkScalar sy);
+
+ void purgeAsNeeded();
+
+ // linklist management
+ void moveToHead(Rec*);
+ void addToHead(Rec*);
+ void detach(Rec*);
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+};
+
+#endif
diff --git a/core/SkScalerContext.cpp b/core/SkScalerContext.cpp
new file mode 100644
index 00000000..ee9d9194
--- /dev/null
+++ b/core/SkScalerContext.cpp
@@ -0,0 +1,941 @@
+
+/*
+ * 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 "SkScalerContext.h"
+#include "SkColorPriv.h"
+#include "SkDescriptor.h"
+#include "SkDraw.h"
+#include "SkFontHost.h"
+#include "SkGlyph.h"
+#include "SkMaskFilter.h"
+#include "SkMaskGamma.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPathEffect.h"
+#include "SkRasterizer.h"
+#include "SkRasterClip.h"
+#include "SkStroke.h"
+#include "SkThread.h"
+
+#ifdef SK_BUILD_FOR_ANDROID
+ #include "SkTypeface_android.h"
+#endif
+
+#define ComputeBWRowBytes(width) (((unsigned)(width) + 7) >> 3)
+
+void SkGlyph::toMask(SkMask* mask) const {
+ SkASSERT(mask);
+
+ mask->fImage = (uint8_t*)fImage;
+ mask->fBounds.set(fLeft, fTop, fLeft + fWidth, fTop + fHeight);
+ mask->fRowBytes = this->rowBytes();
+ mask->fFormat = static_cast<SkMask::Format>(fMaskFormat);
+}
+
+size_t SkGlyph::computeImageSize() const {
+ const size_t size = this->rowBytes() * fHeight;
+
+ switch (fMaskFormat) {
+ case SkMask::k3D_Format:
+ return 3 * size;
+ default:
+ return size;
+ }
+}
+
+void SkGlyph::zeroMetrics() {
+ fAdvanceX = 0;
+ fAdvanceY = 0;
+ fWidth = 0;
+ fHeight = 0;
+ fTop = 0;
+ fLeft = 0;
+ fRsbDelta = 0;
+ fLsbDelta = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+ #define DUMP_RECx
+#endif
+
+static SkFlattenable* load_flattenable(const SkDescriptor* desc, uint32_t tag) {
+ SkFlattenable* obj = NULL;
+ uint32_t len;
+ const void* data = desc->findEntry(tag, &len);
+
+ if (data) {
+ SkOrderedReadBuffer buffer(data, len);
+ obj = buffer.readFlattenable();
+ SkASSERT(buffer.offset() == buffer.size());
+ }
+ return obj;
+}
+
+SkScalerContext::SkScalerContext(SkTypeface* typeface, const SkDescriptor* desc)
+ : fRec(*static_cast<const Rec*>(desc->findEntry(kRec_SkDescriptorTag, NULL)))
+
+ , fBaseGlyphCount(0)
+ , fTypeface(SkRef(typeface))
+ , 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.
+ , fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != NULL || fRasterizer != NULL)
+
+ , fNextContext(NULL)
+
+ , fPreBlend(fMaskFilter ? SkMaskGamma::PreBlend() : SkScalerContext::GetMaskPreBlend(fRec))
+ , fPreBlendForFilter(fMaskFilter ? SkScalerContext::GetMaskPreBlend(fRec)
+ : SkMaskGamma::PreBlend())
+{
+#ifdef DUMP_REC
+ desc->assertChecksum();
+ SkDebugf("SkScalarContext checksum %x count %d length %d\n",
+ desc->getChecksum(), desc->getCount(), desc->getLength());
+ SkDebugf(" textsize %g prescale %g preskew %g post [%g %g %g %g]\n",
+ rec->fTextSize, rec->fPreScaleX, rec->fPreSkewX, rec->fPost2x2[0][0],
+ rec->fPost2x2[0][1], rec->fPost2x2[1][0], rec->fPost2x2[1][1]);
+ SkDebugf(" frame %g miter %g hints %d framefill %d format %d join %d\n",
+ rec->fFrameWidth, rec->fMiterLimit, rec->fHints, rec->fFrameAndFill,
+ rec->fMaskFormat, rec->fStrokeJoin);
+ SkDebugf(" pathEffect %x maskFilter %x\n",
+ desc->findEntry(kPathEffect_SkDescriptorTag, NULL),
+ desc->findEntry(kMaskFilter_SkDescriptorTag, NULL));
+#endif
+#ifdef SK_BUILD_FOR_ANDROID
+ uint32_t len;
+ const void* data = desc->findEntry(kAndroidOpts_SkDescriptorTag, &len);
+ if (data) {
+ SkOrderedReadBuffer buffer(data, len);
+ fPaintOptionsAndroid.unflatten(buffer);
+ SkASSERT(buffer.offset() == buffer.size());
+ }
+#endif
+}
+
+SkScalerContext::~SkScalerContext() {
+ SkDELETE(fNextContext);
+
+ SkSafeUnref(fPathEffect);
+ SkSafeUnref(fMaskFilter);
+ SkSafeUnref(fRasterizer);
+}
+
+// Return the context associated with the next logical typeface, or NULL if
+// there are no more entries in the fallback chain.
+SkScalerContext* SkScalerContext::allocNextContext() const {
+#ifdef SK_BUILD_FOR_ANDROID
+ SkTypeface* newFace = SkAndroidNextLogicalTypeface(fRec.fFontID,
+ fRec.fOrigFontID,
+ fPaintOptionsAndroid);
+ if (0 == newFace) {
+ return NULL;
+ }
+
+ SkAutoTUnref<SkTypeface> aur(newFace);
+ uint32_t newFontID = newFace->uniqueID();
+
+ SkOrderedWriteBuffer androidBuffer(128);
+ fPaintOptionsAndroid.flatten(androidBuffer);
+
+ SkAutoDescriptor ad(sizeof(fRec) + androidBuffer.size() + SkDescriptor::ComputeOverhead(2));
+ SkDescriptor* desc = ad.getDesc();
+
+ desc->init();
+ SkScalerContext::Rec* newRec =
+ (SkScalerContext::Rec*)desc->addEntry(kRec_SkDescriptorTag,
+ sizeof(fRec), &fRec);
+ androidBuffer.writeToMemory(desc->addEntry(kAndroidOpts_SkDescriptorTag,
+ androidBuffer.size(), NULL));
+
+ newRec->fFontID = newFontID;
+ desc->computeChecksum();
+
+ return newFace->createScalerContext(desc);
+#else
+ return NULL;
+#endif
+}
+
+/* Return the next context, creating it if its not already created, but return
+ NULL if the fonthost says there are no more fonts to fallback to.
+ */
+SkScalerContext* SkScalerContext::getNextContext() {
+ SkScalerContext* next = fNextContext;
+ // if next is null, then either it isn't cached yet, or we're at the
+ // end of our possible chain
+ if (NULL == next) {
+ next = this->allocNextContext();
+ if (NULL == next) {
+ return NULL;
+ }
+ // next's base is our base + our local count
+ next->setBaseGlyphCount(fBaseGlyphCount + this->getGlyphCount());
+ // cache the answer
+ fNextContext = next;
+ }
+ return next;
+}
+
+SkScalerContext* SkScalerContext::getGlyphContext(const SkGlyph& glyph) {
+ unsigned glyphID = glyph.getGlyphID();
+ SkScalerContext* ctx = this;
+ for (;;) {
+ unsigned count = ctx->getGlyphCount();
+ if (glyphID < count) {
+ break;
+ }
+ glyphID -= count;
+ ctx = ctx->getNextContext();
+ if (NULL == ctx) {
+// SkDebugf("--- no context for glyph %x\n", glyph.getGlyphID());
+ // just return the original context (this)
+ return this;
+ }
+ }
+ return ctx;
+}
+
+SkScalerContext* SkScalerContext::getContextFromChar(SkUnichar uni,
+ uint16_t* glyphID) {
+ SkScalerContext* ctx = this;
+ for (;;) {
+ const uint16_t glyph = ctx->generateCharToGlyph(uni);
+ if (glyph) {
+ if (NULL != glyphID) {
+ *glyphID = glyph;
+ }
+ break; // found it
+ }
+ ctx = ctx->getNextContext();
+ if (NULL == ctx) {
+ return NULL;
+ }
+ }
+ return ctx;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+SkFontID SkScalerContext::findTypefaceIdForChar(SkUnichar uni) {
+ SkScalerContext* ctx = this->getContextFromChar(uni, NULL);
+ if (NULL != ctx) {
+ return ctx->fRec.fFontID;
+ } else {
+ return 0;
+ }
+}
+
+/* This loops through all available fallback contexts (if needed) until it
+ finds some context that can handle the unichar and return it.
+
+ As this is somewhat expensive operation, it should only be done on the first
+ char of a run.
+ */
+unsigned SkScalerContext::getBaseGlyphCount(SkUnichar uni) {
+ SkScalerContext* ctx = this->getContextFromChar(uni, NULL);
+ if (NULL != ctx) {
+ return ctx->fBaseGlyphCount;
+ } else {
+ SkDEBUGF(("--- no context for char %x\n", uni));
+ return this->fBaseGlyphCount;
+ }
+}
+#endif
+
+/* This loops through all available fallback contexts (if needed) until it
+ finds some context that can handle the unichar. If all fail, returns 0
+ */
+uint16_t SkScalerContext::charToGlyphID(SkUnichar uni) {
+
+ uint16_t tempID;
+ SkScalerContext* ctx = this->getContextFromChar(uni, &tempID);
+ if (NULL == ctx) {
+ return 0; // no more contexts, return missing glyph
+ }
+ // add the ctx's base, making glyphID unique for chain of contexts
+ unsigned glyphID = tempID + ctx->fBaseGlyphCount;
+ // check for overflow of 16bits, since our glyphID cannot exceed that
+ if (glyphID > 0xFFFF) {
+ glyphID = 0;
+ }
+ return SkToU16(glyphID);
+}
+
+SkUnichar SkScalerContext::glyphIDToChar(uint16_t glyphID) {
+ SkScalerContext* ctx = this;
+ unsigned rangeEnd = 0;
+ do {
+ unsigned rangeStart = rangeEnd;
+
+ rangeEnd += ctx->getGlyphCount();
+ if (rangeStart <= glyphID && glyphID < rangeEnd) {
+ return ctx->generateGlyphToChar(glyphID - rangeStart);
+ }
+ ctx = ctx->getNextContext();
+ } while (NULL != ctx);
+ return 0;
+}
+
+void SkScalerContext::getAdvance(SkGlyph* glyph) {
+ // mark us as just having a valid advance
+ glyph->fMaskFormat = MASK_FORMAT_JUST_ADVANCE;
+ // we mark the format before making the call, in case the impl
+ // internally ends up calling its generateMetrics, which is OK
+ // albeit slower than strictly necessary
+ this->getGlyphContext(*glyph)->generateAdvance(glyph);
+}
+
+void SkScalerContext::getMetrics(SkGlyph* glyph) {
+ this->getGlyphContext(*glyph)->generateMetrics(glyph);
+
+ // for now we have separate cache entries for devkerning on and off
+ // in the future we might share caches, but make our measure/draw
+ // code make the distinction. Thus we zap the values if the caller
+ // has not asked for them.
+ if ((fRec.fFlags & SkScalerContext::kDevKernText_Flag) == 0) {
+ // no devkern, so zap the fields
+ glyph->fLsbDelta = glyph->fRsbDelta = 0;
+ }
+
+ // if either dimension is empty, zap the image bounds of the glyph
+ if (0 == glyph->fWidth || 0 == glyph->fHeight) {
+ glyph->fWidth = 0;
+ glyph->fHeight = 0;
+ glyph->fTop = 0;
+ glyph->fLeft = 0;
+ glyph->fMaskFormat = 0;
+ return;
+ }
+
+ if (fGenerateImageFromPath) {
+ SkPath devPath, fillPath;
+ SkMatrix fillToDevMatrix;
+
+ this->internalGetPath(*glyph, &fillPath, &devPath, &fillToDevMatrix);
+
+ if (fRasterizer) {
+ SkMask mask;
+
+ if (fRasterizer->rasterize(fillPath, fillToDevMatrix, NULL,
+ fMaskFilter, &mask,
+ SkMask::kJustComputeBounds_CreateMode)) {
+ glyph->fLeft = mask.fBounds.fLeft;
+ glyph->fTop = mask.fBounds.fTop;
+ glyph->fWidth = SkToU16(mask.fBounds.width());
+ glyph->fHeight = SkToU16(mask.fBounds.height());
+ } else {
+ goto SK_ERROR;
+ }
+ } else {
+ // just use devPath
+ SkIRect ir;
+ devPath.getBounds().roundOut(&ir);
+
+ if (ir.isEmpty() || !ir.is16Bit()) {
+ goto SK_ERROR;
+ }
+ glyph->fLeft = ir.fLeft;
+ glyph->fTop = ir.fTop;
+ glyph->fWidth = SkToU16(ir.width());
+ glyph->fHeight = SkToU16(ir.height());
+
+ if (glyph->fWidth > 0) {
+ switch (fRec.fMaskFormat) {
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format:
+ glyph->fWidth += 2;
+ glyph->fLeft -= 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (SkMask::kARGB32_Format != glyph->fMaskFormat) {
+ glyph->fMaskFormat = fRec.fMaskFormat;
+ }
+
+ // If we are going to create the mask, then we cannot keep the color
+ if ((fGenerateImageFromPath || fMaskFilter) &&
+ SkMask::kARGB32_Format == glyph->fMaskFormat) {
+ glyph->fMaskFormat = SkMask::kA8_Format;
+ }
+
+ if (fMaskFilter) {
+ SkMask src, dst;
+ SkMatrix matrix;
+
+ glyph->toMask(&src);
+ fRec.getMatrixFrom2x2(&matrix);
+
+ src.fImage = NULL; // only want the bounds from the filter
+ if (fMaskFilter->filterMask(&dst, src, matrix, NULL)) {
+ if (dst.fBounds.isEmpty() || !dst.fBounds.is16Bit()) {
+ goto SK_ERROR;
+ }
+ SkASSERT(dst.fImage == NULL);
+ glyph->fLeft = dst.fBounds.fLeft;
+ glyph->fTop = dst.fBounds.fTop;
+ glyph->fWidth = SkToU16(dst.fBounds.width());
+ glyph->fHeight = SkToU16(dst.fBounds.height());
+ glyph->fMaskFormat = dst.fFormat;
+ }
+ }
+ return;
+
+SK_ERROR:
+ // draw nothing 'cause we failed
+ glyph->fLeft = 0;
+ glyph->fTop = 0;
+ glyph->fWidth = 0;
+ glyph->fHeight = 0;
+ // put a valid value here, in case it was earlier set to
+ // MASK_FORMAT_JUST_ADVANCE
+ glyph->fMaskFormat = fRec.fMaskFormat;
+}
+
+#define SK_SHOW_TEXT_BLIT_COVERAGE 0
+
+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 pack4xHToLCD16(const SkBitmap& src, const SkMask& dst,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
+#define SAMPLES_PER_PIXEL 4
+#define LCD_PER_PIXEL 3
+ SkASSERT(SkBitmap::kA8_Config == src.config());
+ SkASSERT(SkMask::kLCD16_Format == dst.fFormat);
+
+ const int sample_width = src.width();
+ const int height = src.height();
+
+ uint16_t* dstP = (uint16_t*)dst.fImage;
+ size_t dstRB = dst.fRowBytes;
+ // An N tap FIR is defined by
+ // out[n] = coeff[0]*x[n] + coeff[1]*x[n-1] + ... + coeff[N]*x[n-N]
+ // or
+ // out[n] = sum(i, 0, N, coeff[i]*x[n-i])
+
+ // The strategy is to use one FIR (different coefficients) for each of r, g, and b.
+ // This means using every 4th FIR output value of each FIR and discarding the rest.
+ // The FIRs are aligned, and the coefficients reach 5 samples to each side of their 'center'.
+ // (For r and b this is technically incorrect, but the coeffs outside round to zero anyway.)
+
+ // These are in some fixed point repesentation.
+ // Adding up to more than one simulates ink spread.
+ // For implementation reasons, these should never add up to more than two.
+
+ // Coefficients determined by a gausian where 5 samples = 3 std deviations (0x110 'contrast').
+ // Calculated using tools/generate_fir_coeff.py
+ // With this one almost no fringing is ever seen, but it is imperceptibly blurry.
+ // The lcd smoothed text is almost imperceptibly different from gray,
+ // but is still sharper on small stems and small rounded corners than gray.
+ // This also seems to be about as wide as one can get and only have a three pixel kernel.
+ // TODO: caculate these at runtime so parameters can be adjusted (esp contrast).
+ static const unsigned int coefficients[LCD_PER_PIXEL][SAMPLES_PER_PIXEL*3] = {
+ //The red subpixel is centered inside the first sample (at 1/6 pixel), and is shifted.
+ { 0x03, 0x0b, 0x1c, 0x33, 0x40, 0x39, 0x24, 0x10, 0x05, 0x01, 0x00, 0x00, },
+ //The green subpixel is centered between two samples (at 1/2 pixel), so is symetric
+ { 0x00, 0x02, 0x08, 0x16, 0x2b, 0x3d, 0x3d, 0x2b, 0x16, 0x08, 0x02, 0x00, },
+ //The blue subpixel is centered inside the last sample (at 5/6 pixel), and is shifted.
+ { 0x00, 0x00, 0x01, 0x05, 0x10, 0x24, 0x39, 0x40, 0x33, 0x1c, 0x0b, 0x03, },
+ };
+
+ for (int y = 0; y < height; ++y) {
+ const uint8_t* srcP = src.getAddr8(0, y);
+
+ // TODO: this fir filter implementation is straight forward, but slow.
+ // It should be possible to make it much faster.
+ for (int sample_x = -4, pixel_x = 0; sample_x < sample_width + 4; sample_x += 4, ++pixel_x) {
+ int fir[LCD_PER_PIXEL] = { 0 };
+ for (int sample_index = SkMax32(0, sample_x - 4), coeff_index = sample_index - (sample_x - 4)
+ ; sample_index < SkMin32(sample_x + 8, sample_width)
+ ; ++sample_index, ++coeff_index)
+ {
+ int sample_value = srcP[sample_index];
+ for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
+ fir[subpxl_index] += coefficients[subpxl_index][coeff_index] * sample_value;
+ }
+ }
+ for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
+ fir[subpxl_index] /= 0x100;
+ fir[subpxl_index] = SkMin32(fir[subpxl_index], 255);
+ }
+
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(fir[0], maskPreBlend.fR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(fir[1], maskPreBlend.fG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(fir[2], maskPreBlend.fB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
+ dstP[pixel_x] = SkPack888ToRGB16(r, g, b);
+ }
+ dstP = (uint16_t*)((char*)dstP + dstRB);
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void pack4xHToLCD32(const SkBitmap& src, const SkMask& dst,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
+ SkASSERT(SkBitmap::kA8_Config == src.config());
+ SkASSERT(SkMask::kLCD32_Format == dst.fFormat);
+
+ const int width = dst.fBounds.width();
+ const int height = dst.fBounds.height();
+ SkPMColor* dstP = (SkPMColor*)dst.fImage;
+ size_t dstRB = dst.fRowBytes;
+
+ for (int y = 0; y < height; ++y) {
+ const uint8_t* srcP = src.getAddr8(0, y);
+
+ // TODO: need to use fir filter here as well.
+ for (int x = 0; x < width; ++x) {
+ 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,
+ const SkMaskGamma::PreBlend& maskPreBlend) {
+ SkBitmap::Config config;
+ SkPaint paint;
+
+ int srcW = mask.fBounds.width();
+ int srcH = mask.fBounds.height();
+ int dstW = srcW;
+ int dstH = srcH;
+ int dstRB = mask.fRowBytes;
+
+ SkMatrix matrix;
+ matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
+ -SkIntToScalar(mask.fBounds.fTop));
+
+ if (SkMask::kBW_Format == mask.fFormat) {
+ config = SkBitmap::kA1_Config;
+ paint.setAntiAlias(false);
+ } else {
+ config = SkBitmap::kA8_Config;
+ paint.setAntiAlias(true);
+ switch (mask.fFormat) {
+ case SkMask::kA8_Format:
+ break;
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format:
+ // TODO: trigger off LCD orientation
+ dstW = 4*dstW - 8;
+ matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft + 1),
+ -SkIntToScalar(mask.fBounds.fTop));
+ matrix.postScale(SkIntToScalar(4), SK_Scalar1);
+ dstRB = 0; // signals we need a copy
+ break;
+ default:
+ SkDEBUGFAIL("unexpected mask format");
+ }
+ }
+
+ SkRasterClip clip;
+ clip.setRect(SkIRect::MakeWH(dstW, dstH));
+
+ SkBitmap bm;
+ bm.setConfig(config, dstW, dstH, dstRB);
+
+ if (0 == dstRB) {
+ 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);
+ }
+ sk_bzero(bm.getPixels(), bm.getSafeSize());
+
+ SkDraw draw;
+ draw.fRC = &clip;
+ draw.fClip = &clip.bwRgn();
+ draw.fMatrix = &matrix;
+ draw.fBitmap = &bm;
+ draw.drawPath(path, paint);
+
+ switch (mask.fFormat) {
+ case SkMask::kA8_Format:
+ if (maskPreBlend.isApplicable()) {
+ applyLUTToA8Mask(mask, maskPreBlend.fG);
+ }
+ break;
+ case SkMask::kLCD16_Format:
+ if (maskPreBlend.isApplicable()) {
+ pack4xHToLCD16<true>(bm, mask, maskPreBlend);
+ } else {
+ pack4xHToLCD16<false>(bm, mask, maskPreBlend);
+ }
+ break;
+ case SkMask::kLCD32_Format:
+ if (maskPreBlend.isApplicable()) {
+ pack4xHToLCD32<true>(bm, mask, maskPreBlend);
+ } else {
+ pack4xHToLCD32<false>(bm, mask, maskPreBlend);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void extract_alpha(const SkMask& dst,
+ const SkPMColor* srcRow, size_t srcRB) {
+ int width = dst.fBounds.width();
+ int height = dst.fBounds.height();
+ int dstRB = dst.fRowBytes;
+ uint8_t* dstRow = dst.fImage;
+
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ dstRow[x] = SkGetPackedA32(srcRow[x]);
+ }
+ // zero any padding on each row
+ for (int x = width; x < dstRB; ++x) {
+ dstRow[x] = 0;
+ }
+ dstRow += dstRB;
+ srcRow = (const SkPMColor*)((const char*)srcRow + srcRB);
+ }
+}
+
+void SkScalerContext::getImage(const SkGlyph& origGlyph) {
+ const SkGlyph* glyph = &origGlyph;
+ SkGlyph tmpGlyph;
+
+ // in case we need to call generateImage on a mask-format that is different
+ // (i.e. larger) than what our caller allocated by looking at origGlyph.
+ SkAutoMalloc tmpGlyphImageStorage;
+
+ // If we are going to draw-from-path, then we cannot generate color, since
+ // the path only makes a mask. This case should have been caught up in
+ // generateMetrics().
+ SkASSERT(!fGenerateImageFromPath ||
+ SkMask::kARGB32_Format != origGlyph.fMaskFormat);
+
+ if (fMaskFilter) { // restore the prefilter bounds
+ tmpGlyph.init(origGlyph.fID);
+
+ // need the original bounds, sans our maskfilter
+ SkMaskFilter* mf = fMaskFilter;
+ fMaskFilter = NULL; // temp disable
+ this->getMetrics(&tmpGlyph);
+ fMaskFilter = mf; // restore
+
+ // we need the prefilter bounds to be <= filter bounds
+ SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth);
+ SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight);
+
+ if (tmpGlyph.fMaskFormat == origGlyph.fMaskFormat) {
+ tmpGlyph.fImage = origGlyph.fImage;
+ } else {
+ tmpGlyphImageStorage.reset(tmpGlyph.computeImageSize());
+ tmpGlyph.fImage = tmpGlyphImageStorage.get();
+ }
+ glyph = &tmpGlyph;
+ }
+
+ if (fGenerateImageFromPath) {
+ SkPath devPath, fillPath;
+ SkMatrix fillToDevMatrix;
+ SkMask mask;
+
+ this->internalGetPath(*glyph, &fillPath, &devPath, &fillToDevMatrix);
+ glyph->toMask(&mask);
+
+ if (fRasterizer) {
+ mask.fFormat = SkMask::kA8_Format;
+ sk_bzero(glyph->fImage, mask.computeImageSize());
+
+ if (!fRasterizer->rasterize(fillPath, fillToDevMatrix, NULL,
+ fMaskFilter, &mask,
+ SkMask::kJustRenderImage_CreateMode)) {
+ return;
+ }
+ if (fPreBlend.isApplicable()) {
+ applyLUTToA8Mask(mask, fPreBlend.fG);
+ }
+ } else {
+ SkASSERT(SkMask::kARGB32_Format != mask.fFormat);
+ generateMask(mask, devPath, fPreBlend);
+ }
+ } else {
+ this->getGlyphContext(*glyph)->generateImage(*glyph);
+ }
+
+ if (fMaskFilter) {
+ SkMask srcM, dstM;
+ SkMatrix matrix;
+
+ // the src glyph image shouldn't be 3D
+ SkASSERT(SkMask::k3D_Format != glyph->fMaskFormat);
+
+ SkAutoSMalloc<32*32> a8storage;
+ glyph->toMask(&srcM);
+ if (SkMask::kARGB32_Format == srcM.fFormat) {
+ // now we need to extract the alpha-channel from the glyph's image
+ // and copy it into a temp buffer, and then point srcM at that temp.
+ srcM.fFormat = SkMask::kA8_Format;
+ srcM.fRowBytes = SkAlign4(srcM.fBounds.width());
+ size_t size = srcM.computeImageSize();
+ a8storage.reset(size);
+ srcM.fImage = (uint8_t*)a8storage.get();
+ extract_alpha(srcM,
+ (const SkPMColor*)glyph->fImage, glyph->rowBytes());
+ }
+
+ fRec.getMatrixFrom2x2(&matrix);
+
+ if (fMaskFilter->filterMask(&dstM, srcM, matrix, NULL)) {
+ int width = SkFastMin32(origGlyph.fWidth, dstM.fBounds.width());
+ int height = SkFastMin32(origGlyph.fHeight, dstM.fBounds.height());
+ int dstRB = origGlyph.rowBytes();
+ int srcRB = dstM.fRowBytes;
+
+ const uint8_t* src = (const uint8_t*)dstM.fImage;
+ uint8_t* dst = (uint8_t*)origGlyph.fImage;
+
+ if (SkMask::k3D_Format == dstM.fFormat) {
+ // we have to copy 3 times as much
+ height *= 3;
+ }
+
+ // clean out our glyph, since it may be larger than dstM
+ //sk_bzero(dst, height * dstRB);
+
+ while (--height >= 0) {
+ memcpy(dst, src, width);
+ src += srcRB;
+ dst += dstRB;
+ }
+ SkMask::FreeImage(dstM.fImage);
+
+ if (fPreBlendForFilter.isApplicable()) {
+ applyLUTToA8Mask(srcM, fPreBlendForFilter.fG);
+ }
+ }
+ }
+}
+
+void SkScalerContext::getPath(const SkGlyph& glyph, SkPath* path) {
+ this->internalGetPath(glyph, NULL, path, NULL);
+}
+
+void SkScalerContext::getFontMetrics(SkPaint::FontMetrics* fm) {
+ // All of this complexity should go away when we change generateFontMetrics
+ // to just take one parameter (since it knows if it is vertical or not)
+ SkPaint::FontMetrics* mx = NULL;
+ SkPaint::FontMetrics* my = NULL;
+ if (fRec.fFlags & kVertical_Flag) {
+ mx = fm;
+ } else {
+ my = fm;
+ }
+ this->generateFontMetrics(mx, my);
+}
+
+SkUnichar SkScalerContext::generateGlyphToChar(uint16_t glyph) {
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath,
+ SkPath* devPath, SkMatrix* fillToDevMatrix) {
+ SkPath path;
+
+ this->getGlyphContext(glyph)->generatePath(glyph, &path);
+
+ if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
+ SkFixed dx = glyph.getSubXFixed();
+ SkFixed dy = glyph.getSubYFixed();
+ if (dx | dy) {
+ path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy));
+ }
+ }
+
+ if (fRec.fFrameWidth > 0 || fPathEffect != NULL) {
+ // need the path in user-space, with only the point-size applied
+ // so that our stroking and effects will operate the same way they
+ // would if the user had extracted the path themself, and then
+ // called drawPath
+ SkPath localPath;
+ SkMatrix matrix, inverse;
+
+ fRec.getMatrixFrom2x2(&matrix);
+ if (!matrix.invert(&inverse)) {
+ // assume fillPath and devPath are already empty.
+ return;
+ }
+ path.transform(inverse, &localPath);
+ // now localPath is only affected by the paint settings, and not the canvas matrix
+
+ SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
+
+ if (fRec.fFrameWidth > 0) {
+ rec.setStrokeStyle(fRec.fFrameWidth,
+ SkToBool(fRec.fFlags & kFrameAndFill_Flag));
+ // glyphs are always closed contours, so cap type is ignored,
+ // so we just pass something.
+ rec.setStrokeParams(SkPaint::kButt_Cap,
+ (SkPaint::Join)fRec.fStrokeJoin,
+ fRec.fMiterLimit);
+ }
+
+ if (fPathEffect) {
+ SkPath effectPath;
+ if (fPathEffect->filterPath(&effectPath, localPath, &rec, NULL)) {
+ localPath.swap(effectPath);
+ }
+ }
+
+ if (rec.needToApply()) {
+ SkPath strokePath;
+ if (rec.applyToPath(&strokePath, localPath)) {
+ localPath.swap(strokePath);
+ }
+ }
+
+ // now return stuff to the caller
+ if (fillToDevMatrix) {
+ *fillToDevMatrix = matrix;
+ }
+ if (devPath) {
+ localPath.transform(matrix, devPath);
+ }
+ if (fillPath) {
+ fillPath->swap(localPath);
+ }
+ } else { // nothing tricky to do
+ if (fillToDevMatrix) {
+ fillToDevMatrix->reset();
+ }
+ if (devPath) {
+ if (fillPath == NULL) {
+ devPath->swap(path);
+ } else {
+ *devPath = path;
+ }
+ }
+
+ if (fillPath) {
+ fillPath->swap(path);
+ }
+ }
+
+ if (devPath) {
+ devPath->updateBoundsCache();
+ }
+ if (fillPath) {
+ fillPath->updateBoundsCache();
+ }
+}
+
+
+void SkScalerContextRec::getMatrixFrom2x2(SkMatrix* dst) const {
+ dst->setAll(fPost2x2[0][0], fPost2x2[0][1], 0,
+ fPost2x2[1][0], fPost2x2[1][1], 0,
+ 0, 0, SkScalarToPersp(SK_Scalar1));
+}
+
+void SkScalerContextRec::getLocalMatrix(SkMatrix* m) const {
+ SkPaint::SetTextMatrix(m, fTextSize, fPreScaleX, fPreSkewX);
+}
+
+void SkScalerContextRec::getSingleMatrix(SkMatrix* m) const {
+ this->getLocalMatrix(m);
+
+ // now concat the device matrix
+ SkMatrix deviceMatrix;
+ this->getMatrixFrom2x2(&deviceMatrix);
+ m->postConcat(deviceMatrix);
+}
+
+SkAxisAlignment SkComputeAxisAlignmentForHText(const SkMatrix& matrix) {
+ SkASSERT(!matrix.hasPerspective());
+
+ if (0 == matrix[SkMatrix::kMSkewY]) {
+ return kX_SkAxisAlignment;
+ }
+ if (0 == matrix[SkMatrix::kMScaleX]) {
+ return kY_SkAxisAlignment;
+ }
+ return kNone_SkAxisAlignment;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontHost.h"
+
+class SkScalerContext_Empty : public SkScalerContext {
+public:
+ SkScalerContext_Empty(SkTypeface* face, const SkDescriptor* desc)
+ : SkScalerContext(face, desc) {}
+
+protected:
+ virtual unsigned generateGlyphCount() SK_OVERRIDE {
+ return 0;
+ }
+ virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE {
+ return 0;
+ }
+ virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE {
+ glyph->zeroMetrics();
+ }
+ virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE {
+ glyph->zeroMetrics();
+ }
+ 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 {
+ if (mx) {
+ sk_bzero(mx, sizeof(*mx));
+ }
+ if (my) {
+ sk_bzero(my, sizeof(*my));
+ }
+ }
+};
+
+extern SkScalerContext* SkCreateColorScalerContext(const SkDescriptor* desc);
+
+SkScalerContext* SkTypeface::createScalerContext(const SkDescriptor* desc,
+ bool allowFailure) const {
+ SkScalerContext* c = this->onCreateScalerContext(desc);
+
+ if (!c && !allowFailure) {
+ c = SkNEW_ARGS(SkScalerContext_Empty,
+ (const_cast<SkTypeface*>(this), desc));
+ }
+ return c;
+}
diff --git a/core/SkScalerContext.h b/core/SkScalerContext.h
new file mode 100644
index 00000000..52707a38
--- /dev/null
+++ b/core/SkScalerContext.h
@@ -0,0 +1,308 @@
+/*
+ * 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.
+ */
+
+#ifndef SkScalerContext_DEFINED
+#define SkScalerContext_DEFINED
+
+#include "SkMask.h"
+#include "SkMaskGamma.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkTypeface.h"
+
+#ifdef SK_BUILD_FOR_ANDROID
+ #include "SkPaintOptionsAndroid.h"
+#endif
+
+struct SkGlyph;
+class SkDescriptor;
+class SkMaskFilter;
+class SkPathEffect;
+class SkRasterizer;
+
+/*
+ * To allow this to be forward-declared, it must be its own typename, rather
+ * than a nested struct inside SkScalerContext (where it started).
+ */
+struct SkScalerContextRec {
+ uint32_t fOrigFontID;
+ uint32_t fFontID;
+ SkScalar fTextSize, fPreScaleX, fPreSkewX;
+ SkScalar fPost2x2[2][2];
+ SkScalar fFrameWidth, fMiterLimit;
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ SkScalar fHintingScaleFactor;
+#endif
+
+ //These describe the parameters to create (uniquely identify) the pre-blend.
+ uint32_t fLumBits;
+ uint8_t fDeviceGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB
+ uint8_t fPaintGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB
+ uint8_t fContrast; //0.8+1, [0.0, 1.0] artificial contrast
+ uint8_t fReservedAlign;
+
+ SkScalar getDeviceGamma() const {
+ return SkIntToScalar(fDeviceGamma) / (1 << 6);
+ }
+ void setDeviceGamma(SkScalar dg) {
+ SkASSERT(0 <= dg && dg < SkIntToScalar(4));
+ fDeviceGamma = SkScalarFloorToInt(dg * (1 << 6));
+ }
+
+ SkScalar getPaintGamma() const {
+ return SkIntToScalar(fPaintGamma) / (1 << 6);
+ }
+ void setPaintGamma(SkScalar pg) {
+ SkASSERT(0 <= pg && pg < SkIntToScalar(4));
+ fPaintGamma = SkScalarFloorToInt(pg * (1 << 6));
+ }
+
+ SkScalar getContrast() const {
+ return SkIntToScalar(fContrast) / ((1 << 8) - 1);
+ }
+ void setContrast(SkScalar c) {
+ SkASSERT(0 <= c && c <= SK_Scalar1);
+ fContrast = SkScalarRoundToInt(c * ((1 << 8) - 1));
+ }
+
+ /**
+ * Causes the luminance color and contrast to be ignored, and the
+ * paint and device gamma to be effectively 1.0.
+ */
+ void ignorePreBlend() {
+ setLuminanceColor(SK_ColorTRANSPARENT);
+ setPaintGamma(SK_Scalar1);
+ setDeviceGamma(SK_Scalar1);
+ setContrast(0);
+ }
+
+ uint8_t fMaskFormat;
+ uint8_t fStrokeJoin;
+ uint16_t fFlags;
+ // Warning: when adding members note that the size of this structure
+ // must be a multiple of 4. SkDescriptor requires that its arguments be
+ // multiples of four and this structure is put in an SkDescriptor in
+ // SkPaint::MakeRec.
+
+ void getMatrixFrom2x2(SkMatrix*) const;
+ void getLocalMatrix(SkMatrix*) const;
+ void getSingleMatrix(SkMatrix*) const;
+
+ inline SkPaint::Hinting getHinting() const;
+ inline void setHinting(SkPaint::Hinting);
+
+ SkMask::Format getFormat() const {
+ return static_cast<SkMask::Format>(fMaskFormat);
+ }
+
+ SkColor getLuminanceColor() const {
+ return fLumBits;
+ }
+
+ void setLuminanceColor(SkColor c) {
+ fLumBits = c;
+ }
+};
+
+//The following typedef hides from the rest of the implementation the number of
+//most significant bits to consider when creating mask gamma tables. Two bits
+//per channel was chosen as a balance between fidelity (more bits) and cache
+//sizes (fewer bits). Three bits per channel was chosen when #303942; (used by
+//the Chrome UI) turned out too green.
+typedef SkTMaskGamma<3, 3, 3> SkMaskGamma;
+
+class SkScalerContext {
+public:
+ typedef SkScalerContextRec Rec;
+
+ enum Flags {
+ kFrameAndFill_Flag = 0x0001,
+ kDevKernText_Flag = 0x0002,
+ kEmbeddedBitmapText_Flag = 0x0004,
+ kEmbolden_Flag = 0x0008,
+ kSubpixelPositioning_Flag = 0x0010,
+ kAutohinting_Flag = 0x0020,
+ kVertical_Flag = 0x0040,
+
+ // together, these two flags resulting in a two bit value which matches
+ // up with the SkPaint::Hinting enum.
+ kHinting_Shift = 7, // to shift into the other flags above
+ kHintingBit1_Flag = 0x0080,
+ kHintingBit2_Flag = 0x0100,
+
+ // Pixel geometry information.
+ // only meaningful if fMaskFormat is LCD16 or LCD32
+ kLCD_Vertical_Flag = 0x0200, // else Horizontal
+ kLCD_BGROrder_Flag = 0x0400, // else RGB order
+
+ // Generate A8 from LCD source (for GDI and CoreGraphics).
+ // only meaningful if fMaskFormat is kA8
+ kGenA8FromLCD_Flag = 0x0800, // could be 0x200 (bit meaning dependent on fMaskFormat)
+ };
+
+ // computed values
+ enum {
+ kHinting_Mask = kHintingBit1_Flag | kHintingBit2_Flag,
+ };
+
+
+ SkScalerContext(SkTypeface*, const SkDescriptor*);
+ virtual ~SkScalerContext();
+
+ SkTypeface* getTypeface() const { return fTypeface.get(); }
+
+ SkMask::Format getMaskFormat() const {
+ return (SkMask::Format)fRec.fMaskFormat;
+ }
+
+ bool isSubpixel() const {
+ return SkToBool(fRec.fFlags & kSubpixelPositioning_Flag);
+ }
+
+ // remember our glyph offset/base
+ void setBaseGlyphCount(unsigned baseGlyphCount) {
+ fBaseGlyphCount = baseGlyphCount;
+ }
+
+ /** Return the corresponding glyph for the specified unichar. Since contexts
+ may be chained (under the hood), the glyphID that is returned may in
+ fact correspond to a different font/context. In that case, we use the
+ base-glyph-count to know how to translate back into local glyph space.
+ */
+ uint16_t charToGlyphID(SkUnichar uni);
+
+ /** Map the glyphID to its glyph index, and then to its char code. Unmapped
+ glyphs return zero.
+ */
+ SkUnichar glyphIDToChar(uint16_t glyphID);
+
+ unsigned getGlyphCount() { return this->generateGlyphCount(); }
+ void getAdvance(SkGlyph*);
+ void getMetrics(SkGlyph*);
+ void getImage(const SkGlyph&);
+ void getPath(const SkGlyph&, SkPath*);
+ void getFontMetrics(SkPaint::FontMetrics*);
+
+#ifdef SK_BUILD_FOR_ANDROID
+ unsigned getBaseGlyphCount(SkUnichar charCode);
+
+ // This function must be public for SkTypeface_android.h, but should not be
+ // called by other callers
+ SkFontID findTypefaceIdForChar(SkUnichar uni);
+#endif
+
+ static inline void MakeRec(const SkPaint&, const SkDeviceProperties* deviceProperties,
+ const SkMatrix*, Rec* rec);
+ static inline void PostMakeRec(const SkPaint&, Rec*);
+
+ static SkMaskGamma::PreBlend GetMaskPreBlend(const Rec& rec);
+
+protected:
+ Rec fRec;
+ unsigned fBaseGlyphCount;
+
+ virtual unsigned generateGlyphCount() = 0;
+ virtual uint16_t generateCharToGlyph(SkUnichar) = 0;
+ virtual void generateAdvance(SkGlyph*) = 0;
+ virtual void generateMetrics(SkGlyph*) = 0;
+ virtual void generateImage(const SkGlyph&) = 0;
+ virtual void generatePath(const SkGlyph&, SkPath*) = 0;
+ virtual void generateFontMetrics(SkPaint::FontMetrics* mX,
+ SkPaint::FontMetrics* mY) = 0;
+ // default impl returns 0, indicating failure.
+ virtual SkUnichar generateGlyphToChar(uint16_t);
+
+ void forceGenerateImageFromPath() { fGenerateImageFromPath = true; }
+
+private:
+ // never null
+ SkAutoTUnref<SkTypeface> fTypeface;
+
+#ifdef SK_BUILD_FOR_ANDROID
+ SkPaintOptionsAndroid fPaintOptionsAndroid;
+#endif
+
+ // optional object, which may be null
+ SkPathEffect* fPathEffect;
+ SkMaskFilter* fMaskFilter;
+ SkRasterizer* fRasterizer;
+
+ // if this is set, we draw the image from a path, rather than
+ // calling generateImage.
+ bool fGenerateImageFromPath;
+
+ void internalGetPath(const SkGlyph& glyph, SkPath* fillPath,
+ SkPath* devPath, SkMatrix* fillToDevMatrix);
+
+ // Return the context associated with the next logical typeface, or NULL if
+ // there are no more entries in the fallback chain.
+ SkScalerContext* allocNextContext() const;
+
+ // return the next context, treating fNextContext as a cache of the answer
+ SkScalerContext* getNextContext();
+
+ // returns the right context from our link-list for this glyph. If no match
+ // is found, just returns the original context (this)
+ SkScalerContext* getGlyphContext(const SkGlyph& glyph);
+
+ // returns the right context from our link-list for this char. If no match
+ // is found it returns NULL. If a match is found then the glyphID param is
+ // set to the glyphID that maps to the provided char.
+ SkScalerContext* getContextFromChar(SkUnichar uni, uint16_t* glyphID);
+
+ // link-list of context, to handle missing chars. null-terminated.
+ SkScalerContext* fNextContext;
+
+ // 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')
+#define kPathEffect_SkDescriptorTag SkSetFourByteTag('p', 't', 'h', 'e')
+#define kMaskFilter_SkDescriptorTag SkSetFourByteTag('m', 's', 'k', 'f')
+#define kRasterizer_SkDescriptorTag SkSetFourByteTag('r', 'a', 's', 't')
+#ifdef SK_BUILD_FOR_ANDROID
+#define kAndroidOpts_SkDescriptorTag SkSetFourByteTag('a', 'n', 'd', 'r')
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum SkAxisAlignment {
+ kNone_SkAxisAlignment,
+ kX_SkAxisAlignment,
+ kY_SkAxisAlignment
+};
+
+/**
+ * Return the axis (if any) that the baseline for horizontal text will land on
+ * after running through the specified matrix.
+ *
+ * As an example, the identity matrix will return kX_SkAxisAlignment
+ */
+SkAxisAlignment SkComputeAxisAlignmentForHText(const SkMatrix& matrix);
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPaint::Hinting SkScalerContextRec::getHinting() const {
+ unsigned hint = (fFlags & SkScalerContext::kHinting_Mask) >>
+ SkScalerContext::kHinting_Shift;
+ return static_cast<SkPaint::Hinting>(hint);
+}
+
+void SkScalerContextRec::setHinting(SkPaint::Hinting hinting) {
+ fFlags = (fFlags & ~SkScalerContext::kHinting_Mask) |
+ (hinting << SkScalerContext::kHinting_Shift);
+}
+
+
+#endif
diff --git a/core/SkScan.cpp b/core/SkScan.cpp
new file mode 100644
index 00000000..44968bd9
--- /dev/null
+++ b/core/SkScan.cpp
@@ -0,0 +1,117 @@
+
+/*
+ * 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 "SkScan.h"
+#include "SkBlitter.h"
+#include "SkRasterClip.h"
+
+static inline void blitrect(SkBlitter* blitter, const SkIRect& r) {
+ blitter->blitRect(r.fLeft, r.fTop, r.width(), r.height());
+}
+
+void SkScan::FillIRect(const SkIRect& r, const SkRegion* clip,
+ SkBlitter* blitter) {
+ if (!r.isEmpty()) {
+ if (clip) {
+ if (clip->isRect()) {
+ const SkIRect& clipBounds = clip->getBounds();
+
+ if (clipBounds.contains(r)) {
+ blitrect(blitter, r);
+ } else {
+ SkIRect rr = r;
+ if (rr.intersect(clipBounds)) {
+ blitrect(blitter, rr);
+ }
+ }
+ } else {
+ SkRegion::Cliperator cliper(*clip, r);
+ const SkIRect& rr = cliper.rect();
+
+ while (!cliper.done()) {
+ blitrect(blitter, rr);
+ cliper.next();
+ }
+ }
+ } else {
+ blitrect(blitter, r);
+ }
+ }
+}
+
+void SkScan::FillXRect(const SkXRect& xr, const SkRegion* clip,
+ SkBlitter* blitter) {
+ SkIRect r;
+
+ XRect_round(xr, &r);
+ SkScan::FillIRect(r, clip, blitter);
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+void SkScan::FillRect(const SkRect& r, const SkRegion* clip,
+ SkBlitter* blitter) {
+ SkIRect ir;
+
+ r.round(&ir);
+ SkScan::FillIRect(ir, clip, blitter);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScan::FillIRect(const SkIRect& r, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty() || r.isEmpty()) {
+ return;
+ }
+
+ if (clip.isBW()) {
+ FillIRect(r, &clip.bwRgn(), blitter);
+ return;
+ }
+
+ SkAAClipBlitterWrapper wrapper(clip, blitter);
+ FillIRect(r, &wrapper.getRgn(), wrapper.getBlitter());
+}
+
+void SkScan::FillXRect(const SkXRect& xr, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty() || xr.isEmpty()) {
+ return;
+ }
+
+ if (clip.isBW()) {
+ FillXRect(xr, &clip.bwRgn(), blitter);
+ return;
+ }
+
+ SkAAClipBlitterWrapper wrapper(clip, blitter);
+ FillXRect(xr, &wrapper.getRgn(), wrapper.getBlitter());
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+void SkScan::FillRect(const SkRect& r, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty() || r.isEmpty()) {
+ return;
+ }
+
+ if (clip.isBW()) {
+ FillRect(r, &clip.bwRgn(), blitter);
+ return;
+ }
+
+ SkAAClipBlitterWrapper wrapper(clip, blitter);
+ FillRect(r, &wrapper.getRgn(), wrapper.getBlitter());
+}
+
+#endif
diff --git a/core/SkScan.h b/core/SkScan.h
new file mode 100644
index 00000000..5989435d
--- /dev/null
+++ b/core/SkScan.h
@@ -0,0 +1,138 @@
+
+/*
+ * 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 SkScan_DEFINED
+#define SkScan_DEFINED
+
+#include "SkRect.h"
+
+class SkRasterClip;
+class SkRegion;
+class SkBlitter;
+class SkPath;
+
+/** Defines a fixed-point rectangle, identical to the integer SkIRect, but its
+ coordinates are treated as SkFixed rather than int32_t.
+*/
+typedef SkIRect SkXRect;
+
+class SkScan {
+public:
+ static void FillPath(const SkPath&, const SkIRect&, SkBlitter*);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // rasterclip
+
+ static void FillIRect(const SkIRect&, const SkRasterClip&, SkBlitter*);
+ static void FillXRect(const SkXRect&, const SkRasterClip&, SkBlitter*);
+#ifdef SK_SCALAR_IS_FIXED
+ static void FillRect(const SkRect& rect, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ SkScan::FillXRect(*(const SkXRect*)&rect, clip, blitter);
+ }
+ static void AntiFillRect(const SkRect& rect, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ SkScan::AntiFillXRect(*(const SkXRect*)&rect, clip, blitter);
+ }
+#else
+ static void FillRect(const SkRect&, const SkRasterClip&, SkBlitter*);
+ static void AntiFillRect(const SkRect&, const SkRasterClip&, SkBlitter*);
+#endif
+ static void AntiFillXRect(const SkXRect&, const SkRasterClip&, SkBlitter*);
+ static void FillPath(const SkPath&, const SkRasterClip&, SkBlitter*);
+ static void AntiFillPath(const SkPath&, const SkRasterClip&, SkBlitter*);
+ static void FrameRect(const SkRect&, const SkPoint& strokeSize,
+ const SkRasterClip&, SkBlitter*);
+ static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize,
+ const SkRasterClip&, SkBlitter*);
+ static void FillTriangle(const SkPoint pts[], const SkRasterClip&, SkBlitter*);
+ static void HairLine(const SkPoint&, const SkPoint&, const SkRasterClip&,
+ SkBlitter*);
+ static void AntiHairLine(const SkPoint&, const SkPoint&, const SkRasterClip&,
+ SkBlitter*);
+ static void HairRect(const SkRect&, const SkRasterClip&, SkBlitter*);
+ static void AntiHairRect(const SkRect&, const SkRasterClip&, SkBlitter*);
+ static void HairPath(const SkPath&, const SkRasterClip&, SkBlitter*);
+ static void AntiHairPath(const SkPath&, const SkRasterClip&, SkBlitter*);
+
+private:
+ friend class SkAAClip;
+ friend class SkRegion;
+
+ static void FillIRect(const SkIRect&, const SkRegion* clip, SkBlitter*);
+ static void FillXRect(const SkXRect&, const SkRegion* clip, SkBlitter*);
+#ifdef SK_SCALAR_IS_FIXED
+ static void FillRect(const SkRect& rect, const SkRegion* clip,
+ SkBlitter* blitter) {
+ SkScan::FillXRect(*(const SkXRect*)&rect, clip, blitter);
+ }
+ static void AntiFillRect(const SkRect& rect, const SkRegion* clip,
+ SkBlitter* blitter) {
+ SkScan::AntiFillXRect(*(const SkXRect*)&rect, clip, blitter);
+ }
+#else
+ static void FillRect(const SkRect&, const SkRegion* clip, SkBlitter*);
+ static void AntiFillRect(const SkRect&, const SkRegion* clip, SkBlitter*);
+#endif
+ static void AntiFillXRect(const SkXRect&, const SkRegion*, SkBlitter*);
+ static void FillPath(const SkPath&, const SkRegion& clip, SkBlitter*);
+ static void AntiFillPath(const SkPath&, const SkRegion& clip, SkBlitter*,
+ bool forceRLE = false);
+ static void FillTriangle(const SkPoint pts[], const SkRegion*, SkBlitter*);
+
+ static void AntiFrameRect(const SkRect&, const SkPoint& strokeSize,
+ const SkRegion*, SkBlitter*);
+ static void HairLineRgn(const SkPoint&, const SkPoint&, const SkRegion*,
+ SkBlitter*);
+ static void AntiHairLineRgn(const SkPoint&, const SkPoint&, const SkRegion*,
+ SkBlitter*);
+};
+
+/** Assign an SkXRect from a SkIRect, by promoting the src rect's coordinates
+ from int to SkFixed. Does not check for overflow if the src coordinates
+ exceed 32K
+*/
+static inline void XRect_set(SkXRect* xr, const SkIRect& src) {
+ xr->fLeft = SkIntToFixed(src.fLeft);
+ xr->fTop = SkIntToFixed(src.fTop);
+ xr->fRight = SkIntToFixed(src.fRight);
+ xr->fBottom = SkIntToFixed(src.fBottom);
+}
+
+/** Assign an SkXRect from a SkRect, by promoting the src rect's coordinates
+ from SkScalar to SkFixed. Does not check for overflow if the src coordinates
+ exceed 32K
+*/
+static inline void XRect_set(SkXRect* xr, const SkRect& src) {
+ xr->fLeft = SkScalarToFixed(src.fLeft);
+ xr->fTop = SkScalarToFixed(src.fTop);
+ xr->fRight = SkScalarToFixed(src.fRight);
+ xr->fBottom = SkScalarToFixed(src.fBottom);
+}
+
+/** Round the SkXRect coordinates, and store the result in the SkIRect.
+*/
+static inline void XRect_round(const SkXRect& xr, SkIRect* dst) {
+ dst->fLeft = SkFixedRound(xr.fLeft);
+ dst->fTop = SkFixedRound(xr.fTop);
+ dst->fRight = SkFixedRound(xr.fRight);
+ dst->fBottom = SkFixedRound(xr.fBottom);
+}
+
+/** Round the SkXRect coordinates out (i.e. use floor for left/top, and ceiling
+ for right/bottom), and store the result in the SkIRect.
+*/
+static inline void XRect_roundOut(const SkXRect& xr, SkIRect* dst) {
+ dst->fLeft = SkFixedFloor(xr.fLeft);
+ dst->fTop = SkFixedFloor(xr.fTop);
+ dst->fRight = SkFixedCeil(xr.fRight);
+ dst->fBottom = SkFixedCeil(xr.fBottom);
+}
+
+#endif
diff --git a/core/SkScanPriv.h b/core/SkScanPriv.h
new file mode 100644
index 00000000..75ceee08
--- /dev/null
+++ b/core/SkScanPriv.h
@@ -0,0 +1,40 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkScanPriv_DEFINED
+#define SkScanPriv_DEFINED
+
+#include "SkScan.h"
+#include "SkBlitter.h"
+
+class SkScanClipper {
+public:
+ SkScanClipper(SkBlitter* blitter, const SkRegion* clip, const SkIRect& bounds,
+ bool skipRejectTest = false);
+
+ SkBlitter* getBlitter() const { return fBlitter; }
+ const SkIRect* getClipRect() const { return fClipRect; }
+
+private:
+ SkRectClipBlitter fRectBlitter;
+ SkRgnClipBlitter fRgnBlitter;
+ SkBlitter* fBlitter;
+ const SkIRect* fClipRect;
+};
+
+// clipRect == null means path is entirely inside the clip
+void sk_fill_path(const SkPath& path, const SkIRect* clipRect,
+ SkBlitter* blitter, int start_y, int stop_y, int shiftEdgesUp,
+ const SkRegion& clipRgn);
+
+// blit the rects above and below avoid, clipped to clip
+void sk_blit_above(SkBlitter*, const SkIRect& avoid, const SkRegion& clip);
+void sk_blit_below(SkBlitter*, const SkIRect& avoid, const SkRegion& clip);
+
+#endif
diff --git a/core/SkScan_AntiPath.cpp b/core/SkScan_AntiPath.cpp
new file mode 100644
index 00000000..0d2152c7
--- /dev/null
+++ b/core/SkScan_AntiPath.cpp
@@ -0,0 +1,746 @@
+
+/*
+ * 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 "SkScanPriv.h"
+#include "SkPath.h"
+#include "SkMatrix.h"
+#include "SkBlitter.h"
+#include "SkRegion.h"
+#include "SkAntiRun.h"
+
+#define SHIFT 2
+#define SCALE (1 << SHIFT)
+#define MASK (SCALE - 1)
+
+/** @file
+ We have two techniques for capturing the output of the supersampler:
+ - SUPERMASK, which records a large mask-bitmap
+ this is often faster for small, complex objects
+ - RLE, which records a rle-encoded scanline
+ this is often faster for large objects with big spans
+
+ These blitters use two coordinate systems:
+ - destination coordinates, scale equal to the output - often
+ abbreviated with 'i' or 'I' in variable names
+ - supersampled coordinates, scale equal to the output * SCALE
+
+ Enabling SK_USE_LEGACY_AA_COVERAGE keeps the aa coverage calculations as
+ they were before the fix that unified the output of the RLE and MASK
+ supersamplers.
+ */
+
+//#define FORCE_SUPERMASK
+//#define FORCE_RLE
+//#define SK_USE_LEGACY_AA_COVERAGE
+
+///////////////////////////////////////////////////////////////////////////////
+
+/// Base class for a single-pass supersampled blitter.
+class BaseSuperBlitter : public SkBlitter {
+public:
+ BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip);
+
+ /// Must be explicitly defined on subclasses.
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[],
+ const int16_t runs[]) SK_OVERRIDE {
+ SkDEBUGFAIL("How did I get here?");
+ }
+ /// May not be called on BaseSuperBlitter because it blits out of order.
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) SK_OVERRIDE {
+ SkDEBUGFAIL("How did I get here?");
+ }
+
+protected:
+ SkBlitter* fRealBlitter;
+ /// Current y coordinate, in destination coordinates.
+ int fCurrIY;
+ /// Widest row of region to be blitted, in destination coordinates.
+ int fWidth;
+ /// Leftmost x coordinate in any row, in destination coordinates.
+ int fLeft;
+ /// Leftmost x coordinate in any row, in supersampled coordinates.
+ int fSuperLeft;
+
+ SkDEBUGCODE(int fCurrX;)
+ /// Current y coordinate in supersampled coordinates.
+ int fCurrY;
+ /// Initial y coordinate (top of bounds).
+ int fTop;
+};
+
+BaseSuperBlitter::BaseSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip) {
+ fRealBlitter = realBlitter;
+
+ /*
+ * We use the clip bounds instead of the ir, since we may be asked to
+ * draw outside of the rect if we're a inverse filltype
+ */
+ const int left = clip.getBounds().fLeft;
+ const int right = clip.getBounds().fRight;
+
+ fLeft = left;
+ fSuperLeft = left << SHIFT;
+ fWidth = right - left;
+#if 0
+ fCurrIY = -1;
+ fCurrY = -1;
+#else
+ fTop = ir.fTop;
+ fCurrIY = ir.fTop - 1;
+ fCurrY = (ir.fTop << SHIFT) - 1;
+#endif
+ SkDEBUGCODE(fCurrX = -1;)
+}
+
+/// Run-length-encoded supersampling antialiased blitter.
+class SuperBlitter : public BaseSuperBlitter {
+public:
+ SuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip);
+
+ virtual ~SuperBlitter() {
+ this->flush();
+ sk_free(fRuns.fRuns);
+ }
+
+ /// Once fRuns contains a complete supersampled row, flush() blits
+ /// it out through the wrapped blitter.
+ void flush();
+
+ /// Blits a row of pixels, with location and width specified
+ /// in supersampled coordinates.
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+ /// Blits a rectangle of pixels, with location and size specified
+ /// in supersampled coordinates.
+ virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE;
+
+private:
+ SkAlphaRuns fRuns;
+ int fOffsetX;
+};
+
+SuperBlitter::SuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip)
+ : BaseSuperBlitter(realBlitter, ir, clip) {
+ const int width = fWidth;
+
+ // extra one to store the zero at the end
+ fRuns.fRuns = (int16_t*)sk_malloc_throw((width + 1 + (width + 2)/2) * sizeof(int16_t));
+ fRuns.fAlpha = (uint8_t*)(fRuns.fRuns + width + 1);
+ fRuns.reset(width);
+
+ fOffsetX = 0;
+}
+
+void SuperBlitter::flush() {
+ if (fCurrIY >= fTop) {
+ if (!fRuns.empty()) {
+ // SkDEBUGCODE(fRuns.dump();)
+ fRealBlitter->blitAntiH(fLeft, fCurrIY, fRuns.fAlpha, fRuns.fRuns);
+ fRuns.reset(fWidth);
+ fOffsetX = 0;
+ }
+ fCurrIY = fTop - 1;
+ SkDEBUGCODE(fCurrX = -1;)
+ }
+}
+
+/** coverage_to_partial_alpha() is being used by SkAlphaRuns, which
+ *accumulates* SCALE pixels worth of "alpha" in [0,(256/SCALE)]
+ to produce a final value in [0, 255] and handles clamping 256->255
+ itself, with the same (alpha - (alpha >> 8)) correction as
+ coverage_to_exact_alpha().
+*/
+static inline int coverage_to_partial_alpha(int aa) {
+ aa <<= 8 - 2*SHIFT;
+#ifdef SK_USE_LEGACY_AA_COVERAGE
+ aa -= aa >> (8 - SHIFT - 1);
+#endif
+ return aa;
+}
+
+/** coverage_to_exact_alpha() is being used by our blitter, which wants
+ a final value in [0, 255].
+*/
+static inline int coverage_to_exact_alpha(int aa) {
+ int alpha = (256 >> SHIFT) * aa;
+ // clamp 256->255
+ return alpha - (alpha >> 8);
+}
+
+void SuperBlitter::blitH(int x, int y, int width) {
+ SkASSERT(width > 0);
+
+ int iy = y >> SHIFT;
+ SkASSERT(iy >= fCurrIY);
+
+ x -= fSuperLeft;
+ // hack, until I figure out why my cubics (I think) go beyond the bounds
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+
+#ifdef SK_DEBUG
+ SkASSERT(y != fCurrY || x >= fCurrX);
+#endif
+ SkASSERT(y >= fCurrY);
+ if (fCurrY != y) {
+ fOffsetX = 0;
+ fCurrY = y;
+ }
+
+ if (iy != fCurrIY) { // new scanline
+ this->flush();
+ fCurrIY = iy;
+ }
+
+ int start = x;
+ int stop = x + width;
+
+ SkASSERT(start >= 0 && stop > start);
+ // integer-pixel-aligned ends of blit, rounded out
+ int fb = start & MASK;
+ int fe = stop & MASK;
+ int n = (stop >> SHIFT) - (start >> SHIFT) - 1;
+
+ if (n < 0) {
+ fb = fe - fb;
+ n = 0;
+ fe = 0;
+ } else {
+ if (fb == 0) {
+ n += 1;
+ } else {
+ fb = SCALE - fb;
+ }
+ }
+
+ fOffsetX = fRuns.add(x >> SHIFT, coverage_to_partial_alpha(fb),
+ n, coverage_to_partial_alpha(fe),
+ (1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT),
+ fOffsetX);
+
+#ifdef SK_DEBUG
+ fRuns.assertValid(y & MASK, (1 << (8 - SHIFT)));
+ fCurrX = x + width;
+#endif
+}
+
+#if 0 // UNUSED
+static void set_left_rite_runs(SkAlphaRuns& runs, int ileft, U8CPU leftA,
+ int n, U8CPU riteA) {
+ SkASSERT(leftA <= 0xFF);
+ SkASSERT(riteA <= 0xFF);
+
+ int16_t* run = runs.fRuns;
+ uint8_t* aa = runs.fAlpha;
+
+ if (ileft > 0) {
+ run[0] = ileft;
+ aa[0] = 0;
+ run += ileft;
+ aa += ileft;
+ }
+
+ SkASSERT(leftA < 0xFF);
+ if (leftA > 0) {
+ *run++ = 1;
+ *aa++ = leftA;
+ }
+
+ if (n > 0) {
+ run[0] = n;
+ aa[0] = 0xFF;
+ run += n;
+ aa += n;
+ }
+
+ SkASSERT(riteA < 0xFF);
+ if (riteA > 0) {
+ *run++ = 1;
+ *aa++ = riteA;
+ }
+ run[0] = 0;
+}
+#endif
+
+void SuperBlitter::blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0);
+ SkASSERT(height > 0);
+
+ // blit leading rows
+ while ((y & MASK)) {
+ this->blitH(x, y++, width);
+ if (--height <= 0) {
+ return;
+ }
+ }
+ SkASSERT(height > 0);
+
+ // Since this is a rect, instead of blitting supersampled rows one at a
+ // time and then resolving to the destination canvas, we can blit
+ // directly to the destintion canvas one row per SCALE supersampled rows.
+ int start_y = y >> SHIFT;
+ int stop_y = (y + height) >> SHIFT;
+ int count = stop_y - start_y;
+ if (count > 0) {
+ y += count << SHIFT;
+ height -= count << SHIFT;
+
+ // save original X for our tail blitH() loop at the bottom
+ int origX = x;
+
+ x -= fSuperLeft;
+ // hack, until I figure out why my cubics (I think) go beyond the bounds
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+
+ // There is always a left column, a middle, and a right column.
+ // ileft is the destination x of the first pixel of the entire rect.
+ // xleft is (SCALE - # of covered supersampled pixels) in that
+ // destination pixel.
+ int ileft = x >> SHIFT;
+ int xleft = x & MASK;
+ // irite is the destination x of the last pixel of the OPAQUE section.
+ // xrite is the number of supersampled pixels extending beyond irite;
+ // xrite/SCALE should give us alpha.
+ int irite = (x + width) >> SHIFT;
+ int xrite = (x + width) & MASK;
+ if (!xrite) {
+ xrite = SCALE;
+ irite--;
+ }
+
+ // Need to call flush() to clean up pending draws before we
+ // even consider blitV(), since otherwise it can look nonmonotonic.
+ SkASSERT(start_y > fCurrIY);
+ this->flush();
+
+ int n = irite - ileft - 1;
+ if (n < 0) {
+ // If n < 0, we'll only have a single partially-transparent column
+ // of pixels to render.
+ xleft = xrite - xleft;
+ SkASSERT(xleft <= SCALE);
+ SkASSERT(xleft > 0);
+ xrite = 0;
+ fRealBlitter->blitV(ileft + fLeft, start_y, count,
+ coverage_to_exact_alpha(xleft));
+ } else {
+ // With n = 0, we have two possibly-transparent columns of pixels
+ // to render; with n > 0, we have opaque columns between them.
+
+ xleft = SCALE - xleft;
+
+ // Using coverage_to_exact_alpha is not consistent with blitH()
+ const int coverageL = coverage_to_exact_alpha(xleft);
+ const int coverageR = coverage_to_exact_alpha(xrite);
+
+ SkASSERT(coverageL > 0 || n > 0 || coverageR > 0);
+ SkASSERT((coverageL != 0) + n + (coverageR != 0) <= fWidth);
+
+ fRealBlitter->blitAntiRect(ileft + fLeft, start_y, n, count,
+ coverageL, coverageR);
+ }
+
+ // preamble for our next call to blitH()
+ fCurrIY = stop_y - 1;
+ fOffsetX = 0;
+ fCurrY = y - 1;
+ fRuns.reset(fWidth);
+ x = origX;
+ }
+
+ // catch any remaining few rows
+ SkASSERT(height <= MASK);
+ while (--height >= 0) {
+ this->blitH(x, y++, width);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/// Masked supersampling antialiased blitter.
+class MaskSuperBlitter : public BaseSuperBlitter {
+public:
+ MaskSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip);
+ virtual ~MaskSuperBlitter() {
+ fRealBlitter->blitMask(fMask, fClipRect);
+ }
+
+ virtual void blitH(int x, int y, int width) SK_OVERRIDE;
+
+ static bool CanHandleRect(const SkIRect& bounds) {
+#ifdef FORCE_RLE
+ return false;
+#endif
+ int width = bounds.width();
+ int64_t rb = SkAlign4(width);
+ // use 64bits to detect overflow
+ int64_t storage = rb * bounds.height();
+
+ return (width <= MaskSuperBlitter::kMAX_WIDTH) &&
+ (storage <= MaskSuperBlitter::kMAX_STORAGE);
+ }
+
+private:
+ enum {
+#ifdef FORCE_SUPERMASK
+ kMAX_WIDTH = 2048,
+ kMAX_STORAGE = 1024 * 1024 * 2
+#else
+ kMAX_WIDTH = 32, // so we don't try to do very wide things, where the RLE blitter would be faster
+ kMAX_STORAGE = 1024
+#endif
+ };
+
+ SkMask fMask;
+ SkIRect fClipRect;
+ // we add 1 because add_aa_span can write (unchanged) 1 extra byte at the end, rather than
+ // perform a test to see if stopAlpha != 0
+ uint32_t fStorage[(kMAX_STORAGE >> 2) + 1];
+};
+
+MaskSuperBlitter::MaskSuperBlitter(SkBlitter* realBlitter, const SkIRect& ir,
+ const SkRegion& clip)
+ : BaseSuperBlitter(realBlitter, ir, clip) {
+ SkASSERT(CanHandleRect(ir));
+
+ fMask.fImage = (uint8_t*)fStorage;
+ fMask.fBounds = ir;
+ fMask.fRowBytes = ir.width();
+ fMask.fFormat = SkMask::kA8_Format;
+
+ fClipRect = ir;
+ fClipRect.intersect(clip.getBounds());
+
+ // For valgrind, write 1 extra byte at the end so we don't read
+ // uninitialized memory. See comment in add_aa_span and fStorage[].
+ memset(fStorage, 0, fMask.fBounds.height() * fMask.fRowBytes + 1);
+}
+
+static void add_aa_span(uint8_t* alpha, U8CPU startAlpha) {
+ /* I should be able to just add alpha[x] + startAlpha.
+ However, if the trailing edge of the previous span and the leading
+ edge of the current span round to the same super-sampled x value,
+ I might overflow to 256 with this add, hence the funny subtract.
+ */
+ unsigned tmp = *alpha + startAlpha;
+ SkASSERT(tmp <= 256);
+ *alpha = SkToU8(tmp - (tmp >> 8));
+}
+
+static inline uint32_t quadplicate_byte(U8CPU value) {
+ uint32_t pair = (value << 8) | value;
+ return (pair << 16) | pair;
+}
+
+// Perform this tricky subtract, to avoid overflowing to 256. Our caller should
+// only ever call us with at most enough to hit 256 (never larger), so it is
+// enough to just subtract the high-bit. Actually clamping with a branch would
+// be slower (e.g. if (tmp > 255) tmp = 255;)
+//
+static inline void saturated_add(uint8_t* ptr, U8CPU add) {
+ unsigned tmp = *ptr + add;
+ SkASSERT(tmp <= 256);
+ *ptr = SkToU8(tmp - (tmp >> 8));
+}
+
+// minimum count before we want to setup an inner loop, adding 4-at-a-time
+#define MIN_COUNT_FOR_QUAD_LOOP 16
+
+static void add_aa_span(uint8_t* alpha, U8CPU startAlpha, int middleCount,
+ U8CPU stopAlpha, U8CPU maxValue) {
+ SkASSERT(middleCount >= 0);
+
+ saturated_add(alpha, startAlpha);
+ alpha += 1;
+
+ if (middleCount >= MIN_COUNT_FOR_QUAD_LOOP) {
+ // loop until we're quad-byte aligned
+ while (SkTCast<intptr_t>(alpha) & 0x3) {
+ alpha[0] = SkToU8(alpha[0] + maxValue);
+ alpha += 1;
+ middleCount -= 1;
+ }
+
+ int bigCount = middleCount >> 2;
+ uint32_t* qptr = reinterpret_cast<uint32_t*>(alpha);
+ uint32_t qval = quadplicate_byte(maxValue);
+ do {
+ *qptr++ += qval;
+ } while (--bigCount > 0);
+
+ middleCount &= 3;
+ alpha = reinterpret_cast<uint8_t*> (qptr);
+ // fall through to the following while-loop
+ }
+
+ while (--middleCount >= 0) {
+ alpha[0] = SkToU8(alpha[0] + maxValue);
+ alpha += 1;
+ }
+
+ // potentially this can be off the end of our "legal" alpha values, but that
+ // only happens if stopAlpha is also 0. Rather than test for stopAlpha != 0
+ // every time (slow), we just do it, and ensure that we've allocated extra space
+ // (see the + 1 comment in fStorage[]
+ saturated_add(alpha, stopAlpha);
+}
+
+void MaskSuperBlitter::blitH(int x, int y, int width) {
+ int iy = (y >> SHIFT);
+
+ SkASSERT(iy >= fMask.fBounds.fTop && iy < fMask.fBounds.fBottom);
+ iy -= fMask.fBounds.fTop; // make it relative to 0
+
+ // This should never happen, but it does. Until the true cause is
+ // discovered, let's skip this span instead of crashing.
+ // See http://crbug.com/17569.
+ if (iy < 0) {
+ return;
+ }
+
+#ifdef SK_DEBUG
+ {
+ int ix = x >> SHIFT;
+ SkASSERT(ix >= fMask.fBounds.fLeft && ix < fMask.fBounds.fRight);
+ }
+#endif
+
+ x -= (fMask.fBounds.fLeft << SHIFT);
+
+ // hack, until I figure out why my cubics (I think) go beyond the bounds
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+
+ uint8_t* row = fMask.fImage + iy * fMask.fRowBytes + (x >> SHIFT);
+
+ int start = x;
+ int stop = x + width;
+
+ SkASSERT(start >= 0 && stop > start);
+ int fb = start & MASK;
+ int fe = stop & MASK;
+ int n = (stop >> SHIFT) - (start >> SHIFT) - 1;
+
+
+ if (n < 0) {
+ SkASSERT(row >= fMask.fImage);
+ SkASSERT(row < fMask.fImage + kMAX_STORAGE + 1);
+ add_aa_span(row, coverage_to_partial_alpha(fe - fb));
+ } else {
+ fb = SCALE - fb;
+ SkASSERT(row >= fMask.fImage);
+ SkASSERT(row + n + 1 < fMask.fImage + kMAX_STORAGE + 1);
+ add_aa_span(row, coverage_to_partial_alpha(fb),
+ n, coverage_to_partial_alpha(fe),
+ (1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT));
+ }
+
+#ifdef SK_DEBUG
+ fCurrX = x + width;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool fitsInsideLimit(const SkRect& r, SkScalar max) {
+ const SkScalar min = -max;
+ return r.fLeft > min && r.fTop > min &&
+ r.fRight < max && r.fBottom < max;
+}
+
+static int overflows_short_shift(int value, int shift) {
+ const int s = 16 + shift;
+ return (value << s >> s) - value;
+}
+
+/**
+ Would any of the coordinates of this rectangle not fit in a short,
+ when left-shifted by shift?
+*/
+static int rect_overflows_short_shift(SkIRect rect, int shift) {
+ SkASSERT(!overflows_short_shift(8191, SHIFT));
+ SkASSERT(overflows_short_shift(8192, SHIFT));
+ SkASSERT(!overflows_short_shift(32767, 0));
+ SkASSERT(overflows_short_shift(32768, 0));
+
+ // Since we expect these to succeed, we bit-or together
+ // for a tiny extra bit of speed.
+ return overflows_short_shift(rect.fLeft, SHIFT) |
+ overflows_short_shift(rect.fRight, SHIFT) |
+ overflows_short_shift(rect.fTop, SHIFT) |
+ overflows_short_shift(rect.fBottom, SHIFT);
+}
+
+static bool safeRoundOut(const SkRect& src, SkIRect* dst, int32_t maxInt) {
+#ifdef SK_SCALAR_IS_FIXED
+ // the max-int (shifted) is exactly what we want to compare against, to know
+ // if we can survive shifting our fixed-point coordinates
+ const SkFixed maxScalar = maxInt;
+#else
+ const SkScalar maxScalar = SkIntToScalar(maxInt);
+#endif
+ if (fitsInsideLimit(src, maxScalar)) {
+ src.roundOut(dst);
+ return true;
+ }
+ return false;
+}
+
+void SkScan::AntiFillPath(const SkPath& path, const SkRegion& origClip,
+ SkBlitter* blitter, bool forceRLE) {
+ if (origClip.isEmpty()) {
+ return;
+ }
+
+ SkIRect ir;
+
+ if (!safeRoundOut(path.getBounds(), &ir, SK_MaxS32 >> SHIFT)) {
+#if 0
+ const SkRect& r = path.getBounds();
+ SkDebugf("--- bounds can't fit in SkIRect\n", r.fLeft, r.fTop, r.fRight, r.fBottom);
+#endif
+ return;
+ }
+ if (ir.isEmpty()) {
+ if (path.isInverseFillType()) {
+ blitter->blitRegion(origClip);
+ }
+ return;
+ }
+
+ // If the intersection of the path bounds and the clip bounds
+ // will overflow 32767 when << by SHIFT, we can't supersample,
+ // so draw without antialiasing.
+ SkIRect clippedIR;
+ if (path.isInverseFillType()) {
+ // If the path is an inverse fill, it's going to fill the entire
+ // clip, and we care whether the entire clip exceeds our limits.
+ clippedIR = origClip.getBounds();
+ } else {
+ if (!clippedIR.intersect(ir, origClip.getBounds())) {
+ return;
+ }
+ }
+ if (rect_overflows_short_shift(clippedIR, SHIFT)) {
+ SkScan::FillPath(path, origClip, blitter);
+ return;
+ }
+
+ // Our antialiasing can't handle a clip larger than 32767, so we restrict
+ // the clip to that limit here. (the runs[] uses int16_t for its index).
+ //
+ // A more general solution (one that could also eliminate the need to
+ // disable aa based on ir bounds (see overflows_short_shift) would be
+ // to tile the clip/target...
+ SkRegion tmpClipStorage;
+ const SkRegion* clipRgn = &origClip;
+ {
+ static const int32_t kMaxClipCoord = 32767;
+ const SkIRect& bounds = origClip.getBounds();
+ if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) {
+ SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord };
+ tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op);
+ clipRgn = &tmpClipStorage;
+ }
+ }
+ // for here down, use clipRgn, not origClip
+
+ SkScanClipper clipper(blitter, clipRgn, ir);
+ const SkIRect* clipRect = clipper.getClipRect();
+
+ if (clipper.getBlitter() == NULL) { // clipped out
+ if (path.isInverseFillType()) {
+ blitter->blitRegion(*clipRgn);
+ }
+ return;
+ }
+
+ // now use the (possibly wrapped) blitter
+ blitter = clipper.getBlitter();
+
+ if (path.isInverseFillType()) {
+ sk_blit_above(blitter, ir, *clipRgn);
+ }
+
+ SkIRect superRect, *superClipRect = NULL;
+
+ if (clipRect) {
+ superRect.set( clipRect->fLeft << SHIFT, clipRect->fTop << SHIFT,
+ clipRect->fRight << SHIFT, clipRect->fBottom << SHIFT);
+ superClipRect = &superRect;
+ }
+
+ SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+
+ // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it
+ // if we're an inverse filltype
+ if (!path.isInverseFillType() && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) {
+ MaskSuperBlitter superBlit(blitter, ir, *clipRgn);
+ SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+ sk_fill_path(path, superClipRect, &superBlit, ir.fTop, ir.fBottom, SHIFT, *clipRgn);
+ } else {
+ SuperBlitter superBlit(blitter, ir, *clipRgn);
+ sk_fill_path(path, superClipRect, &superBlit, ir.fTop, ir.fBottom, SHIFT, *clipRgn);
+ }
+
+ if (path.isInverseFillType()) {
+ sk_blit_below(blitter, ir, *clipRgn);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkRasterClip.h"
+
+void SkScan::FillPath(const SkPath& path, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty()) {
+ return;
+ }
+
+ if (clip.isBW()) {
+ FillPath(path, clip.bwRgn(), blitter);
+ } else {
+ SkRegion tmp;
+ SkAAClipBlitter aaBlitter;
+
+ tmp.setRect(clip.getBounds());
+ aaBlitter.init(blitter, &clip.aaRgn());
+ SkScan::FillPath(path, tmp, &aaBlitter);
+ }
+}
+
+void SkScan::AntiFillPath(const SkPath& path, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty()) {
+ return;
+ }
+
+ if (clip.isBW()) {
+ AntiFillPath(path, clip.bwRgn(), blitter);
+ } else {
+ SkRegion tmp;
+ SkAAClipBlitter aaBlitter;
+
+ tmp.setRect(clip.getBounds());
+ aaBlitter.init(blitter, &clip.aaRgn());
+ SkScan::AntiFillPath(path, tmp, &aaBlitter, true);
+ }
+}
diff --git a/core/SkScan_Antihair.cpp b/core/SkScan_Antihair.cpp
new file mode 100644
index 00000000..a6a0869f
--- /dev/null
+++ b/core/SkScan_Antihair.cpp
@@ -0,0 +1,1064 @@
+
+/*
+ * 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 "SkScan.h"
+#include "SkBlitter.h"
+#include "SkColorPriv.h"
+#include "SkLineClipper.h"
+#include "SkRasterClip.h"
+#include "SkFDot6.h"
+
+/* Our attempt to compute the worst case "bounds" for the horizontal and
+ vertical cases has some numerical bug in it, and we sometimes undervalue
+ our extends. The bug is that when this happens, we will set the clip to
+ NULL (for speed), and thus draw outside of the clip by a pixel, which might
+ only look bad, but it might also access memory outside of the valid range
+ allcoated for the device bitmap.
+
+ This define enables our fix to outset our "bounds" by 1, thus avoiding the
+ chance of the bug, but at the cost of sometimes taking the rectblitter
+ case (i.e. not setting the clip to NULL) when we might not actually need
+ to. If we can improve/fix the actual calculations, then we can remove this
+ step.
+ */
+#define OUTSET_BEFORE_CLIP_TEST true
+
+#define HLINE_STACK_BUFFER 100
+
+static inline int SmallDot6Scale(int value, int dot6) {
+ SkASSERT((int16_t)value == value);
+ SkASSERT((unsigned)dot6 <= 64);
+ return SkMulS16(value, dot6) >> 6;
+}
+
+//#define TEST_GAMMA
+
+#ifdef TEST_GAMMA
+ static uint8_t gGammaTable[256];
+ #define ApplyGamma(table, alpha) (table)[alpha]
+
+ static void build_gamma_table() {
+ static bool gInit = false;
+
+ if (gInit == false) {
+ for (int i = 0; i < 256; i++) {
+ SkFixed n = i * 257;
+ n += n >> 15;
+ SkASSERT(n >= 0 && n <= SK_Fixed1);
+ n = SkFixedSqrt(n);
+ n = n * 255 >> 16;
+ // SkDebugf("morph %d -> %d\n", i, n);
+ gGammaTable[i] = SkToU8(n);
+ }
+ gInit = true;
+ }
+ }
+#else
+ #define ApplyGamma(table, alpha) SkToU8(alpha)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void call_hline_blitter(SkBlitter* blitter, int x, int y, int count,
+ U8CPU alpha) {
+ SkASSERT(count > 0);
+
+ int16_t runs[HLINE_STACK_BUFFER + 1];
+ uint8_t aa[HLINE_STACK_BUFFER];
+
+ aa[0] = ApplyGamma(gGammaTable, alpha);
+ do {
+ int n = count;
+ if (n > HLINE_STACK_BUFFER) {
+ n = HLINE_STACK_BUFFER;
+ }
+ runs[0] = SkToS16(n);
+ runs[n] = 0;
+ blitter->blitAntiH(x, y, aa, runs);
+ x += n;
+ count -= n;
+ } while (count > 0);
+}
+
+class SkAntiHairBlitter {
+public:
+ SkAntiHairBlitter() : fBlitter(NULL) {}
+ virtual ~SkAntiHairBlitter() {}
+
+ SkBlitter* getBlitter() const { return fBlitter; }
+
+ void setup(SkBlitter* blitter) {
+ fBlitter = blitter;
+ }
+
+ 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;
+ }
+
+ virtual SkFixed drawLine(int x, int stopx, SkFixed fy,
+ SkFixed slope) SK_OVERRIDE {
+ SkASSERT(x < stopx);
+ int count = stopx - x;
+ fy += SK_Fixed1/2;
+
+ int y = fy >> 16;
+ uint8_t a = (uint8_t)(fy >> 8);
+
+ // 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);
+ }
+
+ 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();
+
+ int lower_y = fy >> 16;
+ uint8_t a = (uint8_t)(fy >> 8);
+ unsigned ma = SmallDot6Scale(a, mod64);
+ if (ma) {
+ aa[0] = ApplyGamma(gamma, ma);
+ 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);
+ }
+ ma = SmallDot6Scale(255 - a, mod64);
+ if (ma) {
+ aa[0] = ApplyGamma(gamma, ma);
+ 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;
+
+ return fy - SK_Fixed1/2;
+ }
+
+ 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);
+
+ return fy - SK_Fixed1/2;
+ }
+};
+
+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;
+ }
+
+ virtual SkFixed drawLine(int y, int stopy, SkFixed fx, SkFixed dx) SK_OVERRIDE {
+ SkASSERT(y < stopy);
+ SkASSERT(0 == dx);
+ fx += SK_Fixed1/2;
+
+ int x = fx >> 16;
+ int a = (uint8_t)(fx >> 8);
+
+ if (a) {
+ this->getBlitter()->blitV(x, y, stopy - y, a);
+ }
+ a = 255 - a;
+ if (a) {
+ this->getBlitter()->blitV(x - 1, y, stopy - y, a);
+ }
+
+ 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] = 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;
+ 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;
+
+ 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];
+
+ 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);
+ SkASSERT(b != 0);
+ return (a << 16) / b;
+}
+
+#define SkBITCOUNT(x) (sizeof(x) << 3)
+
+#if 1
+// returns high-bit set iff x==0x8000...
+static inline int bad_int(int x) {
+ return x & -x;
+}
+
+static int any_bad_ints(int a, int b, int c, int d) {
+ return (bad_int(a) | bad_int(b) | bad_int(c) | bad_int(d)) >> (SkBITCOUNT(int) - 1);
+}
+#else
+static inline int good_int(int x) {
+ return x ^ (1 << (SkBITCOUNT(x) - 1));
+}
+
+static int any_bad_ints(int a, int b, int c, int d) {
+ return !(good_int(a) & good_int(b) & good_int(c) & good_int(d));
+}
+#endif
+
+#ifdef SK_DEBUG
+static bool canConvertFDot6ToFixed(SkFDot6 x) {
+ const int maxDot6 = SK_MaxS32 >> (16 - 6);
+ return SkAbs32(x) <= maxDot6;
+}
+#endif
+
+/*
+ * 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)
+ // It appears typically from a huge float (inf or nan) being converted to int.
+ // If we see it, just don't draw.
+ if (any_bad_ints(x0, y0, x1, y1)) {
+ return;
+ }
+
+ // The caller must clip the line to [-32767.0 ... 32767.0] ahead of time
+ // (in dot6 format)
+ SkASSERT(canConvertFDot6ToFixed(x0));
+ SkASSERT(canConvertFDot6ToFixed(y0));
+ SkASSERT(canConvertFDot6ToFixed(x1));
+ SkASSERT(canConvertFDot6ToFixed(y1));
+
+ if (SkAbs32(x1 - x0) > SkIntToFDot6(511) || SkAbs32(y1 - y0) > SkIntToFDot6(511)) {
+ /* instead of (x0 + x1) >> 1, we shift each separately. This is less
+ precise, but avoids overflowing the intermediate result if the
+ values are huge. A better fix might be to clip the original pts
+ directly (i.e. do the divide), so we don't spend time subdividing
+ huge lines at all.
+ */
+ int hx = (x0 >> 1) + (x1 >> 1);
+ int hy = (y0 >> 1) + (y1 >> 1);
+ do_anti_hairline(x0, y0, hx, hy, clip, blitter);
+ do_anti_hairline(hx, hy, x1, y1, clip, blitter);
+ return;
+ }
+
+ int scaleStart, scaleStop;
+ int istart, istop;
+ SkFixed fstart, slope;
+
+ 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
+ SkTSwap<SkFDot6>(x0, x1);
+ SkTSwap<SkFDot6>(y0, y1);
+ }
+
+ istart = SkFDot6Floor(x0);
+ istop = SkFDot6Ceil(x1);
+ fstart = SkFDot6ToFixed(y0);
+ if (y0 == y1) { // completely horizontal, take fast case
+ slope = 0;
+ hairBlitter = &hline_blitter;
+ } else {
+ slope = fastfixdiv(y1 - y0, x1 - x0);
+ SkASSERT(slope >= -SK_Fixed1 && slope <= SK_Fixed1);
+ fstart += (slope * (32 - (x0 & 63)) + 32) >> 6;
+ 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;
+ } else {
+ scaleStart = 64 - (x0 & 63);
+ scaleStop = x1 & 63;
+ }
+
+ if (clip){
+ if (istart >= clip->fRight || istop <= clip->fLeft) {
+ return;
+ }
+ if (istart < clip->fLeft) {
+ 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;
+ scaleStop = 0; // so we don't draw this last column
+ }
+
+ SkASSERT(istart <= istop);
+ if (istart == istop) {
+ return;
+ }
+ // now test if our Y values are completely inside the clip
+ int top, bottom;
+ if (slope >= 0) { // T2B
+ top = SkFixedFloor(fstart - SK_FixedHalf);
+ bottom = SkFixedCeil(fstart + (istop - istart - 1) * slope + SK_FixedHalf);
+ } else { // B2T
+ bottom = SkFixedCeil(fstart + SK_FixedHalf);
+ top = SkFixedFloor(fstart + (istop - istart - 1) * slope - SK_FixedHalf);
+ }
+#ifdef OUTSET_BEFORE_CLIP_TEST
+ top -= 1;
+ bottom += 1;
+#endif
+ if (top >= clip->fBottom || bottom <= clip->fTop) {
+ return;
+ }
+ if (clip->fTop <= top && clip->fBottom >= bottom) {
+ clip = NULL;
+ }
+ }
+ } else { // mostly vertical
+ if (y0 > y1) { // we want to go top-to-bottom
+ SkTSwap<SkFDot6>(x0, x1);
+ SkTSwap<SkFDot6>(y0, y1);
+ }
+
+ istart = SkFDot6Floor(y0);
+ istop = SkFDot6Ceil(y1);
+ fstart = SkFDot6ToFixed(x0);
+ if (x0 == x1) {
+ if (y0 == y1) { // are we zero length?
+ return; // nothing to do
+ }
+ slope = 0;
+ hairBlitter = &vline_blitter;
+ } else {
+ slope = fastfixdiv(x1 - x0, y1 - y0);
+ SkASSERT(slope <= SK_Fixed1 && slope >= -SK_Fixed1);
+ fstart += (slope * (32 - (y0 & 63)) + 32) >> 6;
+ 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;
+ } else {
+ scaleStart = 64 - (y0 & 63);
+ scaleStop = y1 & 63;
+ }
+
+ if (clip) {
+ if (istart >= clip->fBottom || istop <= clip->fTop) {
+ return;
+ }
+ if (istart < clip->fTop) {
+ 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;
+ scaleStop = 0; // so we don't draw this last row
+ }
+
+ SkASSERT(istart <= istop);
+ if (istart == istop)
+ return;
+
+ // now test if our X values are completely inside the clip
+ int left, right;
+ if (slope >= 0) { // L2R
+ left = SkFixedFloor(fstart - SK_FixedHalf);
+ right = SkFixedCeil(fstart + (istop - istart - 1) * slope + SK_FixedHalf);
+ } else { // R2L
+ right = SkFixedCeil(fstart + SK_FixedHalf);
+ left = SkFixedFloor(fstart + (istop - istart - 1) * slope - SK_FixedHalf);
+ }
+#ifdef OUTSET_BEFORE_CLIP_TEST
+ left -= 1;
+ right += 1;
+#endif
+ if (left >= clip->fRight || right <= clip->fLeft) {
+ return;
+ }
+ if (clip->fLeft <= left && clip->fRight >= right) {
+ clip = NULL;
+ }
+ }
+ }
+
+ SkRectClipBlitter rectClipper;
+ if (clip) {
+ rectClipper.init(blitter, *clip);
+ blitter = &rectClipper;
+ }
+
+ 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 = hairBlitter->drawLine(istart, istart + fullSpans, fstart, slope);
+ }
+ if (scaleStop > 0) {
+ hairBlitter->drawCap(istop - 1, fstart, slope, scaleStop);
+ }
+}
+
+void SkScan::AntiHairLineRgn(const SkPoint& pt0, const SkPoint& pt1,
+ const SkRegion* clip, SkBlitter* blitter) {
+ if (clip && clip->isEmpty()) {
+ return;
+ }
+
+ SkASSERT(clip == NULL || !clip->getBounds().isEmpty());
+
+#ifdef TEST_GAMMA
+ build_gamma_table();
+#endif
+
+ SkPoint pts[2] = { pt0, pt1 };
+
+#ifdef SK_SCALAR_IS_FLOAT
+ // We have to pre-clip the line to fit in a SkFixed, so we just chop
+ // the line. TODO find a way to actually draw beyond that range.
+ {
+ SkRect fixedBounds;
+ const SkScalar max = SkIntToScalar(32767);
+ fixedBounds.set(-max, -max, max, max);
+ if (!SkLineClipper::IntersectLine(pts, fixedBounds, pts)) {
+ return;
+ }
+ }
+#endif
+
+ if (clip) {
+ SkRect clipBounds;
+ clipBounds.set(clip->getBounds());
+ /* We perform integral clipping later on, but we do a scalar clip first
+ to ensure that our coordinates are expressible in fixed/integers.
+
+ antialiased hairlines can draw up to 1/2 of a pixel outside of
+ their bounds, so we need to outset the clip before calling the
+ clipper. To make the numerics safer, we outset by a whole pixel,
+ since the 1/2 pixel boundary is important to the antihair blitter,
+ we don't want to risk numerical fate by chopping on that edge.
+ */
+ clipBounds.inset(-SK_Scalar1, -SK_Scalar1);
+
+ if (!SkLineClipper::IntersectLine(pts, clipBounds, pts)) {
+ return;
+ }
+ }
+
+ SkFDot6 x0 = SkScalarToFDot6(pts[0].fX);
+ SkFDot6 y0 = SkScalarToFDot6(pts[0].fY);
+ SkFDot6 x1 = SkScalarToFDot6(pts[1].fX);
+ SkFDot6 y1 = SkScalarToFDot6(pts[1].fY);
+
+ if (clip) {
+ SkFDot6 left = SkMin32(x0, x1);
+ SkFDot6 top = SkMin32(y0, y1);
+ SkFDot6 right = SkMax32(x0, x1);
+ SkFDot6 bottom = SkMax32(y0, y1);
+ SkIRect ir;
+
+ ir.set( SkFDot6Floor(left) - 1,
+ SkFDot6Floor(top) - 1,
+ SkFDot6Ceil(right) + 1,
+ SkFDot6Ceil(bottom) + 1);
+
+ if (clip->quickReject(ir)) {
+ return;
+ }
+ if (!clip->quickContains(ir)) {
+ SkRegion::Cliperator iter(*clip, ir);
+ const SkIRect* r = &iter.rect();
+
+ while (!iter.done()) {
+ do_anti_hairline(x0, y0, x1, y1, r, blitter);
+ iter.next();
+ }
+ return;
+ }
+ // fall through to no-clip case
+ }
+ do_anti_hairline(x0, y0, x1, y1, NULL, blitter);
+}
+
+void SkScan::AntiHairRect(const SkRect& rect, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ SkPoint p0, p1;
+
+ p0.set(rect.fLeft, rect.fTop);
+ p1.set(rect.fRight, rect.fTop);
+ SkScan::AntiHairLine(p0, p1, clip, blitter);
+ p0.set(rect.fRight, rect.fBottom);
+ SkScan::AntiHairLine(p0, p1, clip, blitter);
+ p1.set(rect.fLeft, rect.fBottom);
+ SkScan::AntiHairLine(p0, p1, clip, blitter);
+ p0.set(rect.fLeft, rect.fTop);
+ SkScan::AntiHairLine(p0, p1, clip, blitter);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef int FDot8; // 24.8 integer fixed point
+
+static inline FDot8 SkFixedToFDot8(SkFixed x) {
+ return (x + 0x80) >> 8;
+}
+
+static void do_scanline(FDot8 L, int top, FDot8 R, U8CPU alpha,
+ SkBlitter* blitter) {
+ SkASSERT(L < R);
+
+ if ((L >> 8) == ((R - 1) >> 8)) { // 1x1 pixel
+ blitter->blitV(L >> 8, top, 1, SkAlphaMul(alpha, R - L));
+ return;
+ }
+
+ int left = L >> 8;
+
+ if (L & 0xFF) {
+ blitter->blitV(left, top, 1, SkAlphaMul(alpha, 256 - (L & 0xFF)));
+ left += 1;
+ }
+
+ int rite = R >> 8;
+ int width = rite - left;
+ if (width > 0) {
+ call_hline_blitter(blitter, left, top, width, alpha);
+ }
+ if (R & 0xFF) {
+ blitter->blitV(rite, top, 1, SkAlphaMul(alpha, R & 0xFF));
+ }
+}
+
+static void antifilldot8(FDot8 L, FDot8 T, FDot8 R, FDot8 B, SkBlitter* blitter,
+ bool fillInner) {
+ // check for empty now that we're in our reduced precision space
+ if (L >= R || T >= B) {
+ return;
+ }
+ int top = T >> 8;
+ if (top == ((B - 1) >> 8)) { // just one scanline high
+ do_scanline(L, top, R, B - T - 1, blitter);
+ return;
+ }
+
+ if (T & 0xFF) {
+ do_scanline(L, top, R, 256 - (T & 0xFF), blitter);
+ top += 1;
+ }
+
+ int bot = B >> 8;
+ int height = bot - top;
+ if (height > 0) {
+ int left = L >> 8;
+ if (left == ((R - 1) >> 8)) { // just 1-pixel wide
+ blitter->blitV(left, top, height, R - L - 1);
+ } else {
+ if (L & 0xFF) {
+ blitter->blitV(left, top, height, 256 - (L & 0xFF));
+ left += 1;
+ }
+ int rite = R >> 8;
+ int width = rite - left;
+ if (width > 0 && fillInner) {
+ blitter->blitRect(left, top, width, height);
+ }
+ if (R & 0xFF) {
+ blitter->blitV(rite, top, height, R & 0xFF);
+ }
+ }
+ }
+
+ if (B & 0xFF) {
+ do_scanline(L, bot, R, B & 0xFF, blitter);
+ }
+}
+
+static void antifillrect(const SkXRect& xr, SkBlitter* blitter) {
+ antifilldot8(SkFixedToFDot8(xr.fLeft), SkFixedToFDot8(xr.fTop),
+ SkFixedToFDot8(xr.fRight), SkFixedToFDot8(xr.fBottom),
+ blitter, true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScan::AntiFillXRect(const SkXRect& xr, const SkRegion* clip,
+ SkBlitter* blitter) {
+ if (NULL == clip) {
+ antifillrect(xr, blitter);
+ } else {
+ SkIRect outerBounds;
+ XRect_roundOut(xr, &outerBounds);
+
+ if (clip->isRect()) {
+ const SkIRect& clipBounds = clip->getBounds();
+
+ if (clipBounds.contains(outerBounds)) {
+ antifillrect(xr, blitter);
+ } else {
+ SkXRect tmpR;
+ // this keeps our original edges fractional
+ XRect_set(&tmpR, clipBounds);
+ if (tmpR.intersect(xr)) {
+ antifillrect(tmpR, blitter);
+ }
+ }
+ } else {
+ SkRegion::Cliperator clipper(*clip, outerBounds);
+ const SkIRect& rr = clipper.rect();
+
+ while (!clipper.done()) {
+ SkXRect tmpR;
+
+ // this keeps our original edges fractional
+ XRect_set(&tmpR, rr);
+ if (tmpR.intersect(xr)) {
+ antifillrect(tmpR, blitter);
+ }
+ clipper.next();
+ }
+ }
+ }
+}
+
+void SkScan::AntiFillXRect(const SkXRect& xr, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isBW()) {
+ AntiFillXRect(xr, &clip.bwRgn(), blitter);
+ } else {
+ SkIRect outerBounds;
+ XRect_roundOut(xr, &outerBounds);
+
+ if (clip.quickContains(outerBounds)) {
+ AntiFillXRect(xr, NULL, blitter);
+ } else {
+ SkAAClipBlitterWrapper wrapper(clip, blitter);
+ blitter = wrapper.getBlitter();
+
+ AntiFillXRect(xr, &wrapper.getRgn(), wrapper.getBlitter());
+ }
+ }
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+
+/* This guy takes a float-rect, but with the key improvement that it has
+ already been clipped, so we know that it is safe to convert it into a
+ XRect (fixedpoint), as it won't overflow.
+*/
+static void antifillrect(const SkRect& r, SkBlitter* blitter) {
+ SkXRect xr;
+
+ XRect_set(&xr, r);
+ antifillrect(xr, blitter);
+}
+
+/* We repeat the clipping logic of AntiFillXRect because the float rect might
+ overflow if we blindly converted it to an XRect. This sucks that we have to
+ repeat the clipping logic, but I don't see how to share the code/logic.
+
+ We clip r (as needed) into one or more (smaller) float rects, and then pass
+ those to our version of antifillrect, which converts it into an XRect and
+ then calls the blit.
+*/
+void SkScan::AntiFillRect(const SkRect& origR, const SkRegion* clip,
+ SkBlitter* blitter) {
+ if (clip) {
+ SkRect newR;
+ newR.set(clip->getBounds());
+ if (!newR.intersect(origR)) {
+ return;
+ }
+
+ SkIRect outerBounds;
+ newR.roundOut(&outerBounds);
+
+ if (clip->isRect()) {
+ antifillrect(newR, blitter);
+ } else {
+ SkRegion::Cliperator clipper(*clip, outerBounds);
+ while (!clipper.done()) {
+ newR.set(clipper.rect());
+ if (newR.intersect(origR)) {
+ antifillrect(newR, blitter);
+ }
+ clipper.next();
+ }
+ }
+ } else {
+ antifillrect(origR, blitter);
+ }
+}
+
+void SkScan::AntiFillRect(const SkRect& r, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isBW()) {
+ AntiFillRect(r, &clip.bwRgn(), blitter);
+ } else {
+ SkAAClipBlitterWrapper wrap(clip, blitter);
+ AntiFillRect(r, &wrap.getRgn(), wrap.getBlitter());
+ }
+}
+
+#endif // SK_SCALAR_IS_FLOAT
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define SkAlphaMulRound(a, b) SkMulDiv255Round(a, b)
+
+// calls blitRect() if the rectangle is non-empty
+static void fillcheckrect(int L, int T, int R, int B, SkBlitter* blitter) {
+ if (L < R && T < B) {
+ blitter->blitRect(L, T, R - L, B - T);
+ }
+}
+
+static inline FDot8 SkScalarToFDot8(SkScalar x) {
+#ifdef SK_SCALAR_IS_FLOAT
+ return (int)(x * 256);
+#else
+ return x >> 8;
+#endif
+}
+
+static inline int FDot8Floor(FDot8 x) {
+ return x >> 8;
+}
+
+static inline int FDot8Ceil(FDot8 x) {
+ return (x + 0xFF) >> 8;
+}
+
+// 1 - (1 - a)*(1 - b)
+static inline U8CPU InvAlphaMul(U8CPU a, U8CPU b) {
+ // need precise rounding (not just SkAlphaMul) so that values like
+ // a=228, b=252 don't overflow the result
+ return SkToU8(a + b - SkAlphaMulRound(a, b));
+}
+
+static void inner_scanline(FDot8 L, int top, FDot8 R, U8CPU alpha,
+ SkBlitter* blitter) {
+ SkASSERT(L < R);
+
+ if ((L >> 8) == ((R - 1) >> 8)) { // 1x1 pixel
+ blitter->blitV(L >> 8, top, 1, InvAlphaMul(alpha, R - L));
+ return;
+ }
+
+ int left = L >> 8;
+ if (L & 0xFF) {
+ blitter->blitV(left, top, 1, InvAlphaMul(alpha, L & 0xFF));
+ left += 1;
+ }
+
+ int rite = R >> 8;
+ int width = rite - left;
+ if (width > 0) {
+ call_hline_blitter(blitter, left, top, width, alpha);
+ }
+
+ if (R & 0xFF) {
+ blitter->blitV(rite, top, 1, InvAlphaMul(alpha, ~R & 0xFF));
+ }
+}
+
+static void innerstrokedot8(FDot8 L, FDot8 T, FDot8 R, FDot8 B,
+ SkBlitter* blitter) {
+ SkASSERT(L < R && T < B);
+
+ int top = T >> 8;
+ if (top == ((B - 1) >> 8)) { // just one scanline high
+ // We want the inverse of B-T, since we're the inner-stroke
+ int alpha = 256 - (B - T);
+ if (alpha) {
+ inner_scanline(L, top, R, alpha, blitter);
+ }
+ return;
+ }
+
+ if (T & 0xFF) {
+ inner_scanline(L, top, R, T & 0xFF, blitter);
+ top += 1;
+ }
+
+ int bot = B >> 8;
+ int height = bot - top;
+ if (height > 0) {
+ if (L & 0xFF) {
+ blitter->blitV(L >> 8, top, height, L & 0xFF);
+ }
+ if (R & 0xFF) {
+ blitter->blitV(R >> 8, top, height, ~R & 0xFF);
+ }
+ }
+
+ if (B & 0xFF) {
+ inner_scanline(L, bot, R, ~B & 0xFF, blitter);
+ }
+}
+
+void SkScan::AntiFrameRect(const SkRect& r, const SkPoint& strokeSize,
+ const SkRegion* clip, SkBlitter* blitter) {
+ SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0);
+
+ SkScalar rx = SkScalarHalf(strokeSize.fX);
+ SkScalar ry = SkScalarHalf(strokeSize.fY);
+
+ // outset by the radius
+ FDot8 L = SkScalarToFDot8(r.fLeft - rx);
+ FDot8 T = SkScalarToFDot8(r.fTop - ry);
+ FDot8 R = SkScalarToFDot8(r.fRight + rx);
+ FDot8 B = SkScalarToFDot8(r.fBottom + ry);
+
+ SkIRect outer;
+ // set outer to the outer rect of the outer section
+ outer.set(FDot8Floor(L), FDot8Floor(T), FDot8Ceil(R), FDot8Ceil(B));
+
+ SkBlitterClipper clipper;
+ if (clip) {
+ if (clip->quickReject(outer)) {
+ return;
+ }
+ if (!clip->contains(outer)) {
+ blitter = clipper.apply(blitter, clip, &outer);
+ }
+ // now we can ignore clip for the rest of the function
+ }
+
+ // stroke the outer hull
+ antifilldot8(L, T, R, B, blitter, false);
+
+ // set outer to the outer rect of the middle section
+ outer.set(FDot8Ceil(L), FDot8Ceil(T), FDot8Floor(R), FDot8Floor(B));
+
+ // in case we lost a bit with diameter/2
+ rx = strokeSize.fX - rx;
+ ry = strokeSize.fY - ry;
+ // inset by the radius
+ L = SkScalarToFDot8(r.fLeft + rx);
+ T = SkScalarToFDot8(r.fTop + ry);
+ R = SkScalarToFDot8(r.fRight - rx);
+ B = SkScalarToFDot8(r.fBottom - ry);
+
+ if (L >= R || T >= B) {
+ fillcheckrect(outer.fLeft, outer.fTop, outer.fRight, outer.fBottom,
+ blitter);
+ } else {
+ SkIRect inner;
+ // set inner to the inner rect of the middle section
+ inner.set(FDot8Floor(L), FDot8Floor(T), FDot8Ceil(R), FDot8Ceil(B));
+
+ // draw the frame in 4 pieces
+ fillcheckrect(outer.fLeft, outer.fTop, outer.fRight, inner.fTop,
+ blitter);
+ fillcheckrect(outer.fLeft, inner.fTop, inner.fLeft, inner.fBottom,
+ blitter);
+ fillcheckrect(inner.fRight, inner.fTop, outer.fRight, inner.fBottom,
+ blitter);
+ fillcheckrect(outer.fLeft, inner.fBottom, outer.fRight, outer.fBottom,
+ blitter);
+
+ // now stroke the inner rect, which is similar to antifilldot8() except that
+ // it treats the fractional coordinates with the inverse bias (since its
+ // inner).
+ innerstrokedot8(L, T, R, B, blitter);
+ }
+}
+
+void SkScan::AntiFrameRect(const SkRect& r, const SkPoint& strokeSize,
+ const SkRasterClip& clip, SkBlitter* blitter) {
+ if (clip.isBW()) {
+ AntiFrameRect(r, strokeSize, &clip.bwRgn(), blitter);
+ } else {
+ SkAAClipBlitterWrapper wrap(clip, blitter);
+ AntiFrameRect(r, strokeSize, &wrap.getRgn(), wrap.getBlitter());
+ }
+}
diff --git a/core/SkScan_Hairline.cpp b/core/SkScan_Hairline.cpp
new file mode 100644
index 00000000..f440d326
--- /dev/null
+++ b/core/SkScan_Hairline.cpp
@@ -0,0 +1,419 @@
+
+/*
+ * 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 "SkScan.h"
+#include "SkBlitter.h"
+#include "SkRasterClip.h"
+#include "SkFDot6.h"
+#include "SkLineClipper.h"
+
+static void horiline(int x, int stopx, SkFixed fy, SkFixed dy,
+ SkBlitter* blitter) {
+ SkASSERT(x < stopx);
+
+ do {
+ blitter->blitH(x, fy >> 16, 1);
+ fy += dy;
+ } while (++x < stopx);
+}
+
+static void vertline(int y, int stopy, SkFixed fx, SkFixed dx,
+ SkBlitter* blitter) {
+ SkASSERT(y < stopy);
+
+ do {
+ blitter->blitH(fx >> 16, y, 1);
+ fx += dx;
+ } while (++y < stopy);
+}
+
+#ifdef SK_DEBUG
+static bool canConvertFDot6ToFixed(SkFDot6 x) {
+ const int maxDot6 = SK_MaxS32 >> (16 - 6);
+ return SkAbs32(x) <= maxDot6;
+}
+#endif
+
+void SkScan::HairLineRgn(const SkPoint& pt0, const SkPoint& pt1,
+ const SkRegion* clip, SkBlitter* blitter) {
+ SkBlitterClipper clipper;
+ SkRect r;
+ SkIRect clipR, ptsR;
+ SkPoint pts[2] = { pt0, pt1 };
+
+#ifdef SK_SCALAR_IS_FLOAT
+ // We have to pre-clip the line to fit in a SkFixed, so we just chop
+ // the line. TODO find a way to actually draw beyond that range.
+ {
+ SkRect fixedBounds;
+ const SkScalar max = SkIntToScalar(32767);
+ fixedBounds.set(-max, -max, max, max);
+ if (!SkLineClipper::IntersectLine(pts, fixedBounds, pts)) {
+ return;
+ }
+ }
+#endif
+
+ if (clip) {
+ // Perform a clip in scalar space, so we catch huge values which might
+ // be missed after we convert to SkFDot6 (overflow)
+ r.set(clip->getBounds());
+ if (!SkLineClipper::IntersectLine(pts, r, pts)) {
+ return;
+ }
+ }
+
+ SkFDot6 x0 = SkScalarToFDot6(pts[0].fX);
+ SkFDot6 y0 = SkScalarToFDot6(pts[0].fY);
+ SkFDot6 x1 = SkScalarToFDot6(pts[1].fX);
+ SkFDot6 y1 = SkScalarToFDot6(pts[1].fY);
+
+ SkASSERT(canConvertFDot6ToFixed(x0));
+ SkASSERT(canConvertFDot6ToFixed(y0));
+ SkASSERT(canConvertFDot6ToFixed(x1));
+ SkASSERT(canConvertFDot6ToFixed(y1));
+
+ if (clip) {
+ // now perform clipping again, as the rounding to dot6 can wiggle us
+ // our rects are really dot6 rects, but since we've already used
+ // lineclipper, we know they will fit in 32bits (26.6)
+ const SkIRect& bounds = clip->getBounds();
+
+ clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop),
+ SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom));
+ ptsR.set(x0, y0, x1, y1);
+ ptsR.sort();
+
+ // outset the right and bottom, to account for how hairlines are
+ // actually drawn, which may hit the pixel to the right or below of
+ // the coordinate
+ ptsR.fRight += SK_FDot6One;
+ ptsR.fBottom += SK_FDot6One;
+
+ if (!SkIRect::Intersects(ptsR, clipR)) {
+ return;
+ }
+ if (clip->isRect() && clipR.contains(ptsR)) {
+ clip = NULL;
+ } else {
+ blitter = clipper.apply(blitter, clip);
+ }
+ }
+
+ SkFDot6 dx = x1 - x0;
+ SkFDot6 dy = y1 - y0;
+
+ if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal
+ if (x0 > x1) { // we want to go left-to-right
+ SkTSwap<SkFDot6>(x0, x1);
+ SkTSwap<SkFDot6>(y0, y1);
+ }
+ int ix0 = SkFDot6Round(x0);
+ int ix1 = SkFDot6Round(x1);
+ if (ix0 == ix1) {// too short to draw
+ return;
+ }
+
+ SkFixed slope = SkFixedDiv(dy, dx);
+ SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
+
+ horiline(ix0, ix1, startY, slope, blitter);
+ } else { // mostly vertical
+ if (y0 > y1) { // we want to go top-to-bottom
+ SkTSwap<SkFDot6>(x0, x1);
+ SkTSwap<SkFDot6>(y0, y1);
+ }
+ int iy0 = SkFDot6Round(y0);
+ int iy1 = SkFDot6Round(y1);
+ if (iy0 == iy1) { // too short to draw
+ return;
+ }
+
+ SkFixed slope = SkFixedDiv(dx, dy);
+ SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
+
+ vertline(iy0, iy1, startX, slope, blitter);
+ }
+}
+
+// we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right
+// and double-hit the top-left.
+// TODO: handle huge coordinates on rect (before calling SkScalarToFixed)
+void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ SkAAClipBlitterWrapper wrapper;
+ SkBlitterClipper clipper;
+ SkIRect r;
+
+ r.set(SkScalarToFixed(rect.fLeft) >> 16,
+ SkScalarToFixed(rect.fTop) >> 16,
+ (SkScalarToFixed(rect.fRight) >> 16) + 1,
+ (SkScalarToFixed(rect.fBottom) >> 16) + 1);
+
+ if (clip.quickReject(r)) {
+ return;
+ }
+ if (!clip.quickContains(r)) {
+ const SkRegion* clipRgn;
+ if (clip.isBW()) {
+ clipRgn = &clip.bwRgn();
+ } else {
+ wrapper.init(clip, blitter);
+ clipRgn = &wrapper.getRgn();
+ blitter = wrapper.getBlitter();
+ }
+ blitter = clipper.apply(blitter, clipRgn);
+ }
+
+ int width = r.width();
+ int height = r.height();
+
+ if ((width | height) == 0) {
+ return;
+ }
+ if (width <= 2 || height <= 2) {
+ blitter->blitRect(r.fLeft, r.fTop, width, height);
+ return;
+ }
+ // if we get here, we know we have 4 segments to draw
+ blitter->blitH(r.fLeft, r.fTop, width); // top
+ blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2); // left
+ blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right
+ blitter->blitH(r.fLeft, r.fBottom - 1, width); // bottom
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkPath.h"
+#include "SkGeometry.h"
+
+static int compute_int_quad_dist(const SkPoint pts[3]) {
+ // compute the vector between the control point ([1]) and the middle of the
+ // line connecting the start and end ([0] and [2])
+ SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
+ SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY;
+ // we want everyone to be positive
+ dx = SkScalarAbs(dx);
+ dy = SkScalarAbs(dy);
+ // convert to whole pixel values (use ceiling to be conservative)
+ int idx = SkScalarCeil(dx);
+ int idy = SkScalarCeil(dy);
+ // use the cheap approx for distance
+ if (idx > idy) {
+ return idx + (idy >> 1);
+ } else {
+ return idy + (idx >> 1);
+ }
+}
+
+typedef void (*LineProc)(const SkPoint&, const SkPoint&, const SkRegion*,
+ SkBlitter*);
+
+static void hairquad(const SkPoint pts[3], const SkRegion* clip,
+ SkBlitter* blitter, int level, LineProc lineproc) {
+ if (level > 0) {
+ SkPoint tmp[5];
+
+ SkChopQuadAtHalf(pts, tmp);
+ hairquad(tmp, clip, blitter, level - 1, lineproc);
+ hairquad(&tmp[2], clip, blitter, level - 1, lineproc);
+ } else {
+ lineproc(pts[0], pts[2], clip, blitter);
+ }
+}
+
+static void haircubic(const SkPoint pts[4], const SkRegion* clip,
+ SkBlitter* blitter, int level, LineProc lineproc) {
+ if (level > 0) {
+ SkPoint tmp[7];
+
+ SkChopCubicAt(pts, tmp, SK_Scalar1/2);
+ haircubic(tmp, clip, blitter, level - 1, lineproc);
+ haircubic(&tmp[3], clip, blitter, level - 1, lineproc);
+ } else {
+ lineproc(pts[0], pts[3], clip, blitter);
+ }
+}
+
+#define kMaxCubicSubdivideLevel 6
+#define kMaxQuadSubdivideLevel 5
+
+static int compute_quad_level(const SkPoint pts[3]) {
+ int d = compute_int_quad_dist(pts);
+ /* quadratics approach the line connecting their start and end points
+ 4x closer with each subdivision, so we compute the number of
+ subdivisions to be the minimum need to get that distance to be less
+ than a pixel.
+ */
+ int level = (33 - SkCLZ(d)) >> 1;
+ // sanity check on level (from the previous version)
+ if (level > kMaxQuadSubdivideLevel) {
+ level = kMaxQuadSubdivideLevel;
+ }
+ return level;
+}
+
+static void hair_path(const SkPath& path, const SkRasterClip& rclip,
+ SkBlitter* blitter, LineProc lineproc) {
+ if (path.isEmpty()) {
+ return;
+ }
+
+ SkAAClipBlitterWrapper wrap;
+ const SkRegion* clip = NULL;
+
+ {
+ SkIRect ibounds;
+ path.getBounds().roundOut(&ibounds);
+ ibounds.inset(-1, -1);
+
+ if (rclip.quickReject(ibounds)) {
+ return;
+ }
+ if (!rclip.quickContains(ibounds)) {
+ if (rclip.isBW()) {
+ clip = &rclip.bwRgn();
+ } else {
+ wrap.init(rclip, blitter);
+ blitter = wrap.getBlitter();
+ clip = &wrap.getRgn();
+ }
+ }
+ }
+
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ SkAutoConicToQuads converter;
+
+ while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ break;
+ case SkPath::kLine_Verb:
+ lineproc(pts[0], pts[1], clip, blitter);
+ break;
+ case SkPath::kQuad_Verb:
+ hairquad(pts, clip, blitter, compute_quad_level(pts), lineproc);
+ break;
+ case SkPath::kConic_Verb: {
+ // how close should the quads be to the original conic?
+ const SkScalar tol = SK_Scalar1 / 4;
+ const SkPoint* quadPts = converter.computeQuads(pts,
+ iter.conicWeight(), tol);
+ for (int i = 0; i < converter.countQuads(); ++i) {
+ int level = compute_quad_level(quadPts);
+ hairquad(quadPts, clip, blitter, level, lineproc);
+ quadPts += 2;
+ }
+ break;
+ }
+ case SkPath::kCubic_Verb:
+ haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc);
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ case SkPath::kDone_Verb:
+ break;
+ }
+ }
+}
+
+void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ hair_path(path, clip, blitter, SkScan::HairLineRgn);
+}
+
+void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ hair_path(path, clip, blitter, SkScan::AntiHairLineRgn);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize,
+ const SkRasterClip& clip, SkBlitter* blitter) {
+ SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0);
+
+ if (strokeSize.fX < 0 || strokeSize.fY < 0) {
+ return;
+ }
+
+ const SkScalar dx = strokeSize.fX;
+ const SkScalar dy = strokeSize.fY;
+ SkScalar rx = SkScalarHalf(dx);
+ SkScalar ry = SkScalarHalf(dy);
+ SkRect outer, tmp;
+
+ outer.set(r.fLeft - rx, r.fTop - ry,
+ r.fRight + rx, r.fBottom + ry);
+
+ if (r.width() <= dx || r.height() <= dx) {
+ SkScan::FillRect(outer, clip, blitter);
+ return;
+ }
+
+ tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy);
+ SkScan::FillRect(tmp, clip, blitter);
+ tmp.fTop = outer.fBottom - dy;
+ tmp.fBottom = outer.fBottom;
+ SkScan::FillRect(tmp, clip, blitter);
+
+ tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy);
+ SkScan::FillRect(tmp, clip, blitter);
+ tmp.fLeft = outer.fRight - dx;
+ tmp.fRight = outer.fRight;
+ SkScan::FillRect(tmp, clip, blitter);
+}
+
+void SkScan::HairLine(const SkPoint& p0, const SkPoint& p1,
+ const SkRasterClip& clip, SkBlitter* blitter) {
+ if (clip.isBW()) {
+ HairLineRgn(p0, p1, &clip.bwRgn(), blitter);
+ } else {
+ const SkRegion* clipRgn = NULL;
+ SkRect r;
+ SkIRect ir;
+ r.set(p0.fX, p0.fY, p1.fX, p1.fY);
+ r.sort();
+ r.inset(-SK_ScalarHalf, -SK_ScalarHalf);
+ r.roundOut(&ir);
+
+ SkAAClipBlitterWrapper wrap;
+ if (!clip.quickContains(ir)) {
+ wrap.init(clip, blitter);
+ blitter = wrap.getBlitter();
+ clipRgn = &wrap.getRgn();
+ }
+ HairLineRgn(p0, p1, clipRgn, blitter);
+ }
+}
+
+void SkScan::AntiHairLine(const SkPoint& p0, const SkPoint& p1,
+ const SkRasterClip& clip, SkBlitter* blitter) {
+ if (clip.isBW()) {
+ AntiHairLineRgn(p0, p1, &clip.bwRgn(), blitter);
+ } else {
+ const SkRegion* clipRgn = NULL;
+ SkRect r;
+ SkIRect ir;
+ r.set(p0.fX, p0.fY, p1.fX, p1.fY);
+ r.sort();
+ r.roundOut(&ir);
+ ir.inset(-1, -1);
+
+ SkAAClipBlitterWrapper wrap;
+ if (!clip.quickContains(ir)) {
+ wrap.init(clip, blitter);
+ blitter = wrap.getBlitter();
+ clipRgn = &wrap.getRgn();
+ }
+ AntiHairLineRgn(p0, p1, clipRgn, blitter);
+ }
+}
diff --git a/core/SkScan_Path.cpp b/core/SkScan_Path.cpp
new file mode 100644
index 00000000..66e95076
--- /dev/null
+++ b/core/SkScan_Path.cpp
@@ -0,0 +1,729 @@
+/*
+ * 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 "SkScanPriv.h"
+#include "SkBlitter.h"
+#include "SkEdge.h"
+#include "SkEdgeBuilder.h"
+#include "SkGeometry.h"
+#include "SkPath.h"
+#include "SkQuadClipper.h"
+#include "SkRasterClip.h"
+#include "SkRegion.h"
+#include "SkTemplates.h"
+#include "SkTSort.h"
+
+#ifdef SK_USE_LEGACY_AA_COVERAGE
+ #define SK_USE_STD_SORT_FOR_EDGES
+#endif
+
+#define kEDGE_HEAD_Y SK_MinS32
+#define kEDGE_TAIL_Y SK_MaxS32
+
+#ifdef SK_DEBUG
+ static void validate_sort(const SkEdge* edge) {
+ int y = kEDGE_HEAD_Y;
+
+ while (edge->fFirstY != SK_MaxS32) {
+ edge->validate();
+ SkASSERT(y <= edge->fFirstY);
+
+ y = edge->fFirstY;
+ edge = edge->fNext;
+ }
+ }
+#else
+ #define validate_sort(edge)
+#endif
+
+static inline void remove_edge(SkEdge* edge) {
+ edge->fPrev->fNext = edge->fNext;
+ edge->fNext->fPrev = edge->fPrev;
+}
+
+static inline void swap_edges(SkEdge* prev, SkEdge* 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(SkEdge* edge SkDECLAREPARAM(int, curr_y)) {
+ SkFixed x = edge->fX;
+
+ for (;;) {
+ SkEdge* 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 && prev->fFirstY <= curr_y + 1);
+
+ if (prev->fX <= x) {
+ break;
+ }
+ swap_edges(prev, edge);
+ }
+}
+
+static void insert_new_edges(SkEdge* newEdge, int curr_y) {
+ SkASSERT(newEdge->fFirstY >= curr_y);
+
+ while (newEdge->fFirstY == curr_y) {
+ SkEdge* next = newEdge->fNext;
+ backward_insert_edge_based_on_x(newEdge SkPARAM(curr_y));
+ newEdge = next;
+ }
+}
+
+#ifdef SK_DEBUG
+static void validate_edges_for_y(const SkEdge* edge, int curr_y) {
+ while (edge->fFirstY <= curr_y) {
+ SkASSERT(edge->fPrev && edge->fNext);
+ SkASSERT(edge->fPrev->fNext == edge);
+ SkASSERT(edge->fNext->fPrev == edge);
+ SkASSERT(edge->fFirstY <= edge->fLastY);
+
+ SkASSERT(edge->fPrev->fX <= edge->fX);
+ edge = edge->fNext;
+ }
+}
+#else
+ #define validate_edges_for_y(edge, curr_y)
+#endif
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable warning : local variable used without having been initialized
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+typedef void (*PrePostProc)(SkBlitter* blitter, int y, bool isStartOfScanline);
+#define PREPOST_START true
+#define PREPOST_END false
+
+static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType,
+ SkBlitter* blitter, int start_y, int stop_y,
+ PrePostProc proc) {
+ validate_sort(prevHead->fNext);
+
+ int curr_y = start_y;
+ // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
+ int windingMask = (fillType & 1) ? 1 : -1;
+
+ for (;;) {
+ int w = 0;
+ int left SK_INIT_TO_AVOID_WARNING;
+ bool in_interval = false;
+ SkEdge* currE = prevHead->fNext;
+ SkFixed prevX = prevHead->fX;
+
+ validate_edges_for_y(currE, curr_y);
+
+ if (proc) {
+ proc(blitter, curr_y, PREPOST_START); // pre-proc
+ }
+
+ while (currE->fFirstY <= curr_y) {
+ SkASSERT(currE->fLastY >= curr_y);
+
+ int x = SkFixedRoundToInt(currE->fX);
+ w += currE->fWinding;
+ if ((w & windingMask) == 0) { // we finished an interval
+ SkASSERT(in_interval);
+ int width = x - left;
+ SkASSERT(width >= 0);
+ if (width)
+ blitter->blitH(left, curr_y, width);
+ in_interval = false;
+ } else if (!in_interval) {
+ left = x;
+ in_interval = true;
+ }
+
+ SkEdge* next = currE->fNext;
+ SkFixed newX;
+
+ if (currE->fLastY == curr_y) { // are we done with this edge?
+ if (currE->fCurveCount < 0) {
+ if (((SkCubicEdge*)currE)->updateCubic()) {
+ SkASSERT(currE->fFirstY == curr_y + 1);
+
+ newX = currE->fX;
+ goto NEXT_X;
+ }
+ } else if (currE->fCurveCount > 0) {
+ if (((SkQuadraticEdge*)currE)->updateQuadratic()) {
+ newX = currE->fX;
+ goto NEXT_X;
+ }
+ }
+ remove_edge(currE);
+ } else {
+ SkASSERT(currE->fLastY > curr_y);
+ newX = currE->fX + currE->fDX;
+ currE->fX = newX;
+ NEXT_X:
+ if (newX < prevX) { // ripple currE backwards until it is x-sorted
+ backward_insert_edge_based_on_x(currE SkPARAM(curr_y));
+ } else {
+ prevX = newX;
+ }
+ }
+ currE = next;
+ SkASSERT(currE);
+ }
+
+ if (proc) {
+ proc(blitter, curr_y, PREPOST_END); // post-proc
+ }
+
+ curr_y += 1;
+ if (curr_y >= stop_y) {
+ break;
+ }
+ // now currE points to the first edge with a Yint larger than curr_y
+ insert_new_edges(currE, curr_y);
+ }
+}
+
+// return true if we're done with this edge
+static bool update_edge(SkEdge* edge, int last_y) {
+ SkASSERT(edge->fLastY >= last_y);
+ if (last_y == edge->fLastY) {
+ if (edge->fCurveCount < 0) {
+ if (((SkCubicEdge*)edge)->updateCubic()) {
+ SkASSERT(edge->fFirstY == last_y + 1);
+ return false;
+ }
+ } else if (edge->fCurveCount > 0) {
+ if (((SkQuadraticEdge*)edge)->updateQuadratic()) {
+ SkASSERT(edge->fFirstY == last_y + 1);
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,
+ SkBlitter* blitter, int start_y, int stop_y,
+ PrePostProc proc) {
+ validate_sort(prevHead->fNext);
+
+ SkEdge* leftE = prevHead->fNext;
+ SkEdge* riteE = leftE->fNext;
+ SkEdge* currE = riteE->fNext;
+
+#if 0
+ int local_top = leftE->fFirstY;
+ SkASSERT(local_top == riteE->fFirstY);
+#else
+ // our edge choppers for curves can result in the initial edges
+ // not lining up, so we take the max.
+ int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);
+#endif
+ SkASSERT(local_top >= start_y);
+
+ for (;;) {
+ SkASSERT(leftE->fFirstY <= stop_y);
+ SkASSERT(riteE->fFirstY <= stop_y);
+
+ if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&
+ leftE->fDX > riteE->fDX)) {
+ SkTSwap(leftE, riteE);
+ }
+
+ int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);
+ local_bot = SkMin32(local_bot, stop_y - 1);
+ SkASSERT(local_top <= local_bot);
+
+ SkFixed left = leftE->fX;
+ SkFixed dLeft = leftE->fDX;
+ SkFixed rite = riteE->fX;
+ SkFixed dRite = riteE->fDX;
+ int count = local_bot - local_top;
+ SkASSERT(count >= 0);
+ if (0 == (dLeft | dRite)) {
+ int L = SkFixedRoundToInt(left);
+ int R = SkFixedRoundToInt(rite);
+ if (L < R) {
+ count += 1;
+ blitter->blitRect(L, local_top, R - L, count);
+ left += count * dLeft;
+ rite += count * dRite;
+ }
+ local_top = local_bot + 1;
+ } else {
+ do {
+ int L = SkFixedRoundToInt(left);
+ int R = SkFixedRoundToInt(rite);
+ if (L < R) {
+ blitter->blitH(L, local_top, R - L);
+ }
+ left += dLeft;
+ rite += dRite;
+ local_top += 1;
+ } while (--count >= 0);
+ }
+
+ leftE->fX = left;
+ riteE->fX = rite;
+
+ if (update_edge(leftE, local_bot)) {
+ if (currE->fFirstY >= stop_y) {
+ break;
+ }
+ leftE = currE;
+ currE = currE->fNext;
+ }
+ if (update_edge(riteE, local_bot)) {
+ if (currE->fFirstY >= stop_y) {
+ break;
+ }
+ riteE = currE;
+ currE = currE->fNext;
+ }
+
+ SkASSERT(leftE);
+ SkASSERT(riteE);
+
+ // check our bottom clip
+ SkASSERT(local_top == local_bot + 1);
+ if (local_top >= stop_y) {
+ break;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// this guy overrides blitH, and will call its proxy blitter with the inverse
+// of the spans it is given (clipped to the left/right of the cliprect)
+//
+// used to implement inverse filltypes on paths
+//
+class InverseBlitter : public SkBlitter {
+public:
+ void setBlitter(SkBlitter* blitter, const SkIRect& clip, int shift) {
+ fBlitter = blitter;
+ fFirstX = clip.fLeft << shift;
+ fLastX = clip.fRight << shift;
+ }
+ void prepost(int y, bool isStart) {
+ if (isStart) {
+ fPrevX = fFirstX;
+ } else {
+ int invWidth = fLastX - fPrevX;
+ if (invWidth > 0) {
+ fBlitter->blitH(fPrevX, y, invWidth);
+ }
+ }
+ }
+
+ // overrides
+ virtual void blitH(int x, int y, int width) {
+ int invWidth = x - fPrevX;
+ if (invWidth > 0) {
+ fBlitter->blitH(fPrevX, y, invWidth);
+ }
+ fPrevX = x + width;
+ }
+
+ // we do not expect to get called with these entrypoints
+ virtual void blitAntiH(int, int, const SkAlpha[], const int16_t runs[]) {
+ SkDEBUGFAIL("blitAntiH unexpected");
+ }
+ virtual void blitV(int x, int y, int height, SkAlpha alpha) {
+ SkDEBUGFAIL("blitV unexpected");
+ }
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkDEBUGFAIL("blitRect unexpected");
+ }
+ virtual void blitMask(const SkMask&, const SkIRect& clip) {
+ SkDEBUGFAIL("blitMask unexpected");
+ }
+ virtual const SkBitmap* justAnOpaqueColor(uint32_t* value) {
+ SkDEBUGFAIL("justAnOpaqueColor unexpected");
+ return NULL;
+ }
+
+private:
+ SkBlitter* fBlitter;
+ int fFirstX, fLastX, fPrevX;
+};
+
+static void PrePostInverseBlitterProc(SkBlitter* blitter, int y, bool isStart) {
+ ((InverseBlitter*)blitter)->prepost(y, isStart);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+#ifdef SK_USE_STD_SORT_FOR_EDGES
+extern "C" {
+ static int edge_compare(const void* a, const void* b) {
+ const SkEdge* edgea = *(const SkEdge**)a;
+ const SkEdge* edgeb = *(const SkEdge**)b;
+
+ int valuea = edgea->fFirstY;
+ int valueb = edgeb->fFirstY;
+
+ if (valuea == valueb) {
+ valuea = edgea->fX;
+ valueb = edgeb->fX;
+ }
+
+ // this overflows if valuea >>> valueb or vice-versa
+ // return valuea - valueb;
+ // do perform the slower but safe compares
+ return (valuea < valueb) ? -1 : (valuea > valueb);
+ }
+}
+#else
+static bool operator<(const SkEdge& a, const SkEdge& b) {
+ int valuea = a.fFirstY;
+ int valueb = b.fFirstY;
+
+ if (valuea == valueb) {
+ valuea = a.fX;
+ valueb = b.fX;
+ }
+
+ return valuea < valueb;
+}
+#endif
+
+static SkEdge* sort_edges(SkEdge* list[], int count, SkEdge** last) {
+#ifdef SK_USE_STD_SORT_FOR_EDGES
+ qsort(list, count, sizeof(SkEdge*), edge_compare);
+#else
+ SkTQSort(list, list + count - 1);
+#endif
+
+ // now make the edges linked in sorted order
+ for (int i = 1; i < count; i++) {
+ list[i - 1]->fNext = list[i];
+ list[i]->fPrev = list[i - 1];
+ }
+
+ *last = list[count - 1];
+ return list[0];
+}
+
+// clipRect may be null, even though we always have a clip. This indicates that
+// the path is contained in the clip, and so we can ignore it during the blit
+//
+// clipRect (if no null) has already been shifted up
+//
+void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,
+ int start_y, int stop_y, int shiftEdgesUp,
+ const SkRegion& clipRgn) {
+ SkASSERT(&path && blitter);
+
+ SkEdgeBuilder builder;
+
+ int count = builder.build(path, clipRect, shiftEdgesUp);
+ SkEdge** list = builder.edgeList();
+
+ if (count < 2) {
+ if (path.isInverseFillType()) {
+ /*
+ * Since we are in inverse-fill, our caller has already drawn above
+ * our top (start_y) and will draw below our bottom (stop_y). Thus
+ * we need to restrict our drawing to the intersection of the clip
+ * and those two limits.
+ */
+ SkIRect rect = clipRgn.getBounds();
+ if (rect.fTop < start_y) {
+ rect.fTop = start_y;
+ }
+ if (rect.fBottom > stop_y) {
+ rect.fBottom = stop_y;
+ }
+ if (!rect.isEmpty()) {
+ blitter->blitRect(rect.fLeft << shiftEdgesUp,
+ rect.fTop << shiftEdgesUp,
+ rect.width() << shiftEdgesUp,
+ rect.height() << shiftEdgesUp);
+ }
+ }
+
+ return;
+ }
+
+ SkEdge headEdge, tailEdge, *last;
+ // this returns the first and last edge after they're sorted into a dlink list
+ SkEdge* edge = sort_edges(list, count, &last);
+
+ headEdge.fPrev = NULL;
+ headEdge.fNext = edge;
+ headEdge.fFirstY = kEDGE_HEAD_Y;
+ headEdge.fX = SK_MinS32;
+ edge->fPrev = &headEdge;
+
+ tailEdge.fPrev = last;
+ tailEdge.fNext = NULL;
+ tailEdge.fFirstY = kEDGE_TAIL_Y;
+ last->fNext = &tailEdge;
+
+ // now edge is the head of the sorted linklist
+
+ start_y <<= shiftEdgesUp;
+ stop_y <<= shiftEdgesUp;
+ if (clipRect && start_y < clipRect->fTop) {
+ start_y = clipRect->fTop;
+ }
+ if (clipRect && stop_y > clipRect->fBottom) {
+ stop_y = clipRect->fBottom;
+ }
+
+ InverseBlitter ib;
+ PrePostProc proc = NULL;
+
+ if (path.isInverseFillType()) {
+ ib.setBlitter(blitter, clipRgn.getBounds(), shiftEdgesUp);
+ blitter = &ib;
+ proc = PrePostInverseBlitterProc;
+ }
+
+ if (path.isConvex() && (NULL == proc)) {
+ walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, NULL);
+ } else {
+ walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc);
+ }
+}
+
+void sk_blit_above(SkBlitter* blitter, const SkIRect& ir, const SkRegion& clip) {
+ const SkIRect& cr = clip.getBounds();
+ SkIRect tmp;
+
+ tmp.fLeft = cr.fLeft;
+ tmp.fRight = cr.fRight;
+ tmp.fTop = cr.fTop;
+ tmp.fBottom = ir.fTop;
+ if (!tmp.isEmpty()) {
+ blitter->blitRectRegion(tmp, clip);
+ }
+}
+
+void sk_blit_below(SkBlitter* blitter, const SkIRect& ir, const SkRegion& clip) {
+ const SkIRect& cr = clip.getBounds();
+ SkIRect tmp;
+
+ tmp.fLeft = cr.fLeft;
+ tmp.fRight = cr.fRight;
+ tmp.fTop = ir.fBottom;
+ tmp.fBottom = cr.fBottom;
+ if (!tmp.isEmpty()) {
+ blitter->blitRectRegion(tmp, clip);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * If the caller is drawing an inverse-fill path, then it pass true for
+ * skipRejectTest, so we don't abort drawing just because the src bounds (ir)
+ * is outside of the clip.
+ */
+SkScanClipper::SkScanClipper(SkBlitter* blitter, const SkRegion* clip,
+ const SkIRect& ir, bool skipRejectTest) {
+ fBlitter = NULL; // null means blit nothing
+ fClipRect = NULL;
+
+ if (clip) {
+ fClipRect = &clip->getBounds();
+ if (!skipRejectTest && !SkIRect::Intersects(*fClipRect, ir)) { // completely clipped out
+ return;
+ }
+
+ if (clip->isRect()) {
+ if (fClipRect->contains(ir)) {
+ fClipRect = NULL;
+ } else {
+ // only need a wrapper blitter if we're horizontally clipped
+ if (fClipRect->fLeft > ir.fLeft || fClipRect->fRight < ir.fRight) {
+ fRectBlitter.init(blitter, *fClipRect);
+ blitter = &fRectBlitter;
+ }
+ }
+ } else {
+ fRgnBlitter.init(blitter, clip);
+ blitter = &fRgnBlitter;
+ }
+ }
+ fBlitter = blitter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool clip_to_limit(const SkRegion& orig, SkRegion* reduced) {
+ const int32_t limit = 32767;
+
+ SkIRect limitR;
+ limitR.set(-limit, -limit, limit, limit);
+ if (limitR.contains(orig.getBounds())) {
+ return false;
+ }
+ reduced->op(orig, limitR, SkRegion::kIntersect_Op);
+ return true;
+}
+
+void SkScan::FillPath(const SkPath& path, const SkRegion& origClip,
+ SkBlitter* blitter) {
+ if (origClip.isEmpty()) {
+ return;
+ }
+
+ // Our edges are fixed-point, and don't like the bounds of the clip to
+ // exceed that. Here we trim the clip just so we don't overflow later on
+ const SkRegion* clipPtr = &origClip;
+ SkRegion finiteClip;
+ if (clip_to_limit(origClip, &finiteClip)) {
+ if (finiteClip.isEmpty()) {
+ return;
+ }
+ clipPtr = &finiteClip;
+ }
+ // don't reference "origClip" any more, just use clipPtr
+
+ SkIRect ir;
+ path.getBounds().round(&ir);
+ if (ir.isEmpty()) {
+ if (path.isInverseFillType()) {
+ blitter->blitRegion(*clipPtr);
+ }
+ return;
+ }
+
+ SkScanClipper clipper(blitter, clipPtr, ir, path.isInverseFillType());
+
+ blitter = clipper.getBlitter();
+ if (blitter) {
+ // we have to keep our calls to blitter in sorted order, so we
+ // must blit the above section first, then the middle, then the bottom.
+ if (path.isInverseFillType()) {
+ sk_blit_above(blitter, ir, *clipPtr);
+ }
+ sk_fill_path(path, clipper.getClipRect(), blitter, ir.fTop, ir.fBottom,
+ 0, *clipPtr);
+ if (path.isInverseFillType()) {
+ sk_blit_below(blitter, ir, *clipPtr);
+ }
+ } else {
+ // what does it mean to not have a blitter if path.isInverseFillType???
+ }
+}
+
+void SkScan::FillPath(const SkPath& path, const SkIRect& ir,
+ SkBlitter* blitter) {
+ SkRegion rgn(ir);
+ FillPath(path, rgn, blitter);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int build_tri_edges(SkEdge edge[], const SkPoint pts[],
+ const SkIRect* clipRect, SkEdge* list[]) {
+ SkEdge** start = list;
+
+ if (edge->setLine(pts[0], pts[1], clipRect, 0)) {
+ *list++ = edge;
+ edge = (SkEdge*)((char*)edge + sizeof(SkEdge));
+ }
+ if (edge->setLine(pts[1], pts[2], clipRect, 0)) {
+ *list++ = edge;
+ edge = (SkEdge*)((char*)edge + sizeof(SkEdge));
+ }
+ if (edge->setLine(pts[2], pts[0], clipRect, 0)) {
+ *list++ = edge;
+ }
+ return (int)(list - start);
+}
+
+
+static void sk_fill_triangle(const SkPoint pts[], const SkIRect* clipRect,
+ SkBlitter* blitter, const SkIRect& ir) {
+ SkASSERT(pts && blitter);
+
+ SkEdge edgeStorage[3];
+ SkEdge* list[3];
+
+ int count = build_tri_edges(edgeStorage, pts, clipRect, list);
+ if (count < 2) {
+ return;
+ }
+
+ SkEdge headEdge, tailEdge, *last;
+
+ // this returns the first and last edge after they're sorted into a dlink list
+ SkEdge* edge = sort_edges(list, count, &last);
+
+ headEdge.fPrev = NULL;
+ headEdge.fNext = edge;
+ headEdge.fFirstY = kEDGE_HEAD_Y;
+ headEdge.fX = SK_MinS32;
+ edge->fPrev = &headEdge;
+
+ tailEdge.fPrev = last;
+ tailEdge.fNext = NULL;
+ tailEdge.fFirstY = kEDGE_TAIL_Y;
+ last->fNext = &tailEdge;
+
+ // now edge is the head of the sorted linklist
+ int stop_y = ir.fBottom;
+ if (clipRect && stop_y > clipRect->fBottom) {
+ stop_y = clipRect->fBottom;
+ }
+ int start_y = ir.fTop;
+ if (clipRect && start_y < clipRect->fTop) {
+ start_y = clipRect->fTop;
+ }
+ walk_convex_edges(&headEdge, SkPath::kEvenOdd_FillType, blitter, start_y, stop_y, NULL);
+// walk_edges(&headEdge, SkPath::kEvenOdd_FillType, blitter, start_y, stop_y, NULL);
+}
+
+void SkScan::FillTriangle(const SkPoint pts[], const SkRasterClip& clip,
+ SkBlitter* blitter) {
+ if (clip.isEmpty()) {
+ return;
+ }
+
+ SkRect r;
+ SkIRect ir;
+ r.set(pts, 3);
+ r.round(&ir);
+ if (ir.isEmpty() || !SkIRect::Intersects(ir, clip.getBounds())) {
+ return;
+ }
+
+ SkAAClipBlitterWrapper wrap;
+ const SkRegion* clipRgn;
+ if (clip.isBW()) {
+ clipRgn = &clip.bwRgn();
+ } else {
+ wrap.init(clip, blitter);
+ clipRgn = &wrap.getRgn();
+ blitter = wrap.getBlitter();
+ }
+
+ SkScanClipper clipper(blitter, clipRgn, ir);
+ blitter = clipper.getBlitter();
+ if (NULL != blitter) {
+ sk_fill_triangle(pts, clipper.getClipRect(), blitter, ir);
+ }
+}
diff --git a/core/SkShader.cpp b/core/SkShader.cpp
new file mode 100644
index 00000000..d51c2ef5
--- /dev/null
+++ b/core/SkShader.cpp
@@ -0,0 +1,362 @@
+
+/*
+ * 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 "SkScalar.h"
+#include "SkShader.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPaint.h"
+#include "SkMallocPixelRef.h"
+
+SK_DEFINE_INST_COUNT(SkShader)
+
+SkShader::SkShader() {
+ fLocalMatrix.reset();
+ SkDEBUGCODE(fInSetContext = false;)
+}
+
+SkShader::SkShader(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ if (buffer.readBool()) {
+ buffer.readMatrix(&fLocalMatrix);
+ } else {
+ fLocalMatrix.reset();
+ }
+
+ SkDEBUGCODE(fInSetContext = false;)
+}
+
+SkShader::~SkShader() {
+ SkASSERT(!fInSetContext);
+}
+
+void SkShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ 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 (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;
+}
+
+#include "SkColorPriv.h"
+
+void SkShader::shadeSpan16(int x, int y, uint16_t span16[], int count) {
+ SkASSERT(span16);
+ SkASSERT(count > 0);
+ SkASSERT(this->canCallShadeSpan16());
+
+ // basically, if we get here, the subclass screwed up
+ SkDEBUGFAIL("kHasSpan16 flag is set, but shadeSpan16() not implemented");
+}
+
+#define kTempColorQuadCount 6 // balance between speed (larger) and saving stack-space
+#define kTempColorCount (kTempColorQuadCount << 2)
+
+#ifdef SK_CPU_BENDIAN
+ #define SkU32BitShiftToByteOffset(shift) (3 - ((shift) >> 3))
+#else
+ #define SkU32BitShiftToByteOffset(shift) ((shift) >> 3)
+#endif
+
+void SkShader::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ SkASSERT(count > 0);
+
+ SkPMColor colors[kTempColorCount];
+
+ while ((count -= kTempColorCount) >= 0) {
+ this->shadeSpan(x, y, colors, kTempColorCount);
+ x += kTempColorCount;
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ int quads = kTempColorQuadCount;
+ do {
+ U8CPU a0 = srcA[0];
+ U8CPU a1 = srcA[4];
+ U8CPU a2 = srcA[8];
+ U8CPU a3 = srcA[12];
+ srcA += 4*4;
+ *alpha++ = SkToU8(a0);
+ *alpha++ = SkToU8(a1);
+ *alpha++ = SkToU8(a2);
+ *alpha++ = SkToU8(a3);
+ } while (--quads != 0);
+ }
+ SkASSERT(count < 0);
+ SkASSERT(count + kTempColorCount >= 0);
+ if (count += kTempColorCount) {
+ this->shadeSpan(x, y, colors, count);
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ do {
+ *alpha++ = *srcA;
+ srcA += 4;
+ } while (--count != 0);
+ }
+#if 0
+ do {
+ int n = count;
+ if (n > kTempColorCount)
+ n = kTempColorCount;
+ SkASSERT(n > 0);
+
+ this->shadeSpan(x, y, colors, n);
+ x += n;
+ count -= n;
+
+ const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT);
+ do {
+ *alpha++ = *srcA;
+ srcA += 4;
+ } while (--n != 0);
+ } while (count > 0);
+#endif
+}
+
+SkShader::MatrixClass SkShader::ComputeMatrixClass(const SkMatrix& mat) {
+ MatrixClass mc = kLinear_MatrixClass;
+
+ if (mat.hasPerspective()) {
+ if (mat.fixedStepInX(0, NULL, NULL)) {
+ mc = kFixedStepInX_MatrixClass;
+ } else {
+ mc = kPerspective_MatrixClass;
+ }
+ }
+ return mc;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+SkShader::BitmapType SkShader::asABitmap(SkBitmap*, SkMatrix*,
+ TileMode*) const {
+ return kNone_BitmapType;
+}
+
+SkShader::GradientType SkShader::asAGradient(GradientInfo* info) const {
+ return kNone_GradientType;
+}
+
+GrEffectRef* SkShader::asNewEffect(GrContext*, const SkPaint&) const {
+ return NULL;
+}
+
+SkShader* SkShader::CreateBitmapShader(const SkBitmap& src,
+ TileMode tmx, TileMode tmy) {
+ return SkShader::CreateBitmapShader(src, tmx, tmy, NULL, 0);
+}
+
+#ifdef SK_DEVELOPER
+void SkShader::toString(SkString* str) const {
+ if (this->hasLocalMatrix()) {
+ str->append(" ");
+ this->getLocalMatrix().toString(str);
+ }
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorShader.h"
+#include "SkUtils.h"
+
+SkColorShader::SkColorShader() {
+ fFlags = 0;
+ fInheritColor = true;
+}
+
+SkColorShader::SkColorShader(SkColor c) {
+ fFlags = 0;
+ fColor = c;
+ fInheritColor = false;
+}
+
+SkColorShader::~SkColorShader() {}
+
+bool SkColorShader::isOpaque() const {
+ if (fInheritColor) {
+ return true; // using paint's alpha
+ }
+ return SkColorGetA(fColor) == 255;
+}
+
+SkColorShader::SkColorShader(SkFlattenableReadBuffer& b) : INHERITED(b) {
+ fFlags = 0; // computed in setContext
+
+ fInheritColor = b.readBool();
+ if (fInheritColor) {
+ return;
+ }
+ fColor = b.readColor();
+}
+
+void SkColorShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeBool(fInheritColor);
+ if (fInheritColor) {
+ return;
+ }
+ buffer.writeColor(fColor);
+}
+
+uint32_t SkColorShader::getFlags() {
+ return fFlags;
+}
+
+uint8_t SkColorShader::getSpan16Alpha() const {
+ return SkGetPackedA32(fPMColor);
+}
+
+bool SkColorShader::setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ unsigned a;
+
+ if (fInheritColor) {
+ fColor = paint.getColor();
+ a = SkColorGetA(fColor);
+ } else {
+ a = SkAlphaMul(SkColorGetA(fColor), SkAlpha255To256(paint.getAlpha()));
+ }
+
+ unsigned r = SkColorGetR(fColor);
+ unsigned g = SkColorGetG(fColor);
+ unsigned b = SkColorGetB(fColor);
+
+ // we want this before we apply any alpha
+ fColor16 = SkPack888ToRGB16(r, g, b);
+
+ if (a != 255) {
+ r = SkMulDiv255Round(r, a);
+ g = SkMulDiv255Round(g, a);
+ b = SkMulDiv255Round(b, a);
+ }
+ fPMColor = SkPackARGB32(a, r, g, b);
+
+ fFlags = kConstInY32_Flag;
+ if (255 == a) {
+ fFlags |= kOpaqueAlpha_Flag;
+ if (paint.isDither() == false) {
+ fFlags |= kHasSpan16_Flag;
+ }
+ }
+
+ return true;
+}
+
+void SkColorShader::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ sk_memset32(span, fPMColor, count);
+}
+
+void SkColorShader::shadeSpan16(int x, int y, uint16_t span[], int count) {
+ sk_memset16(span, fColor16, count);
+}
+
+void SkColorShader::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ memset(alpha, SkGetPackedA32(fPMColor), count);
+}
+
+// if we had a asAColor method, that would be more efficient...
+SkShader::BitmapType SkColorShader::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
+ TileMode modes[]) const {
+ return kNone_BitmapType;
+}
+
+SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const {
+ if (info) {
+ if (info->fColors && info->fColorCount >= 1) {
+ info->fColors[0] = fColor;
+ }
+ info->fColorCount = 1;
+ info->fTileMode = SkShader::kRepeat_TileMode;
+ }
+ return kColor_GradientType;
+}
+
+#ifdef SK_DEVELOPER
+void SkColorShader::toString(SkString* str) const {
+ str->append("SkColorShader: (");
+
+ if (fInheritColor) {
+ str->append("Color: inherited from paint");
+ } else {
+ str->append("Color: ");
+ str->appendHex(fColor);
+ }
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkEmptyShader.h"
+
+uint32_t SkEmptyShader::getFlags() { return 0; }
+uint8_t SkEmptyShader::getSpan16Alpha() const { return 0; }
+
+bool SkEmptyShader::setContext(const SkBitmap&, const SkPaint&,
+ const SkMatrix&) { return false; }
+
+void SkEmptyShader::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ SkDEBUGFAIL("should never get called, since setContext() returned false");
+}
+
+void SkEmptyShader::shadeSpan16(int x, int y, uint16_t span[], int count) {
+ SkDEBUGFAIL("should never get called, since setContext() returned false");
+}
+
+void SkEmptyShader::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) {
+ SkDEBUGFAIL("should never get called, since setContext() returned false");
+}
+
+#ifdef SK_DEVELOPER
+void SkEmptyShader::toString(SkString* str) const {
+ str->append("SkEmptyShader: (");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/core/SkSinTable.h b/core/SkSinTable.h
new file mode 100644
index 00000000..ac26b9e5
--- /dev/null
+++ b/core/SkSinTable.h
@@ -0,0 +1,277 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSinTable_DEFINED
+#define SkSinTable_DEFINED
+
+#include "SkTypes.h"
+
+/* Fixed point values (low 16 bits) of sin(radians) for
+ radians in [0...PI/2)
+*/
+static const uint16_t gSkSinTable[256] = {
+ 0x0000,
+ 0x0192,
+ 0x0324,
+ 0x04B6,
+ 0x0648,
+ 0x07DA,
+ 0x096C,
+ 0x0AFE,
+ 0x0C8F,
+ 0x0E21,
+ 0x0FB2,
+ 0x1144,
+ 0x12D5,
+ 0x1466,
+ 0x15F6,
+ 0x1787,
+ 0x1917,
+ 0x1AA7,
+ 0x1C37,
+ 0x1DC7,
+ 0x1F56,
+ 0x20E5,
+ 0x2273,
+ 0x2402,
+ 0x2590,
+ 0x271D,
+ 0x28AA,
+ 0x2A37,
+ 0x2BC4,
+ 0x2D50,
+ 0x2EDB,
+ 0x3066,
+ 0x31F1,
+ 0x337B,
+ 0x3505,
+ 0x368E,
+ 0x3817,
+ 0x399F,
+ 0x3B26,
+ 0x3CAD,
+ 0x3E33,
+ 0x3FB9,
+ 0x413E,
+ 0x42C3,
+ 0x4447,
+ 0x45CA,
+ 0x474D,
+ 0x48CE,
+ 0x4A50,
+ 0x4BD0,
+ 0x4D50,
+ 0x4ECF,
+ 0x504D,
+ 0x51CA,
+ 0x5347,
+ 0x54C3,
+ 0x563E,
+ 0x57B8,
+ 0x5931,
+ 0x5AAA,
+ 0x5C22,
+ 0x5D98,
+ 0x5F0E,
+ 0x6083,
+ 0x61F7,
+ 0x636A,
+ 0x64DC,
+ 0x664D,
+ 0x67BD,
+ 0x692D,
+ 0x6A9B,
+ 0x6C08,
+ 0x6D74,
+ 0x6EDF,
+ 0x7049,
+ 0x71B1,
+ 0x7319,
+ 0x7480,
+ 0x75E5,
+ 0x774A,
+ 0x78AD,
+ 0x7A0F,
+ 0x7B70,
+ 0x7CD0,
+ 0x7E2E,
+ 0x7F8B,
+ 0x80E7,
+ 0x8242,
+ 0x839C,
+ 0x84F4,
+ 0x864B,
+ 0x87A1,
+ 0x88F5,
+ 0x8A48,
+ 0x8B9A,
+ 0x8CEA,
+ 0x8E39,
+ 0x8F87,
+ 0x90D3,
+ 0x921E,
+ 0x9368,
+ 0x94B0,
+ 0x95F6,
+ 0x973C,
+ 0x987F,
+ 0x99C2,
+ 0x9B02,
+ 0x9C42,
+ 0x9D7F,
+ 0x9EBC,
+ 0x9FF6,
+ 0xA12F,
+ 0xA267,
+ 0xA39D,
+ 0xA4D2,
+ 0xA605,
+ 0xA736,
+ 0xA866,
+ 0xA994,
+ 0xAAC0,
+ 0xABEB,
+ 0xAD14,
+ 0xAE3B,
+ 0xAF61,
+ 0xB085,
+ 0xB1A8,
+ 0xB2C8,
+ 0xB3E7,
+ 0xB504,
+ 0xB620,
+ 0xB73A,
+ 0xB852,
+ 0xB968,
+ 0xBA7C,
+ 0xBB8F,
+ 0xBCA0,
+ 0xBDAE,
+ 0xBEBC,
+ 0xBFC7,
+ 0xC0D0,
+ 0xC1D8,
+ 0xC2DE,
+ 0xC3E2,
+ 0xC4E3,
+ 0xC5E4,
+ 0xC6E2,
+ 0xC7DE,
+ 0xC8D8,
+ 0xC9D1,
+ 0xCAC7,
+ 0xCBBB,
+ 0xCCAE,
+ 0xCD9F,
+ 0xCE8D,
+ 0xCF7A,
+ 0xD064,
+ 0xD14D,
+ 0xD233,
+ 0xD318,
+ 0xD3FA,
+ 0xD4DB,
+ 0xD5B9,
+ 0xD695,
+ 0xD770,
+ 0xD848,
+ 0xD91E,
+ 0xD9F2,
+ 0xDAC4,
+ 0xDB94,
+ 0xDC61,
+ 0xDD2D,
+ 0xDDF6,
+ 0xDEBE,
+ 0xDF83,
+ 0xE046,
+ 0xE106,
+ 0xE1C5,
+ 0xE282,
+ 0xE33C,
+ 0xE3F4,
+ 0xE4AA,
+ 0xE55E,
+ 0xE60F,
+ 0xE6BE,
+ 0xE76B,
+ 0xE816,
+ 0xE8BF,
+ 0xE965,
+ 0xEA09,
+ 0xEAAB,
+ 0xEB4B,
+ 0xEBE8,
+ 0xEC83,
+ 0xED1C,
+ 0xEDB2,
+ 0xEE46,
+ 0xEED8,
+ 0xEF68,
+ 0xEFF5,
+ 0xF080,
+ 0xF109,
+ 0xF18F,
+ 0xF213,
+ 0xF294,
+ 0xF314,
+ 0xF391,
+ 0xF40B,
+ 0xF484,
+ 0xF4FA,
+ 0xF56D,
+ 0xF5DE,
+ 0xF64D,
+ 0xF6BA,
+ 0xF724,
+ 0xF78B,
+ 0xF7F1,
+ 0xF853,
+ 0xF8B4,
+ 0xF912,
+ 0xF96E,
+ 0xF9C7,
+ 0xFA1E,
+ 0xFA73,
+ 0xFAC5,
+ 0xFB14,
+ 0xFB61,
+ 0xFBAC,
+ 0xFBF5,
+ 0xFC3B,
+ 0xFC7E,
+ 0xFCBF,
+ 0xFCFE,
+ 0xFD3A,
+ 0xFD74,
+ 0xFDAB,
+ 0xFDE0,
+ 0xFE13,
+ 0xFE43,
+ 0xFE70,
+ 0xFE9B,
+ 0xFEC4,
+ 0xFEEA,
+ 0xFF0E,
+ 0xFF2F,
+ 0xFF4E,
+ 0xFF6A,
+ 0xFF84,
+ 0xFF9C,
+ 0xFFB1,
+ 0xFFC3,
+ 0xFFD3,
+ 0xFFE1,
+ 0xFFEC,
+ 0xFFF4,
+ 0xFFFB,
+ 0xFFFE
+};
+
+#endif
diff --git a/core/SkSpriteBlitter.h b/core/SkSpriteBlitter.h
new file mode 100644
index 00000000..ae79afe1
--- /dev/null
+++ b/core/SkSpriteBlitter.h
@@ -0,0 +1,46 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSpriteBlitter_DEFINED
+#define SkSpriteBlitter_DEFINED
+
+#include "SkBlitter.h"
+#include "SkBitmap.h"
+
+class SkPaint;
+
+class SkSpriteBlitter : public SkBlitter {
+public:
+ SkSpriteBlitter(const SkBitmap& source);
+ virtual ~SkSpriteBlitter();
+
+ virtual void setup(const SkBitmap& device, int left, int top,
+ const SkPaint& paint);
+
+ // overrides
+#ifdef SK_DEBUG
+ virtual void blitH(int x, int y, int width);
+ virtual void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]);
+ virtual void blitV(int x, int y, int height, SkAlpha alpha);
+ virtual void blitMask(const SkMask&, const SkIRect& clip);
+#endif
+
+ static SkSpriteBlitter* ChooseD16(const SkBitmap& source, const SkPaint&,
+ void* storage, size_t storageSize);
+ static SkSpriteBlitter* ChooseD32(const SkBitmap& source, const SkPaint&,
+ void* storage, size_t storageSize);
+
+protected:
+ const SkBitmap* fDevice;
+ const SkBitmap* fSource;
+ int fLeft, fTop;
+ const SkPaint* fPaint;
+};
+
+#endif
diff --git a/core/SkSpriteBlitterTemplate.h b/core/SkSpriteBlitterTemplate.h
new file mode 100644
index 00000000..0243e4f2
--- /dev/null
+++ b/core/SkSpriteBlitterTemplate.h
@@ -0,0 +1,82 @@
+
+/*
+ * 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.
+ */
+
+
+
+class SkSPRITE_CLASSNAME : public SkSpriteBlitter {
+public:
+ SkSPRITE_CLASSNAME(const SkBitmap& source SkSPRITE_ARGS)
+ : SkSpriteBlitter(source) {
+ SkSPRITE_INIT
+ }
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ int srcX = x - fLeft;
+ int srcY = y - fTop;
+ SkSPRITE_DST_TYPE* SK_RESTRICT dst =fDevice->SkSPRITE_DST_GETADDR(x, y);
+ const SkSPRITE_SRC_TYPE* SK_RESTRICT src =
+ fSource->SkSPRITE_SRC_GETADDR(srcX, srcY);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+
+ SkDEBUGCODE((void)fDevice->SkSPRITE_DST_GETADDR(x + width - 1, y + height - 1);)
+ SkDEBUGCODE((void)fSource->SkSPRITE_SRC_GETADDR(srcX + width - 1, srcY + height - 1);)
+
+ SkSPRITE_PREAMBLE((*fSource), srcX, srcY);
+
+ do {
+ SkSPRITE_DST_TYPE* d = dst;
+ const SkSPRITE_SRC_TYPE* s = src;
+#ifdef SkSPRITE_BEGIN_ROW
+ SkSPRITE_BEGIN_ROW
+#endif
+
+#ifdef SkSPRITE_ROW_PROC
+ SkSPRITE_ROW_PROC(d, s, width, x, y);
+#else
+ int w = width;
+ do {
+ SkSPRITE_SRC_TYPE sc = *s++;
+ SkSPRITE_BLIT_PIXEL(d, sc);
+ d += 1;
+ } while (--w != 0);
+#endif
+ dst = (SkSPRITE_DST_TYPE* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const SkSPRITE_SRC_TYPE* SK_RESTRICT)
+ ((const char*)src + srcRB);
+ SkSPRITE_NEXT_ROW
+#ifdef SkSPRITE_ROW_PROC
+ y += 1;
+#endif
+ } while (--height != 0);
+
+ SkSPRITE_POSTAMBLE((*fSource));
+ }
+
+private:
+ SkSPRITE_FIELDS
+};
+
+#undef SkSPRITE_BLIT_PIXEL
+#undef SkSPRITE_CLASSNAME
+#undef SkSPRITE_DST_TYPE
+#undef SkSPRITE_SRC_TYPE
+#undef SkSPRITE_DST_GETADDR
+#undef SkSPRITE_SRC_GETADDR
+#undef SkSPRITE_PREAMBLE
+#undef SkSPRITE_POSTAMBLE
+#undef SkSPRITE_ARGS
+#undef SkSPRITE_FIELDS
+#undef SkSPRITE_INIT
+#undef SkSPRITE_NEXT_ROW
+#undef SkSPRITE_BEGIN_ROW
+
+#ifdef SkSPRITE_ROW_PROC
+ #undef SkSPRITE_ROW_PROC
+#endif
diff --git a/core/SkSpriteBlitter_ARGB32.cpp b/core/SkSpriteBlitter_ARGB32.cpp
new file mode 100644
index 00000000..255ef26d
--- /dev/null
+++ b/core/SkSpriteBlitter_ARGB32.cpp
@@ -0,0 +1,313 @@
+
+/*
+ * 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 "SkSpriteBlitter.h"
+#include "SkBlitRow.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+#include "SkXfermode.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+class Sprite_D32_S32 : public SkSpriteBlitter {
+public:
+ Sprite_D32_S32(const SkBitmap& src, U8CPU alpha) : INHERITED(src) {
+ SkASSERT(src.config() == SkBitmap::kARGB_8888_Config);
+
+ unsigned flags32 = 0;
+ if (255 != alpha) {
+ flags32 |= SkBlitRow::kGlobalAlpha_Flag32;
+ }
+ if (!src.isOpaque()) {
+ flags32 |= SkBlitRow::kSrcPixelAlpha_Flag32;
+ }
+
+ fProc32 = SkBlitRow::Factory32(flags32);
+ fAlpha = alpha;
+ }
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ uint32_t* SK_RESTRICT dst = fDevice->getAddr32(x, y);
+ const uint32_t* SK_RESTRICT src = fSource->getAddr32(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+ SkBlitRow::Proc32 proc = fProc32;
+ U8CPU alpha = fAlpha;
+
+ do {
+ proc(dst, src, width, alpha);
+ dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB);
+ } while (--height != 0);
+ }
+
+private:
+ SkBlitRow::Proc32 fProc32;
+ U8CPU fAlpha;
+
+ typedef SkSpriteBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class Sprite_D32_XferFilter : public SkSpriteBlitter {
+public:
+ Sprite_D32_XferFilter(const SkBitmap& source, const SkPaint& paint)
+ : SkSpriteBlitter(source) {
+ fColorFilter = paint.getColorFilter();
+ SkSafeRef(fColorFilter);
+
+ fXfermode = paint.getXfermode();
+ SkSafeRef(fXfermode);
+
+ fBufferSize = 0;
+ fBuffer = NULL;
+
+ unsigned flags32 = 0;
+ if (255 != paint.getAlpha()) {
+ flags32 |= SkBlitRow::kGlobalAlpha_Flag32;
+ }
+ if (!source.isOpaque()) {
+ flags32 |= SkBlitRow::kSrcPixelAlpha_Flag32;
+ }
+
+ fProc32 = SkBlitRow::Factory32(flags32);
+ fAlpha = paint.getAlpha();
+ }
+
+ virtual ~Sprite_D32_XferFilter() {
+ delete[] fBuffer;
+ SkSafeUnref(fXfermode);
+ SkSafeUnref(fColorFilter);
+ }
+
+ virtual void setup(const SkBitmap& device, int left, int top,
+ const SkPaint& paint) {
+ this->INHERITED::setup(device, left, top, paint);
+
+ int width = device.width();
+ if (width > fBufferSize) {
+ fBufferSize = width;
+ delete[] fBuffer;
+ fBuffer = new SkPMColor[width];
+ }
+ }
+
+protected:
+ SkColorFilter* fColorFilter;
+ SkXfermode* fXfermode;
+ int fBufferSize;
+ SkPMColor* fBuffer;
+ SkBlitRow::Proc32 fProc32;
+ U8CPU fAlpha;
+
+private:
+ typedef SkSpriteBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class Sprite_D32_S32A_XferFilter : public Sprite_D32_XferFilter {
+public:
+ Sprite_D32_S32A_XferFilter(const SkBitmap& source, const SkPaint& paint)
+ : Sprite_D32_XferFilter(source, paint) {}
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ uint32_t* SK_RESTRICT dst = fDevice->getAddr32(x, y);
+ const uint32_t* SK_RESTRICT src = fSource->getAddr32(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+ SkColorFilter* colorFilter = fColorFilter;
+ SkXfermode* xfermode = fXfermode;
+
+ do {
+ const SkPMColor* tmp = src;
+
+ if (NULL != colorFilter) {
+ colorFilter->filterSpan(src, width, fBuffer);
+ tmp = fBuffer;
+ }
+
+ if (NULL != xfermode) {
+ xfermode->xfer32(dst, tmp, width, NULL);
+ } else {
+ fProc32(dst, tmp, width, fAlpha);
+ }
+
+ dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB);
+ } while (--height != 0);
+ }
+
+private:
+ typedef Sprite_D32_XferFilter INHERITED;
+};
+
+static void fillbuffer(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor16* SK_RESTRICT src, int count) {
+ SkASSERT(count > 0);
+
+ do {
+ *dst++ = SkPixel4444ToPixel32(*src++);
+ } while (--count != 0);
+}
+
+class Sprite_D32_S4444_XferFilter : public Sprite_D32_XferFilter {
+public:
+ Sprite_D32_S4444_XferFilter(const SkBitmap& source, const SkPaint& paint)
+ : Sprite_D32_XferFilter(source, paint) {}
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ SkPMColor* SK_RESTRICT dst = fDevice->getAddr32(x, y);
+ const SkPMColor16* SK_RESTRICT src = fSource->getAddr16(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+ SkPMColor* SK_RESTRICT buffer = fBuffer;
+ SkColorFilter* colorFilter = fColorFilter;
+ SkXfermode* xfermode = fXfermode;
+
+ do {
+ fillbuffer(buffer, src, width);
+
+ if (NULL != colorFilter) {
+ colorFilter->filterSpan(buffer, width, buffer);
+ }
+ if (NULL != xfermode) {
+ xfermode->xfer32(dst, buffer, width, NULL);
+ } else {
+ fProc32(dst, buffer, width, fAlpha);
+ }
+
+ dst = (SkPMColor* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const SkPMColor16* SK_RESTRICT)((const char*)src + srcRB);
+ } while (--height != 0);
+ }
+
+private:
+ typedef Sprite_D32_XferFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void src_row(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor16* SK_RESTRICT src, int count) {
+ do {
+ *dst = SkPixel4444ToPixel32(*src);
+ src += 1;
+ dst += 1;
+ } while (--count != 0);
+}
+
+class Sprite_D32_S4444_Opaque : public SkSpriteBlitter {
+public:
+ Sprite_D32_S4444_Opaque(const SkBitmap& source) : SkSpriteBlitter(source) {}
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ SkPMColor* SK_RESTRICT dst = fDevice->getAddr32(x, y);
+ const SkPMColor16* SK_RESTRICT src = fSource->getAddr16(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+
+ do {
+ src_row(dst, src, width);
+ dst = (SkPMColor* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const SkPMColor16* SK_RESTRICT)((const char*)src + srcRB);
+ } while (--height != 0);
+ }
+};
+
+static void srcover_row(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor16* SK_RESTRICT src, int count) {
+ do {
+ *dst = SkPMSrcOver(SkPixel4444ToPixel32(*src), *dst);
+ src += 1;
+ dst += 1;
+ } while (--count != 0);
+}
+
+class Sprite_D32_S4444 : public SkSpriteBlitter {
+public:
+ Sprite_D32_S4444(const SkBitmap& source) : SkSpriteBlitter(source) {}
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ SkASSERT(width > 0 && height > 0);
+ SkPMColor* SK_RESTRICT dst = fDevice->getAddr32(x, y);
+ const SkPMColor16* SK_RESTRICT src = fSource->getAddr16(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+
+ do {
+ srcover_row(dst, src, width);
+ dst = (SkPMColor* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const SkPMColor16* SK_RESTRICT)((const char*)src + srcRB);
+ } while (--height != 0);
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTemplatesPriv.h"
+
+SkSpriteBlitter* SkSpriteBlitter::ChooseD32(const SkBitmap& source,
+ const SkPaint& paint,
+ void* storage, size_t storageSize) {
+ if (paint.getMaskFilter() != NULL) {
+ return NULL;
+ }
+
+ U8CPU alpha = paint.getAlpha();
+ SkXfermode* xfermode = paint.getXfermode();
+ SkColorFilter* filter = paint.getColorFilter();
+ SkSpriteBlitter* blitter = NULL;
+
+ switch (source.getConfig()) {
+ case SkBitmap::kARGB_4444_Config:
+ if (alpha != 0xFF) {
+ return NULL; // we only have opaque sprites
+ }
+ if (xfermode || filter) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D32_S4444_XferFilter,
+ storage, storageSize, (source, paint));
+ } else if (source.isOpaque()) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D32_S4444_Opaque,
+ storage, storageSize, (source));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D32_S4444,
+ storage, storageSize, (source));
+ }
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ if (xfermode || filter) {
+ if (255 == alpha) {
+ // this can handle xfermode or filter, but not alpha
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D32_S32A_XferFilter,
+ storage, storageSize, (source, paint));
+ }
+ } else {
+ // this can handle alpha, but not xfermode or filter
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D32_S32,
+ storage, storageSize, (source, alpha));
+ }
+ break;
+ default:
+ break;
+ }
+ return blitter;
+}
diff --git a/core/SkSpriteBlitter_RGB16.cpp b/core/SkSpriteBlitter_RGB16.cpp
new file mode 100644
index 00000000..9936867f
--- /dev/null
+++ b/core/SkSpriteBlitter_RGB16.cpp
@@ -0,0 +1,377 @@
+
+/*
+ * 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 "SkSpriteBlitter.h"
+#include "SkBlitRow.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+
+#define D16_S32A_Opaque_Pixel(dst, sc) \
+do { \
+ if (sc) { \
+ *dst = SkSrcOver32To16(sc, *dst); \
+ } \
+} while (0)
+
+static inline void D16_S32A_Blend_Pixel_helper(uint16_t* dst, SkPMColor sc,
+ unsigned src_scale) {
+ uint16_t dc = *dst;
+ unsigned sa = SkGetPackedA32(sc);
+ unsigned dr, dg, db;
+
+ if (255 == sa) {
+ dr = SkAlphaBlend(SkPacked32ToR16(sc), SkGetPackedR16(dc), src_scale);
+ dg = SkAlphaBlend(SkPacked32ToG16(sc), SkGetPackedG16(dc), src_scale);
+ db = SkAlphaBlend(SkPacked32ToB16(sc), SkGetPackedB16(dc), src_scale);
+ } else {
+ unsigned dst_scale = 255 - SkAlphaMul(sa, src_scale);
+ dr = (SkPacked32ToR16(sc) * src_scale +
+ SkGetPackedR16(dc) * dst_scale) >> 8;
+ dg = (SkPacked32ToG16(sc) * src_scale +
+ SkGetPackedG16(dc) * dst_scale) >> 8;
+ db = (SkPacked32ToB16(sc) * src_scale +
+ SkGetPackedB16(dc) * dst_scale) >> 8;
+ }
+ *dst = SkPackRGB16(dr, dg, db);
+}
+
+#define D16_S32A_Blend_Pixel(dst, sc, src_scale) \
+ do { if (sc) D16_S32A_Blend_Pixel_helper(dst, sc, src_scale); } while (0)
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+class Sprite_D16_S16_Opaque : public SkSpriteBlitter {
+public:
+ Sprite_D16_S16_Opaque(const SkBitmap& source)
+ : SkSpriteBlitter(source) {}
+
+ // overrides
+ virtual void blitRect(int x, int y, int width, int height) {
+ uint16_t* SK_RESTRICT dst = fDevice->getAddr16(x, y);
+ const uint16_t* SK_RESTRICT src = fSource->getAddr16(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+
+ while (--height >= 0) {
+ memcpy(dst, src, width << 1);
+ dst = (uint16_t*)((char*)dst + dstRB);
+ src = (const uint16_t*)((const char*)src + srcRB);
+ }
+ }
+};
+
+#define D16_S16_Blend_Pixel(dst, sc, scale) \
+ do { \
+ uint16_t dc = *dst; \
+ *dst = SkBlendRGB16(sc, dc, scale); \
+ } while (0)
+
+#define SkSPRITE_CLASSNAME Sprite_D16_S16_Blend
+#define SkSPRITE_ARGS , uint8_t alpha
+#define SkSPRITE_FIELDS uint8_t fSrcAlpha;
+#define SkSPRITE_INIT fSrcAlpha = alpha;
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint16_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr16
+#define SkSPRITE_PREAMBLE(srcBM, x, y) int scale = SkAlpha255To256(fSrcAlpha);
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S16_Blend_Pixel(dst, src, scale)
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM)
+#include "SkSpriteBlitterTemplate.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define D16_S4444_Opaque(dst, sc) \
+ do { \
+ uint16_t dc = *dst; \
+ *dst = SkSrcOver4444To16(sc, dc); \
+ } while (0)
+
+#define SkSPRITE_CLASSNAME Sprite_D16_S4444_Opaque
+#define SkSPRITE_ARGS
+#define SkSPRITE_FIELDS
+#define SkSPRITE_INIT
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE SkPMColor16
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr16
+#define SkSPRITE_PREAMBLE(srcBM, x, y)
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S4444_Opaque(dst, src)
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM)
+#include "SkSpriteBlitterTemplate.h"
+
+#define D16_S4444_Blend(dst, sc, scale16) \
+ do { \
+ uint16_t dc = *dst; \
+ *dst = SkBlend4444To16(sc, dc, scale16); \
+ } while (0)
+
+
+#define SkSPRITE_CLASSNAME Sprite_D16_S4444_Blend
+#define SkSPRITE_ARGS , uint8_t alpha
+#define SkSPRITE_FIELDS uint8_t fSrcAlpha;
+#define SkSPRITE_INIT fSrcAlpha = alpha;
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint16_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr16
+#define SkSPRITE_PREAMBLE(srcBM, x, y) int scale = SkAlpha15To16(fSrcAlpha);
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S4444_Blend(dst, src, scale)
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM)
+#include "SkSpriteBlitterTemplate.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define SkSPRITE_CLASSNAME Sprite_D16_SIndex8A_Opaque
+#define SkSPRITE_ARGS
+#define SkSPRITE_FIELDS
+#define SkSPRITE_INIT
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint8_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr8
+#define SkSPRITE_PREAMBLE(srcBM, x, y) const SkPMColor* ctable = srcBM.getColorTable()->lockColors()
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S32A_Opaque_Pixel(dst, ctable[src])
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM) srcBM.getColorTable()->unlockColors(false)
+#include "SkSpriteBlitterTemplate.h"
+
+#define SkSPRITE_CLASSNAME Sprite_D16_SIndex8A_Blend
+#define SkSPRITE_ARGS , uint8_t alpha
+#define SkSPRITE_FIELDS uint8_t fSrcAlpha;
+#define SkSPRITE_INIT fSrcAlpha = alpha;
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint8_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr8
+#define SkSPRITE_PREAMBLE(srcBM, x, y) const SkPMColor* ctable = srcBM.getColorTable()->lockColors(); unsigned src_scale = SkAlpha255To256(fSrcAlpha);
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S32A_Blend_Pixel(dst, ctable[src], src_scale)
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM) srcBM.getColorTable()->unlockColors(false);
+#include "SkSpriteBlitterTemplate.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static intptr_t asint(const void* ptr) {
+ return reinterpret_cast<const char*>(ptr) - (const char*)0;
+}
+
+static void blitrow_d16_si8(uint16_t* SK_RESTRICT dst,
+ const uint8_t* SK_RESTRICT src, int count,
+ const uint16_t* SK_RESTRICT ctable) {
+ if (count <= 8) {
+ do {
+ *dst++ = ctable[*src++];
+ } while (--count);
+ return;
+ }
+
+ // eat src until we're on a 4byte boundary
+ while (asint(src) & 3) {
+ *dst++ = ctable[*src++];
+ count -= 1;
+ }
+
+ int qcount = count >> 2;
+ SkASSERT(qcount > 0);
+ const uint32_t* qsrc = reinterpret_cast<const uint32_t*>(src);
+ if (asint(dst) & 2) {
+ do {
+ uint32_t s4 = *qsrc++;
+#ifdef SK_CPU_LENDIAN
+ *dst++ = ctable[s4 & 0xFF];
+ *dst++ = ctable[(s4 >> 8) & 0xFF];
+ *dst++ = ctable[(s4 >> 16) & 0xFF];
+ *dst++ = ctable[s4 >> 24];
+#else // BENDIAN
+ *dst++ = ctable[s4 >> 24];
+ *dst++ = ctable[(s4 >> 16) & 0xFF];
+ *dst++ = ctable[(s4 >> 8) & 0xFF];
+ *dst++ = ctable[s4 & 0xFF];
+#endif
+ } while (--qcount);
+ } else { // dst is on a 4byte boundary
+ uint32_t* ddst = reinterpret_cast<uint32_t*>(dst);
+ do {
+ uint32_t s4 = *qsrc++;
+#ifdef SK_CPU_LENDIAN
+ *ddst++ = (ctable[(s4 >> 8) & 0xFF] << 16) | ctable[s4 & 0xFF];
+ *ddst++ = (ctable[s4 >> 24] << 16) | ctable[(s4 >> 16) & 0xFF];
+#else // BENDIAN
+ *ddst++ = (ctable[s4 >> 24] << 16) | ctable[(s4 >> 16) & 0xFF];
+ *ddst++ = (ctable[(s4 >> 8) & 0xFF] << 16) | ctable[s4 & 0xFF];
+#endif
+ } while (--qcount);
+ dst = reinterpret_cast<uint16_t*>(ddst);
+ }
+ src = reinterpret_cast<const uint8_t*>(qsrc);
+ count &= 3;
+ // catch any remaining (will be < 4)
+ while (--count >= 0) {
+ *dst++ = ctable[*src++];
+ }
+}
+
+#define SkSPRITE_ROW_PROC(d, s, n, x, y) blitrow_d16_si8(d, s, n, ctable)
+
+#define SkSPRITE_CLASSNAME Sprite_D16_SIndex8_Opaque
+#define SkSPRITE_ARGS
+#define SkSPRITE_FIELDS
+#define SkSPRITE_INIT
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint8_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr8
+#define SkSPRITE_PREAMBLE(srcBM, x, y) const uint16_t* ctable = srcBM.getColorTable()->lock16BitCache()
+#define SkSPRITE_BLIT_PIXEL(dst, src) *dst = ctable[src]
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM) srcBM.getColorTable()->unlock16BitCache()
+#include "SkSpriteBlitterTemplate.h"
+
+#define SkSPRITE_CLASSNAME Sprite_D16_SIndex8_Blend
+#define SkSPRITE_ARGS , uint8_t alpha
+#define SkSPRITE_FIELDS uint8_t fSrcAlpha;
+#define SkSPRITE_INIT fSrcAlpha = alpha;
+#define SkSPRITE_DST_TYPE uint16_t
+#define SkSPRITE_SRC_TYPE uint8_t
+#define SkSPRITE_DST_GETADDR getAddr16
+#define SkSPRITE_SRC_GETADDR getAddr8
+#define SkSPRITE_PREAMBLE(srcBM, x, y) const uint16_t* ctable = srcBM.getColorTable()->lock16BitCache(); unsigned src_scale = SkAlpha255To256(fSrcAlpha);
+#define SkSPRITE_BLIT_PIXEL(dst, src) D16_S16_Blend_Pixel(dst, ctable[src], src_scale)
+#define SkSPRITE_NEXT_ROW
+#define SkSPRITE_POSTAMBLE(srcBM) srcBM.getColorTable()->unlock16BitCache();
+#include "SkSpriteBlitterTemplate.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+class Sprite_D16_S32_BlitRowProc : public SkSpriteBlitter {
+public:
+ Sprite_D16_S32_BlitRowProc(const SkBitmap& source)
+ : SkSpriteBlitter(source) {}
+
+ // overrides
+
+ virtual void setup(const SkBitmap& device, int left, int top,
+ const SkPaint& paint) {
+ this->INHERITED::setup(device, left, top, paint);
+
+ unsigned flags = 0;
+
+ if (paint.getAlpha() < 0xFF) {
+ flags |= SkBlitRow::kGlobalAlpha_Flag;
+ }
+ if (!fSource->isOpaque()) {
+ flags |= SkBlitRow::kSrcPixelAlpha_Flag;
+ }
+ if (paint.isDither()) {
+ flags |= SkBlitRow::kDither_Flag;
+ }
+ fProc = SkBlitRow::Factory(flags, SkBitmap::kRGB_565_Config);
+ }
+
+ virtual void blitRect(int x, int y, int width, int height) {
+ uint16_t* SK_RESTRICT dst = fDevice->getAddr16(x, y);
+ const SkPMColor* SK_RESTRICT src = fSource->getAddr32(x - fLeft,
+ y - fTop);
+ size_t dstRB = fDevice->rowBytes();
+ size_t srcRB = fSource->rowBytes();
+ SkBlitRow::Proc proc = fProc;
+ U8CPU alpha = fPaint->getAlpha();
+
+ while (--height >= 0) {
+ proc(dst, src, width, alpha, x, y);
+ y += 1;
+ dst = (uint16_t* SK_RESTRICT)((char*)dst + dstRB);
+ src = (const SkPMColor* SK_RESTRICT)((const char*)src + srcRB);
+ }
+ }
+
+private:
+ SkBlitRow::Proc fProc;
+
+ typedef SkSpriteBlitter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTemplatesPriv.h"
+
+SkSpriteBlitter* SkSpriteBlitter::ChooseD16(const SkBitmap& source,
+ const SkPaint& paint,
+ void* storage, size_t storageSize) {
+ if (paint.getMaskFilter() != NULL) { // may add cases for this
+ return NULL;
+ }
+ if (paint.getXfermode() != NULL) { // may add cases for this
+ return NULL;
+ }
+ if (paint.getColorFilter() != NULL) { // may add cases for this
+ return NULL;
+ }
+
+ SkSpriteBlitter* blitter = NULL;
+ unsigned alpha = paint.getAlpha();
+
+ switch (source.getConfig()) {
+ case SkBitmap::kARGB_8888_Config:
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_S32_BlitRowProc,
+ storage, storageSize, (source));
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ if (255 == alpha) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_S4444_Opaque,
+ storage, storageSize, (source));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_S4444_Blend,
+ storage, storageSize, (source, alpha >> 4));
+ }
+ break;
+ case SkBitmap::kRGB_565_Config:
+ if (255 == alpha) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_S16_Opaque,
+ storage, storageSize, (source));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_S16_Blend,
+ storage, storageSize, (source, alpha));
+ }
+ break;
+ case SkBitmap::kIndex8_Config:
+ if (paint.isDither()) {
+ // we don't support dither yet in these special cases
+ break;
+ }
+ if (source.isOpaque()) {
+ if (255 == alpha) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_SIndex8_Opaque,
+ storage, storageSize, (source));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_SIndex8_Blend,
+ storage, storageSize, (source, alpha));
+ }
+ } else {
+ if (255 == alpha) {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_SIndex8A_Opaque,
+ storage, storageSize, (source));
+ } else {
+ SK_PLACEMENT_NEW_ARGS(blitter, Sprite_D16_SIndex8A_Blend,
+ storage, storageSize, (source, alpha));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return blitter;
+}
diff --git a/core/SkStream.cpp b/core/SkStream.cpp
new file mode 100644
index 00000000..36a6f96f
--- /dev/null
+++ b/core/SkStream.cpp
@@ -0,0 +1,846 @@
+
+/*
+ * 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 "SkStream.h"
+#include "SkData.h"
+#include "SkFixed.h"
+#include "SkString.h"
+#include "SkOSFile.h"
+
+SK_DEFINE_INST_COUNT(SkStream)
+SK_DEFINE_INST_COUNT(SkWStream)
+SK_DEFINE_INST_COUNT(SkFILEStream)
+SK_DEFINE_INST_COUNT(SkMemoryStream)
+SK_DEFINE_INST_COUNT(SkFILEWStream)
+SK_DEFINE_INST_COUNT(SkMemoryWStream)
+SK_DEFINE_INST_COUNT(SkDynamicMemoryWStream)
+SK_DEFINE_INST_COUNT(SkDebugWStream)
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int8_t SkStream::readS8() {
+ int8_t value;
+ SkDEBUGCODE(size_t len =) this->read(&value, 1);
+ SkASSERT(1 == len);
+ return value;
+}
+
+int16_t SkStream::readS16() {
+ int16_t value;
+ SkDEBUGCODE(size_t len =) this->read(&value, 2);
+ SkASSERT(2 == len);
+ return value;
+}
+
+int32_t SkStream::readS32() {
+ int32_t value;
+ SkDEBUGCODE(size_t len =) this->read(&value, 4);
+ SkASSERT(4 == len);
+ return value;
+}
+
+SkScalar SkStream::readScalar() {
+ SkScalar value;
+ SkDEBUGCODE(size_t len =) this->read(&value, sizeof(SkScalar));
+ SkASSERT(sizeof(SkScalar) == len);
+ return value;
+}
+
+#define SK_MAX_BYTE_FOR_U8 0xFD
+#define SK_BYTE_SENTINEL_FOR_U16 0xFE
+#define SK_BYTE_SENTINEL_FOR_U32 0xFF
+
+size_t SkStream::readPackedUInt() {
+ uint8_t byte;
+ if (!this->read(&byte, 1)) {
+ return 0;
+ }
+ if (SK_BYTE_SENTINEL_FOR_U16 == byte) {
+ return this->readU16();
+ } else if (SK_BYTE_SENTINEL_FOR_U32 == byte) {
+ return this->readU32();
+ } else {
+ return byte;
+ }
+}
+
+SkData* SkStream::readData() {
+ size_t size = this->readU32();
+ if (0 == size) {
+ return SkData::NewEmpty();
+ } else {
+ void* buffer = sk_malloc_throw(size);
+ this->read(buffer, size);
+ return SkData::NewFromMalloc(buffer, size);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+SkWStream::~SkWStream()
+{
+}
+
+void SkWStream::newline()
+{
+ this->write("\n", 1);
+}
+
+void SkWStream::flush()
+{
+}
+
+bool SkWStream::writeText(const char text[])
+{
+ SkASSERT(text);
+ return this->write(text, strlen(text));
+}
+
+bool SkWStream::writeDecAsText(int32_t dec)
+{
+ SkString tmp;
+ tmp.appendS32(dec);
+ return this->write(tmp.c_str(), tmp.size());
+}
+
+bool SkWStream::writeBigDecAsText(int64_t dec, int minDigits)
+{
+ SkString tmp;
+ tmp.appendS64(dec, minDigits);
+ return this->write(tmp.c_str(), tmp.size());
+}
+
+bool SkWStream::writeHexAsText(uint32_t hex, int digits)
+{
+ SkString tmp;
+ tmp.appendHex(hex, digits);
+ return this->write(tmp.c_str(), tmp.size());
+}
+
+bool SkWStream::writeScalarAsText(SkScalar value)
+{
+ SkString tmp;
+ tmp.appendScalar(value);
+ return this->write(tmp.c_str(), tmp.size());
+}
+
+bool SkWStream::write8(U8CPU value) {
+ uint8_t v = SkToU8(value);
+ return this->write(&v, 1);
+}
+
+bool SkWStream::write16(U16CPU value) {
+ uint16_t v = SkToU16(value);
+ return this->write(&v, 2);
+}
+
+bool SkWStream::write32(uint32_t value) {
+ return this->write(&value, 4);
+}
+
+bool SkWStream::writeScalar(SkScalar value) {
+ return this->write(&value, sizeof(value));
+}
+
+bool SkWStream::writePackedUInt(size_t value) {
+ uint8_t data[5];
+ size_t len = 1;
+ if (value <= SK_MAX_BYTE_FOR_U8) {
+ data[0] = value;
+ len = 1;
+ } else if (value <= 0xFFFF) {
+ uint16_t value16 = value;
+ data[0] = SK_BYTE_SENTINEL_FOR_U16;
+ memcpy(&data[1], &value16, 2);
+ len = 3;
+ } else {
+ uint32_t value32 = value;
+ data[0] = SK_BYTE_SENTINEL_FOR_U32;
+ memcpy(&data[1], &value32, 4);
+ len = 5;
+ }
+ return this->write(data, len);
+}
+
+bool SkWStream::writeStream(SkStream* stream, size_t length) {
+ char scratch[1024];
+ const size_t MAX = sizeof(scratch);
+
+ while (length != 0) {
+ size_t n = length;
+ if (n > MAX) {
+ n = MAX;
+ }
+ stream->read(scratch, n);
+ if (!this->write(scratch, n)) {
+ return false;
+ }
+ length -= n;
+ }
+ return true;
+}
+
+bool SkWStream::writeData(const SkData* data) {
+ if (data) {
+ this->write32(data->size());
+ this->write(data->data(), data->size());
+ } else {
+ this->write32(0);
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFILEStream::SkFILEStream(const char file[]) : fName(file), fOwnership(kCallerPasses_Ownership) {
+ fFILE = file ? sk_fopen(fName.c_str(), kRead_SkFILE_Flag) : NULL;
+}
+
+SkFILEStream::SkFILEStream(FILE* file, Ownership ownership)
+ : fFILE((SkFILE*)file)
+ , fOwnership(ownership) {
+}
+
+SkFILEStream::~SkFILEStream() {
+ if (fFILE && fOwnership != kCallerRetains_Ownership) {
+ sk_fclose(fFILE);
+ }
+}
+
+void SkFILEStream::setPath(const char path[]) {
+ fName.set(path);
+ if (fFILE) {
+ sk_fclose(fFILE);
+ fFILE = NULL;
+ }
+ if (path) {
+ fFILE = sk_fopen(fName.c_str(), kRead_SkFILE_Flag);
+ }
+}
+
+size_t SkFILEStream::read(void* buffer, size_t size) {
+ if (fFILE) {
+ return sk_fread(buffer, size, fFILE);
+ }
+ return 0;
+}
+
+bool SkFILEStream::isAtEnd() const {
+ return sk_feof(fFILE);
+}
+
+bool SkFILEStream::rewind() {
+ if (fFILE) {
+ if (sk_frewind(fFILE)) {
+ return true;
+ }
+ // we hit an error
+ sk_fclose(fFILE);
+ fFILE = NULL;
+ }
+ return false;
+}
+
+SkStreamAsset* SkFILEStream::duplicate() const {
+ if (NULL == fFILE) {
+ return new SkMemoryStream();
+ }
+
+ if (NULL != fData.get()) {
+ return new SkMemoryStream(fData);
+ }
+
+ if (!fName.isEmpty()) {
+ SkAutoTUnref<SkFILEStream> that(new SkFILEStream(fName.c_str()));
+ if (sk_fidentical(that->fFILE, this->fFILE)) {
+ return that.detach();
+ }
+ }
+
+ fData.reset(SkData::NewFromFILE(fFILE));
+ if (NULL == fData.get()) {
+ return NULL;
+ }
+ return new SkMemoryStream(fData);
+}
+
+size_t SkFILEStream::getPosition() const {
+ return sk_ftell(fFILE);
+}
+
+bool SkFILEStream::seek(size_t position) {
+ return sk_fseek(fFILE, position);
+}
+
+bool SkFILEStream::move(long offset) {
+ return sk_fmove(fFILE, offset);
+}
+
+SkStreamAsset* SkFILEStream::fork() const {
+ SkAutoTUnref<SkStreamAsset> that(this->duplicate());
+ that->seek(this->getPosition());
+ return that.detach();
+}
+
+size_t SkFILEStream::getLength() const {
+ return sk_fgetsize(fFILE);
+}
+
+const void* SkFILEStream::getMemoryBase() {
+ if (NULL == fData.get()) {
+ return NULL;
+ }
+ return fData->data();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkData* newFromParams(const void* src, size_t size, bool copyData) {
+ if (copyData) {
+ return SkData::NewWithCopy(src, size);
+ } else {
+ return SkData::NewWithProc(src, size, NULL, NULL);
+ }
+}
+
+SkMemoryStream::SkMemoryStream() {
+ fData = SkData::NewEmpty();
+ fOffset = 0;
+}
+
+SkMemoryStream::SkMemoryStream(size_t size) {
+ fData = SkData::NewFromMalloc(sk_malloc_throw(size), size);
+ fOffset = 0;
+}
+
+SkMemoryStream::SkMemoryStream(const void* src, size_t size, bool copyData) {
+ fData = newFromParams(src, size, copyData);
+ fOffset = 0;
+}
+
+SkMemoryStream::SkMemoryStream(SkData* data) {
+ if (NULL == data) {
+ fData = SkData::NewEmpty();
+ } else {
+ fData = data;
+ fData->ref();
+ }
+ fOffset = 0;
+}
+
+SkMemoryStream::~SkMemoryStream() {
+ fData->unref();
+}
+
+void SkMemoryStream::setMemoryOwned(const void* src, size_t size) {
+ fData->unref();
+ fData = SkData::NewFromMalloc(src, size);
+ fOffset = 0;
+}
+
+void SkMemoryStream::setMemory(const void* src, size_t size, bool copyData) {
+ fData->unref();
+ fData = newFromParams(src, size, copyData);
+ fOffset = 0;
+}
+
+SkData* SkMemoryStream::copyToData() const {
+ fData->ref();
+ return fData;
+}
+
+SkData* SkMemoryStream::setData(SkData* data) {
+ fData->unref();
+ if (NULL == data) {
+ fData = SkData::NewEmpty();
+ } else {
+ fData = data;
+ fData->ref();
+ }
+ return data;
+}
+
+void SkMemoryStream::skipToAlign4() {
+ // cast to remove unary-minus warning
+ fOffset += -(int)fOffset & 0x03;
+}
+
+size_t SkMemoryStream::read(void* buffer, size_t size) {
+ size_t dataSize = fData->size();
+
+ if (size > dataSize - fOffset) {
+ size = dataSize - fOffset;
+ }
+ if (buffer) {
+ memcpy(buffer, fData->bytes() + fOffset, size);
+ }
+ fOffset += size;
+ return size;
+}
+
+bool SkMemoryStream::isAtEnd() const {
+ return fOffset == fData->size();
+}
+
+bool SkMemoryStream::rewind() {
+ fOffset = 0;
+ return true;
+}
+
+SkMemoryStream* SkMemoryStream::duplicate() const {
+ return SkNEW_ARGS(SkMemoryStream, (fData));
+}
+
+size_t SkMemoryStream::getPosition() const {
+ return fOffset;
+}
+
+bool SkMemoryStream::seek(size_t position) {
+ fOffset = position > fData->size()
+ ? fData->size()
+ : position;
+ return true;
+}
+
+bool SkMemoryStream::move(long offset) {
+ return this->seek(fOffset + offset);
+}
+
+SkMemoryStream* SkMemoryStream::fork() const {
+ SkAutoTUnref<SkMemoryStream> that(this->duplicate());
+ that->seek(fOffset);
+ return that.detach();
+}
+
+size_t SkMemoryStream::getLength() const {
+ return fData->size();
+}
+
+const void* SkMemoryStream::getMemoryBase() {
+ return fData->data();
+}
+
+const void* SkMemoryStream::getAtPos() {
+ return fData->bytes() + fOffset;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkFILEWStream::SkFILEWStream(const char path[])
+{
+ fFILE = sk_fopen(path, kWrite_SkFILE_Flag);
+}
+
+SkFILEWStream::~SkFILEWStream()
+{
+ if (fFILE)
+ sk_fclose(fFILE);
+}
+
+bool SkFILEWStream::write(const void* buffer, size_t size)
+{
+ if (fFILE == NULL)
+ return false;
+
+ if (sk_fwrite(buffer, size, fFILE) != size)
+ {
+ SkDEBUGCODE(SkDebugf("SkFILEWStream failed writing %d bytes\n", size);)
+ sk_fclose(fFILE);
+ fFILE = NULL;
+ return false;
+ }
+ return true;
+}
+
+void SkFILEWStream::flush()
+{
+ if (fFILE)
+ sk_fflush(fFILE);
+}
+
+////////////////////////////////////////////////////////////////////////
+
+SkMemoryWStream::SkMemoryWStream(void* buffer, size_t size)
+ : fBuffer((char*)buffer), fMaxLength(size), fBytesWritten(0)
+{
+}
+
+bool SkMemoryWStream::write(const void* buffer, size_t size)
+{
+ size = SkMin32(size, fMaxLength - fBytesWritten);
+ if (size > 0)
+ {
+ memcpy(fBuffer + fBytesWritten, buffer, size);
+ fBytesWritten += size;
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+#define SkDynamicMemoryWStream_MinBlockSize 256
+
+struct SkDynamicMemoryWStream::Block {
+ Block* fNext;
+ char* fCurr;
+ char* fStop;
+
+ const char* start() const { return (const char*)(this + 1); }
+ char* start() { return (char*)(this + 1); }
+ size_t avail() const { return fStop - fCurr; }
+ size_t written() const { return fCurr - this->start(); }
+
+ void init(size_t size)
+ {
+ fNext = NULL;
+ fCurr = this->start();
+ fStop = this->start() + size;
+ }
+
+ const void* append(const void* data, size_t size)
+ {
+ SkASSERT((size_t)(fStop - fCurr) >= size);
+ memcpy(fCurr, data, size);
+ fCurr += size;
+ return (const void*)((const char*)data + size);
+ }
+};
+
+SkDynamicMemoryWStream::SkDynamicMemoryWStream()
+ : fHead(NULL), fTail(NULL), fBytesWritten(0), fCopy(NULL)
+{
+}
+
+SkDynamicMemoryWStream::~SkDynamicMemoryWStream()
+{
+ reset();
+}
+
+void SkDynamicMemoryWStream::reset()
+{
+ this->invalidateCopy();
+
+ Block* block = fHead;
+
+ while (block != NULL) {
+ Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
+ fHead = fTail = NULL;
+ fBytesWritten = 0;
+}
+
+bool SkDynamicMemoryWStream::write(const void* buffer, size_t count)
+{
+ if (count > 0) {
+ this->invalidateCopy();
+
+ fBytesWritten += count;
+
+ size_t size;
+
+ if (fTail != NULL && fTail->avail() > 0) {
+ size = SkMin32(fTail->avail(), count);
+ buffer = fTail->append(buffer, size);
+ SkASSERT(count >= size);
+ count -= size;
+ if (count == 0)
+ return true;
+ }
+
+ size = SkMax32(count, SkDynamicMemoryWStream_MinBlockSize);
+ Block* block = (Block*)sk_malloc_throw(sizeof(Block) + size);
+ block->init(size);
+ block->append(buffer, count);
+
+ if (fTail != NULL)
+ fTail->fNext = block;
+ else
+ fHead = fTail = block;
+ fTail = block;
+ }
+ return true;
+}
+
+bool SkDynamicMemoryWStream::write(const void* buffer, size_t offset, size_t count)
+{
+ if (offset + count > fBytesWritten) {
+ return false; // test does not partially modify
+ }
+
+ this->invalidateCopy();
+
+ Block* block = fHead;
+ while (block != NULL) {
+ size_t size = block->written();
+ if (offset < size) {
+ size_t part = offset + count > size ? size - offset : count;
+ memcpy(block->start() + offset, buffer, part);
+ if (count <= part)
+ return true;
+ count -= part;
+ buffer = (const void*) ((char* ) buffer + part);
+ }
+ offset = offset > size ? offset - size : 0;
+ block = block->fNext;
+ }
+ return false;
+}
+
+bool SkDynamicMemoryWStream::read(void* buffer, size_t offset, size_t count)
+{
+ if (offset + count > fBytesWritten)
+ return false; // test does not partially modify
+ Block* block = fHead;
+ while (block != NULL) {
+ size_t size = block->written();
+ if (offset < size) {
+ size_t part = offset + count > size ? size - offset : count;
+ memcpy(buffer, block->start() + offset, part);
+ if (count <= part)
+ return true;
+ count -= part;
+ buffer = (void*) ((char* ) buffer + part);
+ }
+ offset = offset > size ? offset - size : 0;
+ block = block->fNext;
+ }
+ return false;
+}
+
+void SkDynamicMemoryWStream::copyTo(void* dst) const
+{
+ if (fCopy) {
+ memcpy(dst, fCopy->data(), fBytesWritten);
+ } else {
+ Block* block = fHead;
+
+ while (block != NULL) {
+ size_t size = block->written();
+ memcpy(dst, block->start(), size);
+ dst = (void*)((char*)dst + size);
+ block = block->fNext;
+ }
+ }
+}
+
+void SkDynamicMemoryWStream::padToAlign4()
+{
+ // cast to remove unary-minus warning
+ int padBytes = -(int)fBytesWritten & 0x03;
+ if (padBytes == 0)
+ return;
+ int zero = 0;
+ write(&zero, padBytes);
+}
+
+SkData* SkDynamicMemoryWStream::copyToData() const {
+ if (NULL == fCopy) {
+ void* buffer = sk_malloc_throw(fBytesWritten);
+ this->copyTo(buffer);
+ fCopy = SkData::NewFromMalloc(buffer, fBytesWritten);
+ }
+ fCopy->ref();
+ return fCopy;
+}
+
+void SkDynamicMemoryWStream::invalidateCopy() {
+ if (fCopy) {
+ fCopy->unref();
+ fCopy = NULL;
+ }
+}
+
+class SkBlockMemoryRefCnt : public SkRefCnt {
+public:
+ explicit SkBlockMemoryRefCnt(SkDynamicMemoryWStream::Block* head) : fHead(head) { }
+
+ virtual ~SkBlockMemoryRefCnt() {
+ SkDynamicMemoryWStream::Block* block = fHead;
+ while (block != NULL) {
+ SkDynamicMemoryWStream::Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
+ }
+
+ SkDynamicMemoryWStream::Block* const fHead;
+};
+
+class SkBlockMemoryStream : public SkStreamAsset {
+public:
+ SkBlockMemoryStream(SkDynamicMemoryWStream::Block* head, size_t size)
+ : fBlockMemory(SkNEW_ARGS(SkBlockMemoryRefCnt, (head))), fCurrent(head)
+ , fSize(size) , fOffset(0), fCurrentOffset(0) { }
+
+ SkBlockMemoryStream(SkBlockMemoryRefCnt* headRef, size_t size)
+ : fBlockMemory(SkRef(headRef)), fCurrent(fBlockMemory->fHead)
+ , fSize(size) , fOffset(0), fCurrentOffset(0) { }
+
+ virtual size_t read(void* buffer, size_t rawCount) SK_OVERRIDE {
+ size_t count = rawCount;
+ if (fOffset + count > fSize) {
+ count = fSize - fOffset;
+ }
+ size_t bytesLeftToRead = count;
+ while (fCurrent != NULL) {
+ size_t bytesLeftInCurrent = fCurrent->written() - fCurrentOffset;
+ size_t bytesFromCurrent = bytesLeftToRead <= bytesLeftInCurrent
+ ? bytesLeftToRead : bytesLeftInCurrent;
+ if (buffer) {
+ memcpy(buffer, fCurrent->start() + fCurrentOffset, bytesFromCurrent);
+ }
+ if (bytesLeftToRead <= bytesFromCurrent) {
+ fCurrentOffset += bytesFromCurrent;
+ fOffset += count;
+ return count;
+ }
+ bytesLeftToRead -= bytesFromCurrent;
+ buffer = SkTAddOffset<void>(buffer, bytesFromCurrent);
+ fCurrent = fCurrent->fNext;
+ fCurrentOffset = 0;
+ }
+ SkASSERT(false);
+ return 0;
+ }
+
+ virtual bool isAtEnd() const SK_OVERRIDE {
+ return fOffset == fSize;
+ }
+
+ virtual bool rewind() SK_OVERRIDE {
+ fCurrent = fBlockMemory->fHead;
+ fOffset = 0;
+ fCurrentOffset = 0;
+ return true;
+ }
+
+ virtual SkBlockMemoryStream* duplicate() const SK_OVERRIDE {
+ return SkNEW_ARGS(SkBlockMemoryStream, (fBlockMemory.get(), fSize));
+ }
+
+ virtual size_t getPosition() const SK_OVERRIDE {
+ return fOffset;
+ }
+
+ virtual bool seek(size_t position) SK_OVERRIDE {
+ // If possible, skip forward.
+ if (position >= fOffset) {
+ size_t skipAmount = position - fOffset;
+ return this->skip(skipAmount) == skipAmount;
+ }
+ // If possible, move backward within the current block.
+ size_t moveBackAmount = fOffset - position;
+ if (moveBackAmount <= fCurrentOffset) {
+ fCurrentOffset -= moveBackAmount;
+ fOffset -= moveBackAmount;
+ return true;
+ }
+ // Otherwise rewind and move forward.
+ return this->rewind() && this->skip(position) == position;
+ }
+
+ virtual bool move(long offset) SK_OVERRIDE {
+ return seek(fOffset + offset);
+ }
+
+ virtual SkBlockMemoryStream* fork() const SK_OVERRIDE {
+ SkAutoTUnref<SkBlockMemoryStream> that(this->duplicate());
+ that->fCurrent = this->fCurrent;
+ that->fOffset = this->fOffset;
+ that->fCurrentOffset = this->fCurrentOffset;
+ return that.detach();
+ }
+
+ virtual size_t getLength() const SK_OVERRIDE {
+ return fSize;
+ }
+
+ virtual const void* getMemoryBase() SK_OVERRIDE {
+ if (NULL == fBlockMemory->fHead->fNext) {
+ return fBlockMemory->fHead->start();
+ }
+ return NULL;
+ }
+
+private:
+ SkAutoTUnref<SkBlockMemoryRefCnt> const fBlockMemory;
+ SkDynamicMemoryWStream::Block const * fCurrent;
+ size_t const fSize;
+ size_t fOffset;
+ size_t fCurrentOffset;
+};
+
+SkStreamAsset* SkDynamicMemoryWStream::detachAsStream() {
+ if (fCopy) {
+ SkMemoryStream* stream = SkNEW_ARGS(SkMemoryStream, (fCopy));
+ this->reset();
+ return stream;
+ }
+ SkBlockMemoryStream* stream = SkNEW_ARGS(SkBlockMemoryStream, (fHead, fBytesWritten));
+ fHead = 0;
+ this->reset();
+ return stream;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkDebugWStream::newline()
+{
+#if defined(SK_DEBUG) || defined(SK_DEVELOPER)
+ SkDebugf("\n");
+#endif
+}
+
+bool SkDebugWStream::write(const void* buffer, size_t size)
+{
+#if defined(SK_DEBUG) || defined(SK_DEVELOPER)
+ char* s = new char[size+1];
+ memcpy(s, buffer, size);
+ s[size] = 0;
+ SkDebugf("%s", s);
+ delete[] s;
+#endif
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+
+static SkData* mmap_filename(const char path[]) {
+ SkFILE* file = sk_fopen(path, kRead_SkFILE_Flag);
+ if (NULL == file) {
+ return NULL;
+ }
+
+ SkData* data = SkData::NewFromFILE(file);
+ sk_fclose(file);
+ return data;
+}
+
+SkStreamAsset* SkStream::NewFromFile(const char path[]) {
+ SkAutoTUnref<SkData> data(mmap_filename(path));
+ if (data.get()) {
+ return SkNEW_ARGS(SkMemoryStream, (data.get()));
+ }
+
+ // If we get here, then our attempt at using mmap failed, so try normal
+ // file access.
+ SkFILEStream* stream = SkNEW_ARGS(SkFILEStream, (path));
+ if (!stream->isValid()) {
+ stream->unref();
+ stream = NULL;
+ }
+ return stream;
+}
diff --git a/core/SkString.cpp b/core/SkString.cpp
new file mode 100644
index 00000000..4e5e204e
--- /dev/null
+++ b/core/SkString.cpp
@@ -0,0 +1,659 @@
+
+/*
+ * 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 "SkString.h"
+#include "SkFixed.h"
+#include "SkThread.h"
+#include "SkUtils.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+// number of bytes (on the stack) to receive the printf result
+static const size_t kBufferSize = 1024;
+
+#ifdef SK_BUILD_FOR_WIN
+ #define VSNPRINTF(buffer, size, format, args) \
+ _vsnprintf_s(buffer, size, _TRUNCATE, format, args)
+ #define SNPRINTF _snprintf
+#else
+ #define VSNPRINTF vsnprintf
+ #define SNPRINTF snprintf
+#endif
+
+#define ARGS_TO_BUFFER(format, buffer, size) \
+ do { \
+ va_list args; \
+ va_start(args, format); \
+ VSNPRINTF(buffer, size, format, args); \
+ va_end(args); \
+ } while (0)
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkStrEndsWith(const char string[], const char suffixStr[]) {
+ SkASSERT(string);
+ SkASSERT(suffixStr);
+ size_t strLen = strlen(string);
+ size_t suffixLen = strlen(suffixStr);
+ return strLen >= 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[]) {
+ int index = 0;
+ do {
+ const char* limit = strchr(prefixes, '\0');
+ if (!strncmp(string, prefixes, limit - prefixes)) {
+ return index;
+ }
+ prefixes = limit + 1;
+ index++;
+ } while (prefixes[0]);
+ return -1;
+}
+
+char* SkStrAppendU32(char string[], uint32_t dec) {
+ SkDEBUGCODE(char* start = string;)
+
+ char buffer[SkStrAppendU32_MaxSize];
+ char* p = buffer + sizeof(buffer);
+
+ do {
+ *--p = SkToU8('0' + dec % 10);
+ dec /= 10;
+ } while (dec != 0);
+
+ SkASSERT(p >= buffer);
+ char* stop = buffer + sizeof(buffer);
+ while (p < stop) {
+ *string++ = *p++;
+ }
+ SkASSERT(string - start <= SkStrAppendU32_MaxSize);
+ return string;
+}
+
+char* SkStrAppendS32(char string[], int32_t dec) {
+ if (dec < 0) {
+ *string++ = '-';
+ dec = -dec;
+ }
+ return SkStrAppendU32(string, static_cast<uint32_t>(dec));
+}
+
+char* SkStrAppendU64(char string[], uint64_t dec, int minDigits) {
+ SkDEBUGCODE(char* start = string;)
+
+ char buffer[SkStrAppendU64_MaxSize];
+ char* p = buffer + sizeof(buffer);
+
+ do {
+ *--p = SkToU8('0' + (int32_t) (dec % 10));
+ dec /= 10;
+ minDigits--;
+ } while (dec != 0);
+
+ while (minDigits > 0) {
+ *--p = '0';
+ minDigits--;
+ }
+
+ SkASSERT(p >= buffer);
+ size_t cp_len = buffer + sizeof(buffer) - p;
+ memcpy(string, p, cp_len);
+ string += cp_len;
+
+ SkASSERT(string - start <= SkStrAppendU64_MaxSize);
+ return string;
+}
+
+char* SkStrAppendS64(char string[], int64_t dec, int minDigits) {
+ if (dec < 0) {
+ *string++ = '-';
+ dec = -dec;
+ }
+ return SkStrAppendU64(string, static_cast<uint64_t>(dec), minDigits);
+}
+
+char* SkStrAppendFloat(char string[], float value) {
+ // since floats have at most 8 significant digits, we limit our %g to that.
+ static const char gFormat[] = "%.8g";
+ // make it 1 larger for the terminating 0
+ char buffer[SkStrAppendScalar_MaxSize + 1];
+ int len = SNPRINTF(buffer, sizeof(buffer), gFormat, value);
+ memcpy(string, buffer, len);
+ SkASSERT(len <= SkStrAppendScalar_MaxSize);
+ return string + len;
+}
+
+char* SkStrAppendFixed(char string[], SkFixed x) {
+ SkDEBUGCODE(char* start = string;)
+ if (x < 0) {
+ *string++ = '-';
+ x = -x;
+ }
+
+ unsigned frac = x & 0xFFFF;
+ x >>= 16;
+ if (frac == 0xFFFF) {
+ // need to do this to "round up", since 65535/65536 is closer to 1 than to .9999
+ x += 1;
+ frac = 0;
+ }
+ string = SkStrAppendS32(string, x);
+
+ // now handle the fractional part (if any)
+ if (frac) {
+ static const uint16_t gTens[] = { 1000, 100, 10, 1 };
+ const uint16_t* tens = gTens;
+
+ x = SkFixedRound(frac * 10000);
+ SkASSERT(x <= 10000);
+ if (x == 10000) {
+ x -= 1;
+ }
+ *string++ = '.';
+ do {
+ unsigned powerOfTen = *tens++;
+ *string++ = SkToU8('0' + x / powerOfTen);
+ x %= powerOfTen;
+ } while (x != 0);
+ }
+
+ SkASSERT(string - start <= SkStrAppendScalar_MaxSize);
+ return string;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// the 3 values are [length] [refcnt] [terminating zero data]
+const SkString::Rec SkString::gEmptyRec = { 0, 0, 0 };
+
+#define SizeOfRec() (gEmptyRec.data() - (const char*)&gEmptyRec)
+
+static uint32_t trim_size_t_to_u32(size_t value) {
+ if (sizeof(size_t) > sizeof(uint32_t)) {
+ if (value > SK_MaxU32) {
+ value = SK_MaxU32;
+ }
+ }
+ return (uint32_t)value;
+}
+
+static size_t check_add32(size_t base, size_t extra) {
+ SkASSERT(base <= SK_MaxU32);
+ if (sizeof(size_t) > sizeof(uint32_t)) {
+ if (base + extra > SK_MaxU32) {
+ extra = SK_MaxU32 - base;
+ }
+ }
+ return extra;
+}
+
+SkString::Rec* SkString::AllocRec(const char text[], size_t len) {
+ Rec* rec;
+
+ if (0 == len) {
+ rec = const_cast<Rec*>(&gEmptyRec);
+ } else {
+ len = trim_size_t_to_u32(len);
+
+ // add 1 for terminating 0, then align4 so we can have some slop when growing the string
+ rec = (Rec*)sk_malloc_throw(SizeOfRec() + SkAlign4(len + 1));
+ rec->fLength = SkToU32(len);
+ rec->fRefCnt = 1;
+ if (text) {
+ memcpy(rec->data(), text, len);
+ }
+ rec->data()[len] = 0;
+ }
+ return rec;
+}
+
+SkString::Rec* SkString::RefRec(Rec* src) {
+ if (src != &gEmptyRec) {
+ sk_atomic_inc(&src->fRefCnt);
+ }
+ return src;
+}
+
+#ifdef SK_DEBUG
+void SkString::validate() const {
+ // make sure know one has written over our global
+ SkASSERT(0 == gEmptyRec.fLength);
+ SkASSERT(0 == gEmptyRec.fRefCnt);
+ SkASSERT(0 == gEmptyRec.data()[0]);
+
+ if (fRec != &gEmptyRec) {
+ SkASSERT(fRec->fLength > 0);
+ SkASSERT(fRec->fRefCnt > 0);
+ SkASSERT(0 == fRec->data()[fRec->fLength]);
+ }
+ SkASSERT(fStr == c_str());
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkString::SkString() : fRec(const_cast<Rec*>(&gEmptyRec)) {
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+SkString::SkString(size_t len) {
+ fRec = AllocRec(NULL, len);
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+SkString::SkString(const char text[]) {
+ size_t len = text ? strlen(text) : 0;
+
+ fRec = AllocRec(text, len);
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+SkString::SkString(const char text[], size_t len) {
+ fRec = AllocRec(text, len);
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+SkString::SkString(const SkString& src) {
+ src.validate();
+
+ fRec = RefRec(src.fRec);
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+SkString::~SkString() {
+ this->validate();
+
+ if (fRec->fLength) {
+ SkASSERT(fRec->fRefCnt > 0);
+ if (sk_atomic_dec(&fRec->fRefCnt) == 1) {
+ sk_free(fRec);
+ }
+ }
+}
+
+bool SkString::equals(const SkString& src) const {
+ return fRec == src.fRec || this->equals(src.c_str(), src.size());
+}
+
+bool SkString::equals(const char text[]) const {
+ return this->equals(text, text ? strlen(text) : 0);
+}
+
+bool SkString::equals(const char text[], size_t len) const {
+ SkASSERT(len == 0 || text != NULL);
+
+ return fRec->fLength == len && !memcmp(fRec->data(), text, len);
+}
+
+SkString& SkString::operator=(const SkString& src) {
+ this->validate();
+
+ if (fRec != src.fRec) {
+ SkString tmp(src);
+ this->swap(tmp);
+ }
+ return *this;
+}
+
+SkString& SkString::operator=(const char text[]) {
+ this->validate();
+
+ SkString tmp(text);
+ this->swap(tmp);
+
+ return *this;
+}
+
+void SkString::reset() {
+ this->validate();
+
+ if (fRec->fLength) {
+ SkASSERT(fRec->fRefCnt > 0);
+ if (sk_atomic_dec(&fRec->fRefCnt) == 1) {
+ sk_free(fRec);
+ }
+ }
+
+ fRec = const_cast<Rec*>(&gEmptyRec);
+#ifdef SK_DEBUG
+ fStr = fRec->data();
+#endif
+}
+
+char* SkString::writable_str() {
+ this->validate();
+
+ if (fRec->fLength) {
+ if (fRec->fRefCnt > 1) {
+ Rec* rec = AllocRec(fRec->data(), fRec->fLength);
+ if (sk_atomic_dec(&fRec->fRefCnt) == 1) {
+ // In this case after our check of fRecCnt > 1, we suddenly
+ // did become the only owner, so now we have two copies of the
+ // data (fRec and rec), so we need to delete one of them.
+ sk_free(fRec);
+ }
+ fRec = rec;
+ #ifdef SK_DEBUG
+ fStr = fRec->data();
+ #endif
+ }
+ }
+ return fRec->data();
+}
+
+void SkString::set(const char text[]) {
+ this->set(text, text ? strlen(text) : 0);
+}
+
+void SkString::set(const char text[], size_t len) {
+ len = trim_size_t_to_u32(len);
+
+ if (0 == len) {
+ this->reset();
+ } else if (1 == fRec->fRefCnt && len <= fRec->fLength) {
+ // should we resize if len <<<< fLength, to save RAM? (e.g. len < (fLength>>1))?
+ // just use less of the buffer without allocating a smaller one
+ char* p = this->writable_str();
+ if (text) {
+ memcpy(p, text, len);
+ }
+ p[len] = 0;
+ fRec->fLength = SkToU32(len);
+ } else if (1 == fRec->fRefCnt && (fRec->fLength >> 2) == (len >> 2)) {
+ // we have spare room in the current allocation, so don't alloc a larger one
+ char* p = this->writable_str();
+ if (text) {
+ memcpy(p, text, len);
+ }
+ p[len] = 0;
+ fRec->fLength = SkToU32(len);
+ } else {
+ SkString tmp(text, len);
+ this->swap(tmp);
+ }
+}
+
+void SkString::setUTF16(const uint16_t src[]) {
+ int count = 0;
+
+ while (src[count]) {
+ count += 1;
+ }
+ this->setUTF16(src, count);
+}
+
+void SkString::setUTF16(const uint16_t src[], size_t count) {
+ count = trim_size_t_to_u32(count);
+
+ if (0 == count) {
+ this->reset();
+ } else if (count <= fRec->fLength) {
+ // should we resize if len <<<< fLength, to save RAM? (e.g. len < (fLength>>1))
+ if (count < fRec->fLength) {
+ this->resize(count);
+ }
+ char* p = this->writable_str();
+ for (size_t i = 0; i < count; i++) {
+ p[i] = SkToU8(src[i]);
+ }
+ p[count] = 0;
+ } else {
+ SkString tmp(count); // puts a null terminator at the end of the string
+ char* p = tmp.writable_str();
+
+ for (size_t i = 0; i < count; i++) {
+ p[i] = SkToU8(src[i]);
+ }
+ this->swap(tmp);
+ }
+}
+
+void SkString::insert(size_t offset, const char text[]) {
+ this->insert(offset, text, text ? strlen(text) : 0);
+}
+
+void SkString::insert(size_t offset, const char text[], size_t len) {
+ if (len) {
+ size_t length = fRec->fLength;
+ if (offset > length) {
+ offset = length;
+ }
+
+ // Check if length + len exceeds 32bits, we trim len
+ len = check_add32(length, len);
+ if (0 == len) {
+ return;
+ }
+
+ /* If we're the only owner, and we have room in our allocation for the insert,
+ do it in place, rather than allocating a new buffer.
+
+ To know we have room, compare the allocated sizes
+ beforeAlloc = SkAlign4(length + 1)
+ afterAlloc = SkAligh4(length + 1 + len)
+ but SkAlign4(x) is (x + 3) >> 2 << 2
+ which is equivalent for testing to (length + 1 + 3) >> 2 == (length + 1 + 3 + len) >> 2
+ and we can then eliminate the +1+3 since that doesn't affec the answer
+ */
+ if (1 == fRec->fRefCnt && (length >> 2) == ((length + len) >> 2)) {
+ char* dst = this->writable_str();
+
+ if (offset < length) {
+ memmove(dst + offset + len, dst + offset, length - offset);
+ }
+ memcpy(dst + offset, text, len);
+
+ dst[length + len] = 0;
+ fRec->fLength = SkToU32(length + len);
+ } else {
+ /* Seems we should use realloc here, since that is safe if it fails
+ (we have the original data), and might be faster than alloc/copy/free.
+ */
+ SkString tmp(fRec->fLength + len);
+ char* dst = tmp.writable_str();
+
+ if (offset > 0) {
+ memcpy(dst, fRec->data(), offset);
+ }
+ memcpy(dst + offset, text, len);
+ if (offset < fRec->fLength) {
+ memcpy(dst + offset + len, fRec->data() + offset,
+ fRec->fLength - offset);
+ }
+
+ this->swap(tmp);
+ }
+ }
+}
+
+void SkString::insertUnichar(size_t offset, SkUnichar uni) {
+ char buffer[kMaxBytesInUTF8Sequence];
+ size_t len = SkUTF8_FromUnichar(uni, buffer);
+
+ if (len) {
+ this->insert(offset, buffer, len);
+ }
+}
+
+void SkString::insertS32(size_t offset, int32_t dec) {
+ char buffer[SkStrAppendS32_MaxSize];
+ char* stop = SkStrAppendS32(buffer, dec);
+ this->insert(offset, buffer, stop - buffer);
+}
+
+void SkString::insertS64(size_t offset, int64_t dec, int minDigits) {
+ char buffer[SkStrAppendS64_MaxSize];
+ char* stop = SkStrAppendS64(buffer, dec, minDigits);
+ this->insert(offset, buffer, stop - buffer);
+}
+
+void SkString::insertU32(size_t offset, uint32_t dec) {
+ char buffer[SkStrAppendU32_MaxSize];
+ char* stop = SkStrAppendU32(buffer, dec);
+ this->insert(offset, buffer, stop - buffer);
+}
+
+void SkString::insertU64(size_t offset, uint64_t dec, int minDigits) {
+ char buffer[SkStrAppendU64_MaxSize];
+ char* stop = SkStrAppendU64(buffer, dec, minDigits);
+ this->insert(offset, buffer, stop - buffer);
+}
+
+void SkString::insertHex(size_t offset, uint32_t hex, int minDigits) {
+ minDigits = SkPin32(minDigits, 0, 8);
+
+ static const char gHex[] = "0123456789ABCDEF";
+
+ char buffer[8];
+ char* p = buffer + sizeof(buffer);
+
+ do {
+ *--p = gHex[hex & 0xF];
+ hex >>= 4;
+ minDigits -= 1;
+ } while (hex != 0);
+
+ while (--minDigits >= 0) {
+ *--p = '0';
+ }
+
+ SkASSERT(p >= buffer);
+ this->insert(offset, p, buffer + sizeof(buffer) - p);
+}
+
+void SkString::insertScalar(size_t offset, SkScalar value) {
+ char buffer[SkStrAppendScalar_MaxSize];
+ char* stop = SkStrAppendScalar(buffer, value);
+ this->insert(offset, buffer, stop - buffer);
+}
+
+void SkString::printf(const char format[], ...) {
+ char buffer[kBufferSize];
+ ARGS_TO_BUFFER(format, buffer, kBufferSize);
+
+ this->set(buffer, strlen(buffer));
+}
+
+void SkString::appendf(const char format[], ...) {
+ char buffer[kBufferSize];
+ ARGS_TO_BUFFER(format, buffer, kBufferSize);
+
+ this->append(buffer, strlen(buffer));
+}
+
+void SkString::appendf(const char format[], va_list args) {
+ char buffer[kBufferSize];
+ VSNPRINTF(buffer, kBufferSize, format, args);
+
+ this->append(buffer, strlen(buffer));
+}
+
+void SkString::prependf(const char format[], ...) {
+ char buffer[kBufferSize];
+ ARGS_TO_BUFFER(format, buffer, kBufferSize);
+
+ this->prepend(buffer, strlen(buffer));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkString::remove(size_t offset, size_t length) {
+ size_t size = this->size();
+
+ if (offset < size) {
+ if (offset + length > size) {
+ length = size - offset;
+ }
+ if (length > 0) {
+ SkASSERT(size > length);
+ SkString tmp(size - length);
+ char* dst = tmp.writable_str();
+ const char* src = this->c_str();
+
+ if (offset) {
+ SkASSERT(offset <= tmp.size());
+ memcpy(dst, src, offset);
+ }
+ size_t tail = size - offset - length;
+ SkASSERT((int32_t)tail >= 0);
+ if (tail) {
+ // SkASSERT(offset + length <= tmp.size());
+ memcpy(dst + offset, src + offset + length, tail);
+ }
+ SkASSERT(dst[tmp.size()] == 0);
+ this->swap(tmp);
+ }
+ }
+}
+
+void SkString::swap(SkString& other) {
+ this->validate();
+ other.validate();
+
+ SkTSwap<Rec*>(fRec, other.fRec);
+#ifdef SK_DEBUG
+ SkTSwap<const char*>(fStr, other.fStr);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkAutoUCS2::SkAutoUCS2(const char utf8[]) {
+ size_t len = strlen(utf8);
+ fUCS2 = (uint16_t*)sk_malloc_throw((len + 1) * sizeof(uint16_t));
+
+ uint16_t* dst = fUCS2;
+ for (;;) {
+ SkUnichar uni = SkUTF8_NextUnichar(&utf8);
+ *dst++ = SkToU16(uni);
+ if (uni == 0) {
+ break;
+ }
+ }
+ fCount = (int)(dst - fUCS2);
+}
+
+SkAutoUCS2::~SkAutoUCS2() {
+ sk_free(fUCS2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkString SkStringPrintf(const char* format, ...) {
+ SkString formattedOutput;
+ char buffer[kBufferSize];
+ ARGS_TO_BUFFER(format, buffer, kBufferSize);
+ formattedOutput.set(buffer);
+ return formattedOutput;
+}
+
+#undef VSNPRINTF
+#undef SNPRINTF
diff --git a/core/SkStringUtils.cpp b/core/SkStringUtils.cpp
new file mode 100644
index 00000000..0f93b8e3
--- /dev/null
+++ b/core/SkStringUtils.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkString.h"
+#include "SkStringUtils.h"
+
+void SkAddFlagToString(SkString* string, bool flag, const char* flagStr, bool* needSeparator) {
+ if (flag) {
+ if (*needSeparator) {
+ string->append("|");
+ }
+ string->append(flagStr);
+ *needSeparator = true;
+ }
+}
diff --git a/core/SkStroke.cpp b/core/SkStroke.cpp
new file mode 100644
index 00000000..d094ef65
--- /dev/null
+++ b/core/SkStroke.cpp
@@ -0,0 +1,725 @@
+/*
+ * 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.
+ */
+
+#include "SkStrokerPriv.h"
+#include "SkGeometry.h"
+#include "SkPath.h"
+
+#define kMaxQuadSubdivide 5
+#define kMaxCubicSubdivide 7
+
+static inline bool degenerate_vector(const SkVector& v) {
+ return !SkPoint::CanNormalize(v.fX, v.fY);
+}
+
+static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
+ /* root2/2 is a 45-degree angle
+ make this constant bigger for more subdivisions (but not >= 1)
+ */
+ static const SkScalar kFlatEnoughNormalDotProd =
+ SK_ScalarSqrt2/2 + SK_Scalar1/10;
+
+ SkASSERT(kFlatEnoughNormalDotProd > 0 &&
+ kFlatEnoughNormalDotProd < SK_Scalar1);
+
+ return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
+}
+
+static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
+ static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000;
+
+ return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd;
+}
+
+static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
+ SkScalar radius,
+ SkVector* normal, SkVector* unitNormal) {
+ if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
+ return false;
+ }
+ unitNormal->rotateCCW();
+ unitNormal->scale(radius, normal);
+ return true;
+}
+
+static bool set_normal_unitnormal(const SkVector& vec,
+ SkScalar radius,
+ SkVector* normal, SkVector* unitNormal) {
+ if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
+ return false;
+ }
+ unitNormal->rotateCCW();
+ unitNormal->scale(radius, normal);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkPathStroker {
+public:
+ SkPathStroker(const SkPath& src,
+ SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
+ SkPaint::Join join);
+
+ void moveTo(const SkPoint&);
+ void lineTo(const SkPoint&);
+ void quadTo(const SkPoint&, const SkPoint&);
+ void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
+ void close(bool isLine) { this->finishContour(true, isLine); }
+
+ void done(SkPath* dst, bool isLine) {
+ this->finishContour(false, isLine);
+ fOuter.addPath(fExtra);
+ dst->swap(fOuter);
+ }
+
+private:
+ SkScalar fRadius;
+ SkScalar fInvMiterLimit;
+
+ SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
+ SkPoint fFirstPt, fPrevPt; // on original path
+ SkPoint fFirstOuterPt;
+ int fSegmentCount;
+ bool fPrevIsLine;
+
+ SkStrokerPriv::CapProc fCapper;
+ SkStrokerPriv::JoinProc fJoiner;
+
+ SkPath fInner, fOuter; // outer is our working answer, inner is temp
+ SkPath fExtra; // added as extra complete contours
+
+ void finishContour(bool close, bool isLine);
+ void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
+ bool isLine);
+ void postJoinTo(const SkPoint&, const SkVector& normal,
+ const SkVector& unitNormal);
+
+ void line_to(const SkPoint& currPt, const SkVector& normal);
+ void quad_to(const SkPoint pts[3],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalBC, SkVector* unitNormalBC,
+ int subDivide);
+ void cubic_to(const SkPoint pts[4],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalCD, SkVector* unitNormalCD,
+ int subDivide);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
+ SkVector* unitNormal, bool currIsLine) {
+ SkASSERT(fSegmentCount >= 0);
+
+ SkScalar prevX = fPrevPt.fX;
+ SkScalar prevY = fPrevPt.fY;
+
+ SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
+ unitNormal));
+
+ if (fSegmentCount == 0) {
+ fFirstNormal = *normal;
+ fFirstUnitNormal = *unitNormal;
+ fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
+
+ fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
+ fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
+ } else { // we have a previous segment
+ fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
+ fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
+ }
+ fPrevIsLine = currIsLine;
+}
+
+void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
+ const SkVector& unitNormal) {
+ fPrevPt = currPt;
+ fPrevUnitNormal = unitNormal;
+ fPrevNormal = normal;
+ fSegmentCount += 1;
+}
+
+void SkPathStroker::finishContour(bool close, bool currIsLine) {
+ if (fSegmentCount > 0) {
+ SkPoint pt;
+
+ if (close) {
+ fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
+ fFirstUnitNormal, fRadius, fInvMiterLimit,
+ fPrevIsLine, currIsLine);
+ fOuter.close();
+ // now add fInner as its own contour
+ fInner.getLastPt(&pt);
+ fOuter.moveTo(pt.fX, pt.fY);
+ fOuter.reversePathTo(fInner);
+ fOuter.close();
+ } else { // add caps to start and end
+ // cap the end
+ fInner.getLastPt(&pt);
+ fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
+ currIsLine ? &fInner : NULL);
+ fOuter.reversePathTo(fInner);
+ // cap the start
+ fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
+ fPrevIsLine ? &fInner : NULL);
+ fOuter.close();
+ }
+ }
+ // since we may re-use fInner, we rewind instead of reset, to save on
+ // reallocating its internal storage.
+ fInner.rewind();
+ fSegmentCount = -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPathStroker::SkPathStroker(const SkPath& src,
+ SkScalar radius, SkScalar miterLimit,
+ SkPaint::Cap cap, SkPaint::Join join)
+ : fRadius(radius) {
+
+ /* This is only used when join is miter_join, but we initialize it here
+ so that it is always defined, to fis valgrind warnings.
+ */
+ fInvMiterLimit = 0;
+
+ if (join == SkPaint::kMiter_Join) {
+ if (miterLimit <= SK_Scalar1) {
+ join = SkPaint::kBevel_Join;
+ } else {
+ fInvMiterLimit = SkScalarInvert(miterLimit);
+ }
+ }
+ fCapper = SkStrokerPriv::CapFactory(cap);
+ fJoiner = SkStrokerPriv::JoinFactory(join);
+ fSegmentCount = -1;
+ fPrevIsLine = false;
+
+ // Need some estimate of how large our final result (fOuter)
+ // and our per-contour temp (fInner) will be, so we don't spend
+ // extra time repeatedly growing these arrays.
+ //
+ // 3x for result == inner + outer + join (swag)
+ // 1x for inner == 'wag' (worst contour length would be better guess)
+ fOuter.incReserve(src.countPoints() * 3);
+ fInner.incReserve(src.countPoints());
+}
+
+void SkPathStroker::moveTo(const SkPoint& pt) {
+ if (fSegmentCount > 0) {
+ this->finishContour(false, false);
+ }
+ fSegmentCount = 0;
+ fFirstPt = fPrevPt = pt;
+}
+
+void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
+ fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
+ fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
+}
+
+void SkPathStroker::lineTo(const SkPoint& currPt) {
+ if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
+ return;
+ }
+ SkVector normal, unitNormal;
+
+ this->preJoinTo(currPt, &normal, &unitNormal, true);
+ this->line_to(currPt, normal);
+ this->postJoinTo(currPt, normal, unitNormal);
+}
+
+void SkPathStroker::quad_to(const SkPoint pts[3],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalBC, SkVector* unitNormalBC,
+ int subDivide) {
+ if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
+ normalBC, unitNormalBC)) {
+ // pts[1] nearly equals pts[2], so just draw a line to pts[2]
+ this->line_to(pts[2], normalAB);
+ *normalBC = normalAB;
+ *unitNormalBC = unitNormalAB;
+ return;
+ }
+
+ if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
+ SkPoint tmp[5];
+ SkVector norm, unit;
+
+ SkChopQuadAtHalf(pts, tmp);
+ this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
+ this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
+ } else {
+ SkVector normalB;
+
+ normalB = pts[2] - pts[0];
+ normalB.rotateCCW();
+ SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
+ SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
+ SkScalarSqrt((SK_Scalar1 + dot)/2))));
+
+ fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
+ pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
+ fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
+ pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
+ }
+}
+
+void SkPathStroker::cubic_to(const SkPoint pts[4],
+ const SkVector& normalAB, const SkVector& unitNormalAB,
+ SkVector* normalCD, SkVector* unitNormalCD,
+ int subDivide) {
+ SkVector ab = pts[1] - pts[0];
+ SkVector cd = pts[3] - pts[2];
+ SkVector normalBC, unitNormalBC;
+
+ bool degenerateAB = degenerate_vector(ab);
+ bool degenerateCD = degenerate_vector(cd);
+
+ if (degenerateAB && degenerateCD) {
+DRAW_LINE:
+ this->line_to(pts[3], normalAB);
+ *normalCD = normalAB;
+ *unitNormalCD = unitNormalAB;
+ return;
+ }
+
+ if (degenerateAB) {
+ ab = pts[2] - pts[0];
+ degenerateAB = degenerate_vector(ab);
+ }
+ if (degenerateCD) {
+ cd = pts[3] - pts[1];
+ degenerateCD = degenerate_vector(cd);
+ }
+ if (degenerateAB || degenerateCD) {
+ goto DRAW_LINE;
+ }
+ SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
+ bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
+ &normalBC, &unitNormalBC);
+#ifndef SK_IGNORE_CUBIC_STROKE_FIX
+ if (--subDivide < 0) {
+ goto DRAW_LINE;
+ }
+#endif
+ if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
+ normals_too_curvy(unitNormalBC, *unitNormalCD)) {
+#ifdef SK_IGNORE_CUBIC_STROKE_FIX
+ // subdivide if we can
+ if (--subDivide < 0) {
+ goto DRAW_LINE;
+ }
+#endif
+ SkPoint tmp[7];
+ SkVector norm, unit, dummy, unitDummy;
+
+ SkChopCubicAtHalf(pts, tmp);
+ this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
+ subDivide);
+ // we use dummys since we already have a valid (and more accurate)
+ // normals for CD
+ this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
+ } else {
+ SkVector normalB, normalC;
+
+ // need normals to inset/outset the off-curve pts B and C
+
+ SkVector unitBC = pts[2] - pts[1];
+ unitBC.normalize();
+ unitBC.rotateCCW();
+
+ normalB = unitNormalAB + unitBC;
+ normalC = *unitNormalCD + unitBC;
+
+ SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
+ SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
+ SkScalarSqrt((SK_Scalar1 + dot)/2))));
+ dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
+ SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
+ SkScalarSqrt((SK_Scalar1 + dot)/2))));
+
+ fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
+ pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
+ pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
+
+ fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
+ pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
+ pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
+ }
+}
+
+void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+ bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
+ bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
+
+ if (degenerateAB | degenerateBC) {
+ if (degenerateAB ^ degenerateBC) {
+ this->lineTo(pt2);
+ }
+ return;
+ }
+
+ SkVector normalAB, unitAB, normalBC, unitBC;
+
+ this->preJoinTo(pt1, &normalAB, &unitAB, false);
+
+ {
+ SkPoint pts[3], tmp[5];
+ pts[0] = fPrevPt;
+ pts[1] = pt1;
+ pts[2] = pt2;
+
+ if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
+ unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
+ unitBC.rotateCCW();
+ if (normals_too_pinchy(unitAB, unitBC)) {
+ normalBC = unitBC;
+ normalBC.scale(fRadius);
+
+ fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
+ fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
+ fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
+
+ fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
+ fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
+ fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
+
+ fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
+ SkPath::kCW_Direction);
+ } else {
+ this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ SkVector n = normalBC;
+ SkVector u = unitBC;
+ this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ }
+ } else {
+ this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
+ kMaxQuadSubdivide);
+ }
+ }
+
+ this->postJoinTo(pt2, normalBC, unitBC);
+}
+
+void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
+ const SkPoint& pt3) {
+ bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
+ bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
+ bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
+
+ if (degenerateAB + degenerateBC + degenerateCD >= 2) {
+ this->lineTo(pt3);
+ return;
+ }
+
+ SkVector normalAB, unitAB, normalCD, unitCD;
+
+ // find the first tangent (which might be pt1 or pt2
+ {
+ const SkPoint* nextPt = &pt1;
+ if (degenerateAB)
+ nextPt = &pt2;
+ this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
+ }
+
+ {
+ SkPoint pts[4], tmp[13];
+ int i, count;
+ SkVector n, u;
+ SkScalar tValues[3];
+
+ pts[0] = fPrevPt;
+ pts[1] = pt1;
+ pts[2] = pt2;
+ pts[3] = pt3;
+
+ count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
+ n = normalAB;
+ u = unitAB;
+ for (i = 0; i < count; i++) {
+ this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
+ kMaxCubicSubdivide);
+ if (i == count - 1) {
+ break;
+ }
+ n = normalCD;
+ u = unitCD;
+
+ }
+ }
+
+ this->postJoinTo(pt3, normalCD, unitCD);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkPaintDefaults.h"
+
+SkStroke::SkStroke() {
+ fWidth = SK_Scalar1;
+ fMiterLimit = SkPaintDefaults_MiterLimit;
+ fCap = SkPaint::kDefault_Cap;
+ fJoin = SkPaint::kDefault_Join;
+ fDoFill = false;
+}
+
+SkStroke::SkStroke(const SkPaint& p) {
+ fWidth = p.getStrokeWidth();
+ fMiterLimit = p.getStrokeMiter();
+ fCap = (uint8_t)p.getStrokeCap();
+ fJoin = (uint8_t)p.getStrokeJoin();
+ fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
+}
+
+SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
+ fWidth = width;
+ fMiterLimit = p.getStrokeMiter();
+ fCap = (uint8_t)p.getStrokeCap();
+ fJoin = (uint8_t)p.getStrokeJoin();
+ fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
+}
+
+void SkStroke::setWidth(SkScalar width) {
+ SkASSERT(width >= 0);
+ fWidth = width;
+}
+
+void SkStroke::setMiterLimit(SkScalar miterLimit) {
+ SkASSERT(miterLimit >= 0);
+ fMiterLimit = miterLimit;
+}
+
+void SkStroke::setCap(SkPaint::Cap cap) {
+ SkASSERT((unsigned)cap < SkPaint::kCapCount);
+ fCap = SkToU8(cap);
+}
+
+void SkStroke::setJoin(SkPaint::Join join) {
+ SkASSERT((unsigned)join < SkPaint::kJoinCount);
+ fJoin = SkToU8(join);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// If src==dst, then we use a tmp path to record the stroke, and then swap
+// its contents with src when we're done.
+class AutoTmpPath {
+public:
+ AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
+ if (&src == *dst) {
+ *dst = &fTmpDst;
+ fSwapWithSrc = true;
+ } else {
+ (*dst)->reset();
+ fSwapWithSrc = false;
+ }
+ }
+
+ ~AutoTmpPath() {
+ if (fSwapWithSrc) {
+ fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
+ }
+ }
+
+private:
+ SkPath fTmpDst;
+ const SkPath& fSrc;
+ bool fSwapWithSrc;
+};
+
+void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
+ SkASSERT(&src != NULL && dst != NULL);
+
+ SkScalar radius = SkScalarHalf(fWidth);
+
+ AutoTmpPath tmp(src, &dst);
+
+ if (radius <= 0) {
+ 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;
+ }
+ }
+
+ SkAutoConicToQuads converter;
+ const SkScalar conicTol = SK_Scalar1 / 4;
+
+ SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
+ this->getJoin());
+ SkPath::Iter iter(src, false);
+ SkPath::Verb lastSegment = SkPath::kMove_Verb;
+
+ for (;;) {
+ SkPoint pts[4];
+ switch (iter.next(pts, false)) {
+ case SkPath::kMove_Verb:
+ stroker.moveTo(pts[0]);
+ break;
+ case SkPath::kLine_Verb:
+ stroker.lineTo(pts[1]);
+ lastSegment = SkPath::kLine_Verb;
+ break;
+ case SkPath::kQuad_Verb:
+ stroker.quadTo(pts[1], pts[2]);
+ lastSegment = SkPath::kQuad_Verb;
+ break;
+ case SkPath::kConic_Verb: {
+ // todo: if we had maxcurvature for conics, perhaps we should
+ // natively extrude the conic instead of converting to quads.
+ const SkPoint* quadPts =
+ converter.computeQuads(pts, iter.conicWeight(), conicTol);
+ for (int i = 0; i < converter.countQuads(); ++i) {
+ stroker.quadTo(quadPts[1], quadPts[2]);
+ quadPts += 2;
+ }
+ lastSegment = SkPath::kQuad_Verb;
+ } break;
+ case SkPath::kCubic_Verb:
+ stroker.cubicTo(pts[1], pts[2], pts[3]);
+ lastSegment = SkPath::kCubic_Verb;
+ break;
+ case SkPath::kClose_Verb:
+ stroker.close(lastSegment == SkPath::kLine_Verb);
+ break;
+ case SkPath::kDone_Verb:
+ goto DONE;
+ }
+ }
+DONE:
+ stroker.done(dst, lastSegment == SkPath::kLine_Verb);
+
+ if (fDoFill) {
+ if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
+ dst->reverseAddPath(src);
+ } else {
+ dst->addPath(src);
+ }
+ } else {
+ // Seems like we can assume that a 2-point src would always result in
+ // a convex stroke, but testing has proved otherwise.
+ // TODO: fix the stroker to make this assumption true (without making
+ // it slower that the work that will be done in computeConvexity())
+#if 0
+ // this test results in a non-convex stroke :(
+ static void test(SkCanvas* canvas) {
+ SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
+ SkPaint paint;
+ paint.setStrokeWidth(7);
+ paint.setStrokeCap(SkPaint::kRound_Cap);
+ canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
+ }
+#endif
+#if 0
+ if (2 == src.countPoints()) {
+ dst->setIsConvex(true);
+ }
+#endif
+ }
+
+ // our answer should preserve the inverseness of the src
+ if (src.isInverseFillType()) {
+ SkASSERT(!dst->isInverseFillType());
+ dst->toggleInverseFillType();
+ }
+}
+
+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/core/SkStroke.h b/core/SkStroke.h
new file mode 100644
index 00000000..a6a9f083
--- /dev/null
+++ b/core/SkStroke.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef SkStroke_DEFINED
+#define SkStroke_DEFINED
+
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkPaint.h"
+
+/** \class SkStroke
+ SkStroke is the utility class that constructs paths by stroking
+ geometries (lines, rects, ovals, roundrects, paths). This is
+ invoked when a geometry or text is drawn in a canvas with the
+ kStroke_Mask bit set in the paint.
+*/
+class SkStroke {
+public:
+ SkStroke();
+ SkStroke(const SkPaint&);
+ SkStroke(const SkPaint&, SkScalar width); // width overrides paint.getStrokeWidth()
+
+ SkPaint::Cap getCap() const { return (SkPaint::Cap)fCap; }
+ void setCap(SkPaint::Cap);
+
+ SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; }
+ void setJoin(SkPaint::Join);
+
+ void setMiterLimit(SkScalar);
+ void setWidth(SkScalar);
+
+ 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;
+
+ ////////////////////////////////////////////////////////////////
+
+private:
+ SkScalar fWidth, fMiterLimit;
+ uint8_t fCap, fJoin;
+ SkBool8 fDoFill;
+
+ friend class SkPaint;
+};
+
+#endif
diff --git a/core/SkStrokeRec.cpp b/core/SkStrokeRec.cpp
new file mode 100644
index 00000000..cdaf2413
--- /dev/null
+++ b/core/SkStrokeRec.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 "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/core/SkStrokerPriv.cpp b/core/SkStrokerPriv.cpp
new file mode 100644
index 00000000..269ebd3d
--- /dev/null
+++ b/core/SkStrokerPriv.cpp
@@ -0,0 +1,264 @@
+
+/*
+ * 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 "SkStrokerPriv.h"
+#include "SkGeometry.h"
+#include "SkPath.h"
+
+static void ButtCapper(SkPath* path, const SkPoint& pivot,
+ const SkVector& normal, const SkPoint& stop,
+ SkPath*)
+{
+ path->lineTo(stop.fX, stop.fY);
+}
+
+static void RoundCapper(SkPath* path, const SkPoint& pivot,
+ const SkVector& normal, const SkPoint& stop,
+ SkPath*)
+{
+ SkScalar px = pivot.fX;
+ SkScalar py = pivot.fY;
+ SkScalar nx = normal.fX;
+ SkScalar ny = normal.fY;
+ SkScalar sx = SkScalarMul(nx, CUBIC_ARC_FACTOR);
+ SkScalar sy = SkScalarMul(ny, CUBIC_ARC_FACTOR);
+
+ path->cubicTo(px + nx + CWX(sx, sy), py + ny + CWY(sx, sy),
+ px + CWX(nx, ny) + sx, py + CWY(nx, ny) + sy,
+ px + CWX(nx, ny), py + CWY(nx, ny));
+ path->cubicTo(px + CWX(nx, ny) - sx, py + CWY(nx, ny) - sy,
+ px - nx + CWX(sx, sy), py - ny + CWY(sx, sy),
+ stop.fX, stop.fY);
+}
+
+static void SquareCapper(SkPath* path, const SkPoint& pivot,
+ const SkVector& normal, const SkPoint& stop,
+ SkPath* otherPath)
+{
+ SkVector parallel;
+ normal.rotateCW(&parallel);
+
+ if (otherPath)
+ {
+ path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
+ path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
+ }
+ else
+ {
+ path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
+ path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
+ path->lineTo(stop.fX, stop.fY);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool is_clockwise(const SkVector& before, const SkVector& after)
+{
+ return SkScalarMul(before.fX, after.fY) - SkScalarMul(before.fY, after.fX) > 0;
+}
+
+enum AngleType {
+ kNearly180_AngleType,
+ kSharp_AngleType,
+ kShallow_AngleType,
+ kNearlyLine_AngleType
+};
+
+static AngleType Dot2AngleType(SkScalar dot)
+{
+// need more precise fixed normalization
+// SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
+
+ if (dot >= 0) // shallow or line
+ return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
+ else // sharp or 180
+ return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
+}
+
+static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after)
+{
+#if 1
+ /* In the degenerate case that the stroke radius is larger than our segments
+ just connecting the two inner segments may "show through" as a funny
+ diagonal. To pseudo-fix this, we go through the pivot point. This adds
+ an extra point/edge, but I can't see a cheap way to know when this is
+ not needed :(
+ */
+ inner->lineTo(pivot.fX, pivot.fY);
+#endif
+
+ inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY);
+}
+
+static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
+ const SkPoint& pivot, const SkVector& afterUnitNormal,
+ SkScalar radius, SkScalar invMiterLimit, bool, bool)
+{
+ SkVector after;
+ afterUnitNormal.scale(radius, &after);
+
+ if (!is_clockwise(beforeUnitNormal, afterUnitNormal))
+ {
+ SkTSwap<SkPath*>(outer, inner);
+ after.negate();
+ }
+
+ outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
+ HandleInnerJoin(inner, pivot, after);
+}
+
+static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
+ const SkPoint& pivot, const SkVector& afterUnitNormal,
+ SkScalar radius, SkScalar invMiterLimit, bool, bool)
+{
+ SkScalar dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
+ AngleType angleType = Dot2AngleType(dotProd);
+
+ if (angleType == kNearlyLine_AngleType)
+ return;
+
+ SkVector before = beforeUnitNormal;
+ SkVector after = afterUnitNormal;
+ SkRotationDirection dir = kCW_SkRotationDirection;
+
+ if (!is_clockwise(before, after))
+ {
+ SkTSwap<SkPath*>(outer, inner);
+ before.negate();
+ after.negate();
+ dir = kCCW_SkRotationDirection;
+ }
+
+ SkPoint pts[kSkBuildQuadArcStorage];
+ SkMatrix matrix;
+ matrix.setScale(radius, radius);
+ matrix.postTranslate(pivot.fX, pivot.fY);
+ int count = SkBuildQuadArc(before, after, dir, &matrix, pts);
+ SkASSERT((count & 1) == 1);
+
+ if (count > 1)
+ {
+ for (int i = 1; i < count; i += 2)
+ outer->quadTo(pts[i].fX, pts[i].fY, pts[i+1].fX, pts[i+1].fY);
+
+ after.scale(radius);
+ HandleInnerJoin(inner, pivot, after);
+ }
+}
+
+#ifdef SK_SCALAR_IS_FLOAT
+ #define kOneOverSqrt2 (0.707106781f)
+#else
+ #define kOneOverSqrt2 (46341)
+#endif
+
+static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
+ const SkPoint& pivot, const SkVector& afterUnitNormal,
+ SkScalar radius, SkScalar invMiterLimit,
+ bool prevIsLine, bool currIsLine)
+{
+ // negate the dot since we're using normals instead of tangents
+ SkScalar dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
+ AngleType angleType = Dot2AngleType(dotProd);
+ SkVector before = beforeUnitNormal;
+ SkVector after = afterUnitNormal;
+ SkVector mid;
+ SkScalar sinHalfAngle;
+ bool ccw;
+
+ if (angleType == kNearlyLine_AngleType)
+ return;
+ if (angleType == kNearly180_AngleType)
+ {
+ currIsLine = false;
+ goto DO_BLUNT;
+ }
+
+ ccw = !is_clockwise(before, after);
+ if (ccw)
+ {
+ SkTSwap<SkPath*>(outer, inner);
+ before.negate();
+ after.negate();
+ }
+
+ /* Before we enter the world of square-roots and divides,
+ check if we're trying to join an upright right angle
+ (common case for stroking rectangles). If so, special case
+ that (for speed an accuracy).
+ Note: we only need to check one normal if dot==0
+ */
+ if (0 == dotProd && invMiterLimit <= kOneOverSqrt2)
+ {
+ mid.set(SkScalarMul(before.fX + after.fX, radius),
+ SkScalarMul(before.fY + after.fY, radius));
+ goto DO_MITER;
+ }
+
+ /* midLength = radius / sinHalfAngle
+ if (midLength > miterLimit * radius) abort
+ if (radius / sinHalf > miterLimit * radius) abort
+ if (1 / sinHalf > miterLimit) abort
+ if (1 / miterLimit > sinHalf) abort
+ My dotProd is opposite sign, since it is built from normals and not tangents
+ hence 1 + dot instead of 1 - dot in the formula
+ */
+ sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd));
+ if (sinHalfAngle < invMiterLimit)
+ {
+ currIsLine = false;
+ goto DO_BLUNT;
+ }
+
+ // choose the most accurate way to form the initial mid-vector
+ if (angleType == kSharp_AngleType)
+ {
+ mid.set(after.fY - before.fY, before.fX - after.fX);
+ if (ccw)
+ mid.negate();
+ }
+ else
+ mid.set(before.fX + after.fX, before.fY + after.fY);
+
+ mid.setLength(SkScalarDiv(radius, sinHalfAngle));
+DO_MITER:
+ if (prevIsLine)
+ outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
+ else
+ outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
+
+DO_BLUNT:
+ after.scale(radius);
+ if (!currIsLine)
+ outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
+ HandleInnerJoin(inner, pivot, after);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap)
+{
+ static const SkStrokerPriv::CapProc gCappers[] = {
+ ButtCapper, RoundCapper, SquareCapper
+ };
+
+ SkASSERT((unsigned)cap < SkPaint::kCapCount);
+ return gCappers[cap];
+}
+
+SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join)
+{
+ static const SkStrokerPriv::JoinProc gJoiners[] = {
+ MiterJoiner, RoundJoiner, BluntJoiner
+ };
+
+ SkASSERT((unsigned)join < SkPaint::kJoinCount);
+ return gJoiners[join];
+}
diff --git a/core/SkStrokerPriv.h b/core/SkStrokerPriv.h
new file mode 100644
index 00000000..1c35f0a8
--- /dev/null
+++ b/core/SkStrokerPriv.h
@@ -0,0 +1,41 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkStrokerPriv_DEFINED
+#define SkStrokerPriv_DEFINED
+
+#include "SkStroke.h"
+
+#define CWX(x, y) (-y)
+#define CWY(x, y) (x)
+#define CCWX(x, y) (y)
+#define CCWY(x, y) (-x)
+
+#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
+
+class SkStrokerPriv {
+public:
+ typedef void (*CapProc)(SkPath* path,
+ const SkPoint& pivot,
+ const SkVector& normal,
+ const SkPoint& stop,
+ SkPath* otherPath);
+
+ typedef void (*JoinProc)(SkPath* outer, SkPath* inner,
+ const SkVector& beforeUnitNormal,
+ const SkPoint& pivot,
+ const SkVector& afterUnitNormal,
+ SkScalar radius, SkScalar invMiterLimit,
+ bool prevIsLine, bool currIsLine);
+
+ static CapProc CapFactory(SkPaint::Cap);
+ static JoinProc JoinFactory(SkPaint::Join);
+};
+
+#endif
diff --git a/core/SkTDynamicHash.h b/core/SkTDynamicHash.h
new file mode 100644
index 00000000..a4087019
--- /dev/null
+++ b/core/SkTDynamicHash.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTDynamicHash_DEFINED
+#define SkTDynamicHash_DEFINED
+
+#include "SkTypes.h"
+#include "SkMath.h"
+
+template <typename T,
+ typename Key,
+ const Key& (GetKey)(const T&),
+ uint32_t (Hash)(const Key&),
+ bool (Equal)(const T&, const Key&),
+ int kGrowPercent = 75, // Larger -> more memory efficient, but slower.
+ int kShrinkPercent = 25>
+class SkTDynamicHash {
+ static const int kMinCapacity = 4; // Smallest capacity we allow.
+public:
+ SkTDynamicHash(int initialCapacity=64/sizeof(T*)) {
+ this->reset(SkNextPow2(initialCapacity > kMinCapacity ? initialCapacity : kMinCapacity));
+ SkASSERT(this->validate());
+ }
+
+ ~SkTDynamicHash() {
+ sk_free(fArray);
+ }
+
+ int count() const { return fCount; }
+
+ // Return the entry with this key if we have it, otherwise NULL.
+ T* find(const Key& key) const {
+ int index = this->firstIndex(key);
+ for (int round = 0; round < fCapacity; round++) {
+ T* candidate = fArray[index];
+ if (Empty() == candidate) {
+ return NULL;
+ }
+ if (Deleted() != candidate && Equal(*candidate, key)) {
+ return candidate;
+ }
+ index = this->nextIndex(index, round);
+ }
+ SkASSERT(!"find: should be unreachable");
+ return NULL;
+ }
+
+ // Add an entry with this key. We require that no entry with newEntry's key is already present.
+ void add(T* newEntry) {
+ SkASSERT(NULL == this->find(GetKey(*newEntry)));
+ this->maybeGrow();
+ SkASSERT(this->validate());
+ this->innerAdd(newEntry);
+ SkASSERT(this->validate());
+ }
+
+ // Remove the entry with this key. We reqire that an entry with this key is present.
+ void remove(const Key& key) {
+ SkASSERT(NULL != this->find(key));
+ this->innerRemove(key);
+ SkASSERT(this->validate());
+ this->maybeShrink();
+ SkASSERT(this->validate());
+ }
+
+protected:
+ // These methods are used by tests only.
+
+ int capacity() const { return fCapacity; }
+
+ // How many collisions do we go through before finding where this entry should be inserted?
+ int countCollisions(const Key& key) const {
+ int index = this->firstIndex(key);
+ for (int round = 0; round < fCapacity; round++) {
+ const T* candidate = fArray[index];
+ if (Empty() == candidate || Deleted() == candidate || Equal(*candidate, key)) {
+ return round;
+ }
+ index = this->nextIndex(index, round);
+ }
+ SkASSERT(!"countCollisions: should be unreachable");
+ return -1;
+ }
+
+private:
+ // We have two special values to indicate an empty or deleted entry.
+ static T* Empty() { return reinterpret_cast<T*>(0); } // i.e. NULL
+ static T* Deleted() { return reinterpret_cast<T*>(1); } // Also an invalid pointer.
+
+ static T** AllocArray(int capacity) {
+ T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity);
+ sk_bzero(array, sizeof(T*) * capacity); // All cells == Empty().
+ return array;
+ }
+
+ void reset(int capacity) {
+ fCount = 0;
+ fDeleted = 0;
+ fCapacity = capacity;
+ fArray = AllocArray(fCapacity);
+ }
+
+ bool validate() const {
+ #define CHECK(x) SkASSERT((x)); if (!(x)) return false
+
+ // Is capacity sane?
+ CHECK(SkIsPow2(fCapacity));
+ CHECK(fCapacity >= kMinCapacity);
+
+ // Is fCount correct?
+ int count = 0;
+ for (int i = 0; i < fCapacity; i++) {
+ if (Empty() != fArray[i] && Deleted() != fArray[i]) {
+ count++;
+ }
+ }
+ CHECK(count == fCount);
+
+ // Is fDeleted correct?
+ int deleted = 0;
+ for (int i = 0; i < fCapacity; i++) {
+ if (Deleted() == fArray[i]) {
+ deleted++;
+ }
+ }
+ CHECK(deleted == fDeleted);
+
+ // Are all entries findable?
+ for (int i = 0; i < fCapacity; i++) {
+ if (Empty() == fArray[i] || Deleted() == fArray[i]) {
+ continue;
+ }
+ CHECK(NULL != this->find(GetKey(*fArray[i])));
+ }
+
+ // Are all entries unique?
+ for (int i = 0; i < fCapacity; i++) {
+ if (Empty() == fArray[i] || Deleted() == fArray[i]) {
+ continue;
+ }
+ for (int j = i+1; j < fCapacity; j++) {
+ if (Empty() == fArray[j] || Deleted() == fArray[j]) {
+ continue;
+ }
+ CHECK(fArray[i] != fArray[j]);
+ CHECK(!Equal(*fArray[i], GetKey(*fArray[j])));
+ CHECK(!Equal(*fArray[j], GetKey(*fArray[i])));
+ }
+ }
+ #undef CHECK
+ return true;
+ }
+
+ void innerAdd(T* newEntry) {
+ const Key& key = GetKey(*newEntry);
+ int index = this->firstIndex(key);
+ for (int round = 0; round < fCapacity; round++) {
+ const T* candidate = fArray[index];
+ if (Empty() == candidate || Deleted() == candidate) {
+ if (Deleted() == candidate) {
+ fDeleted--;
+ }
+ fCount++;
+ fArray[index] = newEntry;
+ return;
+ }
+ index = this->nextIndex(index, round);
+ }
+ SkASSERT(!"add: should be unreachable");
+ }
+
+ void innerRemove(const Key& key) {
+ const int firstIndex = this->firstIndex(key);
+ int index = firstIndex;
+ for (int round = 0; round < fCapacity; round++) {
+ const T* candidate = fArray[index];
+ if (Deleted() != candidate && Equal(*candidate, key)) {
+ fDeleted++;
+ fCount--;
+ fArray[index] = Deleted();
+ return;
+ }
+ index = this->nextIndex(index, round);
+ }
+ SkASSERT(!"innerRemove: should be unreachable");
+ }
+
+ void maybeGrow() {
+ if (fCount + fDeleted + 1 > (fCapacity * kGrowPercent) / 100) {
+ resize(fCapacity * 2);
+ }
+ }
+
+ void maybeShrink() {
+ if (fCount < (fCapacity * kShrinkPercent) / 100 && fCapacity / 2 > kMinCapacity) {
+ resize(fCapacity / 2);
+ }
+ }
+
+ void resize(int newCapacity) {
+ SkDEBUGCODE(int oldCount = fCount;)
+ int oldCapacity = fCapacity;
+ T** oldArray = fArray;
+
+ reset(newCapacity);
+
+ for (int i = 0; i < oldCapacity; i++) {
+ T* entry = oldArray[i];
+ if (Empty() != entry && Deleted() != entry) {
+ this->add(entry);
+ }
+ }
+ SkASSERT(oldCount == fCount);
+
+ sk_free(oldArray);
+ }
+
+ // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
+ uint32_t hashMask() const { return fCapacity - 1; }
+
+ int firstIndex(const Key& key) const {
+ return Hash(key) & this->hashMask();
+ }
+
+ // Given index at round N, what is the index to check at N+1? round should start at 0.
+ int nextIndex(int index, int round) const {
+ // This will search a power-of-two array fully without repeating an index.
+ return (index + round + 1) & this->hashMask();
+ }
+
+ int fCount; // Number of non Empty(), non Deleted() entries in fArray.
+ int fDeleted; // Number of Deleted() entries in fArray.
+ int fCapacity; // Number of entries in fArray. Always a power of 2.
+ T** fArray;
+};
+
+#endif
diff --git a/core/SkTLList.h b/core/SkTLList.h
new file mode 100644
index 00000000..298ce516
--- /dev/null
+++ b/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/core/SkTLS.cpp b/core/SkTLS.cpp
new file mode 100755
index 00000000..f7bf3048
--- /dev/null
+++ b/core/SkTLS.cpp
@@ -0,0 +1,123 @@
+#include "SkTLS.h"
+
+// enable to help debug TLS storage
+//#define SK_TRACE_TLS_LIFETIME
+
+
+#ifdef SK_TRACE_TLS_LIFETIME
+ #include "SkThread.h"
+ static int32_t gTLSRecCount;
+#endif
+
+struct SkTLSRec {
+ SkTLSRec* fNext;
+ void* fData;
+ SkTLS::CreateProc fCreateProc;
+ SkTLS::DeleteProc fDeleteProc;
+
+#ifdef SK_TRACE_TLS_LIFETIME
+ SkTLSRec() {
+ int n = sk_atomic_inc(&gTLSRecCount);
+ SkDebugf(" SkTLSRec[%d]\n", n);
+ }
+#endif
+
+ ~SkTLSRec() {
+ if (fDeleteProc) {
+ fDeleteProc(fData);
+ }
+ // else we leak fData, or it will be managed by the caller
+
+#ifdef SK_TRACE_TLS_LIFETIME
+ int n = sk_atomic_dec(&gTLSRecCount);
+ SkDebugf("~SkTLSRec[%d]\n", n - 1);
+#endif
+ }
+};
+
+void SkTLS::Destructor(void* ptr) {
+#ifdef SK_TRACE_TLS_LIFETIME
+ SkDebugf("SkTLS::Destructor(%p)\n", ptr);
+#endif
+
+ SkTLSRec* rec = (SkTLSRec*)ptr;
+ do {
+ SkTLSRec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ } while (NULL != rec);
+}
+
+void* SkTLS::Get(CreateProc createProc, DeleteProc deleteProc) {
+ if (NULL == createProc) {
+ return NULL;
+ }
+
+ void* ptr = SkTLS::PlatformGetSpecific(true);
+
+ if (ptr) {
+ const SkTLSRec* rec = (const SkTLSRec*)ptr;
+ do {
+ if (rec->fCreateProc == createProc) {
+ SkASSERT(rec->fDeleteProc == deleteProc);
+ return rec->fData;
+ }
+ } while ((rec = rec->fNext) != NULL);
+ // not found, so create a new one
+ }
+
+ // add a new head of our change
+ SkTLSRec* rec = new SkTLSRec;
+ rec->fNext = (SkTLSRec*)ptr;
+
+ SkTLS::PlatformSetSpecific(rec);
+
+ rec->fData = createProc();
+ rec->fCreateProc = createProc;
+ rec->fDeleteProc = deleteProc;
+ return rec->fData;
+}
+
+void* SkTLS::Find(CreateProc createProc) {
+ if (NULL == createProc) {
+ return NULL;
+ }
+
+ void* ptr = SkTLS::PlatformGetSpecific(false);
+
+ if (ptr) {
+ const SkTLSRec* rec = (const SkTLSRec*)ptr;
+ do {
+ if (rec->fCreateProc == createProc) {
+ return rec->fData;
+ }
+ } while ((rec = rec->fNext) != NULL);
+ }
+ return NULL;
+}
+
+void SkTLS::Delete(CreateProc createProc) {
+ if (NULL == createProc) {
+ return;
+ }
+
+ void* ptr = SkTLS::PlatformGetSpecific(false);
+
+ SkTLSRec* curr = (SkTLSRec*)ptr;
+ SkTLSRec* prev = NULL;
+ while (curr) {
+ SkTLSRec* next = curr->fNext;
+ if (curr->fCreateProc == createProc) {
+ if (prev) {
+ prev->fNext = next;
+ } else {
+ // we have a new head of our chain
+ SkTLS::PlatformSetSpecific(next);
+ }
+ SkDELETE(curr);
+ break;
+ }
+ prev = curr;
+ curr = next;
+ }
+}
diff --git a/core/SkTLS.h b/core/SkTLS.h
new file mode 100644
index 00000000..ad5daa7c
--- /dev/null
+++ b/core/SkTLS.h
@@ -0,0 +1,84 @@
+//
+// SkTLS.h
+//
+//
+// Created by Mike Reed on 4/21/12.
+// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#ifndef SkTLS_DEFINED
+#define SkTLS_DEFINED
+
+#include "SkTypes.h"
+
+/**
+ * Maintains a per-thread cache, using a CreateProc as the key into that cache.
+ */
+class SkTLS {
+public:
+ typedef void* (*CreateProc)();
+ typedef void (*DeleteProc)(void*);
+
+ /**
+ * If Get() has previously been called with this CreateProc, then this
+ * returns its cached data, otherwise it returns NULL. The CreateProc is
+ * never invoked in Find, it is only used as a key for searching the
+ * cache.
+ */
+ static void* Find(CreateProc);
+
+ /**
+ * Return the cached data that was returned by the CreateProc. This proc
+ * is only called the first time Get is called, and there after it is
+ * cached (per-thread), using the CreateProc as a key to look it up.
+ *
+ * When this thread, or Delete is called, the cached data is removed, and
+ * if a DeleteProc was specified, it is passed the pointer to the cached
+ * data.
+ */
+ static void* Get(CreateProc, DeleteProc);
+
+ /**
+ * Remove (optionally calling the DeleteProc if it was specificed in Get)
+ * the cached data associated with this CreateProc. If no associated cached
+ * data is found, do nothing.
+ */
+ static void Delete(CreateProc);
+
+private:
+ // Our implementation requires only 1 TLS slot, as we manage multiple values
+ // ourselves in a list, with the platform specific value as our head.
+
+ /**
+ * Implemented by the platform, to return the value of our (one) slot per-thread
+ *
+ * If forceCreateTheSlot is true, then we must have created the "slot" for
+ * our TLS, even though we know that the return value will be NULL in that
+ * case (i.e. no-slot and first-time-slot both return NULL). This ensures
+ * that after calling GetSpecific, we know that we can legally call
+ * SetSpecific.
+ *
+ * If forceCreateTheSlot is false, then the impl can either create the
+ * slot or not.
+ */
+ static void* PlatformGetSpecific(bool forceCreateTheSlot);
+
+ /**
+ * Implemented by the platform, to set the value for our (one) slot per-thread
+ *
+ * The implementation can rely on GetSpecific(true) having been previously
+ * called before SetSpecific is called.
+ */
+ static void PlatformSetSpecific(void*);
+
+public:
+ /**
+ * Will delete our internal list. To be called by the platform if/when its
+ * TLS slot is deleted (often at thread shutdown).
+ *
+ * Public *only* for the platform's use, not to be called by a client.
+ */
+ static void Destructor(void* ptr);
+};
+
+#endif
diff --git a/core/SkTRefArray.h b/core/SkTRefArray.h
new file mode 100755
index 00000000..c4bca241
--- /dev/null
+++ b/core/SkTRefArray.h
@@ -0,0 +1,112 @@
+//
+// SkTRefArray.h
+// core
+//
+// Created by Mike Reed on 7/17/12.
+// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#ifndef SkTRefArray_DEFINED
+#define SkTRefArray_DEFINED
+
+#include "SkRefCnt.h"
+#include <new>
+
+/**
+ * Wrapper to manage thread-safe sharing of an array of T objects. The array
+ * cannot be grown or shrunk.
+ */
+template <typename T> class SkTRefArray : public SkRefCnt {
+ /*
+ * Shared factory to allocate the space needed for our instance plus N
+ * T entries at the end. We call our constructor, but not the constructors
+ * for the elements. Those are called by the proper Create method.
+ */
+ static SkTRefArray<T>* Alloc(int count) {
+ // space for us, and our [count] elements
+ size_t size = sizeof(SkTRefArray<T>) + count * sizeof(T);
+ SkTRefArray<T>* obj = (SkTRefArray<T>*)sk_malloc_throw(size);
+
+ SkNEW_PLACEMENT(obj, SkTRefArray<T>);
+ obj->fCount = count;
+ return obj;
+ }
+
+public:
+ /**
+ * Return a new array with 'count' elements, initialized to their default
+ * value. To change them to some other value, use writableBegin/End or
+ * writableAt(), but do that before this array is given to another thread.
+ */
+ static SkTRefArray<T>* Create(int count) {
+ SkTRefArray<T>* obj = Alloc(count);
+ T* array = const_cast<T*>(obj->begin());
+ for (int i = 0; i < count; ++i) {
+ SkNEW_PLACEMENT(&array[i], T);
+ }
+ return obj;
+ }
+
+ /**
+ * Return a new array with 'count' elements, initialized from the provided
+ * src array. To change them to some other value, use writableBegin/End or
+ * writableAt(), but do that before this array is given to another thread.
+ */
+ static SkTRefArray<T>* Create(const T src[], int count) {
+ SkTRefArray<T>* obj = Alloc(count);
+ T* array = const_cast<T*>(obj->begin());
+ for (int i = 0; i < count; ++i) {
+ SkNEW_PLACEMENT_ARGS(&array[i], T, (src[i]));
+ }
+ return obj;
+ }
+
+ int count() const { return fCount; }
+ const T* begin() const { return (const T*)(this + 1); }
+ const T* end() const { return this->begin() + fCount; }
+ const T& at(int index) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ return this->begin()[index];
+ }
+ const T& operator[](int index) const { return this->at(index); }
+
+ // For the writable methods, we assert that we are the only owner if we
+ // call these, since other owners are not informed if we change an element.
+
+ T* writableBegin() {
+ SkASSERT(this->unique());
+ return (T*)(this + 1);
+ }
+ T* writableEnd() {
+ return this->writableBegin() + fCount;
+ }
+ T& writableAt(int index) {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ return this->writableBegin()[index];
+ }
+
+protected:
+ virtual void internal_dispose() const SK_OVERRIDE {
+ T* array = const_cast<T*>(this->begin());
+ int n = fCount;
+
+ for (int i = 0; i < n; ++i) {
+ array->~T();
+ array += 1;
+ }
+
+ this->internal_dispose_restore_refcnt_to_1();
+ this->~SkTRefArray<T>();
+ sk_free((void*)this);
+ }
+
+private:
+ int fCount;
+
+ // hide this
+ virtual ~SkTRefArray() {}
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/core/SkTSearch.cpp b/core/SkTSearch.cpp
new file mode 100644
index 00000000..64c70cb3
--- /dev/null
+++ b/core/SkTSearch.cpp
@@ -0,0 +1,114 @@
+
+/*
+ * 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 "SkTSearch.h"
+#include <ctype.h>
+
+static inline const char* index_into_base(const char*const* base, int index,
+ size_t elemSize)
+{
+ return *(const char*const*)((const char*)base + index * elemSize);
+}
+
+int SkStrSearch(const char*const* base, int count, const char target[],
+ size_t target_len, size_t elemSize)
+{
+ if (count <= 0)
+ return ~0;
+
+ SkASSERT(base != NULL);
+
+ int lo = 0;
+ int hi = count - 1;
+
+ while (lo < hi)
+ {
+ int mid = (hi + lo) >> 1;
+ const char* elem = index_into_base(base, mid, elemSize);
+
+ int cmp = strncmp(elem, target, target_len);
+ if (cmp < 0)
+ lo = mid + 1;
+ else if (cmp > 0 || strlen(elem) > target_len)
+ hi = mid;
+ else
+ return mid;
+ }
+
+ const char* elem = index_into_base(base, hi, elemSize);
+ int cmp = strncmp(elem, target, target_len);
+ if (cmp || strlen(elem) > target_len)
+ {
+ if (cmp < 0)
+ hi += 1;
+ hi = ~hi;
+ }
+ return hi;
+}
+
+int SkStrSearch(const char*const* base, int count, const char target[],
+ size_t elemSize)
+{
+ return SkStrSearch(base, count, target, strlen(target), elemSize);
+}
+
+int SkStrLCSearch(const char*const* base, int count, const char target[],
+ size_t len, size_t elemSize)
+{
+ SkASSERT(target);
+
+ SkAutoAsciiToLC tolc(target, len);
+
+ return SkStrSearch(base, count, tolc.lc(), len, elemSize);
+}
+
+int SkStrLCSearch(const char*const* base, int count, const char target[],
+ size_t elemSize)
+{
+ return SkStrLCSearch(base, count, target, strlen(target), elemSize);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+SkAutoAsciiToLC::SkAutoAsciiToLC(const char str[], size_t len)
+{
+ // see if we need to compute the length
+ if ((long)len < 0) {
+ len = strlen(str);
+ }
+ fLength = len;
+
+ // assign lc to our preallocated storage if len is small enough, or allocate
+ // it on the heap
+ char* lc;
+ if (len <= STORAGE) {
+ lc = fStorage;
+ } else {
+ lc = (char*)sk_malloc_throw(len + 1);
+ }
+ fLC = lc;
+
+ // convert any asii to lower-case. we let non-ascii (utf8) chars pass
+ // through unchanged
+ for (int i = (int)(len - 1); i >= 0; --i) {
+ int c = str[i];
+ if ((c & 0x80) == 0) { // is just ascii
+ c = tolower(c);
+ }
+ lc[i] = c;
+ }
+ lc[len] = 0;
+}
+
+SkAutoAsciiToLC::~SkAutoAsciiToLC()
+{
+ if (fLC != fStorage) {
+ sk_free(fLC);
+ }
+}
diff --git a/core/SkTSort.h b/core/SkTSort.h
new file mode 100644
index 00000000..027ea52a
--- /dev/null
+++ b/core/SkTSort.h
@@ -0,0 +1,210 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTSort_DEFINED
+#define SkTSort_DEFINED
+
+#include "SkTypes.h"
+#include "SkMath.h"
+
+/* A comparison functor which performs the comparison 'a < b'. */
+template <typename T> struct SkTCompareLT {
+ bool operator()(const T a, const T b) const { return a < b; }
+};
+
+/* A comparison functor which performs the comparison '*a < *b'. */
+template <typename T> struct SkTPointerCompareLT {
+ bool operator()(const T* a, const T* b) const { return *a < *b; }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* Sifts a broken heap. The input array is a heap from root to bottom
+ * except that the root entry may be out of place.
+ *
+ * Sinks a hole from array[root] to leaf and then sifts the original array[root] element
+ * from the leaf level up.
+ *
+ * This version does extra work, in that it copies child to parent on the way down,
+ * then copies parent to child on the way back up. When copies are inexpensive,
+ * this is an optimization as this sift variant should only be used when
+ * the potentially out of place root entry value is expected to be small.
+ *
+ * @param root the one based index into array of the out-of-place root of the heap.
+ * @param bottom the one based index in the array of the last entry in the heap.
+ */
+template <typename T, typename C>
+void SkTHeapSort_SiftUp(T array[], size_t root, size_t bottom, C lessThan) {
+ T x = array[root-1];
+ size_t start = root;
+ size_t j = root << 1;
+ while (j <= bottom) {
+ if (j < bottom && lessThan(array[j-1], array[j])) {
+ ++j;
+ }
+ array[root-1] = array[j-1];
+ root = j;
+ j = root << 1;
+ }
+ j = root >> 1;
+ while (j >= start) {
+ if (lessThan(array[j-1], x)) {
+ array[root-1] = array[j-1];
+ root = j;
+ j = root >> 1;
+ } else {
+ break;
+ }
+ }
+ array[root-1] = x;
+}
+
+/* Sifts a broken heap. The input array is a heap from root to bottom
+ * except that the root entry may be out of place.
+ *
+ * Sifts the array[root] element from the root down.
+ *
+ * @param root the one based index into array of the out-of-place root of the heap.
+ * @param bottom the one based index in the array of the last entry in the heap.
+ */
+template <typename T, typename C>
+void SkTHeapSort_SiftDown(T array[], size_t root, size_t bottom, C lessThan) {
+ T x = array[root-1];
+ size_t child = root << 1;
+ while (child <= bottom) {
+ if (child < bottom && lessThan(array[child-1], array[child])) {
+ ++child;
+ }
+ if (lessThan(x, array[child-1])) {
+ array[root-1] = array[child-1];
+ root = child;
+ child = root << 1;
+ } else {
+ break;
+ }
+ }
+ array[root-1] = x;
+}
+
+/** Sorts the array of size count using comparator lessThan using a Heap Sort algorithm. Be sure to
+ * specialize SkTSwap if T has an efficient swap operation.
+ *
+ * @param array the array to be sorted.
+ * @param count the number of elements in the array.
+ * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b.
+ */
+template <typename T, typename C> void SkTHeapSort(T array[], size_t count, C lessThan) {
+ for (size_t i = count >> 1; i > 0; --i) {
+ SkTHeapSort_SiftDown(array, i, count, lessThan);
+ }
+
+ for (size_t i = count - 1; i > 0; --i) {
+ SkTSwap<T>(array[0], array[i]);
+ SkTHeapSort_SiftUp(array, 1, i, lessThan);
+ }
+}
+
+/** Sorts the array of size count using comparator '<' using a Heap Sort algorithm. */
+template <typename T> void SkTHeapSort(T array[], size_t count) {
+ SkTHeapSort(array, count, SkTCompareLT<T>());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** Sorts the array of size count using comparator lessThan using an Insertion Sort algorithm. */
+template <typename T, typename C> static void SkTInsertionSort(T* left, T* right, C lessThan) {
+ for (T* next = left + 1; next <= right; ++next) {
+ T insert = *next;
+ T* hole = next;
+ while (left < hole && lessThan(insert, *(hole - 1))) {
+ *hole = *(hole - 1);
+ --hole;
+ }
+ *hole = insert;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename C>
+static T* SkTQSort_Partition(T* left, T* right, T* pivot, C lessThan) {
+ T pivotValue = *pivot;
+ SkTSwap(*pivot, *right);
+ T* newPivot = left;
+ while (left < right) {
+ if (lessThan(*left, pivotValue)) {
+ SkTSwap(*left, *newPivot);
+ newPivot += 1;
+ }
+ left += 1;
+ }
+ SkTSwap(*newPivot, *right);
+ return newPivot;
+}
+
+/* Intro Sort is a modified Quick Sort.
+ * When the region to be sorted is a small constant size it uses Insertion Sort.
+ * When depth becomes zero, it switches over to Heap Sort.
+ * This implementation recurses on the left region after pivoting and loops on the right,
+ * we already limit the stack depth by switching to heap sort,
+ * and cache locality on the data appears more important than saving a few stack frames.
+ *
+ * @param depth at this recursion depth, switch to Heap Sort.
+ * @param left the beginning of the region to be sorted.
+ * @param right the end of the region to be sorted (inclusive).
+ * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b.
+ */
+template <typename T, typename C> void SkTIntroSort(int depth, T* left, T* right, C lessThan) {
+ while (true) {
+ if (right - left < 32) {
+ SkTInsertionSort(left, right, lessThan);
+ return;
+ }
+
+ if (depth == 0) {
+ SkTHeapSort<T>(left, right - left + 1, lessThan);
+ return;
+ }
+ --depth;
+
+ T* pivot = left + ((right - left) >> 1);
+ pivot = SkTQSort_Partition(left, right, pivot, lessThan);
+
+ SkTIntroSort(depth, left, pivot - 1, lessThan);
+ left = pivot + 1;
+ }
+}
+
+/** Sorts the region from left to right using comparator lessThan using a Quick Sort algorithm. Be
+ * sure to specialize SkTSwap if T has an efficient swap operation.
+ *
+ * @param left the beginning of the region to be sorted.
+ * @param right the end of the region to be sorted (inclusive).
+ * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b.
+ */
+template <typename T, typename C> void SkTQSort(T* left, T* right, C lessThan) {
+ if (left >= right) {
+ return;
+ }
+ // Limit Intro Sort recursion depth to no more than 2 * ceil(log2(n)).
+ int depth = 2 * SkNextLog2(SkToU32(right - left));
+ SkTIntroSort(depth, left, right, lessThan);
+}
+
+/** Sorts the region from left to right using comparator '<' using a Quick Sort algorithm. */
+template <typename T> void SkTQSort(T* left, T* right) {
+ SkTQSort(left, right, SkTCompareLT<T>());
+}
+
+/** Sorts the region from left to right using comparator '* < *' using a Quick Sort algorithm. */
+template <typename T> void SkTQSort(T** left, T** right) {
+ SkTQSort(left, right, SkTPointerCompareLT<T>());
+}
+
+#endif
diff --git a/core/SkTemplatesPriv.h b/core/SkTemplatesPriv.h
new file mode 100644
index 00000000..79ae6093
--- /dev/null
+++ b/core/SkTemplatesPriv.h
@@ -0,0 +1,76 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTemplatesPriv_DEFINED
+#define SkTemplatesPriv_DEFINED
+
+#include "SkTemplates.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_BUILD_FOR_WIN32
+ #define SK_PLACEMENT_NEW(result, classname, storage, storageSize) \
+ result = SkNEW(classname)
+
+ #define SK_PLACEMENT_NEW_ARGS(result, classname, storage, storageSize, args) \
+ result = SkNEW_ARGS(classname, args)
+#else
+ #include <new>
+ #define SK_PLACEMENT_NEW(result, classname, storage, storagesize) \
+ do { \
+ if (storagesize) \
+ { \
+ SkASSERT(storageSize >= sizeof(classname)); \
+ result = new(storage) classname; \
+ } \
+ else \
+ result = SkNEW(classname); \
+ } while (0)
+
+ #define SK_PLACEMENT_NEW_ARGS(result, classname, storage, storagesize, args) \
+ do { \
+ if (storagesize) \
+ { \
+ SkASSERT(storageSize >= sizeof(classname)); \
+ result = new(storage) classname args; \
+ } \
+ else \
+ result = SkNEW_ARGS(classname, args); \
+ } while (0)
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T> class SkAutoTPlacementDelete {
+public:
+ SkAutoTPlacementDelete(T* obj, void* storage) : fObj(obj), fStorage(storage)
+ {
+ }
+ ~SkAutoTPlacementDelete()
+ {
+ if (fObj)
+ {
+ if (fObj == fStorage)
+ fObj->~T();
+ else
+ delete fObj;
+ }
+ }
+ T* detach()
+ {
+ T* obj = fObj;
+ fObj = NULL;
+ return obj;
+ }
+private:
+ T* fObj;
+ void* fStorage;
+};
+
+#endif
diff --git a/core/SkTextFormatParams.h b/core/SkTextFormatParams.h
new file mode 100644
index 00000000..dac4aefd
--- /dev/null
+++ b/core/SkTextFormatParams.h
@@ -0,0 +1,41 @@
+
+/*
+ * Copyright 2010 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 SkTextFormatParams_DEFINES
+#define SkTextFormatParams_DEFINES
+
+#include "SkScalar.h"
+#include "SkTypes.h"
+
+// Fraction of the text size to lower a strike through line below the baseline.
+#define kStdStrikeThru_Offset (-SK_Scalar1 * 6 / 21)
+// Fraction of the text size to lower a underline below the baseline.
+#define kStdUnderline_Offset (SK_Scalar1 / 9)
+// Fraction of the text size to use for a strike through or under-line.
+#define kStdUnderline_Thickness (SK_Scalar1 / 18)
+
+// The fraction of text size to embolden fake bold text scales with text size.
+// At 9 points or below, the stroke width is increased by text size / 24.
+// At 36 points and above, it is increased by text size / 32. In between,
+// it is interpolated between those values.
+static const SkScalar kStdFakeBoldInterpKeys[] = {
+ SK_Scalar1*9,
+ SK_Scalar1*36,
+};
+static const SkScalar kStdFakeBoldInterpValues[] = {
+ SK_Scalar1/24,
+ SK_Scalar1/32
+};
+SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kStdFakeBoldInterpKeys) ==
+ SK_ARRAY_COUNT(kStdFakeBoldInterpValues),
+ mismatched_array_size);
+static const int kStdFakeBoldInterpLength =
+ SK_ARRAY_COUNT(kStdFakeBoldInterpKeys);
+
+#endif //SkTextFormatParams_DEFINES
diff --git a/core/SkTextToPathIter.h b/core/SkTextToPathIter.h
new file mode 100644
index 00000000..e98d6d93
--- /dev/null
+++ b/core/SkTextToPathIter.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 SkTextToPathIter_DEFINED
+#define SkTextToPathIter_DEFINED
+
+#include "SkAutoKern.h"
+#include "SkPaint.h"
+
+class SkGlyphCache;
+
+class SkTextToPathIter {
+public:
+ SkTextToPathIter(const char text[], size_t length, const SkPaint& paint,
+ bool applyStrokeAndPathEffects);
+ ~SkTextToPathIter();
+
+ const SkPaint& getPaint() const { return fPaint; }
+ SkScalar getPathScale() const { return fScale; }
+
+ struct Rec {
+ const SkPath* fPath; // may be null for "whitespace" glyphs
+ SkScalar fXPos;
+ };
+
+ /**
+ * Returns false when all of the text has been consumed
+ */
+ bool next(const SkPath** path, SkScalar* xpos);
+
+private:
+ SkGlyphCache* fCache;
+ SkPaint fPaint;
+ SkScalar fScale;
+ SkFixed fPrevAdvance;
+ const char* fText;
+ const char* fStop;
+ SkMeasureCacheProc fGlyphCacheProc;
+
+ SkScalar fXPos; // accumulated xpos, returned in next
+ SkAutoKern fAutoKern;
+ int fXYIndex; // cache for horizontal -vs- vertical text
+};
+
+#endif
diff --git a/core/SkTileGrid.cpp b/core/SkTileGrid.cpp
new file mode 100644
index 00000000..c39e21b3
--- /dev/null
+++ b/core/SkTileGrid.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 "SkTileGrid.h"
+
+SkTileGrid::SkTileGrid(int xTileCount, int yTileCount, const SkTileGridPicture::TileGridInfo& info,
+ SkTileGridNextDatumFunctionPtr nextDatumFunction)
+{
+ fXTileCount = xTileCount;
+ fYTileCount = yTileCount;
+ fInfo = info;
+ // Margin is offset by 1 as a provision for AA and
+ // to cancel-out the outset applied by getClipDeviceBounds.
+ fInfo.fMargin.fHeight++;
+ fInfo.fMargin.fWidth++;
+ fTileCount = fXTileCount * fYTileCount;
+ fInsertionCount = 0;
+ fGridBounds = SkIRect::MakeXYWH(0, 0, fInfo.fTileInterval.width() * fXTileCount,
+ fInfo.fTileInterval.height() * 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(fInfo.fMargin.width(), fInfo.fMargin.height());
+ dilatedBounds.offset(fInfo.fOffset);
+ if (!SkIRect::Intersects(dilatedBounds, fGridBounds)) {
+ return;
+ }
+
+ // Note: SkIRects are non-inclusive of the right() column and bottom() row,
+ // hence the "-1"s in the computations of maxTileX and maxTileY.
+ int minTileX = SkMax32(SkMin32(dilatedBounds.left() / fInfo.fTileInterval.width(),
+ fXTileCount - 1), 0);
+ int maxTileX = SkMax32(SkMin32((dilatedBounds.right() - 1) / fInfo.fTileInterval.width(),
+ fXTileCount - 1), 0);
+ int minTileY = SkMax32(SkMin32(dilatedBounds.top() / fInfo.fTileInterval.height(),
+ fYTileCount -1), 0);
+ int maxTileY = SkMax32(SkMin32((dilatedBounds.bottom() -1) / fInfo.fTileInterval.height(),
+ 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) {
+ SkIRect adjustedQuery = query;
+ // The inset is to counteract the outset that was applied in 'insert'
+ // The outset/inset is to optimize for lookups of size
+ // 'tileInterval + 2 * margin' that are aligned with the tile grid.
+ adjustedQuery.inset(fInfo.fMargin.width(), fInfo.fMargin.height());
+ adjustedQuery.offset(fInfo.fOffset);
+ adjustedQuery.sort(); // in case the inset inverted the rectangle
+ // Convert the query rectangle from device coordinates to tile coordinates
+ // by rounding outwards to the nearest tile boundary so that the resulting tile
+ // region includes the query rectangle. (using truncating division to "floor")
+ int tileStartX = adjustedQuery.left() / fInfo.fTileInterval.width();
+ int tileEndX = (adjustedQuery.right() + fInfo.fTileInterval.width() - 1) /
+ fInfo.fTileInterval.width();
+ int tileStartY = adjustedQuery.top() / fInfo.fTileInterval.height();
+ int tileEndY = (adjustedQuery.bottom() + fInfo.fTileInterval.height() - 1) /
+ fInfo.fTileInterval.height();
+
+ tileStartX = SkPin32(tileStartX, 0, fXTileCount - 1);
+ tileEndX = SkPin32(tileEndX, tileStartX+1, fXTileCount);
+ tileStartY = SkPin32(tileStartY, 0, fYTileCount - 1);
+ tileEndY = SkPin32(tileEndY, tileStartY+1, fYTileCount);
+
+ int queryTileCount = (tileEndX - tileStartX) * (tileEndY - tileStartY);
+ SkASSERT(queryTileCount);
+ 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;
+}
+
+void SkTileGrid::rewindInserts() {
+ SkASSERT(fClient);
+ for (int i = 0; i < fTileCount; ++i) {
+ while (!fTileData[i].isEmpty() && fClient->shouldRewind(fTileData[i].top())) {
+ fTileData[i].pop();
+ }
+ }
+}
diff --git a/core/SkTileGrid.h b/core/SkTileGrid.h
new file mode 100644
index 00000000..e272673c
--- /dev/null
+++ b/core/SkTileGrid.h
@@ -0,0 +1,130 @@
+
+/*
+ * 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"
+#include "SkTileGridPicture.h" // for TileGridInfo
+
+/**
+ * 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 xTileCount, int yTileCount, const SkTileGridPicture::TileGridInfo& info,
+ 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;
+
+ virtual void rewindInserts() SK_OVERRIDE;
+
+ // Used by search() and in SkTileGridHelper implementations
+ enum {
+ kTileFinished = -1,
+ };
+private:
+ SkTDArray<void*>& tile(int x, int y);
+
+ int fXTileCount, fYTileCount, fTileCount;
+ SkTileGridPicture::TileGridInfo fInfo;
+ 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) {
+ T* minVal = NULL;
+ int tileCount = tileIndices.count();
+ int minIndex = tileCount;
+ int maxIndex = 0;
+ // Find the next Datum; track where it's found so we reduce the size of the second loop.
+ for (int tile = 0; tile < tileCount; ++tile) {
+ int pos = tileIndices[tile];
+ if (pos != SkTileGrid::kTileFinished) {
+ T* candidate = (T*)(*tileData[tile])[pos];
+ if (NULL == minVal || (*candidate) < (*minVal)) {
+ minVal = candidate;
+ minIndex = tile;
+ maxIndex = tile;
+ } else if (!((*minVal) < (*candidate))) {
+ // We don't require operator==; if !(candidate<minVal) && !(minVal<candidate),
+ // candidate==minVal and we have to add this tile to the range searched.
+ maxIndex = tile;
+ }
+ }
+ }
+ // Increment indices past the next datum
+ if (minVal != NULL) {
+ for (int tile = minIndex; tile <= maxIndex; ++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/core/SkTileGridPicture.cpp b/core/SkTileGridPicture.cpp
new file mode 100644
index 00000000..7a8d5932
--- /dev/null
+++ b/core/SkTileGridPicture.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 width, int height, const TileGridInfo& info) {
+ SkASSERT(info.fMargin.width() >= 0);
+ SkASSERT(info.fMargin.height() >= 0);
+ fInfo = info;
+ // Note: SkIRects are non-inclusive of the right() column and bottom() row.
+ // For example, an SkIRect at 0,0 with a size of (1,1) will only have
+ // content at pixel (0,0) and will report left=0 and right=1, hence the
+ // "-1"s below.
+ fXTileCount = (width + info.fTileInterval.width() - 1) / info.fTileInterval.width();
+ fYTileCount = (height + info.fTileInterval.height() - 1) / info.fTileInterval.height();
+}
+
+SkBBoxHierarchy* SkTileGridPicture::createBBoxHierarchy() const {
+ return SkNEW_ARGS(SkTileGrid, (fXTileCount, fYTileCount, fInfo,
+ SkTileGridNextDatum<SkPictureStateTree::Draw>));
+}
diff --git a/core/SkTypeface.cpp b/core/SkTypeface.cpp
new file mode 100644
index 00000000..dbe0f603
--- /dev/null
+++ b/core/SkTypeface.cpp
@@ -0,0 +1,229 @@
+/*
+ * 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 "SkAdvancedTypefaceMetrics.h"
+#include "SkFontDescriptor.h"
+#include "SkFontHost.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+
+SK_DEFINE_INST_COUNT(SkTypeface)
+
+//#define TRACE_LIFECYCLE
+
+#ifdef TRACE_LIFECYCLE
+ static int32_t gTypefaceCounter;
+#endif
+
+SkTypeface::SkTypeface(Style style, SkFontID fontID, bool isFixedPitch)
+ : fUniqueID(fontID), fStyle(style), fIsFixedPitch(isFixedPitch) {
+#ifdef TRACE_LIFECYCLE
+ SkDebugf("SkTypeface: create %p fontID %d total %d\n",
+ this, fontID, ++gTypefaceCounter);
+#endif
+}
+
+SkTypeface::~SkTypeface() {
+#ifdef TRACE_LIFECYCLE
+ SkDebugf("SkTypeface: destroy %p fontID %d total %d\n",
+ this, fUniqueID, --gTypefaceCounter);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTypeface* SkTypeface::GetDefaultTypeface(Style style) {
+ // we keep a reference to this guy for all time, since if we return its
+ // fontID, the font cache may later on ask to resolve that back into a
+ // typeface object.
+ static const uint32_t FONT_STYLE_COUNT = 4;
+ static SkTypeface* gDefaultTypefaces[FONT_STYLE_COUNT];
+ SkASSERT((unsigned)style < FONT_STYLE_COUNT);
+
+ // mask off any other bits to avoid a crash in SK_RELEASE
+ style = (Style)(style & 0x03);
+
+ if (NULL == gDefaultTypefaces[style]) {
+ gDefaultTypefaces[style] =
+ SkFontHost::CreateTypeface(NULL, NULL, style);
+ }
+ return gDefaultTypefaces[style];
+}
+
+SkTypeface* SkTypeface::RefDefault(Style style) {
+ return SkRef(GetDefaultTypeface(style));
+}
+
+uint32_t SkTypeface::UniqueID(const SkTypeface* face) {
+ if (NULL == face) {
+ face = GetDefaultTypeface();
+ }
+ return face->uniqueID();
+}
+
+bool SkTypeface::Equal(const SkTypeface* facea, const SkTypeface* faceb) {
+ return SkTypeface::UniqueID(facea) == SkTypeface::UniqueID(faceb);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTypeface* SkTypeface::CreateFromName(const char name[], Style style) {
+ if (NULL == name) {
+ return RefDefault(style);
+ }
+ return SkFontHost::CreateTypeface(NULL, name, style);
+}
+
+SkTypeface* SkTypeface::CreateFromTypeface(const SkTypeface* family, Style s) {
+ if (family && family->style() == s) {
+ family->ref();
+ return const_cast<SkTypeface*>(family);
+ }
+ return SkFontHost::CreateTypeface(family, NULL, s);
+}
+
+SkTypeface* SkTypeface::CreateFromStream(SkStream* stream) {
+ return SkFontHost::CreateTypefaceFromStream(stream);
+}
+
+SkTypeface* SkTypeface::CreateFromFile(const char path[]) {
+ return SkFontHost::CreateTypefaceFromFile(path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkTypeface::serialize(SkWStream* wstream) const {
+ bool isLocal = false;
+ SkFontDescriptor desc(this->style());
+ this->onGetFontDescriptor(&desc, &isLocal);
+
+ desc.serialize(wstream);
+ if (isLocal) {
+ int ttcIndex; // TODO: write this to the stream?
+ SkAutoTUnref<SkStream> rstream(this->openStream(&ttcIndex));
+ if (rstream.get()) {
+ size_t length = rstream->getLength();
+ wstream->writePackedUInt(length);
+ wstream->writeStream(rstream, length);
+ } else {
+ wstream->writePackedUInt(0);
+ }
+ } else {
+ wstream->writePackedUInt(0);
+ }
+}
+
+SkTypeface* SkTypeface::Deserialize(SkStream* stream) {
+ SkFontDescriptor desc(stream);
+ size_t length = stream->readPackedUInt();
+ if (length > 0) {
+ void* addr = sk_malloc_flags(length, 0);
+ if (addr) {
+ SkAutoTUnref<SkMemoryStream> localStream(SkNEW(SkMemoryStream));
+ localStream->setMemoryOwned(addr, length);
+
+ if (stream->read(addr, length) == length) {
+ return SkTypeface::CreateFromStream(localStream.get());
+ } else {
+ // Failed to read the full font data, so fall through and try to create from name.
+ // If this is because of EOF, all subsequent reads from the stream will be EOF.
+ // If this is because of a stream error, the stream is in an error state,
+ // do not attempt to skip any remaining bytes.
+ }
+ } else {
+ // failed to allocate, so just skip and create-from-name
+ stream->skip(length);
+ }
+ }
+
+ return SkTypeface::CreateFromName(desc.getFamilyName(), desc.getStyle());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkTypeface::countTables() const {
+ return this->onGetTableTags(NULL);
+}
+
+int SkTypeface::getTableTags(SkFontTableTag tags[]) const {
+ return this->onGetTableTags(tags);
+}
+
+size_t SkTypeface::getTableSize(SkFontTableTag tag) const {
+ return this->onGetTableData(tag, 0, ~0U, NULL);
+}
+
+size_t SkTypeface::getTableData(SkFontTableTag tag, size_t offset, size_t length,
+ void* data) const {
+ return this->onGetTableData(tag, offset, length, data);
+}
+
+SkStream* SkTypeface::openStream(int* ttcIndex) const {
+ int ttcIndexStorage;
+ if (NULL == ttcIndex) {
+ // So our subclasses don't need to check for null param
+ ttcIndex = &ttcIndexStorage;
+ }
+ return this->onOpenStream(ttcIndex);
+}
+
+int SkTypeface::charsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const {
+ if (glyphCount <= 0) {
+ return 0;
+ }
+ if (NULL == chars || (unsigned)encoding > kUTF32_Encoding) {
+ if (glyphs) {
+ sk_bzero(glyphs, glyphCount * sizeof(glyphs[0]));
+ }
+ return 0;
+ }
+ return this->onCharsToGlyphs(chars, encoding, glyphs, glyphCount);
+}
+
+int SkTypeface::countGlyphs() const {
+ return this->onCountGlyphs();
+}
+
+int SkTypeface::getUnitsPerEm() const {
+ // should we try to cache this in the base-class?
+ return this->onGetUPEM();
+}
+
+SkTypeface::LocalizedStrings* SkTypeface::createFamilyNameIterator() const {
+ return this->onCreateFamilyNameIterator();
+}
+
+void SkTypeface::getFamilyName(SkString* name) const {
+ bool isLocal = false;
+ SkFontDescriptor desc(this->style());
+ this->onGetFontDescriptor(&desc, &isLocal);
+ name->set(desc.getFamilyName());
+}
+
+SkAdvancedTypefaceMetrics* SkTypeface::getAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo info,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+ return this->onGetAdvancedTypefaceMetrics(info, glyphIDs, glyphIDsCount);
+}
+
+SkTypeface* SkTypeface::refMatchingStyle(Style style) const {
+ return this->onRefMatchingStyle(style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int SkTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const {
+ SkDebugf("onCharsToGlyphs unimplemented\n");
+ if (glyphs && glyphCount > 0) {
+ sk_bzero(glyphs, glyphCount * sizeof(glyphs[0]));
+ }
+ return 0;
+}
diff --git a/core/SkTypefaceCache.cpp b/core/SkTypefaceCache.cpp
new file mode 100644
index 00000000..b75b2a9a
--- /dev/null
+++ b/core/SkTypefaceCache.cpp
@@ -0,0 +1,145 @@
+
+/*
+ * 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 "SkTypefaceCache.h"
+#include "SkThread.h"
+
+#define TYPEFACE_CACHE_LIMIT 1024
+
+void SkTypefaceCache::add(SkTypeface* face,
+ SkTypeface::Style requestedStyle,
+ bool strong) {
+ if (fArray.count() >= TYPEFACE_CACHE_LIMIT) {
+ this->purge(TYPEFACE_CACHE_LIMIT >> 2);
+ }
+
+ Rec* rec = fArray.append();
+ rec->fFace = face;
+ rec->fRequestedStyle = requestedStyle;
+ rec->fStrong = strong;
+ if (strong) {
+ face->ref();
+ } else {
+ face->weak_ref();
+ }
+}
+
+SkTypeface* SkTypefaceCache::findByID(SkFontID fontID) const {
+ const Rec* curr = fArray.begin();
+ const Rec* stop = fArray.end();
+ while (curr < stop) {
+ if (curr->fFace->uniqueID() == fontID) {
+ return curr->fFace;
+ }
+ curr += 1;
+ }
+ return NULL;
+}
+
+SkTypeface* SkTypefaceCache::findByProcAndRef(FindProc proc, void* ctx) const {
+ const Rec* curr = fArray.begin();
+ const Rec* stop = fArray.end();
+ while (curr < stop) {
+ SkTypeface* currFace = curr->fFace;
+ if (proc(currFace, curr->fRequestedStyle, ctx)) {
+ if (curr->fStrong) {
+ currFace->ref();
+ return currFace;
+ } else if (currFace->try_ref()) {
+ return currFace;
+ } else {
+ //remove currFace from fArray?
+ }
+ }
+ curr += 1;
+ }
+ return NULL;
+}
+
+void SkTypefaceCache::purge(int numToPurge) {
+ int count = fArray.count();
+ int i = 0;
+ while (i < count) {
+ SkTypeface* face = fArray[i].fFace;
+ bool strong = fArray[i].fStrong;
+ if ((strong && face->unique()) || (!strong && face->weak_expired())) {
+ if (strong) {
+ face->unref();
+ } else {
+ face->weak_unref();
+ }
+ fArray.remove(i);
+ --count;
+ if (--numToPurge == 0) {
+ return;
+ }
+ } else {
+ ++i;
+ }
+ }
+}
+
+void SkTypefaceCache::purgeAll() {
+ this->purge(fArray.count());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTypefaceCache& SkTypefaceCache::Get() {
+ static SkTypefaceCache gCache;
+ return gCache;
+}
+
+SkFontID SkTypefaceCache::NewFontID() {
+ static int32_t gFontID;
+ return sk_atomic_inc(&gFontID) + 1;
+}
+
+SK_DECLARE_STATIC_MUTEX(gMutex);
+
+void SkTypefaceCache::Add(SkTypeface* face,
+ SkTypeface::Style requestedStyle,
+ bool strong) {
+ SkAutoMutexAcquire ama(gMutex);
+ Get().add(face, requestedStyle, strong);
+}
+
+SkTypeface* SkTypefaceCache::FindByID(SkFontID fontID) {
+ SkAutoMutexAcquire ama(gMutex);
+ return Get().findByID(fontID);
+}
+
+SkTypeface* SkTypefaceCache::FindByProcAndRef(FindProc proc, void* ctx) {
+ SkAutoMutexAcquire ama(gMutex);
+ SkTypeface* typeface = Get().findByProcAndRef(proc, ctx);
+ return typeface;
+}
+
+void SkTypefaceCache::PurgeAll() {
+ SkAutoMutexAcquire ama(gMutex);
+ Get().purgeAll();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+static bool DumpProc(SkTypeface* face, SkTypeface::Style style, void* ctx) {
+ SkDebugf("SkTypefaceCache: face %p fontID %d style %d refcnt %d\n",
+ face, face->uniqueID(), style, face->getRefCnt());
+ return false;
+}
+#endif
+
+void SkTypefaceCache::Dump() {
+#ifdef SK_DEBUG
+ SkAutoMutexAcquire ama(gMutex);
+ (void)Get().findByProcAndRef(DumpProc, NULL);
+#endif
+}
diff --git a/core/SkTypefaceCache.h b/core/SkTypefaceCache.h
new file mode 100644
index 00000000..b6cab816
--- /dev/null
+++ b/core/SkTypefaceCache.h
@@ -0,0 +1,94 @@
+
+/*
+ * 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 SkTypefaceCache_DEFINED
+#define SkTypefaceCache_DEFINED
+
+#include "SkTypeface.h"
+#include "SkTDArray.h"
+
+/* TODO
+ * Provide std way to cache name+requestedStyle aliases to the same typeface.
+ *
+ * The current mechanism ends up create a diff typeface for each one, even if
+ * they map to the same internal obj (e.g. CTFontRef on the mac)
+ */
+
+class SkTypefaceCache {
+public:
+ /**
+ * Callback for FindByProc. Returns true if the given typeface is a match
+ * for the given context. The passed typeface is owned by the cache and is
+ * not additionally ref()ed. The typeface may be in the disposed state.
+ */
+ typedef bool (*FindProc)(SkTypeface*, SkTypeface::Style, void* context);
+
+ /**
+ * Helper: returns a unique fontID to pass to the constructor of
+ * your subclass of SkTypeface
+ */
+ static SkFontID NewFontID();
+
+ /**
+ * Add a typeface to the cache. This ref()s the typeface, so that the
+ * cache is also an owner. Later, if we need to purge the cache, typefaces
+ * whose refcnt is 1 (meaning only the cache is an owner) will be
+ * unref()ed.
+ */
+ static void Add(SkTypeface*,
+ SkTypeface::Style requested,
+ bool strong = true);
+
+ /**
+ * Search the cache for a typeface with the specified fontID (uniqueID).
+ * If one is found, return it (its reference count is unmodified). If none
+ * is found, return NULL. The reference count is unmodified as it is
+ * assumed that the stack will contain a ref to the typeface.
+ */
+ static SkTypeface* FindByID(SkFontID fontID);
+
+ /**
+ * Iterate through the cache, calling proc(typeface, ctx) with each
+ * typeface. If proc returns true, then we return that typeface (this
+ * ref()s the typeface). If it never returns true, we return NULL.
+ */
+ static SkTypeface* FindByProcAndRef(FindProc proc, void* ctx);
+
+ /**
+ * This will unref all of the typefaces in the cache for which the cache
+ * is the only owner. Normally this is handled automatically as needed.
+ * This function is exposed for clients that explicitly want to purge the
+ * cache (e.g. to look for leaks).
+ */
+ static void PurgeAll();
+
+ /**
+ * Debugging only: dumps the status of the typefaces in the cache
+ */
+ static void Dump();
+
+private:
+ static SkTypefaceCache& Get();
+
+ void add(SkTypeface*, SkTypeface::Style requested, bool strong = true);
+ SkTypeface* findByID(SkFontID findID) const;
+ SkTypeface* findByProcAndRef(FindProc proc, void* ctx) const;
+ void purge(int count);
+ void purgeAll();
+
+ struct Rec {
+ SkTypeface* fFace;
+ bool fStrong;
+ SkTypeface::Style fRequestedStyle;
+ };
+ SkTDArray<Rec> fArray;
+};
+
+#endif
diff --git a/core/SkTypefacePriv.h b/core/SkTypefacePriv.h
new file mode 100644
index 00000000..dc993d08
--- /dev/null
+++ b/core/SkTypefacePriv.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTypefacePriv_DEFINED
+#define SkTypefacePriv_DEFINED
+
+#include "SkTypeface.h"
+
+/**
+ * Return a ref'd typeface, which must later be unref'd
+ *
+ * If the parameter is non-null, it will be ref'd and returned, otherwise
+ * it will be the default typeface.
+ */
+static inline SkTypeface* ref_or_default(SkTypeface* face) {
+ return face ? SkRef(face) : SkTypeface::RefDefault();
+}
+
+/**
+ * Always resolves to a non-null typeface, either the value passed to its
+ * constructor, or the default typeface if null was passed.
+ */
+class SkAutoResolveDefaultTypeface : public SkAutoTUnref<SkTypeface> {
+public:
+ SkAutoResolveDefaultTypeface() : INHERITED(SkTypeface::RefDefault()) {}
+
+ SkAutoResolveDefaultTypeface(SkTypeface* face)
+ : INHERITED(ref_or_default(face)) {}
+
+private:
+ typedef SkAutoTUnref<SkTypeface> INHERITED;
+};
+
+#endif
diff --git a/core/SkUnPreMultiply.cpp b/core/SkUnPreMultiply.cpp
new file mode 100644
index 00000000..ad87f8af
--- /dev/null
+++ b/core/SkUnPreMultiply.cpp
@@ -0,0 +1,80 @@
+
+/*
+ * 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 "SkUnPreMultiply.h"
+#include "SkColorPriv.h"
+
+SkColor SkUnPreMultiply::PMColorToColor(SkPMColor c) {
+ const unsigned a = SkGetPackedA32(c);
+ const Scale scale = GetScale(a);
+ return SkColorSetARGB(a,
+ ApplyScale(scale, SkGetPackedR32(c)),
+ ApplyScale(scale, SkGetPackedG32(c)),
+ ApplyScale(scale, SkGetPackedB32(c)));
+}
+
+const uint32_t SkUnPreMultiply::gTable[] = {
+ 0x00000000, 0xFF000000, 0x7F800000, 0x55000000, 0x3FC00000, 0x33000000, 0x2A800000, 0x246DB6DB,
+ 0x1FE00000, 0x1C555555, 0x19800000, 0x172E8BA3, 0x15400000, 0x139D89D9, 0x1236DB6E, 0x11000000,
+ 0x0FF00000, 0x0F000000, 0x0E2AAAAB, 0x0D6BCA1B, 0x0CC00000, 0x0C249249, 0x0B9745D1, 0x0B1642C8,
+ 0x0AA00000, 0x0A333333, 0x09CEC4EC, 0x0971C71C, 0x091B6DB7, 0x08CB08D4, 0x08800000, 0x0839CE74,
+ 0x07F80000, 0x07BA2E8C, 0x07800000, 0x07492492, 0x07155555, 0x06E45307, 0x06B5E50D, 0x0689D89E,
+ 0x06600000, 0x063831F4, 0x06124925, 0x05EE23B9, 0x05CBA2E9, 0x05AAAAAB, 0x058B2164, 0x056CEFA9,
+ 0x05500000, 0x05343EB2, 0x0519999A, 0x05000000, 0x04E76276, 0x04CFB2B8, 0x04B8E38E, 0x04A2E8BA,
+ 0x048DB6DB, 0x0479435E, 0x0465846A, 0x045270D0, 0x04400000, 0x042E29F8, 0x041CE73A, 0x040C30C3,
+ 0x03FC0000, 0x03EC4EC5, 0x03DD1746, 0x03CE540F, 0x03C00000, 0x03B21643, 0x03A49249, 0x03976FC6,
+ 0x038AAAAB, 0x037E3F20, 0x03722983, 0x03666666, 0x035AF287, 0x034FCACE, 0x0344EC4F, 0x033A5441,
+ 0x03300000, 0x0325ED09, 0x031C18FA, 0x0312818B, 0x03092492, 0x03000000, 0x02F711DC, 0x02EE5847,
+ 0x02E5D174, 0x02DD7BAF, 0x02D55555, 0x02CD5CD6, 0x02C590B2, 0x02BDEF7C, 0x02B677D4, 0x02AF286C,
+ 0x02A80000, 0x02A0FD5C, 0x029A1F59, 0x029364D9, 0x028CCCCD, 0x0286562E, 0x02800000, 0x0279C952,
+ 0x0273B13B, 0x026DB6DB, 0x0267D95C, 0x026217ED, 0x025C71C7, 0x0256E62A, 0x0251745D, 0x024C1BAD,
+ 0x0246DB6E, 0x0241B2F9, 0x023CA1AF, 0x0237A6F5, 0x0232C235, 0x022DF2DF, 0x02293868, 0x02249249,
+ 0x02200000, 0x021B810F, 0x021714FC, 0x0212BB51, 0x020E739D, 0x020A3D71, 0x02061862, 0x02020408,
+ 0x01FE0000, 0x01FA0BE8, 0x01F62762, 0x01F25214, 0x01EE8BA3, 0x01EAD3BB, 0x01E72A08, 0x01E38E39,
+ 0x01E00000, 0x01DC7F11, 0x01D90B21, 0x01D5A3EA, 0x01D24925, 0x01CEFA8E, 0x01CBB7E3, 0x01C880E5,
+ 0x01C55555, 0x01C234F7, 0x01BF1F90, 0x01BC14E6, 0x01B914C2, 0x01B61EED, 0x01B33333, 0x01B05161,
+ 0x01AD7943, 0x01AAAAAB, 0x01A7E567, 0x01A5294A, 0x01A27627, 0x019FCBD2, 0x019D2A20, 0x019A90E8,
+ 0x01980000, 0x01957741, 0x0192F685, 0x01907DA5, 0x018E0C7D, 0x018BA2E9, 0x018940C5, 0x0186E5F1,
+ 0x01849249, 0x018245AE, 0x01800000, 0x017DC11F, 0x017B88EE, 0x0179574E, 0x01772C23, 0x01750750,
+ 0x0172E8BA, 0x0170D045, 0x016EBDD8, 0x016CB157, 0x016AAAAB, 0x0168A9B9, 0x0166AE6B, 0x0164B8A8,
+ 0x0162C859, 0x0160DD68, 0x015EF7BE, 0x015D1746, 0x015B3BEA, 0x01596596, 0x01579436, 0x0155C7B5,
+ 0x01540000, 0x01523D04, 0x01507EAE, 0x014EC4EC, 0x014D0FAC, 0x014B5EDD, 0x0149B26D, 0x01480A4B,
+ 0x01466666, 0x0144C6B0, 0x01432B17, 0x0141938C, 0x01400000, 0x013E7064, 0x013CE4A9, 0x013B5CC1,
+ 0x0139D89E, 0x01385831, 0x0136DB6E, 0x01356246, 0x0133ECAE, 0x01327A97, 0x01310BF6, 0x012FA0BF,
+ 0x012E38E4, 0x012CD45A, 0x012B7315, 0x012A150B, 0x0128BA2F, 0x01276276, 0x01260DD6, 0x0124BC45,
+ 0x01236DB7, 0x01222222, 0x0120D97D, 0x011F93BC, 0x011E50D8, 0x011D10C5, 0x011BD37A, 0x011A98EF,
+ 0x0119611A, 0x01182BF3, 0x0116F970, 0x0115C988, 0x01149C34, 0x0113716B, 0x01124925, 0x01112359,
+ 0x01100000, 0x010EDF12, 0x010DC087, 0x010CA458, 0x010B8A7E, 0x010A72F0, 0x01095DA9, 0x01084AA0,
+ 0x010739CE, 0x01062B2E, 0x01051EB8, 0x01041466, 0x01030C31, 0x01020612, 0x01010204, 0x01000000
+};
+
+#ifdef BUILD_DIVIDE_TABLE
+void SkUnPreMultiply_BuildTable() {
+ for (unsigned i = 0; i <= 255; i++) {
+ uint32_t scale;
+
+ if (0 == i) {
+ scale = 0;
+ } else {
+ scale = ((255 << 24) + (i >> 1)) / i;
+ }
+
+ SkDebugf(" 0x%08X,", scale);
+ if ((i & 7) == 7) {
+ SkDebugf("\n");
+ }
+
+ // test the result
+ for (int j = 1; j <= i; j++) {
+ uint32_t test = (j * scale + (1 << 23)) >> 24;
+ uint32_t div = roundf(j * 255.0f / i);
+ int diff = SkAbs32(test - div);
+ SkASSERT(diff <= 1 && test <= 255);
+ }
+ }
+}
+#endif
diff --git a/core/SkUtils.cpp b/core/SkUtils.cpp
new file mode 100644
index 00000000..e460ac8f
--- /dev/null
+++ b/core/SkUtils.cpp
@@ -0,0 +1,397 @@
+
+/*
+ * 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 "SkUtils.h"
+
+#if 0
+#define assign_16_longs(dst, value) \
+ do { \
+ (dst)[0] = value; (dst)[1] = value; \
+ (dst)[2] = value; (dst)[3] = value; \
+ (dst)[4] = value; (dst)[5] = value; \
+ (dst)[6] = value; (dst)[7] = value; \
+ (dst)[8] = value; (dst)[9] = value; \
+ (dst)[10] = value; (dst)[11] = value; \
+ (dst)[12] = value; (dst)[13] = value; \
+ (dst)[14] = value; (dst)[15] = value; \
+ } while (0)
+#else
+#define assign_16_longs(dst, value) \
+ do { \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ *(dst)++ = value; *(dst)++ = value; \
+ } while (0)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void sk_memset16_portable(uint16_t dst[], uint16_t value, int count) {
+ SkASSERT(dst != NULL && count >= 0);
+
+ if (count <= 0) {
+ return;
+ }
+
+ // not sure if this helps to short-circuit on small values of count
+ if (count < 8) {
+ do {
+ *dst++ = (uint16_t)value;
+ } while (--count != 0);
+ return;
+ }
+
+ // ensure we're on a long boundary
+ if ((size_t)dst & 2) {
+ *dst++ = (uint16_t)value;
+ count -= 1;
+ }
+
+ uint32_t value32 = ((uint32_t)value << 16) | value;
+
+ // handle the bulk with our unrolled macro
+ {
+ int sixteenlongs = count >> 5;
+ if (sixteenlongs) {
+ uint32_t* dst32 = (uint32_t*)dst;
+ do {
+ assign_16_longs(dst32, value32);
+ } while (--sixteenlongs != 0);
+ dst = (uint16_t*)dst32;
+ count &= 31;
+ }
+ }
+
+ // handle (most) of the rest
+ {
+ int longs = count >> 1;
+ if (longs) {
+ do {
+ *(uint32_t*)dst = value32;
+ dst += 2;
+ } while (--longs != 0);
+ }
+ }
+
+ // cleanup a possible trailing short
+ if (count & 1) {
+ *dst = (uint16_t)value;
+ }
+}
+
+void sk_memset32_portable(uint32_t dst[], uint32_t value, int count) {
+ SkASSERT(dst != NULL && count >= 0);
+
+ int sixteenlongs = count >> 4;
+ if (sixteenlongs) {
+ do {
+ assign_16_longs(dst, value);
+ } while (--sixteenlongs != 0);
+ count &= 15;
+ }
+
+ if (count) {
+ do {
+ *dst++ = value;
+ } while (--count != 0);
+ }
+}
+
+static void sk_memset16_stub(uint16_t dst[], uint16_t value, int count) {
+ SkMemset16Proc proc = SkMemset16GetPlatformProc();
+ sk_memset16 = proc ? proc : sk_memset16_portable;
+ sk_memset16(dst, value, count);
+}
+
+SkMemset16Proc sk_memset16 = sk_memset16_stub;
+
+static void sk_memset32_stub(uint32_t dst[], uint32_t value, int count) {
+ SkMemset32Proc proc = SkMemset32GetPlatformProc();
+ sk_memset32 = proc ? proc : sk_memset32_portable;
+ sk_memset32(dst, value, count);
+}
+
+SkMemset32Proc sk_memset32 = sk_memset32_stub;
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* 0xxxxxxx 1 total
+ 10xxxxxx // never a leading byte
+ 110xxxxx 2 total
+ 1110xxxx 3 total
+ 11110xxx 4 total
+
+ 11 10 01 01 xx xx xx xx 0...
+ 0xE5XX0000
+ 0xE5 << 24
+*/
+
+#ifdef SK_DEBUG
+ static void assert_utf8_leadingbyte(unsigned c) {
+ SkASSERT(c <= 0xF7); // otherwise leading byte is too big (more than 4 bytes)
+ SkASSERT((c & 0xC0) != 0x80); // can't begin with a middle char
+ }
+
+ int SkUTF8_LeadByteToCount(unsigned c) {
+ assert_utf8_leadingbyte(c);
+ return (((0xE5 << 24) >> (c >> 4 << 1)) & 3) + 1;
+ }
+#else
+ #define assert_utf8_leadingbyte(c)
+#endif
+
+int SkUTF8_CountUnichars(const char utf8[]) {
+ SkASSERT(utf8);
+
+ int count = 0;
+
+ for (;;) {
+ int c = *(const uint8_t*)utf8;
+ if (c == 0) {
+ break;
+ }
+ utf8 += SkUTF8_LeadByteToCount(c);
+ count += 1;
+ }
+ return count;
+}
+
+int SkUTF8_CountUnichars(const char utf8[], size_t byteLength) {
+ SkASSERT(NULL != utf8 || 0 == byteLength);
+
+ int count = 0;
+ const char* stop = utf8 + byteLength;
+
+ while (utf8 < stop) {
+ utf8 += SkUTF8_LeadByteToCount(*(const uint8_t*)utf8);
+ count += 1;
+ }
+ return count;
+}
+
+SkUnichar SkUTF8_ToUnichar(const char utf8[]) {
+ SkASSERT(NULL != utf8);
+
+ const uint8_t* p = (const uint8_t*)utf8;
+ int c = *p;
+ int hic = c << 24;
+
+ assert_utf8_leadingbyte(c);
+
+ if (hic < 0) {
+ uint32_t mask = (uint32_t)~0x3F;
+ hic <<= 1;
+ do {
+ c = (c << 6) | (*++p & 0x3F);
+ mask <<= 5;
+ } while ((hic <<= 1) < 0);
+ c &= ~mask;
+ }
+ return c;
+}
+
+SkUnichar SkUTF8_NextUnichar(const char** ptr) {
+ SkASSERT(NULL != ptr && NULL != *ptr);
+
+ const uint8_t* p = (const uint8_t*)*ptr;
+ int c = *p;
+ int hic = c << 24;
+
+ assert_utf8_leadingbyte(c);
+
+ if (hic < 0) {
+ uint32_t mask = (uint32_t)~0x3F;
+ hic <<= 1;
+ do {
+ c = (c << 6) | (*++p & 0x3F);
+ mask <<= 5;
+ } while ((hic <<= 1) < 0);
+ c &= ~mask;
+ }
+ *ptr = (char*)p + 1;
+ return c;
+}
+
+SkUnichar SkUTF8_PrevUnichar(const char** ptr) {
+ SkASSERT(NULL != ptr && NULL != *ptr);
+
+ const char* p = *ptr;
+
+ if (*--p & 0x80) {
+ while (*--p & 0x40) {
+ ;
+ }
+ }
+
+ *ptr = (char*)p;
+ return SkUTF8_NextUnichar(&p);
+}
+
+size_t SkUTF8_FromUnichar(SkUnichar uni, char utf8[]) {
+ if ((uint32_t)uni > 0x10FFFF) {
+ SkDEBUGFAIL("bad unichar");
+ return 0;
+ }
+
+ if (uni <= 127) {
+ if (utf8) {
+ *utf8 = (char)uni;
+ }
+ return 1;
+ }
+
+ char tmp[4];
+ char* p = tmp;
+ size_t count = 1;
+
+ SkDEBUGCODE(SkUnichar orig = uni;)
+
+ while (uni > 0x7F >> count) {
+ *p++ = (char)(0x80 | (uni & 0x3F));
+ uni >>= 6;
+ count += 1;
+ }
+
+ if (utf8) {
+ p = tmp;
+ utf8 += count;
+ while (p < tmp + count - 1) {
+ *--utf8 = *p++;
+ }
+ *--utf8 = (char)(~(0xFF >> count) | uni);
+ }
+
+ SkASSERT(utf8 == NULL || orig == SkUTF8_ToUnichar(utf8));
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkUTF16_CountUnichars(const uint16_t src[]) {
+ SkASSERT(src);
+
+ int count = 0;
+ unsigned c;
+ while ((c = *src++) != 0) {
+ SkASSERT(!SkUTF16_IsLowSurrogate(c));
+ if (SkUTF16_IsHighSurrogate(c)) {
+ c = *src++;
+ SkASSERT(SkUTF16_IsLowSurrogate(c));
+ }
+ count += 1;
+ }
+ return count;
+}
+
+int SkUTF16_CountUnichars(const uint16_t src[], int numberOf16BitValues) {
+ SkASSERT(src);
+
+ const uint16_t* stop = src + numberOf16BitValues;
+ int count = 0;
+ while (src < stop) {
+ unsigned c = *src++;
+ SkASSERT(!SkUTF16_IsLowSurrogate(c));
+ if (SkUTF16_IsHighSurrogate(c)) {
+ SkASSERT(src < stop);
+ c = *src++;
+ SkASSERT(SkUTF16_IsLowSurrogate(c));
+ }
+ count += 1;
+ }
+ return count;
+}
+
+SkUnichar SkUTF16_NextUnichar(const uint16_t** srcPtr) {
+ SkASSERT(srcPtr && *srcPtr);
+
+ const uint16_t* src = *srcPtr;
+ SkUnichar c = *src++;
+
+ SkASSERT(!SkUTF16_IsLowSurrogate(c));
+ if (SkUTF16_IsHighSurrogate(c)) {
+ unsigned c2 = *src++;
+ SkASSERT(SkUTF16_IsLowSurrogate(c2));
+
+ // c = ((c & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000
+ // c = (((c & 0x3FF) + 64) << 10) + (c2 & 0x3FF)
+ c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00);
+ }
+ *srcPtr = src;
+ return c;
+}
+
+SkUnichar SkUTF16_PrevUnichar(const uint16_t** srcPtr) {
+ SkASSERT(srcPtr && *srcPtr);
+
+ const uint16_t* src = *srcPtr;
+ SkUnichar c = *--src;
+
+ SkASSERT(!SkUTF16_IsHighSurrogate(c));
+ if (SkUTF16_IsLowSurrogate(c)) {
+ unsigned c2 = *--src;
+ SkASSERT(SkUTF16_IsHighSurrogate(c2));
+ c = (c2 << 10) + c + (0x10000 - (0xD800 << 10) - 0xDC00);
+ }
+ *srcPtr = src;
+ return c;
+}
+
+size_t SkUTF16_FromUnichar(SkUnichar uni, uint16_t dst[]) {
+ SkASSERT((unsigned)uni <= 0x10FFFF);
+
+ int extra = (uni > 0xFFFF);
+
+ if (dst) {
+ if (extra) {
+ // dst[0] = SkToU16(0xD800 | ((uni - 0x10000) >> 10));
+ // dst[0] = SkToU16(0xD800 | ((uni >> 10) - 64));
+ dst[0] = SkToU16((0xD800 - 64) + (uni >> 10));
+ dst[1] = SkToU16(0xDC00 | (uni & 0x3FF));
+
+ SkASSERT(SkUTF16_IsHighSurrogate(dst[0]));
+ SkASSERT(SkUTF16_IsLowSurrogate(dst[1]));
+ } else {
+ dst[0] = SkToU16(uni);
+ SkASSERT(!SkUTF16_IsHighSurrogate(dst[0]));
+ SkASSERT(!SkUTF16_IsLowSurrogate(dst[0]));
+ }
+ }
+ return 1 + extra;
+}
+
+size_t SkUTF16_ToUTF8(const uint16_t utf16[], int numberOf16BitValues,
+ char utf8[]) {
+ SkASSERT(numberOf16BitValues >= 0);
+ if (numberOf16BitValues <= 0) {
+ return 0;
+ }
+
+ SkASSERT(utf16 != NULL);
+
+ const uint16_t* stop = utf16 + numberOf16BitValues;
+ size_t size = 0;
+
+ if (utf8 == NULL) { // just count
+ while (utf16 < stop) {
+ size += SkUTF8_FromUnichar(SkUTF16_NextUnichar(&utf16), NULL);
+ }
+ } else {
+ char* start = utf8;
+ while (utf16 < stop) {
+ utf8 += SkUTF8_FromUnichar(SkUTF16_NextUnichar(&utf16), utf8);
+ }
+ size = utf8 - start;
+ }
+ return size;
+}
diff --git a/core/SkUtilsArm.cpp b/core/SkUtilsArm.cpp
new file mode 100644
index 00000000..87a4a7fb
--- /dev/null
+++ b/core/SkUtilsArm.cpp
@@ -0,0 +1,197 @@
+
+/*
+ * 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 "SkUtilsArm.h"
+
+#if SK_ARM_NEON_IS_DYNAMIC
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+
+// Set USE_ANDROID_NDK_CPU_FEATURES to use the Android NDK's
+// cpu-features helper library to detect NEON at runtime. See
+// http://crbug.com/164154 to see why this is needed in Chromium
+// for Android.
+#if defined(SK_BUILD_FOR_ANDROID)
+# define USE_ANDROID_NDK_CPU_FEATURES 1
+#else
+# define USE_ANDROID_NDK_CPU_FEATURES 0
+#endif
+
+#if USE_ANDROID_NDK_CPU_FEATURES
+# include <cpu-features.h>
+#endif
+
+// Set NEON_DEBUG to 1 to allow debugging of the CPU features probing.
+// For now, we always set it for SK_DEBUG builds.
+#ifdef SK_DEBUG
+# define NEON_DEBUG 1
+#else
+# define NEON_DEBUG 0
+#endif
+
+#if NEON_DEBUG
+# ifdef SK_BUILD_FOR_ANDROID
+ // used to declare PROP_VALUE_MAX and __system_property_get()
+# include <sys/system_properties.h>
+# endif
+#endif
+
+// A function used to determine at runtime if the target CPU supports
+// the ARM NEON instruction set. This implementation is Linux-specific.
+static bool sk_cpu_arm_check_neon(void) {
+ bool result = false;
+
+#if NEON_DEBUG
+ // Allow forcing the mode through the environment during debugging.
+# ifdef SK_BUILD_FOR_ANDROID
+ // On Android, we use a system property
+# define PROP_NAME "debug.skia.arm_neon_mode"
+ char prop[PROP_VALUE_MAX];
+ if (__system_property_get(PROP_NAME, prop) > 0) {
+# else
+# define PROP_NAME "SKIA_ARM_NEON_MODE"
+ // On ARM Linux, we use an environment variable
+ const char* prop = getenv(PROP_NAME);
+ if (prop != NULL) {
+# endif
+ SkDebugf("%s: %s", PROP_NAME, prop);
+ if (!strcmp(prop, "1")) {
+ SkDebugf("Forcing ARM Neon mode to full!\n");
+ return true;
+ }
+ if (!strcmp(prop, "0")) {
+ SkDebugf("Disabling ARM NEON mode\n");
+ return false;
+ }
+ }
+ SkDebugf("Running dynamic CPU feature detection\n");
+#endif
+
+#if USE_ANDROID_NDK_CPU_FEATURES
+
+ result = (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
+
+#else // USE_ANDROID_NDK_CPU_FEATURES
+
+ // There is no user-accessible CPUID instruction on ARM that we can use.
+ // Instead, we must parse /proc/cpuinfo and look for the 'neon' feature.
+ // For example, here's a typical output (Nexus S running ICS 4.0.3):
+ /*
+ Processor : ARMv7 Processor rev 2 (v7l)
+ BogoMIPS : 994.65
+ Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3
+ CPU implementer : 0x41
+ CPU architecture: 7
+ CPU variant : 0x2
+ CPU part : 0xc08
+ CPU revision : 2
+
+ Hardware : herring
+ Revision : 000b
+ Serial : 3833c77d6dc000ec
+ */
+ char buffer[4096];
+
+ // If we fail any of the following, assume we don't have NEON instructions
+ // This allows us to return immediately in case of error.
+ result = false;
+
+ do {
+ // open /proc/cpuinfo
+ int fd = TEMP_FAILURE_RETRY(open("/proc/cpuinfo", O_RDONLY));
+ if (fd < 0) {
+ SkDebugf("Could not open /proc/cpuinfo: %s\n", strerror(errno));
+ break;
+ }
+
+ // Read the file. To simplify our search, we're going to place two
+ // sentinel '\n' characters: one at the start of the buffer, and one at
+ // the end. This means we reserve the first and last buffer bytes.
+ buffer[0] = '\n';
+ int size = TEMP_FAILURE_RETRY(read(fd, buffer+1, sizeof(buffer)-2));
+ close(fd);
+
+ if (size < 0) { // should not happen
+ SkDebugf("Could not read /proc/cpuinfo: %s\n", strerror(errno));
+ break;
+ }
+
+ SkDebugf("START /proc/cpuinfo:\n%.*s\nEND /proc/cpuinfo\n",
+ size, buffer+1);
+
+ // Compute buffer limit, and place final sentinel
+ char* buffer_end = buffer + 1 + size;
+ buffer_end[0] = '\n';
+
+ // Now, find a line that starts with "Features", i.e. look for
+ // '\nFeatures ' in our buffer.
+ const char features[] = "\nFeatures\t";
+ const size_t features_len = sizeof(features)-1;
+
+ char* line = (char*) memmem(buffer, buffer_end - buffer,
+ features, features_len);
+ if (line == NULL) { // Weird, no Features line, bad kernel?
+ SkDebugf("Could not find a line starting with 'Features'"
+ "in /proc/cpuinfo ?\n");
+ break;
+ }
+
+ line += features_len; // Skip the "\nFeatures\t" prefix
+
+ // Find the end of the current line
+ char* line_end = (char*) memchr(line, '\n', buffer_end - line);
+ if (line_end == NULL)
+ line_end = buffer_end;
+
+ // Now find an instance of 'neon' in the flags list. We want to
+ // ensure it's only 'neon' and not something fancy like 'noneon'
+ // so check that it follows a space.
+ const char neon[] = " neon";
+ const size_t neon_len = sizeof(neon)-1;
+ const char* flag = (const char*) memmem(line, line_end - line,
+ neon, neon_len);
+ if (flag == NULL)
+ break;
+
+ // Ensure it is followed by a space or a newline.
+ if (flag[neon_len] != ' ' && flag[neon_len] != '\n')
+ break;
+
+ // Fine, we support Arm NEON !
+ result = true;
+
+ } while (0);
+
+#endif // USE_ANDROID_NDK_CPU_FEATURES
+
+ if (result) {
+ SkDebugf("Device supports ARM NEON instructions!\n");
+ } else {
+ SkDebugf("Device does NOT support ARM NEON instructions!\n");
+ }
+ return result;
+}
+
+static pthread_once_t sOnce;
+static bool sHasArmNeon;
+
+// called through pthread_once()
+void sk_cpu_arm_probe_features(void) {
+ sHasArmNeon = sk_cpu_arm_check_neon();
+}
+
+bool sk_cpu_arm_has_neon(void) {
+ pthread_once(&sOnce, sk_cpu_arm_probe_features);
+ return sHasArmNeon;
+}
+
+#endif // SK_ARM_NEON_IS_DYNAMIC
diff --git a/core/SkUtilsArm.h b/core/SkUtilsArm.h
new file mode 100644
index 00000000..b9a26143
--- /dev/null
+++ b/core/SkUtilsArm.h
@@ -0,0 +1,87 @@
+
+/*
+ * 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 SkUtilsArm_DEFINED
+#define SkUtilsArm_DEFINED
+
+#include "SkUtils.h"
+
+// Define SK_ARM_NEON_MODE to one of the following values
+// corresponding respectively to:
+// - No ARM Neon support at all (not targetting ARMv7-A, or don't have NEON)
+// - Full ARM Neon support (i.e. assume the CPU always supports it)
+// - Optional ARM Neon support (i.e. probe CPU at runtime)
+//
+#define SK_ARM_NEON_MODE_NONE 0
+#define SK_ARM_NEON_MODE_ALWAYS 1
+#define SK_ARM_NEON_MODE_DYNAMIC 2
+
+#if defined(SK_CPU_ARM) && defined(__ARM_HAVE_OPTIONAL_NEON_SUPPORT)
+# define SK_ARM_NEON_MODE SK_ARM_NEON_MODE_DYNAMIC
+#elif defined(SK_CPU_ARM) && defined(__ARM_HAVE_NEON)
+# define SK_ARM_NEON_MODE SK_ARM_NEON_MODE_ALWAYS
+#else
+# define SK_ARM_NEON_MODE SK_ARM_NEON_MODE_NONE
+#endif
+
+// Convenience test macros, always defined as 0 or 1
+#define SK_ARM_NEON_IS_NONE (SK_ARM_NEON_MODE == SK_ARM_NEON_MODE_NONE)
+#define SK_ARM_NEON_IS_ALWAYS (SK_ARM_NEON_MODE == SK_ARM_NEON_MODE_ALWAYS)
+#define SK_ARM_NEON_IS_DYNAMIC (SK_ARM_NEON_MODE == SK_ARM_NEON_MODE_DYNAMIC)
+
+// The sk_cpu_arm_has_neon() function returns true iff the target device
+// is ARMv7-A and supports Neon instructions. In DYNAMIC mode, this actually
+// probes the CPU at runtime (and caches the result).
+
+#if SK_ARM_NEON_IS_NONE
+static inline bool sk_cpu_arm_has_neon(void) {
+ return false;
+}
+#elif SK_ARM_NEON_IS_ALWAYS
+static inline bool sk_cpu_arm_has_neon(void) {
+ return true;
+}
+#else // SK_ARM_NEON_IS_DYNAMIC
+
+extern bool sk_cpu_arm_has_neon(void) SK_PURE_FUNC;
+#endif
+
+// Use SK_ARM_NEON_WRAP(symbol) to map 'symbol' to a NEON-specific symbol
+// when applicable. This will transform 'symbol' differently depending on
+// the current NEON configuration, i.e.:
+//
+// NONE -> 'symbol'
+// ALWAYS -> 'symbol_neon'
+// DYNAMIC -> 'symbol' or 'symbol_neon' depending on runtime check.
+//
+// The goal is to simplify user code, for example:
+//
+// return SK_ARM_NEON_WRAP(do_something)(params);
+//
+// Replaces the equivalent:
+//
+// #if SK_ARM_NEON_IS_NONE
+// return do_something(params);
+// #elif SK_ARM_NEON_IS_ALWAYS
+// return do_something_neon(params);
+// #elif SK_ARM_NEON_IS_DYNAMIC
+// if (sk_cpu_arm_has_neon())
+// return do_something_neon(params);
+// else
+// return do_something(params);
+// #endif
+//
+#if SK_ARM_NEON_IS_NONE
+# define SK_ARM_NEON_WRAP(x) (x)
+#elif SK_ARM_NEON_IS_ALWAYS
+# define SK_ARM_NEON_WRAP(x) (x ## _neon)
+#elif SK_ARM_NEON_IS_DYNAMIC
+# define SK_ARM_NEON_WRAP(x) (sk_cpu_arm_has_neon() ? x ## _neon : x)
+#endif
+
+#endif // SkUtilsArm_DEFINED
diff --git a/core/SkWriter32.cpp b/core/SkWriter32.cpp
new file mode 100644
index 00000000..e41e2df0
--- /dev/null
+++ b/core/SkWriter32.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 "SkWriter32.h"
+
+SkWriter32::SkWriter32(size_t minSize, void* storage, size_t storageSize) {
+ fMinSize = minSize;
+ fSize = 0;
+ fWrittenBeforeLastBlock = 0;
+ fHead = fTail = NULL;
+
+ if (storageSize) {
+ this->reset(storage, storageSize);
+ }
+}
+
+SkWriter32::~SkWriter32() {
+ this->reset();
+}
+
+void SkWriter32::reset() {
+ Block* block = fHead;
+
+ if (this->isHeadExternallyAllocated()) {
+ SkASSERT(block);
+ // don't 'free' the first block, since it is owned by the caller
+ block = block->fNext;
+ }
+ while (block) {
+ Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
+
+ fSize = 0;
+ fWrittenBeforeLastBlock = 0;
+ fHead = fTail = NULL;
+}
+
+void SkWriter32::reset(void* storage, size_t storageSize) {
+ this->reset();
+
+ storageSize &= ~3; // trunc down to multiple of 4
+ if (storageSize > 0 && SkIsAlign4((intptr_t)storage)) {
+ fHead = fTail = fExternalBlock.initFromStorage(storage, storageSize);
+ }
+}
+
+SkWriter32::Block* SkWriter32::doReserve(size_t size) {
+ SkASSERT(SkAlign4(size) == size);
+
+ Block* block = fTail;
+ SkASSERT(NULL == block || block->available() < size);
+
+ if (NULL == block) {
+ SkASSERT(NULL == fHead);
+ fHead = fTail = block = Block::Create(SkMax32(size, fMinSize));
+ SkASSERT(0 == fWrittenBeforeLastBlock);
+ } else {
+ SkASSERT(fSize > 0);
+ fWrittenBeforeLastBlock = fSize;
+
+ fTail = Block::Create(SkMax32(size, fMinSize));
+ block->fNext = fTail;
+ block = fTail;
+ }
+ return block;
+}
+
+uint32_t* SkWriter32::peek32(size_t offset) {
+ SkDEBUGCODE(this->validate();)
+
+ SkASSERT(SkAlign4(offset) == offset);
+ SkASSERT(offset <= fSize);
+
+ // try the fast case, where offset is within fTail
+ if (offset >= fWrittenBeforeLastBlock) {
+ return fTail->peek32(offset - fWrittenBeforeLastBlock);
+ }
+
+ Block* block = fHead;
+ SkASSERT(NULL != block);
+
+ while (offset >= block->fAllocatedSoFar) {
+ offset -= block->fAllocatedSoFar;
+ block = block->fNext;
+ SkASSERT(NULL != block);
+ }
+ return block->peek32(offset);
+}
+
+void SkWriter32::rewindToOffset(size_t offset) {
+ if (offset >= fSize) {
+ return;
+ }
+ if (0 == offset) {
+ this->reset();
+ return;
+ }
+
+ SkDEBUGCODE(this->validate();)
+
+ SkASSERT(SkAlign4(offset) == offset);
+ SkASSERT(offset <= fSize);
+ fSize = offset;
+
+ // 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);
+ }
+
+ // 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;
+ block->fNext = NULL;
+ // free up any trailing blocks
+ block = next;
+ while (block) {
+ Block* next = block->fNext;
+ sk_free(block);
+ block = next;
+ }
+ }
+ SkDEBUGCODE(this->validate();)
+}
+
+void SkWriter32::flatten(void* dst) const {
+ const Block* block = fHead;
+ SkDEBUGCODE(size_t total = 0;)
+
+ while (block) {
+ size_t allocated = block->fAllocatedSoFar;
+ memcpy(dst, block->base(), allocated);
+ dst = (char*)dst + allocated;
+ block = block->fNext;
+
+ SkDEBUGCODE(total += allocated;)
+ SkASSERT(total <= fSize);
+ }
+ SkASSERT(total == fSize);
+}
+
+uint32_t* SkWriter32::reservePad(size_t size) {
+ if (size > 0) {
+ size_t alignedSize = SkAlign4(size);
+ char* dst = (char*)this->reserve(alignedSize);
+ // Pad the last four bytes with zeroes in one step.
+ uint32_t* padding = (uint32_t*)(dst + (alignedSize - 4));
+ *padding = 0;
+ return (uint32_t*) dst;
+ }
+ return this->reserve(0);
+}
+
+void SkWriter32::writePad(const void* src, size_t size) {
+ if (size > 0) {
+ char* dst = (char*)this->reservePad(size);
+ // Copy the actual data.
+ memcpy(dst, src, size);
+ }
+}
+
+#include "SkStream.h"
+
+size_t SkWriter32::readFromStream(SkStream* stream, size_t length) {
+ char scratch[1024];
+ const size_t MAX = sizeof(scratch);
+ size_t remaining = length;
+
+ while (remaining != 0) {
+ size_t n = remaining;
+ if (n > MAX) {
+ n = MAX;
+ }
+ size_t bytes = stream->read(scratch, n);
+ this->writePad(scratch, bytes);
+ remaining -= bytes;
+ if (bytes != n) {
+ break;
+ }
+ }
+ return length - remaining;
+}
+
+bool SkWriter32::writeToStream(SkWStream* stream) {
+ const Block* block = fHead;
+ while (block) {
+ if (!stream->write(block->base(), block->fAllocatedSoFar)) {
+ return false;
+ }
+ block = block->fNext;
+ }
+ return true;
+}
+
+#ifdef SK_DEBUG
+void SkWriter32::validate() const {
+ SkASSERT(SkIsAlign4(fSize));
+
+ size_t accum = 0;
+ const Block* block = fHead;
+ while (block) {
+ 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;
+ }
+ SkASSERT(accum == fSize);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkReader32.h"
+#include "SkString.h"
+
+/*
+ * Strings are stored as: length[4-bytes] + string_data + '\0' + pad_to_mul_4
+ */
+
+const char* SkReader32::readString(size_t* outLen) {
+ size_t len = this->readInt();
+ if (0xFFFF == len) {
+ if (outLen) {
+ *outLen = 0;
+ }
+ return NULL;
+ }
+ const void* ptr = this->peek();
+
+ // skip over teh string + '\0' and then pad to a multiple of 4
+ size_t alignedSize = SkAlign4(len + 1);
+ this->skip(alignedSize);
+
+ if (outLen) {
+ *outLen = len;
+ }
+ return (const char*)ptr;
+}
+
+size_t SkReader32::readIntoString(SkString* copy) {
+ size_t len;
+ const char* ptr = this->readString(&len);
+ if (copy) {
+ copy->set(ptr, len);
+ }
+ return len;
+}
+
+void SkWriter32::writeString(const char str[], size_t len) {
+ if (NULL == str) {
+ // We're already requiring len < 0xFFFF, so we can use that to mark NULL.
+ this->write32(0xFFFF);
+ return;
+ }
+ if ((long)len < 0) {
+ len = strlen(str);
+ }
+ this->write32(len);
+ // add 1 since we also write a terminating 0
+ size_t alignedLen = SkAlign4(len + 1);
+ char* ptr = (char*)this->reserve(alignedLen);
+ {
+ // Write the terminating 0 and fill in the rest with zeroes
+ uint32_t* padding = (uint32_t*)(ptr + (alignedLen - 4));
+ *padding = 0;
+ }
+ // Copy the string itself.
+ memcpy(ptr, str, len);
+}
+
+size_t SkWriter32::WriteStringSize(const char* str, size_t len) {
+ if ((long)len < 0) {
+ SkASSERT(str);
+ len = strlen(str);
+ }
+ const size_t lenBytes = 4; // we use 4 bytes to record the length
+ // add 1 since we also write a terminating 0
+ return SkAlign4(lenBytes + len + 1);
+}
diff --git a/core/SkXfermode.cpp b/core/SkXfermode.cpp
new file mode 100644
index 00000000..020ea48b
--- /dev/null
+++ b/core/SkXfermode.cpp
@@ -0,0 +1,1980 @@
+
+/*
+ * 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 "SkXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMathPriv.h"
+#include "SkString.h"
+
+SK_DEFINE_INST_COUNT(SkXfermode)
+
+#define SkAlphaMulAlpha(a, b) SkMulDiv255Round(a, b)
+
+#if 0
+// idea for higher precision blends in xfer procs (and slightly faster)
+// see DstATop as a probable caller
+static U8CPU mulmuldiv255round(U8CPU a, U8CPU b, U8CPU c, U8CPU d) {
+ SkASSERT(a <= 255);
+ SkASSERT(b <= 255);
+ SkASSERT(c <= 255);
+ SkASSERT(d <= 255);
+ unsigned prod = SkMulS16(a, b) + SkMulS16(c, d) + 128;
+ unsigned result = (prod + (prod >> 8)) >> 8;
+ SkASSERT(result <= 255);
+ return result;
+}
+#endif
+
+static inline unsigned saturated_add(unsigned a, unsigned b) {
+ SkASSERT(a <= 255);
+ SkASSERT(b <= 255);
+ unsigned sum = a + b;
+ if (sum > 255) {
+ sum = 255;
+ }
+ return sum;
+}
+
+static inline int clamp_signed_byte(int n) {
+ if (n < 0) {
+ n = 0;
+ } else if (n > 255) {
+ n = 255;
+ }
+ return n;
+}
+
+static inline int clamp_div255round(int prod) {
+ if (prod <= 0) {
+ return 0;
+ } else if (prod >= 255*255) {
+ return 255;
+ } else {
+ return SkDiv255Round(prod);
+ }
+}
+
+static inline int clamp_max(int value, int max) {
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// kClear_Mode, //!< [0, 0]
+static SkPMColor clear_modeproc(SkPMColor src, SkPMColor dst) {
+ return 0;
+}
+
+// kSrc_Mode, //!< [Sa, Sc]
+static SkPMColor src_modeproc(SkPMColor src, SkPMColor dst) {
+ return src;
+}
+
+// kDst_Mode, //!< [Da, Dc]
+static SkPMColor dst_modeproc(SkPMColor src, SkPMColor dst) {
+ return dst;
+}
+
+// kSrcOver_Mode, //!< [Sa + Da - Sa*Da, Sc + (1 - Sa)*Dc]
+static SkPMColor srcover_modeproc(SkPMColor src, SkPMColor dst) {
+#if 0
+ // this is the old, more-correct way, but it doesn't guarantee that dst==255
+ // will always stay opaque
+ return src + SkAlphaMulQ(dst, SkAlpha255To256(255 - SkGetPackedA32(src)));
+#else
+ // this is slightly faster, but more importantly guarantees that dst==255
+ // will always stay opaque
+ return src + SkAlphaMulQ(dst, 256 - SkGetPackedA32(src));
+#endif
+}
+
+// kDstOver_Mode, //!< [Sa + Da - Sa*Da, Dc + (1 - Da)*Sc]
+static SkPMColor dstover_modeproc(SkPMColor src, SkPMColor dst) {
+ // this is the reverse of srcover, just flipping src and dst
+ // see srcover's comment about the 256 for opaqueness guarantees
+ return dst + SkAlphaMulQ(src, 256 - SkGetPackedA32(dst));
+}
+
+// kSrcIn_Mode, //!< [Sa * Da, Sc * Da]
+static SkPMColor srcin_modeproc(SkPMColor src, SkPMColor dst) {
+ return SkAlphaMulQ(src, SkAlpha255To256(SkGetPackedA32(dst)));
+}
+
+// kDstIn_Mode, //!< [Sa * Da, Sa * Dc]
+static SkPMColor dstin_modeproc(SkPMColor src, SkPMColor dst) {
+ return SkAlphaMulQ(dst, SkAlpha255To256(SkGetPackedA32(src)));
+}
+
+// kSrcOut_Mode, //!< [Sa * (1 - Da), Sc * (1 - Da)]
+static SkPMColor srcout_modeproc(SkPMColor src, SkPMColor dst) {
+ return SkAlphaMulQ(src, SkAlpha255To256(255 - SkGetPackedA32(dst)));
+}
+
+// kDstOut_Mode, //!< [Da * (1 - Sa), Dc * (1 - Sa)]
+static SkPMColor dstout_modeproc(SkPMColor src, SkPMColor dst) {
+ return SkAlphaMulQ(dst, SkAlpha255To256(255 - SkGetPackedA32(src)));
+}
+
+// kSrcATop_Mode, //!< [Da, Sc * Da + (1 - Sa) * Dc]
+static SkPMColor srcatop_modeproc(SkPMColor src, SkPMColor dst) {
+ unsigned sa = SkGetPackedA32(src);
+ unsigned da = SkGetPackedA32(dst);
+ unsigned isa = 255 - sa;
+
+ return SkPackARGB32(da,
+ SkAlphaMulAlpha(da, SkGetPackedR32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedR32(dst)),
+ SkAlphaMulAlpha(da, SkGetPackedG32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedG32(dst)),
+ SkAlphaMulAlpha(da, SkGetPackedB32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedB32(dst)));
+}
+
+// kDstATop_Mode, //!< [Sa, Sa * Dc + Sc * (1 - Da)]
+static SkPMColor dstatop_modeproc(SkPMColor src, SkPMColor dst) {
+ unsigned sa = SkGetPackedA32(src);
+ unsigned da = SkGetPackedA32(dst);
+ unsigned ida = 255 - da;
+
+ return SkPackARGB32(sa,
+ SkAlphaMulAlpha(ida, SkGetPackedR32(src)) +
+ SkAlphaMulAlpha(sa, SkGetPackedR32(dst)),
+ SkAlphaMulAlpha(ida, SkGetPackedG32(src)) +
+ SkAlphaMulAlpha(sa, SkGetPackedG32(dst)),
+ SkAlphaMulAlpha(ida, SkGetPackedB32(src)) +
+ SkAlphaMulAlpha(sa, SkGetPackedB32(dst)));
+}
+
+// kXor_Mode [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]
+static SkPMColor xor_modeproc(SkPMColor src, SkPMColor dst) {
+ unsigned sa = SkGetPackedA32(src);
+ unsigned da = SkGetPackedA32(dst);
+ unsigned isa = 255 - sa;
+ unsigned ida = 255 - da;
+
+ return SkPackARGB32(sa + da - (SkAlphaMulAlpha(sa, da) << 1),
+ SkAlphaMulAlpha(ida, SkGetPackedR32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedR32(dst)),
+ SkAlphaMulAlpha(ida, SkGetPackedG32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedG32(dst)),
+ SkAlphaMulAlpha(ida, SkGetPackedB32(src)) +
+ SkAlphaMulAlpha(isa, SkGetPackedB32(dst)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// kPlus_Mode
+static SkPMColor plus_modeproc(SkPMColor src, SkPMColor dst) {
+ unsigned b = saturated_add(SkGetPackedB32(src), SkGetPackedB32(dst));
+ unsigned g = saturated_add(SkGetPackedG32(src), SkGetPackedG32(dst));
+ unsigned r = saturated_add(SkGetPackedR32(src), SkGetPackedR32(dst));
+ unsigned a = saturated_add(SkGetPackedA32(src), SkGetPackedA32(dst));
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kModulate_Mode
+static SkPMColor modulate_modeproc(SkPMColor src, SkPMColor dst) {
+ int a = SkAlphaMulAlpha(SkGetPackedA32(src), SkGetPackedA32(dst));
+ int r = SkAlphaMulAlpha(SkGetPackedR32(src), SkGetPackedR32(dst));
+ int g = SkAlphaMulAlpha(SkGetPackedG32(src), SkGetPackedG32(dst));
+ int b = SkAlphaMulAlpha(SkGetPackedB32(src), SkGetPackedB32(dst));
+ return SkPackARGB32(a, r, g, b);
+}
+
+static inline int srcover_byte(int a, int b) {
+ return a + b - SkAlphaMulAlpha(a, b);
+}
+
+// kMultiply_Mode
+// B(Cb, Cs) = Cb x Cs
+// multiply uses its own version of blendfunc_byte because sa and da are not needed
+static int blendfunc_multiply_byte(int sc, int dc, int sa, int da) {
+ return clamp_div255round(sc * (255 - da) + dc * (255 - sa) + sc * dc);
+}
+
+static SkPMColor multiply_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = blendfunc_multiply_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = blendfunc_multiply_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = blendfunc_multiply_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kScreen_Mode
+static SkPMColor screen_modeproc(SkPMColor src, SkPMColor dst) {
+ int a = srcover_byte(SkGetPackedA32(src), SkGetPackedA32(dst));
+ int r = srcover_byte(SkGetPackedR32(src), SkGetPackedR32(dst));
+ int g = srcover_byte(SkGetPackedG32(src), SkGetPackedG32(dst));
+ int b = srcover_byte(SkGetPackedB32(src), SkGetPackedB32(dst));
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kOverlay_Mode
+static inline int overlay_byte(int sc, int dc, int sa, int da) {
+ int tmp = sc * (255 - da) + dc * (255 - sa);
+ int rc;
+ if (2 * dc <= da) {
+ rc = 2 * sc * dc;
+ } else {
+ rc = sa * da - 2 * (da - dc) * (sa - sc);
+ }
+ return clamp_div255round(rc + tmp);
+}
+static SkPMColor overlay_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = overlay_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = overlay_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = overlay_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kDarken_Mode
+static inline int darken_byte(int sc, int dc, int sa, int da) {
+ int sd = sc * da;
+ int ds = dc * sa;
+ if (sd < ds) {
+ // srcover
+ return sc + dc - SkDiv255Round(ds);
+ } else {
+ // dstover
+ return dc + sc - SkDiv255Round(sd);
+ }
+}
+static SkPMColor darken_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = darken_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = darken_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = darken_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kLighten_Mode
+static inline int lighten_byte(int sc, int dc, int sa, int da) {
+ int sd = sc * da;
+ int ds = dc * sa;
+ if (sd > ds) {
+ // srcover
+ return sc + dc - SkDiv255Round(ds);
+ } else {
+ // dstover
+ return dc + sc - SkDiv255Round(sd);
+ }
+}
+static SkPMColor lighten_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = lighten_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = lighten_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = lighten_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kColorDodge_Mode
+static inline int colordodge_byte(int sc, int dc, int sa, int da) {
+ int diff = sa - sc;
+ int rc;
+ if (0 == dc) {
+ return SkAlphaMulAlpha(sc, 255 - da);
+ } else if (0 == diff) {
+ rc = sa * da + sc * (255 - da) + dc * (255 - sa);
+ } else {
+ diff = dc * sa / diff;
+ rc = sa * ((da < diff) ? da : diff) + sc * (255 - da) + dc * (255 - sa);
+ }
+ return clamp_div255round(rc);
+}
+static SkPMColor colordodge_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = colordodge_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = colordodge_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = colordodge_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kColorBurn_Mode
+static inline int colorburn_byte(int sc, int dc, int sa, int da) {
+ int rc;
+ if (dc == da) {
+ rc = sa * da + sc * (255 - da) + dc * (255 - sa);
+ } else if (0 == sc) {
+ return SkAlphaMulAlpha(dc, 255 - sa);
+ } else {
+ int tmp = (da - dc) * sa / sc;
+ rc = sa * (da - ((da < tmp) ? da : tmp))
+ + sc * (255 - da) + dc * (255 - sa);
+ }
+ return clamp_div255round(rc);
+}
+static SkPMColor colorburn_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = colorburn_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = colorburn_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = colorburn_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kHardLight_Mode
+static inline int hardlight_byte(int sc, int dc, int sa, int da) {
+ int rc;
+ if (2 * sc <= sa) {
+ rc = 2 * sc * dc;
+ } else {
+ rc = sa * da - 2 * (da - dc) * (sa - sc);
+ }
+ return clamp_div255round(rc + sc * (255 - da) + dc * (255 - sa));
+}
+static SkPMColor hardlight_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = hardlight_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = hardlight_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = hardlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// returns 255 * sqrt(n/255)
+static U8CPU sqrt_unit_byte(U8CPU n) {
+ return SkSqrtBits(n, 15+4);
+}
+
+// kSoftLight_Mode
+static inline int softlight_byte(int sc, int dc, int sa, int da) {
+ int m = da ? dc * 256 / da : 0;
+ int rc;
+ if (2 * sc <= sa) {
+ rc = dc * (sa + ((2 * sc - sa) * (256 - m) >> 8));
+ } else if (4 * dc <= da) {
+ int tmp = (4 * m * (4 * m + 256) * (m - 256) >> 16) + 7 * m;
+ rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8);
+ } else {
+ int tmp = sqrt_unit_byte(m) - m;
+ rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8);
+ }
+ return clamp_div255round(rc + sc * (255 - da) + dc * (255 - sa));
+}
+static SkPMColor softlight_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = softlight_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = softlight_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = softlight_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kDifference_Mode
+static inline int difference_byte(int sc, int dc, int sa, int da) {
+ int tmp = SkMin32(sc * da, dc * sa);
+ return clamp_signed_byte(sc + dc - 2 * SkDiv255Round(tmp));
+}
+static SkPMColor difference_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = difference_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = difference_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = difference_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kExclusion_Mode
+static inline int exclusion_byte(int sc, int dc, int, int) {
+ // this equations is wacky, wait for SVG to confirm it
+ //int r = sc * da + dc * sa - 2 * sc * dc + sc * (255 - da) + dc * (255 - sa);
+
+ // The above equation can be simplified as follows
+ int r = 255*(sc + dc) - 2 * sc * dc;
+ return clamp_div255round(r);
+}
+static SkPMColor exclusion_modeproc(SkPMColor src, SkPMColor dst) {
+ int sa = SkGetPackedA32(src);
+ int da = SkGetPackedA32(dst);
+ int a = srcover_byte(sa, da);
+ int r = exclusion_byte(SkGetPackedR32(src), SkGetPackedR32(dst), sa, da);
+ int g = exclusion_byte(SkGetPackedG32(src), SkGetPackedG32(dst), sa, da);
+ int b = exclusion_byte(SkGetPackedB32(src), SkGetPackedB32(dst), sa, da);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// The CSS compositing spec introduces the following formulas:
+// (See https://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html#blendingnonseparable)
+// SkComputeLuminance is similar to this formula but it uses the new definition from Rec. 709
+// while PDF and CG uses the one from Rec. Rec. 601
+// See http://www.glennchan.info/articles/technical/hd-versus-sd-color-space/hd-versus-sd-color-space.htm
+static inline int Lum(int r, int g, int b)
+{
+ return SkDiv255Round(r * 77 + g * 150 + b * 28);
+}
+
+static inline int min2(int a, int b) { return a < b ? a : b; }
+static inline int max2(int a, int b) { return a > b ? a : b; }
+#define minimum(a, b, c) min2(min2(a, b), c)
+#define maximum(a, b, c) max2(max2(a, b), c)
+
+static inline int Sat(int r, int g, int b) {
+ return maximum(r, g, b) - minimum(r, g, b);
+}
+
+static inline void setSaturationComponents(int* Cmin, int* Cmid, int* Cmax, int s) {
+ if(*Cmax > *Cmin) {
+ *Cmid = SkMulDiv(*Cmid - *Cmin, s, *Cmax - *Cmin);
+ *Cmax = s;
+ } else {
+ *Cmax = 0;
+ *Cmid = 0;
+ }
+
+ *Cmin = 0;
+}
+
+static inline void SetSat(int* r, int* g, int* b, int s) {
+ if(*r <= *g) {
+ if(*g <= *b) {
+ setSaturationComponents(r, g, b, s);
+ } else if(*r <= *b) {
+ setSaturationComponents(r, b, g, s);
+ } else {
+ setSaturationComponents(b, r, g, s);
+ }
+ } else if(*r <= *b) {
+ setSaturationComponents(g, r, b, s);
+ } else if(*g <= *b) {
+ setSaturationComponents(g, b, r, s);
+ } else {
+ setSaturationComponents(b, g, r, s);
+ }
+}
+
+static inline void clipColor(int* r, int* g, int* b, int a) {
+ int L = Lum(*r, *g, *b);
+ int n = minimum(*r, *g, *b);
+ int x = maximum(*r, *g, *b);
+ if(n < 0) {
+ *r = L + SkMulDiv(*r - L, L, L - n);
+ *g = L + SkMulDiv(*g - L, L, L - n);
+ *b = L + SkMulDiv(*b - L, L, L - n);
+ }
+
+ if (x > a) {
+ *r = L + SkMulDiv(*r - L, a - L, x - L);
+ *g = L + SkMulDiv(*g - L, a - L, x - L);
+ *b = L + SkMulDiv(*b - L, a - L, x - L);
+ }
+}
+
+static inline void SetLum(int* r, int* g, int* b, int a, int l) {
+ int d = l - Lum(*r, *g, *b);
+ *r += d;
+ *g += d;
+ *b += d;
+
+ clipColor(r, g, b, a);
+}
+
+// non-separable blend modes are done in non-premultiplied alpha
+#define blendfunc_nonsep_byte(sc, dc, sa, da, blendval) \
+ clamp_div255round(sc * (255 - da) + dc * (255 - sa) + blendval)
+
+// kHue_Mode
+// B(Cb, Cs) = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))
+// Create a color with the hue of the source color and the saturation and luminosity of the backdrop color.
+static SkPMColor hue_modeproc(SkPMColor src, SkPMColor dst) {
+ int sr = SkGetPackedR32(src);
+ int sg = SkGetPackedG32(src);
+ int sb = SkGetPackedB32(src);
+ int sa = SkGetPackedA32(src);
+
+ int dr = SkGetPackedR32(dst);
+ int dg = SkGetPackedG32(dst);
+ int db = SkGetPackedB32(dst);
+ int da = SkGetPackedA32(dst);
+ int Sr, Sg, Sb;
+
+ if(sa && da) {
+ Sr = sr * sa;
+ Sg = sg * sa;
+ Sb = sb * sa;
+ SetSat(&Sr, &Sg, &Sb, Sat(dr, dg, db) * sa);
+ SetLum(&Sr, &Sg, &Sb, sa * da, Lum(dr, dg, db) * sa);
+ } else {
+ Sr = 0;
+ Sg = 0;
+ Sb = 0;
+ }
+
+ int a = srcover_byte(sa, da);
+ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Sr);
+ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Sg);
+ int b = blendfunc_nonsep_byte(sb, db, sa, da, Sb);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kSaturation_Mode
+// B(Cb, Cs) = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))
+// Create a color with the saturation of the source color and the hue and luminosity of the backdrop color.
+static SkPMColor saturation_modeproc(SkPMColor src, SkPMColor dst) {
+ int sr = SkGetPackedR32(src);
+ int sg = SkGetPackedG32(src);
+ int sb = SkGetPackedB32(src);
+ int sa = SkGetPackedA32(src);
+
+ int dr = SkGetPackedR32(dst);
+ int dg = SkGetPackedG32(dst);
+ int db = SkGetPackedB32(dst);
+ int da = SkGetPackedA32(dst);
+ int Dr, Dg, Db;
+
+ if(sa && da) {
+ Dr = dr * sa;
+ Dg = dg * sa;
+ Db = db * sa;
+ SetSat(&Dr, &Dg, &Db, Sat(sr, sg, sb) * da);
+ SetLum(&Dr, &Dg, &Db, sa * da, Lum(dr, dg, db) * sa);
+ } else {
+ Dr = 0;
+ Dg = 0;
+ Db = 0;
+ }
+
+ int a = srcover_byte(sa, da);
+ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Dr);
+ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Dg);
+ int b = blendfunc_nonsep_byte(sb, db, sa, da, Db);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kColor_Mode
+// B(Cb, Cs) = SetLum(Cs, Lum(Cb))
+// Create a color with the hue and saturation of the source color and the luminosity of the backdrop color.
+static SkPMColor color_modeproc(SkPMColor src, SkPMColor dst) {
+ int sr = SkGetPackedR32(src);
+ int sg = SkGetPackedG32(src);
+ int sb = SkGetPackedB32(src);
+ int sa = SkGetPackedA32(src);
+
+ int dr = SkGetPackedR32(dst);
+ int dg = SkGetPackedG32(dst);
+ int db = SkGetPackedB32(dst);
+ int da = SkGetPackedA32(dst);
+ int Sr, Sg, Sb;
+
+ if(sa && da) {
+ Sr = sr * da;
+ Sg = sg * da;
+ Sb = sb * da;
+ SetLum(&Sr, &Sg, &Sb, sa * da, Lum(dr, dg, db) * sa);
+ } else {
+ Sr = 0;
+ Sg = 0;
+ Sb = 0;
+ }
+
+ int a = srcover_byte(sa, da);
+ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Sr);
+ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Sg);
+ int b = blendfunc_nonsep_byte(sb, db, sa, da, Sb);
+ return SkPackARGB32(a, r, g, b);
+}
+
+// kLuminosity_Mode
+// B(Cb, Cs) = SetLum(Cb, Lum(Cs))
+// Create a color with the luminosity of the source color and the hue and saturation of the backdrop color.
+static SkPMColor luminosity_modeproc(SkPMColor src, SkPMColor dst) {
+ int sr = SkGetPackedR32(src);
+ int sg = SkGetPackedG32(src);
+ int sb = SkGetPackedB32(src);
+ int sa = SkGetPackedA32(src);
+
+ int dr = SkGetPackedR32(dst);
+ int dg = SkGetPackedG32(dst);
+ int db = SkGetPackedB32(dst);
+ int da = SkGetPackedA32(dst);
+ int Dr, Dg, Db;
+
+ if(sa && da) {
+ Dr = dr * sa;
+ Dg = dg * sa;
+ Db = db * sa;
+ SetLum(&Dr, &Dg, &Db, sa * da, Lum(sr, sg, sb) * da);
+ } else {
+ Dr = 0;
+ Dg = 0;
+ Db = 0;
+ }
+
+ int a = srcover_byte(sa, da);
+ int r = blendfunc_nonsep_byte(sr, dr, sa, da, Dr);
+ int g = blendfunc_nonsep_byte(sg, dg, sa, da, Dg);
+ int b = blendfunc_nonsep_byte(sb, db, sa, da, Db);
+ return SkPackARGB32(a, r, g, b);
+}
+
+
+struct ProcCoeff {
+ SkXfermodeProc fProc;
+ SkXfermode::Coeff fSC;
+ SkXfermode::Coeff fDC;
+};
+
+#define CANNOT_USE_COEFF SkXfermode::Coeff(-1)
+
+static const ProcCoeff gProcCoeffs[] = {
+ { clear_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kZero_Coeff },
+ { src_modeproc, SkXfermode::kOne_Coeff, SkXfermode::kZero_Coeff },
+ { dst_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kOne_Coeff },
+ { srcover_modeproc, SkXfermode::kOne_Coeff, SkXfermode::kISA_Coeff },
+ { dstover_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kOne_Coeff },
+ { srcin_modeproc, SkXfermode::kDA_Coeff, SkXfermode::kZero_Coeff },
+ { dstin_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kSA_Coeff },
+ { srcout_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kZero_Coeff },
+ { dstout_modeproc, SkXfermode::kZero_Coeff, SkXfermode::kISA_Coeff },
+ { srcatop_modeproc, SkXfermode::kDA_Coeff, SkXfermode::kISA_Coeff },
+ { dstatop_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kSA_Coeff },
+ { xor_modeproc, SkXfermode::kIDA_Coeff, SkXfermode::kISA_Coeff },
+
+ { plus_modeproc, SkXfermode::kOne_Coeff, SkXfermode::kOne_Coeff },
+ { modulate_modeproc,SkXfermode::kZero_Coeff, SkXfermode::kSC_Coeff },
+ { screen_modeproc, SkXfermode::kOne_Coeff, SkXfermode::kISC_Coeff },
+ { overlay_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { darken_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { lighten_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { colordodge_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { colorburn_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { hardlight_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { softlight_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { difference_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { exclusion_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { multiply_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { hue_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { saturation_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { color_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+ { luminosity_modeproc, CANNOT_USE_COEFF, CANNOT_USE_COEFF },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkXfermode::asCoeff(Coeff* src, Coeff* dst) const {
+ return false;
+}
+
+bool SkXfermode::asMode(Mode* mode) const {
+ return false;
+}
+
+bool SkXfermode::asNewEffectOrCoeff(GrContext*, GrEffectRef**, Coeff* src, Coeff* dst, GrTexture*) const {
+ return this->asCoeff(src, dst);
+}
+
+bool SkXfermode::AsNewEffectOrCoeff(SkXfermode* xfermode,
+ GrContext* context,
+ GrEffectRef** effect,
+ Coeff* src,
+ Coeff* dst,
+ GrTexture* background) {
+ if (NULL == xfermode) {
+ return ModeAsCoeff(kSrcOver_Mode, src, dst);
+ } else {
+ return xfermode->asNewEffectOrCoeff(context, effect, src, dst, background);
+ }
+}
+
+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 {
+ SkASSERT(dst && src && count >= 0);
+
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ dst[i] = this->xferColor(src[i], dst[i]);
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkPMColor dstC = dst[i];
+ SkPMColor C = this->xferColor(src[i], dstC);
+ if (0xFF != a) {
+ C = SkFourByteInterp(C, dstC, a);
+ }
+ dst[i] = C;
+ }
+ }
+ }
+}
+
+void SkXfermode::xfer16(uint16_t* dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ dst[i] = SkPixel32ToPixel16_ToU16(this->xferColor(src[i], dstC));
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor C = this->xferColor(src[i], dstC);
+ if (0xFF != a) {
+ C = SkFourByteInterp(C, dstC, a);
+ }
+ dst[i] = SkPixel32ToPixel16_ToU16(C);
+ }
+ }
+ }
+}
+
+void SkXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
+ const SkPMColor src[], int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ SkPMColor res = this->xferColor(src[i], (dst[i] << SK_A32_SHIFT));
+ dst[i] = SkToU8(SkGetPackedA32(res));
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkAlpha dstA = dst[i];
+ unsigned A = SkGetPackedA32(this->xferColor(src[i],
+ (SkPMColor)(dstA << SK_A32_SHIFT)));
+ if (0xFF != a) {
+ A = SkAlphaBlend(A, dstA, SkAlpha255To256(a));
+ }
+ dst[i] = SkToU8(A);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkProcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ SkXfermodeProc proc = fProc;
+
+ if (NULL != proc) {
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ dst[i] = proc(src[i], dst[i]);
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkPMColor dstC = dst[i];
+ SkPMColor C = proc(src[i], dstC);
+ if (a != 0xFF) {
+ C = SkFourByteInterp(C, dstC, a);
+ }
+ dst[i] = C;
+ }
+ }
+ }
+ }
+}
+
+void SkProcXfermode::xfer16(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ SkXfermodeProc proc = fProc;
+
+ if (NULL != proc) {
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ dst[i] = SkPixel32ToPixel16_ToU16(proc(src[i], dstC));
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor C = proc(src[i], dstC);
+ if (0xFF != a) {
+ C = SkFourByteInterp(C, dstC, a);
+ }
+ dst[i] = SkPixel32ToPixel16_ToU16(C);
+ }
+ }
+ }
+ }
+}
+
+void SkProcXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ SkXfermodeProc proc = fProc;
+
+ if (NULL != proc) {
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ SkPMColor res = proc(src[i], dst[i] << SK_A32_SHIFT);
+ dst[i] = SkToU8(SkGetPackedA32(res));
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ SkAlpha dstA = dst[i];
+ SkPMColor res = proc(src[i], dstA << SK_A32_SHIFT);
+ unsigned A = SkGetPackedA32(res);
+ if (0xFF != a) {
+ A = SkAlphaBlend(A, dstA, SkAlpha255To256(a));
+ }
+ dst[i] = SkToU8(A);
+ }
+ }
+ }
+ }
+}
+
+SkProcXfermode::SkProcXfermode(SkFlattenableReadBuffer& buffer)
+ : SkXfermode(buffer) {
+ fProc = NULL;
+ if (!buffer.isCrossProcess()) {
+ fProc = (SkXfermodeProc)buffer.readFunctionPtr();
+ }
+}
+
+void SkProcXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ if (!buffer.isCrossProcess()) {
+ buffer.writeFunctionPtr((void*)fProc);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkProcXfermode::toString(SkString* str) const {
+ str->appendf("SkProcXfermode: %p", fProc);
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrEffect.h"
+#include "GrEffectUnitTest.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+
+/**
+ * GrEffect that implements the all the separable xfer modes that cannot be expressed as Coeffs.
+ */
+class XferEffect : public GrEffect {
+public:
+ static bool IsSupportedMode(SkXfermode::Mode mode) {
+ return mode > SkXfermode::kLastCoeffMode && mode <= SkXfermode::kLastMode;
+ }
+
+ static GrEffectRef* Create(SkXfermode::Mode mode, GrTexture* background) {
+ if (!IsSupportedMode(mode)) {
+ return NULL;
+ } else {
+ AutoEffectUnref effect(SkNEW_ARGS(XferEffect, (mode, background)));
+ return CreateEffectRef(effect);
+ }
+ }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<XferEffect>::getInstance();
+ }
+
+ static const char* Name() { return "XferEffect"; }
+
+ SkXfermode::Mode mode() const { return fMode; }
+ const GrTextureAccess& backgroundAccess() const { return fBackgroundAccess; }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : GrGLEffect(factory )
+ , fBackgroundEffectMatrix(kCoordsType) {
+ }
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ SkXfermode::Mode mode = drawEffect.castEffect<XferEffect>().mode();
+ const GrTexture* backgroundTex = drawEffect.castEffect<XferEffect>().backgroundAccess().getTexture();
+ const char* dstColor;
+ if (backgroundTex) {
+ const char* bgCoords;
+ GrSLType bgCoordsType = fBackgroundEffectMatrix.emitCode(builder, key, &bgCoords, NULL, "BG");
+ dstColor = "bgColor";
+ builder->fsCodeAppendf("\t\tvec4 %s = ", dstColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ bgCoords,
+ bgCoordsType);
+ builder->fsCodeAppendf(";\n");
+ } else {
+ dstColor = builder->dstColor();
+ }
+ GrAssert(NULL != dstColor);
+
+ // We don't try to optimize for this case at all
+ if (NULL == inputColor) {
+ builder->fsCodeAppendf("\t\tconst vec4 ones = %s;\n", GrGLSLOnesVecf(4));
+ inputColor = "ones";
+ }
+ builder->fsCodeAppendf("\t\t// SkXfermode::Mode: %s\n", SkXfermode::ModeName(mode));
+
+ // These all perform src-over on the alpha channel.
+ builder->fsCodeAppendf("\t\t%s.a = %s.a + (1.0 - %s.a) * %s.a;\n",
+ outputColor, inputColor, inputColor, dstColor);
+
+ switch (mode) {
+ case SkXfermode::kOverlay_Mode:
+ // Overlay is Hard-Light with the src and dst reversed
+ HardLight(builder, outputColor, dstColor, inputColor);
+ break;
+ case SkXfermode::kDarken_Mode:
+ builder->fsCodeAppendf("\t\t%s.rgb = min((1.0 - %s.a) * %s.rgb + %s.rgb, "
+ "(1.0 - %s.a) * %s.rgb + %s.rgb);\n",
+ outputColor,
+ inputColor, dstColor, inputColor,
+ dstColor, inputColor, dstColor);
+ break;
+ case SkXfermode::kLighten_Mode:
+ builder->fsCodeAppendf("\t\t%s.rgb = max((1.0 - %s.a) * %s.rgb + %s.rgb, "
+ "(1.0 - %s.a) * %s.rgb + %s.rgb);\n",
+ outputColor,
+ inputColor, dstColor, inputColor,
+ dstColor, inputColor, dstColor);
+ break;
+ case SkXfermode::kColorDodge_Mode:
+ ColorDodgeComponent(builder, outputColor, inputColor, dstColor, 'r');
+ ColorDodgeComponent(builder, outputColor, inputColor, dstColor, 'g');
+ ColorDodgeComponent(builder, outputColor, inputColor, dstColor, 'b');
+ break;
+ case SkXfermode::kColorBurn_Mode:
+ ColorBurnComponent(builder, outputColor, inputColor, dstColor, 'r');
+ ColorBurnComponent(builder, outputColor, inputColor, dstColor, 'g');
+ ColorBurnComponent(builder, outputColor, inputColor, dstColor, 'b');
+ break;
+ case SkXfermode::kHardLight_Mode:
+ HardLight(builder, outputColor, inputColor, dstColor);
+ break;
+ case SkXfermode::kSoftLight_Mode:
+ builder->fsCodeAppendf("\t\tif (0.0 == %s.a) {\n", dstColor);
+ builder->fsCodeAppendf("\t\t\t%s.rgba = %s;\n", outputColor, inputColor);
+ builder->fsCodeAppendf("\t\t} else {\n");
+ SoftLightComponentPosDstAlpha(builder, outputColor, inputColor, dstColor, 'r');
+ SoftLightComponentPosDstAlpha(builder, outputColor, inputColor, dstColor, 'g');
+ SoftLightComponentPosDstAlpha(builder, outputColor, inputColor, dstColor, 'b');
+ builder->fsCodeAppendf("\t\t}\n");
+ break;
+ case SkXfermode::kDifference_Mode:
+ builder->fsCodeAppendf("\t\t%s.rgb = %s.rgb + %s.rgb -"
+ "2.0 * min(%s.rgb * %s.a, %s.rgb * %s.a);\n",
+ outputColor, inputColor, dstColor, inputColor, dstColor,
+ dstColor, inputColor);
+ break;
+ case SkXfermode::kExclusion_Mode:
+ builder->fsCodeAppendf("\t\t%s.rgb = %s.rgb + %s.rgb - "
+ "2.0 * %s.rgb * %s.rgb;\n",
+ outputColor, dstColor, inputColor, dstColor, inputColor);
+ break;
+ case SkXfermode::kMultiply_Mode:
+ builder->fsCodeAppendf("\t\t%s.rgb = (1.0 - %s.a) * %s.rgb + "
+ "(1.0 - %s.a) * %s.rgb + "
+ "%s.rgb * %s.rgb;\n",
+ outputColor, inputColor, dstColor, dstColor, inputColor,
+ inputColor, dstColor);
+ break;
+ case SkXfermode::kHue_Mode: {
+ // SetLum(SetSat(S * Da, Sat(D * Sa)), Sa*Da, D*Sa) + (1 - Sa) * D + (1 - Da) * S
+ SkString setSat, setLum;
+ AddSatFunction(builder, &setSat);
+ AddLumFunction(builder, &setLum);
+ builder->fsCodeAppendf("\t\tvec4 dstSrcAlpha = %s * %s.a;\n",
+ dstColor, inputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = %s(%s(%s.rgb * %s.a, dstSrcAlpha.rgb), dstSrcAlpha.a, dstSrcAlpha.rgb);\n",
+ outputColor, setLum.c_str(), setSat.c_str(), inputColor,
+ dstColor);
+ builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n",
+ outputColor, inputColor, dstColor, dstColor, inputColor);
+ break;
+ }
+ case SkXfermode::kSaturation_Mode: {
+ // SetLum(SetSat(D * Sa, Sat(S * Da)), Sa*Da, D*Sa)) + (1 - Sa) * D + (1 - Da) * S
+ SkString setSat, setLum;
+ AddSatFunction(builder, &setSat);
+ AddLumFunction(builder, &setLum);
+ builder->fsCodeAppendf("\t\tvec4 dstSrcAlpha = %s * %s.a;\n",
+ dstColor, inputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = %s(%s(dstSrcAlpha.rgb, %s.rgb * %s.a), dstSrcAlpha.a, dstSrcAlpha.rgb);\n",
+ outputColor, setLum.c_str(), setSat.c_str(), inputColor,
+ dstColor);
+ builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n",
+ outputColor, inputColor, dstColor, dstColor, inputColor);
+ break;
+ }
+ case SkXfermode::kColor_Mode: {
+ // SetLum(S * Da, Sa* Da, D * Sa) + (1 - Sa) * D + (1 - Da) * S
+ SkString setLum;
+ AddLumFunction(builder, &setLum);
+ builder->fsCodeAppendf("\t\tvec4 srcDstAlpha = %s * %s.a;\n",
+ inputColor, dstColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = %s(srcDstAlpha.rgb, srcDstAlpha.a, %s.rgb * %s.a);\n",
+ outputColor, setLum.c_str(), dstColor, inputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n",
+ outputColor, inputColor, dstColor, dstColor, inputColor);
+ break;
+ }
+ case SkXfermode::kLuminosity_Mode: {
+ // SetLum(D * Sa, Sa* Da, S * Da) + (1 - Sa) * D + (1 - Da) * S
+ SkString setLum;
+ AddLumFunction(builder, &setLum);
+ builder->fsCodeAppendf("\t\tvec4 srcDstAlpha = %s * %s.a;\n",
+ inputColor, dstColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = %s(%s.rgb * %s.a, srcDstAlpha.a, srcDstAlpha.rgb);\n",
+ outputColor, setLum.c_str(), dstColor, inputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n",
+ outputColor, inputColor, dstColor, dstColor, inputColor);
+ break;
+ }
+ default:
+ GrCrash("Unknown XferEffect mode.");
+ break;
+ }
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const XferEffect& xfer = drawEffect.castEffect<XferEffect>();
+ GrTexture* bgTex = xfer.backgroundAccess().getTexture();
+ EffectKey bgKey = 0;
+ if (bgTex) {
+ bgKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ GLEffect::kCoordsType,
+ bgTex);
+ }
+ EffectKey modeKey = xfer.mode() << GrGLEffectMatrix::kKeyBits;
+ return modeKey | bgKey;
+ }
+
+ virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) SK_OVERRIDE {
+ const XferEffect& xfer = drawEffect.castEffect<XferEffect>();
+ GrTexture* bgTex = xfer.backgroundAccess().getTexture();
+ if (bgTex) {
+ fBackgroundEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ bgTex);
+ }
+ }
+
+ private:
+ static void HardLight(GrGLShaderBuilder* builder,
+ const char* final,
+ const char* src,
+ const char* dst) {
+ static const char kComponents[] = {'r', 'g', 'b'};
+ for (size_t i = 0; i < SK_ARRAY_COUNT(kComponents); ++i) {
+ char component = kComponents[i];
+ builder->fsCodeAppendf("\t\tif (2.0 * %s.%c <= %s.a) {\n", src, component, src);
+ builder->fsCodeAppendf("\t\t\t%s.%c = 2.0 * %s.%c * %s.%c;\n", final, component, src, component, dst, component);
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\t%s.%c = %s.a * %s.a - 2.0 * (%s.a - %s.%c) * (%s.a - %s.%c);\n",
+ final, component, src, dst, dst, dst, component, src, src, component);
+ builder->fsCodeAppend("\t\t}\n");
+ }
+ builder->fsCodeAppendf("\t\t%s.rgb += %s.rgb * (1.0 - %s.a) + %s.rgb * (1.0 - %s.a);\n",
+ final, src, dst, dst, src);
+ }
+
+ // Does one component of color-dodge
+ static void ColorDodgeComponent(GrGLShaderBuilder* builder,
+ const char* final,
+ const char* src,
+ const char* dst,
+ const char component) {
+ builder->fsCodeAppendf("\t\tif (0.0 == %s.%c) {\n", dst, component);
+ builder->fsCodeAppendf("\t\t\t%s.%c = %s.%c * (1.0 - %s.a);\n",
+ final, component, src, component, dst);
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\tfloat d = %s.a - %s.%c;\n", src, src, component);
+ builder->fsCodeAppend("\t\t\tif (0.0 == d) {\n");
+ builder->fsCodeAppendf("\t\t\t\t%s.%c = %s.a * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);\n",
+ final, component, src, dst, src, component, dst, dst, component,
+ src);
+ builder->fsCodeAppend("\t\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\t\td = min(%s.a, %s.%c * %s.a / d);\n",
+ dst, dst, component, src);
+ builder->fsCodeAppendf("\t\t\t\t%s.%c = d * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);\n",
+ final, component, src, src, component, dst, dst, component, src);
+ builder->fsCodeAppend("\t\t\t}\n");
+ builder->fsCodeAppend("\t\t}\n");
+ }
+
+ // Does one component of color-burn
+ static void ColorBurnComponent(GrGLShaderBuilder* builder,
+ const char* final,
+ const char* src,
+ const char* dst,
+ const char component) {
+ builder->fsCodeAppendf("\t\tif (%s.a == %s.%c) {\n", dst, dst, component);
+ builder->fsCodeAppendf("\t\t\t%s.%c = %s.a * %s.a + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);\n",
+ final, component, src, dst, src, component, dst, dst, component,
+ src);
+ builder->fsCodeAppendf("\t\t} else if (0.0 == %s.%c) {\n", src, component);
+ builder->fsCodeAppendf("\t\t\t%s.%c = %s.%c * (1.0 - %s.a);\n",
+ final, component, dst, component, src);
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\tfloat d = max(0.0, %s.a - (%s.a - %s.%c) * %s.a / %s.%c);\n",
+ dst, dst, dst, component, src, src, component);
+ builder->fsCodeAppendf("\t\t\t%s.%c = %s.a * d + %s.%c * (1.0 - %s.a) + %s.%c * (1.0 - %s.a);\n",
+ final, component, src, src, component, dst, dst, component, src);
+ builder->fsCodeAppend("\t\t}\n");
+ }
+
+ // Does one component of soft-light. Caller should have already checked that dst alpha > 0.
+ static void SoftLightComponentPosDstAlpha(GrGLShaderBuilder* builder,
+ const char* final,
+ const char* src,
+ const char* dst,
+ const char component) {
+ // if (2S < Sa)
+ builder->fsCodeAppendf("\t\t\tif (2.0 * %s.%c <= %s.a) {\n", src, component, src);
+ // (D^2 (Sa-2 S))/Da+(1-Da) S+D (-Sa+2 S+1)
+ builder->fsCodeAppendf("\t\t\t\t%s.%c = (%s.%c*%s.%c*(%s.a - 2.0*%s.%c)) / %s.a + (1.0 - %s.a) * %s.%c + %s.%c*(-%s.a + 2.0*%s.%c + 1.0);\n",
+ final, component, dst, component, dst, component, src, src,
+ component, dst, dst, src, component, dst, component, src, src,
+ component);
+ // else if (4D < Da)
+ builder->fsCodeAppendf("\t\t\t} else if (4.0 * %s.%c <= %s.a) {\n",
+ dst, component, dst);
+ builder->fsCodeAppendf("\t\t\t\tfloat DSqd = %s.%c * %s.%c;\n",
+ dst, component, dst, component);
+ builder->fsCodeAppendf("\t\t\t\tfloat DCub = DSqd * %s.%c;\n", dst, component);
+ builder->fsCodeAppendf("\t\t\t\tfloat DaSqd = %s.a * %s.a;\n", dst, dst);
+ builder->fsCodeAppendf("\t\t\t\tfloat DaCub = DaSqd * %s.a;\n", dst);
+ // (Da^3 (-S)+Da^2 (S-D (3 Sa-6 S-1))+12 Da D^2 (Sa-2 S)-16 D^3 (Sa-2 S))/Da^2
+ builder->fsCodeAppendf("\t\t\t\t%s.%c = (-DaCub*%s.%c + DaSqd*(%s.%c - %s.%c * (3.0*%s.a - 6.0*%s.%c - 1.0)) + 12.0*%s.a*DSqd*(%s.a - 2.0*%s.%c) - 16.0*DCub * (%s.a - 2.0*%s.%c)) / DaSqd;\n",
+ final, component, src, component, src, component, dst, component,
+ src, src, component, dst, src, src, component, src, src,
+ component);
+ builder->fsCodeAppendf("\t\t\t} else {\n");
+ // -sqrt(Da * D) (Sa-2 S)-Da S+D (Sa-2 S+1)+S
+ builder->fsCodeAppendf("\t\t\t\t%s.%c = -sqrt(%s.a*%s.%c)*(%s.a - 2.0*%s.%c) - %s.a*%s.%c + %s.%c*(%s.a - 2.0*%s.%c + 1.0) + %s.%c;\n",
+ final, component, dst, dst, component, src, src, component, dst,
+ src, component, dst, component, src, src, component, src,
+ component);
+ builder->fsCodeAppendf("\t\t\t}\n");
+ }
+
+ // Adds a function that takes two colors and an alpha as input. It produces a color with the
+ // hue and saturation of the first color, the luminosity of the second color, and the input
+ // alpha. It has this signature:
+ // vec3 set_luminance(vec3 hueSatColor, float alpha, vec3 lumColor).
+ static void AddLumFunction(GrGLShaderBuilder* builder, SkString* setLumFunction) {
+ // Emit a helper that gets the luminance of a color.
+ SkString getFunction;
+ GrGLShaderVar getLumArgs[] = {
+ GrGLShaderVar("color", kVec3f_GrSLType),
+ };
+ SkString getLumBody("\treturn dot(vec3(0.3, 0.59, 0.11), color);\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "luminance",
+ SK_ARRAY_COUNT(getLumArgs), getLumArgs,
+ getLumBody.c_str(),
+ &getFunction);
+
+ // Emit the set luminance function.
+ GrGLShaderVar setLumArgs[] = {
+ GrGLShaderVar("hueSat", kVec3f_GrSLType),
+ GrGLShaderVar("alpha", kFloat_GrSLType),
+ GrGLShaderVar("lumColor", kVec3f_GrSLType),
+ };
+ SkString setLumBody;
+ setLumBody.printf("\tfloat diff = %s(lumColor - hueSat);\n", getFunction.c_str());
+ setLumBody.append("\tvec3 outColor = hueSat + diff;\n");
+ setLumBody.appendf("\tfloat outLum = %s(outColor);\n", getFunction.c_str());
+ setLumBody.append("\tfloat minComp = min(min(outColor.r, outColor.g), outColor.b);\n"
+ "\tfloat maxComp = max(max(outColor.r, outColor.g), outColor.b);\n"
+ "\tif (minComp < 0.0) {\n"
+ "\t\toutColor = outLum + ((outColor - vec3(outLum, outLum, outLum)) * outLum) / (outLum - minComp);\n"
+ "\t}\n"
+ "\tif (maxComp > alpha) {\n"
+ "\t\toutColor = outLum + ((outColor - vec3(outLum, outLum, outLum)) * (alpha - outLum)) / (maxComp - outLum);\n"
+ "\t}\n"
+ "\treturn outColor;\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "set_luminance",
+ SK_ARRAY_COUNT(setLumArgs), setLumArgs,
+ setLumBody.c_str(),
+ setLumFunction);
+ }
+
+ // Adds a function that creates a color with the hue and luminosity of one input color and
+ // the saturation of another color. It will have this signature:
+ // float set_saturation(vec3 hueLumColor, vec3 satColor)
+ static void AddSatFunction(GrGLShaderBuilder* builder, SkString* setSatFunction) {
+ // Emit a helper that gets the saturation of a color
+ SkString getFunction;
+ GrGLShaderVar getSatArgs[] = { GrGLShaderVar("color", kVec3f_GrSLType) };
+ SkString getSatBody;
+ getSatBody.printf("\treturn max(max(color.r, color.g), color.b) - "
+ "min(min(color.r, color.g), color.b);\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "saturation",
+ SK_ARRAY_COUNT(getSatArgs), getSatArgs,
+ getSatBody.c_str(),
+ &getFunction);
+
+ // Emit a helper that sets the saturation given sorted input channels. This used
+ // to use inout params for min, mid, and max components but that seems to cause
+ // problems on PowerVR drivers. So instead it returns a vec3 where r, g ,b are the
+ // adjusted min, mid, and max inputs, respectively.
+ SkString helperFunction;
+ GrGLShaderVar helperArgs[] = {
+ GrGLShaderVar("minComp", kFloat_GrSLType),
+ GrGLShaderVar("midComp", kFloat_GrSLType),
+ GrGLShaderVar("maxComp", kFloat_GrSLType),
+ GrGLShaderVar("sat", kFloat_GrSLType),
+ };
+ static const char kHelperBody[] = "\tif (minComp < maxComp) {\n"
+ "\t\tvec3 result;\n"
+ "\t\tresult.r = 0.0;\n"
+ "\t\tresult.g = sat * (midComp - minComp) / (maxComp - minComp);\n"
+ "\t\tresult.b = sat;\n"
+ "\t\treturn result;\n"
+ "\t} else {\n"
+ "\t\treturn vec3(0, 0, 0);\n"
+ "\t}\n";
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "set_saturation_helper",
+ SK_ARRAY_COUNT(helperArgs), helperArgs,
+ kHelperBody,
+ &helperFunction);
+
+ GrGLShaderVar setSatArgs[] = {
+ GrGLShaderVar("hueLumColor", kVec3f_GrSLType),
+ GrGLShaderVar("satColor", kVec3f_GrSLType),
+ };
+ const char* helpFunc = helperFunction.c_str();
+ SkString setSatBody;
+ setSatBody.appendf("\tfloat sat = %s(satColor);\n"
+ "\tif (hueLumColor.r <= hueLumColor.g) {\n"
+ "\t\tif (hueLumColor.g <= hueLumColor.b) {\n"
+ "\t\t\thueLumColor.rgb = %s(hueLumColor.r, hueLumColor.g, hueLumColor.b, sat);\n"
+ "\t\t} else if (hueLumColor.r <= hueLumColor.b) {\n"
+ "\t\t\thueLumColor.rbg = %s(hueLumColor.r, hueLumColor.b, hueLumColor.g, sat);\n"
+ "\t\t} else {\n"
+ "\t\t\thueLumColor.brg = %s(hueLumColor.b, hueLumColor.r, hueLumColor.g, sat);\n"
+ "\t\t}\n"
+ "\t} else if (hueLumColor.r <= hueLumColor.b) {\n"
+ "\t\thueLumColor.grb = %s(hueLumColor.g, hueLumColor.r, hueLumColor.b, sat);\n"
+ "\t} else if (hueLumColor.g <= hueLumColor.b) {\n"
+ "\t\thueLumColor.gbr = %s(hueLumColor.g, hueLumColor.b, hueLumColor.r, sat);\n"
+ "\t} else {\n"
+ "\t\thueLumColor.bgr = %s(hueLumColor.b, hueLumColor.g, hueLumColor.r, sat);\n"
+ "\t}\n"
+ "\treturn hueLumColor;\n",
+ getFunction.c_str(), helpFunc, helpFunc, helpFunc, helpFunc,
+ helpFunc, helpFunc);
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "set_saturation",
+ SK_ARRAY_COUNT(setSatArgs), setSatArgs,
+ setSatBody.c_str(),
+ setSatFunction);
+
+ }
+
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+ GrGLEffectMatrix fBackgroundEffectMatrix;
+ typedef GrGLEffect INHERITED;
+ };
+
+ GR_DECLARE_EFFECT_TEST;
+
+private:
+ XferEffect(SkXfermode::Mode mode, GrTexture* background)
+ : fMode(mode) {
+ if (background) {
+ fBackgroundAccess.reset(background);
+ this->addTextureAccess(&fBackgroundAccess);
+ } else {
+ this->setWillReadDstColor();
+ }
+ }
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ const XferEffect& s = CastEffect<XferEffect>(other);
+ return fMode == s.fMode &&
+ fBackgroundAccess.getTexture() == s.fBackgroundAccess.getTexture();
+ }
+
+ SkXfermode::Mode fMode;
+ GrTextureAccess fBackgroundAccess;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(XferEffect);
+GrEffectRef* XferEffect::TestCreate(SkMWCRandom* rand,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture*[]) {
+ int mode = rand->nextRangeU(SkXfermode::kLastCoeffMode + 1, SkXfermode::kLastSeparableMode);
+
+ static AutoEffectUnref gEffect(SkNEW_ARGS(XferEffect, (static_cast<SkXfermode::Mode>(mode), NULL)));
+ return CreateEffectRef(gEffect);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+class SkProcCoeffXfermode : public SkProcXfermode {
+public:
+ SkProcCoeffXfermode(const ProcCoeff& rec, Mode mode)
+ : INHERITED(rec.fProc) {
+ fMode = mode;
+ // these may be valid, or may be CANNOT_USE_COEFF
+ fSrcCoeff = rec.fSC;
+ fDstCoeff = rec.fDC;
+ }
+
+ virtual bool asMode(Mode* mode) const SK_OVERRIDE {
+ if (mode) {
+ *mode = fMode;
+ }
+ return true;
+ }
+
+ virtual bool asCoeff(Coeff* sc, Coeff* dc) const SK_OVERRIDE {
+ if (CANNOT_USE_COEFF == fSrcCoeff) {
+ return false;
+ }
+
+ if (sc) {
+ *sc = fSrcCoeff;
+ }
+ if (dc) {
+ *dc = fDstCoeff;
+ }
+ return true;
+ }
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffectOrCoeff(GrContext*,
+ GrEffectRef** effect,
+ Coeff* src,
+ Coeff* dst,
+ GrTexture* background) const SK_OVERRIDE {
+ if (this->asCoeff(src, dst)) {
+ return true;
+ }
+ if (XferEffect::IsSupportedMode(fMode)) {
+ if (NULL != effect) {
+ *effect = XferEffect::Create(fMode, background);
+ SkASSERT(NULL != *effect);
+ }
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkProcCoeffXfermode)
+
+protected:
+ SkProcCoeffXfermode(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fMode = (SkXfermode::Mode)buffer.read32();
+
+ const ProcCoeff& rec = gProcCoeffs[fMode];
+ // these may be valid, or may be CANNOT_USE_COEFF
+ fSrcCoeff = rec.fSC;
+ fDstCoeff = rec.fDC;
+ // now update our function-ptr in the super class
+ this->INHERITED::setProc(rec.fProc);
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.write32(fMode);
+ }
+
+private:
+ Mode fMode;
+ Coeff fSrcCoeff, fDstCoeff;
+
+ typedef SkProcXfermode INHERITED;
+};
+
+const char* SkXfermode::ModeName(Mode mode) {
+ SkASSERT((unsigned) mode <= (unsigned)kLastMode);
+ const char* gModeStrings[] = {
+ "Clear", "Src", "Dst", "SrcOver", "DstOver", "SrcIn", "DstIn",
+ "SrcOut", "DstOut", "SrcATop", "DstATop", "Xor", "Plus",
+ "Modulate", "Screen", "Overlay", "Darken", "Lighten", "ColorDodge",
+ "ColorBurn", "HardLight", "SoftLight", "Difference", "Exclusion",
+ "Multiply", "Hue", "Saturation", "Color", "Luminosity"
+ };
+ return gModeStrings[mode];
+ SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gModeStrings) == kLastMode + 1, mode_count);
+}
+
+#ifdef SK_DEVELOPER
+void SkProcCoeffXfermode::toString(SkString* str) const {
+ str->append("SkProcCoeffXfermode: ");
+
+ str->append("mode: ");
+ str->append(ModeName(fMode));
+
+ static const char* gCoeffStrings[kCoeffCount] = {
+ "Zero", "One", "SC", "ISC", "DC", "IDC", "SA", "ISA", "DA", "IDA"
+ };
+
+ str->append(" src: ");
+ if (CANNOT_USE_COEFF == fSrcCoeff) {
+ str->append("can't use");
+ } else {
+ str->append(gCoeffStrings[fSrcCoeff]);
+ }
+
+ str->append(" dst: ");
+ if (CANNOT_USE_COEFF == fDstCoeff) {
+ str->append("can't use");
+ } else {
+ str->append(gCoeffStrings[fDstCoeff]);
+ }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkClearXfermode : public SkProcCoeffXfermode {
+public:
+ SkClearXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kClear_Mode) {}
+
+ 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_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkClearXfermode)
+
+private:
+ SkClearXfermode(SkFlattenableReadBuffer& buffer)
+ : SkProcCoeffXfermode(buffer) {}
+
+ typedef SkProcCoeffXfermode INHERITED;
+};
+
+void SkClearXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && count >= 0);
+
+ if (NULL == aa) {
+ memset(dst, 0, count << 2);
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0xFF == a) {
+ dst[i] = 0;
+ } else if (a != 0) {
+ dst[i] = SkAlphaMulQ(dst[i], SkAlpha255To256(255 - a));
+ }
+ }
+ }
+}
+void SkClearXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && count >= 0);
+
+ if (NULL == aa) {
+ memset(dst, 0, count);
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0xFF == a) {
+ dst[i] = 0;
+ } else if (0 != a) {
+ dst[i] = SkAlphaMulAlpha(dst[i], 255 - a);
+ }
+ }
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkClearXfermode::toString(SkString* str) const {
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSrcXfermode : public SkProcCoeffXfermode {
+public:
+ SkSrcXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kSrc_Mode) {}
+
+ 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_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSrcXfermode)
+
+private:
+ SkSrcXfermode(SkFlattenableReadBuffer& buffer)
+ : SkProcCoeffXfermode(buffer) {}
+
+ typedef SkProcCoeffXfermode INHERITED;
+};
+
+void SkSrcXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ if (NULL == aa) {
+ memcpy(dst, src, count << 2);
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (a == 0xFF) {
+ dst[i] = src[i];
+ } else if (a != 0) {
+ dst[i] = SkFourByteInterp(src[i], dst[i], a);
+ }
+ }
+ }
+}
+
+void SkSrcXfermode::xferA8(SkAlpha* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src && count >= 0);
+
+ if (NULL == aa) {
+ for (int i = count - 1; i >= 0; --i) {
+ dst[i] = SkToU8(SkGetPackedA32(src[i]));
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ unsigned a = aa[i];
+ if (0 != a) {
+ unsigned srcA = SkGetPackedA32(src[i]);
+ if (a == 0xFF) {
+ dst[i] = SkToU8(srcA);
+ } else {
+ dst[i] = SkToU8(SkAlphaBlend(srcA, dst[i], a));
+ }
+ }
+ }
+ }
+}
+#ifdef SK_DEVELOPER
+void SkSrcXfermode::toString(SkString* str) const {
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkDstInXfermode : public SkProcCoeffXfermode {
+public:
+ SkDstInXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kDstIn_Mode) {}
+
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDstInXfermode)
+
+private:
+ SkDstInXfermode(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
+
+ typedef SkProcCoeffXfermode INHERITED;
+};
+
+void SkDstInXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src);
+
+ if (count <= 0) {
+ return;
+ }
+ if (NULL != aa) {
+ return this->INHERITED::xfer32(dst, src, count, aa);
+ }
+
+ do {
+ unsigned a = SkGetPackedA32(*src);
+ *dst = SkAlphaMulQ(*dst, SkAlpha255To256(a));
+ dst++;
+ src++;
+ } while (--count != 0);
+}
+
+#ifdef SK_DEVELOPER
+void SkDstInXfermode::toString(SkString* str) const {
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkDstOutXfermode : public SkProcCoeffXfermode {
+public:
+ SkDstOutXfermode(const ProcCoeff& rec) : SkProcCoeffXfermode(rec, kDstOut_Mode) {}
+
+ virtual void xfer32(SkPMColor*, const SkPMColor*, int, const SkAlpha*) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDstOutXfermode)
+
+private:
+ SkDstOutXfermode(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+ typedef SkProcCoeffXfermode INHERITED;
+};
+
+void SkDstOutXfermode::xfer32(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ const SkAlpha* SK_RESTRICT aa) const {
+ SkASSERT(dst && src);
+
+ if (count <= 0) {
+ return;
+ }
+ if (NULL != aa) {
+ return this->INHERITED::xfer32(dst, src, count, aa);
+ }
+
+ do {
+ unsigned a = SkGetPackedA32(*src);
+ *dst = SkAlphaMulQ(*dst, SkAlpha255To256(255 - a));
+ dst++;
+ src++;
+ } while (--count != 0);
+}
+
+#ifdef SK_DEVELOPER
+void SkDstOutXfermode::toString(SkString* str) const {
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkXfermode* SkXfermode::Create(Mode mode) {
+ SkASSERT(SK_ARRAY_COUNT(gProcCoeffs) == kModeCount);
+ SkASSERT((unsigned)mode < kModeCount);
+
+ const ProcCoeff& rec = gProcCoeffs[mode];
+
+ switch (mode) {
+ case kClear_Mode:
+ return SkNEW_ARGS(SkClearXfermode, (rec));
+ case kSrc_Mode:
+ return SkNEW_ARGS(SkSrcXfermode, (rec));
+ case kSrcOver_Mode:
+ return NULL;
+ case kDstIn_Mode:
+ return SkNEW_ARGS(SkDstInXfermode, (rec));
+ case kDstOut_Mode:
+ return SkNEW_ARGS(SkDstOutXfermode, (rec));
+ default:
+ return SkNEW_ARGS(SkProcCoeffXfermode, (rec, mode));
+ }
+}
+
+SkXfermodeProc SkXfermode::GetProc(Mode mode) {
+ SkXfermodeProc proc = NULL;
+ if ((unsigned)mode < kModeCount) {
+ proc = gProcCoeffs[mode].fProc;
+ }
+ return proc;
+}
+
+bool SkXfermode::ModeAsCoeff(Mode mode, Coeff* src, Coeff* dst) {
+ SkASSERT(SK_ARRAY_COUNT(gProcCoeffs) == kModeCount);
+
+ if ((unsigned)mode >= (unsigned)kModeCount) {
+ // illegal mode parameter
+ return false;
+ }
+
+ const ProcCoeff& rec = gProcCoeffs[mode];
+
+ if (CANNOT_USE_COEFF == rec.fSC) {
+ return false;
+ }
+
+ SkASSERT(CANNOT_USE_COEFF != rec.fDC);
+ if (src) {
+ *src = rec.fSC;
+ }
+ if (dst) {
+ *dst = rec.fDC;
+ }
+ return true;
+}
+
+bool SkXfermode::AsMode(const SkXfermode* xfer, Mode* mode) {
+ if (NULL == xfer) {
+ if (mode) {
+ *mode = kSrcOver_Mode;
+ }
+ return true;
+ }
+ return xfer->asMode(mode);
+}
+
+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(const SkXfermode* xfer, Mode mode) {
+ // if xfer==null then the mode is srcover
+ Mode m = kSrcOver_Mode;
+ if (xfer && !xfer->asMode(&m)) {
+ return false;
+ }
+ return mode == m;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//////////// 16bit xfermode procs
+
+#ifdef SK_DEBUG
+static bool require_255(SkPMColor src) { return SkGetPackedA32(src) == 0xFF; }
+static bool require_0(SkPMColor src) { return SkGetPackedA32(src) == 0; }
+#endif
+
+static uint16_t src_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return SkPixel32ToPixel16(src);
+}
+
+static uint16_t dst_modeproc16(SkPMColor src, uint16_t dst) {
+ return dst;
+}
+
+static uint16_t srcover_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return dst;
+}
+
+static uint16_t srcover_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return SkPixel32ToPixel16(src);
+}
+
+static uint16_t dstover_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return dst;
+}
+
+static uint16_t dstover_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return dst;
+}
+
+static uint16_t srcin_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return SkPixel32ToPixel16(src);
+}
+
+static uint16_t dstin_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return dst;
+}
+
+static uint16_t dstout_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return dst;
+}
+
+static uint16_t srcatop_modeproc16(SkPMColor src, uint16_t dst) {
+ unsigned isa = 255 - SkGetPackedA32(src);
+
+ return SkPackRGB16(
+ SkPacked32ToR16(src) + SkAlphaMulAlpha(SkGetPackedR16(dst), isa),
+ SkPacked32ToG16(src) + SkAlphaMulAlpha(SkGetPackedG16(dst), isa),
+ SkPacked32ToB16(src) + SkAlphaMulAlpha(SkGetPackedB16(dst), isa));
+}
+
+static uint16_t srcatop_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return dst;
+}
+
+static uint16_t srcatop_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return SkPixel32ToPixel16(src);
+}
+
+static uint16_t dstatop_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ return dst;
+}
+
+/*********
+ darken and lighten boil down to this.
+
+ darken = (1 - Sa) * Dc + min(Sc, Dc)
+ lighten = (1 - Sa) * Dc + max(Sc, Dc)
+
+ if (Sa == 0) these become
+ darken = Dc + min(0, Dc) = 0
+ lighten = Dc + max(0, Dc) = Dc
+
+ if (Sa == 1) these become
+ darken = min(Sc, Dc)
+ lighten = max(Sc, Dc)
+*/
+
+static uint16_t darken_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return 0;
+}
+
+static uint16_t darken_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ unsigned r = SkFastMin32(SkPacked32ToR16(src), SkGetPackedR16(dst));
+ unsigned g = SkFastMin32(SkPacked32ToG16(src), SkGetPackedG16(dst));
+ unsigned b = SkFastMin32(SkPacked32ToB16(src), SkGetPackedB16(dst));
+ return SkPackRGB16(r, g, b);
+}
+
+static uint16_t lighten_modeproc16_0(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_0(src));
+ return dst;
+}
+
+static uint16_t lighten_modeproc16_255(SkPMColor src, uint16_t dst) {
+ SkASSERT(require_255(src));
+ unsigned r = SkMax32(SkPacked32ToR16(src), SkGetPackedR16(dst));
+ unsigned g = SkMax32(SkPacked32ToG16(src), SkGetPackedG16(dst));
+ unsigned b = SkMax32(SkPacked32ToB16(src), SkGetPackedB16(dst));
+ return SkPackRGB16(r, g, b);
+}
+
+struct Proc16Rec {
+ SkXfermodeProc16 fProc16_0;
+ SkXfermodeProc16 fProc16_255;
+ SkXfermodeProc16 fProc16_General;
+};
+
+static const Proc16Rec gModeProcs16[] = {
+ { NULL, NULL, NULL }, // CLEAR
+ { NULL, src_modeproc16_255, NULL },
+ { dst_modeproc16, dst_modeproc16, dst_modeproc16 },
+ { srcover_modeproc16_0, srcover_modeproc16_255, NULL },
+ { dstover_modeproc16_0, dstover_modeproc16_255, NULL },
+ { NULL, srcin_modeproc16_255, NULL },
+ { NULL, dstin_modeproc16_255, NULL },
+ { NULL, NULL, NULL },// SRC_OUT
+ { dstout_modeproc16_0, NULL, NULL },
+ { srcatop_modeproc16_0, srcatop_modeproc16_255, srcatop_modeproc16 },
+ { NULL, dstatop_modeproc16_255, NULL },
+ { NULL, NULL, NULL }, // XOR
+
+ { NULL, NULL, NULL }, // plus
+ { NULL, NULL, NULL }, // modulate
+ { NULL, NULL, NULL }, // screen
+ { NULL, NULL, NULL }, // overlay
+ { darken_modeproc16_0, darken_modeproc16_255, NULL }, // darken
+ { lighten_modeproc16_0, lighten_modeproc16_255, NULL }, // lighten
+ { NULL, NULL, NULL }, // colordodge
+ { NULL, NULL, NULL }, // colorburn
+ { NULL, NULL, NULL }, // hardlight
+ { NULL, NULL, NULL }, // softlight
+ { NULL, NULL, NULL }, // difference
+ { NULL, NULL, NULL }, // exclusion
+ { NULL, NULL, NULL }, // multiply
+ { NULL, NULL, NULL }, // hue
+ { NULL, NULL, NULL }, // saturation
+ { NULL, NULL, NULL }, // color
+ { NULL, NULL, NULL }, // luminosity
+};
+
+SkXfermodeProc16 SkXfermode::GetProc16(Mode mode, SkColor srcColor) {
+ SkXfermodeProc16 proc16 = NULL;
+ if ((unsigned)mode < kModeCount) {
+ const Proc16Rec& rec = gModeProcs16[mode];
+ unsigned a = SkColorGetA(srcColor);
+
+ if (0 == a) {
+ proc16 = rec.fProc16_0;
+ } else if (255 == a) {
+ proc16 = rec.fProc16_255;
+ } else {
+ proc16 = rec.fProc16_General;
+ }
+ }
+ return proc16;
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkProcCoeffXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkClearXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSrcXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstInXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstOutXfermode)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/device/xps/SkXPSDevice.cpp b/device/xps/SkXPSDevice.cpp
new file mode 100644
index 00000000..442a51f5
--- /dev/null
+++ b/device/xps/SkXPSDevice.cpp
@@ -0,0 +1,2444 @@
+/*
+ * 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 "SkEndian.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 "SkSFNTHeader.h"
+#include "SkShader.h"
+#include "SkSize.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTLazy.h"
+#include "SkTScopedComPtr.h"
+#include "SkTTCFHeader.h"
+#include "SkTypefacePriv.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 = SkTUMax<
+ SK_ARRAY_COUNT(L"/Documents/1/Metadata/.png") + SK_DIGITS_IN(pageNum),
+ SK_ARRAY_COUNT(L"/Metadata/" L_GUID_ID L".png")
+ >::value;
+ 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);
+
+ int ttcCount = (current->ttcIndex + 1);
+
+ //The following are declared with the types required by CreateFontPackage.
+ unsigned char *fontPackageBufferRaw = NULL;
+ unsigned long fontPackageBufferSize;
+ unsigned long bytesWritten;
+ unsigned long result = CreateFontPackage(
+ (unsigned char *) current->fontData->getMemoryBase(),
+ (unsigned long) current->fontData->getLength(),
+ &fontPackageBufferRaw,
+ &fontPackageBufferSize,
+ &bytesWritten,
+ TTFCFP_FLAGS_SUBSET | TTFCFP_FLAGS_GLYPHLIST | (ttcCount > 0 ? TTFCFP_FLAGS_TTC : 0),
+ current->ttcIndex,
+ TTFCFP_SUBSET,
+ 0,
+ 0,
+ 0,
+ keepList.begin(),
+ keepList.count(),
+ sk_malloc_throw,
+ sk_realloc_throw,
+ sk_free,
+ NULL);
+ SkAutoTMalloc<unsigned char> fontPackageBuffer(fontPackageBufferRaw);
+ if (result != NO_ERROR) {
+ SkDEBUGF(("CreateFontPackage Error %lu", result));
+ return E_UNEXPECTED;
+ }
+
+ // If it was originally a ttc, keep it a ttc.
+ // CreateFontPackage over-allocates, realloc usually decreases the size substantially.
+ size_t extra;
+ if (ttcCount > 0) {
+ // Create space for a ttc header.
+ extra = sizeof(SkTTCFHeader) + (ttcCount * sizeof(SK_OT_ULONG));
+ fontPackageBuffer.realloc(bytesWritten + extra);
+ //overlap is certain, use memmove
+ memmove(fontPackageBuffer.get() + extra, fontPackageBuffer.get(), bytesWritten);
+
+ // Write the ttc header.
+ SkTTCFHeader* ttcfHeader = reinterpret_cast<SkTTCFHeader*>(fontPackageBuffer.get());
+ ttcfHeader->ttcTag = SkTTCFHeader::TAG;
+ ttcfHeader->version = SkTTCFHeader::version_1;
+ ttcfHeader->numOffsets = SkEndian_SwapBE32(ttcCount);
+ SK_OT_ULONG* offsetPtr = SkTAfter<SK_OT_ULONG>(ttcfHeader);
+ for (int i = 0; i < ttcCount; ++i, ++offsetPtr) {
+ *offsetPtr = SkEndian_SwapBE32(extra);
+ }
+
+ // Fix up offsets in sfnt table entries.
+ SkSFNTHeader* sfntHeader = SkTAddOffset<SkSFNTHeader>(fontPackageBuffer.get(), extra);
+ int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
+ SkSFNTHeader::TableDirectoryEntry* tableDirectory =
+ SkTAfter<SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
+ for (int i = 0; i < numTables; ++i, ++tableDirectory) {
+ tableDirectory->offset = SkEndian_SwapBE32(
+ SkEndian_SwapBE32(tableDirectory->offset) + extra);
+ }
+ } else {
+ extra = 0;
+ fontPackageBuffer.realloc(bytesWritten);
+ }
+
+ SkAutoTUnref<SkMemoryStream> newStream(new SkMemoryStream());
+ newStream->setMemoryOwned(fontPackageBuffer.detach(), bytesWritten + extra);
+
+ SkTScopedComPtr<IStream> newIStream;
+ SkIStream::CreateFromSkStream(newStream.detach(), 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;
+ 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) {
+ //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 SkMatrix& matrix, const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+
+ SkIRect srcRect;
+ srcRect.set(0, 0, bitmap.width(), bitmap.height());
+
+ //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(bitmap,
+ 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) {
+ SkAutoResolveDefaultTypeface typeface(paint.getTypeface());
+
+ //Check cache.
+ const SkFontID typefaceID = typeface->uniqueID();
+ 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;
+ int ttcIndex;
+ SkStream* fontData = typeface->openStream(&ttcIndex);
+ 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.");
+
+ //TODO: change openStream to return -1 for non-ttc, get rid of this.
+ uint8_t* data = (uint8_t*)fontData->getMemoryBase();
+ bool isTTC = (data &&
+ fontData->getLength() >= sizeof(SkTTCFHeader) &&
+ ((SkTTCFHeader*)data)->ttcTag == SkTTCFHeader::TAG);
+
+ TypefaceUse& newTypefaceUse = this->fTypefaces.push_back();
+ newTypefaceUse.typefaceId = typefaceID;
+ newTypefaceUse.ttcIndex = isTTC ? ttcIndex : -1;
+ newTypefaceUse.fontData = fontData;
+ newTypefaceUse.xpsFont = xpsFontResource.release();
+
+ SkAutoGlyphCache agc = SkAutoGlyphCache(paint, NULL, &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,
+ TypefaceUse* 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->xpsFont, &glyphs), "Could not create glyphs.");
+ HRM(glyphs->SetFontFaceIndex(font->ttcIndex), "Could not set glyph font face index.");
+
+ //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.
+ x -= state.fHalfSampleX;
+ y -= state.fHalfSampleY;
+
+ 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;
+ size_t 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,
+ 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,
+ 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/doc/SkDocument.cpp b/doc/SkDocument.cpp
new file mode 100644
index 00000000..e94c89bb
--- /dev/null
+++ b/doc/SkDocument.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDocument.h"
+#include "SkStream.h"
+
+SK_DEFINE_INST_COUNT(SkDocument)
+
+SkDocument::SkDocument(SkWStream* stream, void (*doneProc)(SkWStream*)) {
+ fStream = stream; // we do not own this object.
+ fDoneProc = doneProc;
+ fState = kBetweenPages_State;
+}
+
+SkDocument::~SkDocument() {
+ this->close();
+}
+
+SkCanvas* SkDocument::beginPage(SkScalar width, SkScalar height,
+ const SkRect* content) {
+ if (width <= 0 || height <= 0) {
+ return NULL;
+ }
+
+ SkRect outer = SkRect::MakeWH(width, height);
+ SkRect inner;
+ if (content) {
+ inner = *content;
+ if (!inner.intersect(outer)) {
+ return NULL;
+ }
+ } else {
+ inner = outer;
+ }
+
+ for (;;) {
+ switch (fState) {
+ case kBetweenPages_State:
+ fState = kInPage_State;
+ return this->onBeginPage(width, height, inner);
+ case kInPage_State:
+ this->endPage();
+ break;
+ case kClosed_State:
+ return NULL;
+ }
+ }
+ SkASSERT(!"never get here");
+ return NULL;
+}
+
+void SkDocument::endPage() {
+ if (kInPage_State == fState) {
+ fState = kBetweenPages_State;
+ this->onEndPage();
+ }
+}
+
+void SkDocument::close() {
+ for (;;) {
+ switch (fState) {
+ case kBetweenPages_State:
+ fState = kClosed_State;
+ this->onClose(fStream);
+
+ if (fDoneProc) {
+ fDoneProc(fStream);
+ }
+ // we don't own the stream, but we mark it NULL since we can
+ // no longer write to it.
+ fStream = NULL;
+ return;
+ case kInPage_State:
+ this->endPage();
+ break;
+ case kClosed_State:
+ return;
+ }
+ }
+}
diff --git a/doc/SkDocument_PDF.cpp b/doc/SkDocument_PDF.cpp
new file mode 100644
index 00000000..27cf2e88
--- /dev/null
+++ b/doc/SkDocument_PDF.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDocument.h"
+#include "SkPDFDocument.h"
+#include "SkPDFDevice.h"
+
+class SkDocument_PDF : public SkDocument {
+public:
+ SkDocument_PDF(SkWStream* stream, void (*doneProc)(SkWStream*))
+ : SkDocument(stream, doneProc) {
+ fDoc = SkNEW(SkPDFDocument);
+ fCanvas = NULL;
+ fDevice = NULL;
+ }
+
+ virtual ~SkDocument_PDF() {
+ // subclasses must call close() in their destructors
+ this->close();
+ }
+
+protected:
+ virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height,
+ const SkRect& content) SK_OVERRIDE {
+ SkASSERT(NULL == fCanvas);
+ SkASSERT(NULL == fDevice);
+
+ SkISize pageS, contentS;
+ SkMatrix matrix;
+
+ pageS.set(SkScalarRoundToInt(width), SkScalarRoundToInt(height));
+ contentS.set(SkScalarRoundToInt(content.width()),
+ SkScalarRoundToInt(content.height()));
+ matrix.setTranslate(content.fLeft, content.fTop);
+
+ fDevice = SkNEW_ARGS(SkPDFDevice, (pageS, contentS, matrix));
+ fCanvas = SkNEW_ARGS(SkCanvas, (fDevice));
+ return fCanvas;
+ }
+
+ virtual void onEndPage() SK_OVERRIDE {
+ SkASSERT(fCanvas);
+ SkASSERT(fDevice);
+
+ fCanvas->flush();
+ fDoc->appendPage(fDevice);
+
+ fCanvas->unref();
+ fDevice->unref();
+
+ fCanvas = NULL;
+ fDevice = NULL;
+ }
+
+ virtual void onClose(SkWStream* stream) SK_OVERRIDE {
+ SkASSERT(NULL == fCanvas);
+ SkASSERT(NULL == fDevice);
+
+ fDoc->emitPDF(stream);
+ SkDELETE(fDoc);
+ fDoc = NULL;
+ }
+
+private:
+ SkPDFDocument* fDoc;
+ SkPDFDevice* fDevice;
+ SkCanvas* fCanvas;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDocument* SkDocument::CreatePDF(SkWStream* stream, void (*done)(SkWStream*)) {
+ return stream ? SkNEW_ARGS(SkDocument_PDF, (stream, done)) : NULL;
+}
+
+static void delete_wstream(SkWStream* stream) {
+ SkDELETE(stream);
+}
+
+SkDocument* SkDocument::CreatePDF(const char path[]) {
+ SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (path));
+ if (!stream->isValid()) {
+ SkDELETE(stream);
+ return NULL;
+ }
+ return SkNEW_ARGS(SkDocument_PDF, (stream, delete_wstream));
+}
diff --git a/effects/Sk1DPathEffect.cpp b/effects/Sk1DPathEffect.cpp
new file mode 100644
index 00000000..10a68b9f
--- /dev/null
+++ b/effects/Sk1DPathEffect.cpp
@@ -0,0 +1,200 @@
+
+/*
+ * 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 "Sk1DPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+
+bool Sk1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ SkPathMeasure meas(src, false);
+ do {
+ SkScalar length = meas.getLength();
+ SkScalar distance = this->begin(length);
+ while (distance < length) {
+ SkScalar delta = this->next(dst, distance, meas);
+ if (delta <= 0) {
+ break;
+ }
+ distance += delta;
+ }
+ } while (meas.nextContour());
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPath1DPathEffect::SkPath1DPathEffect(const SkPath& path, SkScalar advance,
+ SkScalar phase, Style style) : fPath(path)
+{
+ if (advance <= 0 || path.isEmpty()) {
+ SkDEBUGF(("SkPath1DPathEffect can't use advance <= 0\n"));
+ fAdvance = 0; // signals we can't draw anything
+ fInitialOffset = 0;
+ fStyle = kStyleCount;
+ } else {
+ // cleanup their phase parameter, inverting it so that it becomes an
+ // offset along the path (to match the interpretation in PostScript)
+ if (phase < 0) {
+ phase = -phase;
+ if (phase > advance) {
+ phase = SkScalarMod(phase, advance);
+ }
+ } else {
+ if (phase > advance) {
+ phase = SkScalarMod(phase, advance);
+ }
+ phase = advance - phase;
+ }
+ // now catch the edge case where phase == advance (within epsilon)
+ if (phase >= advance) {
+ phase = 0;
+ }
+ SkASSERT(phase >= 0);
+
+ fAdvance = advance;
+ fInitialOffset = phase;
+
+ if ((unsigned)style >= kStyleCount) {
+ SkDEBUGF(("SkPath1DPathEffect style enum out of range %d\n", style));
+ }
+ fStyle = style;
+ }
+}
+
+bool SkPath1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ if (fAdvance > 0) {
+ rec->setFillStyle();
+ return this->INHERITED::filterPath(dst, src, rec, cullRect);
+ }
+ return false;
+}
+
+static bool morphpoints(SkPoint dst[], const SkPoint src[], int count,
+ SkPathMeasure& meas, SkScalar dist) {
+ for (int i = 0; i < count; i++) {
+ SkPoint pos;
+ SkVector tangent;
+
+ SkScalar sx = src[i].fX;
+ SkScalar sy = src[i].fY;
+
+ if (!meas.getPosTan(dist + sx, &pos, &tangent)) {
+ return false;
+ }
+
+ SkMatrix matrix;
+ SkPoint pt;
+
+ pt.set(sx, sy);
+ matrix.setSinCos(tangent.fY, tangent.fX, 0, 0);
+ matrix.preTranslate(-sx, 0);
+ matrix.postTranslate(pos.fX, pos.fY);
+ matrix.mapPoints(&dst[i], &pt, 1);
+ }
+ return true;
+}
+
+/* TODO
+
+Need differentially more subdivisions when the follow-path is curvy. Not sure how to
+determine that, but we need it. I guess a cheap answer is let the caller tell us,
+but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
+*/
+static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
+ SkScalar dist) {
+ SkPath::Iter iter(src, false);
+ SkPoint srcP[4], dstP[3];
+ SkPath::Verb verb;
+
+ while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (morphpoints(dstP, srcP, 1, meas, dist)) {
+ dst->moveTo(dstP[0]);
+ }
+ break;
+ case SkPath::kLine_Verb:
+ srcP[2] = srcP[1];
+ srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX),
+ SkScalarAve(srcP[0].fY, srcP[2].fY));
+ // fall through to quad
+ case SkPath::kQuad_Verb:
+ if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
+ dst->quadTo(dstP[0], dstP[1]);
+ }
+ break;
+ case SkPath::kCubic_Verb:
+ if (morphpoints(dstP, &srcP[1], 3, meas, dist)) {
+ dst->cubicTo(dstP[0], dstP[1], dstP[2]);
+ }
+ break;
+ case SkPath::kClose_Verb:
+ dst->close();
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ break;
+ }
+ }
+}
+
+SkPath1DPathEffect::SkPath1DPathEffect(SkFlattenableReadBuffer& buffer) {
+ fAdvance = buffer.readScalar();
+ if (fAdvance > 0) {
+ buffer.readPath(&fPath);
+ fInitialOffset = buffer.readScalar();
+ fStyle = (Style) buffer.readUInt();
+ } else {
+ SkDEBUGF(("SkPath1DPathEffect can't use advance <= 0\n"));
+ // Make Coverity happy.
+ fInitialOffset = 0;
+ fStyle = kStyleCount;
+ }
+}
+
+SkScalar SkPath1DPathEffect::begin(SkScalar contourLength) const {
+ return fInitialOffset;
+}
+
+void SkPath1DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fAdvance);
+ if (fAdvance > 0) {
+ buffer.writePath(fPath);
+ buffer.writeScalar(fInitialOffset);
+ buffer.writeUInt(fStyle);
+ }
+}
+
+SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance,
+ SkPathMeasure& meas) const {
+ switch (fStyle) {
+ case kTranslate_Style: {
+ SkPoint pos;
+ if (meas.getPosTan(distance, &pos, NULL)) {
+ dst->addPath(fPath, pos.fX, pos.fY);
+ }
+ } break;
+ case kRotate_Style: {
+ SkMatrix matrix;
+ if (meas.getMatrix(distance, &matrix)) {
+ dst->addPath(fPath, matrix);
+ }
+ } break;
+ case kMorph_Style:
+ morphpath(dst, fPath, meas, distance);
+ break;
+ default:
+ SkDEBUGFAIL("unknown Style enum");
+ break;
+ }
+ return fAdvance;
+}
diff --git a/effects/Sk2DPathEffect.cpp b/effects/Sk2DPathEffect.cpp
new file mode 100644
index 00000000..dc15f076
--- /dev/null
+++ b/effects/Sk2DPathEffect.cpp
@@ -0,0 +1,132 @@
+
+/*
+ * 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 "Sk2DPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+
+Sk2DPathEffect::Sk2DPathEffect(const SkMatrix& mat) : fMatrix(mat) {
+ fMatrixIsInvertible = mat.invert(&fInverse);
+}
+
+bool Sk2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ if (!fMatrixIsInvertible) {
+ return false;
+ }
+
+ SkPath tmp;
+ SkIRect ir;
+
+ src.transform(fInverse, &tmp);
+ tmp.getBounds().round(&ir);
+ if (!ir.isEmpty()) {
+ this->begin(ir, dst);
+
+ SkRegion rgn;
+ rgn.setPath(tmp, SkRegion(ir));
+ SkRegion::Iterator iter(rgn);
+ for (; !iter.done(); iter.next()) {
+ const SkIRect& rect = iter.rect();
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ this->nextSpan(rect.fLeft, y, rect.width(), dst);
+ }
+ }
+
+ this->end(dst);
+ }
+ return true;
+}
+
+void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) const {
+ if (!fMatrixIsInvertible) {
+ return;
+ }
+
+ const SkMatrix& mat = this->getMatrix();
+ SkPoint src, dst;
+
+ src.set(SkIntToScalar(x) + SK_ScalarHalf, SkIntToScalar(y) + SK_ScalarHalf);
+ do {
+ mat.mapPoints(&dst, &src, 1);
+ this->next(dst, x++, y, path);
+ src.fX += SK_Scalar1;
+ } while (--count > 0);
+}
+
+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 {}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Sk2DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeMatrix(fMatrix);
+}
+
+Sk2DPathEffect::Sk2DPathEffect(SkFlattenableReadBuffer& buffer) {
+ buffer.readMatrix(&fMatrix);
+ fMatrixIsInvertible = fMatrix.invert(&fInverse);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkLine2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ if (this->INHERITED::filterPath(dst, src, rec, cullRect)) {
+ rec->setStrokeStyle(fWidth);
+ return true;
+ }
+ return false;
+}
+
+void SkLine2DPathEffect::nextSpan(int u, int v, int ucount, SkPath* dst) const {
+ if (ucount > 1) {
+ SkPoint src[2], dstP[2];
+
+ src[0].set(SkIntToScalar(u) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
+ src[1].set(SkIntToScalar(u+ucount) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
+ this->getMatrix().mapPoints(dstP, src, 2);
+
+ dst->moveTo(dstP[0]);
+ dst->lineTo(dstP[1]);
+ }
+}
+
+SkLine2DPathEffect::SkLine2DPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fWidth = buffer.readScalar();
+}
+
+void SkLine2DPathEffect::flatten(SkFlattenableWriteBuffer &buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fWidth);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPath2DPathEffect::SkPath2DPathEffect(const SkMatrix& m, const SkPath& p)
+ : INHERITED(m), fPath(p) {
+}
+
+SkPath2DPathEffect::SkPath2DPathEffect(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ buffer.readPath(&fPath);
+}
+
+void SkPath2DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePath(fPath);
+}
+
+void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v,
+ SkPath* dst) const {
+ dst->addPath(fPath, loc.fX, loc.fY);
+}
diff --git a/effects/SkArithmeticMode.cpp b/effects/SkArithmeticMode.cpp
new file mode 100644
index 00000000..43c62ecd
--- /dev/null
+++ b/effects/SkArithmeticMode.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkArithmeticMode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+#include "SkUnPreMultiply.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+static const bool gUseUnpremul = false;
+
+class SkArithmeticMode_scalar : public SkXfermode {
+public:
+ SkArithmeticMode_scalar(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4) {
+ fK[0] = k1;
+ fK[1] = k2;
+ fK[2] = k3;
+ fK[3] = k4;
+ }
+
+ virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkArithmeticMode_scalar)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffectOrCoeff(GrContext*, GrEffectRef** effect, Coeff*, Coeff*, GrTexture* background) const SK_OVERRIDE;
+#endif
+
+private:
+ SkArithmeticMode_scalar(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fK[0] = buffer.readScalar();
+ fK[1] = buffer.readScalar();
+ fK[2] = buffer.readScalar();
+ fK[3] = buffer.readScalar();
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ INHERITED::flatten(buffer);
+ buffer.writeScalar(fK[0]);
+ buffer.writeScalar(fK[1]);
+ buffer.writeScalar(fK[2]);
+ buffer.writeScalar(fK[3]);
+ }
+ SkScalar fK[4];
+
+ typedef SkXfermode INHERITED;
+};
+
+static int pinToByte(int value) {
+ if (value < 0) {
+ value = 0;
+ } else if (value > 255) {
+ value = 255;
+ }
+ return value;
+}
+
+static int arith(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4,
+ int src, int dst) {
+ SkScalar result = SkScalarMul(k1, src * dst) +
+ SkScalarMul(k2, src) +
+ SkScalarMul(k3, dst) +
+ k4;
+ int res = SkScalarRoundToInt(result);
+ return pinToByte(res);
+}
+
+static int blend(int src, int dst, int scale) {
+ return dst + ((src - dst) * scale >> 8);
+}
+
+static bool needsUnpremul(int alpha) {
+ return 0 != alpha && 0xFF != alpha;
+}
+
+void SkArithmeticMode_scalar::xfer32(SkPMColor dst[], const SkPMColor src[],
+ int count, const SkAlpha aaCoverage[]) const {
+ SkScalar k1 = fK[0] / 255;
+ SkScalar k2 = fK[1];
+ SkScalar k3 = fK[2];
+ SkScalar k4 = fK[3] * 255;
+
+ for (int i = 0; i < count; ++i) {
+ if ((NULL == aaCoverage) || aaCoverage[i]) {
+ SkPMColor sc = src[i];
+ SkPMColor dc = dst[i];
+
+ int a, r, g, b;
+
+ if (gUseUnpremul) {
+ int sa = SkGetPackedA32(sc);
+ int da = SkGetPackedA32(dc);
+
+ int srcNeedsUnpremul = needsUnpremul(sa);
+ int dstNeedsUnpremul = needsUnpremul(da);
+
+ if (!srcNeedsUnpremul && !dstNeedsUnpremul) {
+ a = arith(k1, k2, k3, k4, sa, da);
+ r = arith(k1, k2, k3, k4, SkGetPackedR32(sc), SkGetPackedR32(dc));
+ g = arith(k1, k2, k3, k4, SkGetPackedG32(sc), SkGetPackedG32(dc));
+ b = arith(k1, k2, k3, k4, SkGetPackedB32(sc), SkGetPackedB32(dc));
+ } else {
+ int sr = SkGetPackedR32(sc);
+ int sg = SkGetPackedG32(sc);
+ int sb = SkGetPackedB32(sc);
+ if (srcNeedsUnpremul) {
+ SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(sa);
+ sr = SkUnPreMultiply::ApplyScale(scale, sr);
+ sg = SkUnPreMultiply::ApplyScale(scale, sg);
+ sb = SkUnPreMultiply::ApplyScale(scale, sb);
+ }
+
+ int dr = SkGetPackedR32(dc);
+ int dg = SkGetPackedG32(dc);
+ int db = SkGetPackedB32(dc);
+ if (dstNeedsUnpremul) {
+ SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(da);
+ dr = SkUnPreMultiply::ApplyScale(scale, dr);
+ dg = SkUnPreMultiply::ApplyScale(scale, dg);
+ db = SkUnPreMultiply::ApplyScale(scale, db);
+ }
+
+ a = arith(k1, k2, k3, k4, sa, da);
+ r = arith(k1, k2, k3, k4, sr, dr);
+ g = arith(k1, k2, k3, k4, sg, dg);
+ b = arith(k1, k2, k3, k4, sb, db);
+ }
+ } else {
+ a = arith(k1, k2, k3, k4, SkGetPackedA32(sc), SkGetPackedA32(dc));
+ r = arith(k1, k2, k3, k4, SkGetPackedR32(sc), SkGetPackedR32(dc));
+ r = SkMin32(r, a);
+ g = arith(k1, k2, k3, k4, SkGetPackedG32(sc), SkGetPackedG32(dc));
+ g = SkMin32(g, a);
+ b = arith(k1, k2, k3, k4, SkGetPackedB32(sc), SkGetPackedB32(dc));
+ b = SkMin32(b, a);
+ }
+
+ // apply antialias coverage if necessary
+ if (aaCoverage && 0xFF != aaCoverage[i]) {
+ int scale = aaCoverage[i] + (aaCoverage[i] >> 7);
+ a = blend(a, SkGetPackedA32(sc), scale);
+ r = blend(r, SkGetPackedR32(sc), scale);
+ g = blend(g, SkGetPackedG32(sc), scale);
+ b = blend(b, SkGetPackedB32(sc), scale);
+ }
+
+ // turn the result back into premul
+ if (gUseUnpremul && (0xFF != a)) {
+ int scale = a + (a >> 7);
+ r = SkAlphaMul(r, scale);
+ g = SkAlphaMul(g, scale);
+ b = SkAlphaMul(b, scale);
+ }
+ dst[i] = SkPackARGB32(a, r, g, b);
+ }
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkArithmeticMode_scalar::toString(SkString* str) const {
+ str->append("SkArithmeticMode_scalar: ");
+ for (int i = 0; i < 4; ++i) {
+ str->appendScalar(fK[i]);
+ if (i < 3) {
+ str->append(" ");
+ }
+ }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool fitsInBits(SkScalar x, int bits) {
+#ifdef SK_SCALAR_IS_FIXED
+ x = SkAbs32(x);
+ x += 1 << 7;
+ x >>= 8;
+ return x < (1 << (bits - 1));
+#else
+ return SkScalarAbs(x) < (1 << (bits - 1));
+#endif
+}
+
+#if 0 // UNUSED
+static int32_t toDot8(SkScalar x) {
+#ifdef SK_SCALAR_IS_FIXED
+ x += 1 << 7;
+ x >>= 8;
+ return x;
+#else
+ return (int32_t)(x * 256);
+#endif
+}
+#endif
+
+SkXfermode* SkArithmeticMode::Create(SkScalar k1, SkScalar k2,
+ SkScalar k3, SkScalar k4) {
+ if (fitsInBits(k1, 8) && fitsInBits(k2, 16) &&
+ fitsInBits(k2, 16) && fitsInBits(k2, 24)) {
+
+#if 0 // UNUSED
+ int32_t i1 = toDot8(k1);
+ int32_t i2 = toDot8(k2);
+ int32_t i3 = toDot8(k3);
+ int32_t i4 = toDot8(k4);
+ if (i1) {
+ return SkNEW_ARGS(SkArithmeticMode_quad, (i1, i2, i3, i4));
+ }
+ if (0 == i2) {
+ return SkNEW_ARGS(SkArithmeticMode_dst, (i3, i4));
+ }
+ if (0 == i3) {
+ return SkNEW_ARGS(SkArithmeticMode_src, (i2, i4));
+ }
+ return SkNEW_ARGS(SkArithmeticMode_linear, (i2, i3, i4));
+#endif
+ }
+ return SkNEW_ARGS(SkArithmeticMode_scalar, (k1, k2, k3, k4));
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+class GrGLArithmeticEffect : public GrGLEffect {
+public:
+ GrGLArithmeticEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+ virtual ~GrGLArithmeticEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+ GrGLEffectMatrix fBackgroundEffectMatrix;
+ GrGLUniformManager::UniformHandle fKUni;
+
+ typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrArithmeticEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(float k1, float k2, float k3, float k4, GrTexture* background) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrArithmeticEffect, (k1, k2, k3, k4, background)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrArithmeticEffect();
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ typedef GrGLArithmeticEffect GLEffect;
+ static const char* Name() { return "Arithmetic"; }
+ GrTexture* backgroundTexture() const { return fBackgroundAccess.getTexture(); }
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ float k1() const { return fK1; }
+ float k2() const { return fK2; }
+ float k3() const { return fK3; }
+ float k4() const { return fK4; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrArithmeticEffect(float k1, float k2, float k3, float k4, GrTexture* background);
+ float fK1, fK2, fK3, fK4;
+ GrTextureAccess fBackgroundAccess;
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrEffect INHERITED;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrArithmeticEffect::GrArithmeticEffect(float k1, float k2, float k3, float k4,
+ GrTexture* background)
+ : fK1(k1), fK2(k2), fK3(k3), fK4(k4) {
+ if (background) {
+ fBackgroundAccess.reset(background);
+ this->addTextureAccess(&fBackgroundAccess);
+ } else {
+ this->setWillReadDstColor();
+ }
+}
+
+GrArithmeticEffect::~GrArithmeticEffect() {
+}
+
+bool GrArithmeticEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrArithmeticEffect& s = CastEffect<GrArithmeticEffect>(sBase);
+ return fK1 == s.fK1 &&
+ fK2 == s.fK2 &&
+ fK3 == s.fK3 &&
+ fK4 == s.fK4 &&
+ backgroundTexture() == s.backgroundTexture();
+}
+
+const GrBackendEffectFactory& GrArithmeticEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrArithmeticEffect>::getInstance();
+}
+
+void GrArithmeticEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // TODO: optimize this
+ *validFlags = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLArithmeticEffect::GrGLArithmeticEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fBackgroundEffectMatrix(kCoordsType) {
+}
+
+GrGLArithmeticEffect::~GrGLArithmeticEffect() {
+}
+
+void GrGLArithmeticEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ GrTexture* backgroundTex = drawEffect.castEffect<GrArithmeticEffect>().backgroundTexture();
+ const char* dstColor;
+ if (backgroundTex) {
+ const char* bgCoords;
+ GrSLType bgCoordsType = fBackgroundEffectMatrix.emitCode(builder, key, &bgCoords, NULL, "BG");
+ builder->fsCodeAppend("\t\tvec4 bgColor = ");
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ bgCoords,
+ bgCoordsType);
+ builder->fsCodeAppendf(";\n");
+ dstColor = "bgColor";
+ } else {
+ dstColor = builder->dstColor();
+ }
+
+ GrAssert(NULL != dstColor);
+ fKUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "k");
+ const char* kUni = builder->getUniformCStr(fKUni);
+
+ // We don't try to optimize for this case at all
+ if (NULL == inputColor) {
+ builder->fsCodeAppendf("\t\tconst vec4 src = %s;\n", GrGLSLOnesVecf(4));
+ } else {
+ builder->fsCodeAppendf("\t\tvec4 src = %s;\n", inputColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\tsrc.rgb = clamp(src.rgb / src.a, 0.0, 1.0);\n");
+ }
+ }
+
+ builder->fsCodeAppendf("\t\tvec4 dst = %s;\n", dstColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\tdst.rgb = clamp(dst.rgb / dst.a, 0.0, 1.0);\n");
+ }
+
+ builder->fsCodeAppendf("\t\t%s = %s.x * src * dst + %s.y * src + %s.z * dst + %s.w;\n", outputColor, kUni, kUni, kUni, kUni);
+ builder->fsCodeAppendf("\t\t%s = clamp(%s, 0.0, 1.0);\n", outputColor, outputColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ } else {
+ builder->fsCodeAppendf("\t\t%s.rgb = min(%s.rgb, %s.a);\n", outputColor, outputColor, outputColor);
+ }
+}
+
+void GrGLArithmeticEffect::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ const GrArithmeticEffect& arith = drawEffect.castEffect<GrArithmeticEffect>();
+ uman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
+ GrTexture* bgTex = arith.backgroundTexture();
+ if (bgTex) {
+ fBackgroundEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ bgTex);
+ }
+}
+
+GrGLEffect::EffectKey GrGLArithmeticEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrArithmeticEffect& effect = drawEffect.castEffect<GrArithmeticEffect>();
+ GrTexture* bgTex = effect.backgroundTexture();
+ EffectKey bgKey = 0;
+ if (bgTex) {
+ bgKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ GrGLArithmeticEffect::kCoordsType,
+ bgTex);
+ }
+ return bgKey;
+}
+
+GrEffectRef* GrArithmeticEffect::TestCreate(SkMWCRandom* rand,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture*[]) {
+ float k1 = rand->nextF();
+ float k2 = rand->nextF();
+ float k3 = rand->nextF();
+ float k4 = rand->nextF();
+
+ static AutoEffectUnref gEffect(SkNEW_ARGS(GrArithmeticEffect, (k1, k2, k3, k4, NULL)));
+ return CreateEffectRef(gEffect);
+}
+
+GR_DEFINE_EFFECT_TEST(GrArithmeticEffect);
+
+bool SkArithmeticMode_scalar::asNewEffectOrCoeff(GrContext*,
+ GrEffectRef** effect,
+ Coeff*,
+ Coeff*,
+ GrTexture* background) const {
+ if (effect) {
+ *effect = GrArithmeticEffect::Create(SkScalarToFloat(fK[0]),
+ SkScalarToFloat(fK[1]),
+ SkScalarToFloat(fK[2]),
+ SkScalarToFloat(fK[3]),
+ background);
+ }
+ return true;
+}
+
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkArithmeticMode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkArithmeticMode_scalar)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkAvoidXfermode.cpp b/effects/SkAvoidXfermode.cpp
new file mode 100644
index 00000000..d76efb83
--- /dev/null
+++ b/effects/SkAvoidXfermode.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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 "SkAvoidXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkAvoidXfermode::SkAvoidXfermode(SkColor opColor, U8CPU tolerance, Mode mode) {
+ if (tolerance > 255) {
+ tolerance = 255;
+ }
+
+ fOpColor = opColor;
+ fDistMul = (256 << 14) / (tolerance + 1);
+ fMode = mode;
+}
+
+SkAvoidXfermode::SkAvoidXfermode(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fOpColor = buffer.readColor();
+ fDistMul = buffer.readUInt();
+ fMode = (Mode)buffer.readUInt();
+}
+
+void SkAvoidXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeColor(fOpColor);
+ buffer.writeUInt(fDistMul);
+ buffer.writeUInt(fMode);
+}
+
+// returns 0..31
+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);
+
+ unsigned dr = SkAbs32(SkGetPackedR16(c) - r);
+ unsigned dg = SkAbs32(SkGetPackedG16(c) - g) >> (SK_G16_BITS - SK_R16_BITS);
+ unsigned db = SkAbs32(SkGetPackedB16(c) - b);
+
+ return SkMax32(dr, SkMax32(dg, db));
+}
+
+// returns 0..255
+static unsigned color_dist32(SkPMColor c, U8CPU r, U8CPU g, U8CPU b) {
+ SkASSERT(r <= 0xFF);
+ SkASSERT(g <= 0xFF);
+ SkASSERT(b <= 0xFF);
+
+ unsigned dr = SkAbs32(SkGetPackedR32(c) - r);
+ unsigned dg = SkAbs32(SkGetPackedG32(c) - g);
+ unsigned db = SkAbs32(SkGetPackedB32(c) - b);
+
+ return SkMax32(dr, SkMax32(dg, db));
+}
+
+static int scale_dist_14(int dist, uint32_t mul, uint32_t sub) {
+ int tmp = dist * mul - sub;
+ int result = (tmp + (1 << 13)) >> 14;
+
+ return result;
+}
+
+static inline unsigned Accurate255To256(unsigned x) {
+ return x + (x >> 7);
+}
+
+void SkAvoidXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ unsigned opR = SkColorGetR(fOpColor);
+ unsigned opG = SkColorGetG(fOpColor);
+ unsigned opB = SkColorGetB(fOpColor);
+ uint32_t mul = fDistMul;
+ uint32_t sub = (fDistMul - (1 << 14)) << 8;
+
+ int MAX, mask;
+
+ if (kTargetColor_Mode == fMode) {
+ mask = -1;
+ MAX = 255;
+ } else {
+ mask = 0;
+ MAX = 0;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int d = color_dist32(dst[i], opR, opG, opB);
+ // now reverse d if we need to
+ d = MAX + (d ^ mask) - mask;
+ SkASSERT((unsigned)d <= 255);
+ d = Accurate255To256(d);
+
+ d = scale_dist_14(d, mul, sub);
+ SkASSERT(d <= 256);
+
+ if (d > 0) {
+ if (NULL != aa) {
+ d = SkAlphaMul(d, Accurate255To256(*aa++));
+ if (0 == d) {
+ continue;
+ }
+ }
+ dst[i] = SkFourByteInterp256(src[i], dst[i], d);
+ }
+ }
+}
+
+static inline U16CPU SkBlend3216(SkPMColor src, U16CPU dst, unsigned scale) {
+ SkASSERT(scale <= 32);
+ scale <<= 3;
+
+ return SkPackRGB16( SkAlphaBlend(SkPacked32ToR16(src), SkGetPackedR16(dst), scale),
+ SkAlphaBlend(SkPacked32ToG16(src), SkGetPackedG16(dst), scale),
+ SkAlphaBlend(SkPacked32ToB16(src), SkGetPackedB16(dst), scale));
+}
+
+void SkAvoidXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ 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);
+ uint32_t mul = fDistMul;
+ uint32_t sub = (fDistMul - (1 << 14)) << SK_R16_BITS;
+
+ int MAX, mask;
+
+ if (kTargetColor_Mode == fMode) {
+ mask = -1;
+ MAX = 31;
+ } else {
+ mask = 0;
+ MAX = 0;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int d = color_dist16(dst[i], opR, opG, opB);
+ // now reverse d if we need to
+ d = MAX + (d ^ mask) - mask;
+ SkASSERT((unsigned)d <= 31);
+ // convert from 0..31 to 0..32
+ d += d >> 4;
+ d = scale_dist_14(d, mul, sub);
+ SkASSERT(d <= 32);
+
+ if (d > 0) {
+ if (NULL != aa) {
+ d = SkAlphaMul(d, Accurate255To256(*aa++));
+ if (0 == d) {
+ continue;
+ }
+ }
+ dst[i] = SkBlend3216(src[i], dst[i], d);
+ }
+ }
+}
+
+void SkAvoidXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ // override in subclass
+}
+
+#ifdef SK_DEVELOPER
+void SkAvoidXfermode::toString(SkString* str) const {
+ str->append("SkAvoidXfermode: opColor: ");
+ str->appendHex(fOpColor);
+ str->appendf("distMul: %d ", fDistMul);
+
+ static const char* gModeStrings[] = { "Avoid", "Target" };
+
+ str->appendf("mode: %s", gModeStrings[fMode]);
+}
+#endif
diff --git a/effects/SkBicubicImageFilter.cpp b/effects/SkBicubicImageFilter.cpp
new file mode 100644
index 00000000..db8dbfd0
--- /dev/null
+++ b/effects/SkBicubicImageFilter.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2013 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 "SkBicubicImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMatrix.h"
+#include "SkRect.h"
+#include "SkUnPreMultiply.h"
+
+#if SK_SUPPORT_GPU
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/GrSingleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkBicubicImageFilter::SkBicubicImageFilter(const SkSize& scale, const SkScalar coefficients[16], SkImageFilter* input)
+ : INHERITED(input),
+ fScale(scale) {
+ memcpy(fCoefficients, coefficients, sizeof(fCoefficients));
+}
+
+#define DS(x) SkDoubleToScalar(x)
+
+SkBicubicImageFilter* SkBicubicImageFilter::CreateMitchell(const SkSize& scale,
+ SkImageFilter* input) {
+ static const SkScalar coefficients[16] = {
+ DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
+ DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
+ DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
+ DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS( 7.0 / 18.0),
+ };
+ return SkNEW_ARGS(SkBicubicImageFilter, (scale, coefficients, input));
+}
+
+SkBicubicImageFilter::SkBicubicImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ SkDEBUGCODE(uint32_t readSize =) buffer.readScalarArray(fCoefficients);
+ SkASSERT(readSize == 16);
+ fScale.fWidth = buffer.readScalar();
+ fScale.fHeight = buffer.readScalar();
+}
+
+void SkBicubicImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalarArray(fCoefficients, 16);
+ buffer.writeScalar(fScale.fWidth);
+ buffer.writeScalar(fScale.fHeight);
+}
+
+SkBicubicImageFilter::~SkBicubicImageFilter() {
+}
+
+inline SkPMColor cubicBlend(const SkScalar c[16], SkScalar t, SkPMColor c0, SkPMColor c1, SkPMColor c2, SkPMColor c3) {
+ SkScalar t2 = t * t, t3 = t2 * t;
+ SkScalar cc[4];
+ // FIXME: For the fractx case, this should be refactored out of this function.
+ cc[0] = c[0] + SkScalarMul(c[1], t) + SkScalarMul(c[2], t2) + SkScalarMul(c[3], t3);
+ cc[1] = c[4] + SkScalarMul(c[5], t) + SkScalarMul(c[6], t2) + SkScalarMul(c[7], t3);
+ cc[2] = c[8] + SkScalarMul(c[9], t) + SkScalarMul(c[10], t2) + SkScalarMul(c[11], t3);
+ cc[3] = c[12] + SkScalarMul(c[13], t) + SkScalarMul(c[14], t2) + SkScalarMul(c[15], t3);
+ SkScalar a = SkScalarClampMax(SkScalarMul(cc[0], SkGetPackedA32(c0)) + SkScalarMul(cc[1], SkGetPackedA32(c1)) + SkScalarMul(cc[2], SkGetPackedA32(c2)) + SkScalarMul(cc[3], SkGetPackedA32(c3)), 255);
+ SkScalar r = SkScalarMul(cc[0], SkGetPackedR32(c0)) + SkScalarMul(cc[1], SkGetPackedR32(c1)) + SkScalarMul(cc[2], SkGetPackedR32(c2)) + SkScalarMul(cc[3], SkGetPackedR32(c3));
+ SkScalar g = SkScalarMul(cc[0], SkGetPackedG32(c0)) + SkScalarMul(cc[1], SkGetPackedG32(c1)) + SkScalarMul(cc[2], SkGetPackedG32(c2)) + SkScalarMul(cc[3], SkGetPackedG32(c3));
+ SkScalar b = SkScalarMul(cc[0], SkGetPackedB32(c0)) + SkScalarMul(cc[1], SkGetPackedB32(c1)) + SkScalarMul(cc[2], SkGetPackedB32(c2)) + SkScalarMul(cc[3], SkGetPackedB32(c3));
+ return SkPackARGB32(SkScalarRoundToInt(a),
+ SkScalarRoundToInt(SkScalarClampMax(r, a)),
+ SkScalarRoundToInt(SkScalarClampMax(g, a)),
+ SkScalarRoundToInt(SkScalarClampMax(b, a)));
+}
+
+bool SkBicubicImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkRect dstRect = SkRect::MakeWH(SkScalarMul(SkIntToScalar(src.width()), fScale.fWidth),
+ SkScalarMul(SkIntToScalar(src.height()), fScale.fHeight));
+ SkIRect dstIRect;
+ dstRect.roundOut(&dstIRect);
+ result->setConfig(src.config(), dstIRect.width(), dstIRect.height());
+ result->allocPixels();
+ if (!result->getPixels()) {
+ return false;
+ }
+
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ SkMatrix inverse;
+ inverse.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit);
+ inverse.postTranslate(SkFloatToScalar(-0.5f), SkFloatToScalar(-0.5f));
+
+ for (int y = dstIRect.fTop; y < dstIRect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(dstIRect.fLeft, y);
+ for (int x = dstIRect.fLeft; x < dstIRect.fRight; ++x) {
+ SkPoint srcPt, dstPt = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ inverse.mapPoints(&srcPt, &dstPt, 1);
+ SkScalar fractx = srcPt.fX - SkScalarFloorToScalar(srcPt.fX);
+ SkScalar fracty = srcPt.fY - SkScalarFloorToScalar(srcPt.fY);
+ int sx = SkScalarFloorToInt(srcPt.fX);
+ int sy = SkScalarFloorToInt(srcPt.fY);
+ int x0 = SkClampMax(sx - 1, src.width() - 1);
+ int x1 = SkClampMax(sx , src.width() - 1);
+ int x2 = SkClampMax(sx + 1, src.width() - 1);
+ int x3 = SkClampMax(sx + 2, src.width() - 1);
+ int y0 = SkClampMax(sy - 1, src.height() - 1);
+ int y1 = SkClampMax(sy , src.height() - 1);
+ int y2 = SkClampMax(sy + 1, src.height() - 1);
+ int y3 = SkClampMax(sy + 2, src.height() - 1);
+ SkPMColor s00 = *src.getAddr32(x0, y0);
+ SkPMColor s10 = *src.getAddr32(x1, y0);
+ SkPMColor s20 = *src.getAddr32(x2, y0);
+ SkPMColor s30 = *src.getAddr32(x3, y0);
+ SkPMColor s0 = cubicBlend(fCoefficients, fractx, s00, s10, s20, s30);
+ SkPMColor s01 = *src.getAddr32(x0, y1);
+ SkPMColor s11 = *src.getAddr32(x1, y1);
+ SkPMColor s21 = *src.getAddr32(x2, y1);
+ SkPMColor s31 = *src.getAddr32(x3, y1);
+ SkPMColor s1 = cubicBlend(fCoefficients, fractx, s01, s11, s21, s31);
+ SkPMColor s02 = *src.getAddr32(x0, y2);
+ SkPMColor s12 = *src.getAddr32(x1, y2);
+ SkPMColor s22 = *src.getAddr32(x2, y2);
+ SkPMColor s32 = *src.getAddr32(x3, y2);
+ SkPMColor s2 = cubicBlend(fCoefficients, fractx, s02, s12, s22, s32);
+ SkPMColor s03 = *src.getAddr32(x0, y3);
+ SkPMColor s13 = *src.getAddr32(x1, y3);
+ SkPMColor s23 = *src.getAddr32(x2, y3);
+ SkPMColor s33 = *src.getAddr32(x3, y3);
+ SkPMColor s3 = cubicBlend(fCoefficients, fractx, s03, s13, s23, s33);
+ *dptr++ = cubicBlend(fCoefficients, fracty, s0, s1, s2, s3);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+class GrGLBicubicEffect;
+
+class GrBicubicEffect : public GrSingleTextureEffect {
+public:
+ virtual ~GrBicubicEffect();
+
+ static const char* Name() { return "Bicubic"; }
+ const float* coefficients() const { return fCoefficients; }
+
+ typedef GrGLBicubicEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ static GrEffectRef* Create(GrTexture* tex, const SkScalar coefficients[16]) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrBicubicEffect, (tex, coefficients)));
+ return CreateEffectRef(effect);
+ }
+
+private:
+ GrBicubicEffect(GrTexture*, const SkScalar coefficients[16]);
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+ float fCoefficients[16];
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+class GrGLBicubicEffect : public GrGLEffect {
+public:
+ GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&);
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLUniformManager::UniformHandle UniformHandle;
+
+ UniformHandle fCoefficientsUni;
+ UniformHandle fImageIncrementUni;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLBicubicEffect::GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fCoefficientsUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrBicubicEffect>().coordsType()) {
+}
+
+void GrGLBicubicEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fCoefficientsUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat44f_GrSLType, "Coefficients");
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* coeff = builder->getUniformCStr(fCoefficientsUni);
+
+ SkString cubicBlendName;
+
+ static const GrGLShaderVar gCubicBlendArgs[] = {
+ GrGLShaderVar("coefficients", kMat44f_GrSLType),
+ GrGLShaderVar("t", kFloat_GrSLType),
+ GrGLShaderVar("c0", kVec4f_GrSLType),
+ GrGLShaderVar("c1", kVec4f_GrSLType),
+ GrGLShaderVar("c2", kVec4f_GrSLType),
+ GrGLShaderVar("c3", kVec4f_GrSLType),
+ };
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "cubicBlend",
+ SK_ARRAY_COUNT(gCubicBlendArgs),
+ gCubicBlendArgs,
+ "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
+ "\tvec4 c = coefficients * ts;\n"
+ "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
+ &cubicBlendName);
+ builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5, 0.5);\n", coords, imgInc);
+ builder->fsCodeAppendf("\tvec2 f = fract(coord / %s);\n", imgInc);
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ SkString coord;
+ coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
+ builder->fsCodeAppendf("\tvec4 s%d%d = ", x, y);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ coord.c_str());
+ builder->fsCodeAppend(";\n");
+ }
+ builder->fsCodeAppendf("\tvec4 s%d = %s(%s, f.x, s0%d, s1%d, s2%d, s3%d);\n", y, cubicBlendName.c_str(), coeff, y, y, y, y);
+ }
+ builder->fsCodeAppendf("\t%s = %s(%s, f.y, s0, s1, s2, s3);\n", outputColor, cubicBlendName.c_str(), coeff);
+}
+
+GrGLEffect::EffectKey GrGLBicubicEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrBicubicEffect& bicubic = drawEffect.castEffect<GrBicubicEffect>();
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(bicubic.getMatrix(),
+ drawEffect,
+ bicubic.coordsType(),
+ bicubic.texture(0));
+ return matrixKey;
+}
+
+void GrGLBicubicEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrBicubicEffect& effect = drawEffect.castEffect<GrBicubicEffect>();
+ GrTexture& texture = *effect.texture(0);
+ float imageIncrement[2];
+ imageIncrement[0] = 1.0f / texture.width();
+ imageIncrement[1] = 1.0f / texture.height();
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ uman.setMatrix4f(fCoefficientsUni, effect.coefficients());
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ drawEffect,
+ effect.texture(0));
+}
+
+GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
+ const SkScalar coefficients[16])
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture)) {
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 4; x++) {
+ // Convert from row-major scalars to column-major floats.
+ fCoefficients[x * 4 + y] = SkScalarToFloat(coefficients[y * 4 + x]);
+ }
+ }
+}
+
+GrBicubicEffect::~GrBicubicEffect() {
+}
+
+const GrBackendEffectFactory& GrBicubicEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrBicubicEffect>::getInstance();
+}
+
+bool GrBicubicEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrBicubicEffect& s = CastEffect<GrBicubicEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ !memcmp(fCoefficients, s.coefficients(), 16);
+}
+
+void GrBicubicEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // FIXME: Perhaps we can do better.
+ *validFlags = 0;
+ return;
+}
+
+GR_DEFINE_EFFECT_TEST(GrBicubicEffect);
+
+GrEffectRef* GrBicubicEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ SkScalar coefficients[16];
+ for (int i = 0; i < 16; i++) {
+ coefficients[i] = random->nextSScalar1();
+ }
+ return GrBicubicEffect::Create(textures[texIdx], coefficients);
+}
+
+bool SkBicubicImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap srcBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &srcBM, offset)) {
+ return false;
+ }
+ GrTexture* srcTexture = srcBM.getTexture();
+ GrContext* context = srcTexture->getContext();
+
+ SkRect dstRect = SkRect::MakeWH(srcBM.width() * fScale.fWidth,
+ srcBM.height() * fScale.fHeight);
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = SkScalarCeilToInt(dstRect.width());
+ desc.fHeight = SkScalarCeilToInt(dstRect.height());
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+ if (!dst) {
+ return false;
+ }
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+ GrPaint paint;
+ paint.addColorEffect(GrBicubicEffect::Create(srcTexture, fCoefficients))->unref();
+ SkRect srcRect;
+ srcBM.getBounds(&srcRect);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ return SkImageFilterUtils::WrapTexture(dst, desc.fWidth, desc.fHeight, result);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/effects/SkBitmapSource.cpp b/effects/SkBitmapSource.cpp
new file mode 100644
index 00000000..854df9df
--- /dev/null
+++ b/effects/SkBitmapSource.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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 "SkBitmapSource.h"
+
+SkBitmapSource::SkBitmapSource(const SkBitmap& bitmap)
+ : INHERITED(0),
+ fBitmap(bitmap) {
+}
+
+SkBitmapSource::SkBitmapSource(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fBitmap.unflatten(buffer);
+}
+
+void SkBitmapSource::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ fBitmap.flatten(buffer);
+}
+
+bool SkBitmapSource::onFilterImage(Proxy*, const SkBitmap&, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) {
+ *result = fBitmap;
+ return true;
+}
diff --git a/effects/SkBlurDrawLooper.cpp b/effects/SkBlurDrawLooper.cpp
new file mode 100644
index 00000000..9585214b
--- /dev/null
+++ b/effects/SkBlurDrawLooper.cpp
@@ -0,0 +1,151 @@
+
+/*
+ * 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 "SkBlurDrawLooper.h"
+#include "SkBlurMaskFilter.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkString.h"
+#include "SkStringUtils.h"
+
+SkBlurDrawLooper::SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy,
+ SkColor color, uint32_t flags)
+ : fDx(dx), fDy(dy), fBlurColor(color), fBlurFlags(flags), fState(kDone) {
+
+ SkASSERT(flags <= kAll_BlurFlag);
+ if (radius > 0) {
+ uint32_t blurFlags = flags & kIgnoreTransform_BlurFlag ?
+ SkBlurMaskFilter::kIgnoreTransform_BlurFlag :
+ SkBlurMaskFilter::kNone_BlurFlag;
+
+ blurFlags |= flags & kHighQuality_BlurFlag ?
+ SkBlurMaskFilter::kHighQuality_BlurFlag :
+ SkBlurMaskFilter::kNone_BlurFlag;
+
+ fBlur = SkBlurMaskFilter::Create(radius,
+ SkBlurMaskFilter::kNormal_BlurStyle,
+ blurFlags);
+ } else {
+ fBlur = NULL;
+ }
+
+ if (flags & kOverrideColor_BlurFlag) {
+ // Set alpha to 1 for the override since transparency will already
+ // be baked into the blurred mask.
+ SkColor opaqueColor = SkColorSetA(color, 255);
+ //The SrcIn xfer mode will multiply 'color' by the incoming alpha
+ fColorFilter = SkColorFilter::CreateModeFilter(opaqueColor,
+ SkXfermode::kSrcIn_Mode);
+ } else {
+ fColorFilter = NULL;
+ }
+}
+
+SkBlurDrawLooper::SkBlurDrawLooper(SkFlattenableReadBuffer& buffer)
+: INHERITED(buffer) {
+
+ fDx = buffer.readScalar();
+ fDy = buffer.readScalar();
+ fBlurColor = buffer.readColor();
+ fBlur = buffer.readFlattenableT<SkMaskFilter>();
+ fColorFilter = buffer.readFlattenableT<SkColorFilter>();
+ fBlurFlags = buffer.readUInt() & kAll_BlurFlag;
+}
+
+SkBlurDrawLooper::~SkBlurDrawLooper() {
+ SkSafeUnref(fBlur);
+ SkSafeUnref(fColorFilter);
+}
+
+void SkBlurDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fDx);
+ buffer.writeScalar(fDy);
+ buffer.writeColor(fBlurColor);
+ buffer.writeFlattenable(fBlur);
+ buffer.writeFlattenable(fColorFilter);
+ buffer.writeUInt(fBlurFlags);
+}
+
+void SkBlurDrawLooper::init(SkCanvas*) {
+ fState = kBeforeEdge;
+}
+
+bool SkBlurDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+ switch (fState) {
+ case kBeforeEdge:
+ // we do nothing if a maskfilter is already installed
+ if (paint->getMaskFilter()) {
+ fState = kDone;
+ return false;
+ }
+#ifdef SK_BUILD_FOR_ANDROID
+ SkColor blurColor;
+ blurColor = fBlurColor;
+ if (SkColorGetA(blurColor) == 255) {
+ blurColor = SkColorSetA(blurColor, paint->getAlpha());
+ }
+ paint->setColor(blurColor);
+#else
+ paint->setColor(fBlurColor);
+#endif
+ paint->setMaskFilter(fBlur);
+ paint->setColorFilter(fColorFilter);
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+ if (fBlurFlags & kIgnoreTransform_BlurFlag) {
+ SkMatrix transform(canvas->getTotalMatrix());
+ transform.postTranslate(fDx, fDy);
+ canvas->setMatrix(transform);
+ } else {
+ canvas->translate(fDx, fDy);
+ }
+ fState = kAfterEdge;
+ return true;
+ case kAfterEdge:
+ canvas->restore();
+ fState = kDone;
+ return true;
+ default:
+ SkASSERT(kDone == fState);
+ return false;
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkBlurDrawLooper::toString(SkString* str) const {
+ str->append("SkBlurDrawLooper: ");
+
+ str->append("dx: ");
+ str->appendScalar(fDx);
+
+ str->append(" dy: ");
+ str->appendScalar(fDy);
+
+ str->append(" color: ");
+ str->appendHex(fBlurColor);
+
+ str->append(" flags: (");
+ if (kNone_BlurFlag == fBlurFlags) {
+ str->append("None");
+ } else {
+ bool needsSeparator = false;
+ SkAddFlagToString(str, SkToBool(kIgnoreTransform_BlurFlag & fBlurFlags), "IgnoreTransform",
+ &needsSeparator);
+ SkAddFlagToString(str, SkToBool(kOverrideColor_BlurFlag & fBlurFlags), "OverrideColor",
+ &needsSeparator);
+ SkAddFlagToString(str, SkToBool(kHighQuality_BlurFlag & fBlurFlags), "HighQuality",
+ &needsSeparator);
+ }
+ str->append(")");
+
+ // TODO: add optional "fBlurFilter->toString(str);" when SkMaskFilter::toString is added
+ // alternatively we could cache the radius in SkBlurDrawLooper and just add it here
+}
+#endif
diff --git a/effects/SkBlurImageFilter.cpp b/effects/SkBlurImageFilter.cpp
new file mode 100644
index 00000000..4b2d3b88
--- /dev/null
+++ b/effects/SkBlurImageFilter.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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 "SkBitmap.h"
+#include "SkBlurImageFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkGpuBlurUtils.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkBlurImageFilter::SkBlurImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fSigma.fWidth = buffer.readScalar();
+ fSigma.fHeight = buffer.readScalar();
+}
+
+SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
+ SkScalar sigmaY,
+ SkImageFilter* input,
+ const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fSigma(SkSize::Make(sigmaX, sigmaY)) {
+ SkASSERT(sigmaX >= 0 && sigmaY >= 0);
+}
+
+void SkBlurImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSigma.fWidth);
+ buffer.writeScalar(fSigma.fHeight);
+}
+
+static void boxBlurX(const SkBitmap& src, SkBitmap* dst, int kernelSize,
+ int leftOffset, int rightOffset, const SkIRect& bounds)
+{
+ int width = bounds.width(), height = bounds.height();
+ int rightBorder = SkMin32(rightOffset + 1, width);
+ for (int y = 0; y < height; ++y) {
+ int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ SkPMColor* p = src.getAddr32(bounds.fLeft, y + bounds.fTop);
+ for (int i = 0; i < rightBorder; ++i) {
+ sumA += SkGetPackedA32(*p);
+ sumR += SkGetPackedR32(*p);
+ sumG += SkGetPackedG32(*p);
+ sumB += SkGetPackedB32(*p);
+ p++;
+ }
+
+ const SkColor* sptr = src.getAddr32(bounds.fLeft, bounds.fTop + y);
+ SkColor* dptr = dst->getAddr32(0, y);
+ for (int x = 0; x < width; ++x) {
+ *dptr = SkPackARGB32(sumA / kernelSize,
+ sumR / kernelSize,
+ sumG / kernelSize,
+ sumB / kernelSize);
+ if (x >= leftOffset) {
+ SkColor l = *(sptr - leftOffset);
+ sumA -= SkGetPackedA32(l);
+ sumR -= SkGetPackedR32(l);
+ sumG -= SkGetPackedG32(l);
+ sumB -= SkGetPackedB32(l);
+ }
+ if (x + rightOffset + 1 < width) {
+ SkColor r = *(sptr + rightOffset + 1);
+ sumA += SkGetPackedA32(r);
+ sumR += SkGetPackedR32(r);
+ sumG += SkGetPackedG32(r);
+ sumB += SkGetPackedB32(r);
+ }
+ sptr++;
+ dptr++;
+ }
+ }
+}
+
+static void boxBlurY(const SkBitmap& src, SkBitmap* dst, int kernelSize,
+ int topOffset, int bottomOffset, const SkIRect& bounds)
+{
+ int width = bounds.width(), height = bounds.height();
+ int bottomBorder = SkMin32(bottomOffset + 1, height);
+ int srcStride = src.rowBytesAsPixels();
+ int dstStride = dst->rowBytesAsPixels();
+ for (int x = 0; x < width; ++x) {
+ int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ SkColor* p = src.getAddr32(bounds.fLeft + x, bounds.fTop);
+ for (int i = 0; i < bottomBorder; ++i) {
+ sumA += SkGetPackedA32(*p);
+ sumR += SkGetPackedR32(*p);
+ sumG += SkGetPackedG32(*p);
+ sumB += SkGetPackedB32(*p);
+ p += srcStride;
+ }
+
+ const SkColor* sptr = src.getAddr32(bounds.fLeft + x, bounds.fTop);
+ SkColor* dptr = dst->getAddr32(x, 0);
+ for (int y = 0; y < height; ++y) {
+ *dptr = SkPackARGB32(sumA / kernelSize,
+ sumR / kernelSize,
+ sumG / kernelSize,
+ sumB / kernelSize);
+ if (y >= topOffset) {
+ SkColor l = *(sptr - topOffset * srcStride);
+ sumA -= SkGetPackedA32(l);
+ sumR -= SkGetPackedR32(l);
+ sumG -= SkGetPackedG32(l);
+ sumB -= SkGetPackedB32(l);
+ }
+ if (y + bottomOffset + 1 < height) {
+ SkColor r = *(sptr + (bottomOffset + 1) * srcStride);
+ sumA += SkGetPackedA32(r);
+ sumR += SkGetPackedR32(r);
+ sumG += SkGetPackedG32(r);
+ sumB += SkGetPackedB32(r);
+ }
+ sptr += srcStride;
+ dptr += dstStride;
+ }
+ }
+}
+
+static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
+ int *highOffset)
+{
+ float pi = SkScalarToFloat(SK_ScalarPI);
+ int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
+ *kernelSize = d;
+ if (d % 2 == 1) {
+ *lowOffset = *highOffset = (d - 1) / 2;
+ *kernelSize3 = d;
+ } else {
+ *highOffset = d / 2;
+ *lowOffset = *highOffset - 1;
+ *kernelSize3 = d + 1;
+ }
+}
+
+bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect srcBounds, dstBounds;
+ src.getBounds(&srcBounds);
+ if (!this->applyCropRect(&srcBounds)) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), srcBounds.width(), srcBounds.height());
+ dst->getBounds(&dstBounds);
+ dst->allocPixels();
+ int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
+ int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
+ getBox3Params(fSigma.width(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
+ getBox3Params(fSigma.height(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
+
+ if (kernelSizeX < 0 || kernelSizeY < 0) {
+ return false;
+ }
+
+ if (kernelSizeX == 0 && kernelSizeY == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (kernelSizeX > 0 && kernelSizeY > 0) {
+ boxBlurX(src, &temp, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurY(temp, dst, kernelSizeY, lowOffsetY, highOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
+ } else if (kernelSizeX > 0) {
+ boxBlurX(src, dst, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurX(temp, dst, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
+ } else if (kernelSizeY > 0) {
+ boxBlurY(src, dst, kernelSizeY, lowOffsetY, highOffsetY, srcBounds);
+ boxBlurY(*dst, &temp, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
+ }
+ offset->fX += srcBounds.fLeft;
+ offset->fY += srcBounds.fTop;
+ return true;
+}
+
+bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+#if SK_SUPPORT_GPU
+ SkBitmap input;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &input, offset)) {
+ return false;
+ }
+ GrTexture* source = input.getTexture();
+ SkIRect rect;
+ src.getBounds(&rect);
+ if (!this->applyCropRect(&rect)) {
+ return false;
+ }
+ SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
+ source,
+ false,
+ SkRect::Make(rect),
+ true,
+ fSigma.width(),
+ fSigma.height()));
+ offset->fX += rect.fLeft;
+ offset->fY += rect.fTop;
+ return SkImageFilterUtils::WrapTexture(tex, rect.width(), rect.height(), result);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return false;
+#endif
+}
diff --git a/effects/SkBlurMask.cpp b/effects/SkBlurMask.cpp
new file mode 100644
index 00000000..c946c5eb
--- /dev/null
+++ b/effects/SkBlurMask.cpp
@@ -0,0 +1,985 @@
+
+/*
+ * 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 "SkBlurMask.h"
+#include "SkMath.h"
+#include "SkTemplates.h"
+#include "SkEndian.h"
+
+const SkScalar SkBlurMask::kBlurRadiusFudgeFactor = SkFloatToScalar(.57735f);
+
+#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.
+ *
+ * This is what the inner loop looks like before unrolling, and with the two
+ * cases broken out separately (width < diameter, width >= diameter):
+ *
+ * if (width < diameter) {
+ * for (int x = 0; x < width; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = width; x < diameter; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < width; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * } else {
+ * for (int x = 0; x < diameter; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = diameter; x < width; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < diameter; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * }
+ */
+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;
+#ifndef SK_DISABLE_BLUR_ROUNDING
+ uint32_t half = 1 << 23;
+#else
+ uint32_t half = 0;
+#endif
+ for (int y = 0; y < height; ++y) {
+ uint32_t 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 + half) >> 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 + half) >> 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 + half) >> 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 + half) >> 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)
+ *
+ * This is what the inner loop looks like before unrolling, and with the two
+ * cases broken out separately (width < diameter, width >= diameter):
+ *
+ * if (width < diameter) {
+ * for (int x = 0; x < width; x++) {
+ * inner_sum = outer_sum;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = width; x < diameter; ++x) {
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < width; x++) {
+ * inner_sum = outer_sum - *left++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum = inner_sum;
+ * }
+ * } else {
+ * for (int x = 0; x < diameter; x++) {
+ * inner_sum = outer_sum;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = diameter; x < width; ++x) {
+ * inner_sum = outer_sum - *left;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum -= *left++;
+ * }
+ * for (int x = 0; x < diameter; x++) {
+ * inner_sum = outer_sum - *left++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum = inner_sum;
+ * }
+ * }
+ * }
+ * return new_width;
+ */
+
+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);
+#ifndef SK_DISABLE_BLUR_ROUNDING
+ uint32_t half = 1 << 23;
+#else
+ uint32_t half = 0;
+#endif
+ 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) {
+ uint32_t 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 + half) >> 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 + half) >> 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 + half) >> 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 + half) >> 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;
+ }
+}
+
+#include "SkColorPriv.h"
+
+static void merge_src_with_blur(uint8_t dst[], int dstRB,
+ const uint8_t src[], int srcRB,
+ const uint8_t blur[], int blurRB,
+ int sw, int sh) {
+ dstRB -= sw;
+ srcRB -= sw;
+ blurRB -= sw;
+ while (--sh >= 0) {
+ for (int x = sw - 1; x >= 0; --x) {
+ *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
+ dst += 1;
+ src += 1;
+ blur += 1;
+ }
+ dst += dstRB;
+ src += srcRB;
+ blur += blurRB;
+ }
+}
+
+static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
+ const uint8_t src[], int srcRowBytes,
+ int sw, int sh,
+ SkBlurMask::Style style) {
+ int x;
+ while (--sh >= 0) {
+ switch (style) {
+ case SkBlurMask::kSolid_Style:
+ for (x = sw - 1; x >= 0; --x) {
+ int s = *src;
+ int d = *dst;
+ *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
+ dst += 1;
+ src += 1;
+ }
+ break;
+ case SkBlurMask::kOuter_Style:
+ for (x = sw - 1; x >= 0; --x) {
+ if (*src) {
+ *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
+ }
+ dst += 1;
+ src += 1;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected blur style here");
+ break;
+ }
+ dst += dstRowBytes - sw;
+ src += srcRowBytes - sw;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// we use a local function to wrap the class static method to work around
+// a bug in gcc98
+void SkMask_FreeImage(uint8_t* image);
+void SkMask_FreeImage(uint8_t* image) {
+ SkMask::FreeImage(image);
+}
+
+bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin)
+{
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ // Force high quality off for small radii (performance)
+ if (radius < SkIntToScalar(3)) {
+ quality = kLow_Quality;
+ }
+
+ // highQuality: use three box blur passes as a cheap way
+ // to approximate a Gaussian blur
+ int passCount = (kHigh_Quality == quality) ? 3 : 1;
+ SkScalar passRadius = (kHigh_Quality == quality) ?
+ SkScalarMul( radius, kBlurRadiusFudgeFactor):
+ radius;
+
+ int rx = SkScalarCeil(passRadius);
+ int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
+
+ SkASSERT(rx >= 0);
+ SkASSERT((unsigned)outerWeight <= 255);
+ if (rx <= 0) {
+ return false;
+ }
+
+ int ry = rx; // only do square blur for now
+
+ int padx = passCount * rx;
+ int pady = passCount * ry;
+
+ if (margin) {
+ margin->set(padx, pady);
+ }
+ dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
+ src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ int sw = src.fBounds.width();
+ int sh = src.fBounds.height();
+ const uint8_t* sp = src.fImage;
+ uint8_t* dp = SkMask::AllocImage(dstSize);
+ SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
+
+ // build the blurry destination
+ SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
+ uint8_t* tp = tmpBuffer.get();
+ int w = sw, h = sh;
+
+ if (outerWeight == 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, outerWeight);
+ w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
+ w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
+ // Do three Y blurs, with a transpose on the final one.
+ h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
+ h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
+ } else {
+ w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
+ }
+ }
+
+ dst->fImage = dp;
+ // if need be, alloc the "real" dst (same size as src) and copy/merge
+ // the blur into it (applying the src)
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = src.computeImageSize();
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ merge_src_with_blur(dst->fImage, src.fRowBytes,
+ sp, src.fRowBytes,
+ dp + passCount * (rx + ry * dst->fRowBytes),
+ dst->fRowBytes, sw, sh);
+ SkMask::FreeImage(dp);
+ } else if (style != kNormal_Style) {
+ clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
+ dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
+ }
+ (void)autoCall.detach();
+ }
+
+ if (style == kInner_Style) {
+ dst->fBounds = src.fBounds; // restore trimmed bounds
+ dst->fRowBytes = src.fRowBytes;
+ }
+
+ return true;
+}
+
+/* Convolving a box with itself three times results in a piecewise
+ quadratic function:
+
+ 0 x <= -1.5
+ 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
+ 3/4 - x^2 -.5 < x <= .5
+ 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
+ 0 1.5 < x
+
+ Mathematica:
+
+ g[x_] := Piecewise [ {
+ {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
+ {3/4 - x^2 , -.5 < x <= .5},
+ {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
+ }, 0]
+
+ To get the profile curve of the blurred step function at the rectangle
+ edge, we evaluate the indefinite integral, which is piecewise cubic:
+
+ 0 x <= -1.5
+ 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5
+ 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
+ 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5
+ 1 1.5 < x
+
+ in Mathematica code:
+
+ gi[x_] := Piecewise[ {
+ { 0 , x <= -1.5 },
+ { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
+ { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
+ { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
+ },1]
+*/
+
+static float gaussianIntegral(float x) {
+ if (x > 1.5f) {
+ return 0.0f;
+ }
+ if (x < -1.5f) {
+ return 1.0f;
+ }
+
+ float x2 = x*x;
+ float x3 = x2*x;
+
+ if ( x > 0.5f ) {
+ return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
+ }
+ if ( x > -0.5f ) {
+ return 0.5f - (0.75f * x - x3 / 3.0f);
+ }
+ return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
+}
+
+// Compute the size of the array allocated for the profile.
+
+static int compute_profile_size(SkScalar radius) {
+ return SkScalarRoundToInt(radius * 3);
+
+}
+
+/* compute_profile allocates and fills in an array of floating
+ point values between 0 and 255 for the profile signature of
+ a blurred half-plane with the given blur radius. Since we're
+ going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
+ all the time, we actually fill in the profile pre-inverted
+ (already done 255-x).
+
+ It's the responsibility of the caller to delete the
+ memory returned in profile_out.
+*/
+
+static void compute_profile(SkScalar radius, unsigned int **profile_out) {
+ int size = compute_profile_size(radius);
+
+ int center = size >> 1;
+ unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
+
+ float invr = 1.f/radius;
+
+ profile[0] = 255;
+ for (int x = 1 ; x < size ; ++x) {
+ float scaled_x = (center - x - .5f) * invr;
+ float gi = gaussianIntegral(scaled_x);
+ profile[x] = 255 - (uint8_t) (255.f * gi);
+ }
+
+ *profile_out = profile;
+}
+
+// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
+// commonly used radii. Consider baking some of the most common blur radii
+// directly in as static data?
+
+// Implementation adapted from Michael Herf's approach:
+// http://stereopsis.com/shadowrect/
+
+static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
+ int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
+ int ox = dx >> 1;
+ if (ox < 0) {
+ ox = 0;
+ }
+
+ return profile[ox];
+}
+
+bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
+ SkScalar provided_radius, Style style,
+ SkIPoint *margin, SkMask::CreateMode createMode) {
+ int profile_size;
+
+ float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
+
+ // adjust blur radius to match interpretation from boxfilter code
+ radius = (radius + .5f) * 2.f;
+
+ profile_size = compute_profile_size(radius);
+
+ int pad = profile_size/2;
+ if (margin) {
+ margin->set( pad, pad );
+ }
+
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
+ SkScalarRoundToInt(src.fTop - pad),
+ SkScalarRoundToInt(src.fRight + pad),
+ SkScalarRoundToInt(src.fBottom + pad));
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ int sw = SkScalarFloorToInt(src.width());
+ int sh = SkScalarFloorToInt(src.height());
+
+ if (createMode == SkMask::kJustComputeBounds_CreateMode) {
+ if (style == kInner_Style) {
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
+ SkScalarRoundToInt(src.fTop),
+ SkScalarRoundToInt(src.fRight),
+ SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
+ dst->fRowBytes = sw;
+ }
+ return true;
+ }
+ unsigned int *profile = NULL;
+
+ compute_profile(radius, &profile);
+ SkAutoTDeleteArray<unsigned int> ada(profile);
+
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ uint8_t* dp = SkMask::AllocImage(dstSize);
+
+ dst->fImage = dp;
+
+ int dstHeight = dst->fBounds.height();
+ int dstWidth = dst->fBounds.width();
+
+ // nearest odd number less than the profile size represents the center
+ // of the (2x scaled) profile
+ int center = ( profile_size & ~1 ) - 1;
+
+ int w = sw - center;
+ int h = sh - center;
+
+ uint8_t *outptr = dp;
+
+ SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
+
+ for (int x = 0 ; x < dstWidth ; ++x) {
+ if (profile_size <= sw) {
+ horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
+ } else {
+ float span = float(sw)/radius;
+ float giX = 1.5f - (x+.5f)/radius;
+ horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
+ }
+ }
+
+ for (int y = 0 ; y < dstHeight ; ++y) {
+ unsigned int profile_y;
+ if (profile_size <= sh) {
+ profile_y = profile_lookup(profile, y, dstHeight, h);
+ } else {
+ float span = float(sh)/radius;
+ float giY = 1.5f - (y+.5f)/radius;
+ profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
+ }
+
+ for (int x = 0 ; x < dstWidth ; x++) {
+ unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
+ *(outptr++) = maskval;
+ }
+ }
+
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = (size_t)(src.width() * src.height());
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ for (int y = 0 ; y < sh ; y++) {
+ uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
+ uint8_t *inner_scanline = dst->fImage + y*sw;
+ memcpy(inner_scanline, blur_scanline, sw);
+ }
+ SkMask::FreeImage(dp);
+
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
+ SkScalarRoundToInt(src.fTop),
+ SkScalarRoundToInt(src.fRight),
+ SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
+ dst->fRowBytes = sw;
+
+ } else if (style == kOuter_Style) {
+ for (int y = pad ; y < dstHeight-pad ; y++) {
+ uint8_t *dst_scanline = dp + y*dstWidth + pad;
+ memset(dst_scanline, 0, sw);
+ }
+ } else if (style == kSolid_Style) {
+ for (int y = pad ; y < dstHeight-pad ; y++) {
+ uint8_t *dst_scanline = dp + y*dstWidth + pad;
+ memset(dst_scanline, 0xff, sw);
+ }
+ }
+ // normal and solid styles are the same for analytic rect blurs, so don't
+ // need to handle solid specially.
+
+ return true;
+}
+
+// The "simple" blur is a direct implementation of separable convolution with a discrete
+// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
+// useful for correctness comparisons.
+
+bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provided_radius,
+ Style style, SkIPoint* margin) {
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
+ float stddev = SkScalarToFloat(radius) /2.0f;
+ float variance = stddev * stddev;
+
+ int windowSize = SkScalarCeil(stddev*4);
+ // round window size up to nearest odd number
+ windowSize |= 1;
+
+ SkAutoTMalloc<float> gaussWindow(windowSize);
+
+ int halfWindow = windowSize >> 1;
+
+ gaussWindow[halfWindow] = 1;
+
+ float windowSum = 1;
+ for (int x = 1 ; x <= halfWindow ; ++x) {
+ float gaussian = expf(-x*x / variance);
+ gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
+ windowSum += 2*gaussian;
+ }
+
+ // leave the filter un-normalized for now; we will divide by the normalization
+ // sum later;
+
+ int pad = halfWindow;
+ if (margin) {
+ margin->set( pad, pad );
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fBounds.outset(pad, pad);
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ int srcWidth = src.fBounds.width();
+ int srcHeight = src.fBounds.height();
+ int dstWidth = dst->fBounds.width();
+
+ const uint8_t* srcPixels = src.fImage;
+ uint8_t* dstPixels = SkMask::AllocImage(dstSize);
+ SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
+
+ // do the actual blur. First, make a padded copy of the source.
+ // use double pad so we never have to check if we're outside anything
+
+ int padWidth = srcWidth + 4*pad;
+ int padHeight = srcHeight;
+ int padSize = padWidth * padHeight;
+
+ SkAutoTMalloc<uint8_t> padPixels(padSize);
+ memset(padPixels, 0, padSize);
+
+ for (int y = 0 ; y < srcHeight; ++y) {
+ uint8_t* padptr = padPixels + y * padWidth + 2*pad;
+ const uint8_t* srcptr = srcPixels + y * srcWidth;
+ memcpy(padptr, srcptr, srcWidth);
+ }
+
+ // blur in X, transposing the result into a temporary floating point buffer.
+ // also double-pad the intermediate result so that the second blur doesn't
+ // have to do extra conditionals.
+
+ int tmpWidth = padHeight + 4*pad;
+ int tmpHeight = padWidth - 2*pad;
+ int tmpSize = tmpWidth * tmpHeight;
+
+ SkAutoTMalloc<float> tmpImage(tmpSize);
+ memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
+
+ for (int y = 0 ; y < padHeight ; ++y) {
+ uint8_t *srcScanline = padPixels + y*padWidth;
+ for (int x = pad ; x < padWidth - pad ; ++x) {
+ float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
+ uint8_t *windowCenter = srcScanline + x;
+ for (int i = -pad ; i <= pad ; ++i) {
+ *outPixel += gaussWindow[pad+i]*windowCenter[i];
+ }
+ *outPixel /= windowSum;
+ }
+ }
+
+ // blur in Y; now filling in the actual desired destination. We have to do
+ // the transpose again; these transposes guarantee that we read memory in
+ // linear order.
+
+ for (int y = 0 ; y < tmpHeight ; ++y) {
+ float *srcScanline = tmpImage + y*tmpWidth;
+ for (int x = pad ; x < tmpWidth - pad ; ++x) {
+ float *windowCenter = srcScanline + x;
+ float finalValue = 0;
+ for (int i = -pad ; i <= pad ; ++i) {
+ finalValue += gaussWindow[pad+i]*windowCenter[i];
+ }
+ finalValue /= windowSum;
+ uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
+ int integerPixel = int(finalValue + 0.5f);
+ *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
+ }
+ }
+
+ dst->fImage = dstPixels;
+ // if need be, alloc the "real" dst (same size as src) and copy/merge
+ // the blur into it (applying the src)
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = src.computeImageSize();
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ merge_src_with_blur(dst->fImage, src.fRowBytes,
+ srcPixels, src.fRowBytes,
+ dstPixels + pad*dst->fRowBytes + pad,
+ dst->fRowBytes, srcWidth, srcHeight);
+ SkMask::FreeImage(dstPixels);
+ } else if (style != kNormal_Style) {
+ clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
+ dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
+ }
+ (void)autoCall.detach();
+ }
+
+ if (style == kInner_Style) {
+ dst->fBounds = src.fBounds; // restore trimmed bounds
+ dst->fRowBytes = src.fRowBytes;
+ }
+
+ return true;
+}
diff --git a/effects/SkBlurMask.h b/effects/SkBlurMask.h
new file mode 100644
index 00000000..36d78000
--- /dev/null
+++ b/effects/SkBlurMask.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBlurMask_DEFINED
+#define SkBlurMask_DEFINED
+
+#include "SkShader.h"
+#include "SkMask.h"
+
+class SkBlurMask {
+public:
+ enum Style {
+ kNormal_Style, //!< fuzzy inside and outside
+ kSolid_Style, //!< solid inside, fuzzy outside
+ kOuter_Style, //!< nothing inside, fuzzy outside
+ kInner_Style, //!< fuzzy inside, nothing outside
+
+ kStyleCount
+ };
+
+ enum Quality {
+ kLow_Quality, //!< box blur
+ kHigh_Quality //!< three pass box blur (similar to gaussian)
+ };
+
+ static bool BlurRect(SkMask *dst, const SkRect &src,
+ SkScalar radius, Style style,
+ SkIPoint *margin = NULL,
+ SkMask::CreateMode createMode=SkMask::kComputeBoundsAndRenderImage_CreateMode);
+ static bool Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin = NULL);
+
+ // the "ground truth" blur does a gaussian convolution; it's slow
+ // but useful for comparison purposes.
+
+ static bool BlurGroundTruth(SkMask* dst, const SkMask& src,
+ SkScalar provided_radius, Style style,
+ SkIPoint* margin = NULL);
+
+ // scale factor for the blur radius to match the behavior of the all existing blur
+ // code (both on the CPU and the GPU). This magic constant is 1/sqrt(3).
+ // TODO: get rid of this fudge factor and move any required fudging up into
+ // the calling library
+ static const SkScalar kBlurRadiusFudgeFactor;
+
+};
+
+#endif
diff --git a/effects/SkBlurMaskFilter.cpp b/effects/SkBlurMaskFilter.cpp
new file mode 100644
index 00000000..b54c3300
--- /dev/null
+++ b/effects/SkBlurMaskFilter.cpp
@@ -0,0 +1,499 @@
+
+/*
+ * 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 "SkBlurMaskFilter.h"
+#include "SkBlurMask.h"
+#include "SkGpuBlurUtils.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMaskFilter.h"
+#include "SkRTConf.h"
+#include "SkStringUtils.h"
+#include "SkStrokeRec.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGrPixelRef.h"
+#endif
+
+class SkBlurMaskFilterImpl : public SkMaskFilter {
+public:
+ SkBlurMaskFilterImpl(SkScalar radius, SkBlurMaskFilter::BlurStyle,
+ uint32_t flags);
+
+ // overrides from SkMaskFilter
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
+ virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
+ SkIPoint* margin) const SK_OVERRIDE;
+
+#if SK_SUPPORT_GPU
+ virtual bool canFilterMaskGPU(const SkRect& devBounds,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const SK_OVERRIDE;
+ virtual bool filterMaskGPU(GrTexture* src,
+ const SkRect& maskRect,
+ GrTexture** result,
+ bool canOverwriteSrc) const;
+#endif
+
+ virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE;
+
+ SkDEVCODE(virtual void toString(SkString* str) 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;
+
+ bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
+ SkIPoint* margin, SkMask::CreateMode createMode) const;
+
+private:
+ // To avoid unseemly allocation requests (esp. for finite platforms like
+ // handset) we limit the radius so something manageable. (as opposed to
+ // a request like 10,000)
+ static const SkScalar kMAX_BLUR_RADIUS;
+ // This constant approximates the scaling done in the software path's
+ // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+ // IMHO, it actually should be 1: we blur "less" than we should do
+ // according to the CSS and canvas specs, simply because Safari does the same.
+ // Firefox used to do the same too, until 4.0 where they fixed it. So at some
+ // point we should probably get rid of these scaling constants and rebaseline
+ // all the blur tests.
+ static const SkScalar kBLUR_SIGMA_SCALE;
+
+ SkScalar fRadius;
+ SkBlurMaskFilter::BlurStyle fBlurStyle;
+ uint32_t fBlurFlags;
+
+ SkBlurMaskFilterImpl(SkFlattenableReadBuffer&);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+ SkScalar computeXformedRadius(const SkMatrix& ctm) const {
+ bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
+
+ SkScalar xformedRadius = ignoreTransform ? fRadius
+ : ctm.mapRadius(fRadius);
+ return SkMinScalar(xformedRadius, kMAX_BLUR_RADIUS);
+ }
+#endif
+
+ typedef SkMaskFilter INHERITED;
+};
+
+const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_RADIUS = SkIntToScalar(128);
+const SkScalar SkBlurMaskFilterImpl::kBLUR_SIGMA_SCALE = SkFloatToScalar(0.6f);
+
+SkMaskFilter* SkBlurMaskFilter::Create(SkScalar radius,
+ SkBlurMaskFilter::BlurStyle style,
+ uint32_t flags) {
+ // use !(radius > 0) instead of radius <= 0 to reject NaN values
+ if (!(radius > 0) || (unsigned)style >= SkBlurMaskFilter::kBlurStyleCount
+ || flags > SkBlurMaskFilter::kAll_BlurFlag) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkBlurMaskFilterImpl, (radius, style, flags));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar radius,
+ SkBlurMaskFilter::BlurStyle style,
+ uint32_t flags)
+ : fRadius(radius), fBlurStyle(style), fBlurFlags(flags) {
+#if 0
+ fGamma = NULL;
+ if (gammaScale) {
+ fGamma = new U8[256];
+ if (gammaScale > 0)
+ SkBlurMask::BuildSqrGamma(fGamma, gammaScale);
+ else
+ SkBlurMask::BuildSqrtGamma(fGamma, -gammaScale);
+ }
+#endif
+ SkASSERT(radius >= 0);
+ SkASSERT((unsigned)style < SkBlurMaskFilter::kBlurStyleCount);
+ SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
+}
+
+SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix& matrix,
+ SkIPoint* margin) const{
+ SkScalar radius;
+ if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
+ radius = fRadius;
+ } else {
+ radius = matrix.mapRadius(fRadius);
+ }
+
+ radius = SkMinScalar(radius, kMAX_BLUR_RADIUS);
+ SkBlurMask::Quality blurQuality =
+ (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
+ SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality;
+
+ return SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
+ blurQuality, margin);
+}
+
+bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
+ const SkMatrix& matrix,
+ SkIPoint* margin, SkMask::CreateMode createMode) const{
+ SkScalar radius;
+ if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
+ radius = fRadius;
+ } else {
+ radius = matrix.mapRadius(fRadius);
+ }
+
+ radius = SkMinScalar(radius, kMAX_BLUR_RADIUS);
+
+ return SkBlurMask::BlurRect(dst, r, radius, (SkBlurMask::Style)fBlurStyle,
+ margin, createMode);
+}
+
+#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;
+}
+
+#ifdef SK_IGNORE_FAST_RECT_BLUR
+SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", false, "Use the faster analytic blur approach for ninepatch rects" );
+#else
+SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", true, "Use the faster analytic blur approach for ninepatch rects" );
+#endif
+
+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;
+
+ bool filterResult = false;
+ if (count == 1 && c_analyticBlurNinepatch) {
+ // special case for fast rect blur
+ // don't actually do the blur the first time, just compute the correct size
+ filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
+ SkMask::kJustComputeBounds_CreateMode);
+ } else {
+ filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
+ }
+
+ if (!filterResult) {
+ 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 (count > 1 || !c_analyticBlurNinepatch) {
+ if (!drawRectsIntoMask(smallR, count, &srcM)) {
+ return kFalse_FilterReturn;
+ }
+
+ SkAutoMaskFreeImage amf(srcM.fImage);
+
+ if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
+ return kFalse_FilterReturn;
+ }
+ } else {
+ if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
+ 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) const {
+ SkScalar gpuPad, rasterPad;
+
+ {
+ // GPU path
+ SkScalar sigma = SkScalarMul(fRadius, kBLUR_SIGMA_SCALE);
+ gpuPad = sigma * 3.0f;
+ }
+
+ {
+ // raster path
+ SkScalar radius = SkScalarMul(fRadius, SkBlurMask::kBlurRadiusFudgeFactor);
+
+ radius = (radius + .5f) * 2.f;
+
+ rasterPad = SkIntToScalar(SkScalarRoundToInt(radius * 3)/2);
+ }
+
+ SkScalar pad = SkMaxScalar(gpuPad, rasterPad);
+
+ dst->set(src.fLeft - pad, src.fTop - pad,
+ src.fRight + pad, src.fBottom + pad);
+}
+
+SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkFlattenableReadBuffer& buffer)
+ : SkMaskFilter(buffer) {
+ fRadius = buffer.readScalar();
+ fBlurStyle = (SkBlurMaskFilter::BlurStyle)buffer.readInt();
+ fBlurFlags = buffer.readUInt() & SkBlurMaskFilter::kAll_BlurFlag;
+ SkASSERT(fRadius >= 0);
+ SkASSERT((unsigned)fBlurStyle < SkBlurMaskFilter::kBlurStyleCount);
+}
+
+void SkBlurMaskFilterImpl::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fRadius);
+ buffer.writeInt(fBlurStyle);
+ buffer.writeUInt(fBlurFlags);
+}
+
+#if SK_SUPPORT_GPU
+
+bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const {
+ SkScalar xformedRadius = this->computeXformedRadius(ctm);
+ if (xformedRadius <= 0) {
+ return false;
+ }
+
+ static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
+ static const SkScalar kMIN_GPU_BLUR_RADIUS = SkIntToScalar(32);
+
+ if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE &&
+ srcBounds.height() <= kMIN_GPU_BLUR_SIZE &&
+ xformedRadius <= kMIN_GPU_BLUR_RADIUS) {
+ // We prefer to blur small rect with small radius via CPU.
+ return false;
+ }
+
+ if (NULL == maskRect) {
+ // don't need to compute maskRect
+ return true;
+ }
+
+ float sigma3 = 3 * SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+ SkRect clipRect = SkRect::MakeFromIRect(clipBounds);
+ SkRect srcRect(srcBounds);
+
+ // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
+ srcRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+ clipRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+ srcRect.intersect(clipRect);
+ *maskRect = srcRect;
+ return true;
+}
+
+bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src,
+ const SkRect& maskRect,
+ GrTexture** result,
+ bool canOverwriteSrc) const {
+ SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
+
+ GrContext* context = src->getContext();
+
+ GrContext::AutoWideOpenIdentityDraw awo(context, NULL);
+
+ SkScalar xformedRadius = this->computeXformedRadius(context->getMatrix());
+ SkASSERT(xformedRadius > 0);
+
+ float sigma = SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+ // 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 = (SkBlurMaskFilter::kNormal_BlurStyle == fBlurStyle);
+ *result = SkGpuBlurUtils::GaussianBlur(context, src, isNormalBlur && canOverwriteSrc,
+ clipRect, false, sigma, sigma);
+ if (NULL == *result) {
+ return false;
+ }
+
+ if (!isNormalBlur) {
+ context->setIdentityMatrix();
+ GrPaint paint;
+ SkMatrix matrix;
+ matrix.setIDiv(src->width(), src->height());
+ // Blend pathTexture over blurTexture.
+ GrContext::AutoRenderTarget art(context, (*result)->asRenderTarget());
+ paint.addColorEffect(GrSimpleTextureEffect::Create(src, matrix))->unref();
+ if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+ // inner: dst = dst * src
+ paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
+ } else if (SkBlurMaskFilter::kSolid_BlurStyle == fBlurStyle) {
+ // solid: dst = src + dst - src * dst
+ // = (1 - dst) * src + 1 * dst
+ paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff);
+ } else if (SkBlurMaskFilter::kOuter_BlurStyle == fBlurStyle) {
+ // outer: dst = dst * (1 - src)
+ // = 0 * src + (1 - src) * dst
+ paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
+ }
+ context->drawRect(paint, clipRect);
+ }
+
+ return true;
+}
+
+#endif // SK_SUPPORT_GPU
+
+
+#ifdef SK_DEVELOPER
+void SkBlurMaskFilterImpl::toString(SkString* str) const {
+ str->append("SkBlurMaskFilterImpl: (");
+
+ str->append("radius: ");
+ str->appendScalar(fRadius);
+ str->append(" ");
+
+ static const char* gStyleName[SkBlurMaskFilter::kBlurStyleCount] = {
+ "normal", "solid", "outer", "inner"
+ };
+
+ str->appendf("style: %s ", gStyleName[fBlurStyle]);
+ str->append("flags: (");
+ if (fBlurFlags) {
+ bool needSeparator = false;
+ SkAddFlagToString(str,
+ SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag),
+ "IgnoreXform", &needSeparator);
+ SkAddFlagToString(str,
+ SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag),
+ "HighQuality", &needSeparator);
+ } else {
+ str->append("None");
+ }
+ str->append("))");
+}
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkBlurMaskFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkColorFilterImageFilter.cpp b/effects/SkColorFilterImageFilter.cpp
new file mode 100755
index 00000000..9c2c54eb
--- /dev/null
+++ b/effects/SkColorFilterImageFilter.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "SkColorFilterImageFilter.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorMatrixFilter.h"
+#include "SkDevice.h"
+#include "SkColorFilter.h"
+#include "SkFlattenableBuffers.h"
+
+namespace {
+
+void mult_color_matrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) {
+ for (int j = 0; j < 4; ++j) {
+ for (int i = 0; i < 5; ++i) {
+ out[i+j*5] = 4 == i ? a[4+j*5] : 0;
+ for (int k = 0; k < 4; ++k)
+ out[i+j*5] += SkScalarMul(a[k+j*5], b[i+k*5]);
+ }
+ }
+}
+
+// To detect if we need to apply clamping after applying a matrix, we check if
+// any output component might go outside of [0, 255] for any combination of
+// input components in [0..255].
+// Each output component is an affine transformation of the input component, so
+// the minimum and maximum values are for any combination of minimum or maximum
+// values of input components (i.e. 0 or 255).
+// E.g. if R' = x*R + y*G + z*B + w*A + t
+// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
+// minimum value will be for R=0 if x>0 or R=255 if x<0.
+// Same goes for all components.
+bool component_needs_clamping(SkScalar row[5]) {
+ SkScalar maxValue = row[4] / 255;
+ SkScalar minValue = row[4] / 255;
+ for (int i = 0; i < 4; ++i) {
+ if (row[i] > 0)
+ maxValue += row[i];
+ else
+ minValue += row[i];
+ }
+ return (maxValue > 1) || (minValue < 0);
+}
+
+bool matrix_needs_clamping(SkScalar matrix[20]) {
+ return component_needs_clamping(matrix)
+ || component_needs_clamping(matrix+5)
+ || component_needs_clamping(matrix+10)
+ || component_needs_clamping(matrix+15);
+}
+
+};
+
+SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf,
+ SkImageFilter* input, const SkIRect* cropRect) {
+ SkASSERT(cf);
+ SkScalar colorMatrix[20], inputMatrix[20];
+ SkColorFilter* inputColorFilter;
+ if (input && cf->asColorMatrix(colorMatrix)
+ && input->asColorFilter(&inputColorFilter)
+ && (NULL != inputColorFilter)) {
+ SkAutoUnref autoUnref(inputColorFilter);
+ if (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), cropRect));
+ }
+ }
+ return SkNEW_ARGS(SkColorFilterImageFilter, (cf, input, cropRect));
+}
+
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkColorFilter* cf,
+ SkImageFilter* input, const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fColorFilter(cf) {
+ SkASSERT(cf);
+ SkSafeRef(cf);
+}
+
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fColorFilter = buffer.readFlattenableT<SkColorFilter>();
+}
+
+void SkColorFilterImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeFlattenable(fColorFilter);
+}
+
+SkColorFilterImageFilter::~SkColorFilterImageFilter() {
+ SkSafeUnref(fColorFilter);
+}
+
+bool SkColorFilterImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(bounds.width(), bounds.height()));
+ SkCanvas canvas(device.get());
+ SkPaint paint;
+
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ paint.setColorFilter(fColorFilter);
+ canvas.drawSprite(src, -bounds.fLeft, -bounds.fTop, &paint);
+
+ *result = device.get()->accessBitmap(false);
+ loc->fX += bounds.fLeft;
+ loc->fY += bounds.fTop;
+ return true;
+}
+
+bool SkColorFilterImageFilter::asColorFilter(SkColorFilter** filter) const {
+ if (cropRect().isLargest()) {
+ if (filter) {
+ *filter = fColorFilter;
+ fColorFilter->ref();
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/effects/SkColorFilters.cpp b/effects/SkColorFilters.cpp
new file mode 100644
index 00000000..41a201ef
--- /dev/null
+++ b/effects/SkColorFilters.cpp
@@ -0,0 +1,547 @@
+
+/*
+ * 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 "SkBlitRow.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUtils.h"
+#include "SkString.h"
+
+#define ILLEGAL_XFERMODE_MODE ((SkXfermode::Mode)-1)
+
+// baseclass for filters that store a color and mode
+class SkModeColorFilter : public SkColorFilter {
+public:
+ SkModeColorFilter(SkColor color) {
+ fColor = color;
+ fMode = ILLEGAL_XFERMODE_MODE;
+ this->updateCache();
+ }
+
+ SkModeColorFilter(SkColor color, SkXfermode::Mode mode) {
+ fColor = color;
+ fMode = mode;
+ this->updateCache();
+ };
+
+ SkColor getColor() const { return fColor; }
+ SkXfermode::Mode getMode() const { return fMode; }
+ bool isModeValid() const { return ILLEGAL_XFERMODE_MODE != fMode; }
+ SkPMColor getPMColor() const { return fPMColor; }
+
+ virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode) const SK_OVERRIDE {
+ if (ILLEGAL_XFERMODE_MODE == fMode) {
+ return false;
+ }
+
+ if (color) {
+ *color = fColor;
+ }
+ if (mode) {
+ *mode = fMode;
+ }
+ return true;
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ return fProc16 ? (kAlphaUnchanged_Flag | kHasFilter16_Flag) : 0;
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ SkPMColor color = fPMColor;
+ SkXfermodeProc proc = fProc;
+
+ for (int i = 0; i < count; i++) {
+ result[i] = proc(color, shader[i]);
+ }
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+
+ SkPMColor color = fPMColor;
+ SkXfermodeProc16 proc16 = fProc16;
+
+ for (int i = 0; i < count; i++) {
+ result[i] = proc16(color, shader[i]);
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkModeColorFilter: color: 0x");
+ str->appendHex(fColor);
+ str->append(" mode: ");
+ str->append(SkXfermode::ModeName(fMode));
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkModeColorFilter)
+
+protected:
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeColor(fColor);
+ buffer.writeUInt(fMode);
+ }
+
+ SkModeColorFilter(SkFlattenableReadBuffer& buffer) {
+ fColor = buffer.readColor();
+ fMode = (SkXfermode::Mode)buffer.readUInt();
+ this->updateCache();
+ }
+
+private:
+ SkColor fColor;
+ SkXfermode::Mode fMode;
+ // cache
+ SkPMColor fPMColor;
+ SkXfermodeProc fProc;
+ SkXfermodeProc16 fProc16;
+
+ void updateCache() {
+ fPMColor = SkPreMultiplyColor(fColor);
+ fProc = SkXfermode::GetProc(fMode);
+ fProc16 = SkXfermode::GetProc16(fMode, fColor);
+ }
+
+ typedef SkColorFilter INHERITED;
+};
+
+class Src_SkModeColorFilter : public SkModeColorFilter {
+public:
+ Src_SkModeColorFilter(SkColor color) : INHERITED(color, SkXfermode::kSrc_Mode) {}
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ if (SkGetPackedA32(this->getPMColor()) == 0xFF) {
+ return kAlphaUnchanged_Flag | kHasFilter16_Flag;
+ } else {
+ return 0;
+ }
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ sk_memset32(result, this->getPMColor(), count);
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+ sk_memset16(result, SkPixel32ToPixel16(this->getPMColor()), count);
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Src_SkModeColorFilter)
+
+protected:
+ Src_SkModeColorFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkModeColorFilter INHERITED;
+};
+
+class SrcOver_SkModeColorFilter : public SkModeColorFilter {
+public:
+ SrcOver_SkModeColorFilter(SkColor color)
+ : INHERITED(color, SkXfermode::kSrcOver_Mode) {
+ fColor32Proc = SkBlitRow::ColorProcFactory();
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ if (SkGetPackedA32(this->getPMColor()) == 0xFF) {
+ return kAlphaUnchanged_Flag | kHasFilter16_Flag;
+ } else {
+ return 0;
+ }
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ fColor32Proc(result, shader, count, this->getPMColor());
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+ sk_memset16(result, SkPixel32ToPixel16(this->getPMColor()), count);
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SrcOver_SkModeColorFilter)
+
+protected:
+ SrcOver_SkModeColorFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fColor32Proc = SkBlitRow::ColorProcFactory();
+ }
+
+private:
+
+ SkBlitRow::ColorProc fColor32Proc;
+
+ typedef SkModeColorFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorFilter* SkColorFilter::CreateModeFilter(SkColor color,
+ SkXfermode::Mode mode) {
+ unsigned alpha = SkColorGetA(color);
+
+ // first collaps some modes if possible
+
+ if (SkXfermode::kClear_Mode == mode) {
+ color = 0;
+ mode = SkXfermode::kSrc_Mode;
+ } else if (SkXfermode::kSrcOver_Mode == mode) {
+ if (0 == alpha) {
+ mode = SkXfermode::kDst_Mode;
+ } else if (255 == alpha) {
+ mode = SkXfermode::kSrc_Mode;
+ }
+ // else just stay srcover
+ }
+
+ // weed out combinations that are noops, and just return null
+ if (SkXfermode::kDst_Mode == mode ||
+ (0 == alpha && (SkXfermode::kSrcOver_Mode == mode ||
+ SkXfermode::kDstOver_Mode == mode ||
+ SkXfermode::kDstOut_Mode == mode ||
+ SkXfermode::kSrcATop_Mode == mode ||
+ SkXfermode::kXor_Mode == mode ||
+ SkXfermode::kDarken_Mode == mode)) ||
+ (0xFF == alpha && SkXfermode::kDstIn_Mode == mode)) {
+ return NULL;
+ }
+
+ switch (mode) {
+ case SkXfermode::kSrc_Mode:
+ return SkNEW_ARGS(Src_SkModeColorFilter, (color));
+ case SkXfermode::kSrcOver_Mode:
+ return SkNEW_ARGS(SrcOver_SkModeColorFilter, (color));
+ default:
+ return SkNEW_ARGS(SkModeColorFilter, (color, mode));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline unsigned pin(unsigned value, unsigned max) {
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+class SkLightingColorFilter : public SkColorFilter {
+public:
+ SkLightingColorFilter(SkColor mul, SkColor add) : fMul(mul), fAdd(add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = pin(SkAlphaMul(SkGetPackedR32(c), scaleR) + SkAlphaMul(addR, scaleA), a);
+ unsigned g = pin(SkAlphaMul(SkGetPackedG32(c), scaleG) + SkAlphaMul(addG, scaleA), a);
+ unsigned b = pin(SkAlphaMul(SkGetPackedB32(c), scaleB) + SkAlphaMul(addB, scaleA), a);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter: mul: 0x");
+ str->appendHex(fMul);
+ str->append(" add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter)
+
+protected:
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeColor(fMul);
+ buffer.writeColor(fAdd);
+ }
+
+ SkLightingColorFilter(SkFlattenableReadBuffer& buffer) {
+ fMul = buffer.readColor();
+ fAdd = buffer.readColor();
+ }
+
+ SkColor fMul, fAdd;
+
+private:
+ typedef SkColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_JustAdd : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_JustAdd(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = pin(SkGetPackedR32(c) + SkAlphaMul(addR, scaleA), a);
+ unsigned g = pin(SkGetPackedG32(c) + SkAlphaMul(addG, scaleA), a);
+ unsigned b = pin(SkGetPackedB32(c) + SkAlphaMul(addB, scaleA), a);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_JustAdd: add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_JustAdd)
+
+protected:
+ SkLightingColorFilter_JustAdd(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_JustMul : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_JustMul(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkAlphaMul(SkGetPackedR32(c), scaleR);
+ unsigned g = SkAlphaMul(SkGetPackedG32(c), scaleG);
+ unsigned b = SkAlphaMul(SkGetPackedB32(c), scaleB);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_JustMul: mul: 0x");
+ str->appendHex(fMul);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_JustMul)
+
+protected:
+ SkLightingColorFilter_JustMul(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_SingleMul : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_SingleMul(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {
+ SkASSERT(SkColorGetR(add) == 0);
+ SkASSERT(SkColorGetG(add) == 0);
+ SkASSERT(SkColorGetB(add) == 0);
+ SkASSERT(SkColorGetR(mul) == SkColorGetG(mul));
+ SkASSERT(SkColorGetR(mul) == SkColorGetB(mul));
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ return this->INHERITED::getFlags() | (kAlphaUnchanged_Flag | kHasFilter16_Flag);
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ // all mul components are the same
+ unsigned scale = SkAlpha255To256(SkColorGetR(fMul));
+
+ if (count > 0) {
+ do {
+ *result++ = SkAlphaMulRGB16(*shader++, scale);
+ } while (--count > 0);
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_SingleMul: mul: 0x");
+ str->appendHex(fMul);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_SingleMul)
+
+protected:
+ SkLightingColorFilter_SingleMul(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_NoPin : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_NoPin(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = SkAlphaMul(SkGetPackedR32(c), scaleR) + SkAlphaMul(addR, scaleA);
+ unsigned g = SkAlphaMul(SkGetPackedG32(c), scaleG) + SkAlphaMul(addG, scaleA);
+ unsigned b = SkAlphaMul(SkGetPackedB32(c), scaleB) + SkAlphaMul(addB, scaleA);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_NoPin: mul: 0x");
+ str->appendHex(fMul);
+ str->append(" add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_NoPin)
+
+protected:
+ SkLightingColorFilter_NoPin(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSimpleColorFilter : public SkColorFilter {
+public:
+ static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+ return SkNEW(SkSimpleColorFilter);
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkSimpleColorFilter");
+ }
+#endif
+
+protected:
+ void filterSpan(const SkPMColor src[], int count, SkPMColor
+ result[]) const SK_OVERRIDE {
+ if (result != src) {
+ memcpy(result, src, count * sizeof(SkPMColor));
+ }
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {}
+
+ virtual Factory getFactory() {
+ return CreateProc;
+ }
+
+};
+
+SkColorFilter* SkColorFilter::CreateLightingFilter(SkColor mul, SkColor add) {
+ mul &= 0x00FFFFFF;
+ add &= 0x00FFFFFF;
+
+ if (0xFFFFFF == mul) {
+ if (0 == add) {
+ return SkNEW(SkSimpleColorFilter); // no change to the colors
+ } else {
+ return SkNEW_ARGS(SkLightingColorFilter_JustAdd, (mul, add));
+ }
+ }
+
+ if (0 == add) {
+ if (SkColorGetR(mul) == SkColorGetG(mul) &&
+ SkColorGetR(mul) == SkColorGetB(mul)) {
+ return SkNEW_ARGS(SkLightingColorFilter_SingleMul, (mul, add));
+ } else {
+ return SkNEW_ARGS(SkLightingColorFilter_JustMul, (mul, add));
+ }
+ }
+
+ if (SkColorGetR(mul) + SkColorGetR(add) <= 255 &&
+ SkColorGetG(mul) + SkColorGetG(add) <= 255 &&
+ SkColorGetB(mul) + SkColorGetB(add) <= 255) {
+ return SkNEW_ARGS(SkLightingColorFilter_NoPin, (mul, add));
+ }
+
+ return SkNEW_ARGS(SkLightingColorFilter, (mul, add));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Src_SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SrcOver_SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_JustAdd)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_JustMul)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_SingleMul)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_NoPin)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSimpleColorFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkColorMatrix.cpp b/effects/SkColorMatrix.cpp
new file mode 100644
index 00000000..d6cb9405
--- /dev/null
+++ b/effects/SkColorMatrix.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 "SkColorMatrix.h"
+#include "SkFlattenableBuffers.h"
+
+#define kRScale 0
+#define kGScale 6
+#define kBScale 12
+#define kAScale 18
+
+void SkColorMatrix::setIdentity() {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[kRScale] = fMat[kGScale] = fMat[kBScale] = fMat[kAScale] = SK_Scalar1;
+}
+
+void SkColorMatrix::setScale(SkScalar rScale, SkScalar gScale, SkScalar bScale,
+ SkScalar aScale) {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[kRScale] = rScale;
+ fMat[kGScale] = gScale;
+ fMat[kBScale] = bScale;
+ fMat[kAScale] = aScale;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrix::setRotate(Axis axis, SkScalar degrees) {
+ SkScalar S, C;
+
+ S = SkScalarSinCos(SkDegreesToRadians(degrees), &C);
+
+ this->setSinCos(axis, S, C);
+}
+
+void SkColorMatrix::setSinCos(Axis axis, SkScalar sine, SkScalar cosine) {
+ SkASSERT((unsigned)axis < 3);
+
+ static const uint8_t gRotateIndex[] = {
+ 6, 7, 11, 12,
+ 0, 10, 2, 12,
+ 0, 1, 5, 6,
+ };
+ const uint8_t* index = gRotateIndex + axis * 4;
+
+ this->setIdentity();
+ fMat[index[0]] = cosine;
+ fMat[index[1]] = sine;
+ fMat[index[2]] = -sine;
+ fMat[index[3]] = cosine;
+}
+
+void SkColorMatrix::preRotate(Axis axis, SkScalar degrees) {
+ SkColorMatrix tmp;
+ tmp.setRotate(axis, degrees);
+ this->preConcat(tmp);
+}
+
+void SkColorMatrix::postRotate(Axis axis, SkScalar degrees) {
+ SkColorMatrix tmp;
+ tmp.setRotate(axis, degrees);
+ this->postConcat(tmp);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrix::setConcat(const SkColorMatrix& matA,
+ const SkColorMatrix& matB) {
+ SkScalar tmp[20];
+ SkScalar* result = fMat;
+
+ if (&matA == this || &matB == this) {
+ result = tmp;
+ }
+
+ const SkScalar* a = matA.fMat;
+ const SkScalar* b = matB.fMat;
+
+ int index = 0;
+ for (int j = 0; j < 20; j += 5) {
+ for (int i = 0; i < 4; i++) {
+ result[index++] = SkScalarMul(a[j + 0], b[i + 0]) +
+ SkScalarMul(a[j + 1], b[i + 5]) +
+ SkScalarMul(a[j + 2], b[i + 10]) +
+ SkScalarMul(a[j + 3], b[i + 15]);
+ }
+ result[index++] = SkScalarMul(a[j + 0], b[4]) +
+ SkScalarMul(a[j + 1], b[9]) +
+ SkScalarMul(a[j + 2], b[14]) +
+ SkScalarMul(a[j + 3], b[19]) +
+ a[j + 4];
+ }
+
+ if (fMat != result) {
+ memcpy(fMat, result, sizeof(fMat));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void setrow(SkScalar row[], SkScalar r, SkScalar g, SkScalar b) {
+ row[0] = r;
+ row[1] = g;
+ row[2] = b;
+}
+
+static const SkScalar kHueR = SkFloatToScalar(0.213f);
+static const SkScalar kHueG = SkFloatToScalar(0.715f);
+static const SkScalar kHueB = SkFloatToScalar(0.072f);
+
+void SkColorMatrix::setSaturation(SkScalar sat) {
+ memset(fMat, 0, sizeof(fMat));
+
+ const SkScalar R = SkScalarMul(kHueR, SK_Scalar1 - sat);
+ const SkScalar G = SkScalarMul(kHueG, SK_Scalar1 - sat);
+ const SkScalar B = SkScalarMul(kHueB, SK_Scalar1 - sat);
+
+ setrow(fMat + 0, R + sat, G, B);
+ setrow(fMat + 5, R, G + sat, B);
+ setrow(fMat + 10, R, G, B + sat);
+ fMat[18] = SK_Scalar1;
+}
+
+static const SkScalar kR2Y = SkFloatToScalar(0.299f);
+static const SkScalar kG2Y = SkFloatToScalar(0.587f);
+static const SkScalar kB2Y = SkFloatToScalar(0.114f);
+
+static const SkScalar kR2U = SkFloatToScalar(-0.16874f);
+static const SkScalar kG2U = SkFloatToScalar(-0.33126f);
+static const SkScalar kB2U = SkFloatToScalar(0.5f);
+
+static const SkScalar kR2V = SkFloatToScalar(0.5f);
+static const SkScalar kG2V = SkFloatToScalar(-0.41869f);
+static const SkScalar kB2V = SkFloatToScalar(-0.08131f);
+
+void SkColorMatrix::setRGB2YUV() {
+ memset(fMat, 0, sizeof(fMat));
+
+ setrow(fMat + 0, kR2Y, kG2Y, kB2Y);
+ setrow(fMat + 5, kR2U, kG2U, kB2U);
+ setrow(fMat + 10, kR2V, kG2V, kB2V);
+ fMat[18] = SK_Scalar1;
+}
+
+static const SkScalar kV2R = SkFloatToScalar(1.402f);
+static const SkScalar kU2G = SkFloatToScalar(-0.34414f);
+static const SkScalar kV2G = SkFloatToScalar(-0.71414f);
+static const SkScalar kU2B = SkFloatToScalar(1.772f);
+
+void SkColorMatrix::setYUV2RGB() {
+ memset(fMat, 0, sizeof(fMat));
+
+ setrow(fMat + 0, SK_Scalar1, 0, kV2R);
+ setrow(fMat + 5, SK_Scalar1, kU2G, kV2G);
+ setrow(fMat + 10, SK_Scalar1, kU2B, 0);
+ fMat[18] = SK_Scalar1;
+}
diff --git a/effects/SkColorMatrixFilter.cpp b/effects/SkColorMatrixFilter.cpp
new file mode 100644
index 00000000..3d1fd6f6
--- /dev/null
+++ b/effects/SkColorMatrixFilter.cpp
@@ -0,0 +1,495 @@
+
+/*
+ * 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 "SkColorMatrixFilter.h"
+#include "SkColorMatrix.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+static int32_t rowmul4(const int32_t array[], unsigned r, unsigned g,
+ unsigned b, unsigned a) {
+ return array[0] * r + array[1] * g + array[2] * b + array[3] * a + array[4];
+}
+
+static int32_t rowmul3(const int32_t array[], unsigned r, unsigned g,
+ unsigned b) {
+ return array[0] * r + array[1] * g + array[2] * b + array[4];
+}
+
+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;
+ result[2] = rowmul4(&array[10], r, g, b, a) >> shift;
+ result[3] = rowmul4(&array[15], r, g, b, a) >> shift;
+}
+
+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;
+ result[2] = rowmul4(&array[10], r, g, b, a) >> 16;
+ result[3] = rowmul4(&array[15], r, g, b, a) >> 16;
+}
+
+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;
+ result[2] = rowmul3(&array[10], r, g, b) >> shift;
+ result[3] = a;
+}
+
+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;
+ result[2] = rowmul3(&array[10], r, g, b) >> 16;
+ result[3] = a;
+}
+
+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;
+ result[1] = (array[6] * (int)g + array[9]) >> shift;
+ result[2] = (array[12] * (int)b + array[14]) >> shift;
+ result[3] = a;
+}
+
+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;
+ result[1] = (array[6] * (int)g + array[9]) >> 16;
+ result[2] = (array[12] * (int)b + array[14]) >> 16;
+ result[3] = a;
+}
+
+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);
+ result[2] = b + (array[14] >> shift);
+ result[3] = a;
+}
+
+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);
+ result[2] = b + (array[14] >> 16);
+ result[3] = a;
+}
+
+#define kNO_ALPHA_FLAGS (SkColorFilter::kAlphaUnchanged_Flag | \
+ SkColorFilter::kHasFilter16_Flag)
+
+// src is [20] but some compilers won't accept __restrict__ on anything
+// but an raw pointer or reference
+void SkColorMatrixFilter::initState(const SkScalar* SK_RESTRICT src) {
+ int32_t* array = fState.fArray;
+ SkFixed max = 0;
+ for (int i = 0; i < 20; i++) {
+ SkFixed value = SkScalarToFixed(src[i]);
+ array[i] = value;
+ value = SkAbs32(value);
+ max = SkMax32(max, value);
+ }
+
+ /* All of fArray[] values must fit in 23 bits, to safely allow me to
+ multiply them by 8bit unsigned values, and get a signed answer without
+ overflow. This means clz needs to be 9 or bigger
+ */
+ int bits = SkCLZ(max);
+ int32_t one = SK_Fixed1;
+
+ fState.fShift = 16; // we are starting out as fixed 16.16
+ if (bits < 9) {
+ bits = 9 - bits;
+ fState.fShift -= bits;
+ for (int i = 0; i < 20; i++) {
+ array[i] >>= bits;
+ }
+ one >>= bits;
+ }
+
+ // check if we have to munge Alpha
+ int32_t changesAlpha = (array[15] | array[16] | array[17] |
+ (array[18] - one) | array[19]);
+ int32_t usesAlpha = (array[3] | array[8] | array[13]);
+ bool shiftIs16 = (16 == fState.fShift);
+
+ if (changesAlpha | usesAlpha) {
+ fProc = shiftIs16 ? General16 : General;
+ fFlags = changesAlpha ? 0 : SkColorFilter::kAlphaUnchanged_Flag;
+ } else {
+ fFlags = kNO_ALPHA_FLAGS;
+
+ int32_t needsScale = (array[0] - one) | // red axis
+ (array[6] - one) | // green axis
+ (array[12] - one); // blue axis
+
+ int32_t needs3x3 = array[1] | array[2] | // red off-axis
+ array[5] | array[7] | // green off-axis
+ array[10] | array[11]; // blue off-axis
+
+ if (needs3x3) {
+ fProc = shiftIs16 ? AffineAdd16 : AffineAdd;
+ } else if (needsScale) {
+ fProc = shiftIs16 ? ScaleAdd16 : ScaleAdd;
+ } else if (array[4] | array[9] | array[14]) { // needs add
+ fProc = shiftIs16 ? Add16 : Add;
+ } else {
+ fProc = NULL; // identity
+ }
+ }
+
+ /* preround our add values so we get a rounded shift. We do this after we
+ analyze the array, so we don't miss the case where the caller has zeros
+ which could make us accidentally take the General or Add case.
+ */
+ if (NULL != fProc) {
+ int32_t add = 1 << (fState.fShift - 1);
+ array[4] += add;
+ array[9] += add;
+ array[14] += add;
+ array[19] += add;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int32_t pin(int32_t value, int32_t max) {
+ if (value < 0) {
+ value = 0;
+ }
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(const SkColorMatrix& cm) : fMatrix(cm) {
+ this->initState(cm.fMat);
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(const SkScalar array[20]) {
+ memcpy(fMatrix.fMat, array, 20 * sizeof(SkScalar));
+ this->initState(array);
+}
+
+uint32_t SkColorMatrixFilter::getFlags() const {
+ return this->INHERITED::getFlags() | fFlags;
+}
+
+void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const {
+ Proc proc = fProc;
+ const State& state = fState;
+ int32_t result[4];
+
+ if (NULL == proc) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(SkPMColor));
+ }
+ return;
+ }
+
+ const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = src[i];
+
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+ unsigned a = SkGetPackedA32(c);
+
+ // need our components to be un-premultiplied
+ if (255 != a) {
+ SkUnPreMultiply::Scale scale = table[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+
+ SkASSERT(r <= 255);
+ SkASSERT(g <= 255);
+ SkASSERT(b <= 255);
+ }
+
+ proc(state, r, g, b, a, result);
+
+ r = pin(result[0], SK_R32_MASK);
+ g = pin(result[1], SK_G32_MASK);
+ b = pin(result[2], SK_B32_MASK);
+ a = pin(result[3], SK_A32_MASK);
+ // re-prepremultiply if needed
+ dst[i] = SkPremultiplyARGBInline(a, r, g, b);
+ }
+}
+
+void SkColorMatrixFilter::filterSpan16(const uint16_t src[], int count,
+ uint16_t dst[]) const {
+ SkASSERT(fFlags & SkColorFilter::kHasFilter16_Flag);
+
+ Proc proc = fProc;
+ const State& state = fState;
+ int32_t result[4];
+
+ if (NULL == proc) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(uint16_t));
+ }
+ return;
+ }
+
+ for (int i = 0; i < count; i++) {
+ uint16_t c = src[i];
+
+ // expand to 8bit components (since our matrix translate is 8bit biased
+ unsigned r = SkPacked16ToR32(c);
+ unsigned g = SkPacked16ToG32(c);
+ unsigned b = SkPacked16ToB32(c);
+
+ proc(state, r, g, b, 0, result);
+
+ r = pin(result[0], SK_R32_MASK);
+ g = pin(result[1], SK_G32_MASK);
+ b = pin(result[2], SK_B32_MASK);
+
+ // now packed it back down to 16bits (hmmm, could dither...)
+ dst[i] = SkPack888ToRGB16(r, g, b);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrixFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ SkASSERT(sizeof(fMatrix.fMat)/sizeof(SkScalar) == 20);
+ buffer.writeScalarArray(fMatrix.fMat, 20);
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ SkASSERT(buffer.getArrayCount() == 20);
+ buffer.readScalarArray(fMatrix.fMat);
+ this->initState(fMatrix.fMat);
+}
+
+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 GrEffectRef* Create(const SkColorMatrix& matrix) {
+ AutoEffectUnref effect(SkNEW_ARGS(ColorMatrixEffect, (matrix)));
+ return CreateEffectRef(effect);
+ }
+
+ static const char* Name() { return "Color Matrix"; }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<ColorMatrixEffect>::getInstance();
+ }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // We only bother to check whether the alpha channel will be constant. If SkColorMatrix had
+ // type flags it might be worth checking the other components.
+
+ // The matrix is defined such the 4th row determines the output alpha. The first four
+ // columns of that row multiply the input r, g, b, and a, respectively, and the last column
+ // is the "translation".
+ static const uint32_t kRGBAFlags[] = {
+ kR_GrColorComponentFlag,
+ kG_GrColorComponentFlag,
+ kB_GrColorComponentFlag,
+ kA_GrColorComponentFlag
+ };
+ static const int kShifts[] = {
+ GrColor_SHIFT_R, GrColor_SHIFT_G, GrColor_SHIFT_B, GrColor_SHIFT_A,
+ };
+ enum {
+ kAlphaRowStartIdx = 15,
+ kAlphaRowTranslateIdx = 19,
+ };
+
+ SkScalar outputA = 0;
+ for (int i = 0; i < 4; ++i) {
+ // If any relevant component of the color to be passed through the matrix is non-const
+ // then we can't know the final result.
+ if (0 != fMatrix.fMat[kAlphaRowStartIdx + i]) {
+ if (!(*validFlags & kRGBAFlags[i])) {
+ *validFlags = 0;
+ return;
+ } else {
+ uint32_t component = (*color >> kShifts[i]) & 0xFF;
+ outputA += fMatrix.fMat[kAlphaRowStartIdx + i] * component;
+ }
+ }
+ }
+ outputA += fMatrix.fMat[kAlphaRowTranslateIdx];
+ *validFlags = kA_GrColorComponentFlag;
+ // We pin the color to [0,1]. This would happen to the *final* color output from the frag
+ // shader but currently the effect does not pin its own output. So in the case of over/
+ // underflow this may deviate from the actual result. Maybe the effect should pin its
+ // result if the matrix could over/underflow for any component?
+ *color = static_cast<uint8_t>(SkScalarPin(outputA, 0, 255)) << GrColor_SHIFT_A;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ class GLEffect : public GrGLEffect {
+ public:
+ // this class always generates the same code.
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&) { return 0; }
+
+ GLEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&)
+ : INHERITED(factory)
+ , fMatrixHandle(GrGLUniformManager::kInvalidUniformHandle)
+ , fVectorHandle(GrGLUniformManager::kInvalidUniformHandle) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey,
+ 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->fsCodeAppendf("\tfloat nonZeroAlpha = max(%s.a, 0.00001);\n", inputColor);
+ builder->fsCodeAppendf("\t%s = %s * vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha) + %s;\n",
+ outputColor,
+ builder->getUniformCStr(fMatrixHandle),
+ inputColor,
+ builder->getUniformCStr(fVectorHandle));
+ builder->fsCodeAppendf("\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ }
+
+ virtual void setData(const GrGLUniformManager& uniManager,
+ const GrDrawEffect& drawEffect) SK_OVERRIDE {
+ const ColorMatrixEffect& cme = drawEffect.castEffect<ColorMatrixEffect>();
+ 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:
+ ColorMatrixEffect(const SkColorMatrix& matrix) : fMatrix(matrix) {}
+
+ virtual bool onIsEqual(const GrEffect& s) const {
+ const ColorMatrixEffect& cme = CastEffect<ColorMatrixEffect>(s);
+ return cme.fMatrix == fMatrix;
+ }
+
+ SkColorMatrix fMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(ColorMatrixEffect);
+
+GrEffectRef* ColorMatrixEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* dummyTextures[2]) {
+ SkColorMatrix colorMatrix;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(colorMatrix.fMat); ++i) {
+ colorMatrix.fMat[i] = random->nextSScalar1();
+ }
+ return ColorMatrixEffect::Create(colorMatrix);
+}
+
+GrEffectRef* SkColorMatrixFilter::asNewEffect(GrContext*) const {
+ return ColorMatrixEffect::Create(fMatrix);
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkColorMatrixFilter::toString(SkString* str) const {
+ str->append("SkColorMatrixFilter: ");
+
+ str->append("matrix: (");
+ for (int i = 0; i < 20; ++i) {
+ str->appendScalar(fMatrix.fMat[i]);
+ if (i < 19) {
+ str->append(", ");
+ }
+ }
+ str->append(")");
+}
+#endif
diff --git a/effects/SkComposeImageFilter.cpp b/effects/SkComposeImageFilter.cpp
new file mode 100644
index 00000000..2ba2f3df
--- /dev/null
+++ b/effects/SkComposeImageFilter.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 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 "SkComposeImageFilter.h"
+#include "SkFlattenableBuffers.h"
+
+SkComposeImageFilter::~SkComposeImageFilter() {
+}
+
+bool SkComposeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkImageFilter* outer = getInput(0);
+ SkImageFilter* inner = getInput(1);
+
+ if (!outer && !inner) {
+ return false;
+ }
+
+ if (!outer || !inner) {
+ return (outer ? outer : inner)->filterImage(proxy, src, ctm, result, loc);
+ }
+
+ SkBitmap tmp;
+ return inner->filterImage(proxy, src, ctm, &tmp, loc) &&
+ outer->filterImage(proxy, tmp, ctm, result, loc);
+}
+
+bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
+ const SkMatrix& ctm,
+ SkIRect* dst) {
+ SkImageFilter* outer = getInput(0);
+ SkImageFilter* inner = getInput(1);
+
+ if (!outer && !inner) {
+ return false;
+ }
+
+ if (!outer || !inner) {
+ return (outer ? outer : inner)->filterBounds(src, ctm, dst);
+ }
+
+ SkIRect tmp;
+ return inner->filterBounds(src, ctm, &tmp) &&
+ outer->filterBounds(tmp, ctm, dst);
+}
+
+SkComposeImageFilter::SkComposeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+}
diff --git a/effects/SkCornerPathEffect.cpp b/effects/SkCornerPathEffect.cpp
new file mode 100644
index 00000000..f4c31473
--- /dev/null
+++ b/effects/SkCornerPathEffect.cpp
@@ -0,0 +1,137 @@
+
+/*
+ * 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 "SkCornerPathEffect.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkFlattenableBuffers.h"
+
+SkCornerPathEffect::SkCornerPathEffect(SkScalar radius) : fRadius(radius) {}
+SkCornerPathEffect::~SkCornerPathEffect() {}
+
+static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius,
+ SkPoint* step) {
+ SkScalar dist = SkPoint::Distance(a, b);
+
+ step->set(b.fX - a.fX, b.fY - a.fY);
+
+ if (dist <= radius * 2) {
+ step->scale(SK_ScalarHalf);
+ return false;
+ } else {
+ step->scale(SkScalarDiv(radius, dist));
+ return true;
+ }
+}
+
+bool SkCornerPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ if (0 == fRadius) {
+ return false;
+ }
+
+ SkPath::Iter iter(src, false);
+ SkPath::Verb verb, prevVerb = (SkPath::Verb)-1;
+ SkPoint pts[4];
+
+ bool closed;
+ SkPoint moveTo, lastCorner;
+ SkVector firstStep, step;
+ bool prevIsValid = true;
+
+ // to avoid warnings
+ moveTo.set(0, 0);
+ firstStep.set(0, 0);
+ lastCorner.set(0, 0);
+
+ for (;;) {
+ switch (verb = iter.next(pts, false)) {
+ case SkPath::kMove_Verb:
+ // close out the previous (open) contour
+ if (SkPath::kLine_Verb == prevVerb) {
+ dst->lineTo(lastCorner);
+ }
+ closed = iter.isClosedContour();
+ if (closed) {
+ moveTo = pts[0];
+ prevIsValid = false;
+ } else {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ break;
+ case SkPath::kLine_Verb: {
+ bool drawSegment = ComputeStep(pts[0], pts[1], fRadius, &step);
+ // prev corner
+ if (!prevIsValid) {
+ dst->moveTo(moveTo + step);
+ prevIsValid = true;
+ } else {
+ dst->quadTo(pts[0].fX, pts[0].fY, pts[0].fX + step.fX,
+ pts[0].fY + step.fY);
+ }
+ if (drawSegment) {
+ dst->lineTo(pts[1].fX - step.fX, pts[1].fY - step.fY);
+ }
+ lastCorner = pts[1];
+ prevIsValid = true;
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ // TBD - just replicate the curve for now
+ if (!prevIsValid) {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ dst->quadTo(pts[1], pts[2]);
+ lastCorner = pts[2];
+ firstStep.set(0, 0);
+ break;
+ case SkPath::kCubic_Verb:
+ if (!prevIsValid) {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ // TBD - just replicate the curve for now
+ dst->cubicTo(pts[1], pts[2], pts[3]);
+ lastCorner = pts[3];
+ firstStep.set(0, 0);
+ break;
+ case SkPath::kClose_Verb:
+ if (firstStep.fX || firstStep.fY) {
+ dst->quadTo(lastCorner.fX, lastCorner.fY,
+ lastCorner.fX + firstStep.fX,
+ lastCorner.fY + firstStep.fY);
+ }
+ dst->close();
+ break;
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kDone_Verb:
+ goto DONE;
+ }
+
+ if (SkPath::kMove_Verb == prevVerb) {
+ firstStep = step;
+ }
+ prevVerb = verb;
+ }
+DONE:
+ return true;
+}
+
+void SkCornerPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fRadius);
+}
+
+SkCornerPathEffect::SkCornerPathEffect(SkFlattenableReadBuffer& buffer) {
+ fRadius = buffer.readScalar();
+}
diff --git a/effects/SkDashPathEffect.cpp b/effects/SkDashPathEffect.cpp
new file mode 100644
index 00000000..be065919
--- /dev/null
+++ b/effects/SkDashPathEffect.cpp
@@ -0,0 +1,559 @@
+
+/*
+ * 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 "SkDashPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+
+static inline int is_even(int x) {
+ return (~x) << 31;
+}
+
+static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase,
+ int32_t* index, int count) {
+ for (int i = 0; i < count; ++i) {
+ if (phase > intervals[i]) {
+ phase -= intervals[i];
+ } else {
+ *index = i;
+ return intervals[i] - phase;
+ }
+ }
+ // If we get here, phase "appears" to be larger than our length. This
+ // shouldn't happen with perfect precision, but we can accumulate errors
+ // during the initial length computation (rounding can make our sum be too
+ // big or too small. In that event, we just have to eat the error here.
+ *index = 0;
+ return intervals[0];
+}
+
+SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
+ SkScalar phase, bool scaleToFit)
+ : fScaleToFit(scaleToFit) {
+ SkASSERT(intervals);
+ SkASSERT(count > 1 && SkAlign2(count) == count);
+
+ fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
+ fCount = count;
+
+ SkScalar len = 0;
+ for (int i = 0; i < count; i++) {
+ SkASSERT(intervals[i] >= 0);
+ fIntervals[i] = intervals[i];
+ len += intervals[i];
+ }
+ fIntervalLength = len;
+
+ // watch out for values that might make us go out of bounds
+ if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
+
+ // Adjust phase to be between 0 and len, "flipping" phase if negative.
+ // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
+ if (phase < 0) {
+ phase = -phase;
+ if (phase > len) {
+ phase = SkScalarMod(phase, len);
+ }
+ phase = len - phase;
+
+ // Due to finite precision, it's possible that phase == len,
+ // even after the subtract (if len >>> phase), so fix that here.
+ // This fixes http://crbug.com/124652 .
+ SkASSERT(phase <= len);
+ if (phase == len) {
+ phase = 0;
+ }
+ } else if (phase >= len) {
+ phase = SkScalarMod(phase, len);
+ }
+ SkASSERT(phase >= 0 && phase < len);
+
+ fInitialDashLength = FindFirstInterval(intervals, phase,
+ &fInitialDashIndex, count);
+
+ SkASSERT(fInitialDashLength >= 0);
+ SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
+ } else {
+ fInitialDashLength = -1; // signal bad dash intervals
+ }
+}
+
+SkDashPathEffect::~SkDashPathEffect() {
+ sk_free(fIntervals);
+}
+
+static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
+ SkScalar radius = SkScalarHalf(rec.getWidth());
+ if (0 == radius) {
+ radius = SK_Scalar1; // hairlines
+ }
+ if (SkPaint::kMiter_Join == rec.getJoin()) {
+ radius = SkScalarMul(radius, rec.getMiter());
+ }
+ rect->outset(radius, radius);
+}
+
+// Only handles lines for now. If returns true, dstPath is the new (smaller)
+// path. If returns false, then dstPath parameter is ignored.
+static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec,
+ const SkRect* cullRect, SkScalar intervalLength,
+ SkPath* dstPath) {
+ if (NULL == cullRect) {
+ return false;
+ }
+
+ SkPoint pts[2];
+ if (!srcPath.isLine(pts)) {
+ return false;
+ }
+
+ SkRect bounds = *cullRect;
+ outset_for_stroke(&bounds, rec);
+
+ SkScalar dx = pts[1].x() - pts[0].x();
+ SkScalar dy = pts[1].y() - pts[0].y();
+
+ // just do horizontal lines for now (lazy)
+ if (dy) {
+ return false;
+ }
+
+ SkScalar minX = pts[0].fX;
+ SkScalar maxX = pts[1].fX;
+
+ if (maxX < bounds.fLeft || minX > bounds.fRight) {
+ return false;
+ }
+
+ if (dx < 0) {
+ SkTSwap(minX, maxX);
+ }
+
+ // Now we actually perform the chop, removing the excess to the left and
+ // right of the bounds (keeping our new line "in phase" with the dash,
+ // hence the (mod intervalLength).
+
+ if (minX < bounds.fLeft) {
+ minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX,
+ intervalLength);
+ }
+ if (maxX > bounds.fRight) {
+ maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight,
+ intervalLength);
+ }
+
+ SkASSERT(maxX >= minX);
+ if (dx < 0) {
+ SkTSwap(minX, maxX);
+ }
+ pts[0].fX = minX;
+ pts[1].fX = maxX;
+
+ dstPath->moveTo(pts[0]);
+ dstPath->lineTo(pts[1]);
+ return true;
+}
+
+class SpecialLineRec {
+public:
+ bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
+ int intervalCount, SkScalar intervalLength) {
+ if (rec->isHairlineStyle() || !src.isLine(fPts)) {
+ return false;
+ }
+
+ // can relax this in the future, if we handle square and round caps
+ if (SkPaint::kButt_Cap != rec->getCap()) {
+ return false;
+ }
+
+ SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]);
+
+ fTangent = fPts[1] - fPts[0];
+ if (fTangent.isZero()) {
+ return false;
+ }
+
+ fPathLength = pathLength;
+ fTangent.scale(SkScalarInvert(pathLength));
+ fTangent.rotateCCW(&fNormal);
+ fNormal.scale(SkScalarHalf(rec->getWidth()));
+
+ // now estimate how many quads will be added to the path
+ // resulting segments = pathLen * intervalCount / intervalLen
+ // resulting points = 4 * segments
+
+ SkScalar ptCount = SkScalarMulDiv(pathLength,
+ SkIntToScalar(intervalCount),
+ intervalLength);
+ int n = SkScalarCeilToInt(ptCount) << 2;
+ dst->incReserve(n);
+
+ // we will take care of the stroking
+ rec->setFillStyle();
+ return true;
+ }
+
+ void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
+ SkASSERT(d0 < fPathLength);
+ // clamp the segment to our length
+ if (d1 > fPathLength) {
+ d1 = fPathLength;
+ }
+
+ SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
+ SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
+ SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
+ SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
+
+ SkPoint pts[4];
+ pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
+ pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
+ pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
+ pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
+
+ path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
+ }
+
+private:
+ SkPoint fPts[2];
+ SkVector fTangent;
+ SkVector fNormal;
+ SkScalar fPathLength;
+};
+
+bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ // we do nothing if the src wants to be filled, or if our dashlength is 0
+ if (rec->isFillStyle() || fInitialDashLength < 0) {
+ return false;
+ }
+
+ const SkScalar* intervals = fIntervals;
+ SkScalar dashCount = 0;
+ int segCount = 0;
+
+ SkPath cullPathStorage;
+ const SkPath* srcPtr = &src;
+ if (cull_path(src, *rec, cullRect, fIntervalLength, &cullPathStorage)) {
+ srcPtr = &cullPathStorage;
+ }
+
+ SpecialLineRec lineRec;
+ bool specialLine = lineRec.init(*srcPtr, dst, rec, fCount >> 1, fIntervalLength);
+
+ SkPathMeasure meas(*srcPtr, false);
+
+ do {
+ bool skipFirstSegment = meas.isClosed();
+ bool addedSegment = false;
+ SkScalar length = meas.getLength();
+ 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);
+ } else {
+ SkScalar div = SkScalarDiv(length, fIntervalLength);
+ int n = SkScalarFloor(div);
+ scale = SkScalarDiv(length, n * fIntervalLength);
+ }
+ }
+
+ // 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);
+ addedSegment = false;
+ if (is_even(index) && dlen > 0 && !skipFirstSegment) {
+ addedSegment = true;
+ ++segCount;
+
+ if (specialLine) {
+ lineRec.addSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst);
+ } else {
+ meas.getSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst, true);
+ }
+ }
+ distance += dlen;
+
+ // clear this so we only respect it the first time around
+ skipFirstSegment = false;
+
+ // wrap around our intervals array if necessary
+ index += 1;
+ SkASSERT(index <= fCount);
+ if (index == fCount) {
+ index = 0;
+ }
+
+ // fetch our next dlen
+ dlen = SkScalarMul(intervals[index], scale);
+ }
+
+ // extend if we ended on a segment and we need to join up with the (skipped) initial segment
+ if (meas.isClosed() && is_even(fInitialDashIndex) &&
+ fInitialDashLength > 0) {
+ meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
+ ++segCount;
+ }
+ } while (meas.nextContour());
+
+ if (segCount > 1) {
+ dst->setConvexity(SkPath::kConcave_Convexity);
+ }
+
+ 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 SkRect* cullRect) 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;
+ SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);
+
+ if (SkPaint::kRound_Cap == rec.getCap()) {
+ results->fFlags |= PointData::kCircles_PointFlag;
+ }
+
+ results->fNumPoints = 0;
+ SkScalar len2 = length;
+ if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(len2 >= clampedInitialDashLength);
+ if (0 == fInitialDashIndex) {
+ if (clampedInitialDashLength > 0) {
+ if (clampedInitialDashLength >= fIntervals[0]) {
+ ++results->fNumPoints; // partial first dash
+ }
+ len2 -= clampedInitialDashLength;
+ }
+ len2 -= fIntervals[1]; // also skip first space
+ if (len2 < 0) {
+ len2 = 0;
+ }
+ } else {
+ len2 -= clampedInitialDashLength; // 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 (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(clampedInitialDashLength <= length);
+
+ if (0 == fInitialDashIndex) {
+ if (clampedInitialDashLength > 0) {
+ // partial first block
+ SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength));
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength));
+ SkScalar halfWidth, halfHeight;
+ if (isXAxis) {
+ halfWidth = SkScalarHalf(clampedInitialDashLength);
+ halfHeight = SkScalarHalf(rec.getWidth());
+ } else {
+ halfWidth = SkScalarHalf(rec.getWidth());
+ halfHeight = SkScalarHalf(clampedInitialDashLength);
+ }
+ if (clampedInitialDashLength < 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 += clampedInitialDashLength;
+ }
+
+ distance += fIntervals[1]; // skip over the next blank block too
+ } else {
+ distance += clampedInitialDashLength;
+ }
+ }
+
+ 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;
+}
+
+void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ SkASSERT(fInitialDashLength >= 0);
+
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fInitialDashIndex);
+ buffer.writeScalar(fInitialDashLength);
+ buffer.writeScalar(fIntervalLength);
+ buffer.writeBool(fScaleToFit);
+ buffer.writeScalarArray(fIntervals, fCount);
+}
+
+SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
+ return SkNEW_ARGS(SkDashPathEffect, (buffer));
+}
+
+SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fInitialDashIndex = buffer.readInt();
+ fInitialDashLength = buffer.readScalar();
+ fIntervalLength = buffer.readScalar();
+ fScaleToFit = buffer.readBool();
+
+ fCount = buffer.getArrayCount();
+ fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
+ buffer.readScalarArray(fIntervals);
+}
diff --git a/effects/SkDiscretePathEffect.cpp b/effects/SkDiscretePathEffect.cpp
new file mode 100644
index 00000000..2c95208a
--- /dev/null
+++ b/effects/SkDiscretePathEffect.cpp
@@ -0,0 +1,82 @@
+
+/*
+ * 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 "SkDiscretePathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+#include "SkRandom.h"
+
+static void Perterb(SkPoint* p, const SkVector& tangent, SkScalar scale) {
+ SkVector normal = tangent;
+ normal.rotateCCW();
+ normal.setLength(scale);
+ *p += normal;
+}
+
+
+SkDiscretePathEffect::SkDiscretePathEffect(SkScalar segLength, SkScalar deviation)
+ : fSegLength(segLength), fPerterb(deviation)
+{
+}
+
+bool SkDiscretePathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect*) const {
+ bool doFill = rec->isFillStyle();
+
+ SkPathMeasure meas(src, doFill);
+ uint32_t seed = SkScalarRound(meas.getLength());
+ SkRandom rand(seed ^ ((seed << 16) | (seed >> 16)));
+ SkScalar scale = fPerterb;
+ SkPoint p;
+ SkVector v;
+
+ do {
+ SkScalar length = meas.getLength();
+
+ if (fSegLength * (2 + doFill) > length) {
+ meas.getSegment(0, length, dst, true); // to short for us to mangle
+ } else {
+ int n = SkScalarRound(SkScalarDiv(length, fSegLength));
+ SkScalar delta = length / n;
+ SkScalar distance = 0;
+
+ if (meas.isClosed()) {
+ n -= 1;
+ distance += delta/2;
+ }
+
+ if (meas.getPosTan(distance, &p, &v)) {
+ Perterb(&p, v, SkScalarMul(rand.nextSScalar1(), scale));
+ dst->moveTo(p);
+ }
+ while (--n >= 0) {
+ distance += delta;
+ if (meas.getPosTan(distance, &p, &v)) {
+ Perterb(&p, v, SkScalarMul(rand.nextSScalar1(), scale));
+ dst->lineTo(p);
+ }
+ }
+ if (meas.isClosed()) {
+ dst->close();
+ }
+ }
+ } while (meas.nextContour());
+ return true;
+}
+
+void SkDiscretePathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSegLength);
+ buffer.writeScalar(fPerterb);
+}
+
+SkDiscretePathEffect::SkDiscretePathEffect(SkFlattenableReadBuffer& buffer) {
+ fSegLength = buffer.readScalar();
+ fPerterb = buffer.readScalar();
+}
diff --git a/effects/SkDisplacementMapEffect.cpp b/effects/SkDisplacementMapEffect.cpp
new file mode 100644
index 00000000..dad0623d
--- /dev/null
+++ b/effects/SkDisplacementMapEffect.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDisplacementMapEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkColorPriv.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+namespace {
+
+template<SkDisplacementMapEffect::ChannelSelectorType type>
+uint32_t getValue(SkColor, const SkUnPreMultiply::Scale*) {
+ SkASSERT(!"Unknown channel selector");
+ return 0;
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedR32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedG32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedB32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale*) {
+ return SkGetPackedA32(l);
+}
+
+template<SkDisplacementMapEffect::ChannelSelectorType typeX,
+ SkDisplacementMapEffect::ChannelSelectorType typeY>
+void computeDisplacement(SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ static const SkScalar Inv8bit = SkScalarDiv(SK_Scalar1, SkFloatToScalar(255.0f));
+ const int dstW = displ->width();
+ const int dstH = displ->height();
+ const int srcW = src->width();
+ const int srcH = src->height();
+ const SkScalar scaleForColor = SkScalarMul(scale, Inv8bit);
+ const SkScalar scaleAdj = SK_ScalarHalf - SkScalarMul(scale, SK_ScalarHalf);
+ const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
+ for (int y = 0; y < dstH; ++y) {
+ const SkPMColor* displPtr = displ->getAddr32(0, y);
+ SkPMColor* dstPtr = dst->getAddr32(0, y);
+ for (int x = 0; x < dstW; ++x, ++displPtr, ++dstPtr) {
+ const SkScalar displX = SkScalarMul(scaleForColor,
+ SkIntToScalar(getValue<typeX>(*displPtr, table))) + scaleAdj;
+ const SkScalar displY = SkScalarMul(scaleForColor,
+ SkIntToScalar(getValue<typeY>(*displPtr, table))) + scaleAdj;
+ // Truncate the displacement values
+ const int srcX = x + SkScalarTruncToInt(displX);
+ const int srcY = y + SkScalarTruncToInt(displY);
+ *dstPtr = ((srcX < 0) || (srcX >= srcW) || (srcY < 0) || (srcY >= srcH)) ?
+ 0 : *(src->getAddr32(srcX, srcY));
+ }
+ }
+}
+
+template<SkDisplacementMapEffect::ChannelSelectorType typeX>
+void computeDisplacement(SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ switch (yChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown Y channel selector");
+ }
+}
+
+void computeDisplacement(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ switch (xChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown X channel selector");
+ }
+}
+
+} // end namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDisplacementMapEffect::SkDisplacementMapEffect(ChannelSelectorType xChannelSelector,
+ ChannelSelectorType yChannelSelector,
+ SkScalar scale,
+ SkImageFilter* displacement,
+ SkImageFilter* color)
+ : INHERITED(displacement, color)
+ , fXChannelSelector(xChannelSelector)
+ , fYChannelSelector(yChannelSelector)
+ , fScale(scale)
+{
+}
+
+SkDisplacementMapEffect::~SkDisplacementMapEffect() {
+}
+
+SkDisplacementMapEffect::SkDisplacementMapEffect(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fXChannelSelector = (SkDisplacementMapEffect::ChannelSelectorType) buffer.readInt();
+ fYChannelSelector = (SkDisplacementMapEffect::ChannelSelectorType) buffer.readInt();
+ fScale = buffer.readScalar();
+}
+
+void SkDisplacementMapEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt((int) fXChannelSelector);
+ buffer.writeInt((int) fYChannelSelector);
+ buffer.writeScalar(fScale);
+}
+
+bool SkDisplacementMapEffect::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ SkBitmap displ, color = src;
+ SkImageFilter* colorInput = getColorInput();
+ SkImageFilter* displacementInput = getDisplacementInput();
+ SkASSERT(NULL != displacementInput);
+ if ((colorInput && !colorInput->filterImage(proxy, src, ctm, &color, offset)) ||
+ !displacementInput->filterImage(proxy, src, ctm, &displ, offset)) {
+ return false;
+ }
+ if ((displ.config() != SkBitmap::kARGB_8888_Config) ||
+ (color.config() != SkBitmap::kARGB_8888_Config)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp_displacement(displ), alp_color(color);
+ if (!displ.getPixels() || !color.getPixels()) {
+ return false;
+ }
+ dst->setConfig(displ.config(), displ.width(), displ.height());
+ dst->allocPixels();
+ if (!dst->getPixels()) {
+ return false;
+ }
+
+ computeDisplacement(fXChannelSelector, fYChannelSelector, fScale, dst, &displ, &color);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+class GrGLDisplacementMapEffect : public GrGLEffect {
+public:
+ GrGLDisplacementMapEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual ~GrGLDisplacementMapEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+
+ SkDisplacementMapEffect::ChannelSelectorType fXChannelSelector;
+ SkDisplacementMapEffect::ChannelSelectorType fYChannelSelector;
+ GrGLEffectMatrix fDisplacementEffectMatrix;
+ GrGLEffectMatrix fColorEffectMatrix;
+ GrGLUniformManager::UniformHandle fScaleUni;
+
+ typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrDisplacementMapEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, GrTexture* displacement, GrTexture* color) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrDisplacementMapEffect, (xChannelSelector,
+ yChannelSelector,
+ scale,
+ displacement,
+ color)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrDisplacementMapEffect();
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector() const
+ { return fXChannelSelector; }
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector() const
+ { return fYChannelSelector; }
+ SkScalar scale() const { return fScale; }
+
+ typedef GrGLDisplacementMapEffect GLEffect;
+ static const char* Name() { return "DisplacementMap"; }
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+private:
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrDisplacementMapEffect(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, GrTexture* displacement, GrTexture* color);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fDisplacementAccess;
+ GrTextureAccess fColorAccess;
+ SkDisplacementMapEffect::ChannelSelectorType fXChannelSelector;
+ SkDisplacementMapEffect::ChannelSelectorType fYChannelSelector;
+ SkScalar fScale;
+
+ typedef GrEffect INHERITED;
+};
+
+bool SkDisplacementMapEffect::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap colorBM;
+ SkIPoint colorOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getColorInput(), proxy, src, ctm, &colorBM,
+ &colorOffset)) {
+ return false;
+ }
+ GrTexture* color = colorBM.getTexture();
+ SkBitmap displacementBM;
+ SkIPoint displacementOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getDisplacementInput(), proxy, src, ctm,
+ &displacementBM, &displacementOffset)) {
+ return false;
+ }
+ GrTexture* displacement = displacementBM.getTexture();
+ GrContext* context = color->getContext();
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = src.width();
+ desc.fHeight = src.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+
+ GrPaint paint;
+ paint.addColorEffect(
+ GrDisplacementMapEffect::Create(fXChannelSelector,
+ fYChannelSelector,
+ fScale,
+ displacement,
+ color))->unref();
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ SkRect dstRect = srcRect;
+ dstRect.offset(SkIntToScalar(colorOffset.fX), SkIntToScalar(colorOffset.fY));
+ context->drawRectToRect(paint, srcRect, dstRect);
+ return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDisplacementMapEffect::GrDisplacementMapEffect(
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale,
+ GrTexture* displacement,
+ GrTexture* color)
+ : fDisplacementAccess(displacement)
+ , fColorAccess(color)
+ , fXChannelSelector(xChannelSelector)
+ , fYChannelSelector(yChannelSelector)
+ , fScale(scale) {
+ this->addTextureAccess(&fDisplacementAccess);
+ this->addTextureAccess(&fColorAccess);
+}
+
+GrDisplacementMapEffect::~GrDisplacementMapEffect() {
+}
+
+bool GrDisplacementMapEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrDisplacementMapEffect& s = CastEffect<GrDisplacementMapEffect>(sBase);
+ return fDisplacementAccess.getTexture() == s.fDisplacementAccess.getTexture() &&
+ fColorAccess.getTexture() == s.fColorAccess.getTexture() &&
+ fXChannelSelector == s.fXChannelSelector &&
+ fYChannelSelector == s.fYChannelSelector &&
+ fScale == s.fScale;
+}
+
+const GrBackendEffectFactory& GrDisplacementMapEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrDisplacementMapEffect>::getInstance();
+}
+
+void GrDisplacementMapEffect::getConstantColorComponents(GrColor*,
+ uint32_t* validFlags) const {
+ // Any displacement offset bringing a pixel out of bounds will output a color of (0,0,0,0),
+ // so the only way we'd get a constant alpha is if the input color image has a constant alpha
+ // and no displacement offset push any texture coordinates out of bounds OR if the constant
+ // alpha is 0. Since this isn't trivial to compute at this point, let's assume the output is
+ // not of constant color when a displacement effect is applied.
+ *validFlags = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrDisplacementMapEffect);
+
+GrEffectRef* GrDisplacementMapEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdxDispl = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ int texIdxColor = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ static const int kMaxComponent = 4;
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector =
+ static_cast<SkDisplacementMapEffect::ChannelSelectorType>(
+ random->nextRangeU(1, kMaxComponent));
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector =
+ static_cast<SkDisplacementMapEffect::ChannelSelectorType>(
+ random->nextRangeU(1, kMaxComponent));
+ SkScalar scale = random->nextRangeScalar(0, SkFloatToScalar(100.0f));
+
+ return GrDisplacementMapEffect::Create(xChannelSelector, yChannelSelector, scale,
+ textures[texIdxDispl], textures[texIdxColor]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLDisplacementMapEffect::GrGLDisplacementMapEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fXChannelSelector(drawEffect.castEffect<GrDisplacementMapEffect>().xChannelSelector())
+ , fYChannelSelector(drawEffect.castEffect<GrDisplacementMapEffect>().yChannelSelector())
+ , fDisplacementEffectMatrix(kCoordsType)
+ , fColorEffectMatrix(kCoordsType) {
+}
+
+GrGLDisplacementMapEffect::~GrGLDisplacementMapEffect() {
+}
+
+void GrGLDisplacementMapEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ sk_ignore_unused_variable(inputColor);
+
+ fScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "Scale");
+ const char* scaleUni = builder->getUniformCStr(fScaleUni);
+
+ const char* dCoordsIn;
+ GrSLType dCoordsType = fDisplacementEffectMatrix.emitCode(
+ builder, key, &dCoordsIn, NULL, "DISPL");
+ const char* cCoordsIn;
+ GrSLType cCoordsType = fColorEffectMatrix.emitCode(
+ builder, key, &cCoordsIn, NULL, "COLOR");
+
+ const char* dColor = "dColor";
+ const char* cCoords = "cCoords";
+ const char* outOfBounds = "outOfBounds";
+ const char* nearZero = "1e-6"; // Since 6.10352e−5 is the smallest half float, use
+ // a number smaller than that to approximate 0, but
+ // leave room for 32-bit float GPU rounding errors.
+
+ builder->fsCodeAppendf("\t\tvec4 %s = ", dColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ dCoordsIn,
+ dCoordsType);
+ builder->fsCodeAppend(";\n");
+
+ // Unpremultiply the displacement
+ builder->fsCodeAppendf("\t\t%s.rgb = (%s.a < %s) ? vec3(0.0) : clamp(%s.rgb / %s.a, 0.0, 1.0);",
+ dColor, dColor, nearZero, dColor, dColor);
+
+ builder->fsCodeAppendf("\t\tvec2 %s = %s + %s*(%s.",
+ cCoords, cCoordsIn, scaleUni, dColor);
+
+ switch (fXChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ builder->fsCodeAppend("r");
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ builder->fsCodeAppend("g");
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ builder->fsCodeAppend("b");
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ builder->fsCodeAppend("a");
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown X channel selector");
+ }
+
+ switch (fYChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ builder->fsCodeAppend("r");
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ builder->fsCodeAppend("g");
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ builder->fsCodeAppend("b");
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ builder->fsCodeAppend("a");
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown Y channel selector");
+ }
+ builder->fsCodeAppend("-vec2(0.5));\t\t");
+
+ // FIXME : This can be achieved with a "clamp to border" texture repeat mode and
+ // a 0 border color instead of computing if cCoords is out of bounds here.
+ builder->fsCodeAppendf(
+ "bool %s = (%s.x < 0.0) || (%s.y < 0.0) || (%s.x > 1.0) || (%s.y > 1.0);\t\t",
+ outOfBounds, cCoords, cCoords, cCoords, cCoords);
+ builder->fsCodeAppendf("%s = %s ? vec4(0.0) : ", outputColor, outOfBounds);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[1],
+ cCoords,
+ cCoordsType);
+ builder->fsCodeAppend(";\n");
+}
+
+void GrGLDisplacementMapEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrDisplacementMapEffect& displacementMap =
+ drawEffect.castEffect<GrDisplacementMapEffect>();
+ GrTexture* displTex = displacementMap.texture(0);
+ GrTexture* colorTex = displacementMap.texture(1);
+ fDisplacementEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(displTex),
+ drawEffect,
+ displTex);
+ fColorEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(colorTex),
+ drawEffect,
+ colorTex);
+
+ SkScalar scaleX = SkScalarDiv(displacementMap.scale(), SkIntToScalar(colorTex->width()));
+ SkScalar scaleY = SkScalarDiv(displacementMap.scale(), SkIntToScalar(colorTex->height()));
+ uman.set2f(fScaleUni, SkScalarToFloat(scaleX),
+ colorTex->origin() == kTopLeft_GrSurfaceOrigin ?
+ SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY));
+}
+
+GrGLEffect::EffectKey GrGLDisplacementMapEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrDisplacementMapEffect& displacementMap =
+ drawEffect.castEffect<GrDisplacementMapEffect>();
+
+ GrTexture* displTex = displacementMap.texture(0);
+ GrTexture* colorTex = displacementMap.texture(1);
+
+ EffectKey displKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(displTex),
+ drawEffect,
+ kCoordsType,
+ displTex);
+
+ EffectKey colorKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(colorTex),
+ drawEffect,
+ kCoordsType,
+ colorTex);
+
+ colorKey <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey xKey = displacementMap.xChannelSelector() << (2 * GrGLEffectMatrix::kKeyBits);
+ EffectKey yKey = displacementMap.yChannelSelector() << (2 * GrGLEffectMatrix::kKeyBits +
+ SkDisplacementMapEffect::kKeyBits);
+
+ return xKey | yKey | displKey | colorKey;
+}
+#endif
diff --git a/effects/SkDropShadowImageFilter.cpp b/effects/SkDropShadowImageFilter.cpp
new file mode 100644
index 00000000..b8bbfd6f
--- /dev/null
+++ b/effects/SkDropShadowImageFilter.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDropShadowImageFilter.h"
+
+#include "SkBitmap.h"
+#include "SkBlurImageFilter.h"
+#include "SkCanvas.h"
+#include "SkColorMatrixFilter.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+SkDropShadowImageFilter::SkDropShadowImageFilter(SkScalar dx, SkScalar dy, SkScalar sigma, SkColor color, SkImageFilter* input)
+ : SkImageFilter(input)
+ , fDx(dx)
+ , fDy(dy)
+ , fSigma(sigma)
+ , fColor(color)
+{
+}
+
+SkDropShadowImageFilter::SkDropShadowImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer)
+{
+ fDx = buffer.readScalar();
+ fDy = buffer.readScalar();
+ fSigma = buffer.readScalar();
+ fColor = buffer.readColor();
+}
+
+void SkDropShadowImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const
+{
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fDx);
+ buffer.writeScalar(fDy);
+ buffer.writeScalar(fSigma);
+ buffer.writeColor(fColor);
+}
+
+bool SkDropShadowImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const SkMatrix& matrix, SkBitmap* result, SkIPoint* loc)
+{
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc))
+ return false;
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(src.width(), src.height()));
+ SkCanvas canvas(device.get());
+
+ SkAutoTUnref<SkImageFilter> blurFilter(new SkBlurImageFilter(fSigma, fSigma));
+ SkAutoTUnref<SkColorFilter> colorFilter(SkColorFilter::CreateModeFilter(fColor, SkXfermode::kSrcIn_Mode));
+ SkPaint paint;
+ paint.setImageFilter(blurFilter.get());
+ paint.setColorFilter(colorFilter.get());
+ paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+ canvas.drawBitmap(src, fDx, fDy, &paint);
+ canvas.drawBitmap(src, 0, 0);
+ *result = device->accessBitmap(false);
+ return true;
+}
diff --git a/effects/SkEmbossMask.cpp b/effects/SkEmbossMask.cpp
new file mode 100644
index 00000000..32e9b232
--- /dev/null
+++ b/effects/SkEmbossMask.cpp
@@ -0,0 +1,163 @@
+
+/*
+ * 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 "SkEmbossMask.h"
+#include "SkMath.h"
+
+static inline int nonzero_to_one(int x) {
+#if 0
+ return x != 0;
+#else
+ return ((unsigned)(x | -x)) >> 31;
+#endif
+}
+
+static inline int neq_to_one(int x, int max) {
+#if 0
+ return x != max;
+#else
+ SkASSERT(x >= 0 && x <= max);
+ return ((unsigned)(x - max)) >> 31;
+#endif
+}
+
+static inline int neq_to_mask(int x, int max) {
+#if 0
+ return -(x != max);
+#else
+ SkASSERT(x >= 0 && x <= max);
+ return (x - max) >> 31;
+#endif
+}
+
+static inline unsigned div255(unsigned x) {
+ SkASSERT(x <= (255*255));
+ return x * ((1 << 24) / 255) >> 24;
+}
+
+#define kDelta 32 // small enough to show off angle differences
+
+#include "SkEmbossMask_Table.h"
+
+#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
+
+#include <stdio.h>
+
+void SkEmbossMask_BuildTable() {
+ // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
+
+ FILE* file = ::fopen("SkEmbossMask_Table.h", "w");
+ SkASSERT(file);
+ ::fprintf(file, "#include \"SkTypes.h\"\n\n");
+ ::fprintf(file, "static const U16 gInvSqrtTable[128 * 128] = {\n");
+ for (int dx = 0; dx <= 255/2; dx++) {
+ for (int dy = 0; dy <= 255/2; dy++) {
+ if ((dy & 15) == 0)
+ ::fprintf(file, "\t");
+
+ uint16_t value = SkToU16((1 << 15) / SkSqrt32(dx * dx + dy * dy + kDelta*kDelta/4));
+
+ ::fprintf(file, "0x%04X", value);
+ if (dx * 128 + dy < 128*128-1) {
+ ::fprintf(file, ", ");
+ }
+ if ((dy & 15) == 15) {
+ ::fprintf(file, "\n");
+ }
+ }
+ }
+ ::fprintf(file, "};\n#define kDeltaUsedToBuildTable\t%d\n", kDelta);
+ ::fclose(file);
+}
+
+#endif
+
+void SkEmbossMask::Emboss(SkMask* mask, const SkEmbossMaskFilter::Light& light) {
+ SkASSERT(kDelta == kDeltaUsedToBuildTable);
+
+ SkASSERT(mask->fFormat == SkMask::k3D_Format);
+
+ int specular = light.fSpecular;
+ int ambient = light.fAmbient;
+ SkFixed lx = SkScalarToFixed(light.fDirection[0]);
+ SkFixed ly = SkScalarToFixed(light.fDirection[1]);
+ SkFixed lz = SkScalarToFixed(light.fDirection[2]);
+ SkFixed lz_dot_nz = lz * kDelta;
+ int lz_dot8 = lz >> 8;
+
+ size_t planeSize = mask->computeImageSize();
+ uint8_t* alpha = mask->fImage;
+ uint8_t* multiply = (uint8_t*)alpha + planeSize;
+ uint8_t* additive = multiply + planeSize;
+
+ int rowBytes = mask->fRowBytes;
+ int maxy = mask->fBounds.height() - 1;
+ int maxx = mask->fBounds.width() - 1;
+
+ int prev_row = 0;
+ for (int y = 0; y <= maxy; y++) {
+ int next_row = neq_to_mask(y, maxy) & rowBytes;
+
+ for (int x = 0; x <= maxx; x++) {
+ if (alpha[x]) {
+ int nx = alpha[x + neq_to_one(x, maxx)] - alpha[x - nonzero_to_one(x)];
+ int ny = alpha[x + next_row] - alpha[x - prev_row];
+
+ SkFixed numer = lx * nx + ly * ny + lz_dot_nz;
+ int mul = ambient;
+ int add = 0;
+
+ if (numer > 0) { // preflight when numer/denom will be <= 0
+#if 0
+ int denom = SkSqrt32(nx * nx + ny * ny + kDelta*kDelta);
+ SkFixed dot = numer / denom;
+ dot >>= 8; // now dot is 2^8 instead of 2^16
+#else
+ // can use full numer, but then we need to call SkFixedMul, since
+ // numer is 24 bits, and our table is 12 bits
+
+ // SkFixed dot = SkFixedMul(numer, gTable[]) >> 8
+ SkFixed dot = (unsigned)(numer >> 4) * gInvSqrtTable[(SkAbs32(nx) >> 1 << 7) | (SkAbs32(ny) >> 1)] >> 20;
+#endif
+ mul = SkFastMin32(mul + dot, 255);
+
+ // now for the reflection
+
+ // R = 2 (Light * Normal) Normal - Light
+ // hilite = R * Eye(0, 0, 1)
+
+ int hilite = (2 * dot - lz_dot8) * lz_dot8 >> 8;
+ if (hilite > 0) {
+ // pin hilite to 255, since our fast math is also a little sloppy
+ hilite = SkClampMax(hilite, 255);
+
+ // specular is 4.4
+ // would really like to compute the fractional part of this
+ // and then possibly cache a 256 table for a given specular
+ // value in the light, and just pass that in to this function.
+ add = hilite;
+ for (int i = specular >> 4; i > 0; --i) {
+ add = div255(add * hilite);
+ }
+ }
+ }
+ multiply[x] = SkToU8(mul);
+ additive[x] = SkToU8(add);
+
+ // multiply[x] = 0xFF;
+ // additive[x] = 0;
+ // ((uint8_t*)alpha)[x] = alpha[x] * multiply[x] >> 8;
+ }
+ }
+ alpha += rowBytes;
+ multiply += rowBytes;
+ additive += rowBytes;
+ prev_row = rowBytes;
+ }
+}
diff --git a/effects/SkEmbossMask.h b/effects/SkEmbossMask.h
new file mode 100644
index 00000000..7053be2b
--- /dev/null
+++ b/effects/SkEmbossMask.h
@@ -0,0 +1,20 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkEmbossMask_DEFINED
+#define SkEmbossMask_DEFINED
+
+#include "SkEmbossMaskFilter.h"
+
+class SkEmbossMask {
+public:
+ static void Emboss(SkMask* mask, const SkEmbossMaskFilter::Light&);
+};
+
+#endif
diff --git a/effects/SkEmbossMaskFilter.cpp b/effects/SkEmbossMaskFilter.cpp
new file mode 100644
index 00000000..391cba55
--- /dev/null
+++ b/effects/SkEmbossMaskFilter.cpp
@@ -0,0 +1,155 @@
+
+/*
+ * 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 "SkEmbossMaskFilter.h"
+#include "SkBlurMaskFilter.h"
+#include "SkBlurMask.h"
+#include "SkEmbossMask.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+static inline int pin2byte(int n) {
+ if (n < 0) {
+ n = 0;
+ } else if (n > 0xFF) {
+ n = 0xFF;
+ }
+ return n;
+}
+
+SkMaskFilter* SkBlurMaskFilter::CreateEmboss(const SkScalar direction[3],
+ SkScalar ambient, SkScalar specular,
+ SkScalar blurRadius) {
+ if (direction == NULL) {
+ return NULL;
+ }
+
+ // ambient should be 0...1 as a scalar
+ int am = pin2byte(SkScalarToFixed(ambient) >> 8);
+
+ // specular should be 0..15.99 as a scalar
+ int sp = pin2byte(SkScalarToFixed(specular) >> 12);
+
+ SkEmbossMaskFilter::Light light;
+
+ memcpy(light.fDirection, direction, sizeof(light.fDirection));
+ light.fAmbient = SkToU8(am);
+ light.fSpecular = SkToU8(sp);
+
+ return SkNEW_ARGS(SkEmbossMaskFilter, (light, blurRadius));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void normalize(SkScalar v[3]) {
+ SkScalar mag = SkScalarSquare(v[0]) + SkScalarSquare(v[1]) + SkScalarSquare(v[2]);
+ mag = SkScalarSqrt(mag);
+
+ for (int i = 0; i < 3; i++) {
+ v[i] = SkScalarDiv(v[i], mag);
+ }
+}
+
+SkEmbossMaskFilter::SkEmbossMaskFilter(const Light& light, SkScalar blurRadius)
+ : fLight(light), fBlurRadius(blurRadius) {
+ normalize(fLight.fDirection);
+}
+
+SkMask::Format SkEmbossMaskFilter::getFormat() const {
+ return SkMask::k3D_Format;
+}
+
+bool SkEmbossMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix& matrix, SkIPoint* margin) const {
+ SkScalar radius = matrix.mapRadius(fBlurRadius);
+
+ if (!SkBlurMask::Blur(dst, src, radius, SkBlurMask::kInner_Style,
+ SkBlurMask::kLow_Quality)) {
+ return false;
+ }
+
+ dst->fFormat = SkMask::k3D_Format;
+ if (margin) {
+ margin->set(SkScalarCeil(radius), SkScalarCeil(radius));
+ }
+
+ if (src.fImage == NULL) {
+ return true;
+ }
+
+ // create a larger buffer for the other two channels (should force fBlur to do this for us)
+
+ {
+ uint8_t* alphaPlane = dst->fImage;
+ size_t planeSize = dst->computeImageSize();
+ if (0 == planeSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(planeSize * 3);
+ memcpy(dst->fImage, alphaPlane, planeSize);
+ SkMask::FreeImage(alphaPlane);
+ }
+
+ // run the light direction through the matrix...
+ Light light = fLight;
+ matrix.mapVectors((SkVector*)(void*)light.fDirection,
+ (SkVector*)(void*)fLight.fDirection, 1);
+
+ // now restore the length of the XY component
+ // cast to SkVector so we can call setLength (this double cast silences alias warnings)
+ SkVector* vec = (SkVector*)(void*)light.fDirection;
+ vec->setLength(light.fDirection[0],
+ light.fDirection[1],
+ SkPoint::Length(fLight.fDirection[0], fLight.fDirection[1]));
+
+ SkEmbossMask::Emboss(dst, light);
+
+ // restore original alpha
+ memcpy(dst->fImage, src.fImage, src.computeImageSize());
+
+ return true;
+}
+
+SkEmbossMaskFilter::SkEmbossMaskFilter(SkFlattenableReadBuffer& buffer)
+ : SkMaskFilter(buffer) {
+ SkASSERT(buffer.getArrayCount() == sizeof(Light));
+ buffer.readByteArray(&fLight);
+ SkASSERT(fLight.fPad == 0); // for the font-cache lookup to be clean
+ fBlurRadius = buffer.readScalar();
+}
+
+void SkEmbossMaskFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ Light tmpLight = fLight;
+ tmpLight.fPad = 0; // for the font-cache lookup to be clean
+ buffer.writeByteArray(&tmpLight, sizeof(tmpLight));
+ buffer.writeScalar(fBlurRadius);
+}
+
+#ifdef SK_DEVELOPER
+void SkEmbossMaskFilter::toString(SkString* str) const {
+ str->append("SkEmbossMaskFilter: (");
+
+ str->append("direction: (");
+ str->appendScalar(fLight.fDirection[0]);
+ str->append(", ");
+ str->appendScalar(fLight.fDirection[1]);
+ str->append(", ");
+ str->appendScalar(fLight.fDirection[2]);
+ str->append(") ");
+
+ str->appendf("ambient: %d specular: %d ",
+ fLight.fAmbient, fLight.fSpecular);
+
+ str->append("blurRadius: ");
+ str->appendScalar(fBlurRadius);
+ str->append(")");
+}
+#endif
diff --git a/effects/SkEmbossMask_Table.h b/effects/SkEmbossMask_Table.h
new file mode 100644
index 00000000..0d331ee1
--- /dev/null
+++ b/effects/SkEmbossMask_Table.h
@@ -0,0 +1,1038 @@
+
+/*
+ * 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 "SkTypes.h"
+
+static const uint16_t gInvSqrtTable[128 * 128] = {
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0618, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1,
+ 0x0590, 0x0590, 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1, 0x05D1,
+ 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x0590,
+ 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590,
+ 0x0555, 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x071C, 0x071C, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x0590, 0x0590,
+ 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0555,
+ 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E,
+ 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555,
+ 0x051E, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E,
+ 0x0375, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555, 0x051E,
+ 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x038E,
+ 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0666, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E,
+ 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x0375,
+ 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0618, 0x0618, 0x0618, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC,
+ 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375,
+ 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04EC,
+ 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x0375, 0x0375,
+ 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04BD,
+ 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E,
+ 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276,
+ 0x026A, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0590, 0x0590, 0x0590, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x04BD,
+ 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x0375, 0x035E, 0x035E,
+ 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276,
+ 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x0492,
+ 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x0348,
+ 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276,
+ 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469,
+ 0x0469, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348,
+ 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A,
+ 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x051E, 0x051E, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0469,
+ 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0333,
+ 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A,
+ 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0444,
+ 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333,
+ 0x031F, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A,
+ 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0444, 0x0444, 0x0421,
+ 0x0421, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333, 0x031F,
+ 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E,
+ 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421,
+ 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x031F,
+ 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E,
+ 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x0400,
+ 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C,
+ 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253,
+ 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x03E0,
+ 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C,
+ 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E, 0x0253,
+ 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0444, 0x0444, 0x0444, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3,
+ 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA,
+ 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249,
+ 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8,
+ 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249,
+ 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4,
+ 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8,
+ 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8,
+ 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E,
+ 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4,
+ 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x038E,
+ 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8,
+ 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E,
+ 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD,
+ 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x0375,
+ 0x0375, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8,
+ 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234,
+ 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD,
+ 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375,
+ 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8,
+ 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234,
+ 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7,
+ 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E,
+ 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9,
+ 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B,
+ 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7,
+ 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348,
+ 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA,
+ 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B,
+ 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7,
+ 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333,
+ 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x02AA,
+ 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222,
+ 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0,
+ 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F,
+ 0x031F, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C,
+ 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222,
+ 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0,
+ 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178,
+ 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x031F, 0x030C,
+ 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F,
+ 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219,
+ 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174,
+ 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8,
+ 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x031F, 0x031F, 0x030C, 0x030C, 0x030C, 0x02FA,
+ 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F,
+ 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219,
+ 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA,
+ 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174,
+ 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02FA,
+ 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282,
+ 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210,
+ 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4,
+ 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170,
+ 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02E8,
+ 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276,
+ 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208,
+ 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4,
+ 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170,
+ 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8,
+ 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A,
+ 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0208,
+ 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF,
+ 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8,
+ 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A,
+ 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200,
+ 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF,
+ 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C,
+ 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9,
+ 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E,
+ 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9,
+ 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C,
+ 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA,
+ 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253,
+ 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9,
+ 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168,
+ 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C,
+ 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249,
+ 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4,
+ 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168,
+ 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F,
+ 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249,
+ 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E,
+ 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135,
+ 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x028F,
+ 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x023E,
+ 0x0234, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135,
+ 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0282,
+ 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234,
+ 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1,
+ 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x0276, 0x0276,
+ 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B,
+ 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA,
+ 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A,
+ 0x026A, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x0222,
+ 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA,
+ 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E,
+ 0x025E, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222,
+ 0x0219, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4,
+ 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C,
+ 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0253,
+ 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219,
+ 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD,
+ 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158,
+ 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C,
+ 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF,
+ 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249,
+ 0x0249, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210,
+ 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD,
+ 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158,
+ 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C,
+ 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E,
+ 0x023E, 0x023E, 0x0234, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208,
+ 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7,
+ 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C,
+ 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234,
+ 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200,
+ 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0,
+ 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129,
+ 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x022B,
+ 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA,
+ 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB,
+ 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x0222,
+ 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8,
+ 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01BA,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127,
+ 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB,
+ 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0219,
+ 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0,
+ 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4,
+ 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D,
+ 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0219, 0x0210,
+ 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9,
+ 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF,
+ 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210,
+ 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1,
+ 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178,
+ 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA,
+ 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01DA,
+ 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9,
+ 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174,
+ 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8,
+ 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4,
+ 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4,
+ 0x019E, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170,
+ 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102,
+ 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F8,
+ 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD,
+ 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6,
+ 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01F0,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7,
+ 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6,
+ 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E9,
+ 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7,
+ 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194,
+ 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168,
+ 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1,
+ 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0,
+ 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168,
+ 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA,
+ 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA,
+ 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3,
+ 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4,
+ 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A,
+ 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC,
+ 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3,
+ 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD,
+ 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF,
+ 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186,
+ 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C,
+ 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7,
+ 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1,
+ 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0,
+ 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4,
+ 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181,
+ 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158,
+ 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1,
+ 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA,
+ 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D,
+ 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155,
+ 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0,
+ 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4,
+ 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178,
+ 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151,
+ 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0,
+ 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF,
+ 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199,
+ 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174,
+ 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151,
+ 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F,
+ 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE,
+ 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170,
+ 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E,
+ 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C,
+ 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE,
+ 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4,
+ 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A,
+ 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129,
+ 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD,
+ 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E,
+ 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A,
+ 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147,
+ 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2,
+ 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD,
+ 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186,
+ 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168,
+ 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147,
+ 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127,
+ 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB,
+ 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194,
+ 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181,
+ 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0141, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124,
+ 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0,
+ 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB,
+ 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D,
+ 0x017D, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA,
+ 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178,
+ 0x0178, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E,
+ 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A,
+ 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0174,
+ 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9,
+ 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186,
+ 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0170,
+ 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158,
+ 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x013B,
+ 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7,
+ 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181,
+ 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x016C,
+ 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155,
+ 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C,
+ 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102,
+ 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB,
+ 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7,
+ 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D,
+ 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151,
+ 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6,
+ 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178,
+ 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168,
+ 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118,
+ 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6,
+ 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174,
+ 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164,
+ 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A,
+ 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8,
+ 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4,
+ 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170,
+ 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160,
+ 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147,
+ 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4,
+ 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C,
+ 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C,
+ 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC,
+ 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6,
+ 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3,
+ 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168,
+ 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158,
+ 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144,
+ 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129,
+ 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111,
+ 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3,
+ 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164,
+ 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155,
+ 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141,
+ 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127,
+ 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2,
+ 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160,
+ 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151,
+ 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E,
+ 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3,
+ 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0,
+ 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C,
+ 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E,
+ 0x014E, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C,
+ 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1,
+ 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0,
+ 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158,
+ 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A,
+ 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138,
+ 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A,
+ 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF,
+ 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155,
+ 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147,
+ 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4,
+ 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0,
+ 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF,
+ 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151,
+ 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144,
+ 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0,
+ 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE,
+ 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E,
+ 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141,
+ 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F,
+ 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C,
+ 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE,
+ 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CC,
+ 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A,
+ 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E,
+ 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC,
+ 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147,
+ 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B,
+ 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C,
+ 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD,
+ 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB,
+ 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144,
+ 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B,
+ 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115,
+ 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED,
+ 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB,
+ 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB,
+ 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141,
+ 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138,
+ 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA,
+ 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E,
+ 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135,
+ 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124,
+ 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00C9,
+ 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B,
+ 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132,
+ 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA,
+ 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9,
+ 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9,
+ 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138,
+ 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E,
+ 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8,
+ 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7,
+ 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7,
+ 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135,
+ 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C,
+ 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C,
+ 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7,
+ 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7, 0x00C7,
+ 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132,
+ 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129,
+ 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A,
+ 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6,
+ 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6,
+ 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6,
+ 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F,
+ 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127,
+ 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5,
+ 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4,
+ 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C5,
+ 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124,
+ 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3,
+ 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C5, 0x00C5,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129,
+ 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104,
+ 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3,
+ 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4,
+ 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113,
+ 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2,
+ 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C3,
+ 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124,
+ 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C,
+ 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0,
+ 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0,
+ 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C3, 0x00C3,
+ 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A,
+ 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0,
+ 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0,
+ 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1,
+ 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F,
+ 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF,
+ 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE,
+ 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF,
+ 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C0,
+ 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A,
+ 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE,
+ 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C0, 0x00C0,
+ 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A,
+ 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB,
+ 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE,
+ 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF,
+ 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118,
+ 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC,
+ 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BE,
+ 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115,
+ 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E,
+ 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104,
+ 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8,
+ 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB,
+ 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BE, 0x00BE,
+ 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C,
+ 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102,
+ 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6,
+ 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9,
+ 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA,
+ 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD,
+ 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A,
+ 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6,
+ 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7,
+ 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA,
+ 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC,
+ 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E,
+ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6,
+ 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9,
+ 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC,
+ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E,
+ 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108,
+ 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0,
+ 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6,
+ 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7,
+ 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB,
+ 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C,
+ 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC,
+ 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4,
+ 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7,
+ 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA,
+ 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A,
+ 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED,
+ 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0,
+ 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3,
+ 0x00D2, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6,
+ 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA,
+ 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108,
+ 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB,
+ 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE,
+ 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2,
+ 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5,
+ 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9,
+ 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE,
+ 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0,
+ 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4,
+ 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA,
+ 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00D0,
+ 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C4,
+ 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2,
+ 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB,
+ 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF,
+ 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C3,
+ 0x00C1, 0x00C0, 0x00C0, 0x00BF, 0x00BE, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8, 0x00B7,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0,
+ 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6,
+ 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CF, 0x00CE,
+ 0x00CC, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C3, 0x00C1,
+ 0x00C0, 0x00C0, 0x00BF, 0x00BE, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8, 0x00B7, 0x00B6
+};
+#define kDeltaUsedToBuildTable 32
diff --git a/effects/SkGpuBlurUtils.cpp b/effects/SkGpuBlurUtils.cpp
new file mode 100644
index 00000000..5fc51811
--- /dev/null
+++ b/effects/SkGpuBlurUtils.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkGpuBlurUtils.h"
+
+#include "SkRect.h"
+
+#if SK_SUPPORT_GPU
+#include "effects/GrConvolutionEffect.h"
+#include "effects/GrTextureDomainEffect.h"
+#include "GrContext.h"
+#endif
+
+namespace SkGpuBlurUtils {
+
+#if SK_SUPPORT_GPU
+
+#define MAX_BLUR_SIGMA 4.0f
+
+static void scale_rect(SkRect* rect, float xScale, float yScale) {
+ rect->fLeft = SkScalarMul(rect->fLeft, SkFloatToScalar(xScale));
+ rect->fTop = SkScalarMul(rect->fTop, SkFloatToScalar(yScale));
+ rect->fRight = SkScalarMul(rect->fRight, SkFloatToScalar(xScale));
+ rect->fBottom = SkScalarMul(rect->fBottom, SkFloatToScalar(yScale));
+}
+
+static float adjust_sigma(float sigma, int *scaleFactor, int *radius) {
+ *scaleFactor = 1;
+ while (sigma > MAX_BLUR_SIGMA) {
+ *scaleFactor *= 2;
+ sigma *= 0.5f;
+ }
+ *radius = static_cast<int>(ceilf(sigma * 3.0f));
+ GrAssert(*radius <= GrConvolutionEffect::kMaxKernelRadius);
+ return sigma;
+}
+
+static void convolve_gaussian_pass(GrContext* context,
+ const SkRect& srcRect,
+ const SkRect& dstRect,
+ GrTexture* texture,
+ Gr1DKernelEffect::Direction direction,
+ int radius,
+ float sigma,
+ bool useBounds,
+ float bounds[2]) {
+ GrPaint paint;
+ paint.reset();
+ SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(
+ texture, direction, radius, sigma, useBounds, bounds));
+ paint.reset();
+ paint.addColorEffect(conv);
+ context->drawRectToRect(paint, dstRect, srcRect);
+}
+
+static void convolve_gaussian(GrContext* context,
+ const SkRect& srcRect,
+ const SkRect& dstRect,
+ GrTexture* texture,
+ Gr1DKernelEffect::Direction direction,
+ int radius,
+ float sigma,
+ bool cropToSrcRect) {
+ float bounds[2] = { 0.0f, 1.0f };
+ if (!cropToSrcRect) {
+ convolve_gaussian_pass(context, srcRect, dstRect, texture,
+ direction, radius, sigma, false, bounds);
+ return;
+ }
+ SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
+ SkRect middleSrcRect = srcRect, middleDstRect = dstRect;
+ SkRect upperSrcRect = srcRect, upperDstRect = dstRect;
+ SkScalar size;
+ SkScalar rad = SkIntToScalar(radius);
+ if (direction == Gr1DKernelEffect::kX_Direction) {
+ bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width();
+ bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width();
+ size = srcRect.width();
+ lowerSrcRect.fRight = srcRect.left() + rad;
+ lowerDstRect.fRight = dstRect.left() + rad;
+ upperSrcRect.fLeft = srcRect.right() - rad;
+ upperDstRect.fLeft = dstRect.right() - rad;
+ middleSrcRect.inset(rad, 0);
+ middleDstRect.inset(rad, 0);
+ } else {
+ bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height();
+ bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height();
+ size = srcRect.height();
+ lowerSrcRect.fBottom = srcRect.top() + rad;
+ lowerDstRect.fBottom = dstRect.top() + rad;
+ upperSrcRect.fTop = srcRect.bottom() - rad;
+ upperDstRect.fTop = dstRect.bottom() - rad;
+ middleSrcRect.inset(0, rad);
+ middleDstRect.inset(0, rad);
+ }
+ if (radius >= size * SK_ScalarHalf) {
+ // Blur radius covers srcRect; use bounds over entire draw
+ convolve_gaussian_pass(context, srcRect, dstRect, texture,
+ direction, radius, sigma, true, bounds);
+ } else {
+ // Draw upper and lower margins with bounds; middle without.
+ convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture,
+ direction, radius, sigma, true, bounds);
+ convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture,
+ direction, radius, sigma, true, bounds);
+ convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture,
+ direction, radius, sigma, false, bounds);
+ }
+}
+
+GrTexture* GaussianBlur(GrContext* context,
+ GrTexture* srcTexture,
+ bool canClobberSrc,
+ const SkRect& rect,
+ bool cropToRect,
+ float sigmaX,
+ float sigmaY) {
+ GrAssert(NULL != context);
+
+ GrContext::AutoRenderTarget art(context);
+
+ GrContext::AutoMatrix am;
+ am.setIdentity(context);
+
+ SkIRect clearRect;
+ int scaleFactorX, radiusX;
+ int scaleFactorY, radiusY;
+ sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &radiusX);
+ sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &radiusY);
+
+ SkRect srcRect(rect);
+ scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
+ srcRect.roundOut();
+ scale_rect(&srcRect, static_cast<float>(scaleFactorX),
+ static_cast<float>(scaleFactorY));
+
+ GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
+
+ GrAssert(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
+ kRGBA_8888_GrPixelConfig == srcTexture->config() ||
+ kAlpha_8_GrPixelConfig == srcTexture->config());
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = SkScalarFloorToInt(srcRect.width());
+ desc.fHeight = SkScalarFloorToInt(srcRect.height());
+ desc.fConfig = srcTexture->config();
+
+ GrAutoScratchTexture temp1, temp2;
+ GrTexture* dstTexture = temp1.set(context, desc);
+ GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc);
+ if (NULL == dstTexture || NULL == tempTexture) {
+ return NULL;
+ }
+
+ for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
+ GrPaint paint;
+ SkMatrix matrix;
+ matrix.setIDiv(srcTexture->width(), srcTexture->height());
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect(srcRect);
+ if (cropToRect && i == 1) {
+ dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
+ SkRect domain;
+ matrix.mapRect(&domain, rect);
+ domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
+ i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
+ SkAutoTUnref<GrEffectRef> effect(GrTextureDomainEffect::Create(
+ srcTexture,
+ matrix,
+ domain,
+ GrTextureDomainEffect::kDecal_WrapMode,
+ GrTextureParams::kBilerp_FilterMode));
+ paint.addColorEffect(effect);
+ } else {
+ GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
+ paint.addColorTextureEffect(srcTexture, matrix, params);
+ }
+ scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
+ i < scaleFactorY ? 0.5f : 1.0f);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ srcRect = dstRect;
+ srcTexture = dstTexture;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ SkIRect srcIRect;
+ srcRect.roundOut(&srcIRect);
+
+ if (sigmaX > 0.0f) {
+ if (scaleFactorX > 1) {
+ // Clear out a radius to the right of the srcRect to prevent the
+ // X convolution from reading garbage.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
+ radiusX, srcIRect.height());
+ context->clear(&clearRect, 0x0);
+ }
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcRect, dstRect, srcTexture,
+ Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
+ srcTexture = dstTexture;
+ srcRect = dstRect;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ if (sigmaY > 0.0f) {
+ if (scaleFactorY > 1 || sigmaX > 0.0f) {
+ // Clear out a radius below the srcRect to prevent the Y
+ // convolution from reading garbage.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
+ srcIRect.width(), radiusY);
+ context->clear(&clearRect, 0x0);
+ }
+
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcRect, dstRect, srcTexture,
+ Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
+ srcTexture = dstTexture;
+ srcRect = dstRect;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ if (scaleFactorX > 1 || scaleFactorY > 1) {
+ // Clear one pixel to the right and below, to accommodate bilinear
+ // upsampling.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
+ srcIRect.width() + 1, 1);
+ context->clear(&clearRect, 0x0);
+ clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
+ 1, srcIRect.height());
+ context->clear(&clearRect, 0x0);
+ SkMatrix matrix;
+ matrix.setIDiv(srcTexture->width(), srcTexture->height());
+ context->setRenderTarget(dstTexture->asRenderTarget());
+
+ GrPaint paint;
+ // FIXME: this should be mitchell, not bilinear.
+ GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
+ paint.addColorTextureEffect(srcTexture, matrix, params);
+
+ SkRect dstRect(srcRect);
+ scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ srcRect = dstRect;
+ srcTexture = dstTexture;
+ SkTSwap(dstTexture, tempTexture);
+ }
+ if (srcTexture == temp1.texture()) {
+ return temp1.detach();
+ } else if (srcTexture == temp2.texture()) {
+ return temp2.detach();
+ } else {
+ srcTexture->ref();
+ return srcTexture;
+ }
+}
+#endif
+
+}
diff --git a/effects/SkGpuBlurUtils.h b/effects/SkGpuBlurUtils.h
new file mode 100644
index 00000000..98be8130
--- /dev/null
+++ b/effects/SkGpuBlurUtils.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkGpuBlurUtils_DEFINED
+#define SkGpuBlurUtils_DEFINED
+
+#if SK_SUPPORT_GPU
+class GrTexture;
+class GrContext;
+#endif
+
+struct SkRect;
+
+namespace SkGpuBlurUtils {
+
+#if SK_SUPPORT_GPU
+ /**
+ * Applies a 2D Gaussian blur to a given texture.
+ * @param context The GPU context
+ * @param srcTexture The source texture to be blurred.
+ * @param canClobberSrc If true, srcTexture may be overwritten, and
+ * may be returned as the result.
+ * @param rect The destination rectangle.
+ * @param cropToRect If true, do not sample any pixels outside the
+ * source rect.
+ * @param sigmaX The blur's standard deviation in X.
+ * @param sigmaY The blur's standard deviation in Y.
+ * @return the blurred texture, which may be srcTexture reffed, or a
+ * new texture. It is the caller's responsibility to unref this texture.
+ */
+ GrTexture* GaussianBlur(GrContext* context,
+ GrTexture* srcTexture,
+ bool canClobberSrc,
+ const SkRect& rect,
+ bool cropToRect,
+ float sigmaX,
+ float sigmaY);
+#endif
+
+};
+
+#endif
diff --git a/effects/SkKernel33MaskFilter.cpp b/effects/SkKernel33MaskFilter.cpp
new file mode 100644
index 00000000..485001bb
--- /dev/null
+++ b/effects/SkKernel33MaskFilter.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 "SkKernel33MaskFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkMask::Format SkKernel33ProcMaskFilter::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+bool SkKernel33ProcMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix&, SkIPoint* margin) const {
+ // margin???
+ dst->fImage = NULL;
+ dst->fBounds = src.fBounds;
+ dst->fBounds.inset(-1, -1);
+ dst->fFormat = SkMask::kA8_Format;
+
+ if (NULL == src.fImage) {
+ return true;
+ }
+
+ dst->fRowBytes = dst->fBounds.width();
+ size_t size = dst->computeImageSize();
+ if (0 == size) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(size);
+
+ const int h = src.fBounds.height();
+ const int w = src.fBounds.width();
+ const int srcRB = src.fRowBytes;
+ const uint8_t* srcImage = src.fImage;
+ uint8_t* dstImage = dst->fImage;
+
+ uint8_t* srcRows[3];
+ uint8_t storage[3][3];
+
+ srcRows[0] = storage[0];
+ srcRows[1] = storage[1];
+ srcRows[2] = storage[2];
+
+ unsigned scale = fPercent256;
+
+ for (int y = -1; y <= h; y++) {
+ uint8_t* dstRow = dstImage;
+ for (int x = -1; x <= w; x++) {
+ memset(storage, 0, sizeof(storage));
+ uint8_t* storagePtr = &storage[0][0];
+
+ for (int ky = y - 1; ky <= y + 1; ky++) {
+ const uint8_t* srcRow = srcImage + ky * srcRB; // may be out-of-range
+ for (int kx = x - 1; kx <= x + 1; kx++) {
+ if ((unsigned)ky < (unsigned)h && (unsigned)kx < (unsigned)w) {
+ *storagePtr = srcRow[kx];
+ }
+ storagePtr++;
+ }
+ }
+ int value = this->computeValue(srcRows);
+
+ if (scale < 256) {
+ value = SkAlphaBlend(value, srcRows[1][1], scale);
+ }
+ *dstRow++ = SkToU8(value);
+ }
+ dstImage += dst->fRowBytes;
+ }
+ return true;
+}
+
+void SkKernel33ProcMaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeInt(fPercent256);
+}
+
+SkKernel33ProcMaskFilter::SkKernel33ProcMaskFilter(SkFlattenableReadBuffer& rb)
+ : SkMaskFilter(rb) {
+ fPercent256 = rb.readInt();
+}
+
+#ifdef SK_DEVELOPER
+void SkKernel33ProcMaskFilter::toString(SkString* str) const {
+ str->appendf("percent256: %d, ", fPercent256);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint8_t SkKernel33MaskFilter::computeValue(uint8_t* const* srcRows) const {
+ int value = 0;
+
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ value += fKernel[i][j] * srcRows[i][j];
+ }
+ }
+
+ value >>= fShift;
+
+ if (value < 0) {
+ value = 0;
+ } else if (value > 255) {
+ value = 255;
+ }
+ return (uint8_t)value;
+}
+
+void SkKernel33MaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeIntArray(&fKernel[0][0], 9);
+ wb.writeInt(fShift);
+}
+
+SkKernel33MaskFilter::SkKernel33MaskFilter(SkFlattenableReadBuffer& rb)
+ : SkKernel33ProcMaskFilter(rb) {
+ SkDEBUGCODE(const uint32_t count = )rb.readIntArray(&fKernel[0][0]);
+ SkASSERT(9 == count);
+ fShift = rb.readInt();
+}
+
+#ifdef SK_DEVELOPER
+void SkKernel33MaskFilter::toString(SkString* str) const {
+ str->append("SkKernel33MaskFilter: (");
+
+ str->appendf("kernel: (%d, %d, %d, %d, %d, %d, %d, %d, %d), ",
+ fKernel[0][0], fKernel[0][1], fKernel[0][2],
+ fKernel[1][0], fKernel[1][1], fKernel[1][2],
+ fKernel[2][0], fKernel[2][1], fKernel[2][2]);
+ str->appendf("shift: %d, ", fShift);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkLayerDrawLooper.cpp b/effects/SkLayerDrawLooper.cpp
new file mode 100644
index 00000000..998c4bcd
--- /dev/null
+++ b/effects/SkLayerDrawLooper.cpp
@@ -0,0 +1,350 @@
+
+/*
+ * 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 "SkCanvas.h"
+#include "SkColor.h"
+#include "SkFlattenableBuffers.h"
+#include "SkLayerDrawLooper.h"
+#include "SkString.h"
+#include "SkStringUtils.h"
+#include "SkUnPreMultiply.h"
+
+SK_DEFINE_INST_COUNT(SkLayerDrawLooper)
+
+SkLayerDrawLooper::LayerInfo::LayerInfo() {
+ fFlagsMask = 0; // ignore our paint flags
+ fPaintBits = 0; // ignore our paint fields
+ fColorMode = SkXfermode::kDst_Mode; // ignore our color
+ fOffset.set(0, 0);
+ fPostTranslate = false;
+}
+
+SkLayerDrawLooper::SkLayerDrawLooper()
+ : fRecs(NULL),
+ fTopRec(NULL),
+ fCount(0),
+ fCurrRec(NULL) {
+}
+
+SkLayerDrawLooper::~SkLayerDrawLooper() {
+ Rec* rec = fRecs;
+ while (rec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+}
+
+SkPaint* SkLayerDrawLooper::addLayer(const LayerInfo& info) {
+ fCount += 1;
+
+ Rec* rec = SkNEW(Rec);
+ rec->fNext = fRecs;
+ rec->fInfo = info;
+ fRecs = rec;
+ if (NULL == fTopRec) {
+ fTopRec = rec;
+ }
+
+ return &rec->fPaint;
+}
+
+void SkLayerDrawLooper::addLayer(SkScalar dx, SkScalar dy) {
+ LayerInfo info;
+
+ info.fOffset.set(dx, dy);
+ (void)this->addLayer(info);
+}
+
+SkPaint* SkLayerDrawLooper::addLayerOnTop(const LayerInfo& info) {
+ fCount += 1;
+
+ Rec* rec = SkNEW(Rec);
+ rec->fNext = NULL;
+ rec->fInfo = info;
+ if (NULL == fRecs) {
+ fRecs = rec;
+ } else {
+ SkASSERT(NULL != fTopRec);
+ fTopRec->fNext = rec;
+ }
+ fTopRec = rec;
+
+ return &rec->fPaint;
+}
+
+void SkLayerDrawLooper::init(SkCanvas* canvas) {
+ fCurrRec = fRecs;
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+}
+
+static SkColor xferColor(SkColor src, SkColor dst, SkXfermode::Mode mode) {
+ switch (mode) {
+ case SkXfermode::kSrc_Mode:
+ return src;
+ case SkXfermode::kDst_Mode:
+ return dst;
+ default: {
+ SkPMColor pmS = SkPreMultiplyColor(src);
+ SkPMColor pmD = SkPreMultiplyColor(dst);
+ SkPMColor result = SkXfermode::GetProc(mode)(pmS, pmD);
+ return SkUnPreMultiply::PMColorToColor(result);
+ }
+ }
+}
+
+// Even with kEntirePaint_Bits, we always ensure that the master paint's
+// text-encoding is respected, since that controls how we interpret the
+// text/length parameters of a draw[Pos]Text call.
+void SkLayerDrawLooper::ApplyInfo(SkPaint* dst, const SkPaint& src,
+ const LayerInfo& info) {
+
+ uint32_t mask = info.fFlagsMask;
+ dst->setFlags((dst->getFlags() & ~mask) | (src.getFlags() & mask));
+ dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));
+
+ BitFlags bits = info.fPaintBits;
+ SkPaint::TextEncoding encoding = dst->getTextEncoding();
+
+ if (0 == bits) {
+ return;
+ }
+ if (kEntirePaint_Bits == bits) {
+ // we've already computed these, so save it from the assignment
+ uint32_t f = dst->getFlags();
+ SkColor c = dst->getColor();
+ *dst = src;
+ dst->setFlags(f);
+ dst->setColor(c);
+ dst->setTextEncoding(encoding);
+ return;
+ }
+
+ if (bits & kStyle_Bit) {
+ dst->setStyle(src.getStyle());
+ dst->setStrokeWidth(src.getStrokeWidth());
+ dst->setStrokeMiter(src.getStrokeMiter());
+ dst->setStrokeCap(src.getStrokeCap());
+ dst->setStrokeJoin(src.getStrokeJoin());
+ }
+
+ if (bits & kTextSkewX_Bit) {
+ dst->setTextSkewX(src.getTextSkewX());
+ }
+
+ if (bits & kPathEffect_Bit) {
+ dst->setPathEffect(src.getPathEffect());
+ }
+ if (bits & kMaskFilter_Bit) {
+ dst->setMaskFilter(src.getMaskFilter());
+ }
+ if (bits & kShader_Bit) {
+ dst->setShader(src.getShader());
+ }
+ if (bits & kColorFilter_Bit) {
+ dst->setColorFilter(src.getColorFilter());
+ }
+ if (bits & kXfermode_Bit) {
+ dst->setXfermode(src.getXfermode());
+ }
+
+ // we don't override these
+#if 0
+ dst->setTypeface(src.getTypeface());
+ dst->setTextSize(src.getTextSize());
+ dst->setTextScaleX(src.getTextScaleX());
+ dst->setRasterizer(src.getRasterizer());
+ dst->setLooper(src.getLooper());
+ dst->setTextEncoding(src.getTextEncoding());
+ dst->setHinting(src.getHinting());
+#endif
+}
+
+// Should we add this to canvas?
+static void postTranslate(SkCanvas* canvas, SkScalar dx, SkScalar dy) {
+ SkMatrix m = canvas->getTotalMatrix();
+ m.postTranslate(dx, dy);
+ canvas->setMatrix(m);
+}
+
+bool SkLayerDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+ canvas->restore();
+ if (NULL == fCurrRec) {
+ return false;
+ }
+
+ ApplyInfo(paint, fCurrRec->fPaint, fCurrRec->fInfo);
+
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+ if (fCurrRec->fInfo.fPostTranslate) {
+ postTranslate(canvas, fCurrRec->fInfo.fOffset.fX,
+ fCurrRec->fInfo.fOffset.fY);
+ } else {
+ canvas->translate(fCurrRec->fInfo.fOffset.fX, fCurrRec->fInfo.fOffset.fY);
+ }
+ fCurrRec = fCurrRec->fNext;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkLayerDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+#ifdef SK_DEBUG
+ {
+ Rec* rec = fRecs;
+ int count = 0;
+ while (rec) {
+ rec = rec->fNext;
+ count += 1;
+ }
+ SkASSERT(count == fCount);
+ }
+#endif
+
+ buffer.writeInt(fCount);
+
+ Rec* rec = fRecs;
+ for (int i = 0; i < fCount; i++) {
+ buffer.writeInt(rec->fInfo.fFlagsMask);
+ buffer.writeInt(rec->fInfo.fPaintBits);
+ buffer.writeInt(rec->fInfo.fColorMode);
+ buffer.writePoint(rec->fInfo.fOffset);
+ buffer.writeBool(rec->fInfo.fPostTranslate);
+ buffer.writePaint(rec->fPaint);
+ rec = rec->fNext;
+ }
+}
+
+SkLayerDrawLooper::SkLayerDrawLooper(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fRecs(NULL),
+ fTopRec(NULL),
+ fCount(0),
+ fCurrRec(NULL) {
+ int count = buffer.readInt();
+
+ for (int i = 0; i < count; i++) {
+ LayerInfo info;
+ info.fFlagsMask = buffer.readInt();
+ info.fPaintBits = buffer.readInt();
+ info.fColorMode = (SkXfermode::Mode)buffer.readInt();
+ buffer.readPoint(&info.fOffset);
+ info.fPostTranslate = buffer.readBool();
+ buffer.readPaint(this->addLayerOnTop(info));
+ }
+ SkASSERT(count == fCount);
+
+#ifdef SK_DEBUG
+ {
+ Rec* rec = fRecs;
+ int n = 0;
+ while (rec) {
+ rec = rec->fNext;
+ n += 1;
+ }
+ SkASSERT(count == n);
+ }
+#endif
+}
+
+#ifdef SK_DEVELOPER
+void SkLayerDrawLooper::toString(SkString* str) const {
+ str->appendf("SkLayerDrawLooper (%d): ", fCount);
+
+ Rec* rec = fRecs;
+ for (int i = 0; i < fCount; i++) {
+ str->appendf("%d: ", i);
+
+ str->append("flagsMask: (");
+ if (0 == rec->fInfo.fFlagsMask) {
+ str->append("None");
+ } else {
+ bool needSeparator = false;
+ SkAddFlagToString(str, SkToBool(SkPaint::kAntiAlias_Flag & rec->fInfo.fFlagsMask),
+ "AntiAlias", &needSeparator);
+// SkAddFlagToString(str, SkToBool(SkPaint::kFilterBitmap_Flag & rec->fInfo.fFlagsMask), "FilterBitmap", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kDither_Flag & rec->fInfo.fFlagsMask),
+ "Dither", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kUnderlineText_Flag & rec->fInfo.fFlagsMask),
+ "UnderlineText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kStrikeThruText_Flag & rec->fInfo.fFlagsMask),
+ "StrikeThruText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kFakeBoldText_Flag & rec->fInfo.fFlagsMask),
+ "FakeBoldText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kLinearText_Flag & rec->fInfo.fFlagsMask),
+ "LinearText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kSubpixelText_Flag & rec->fInfo.fFlagsMask),
+ "SubpixelText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kDevKernText_Flag & rec->fInfo.fFlagsMask),
+ "DevKernText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kLCDRenderText_Flag & rec->fInfo.fFlagsMask),
+ "LCDRenderText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kEmbeddedBitmapText_Flag & rec->fInfo.fFlagsMask),
+ "EmbeddedBitmapText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kAutoHinting_Flag & rec->fInfo.fFlagsMask),
+ "Autohinted", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kVerticalText_Flag & rec->fInfo.fFlagsMask),
+ "VerticalText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kGenA8FromLCD_Flag & rec->fInfo.fFlagsMask),
+ "GenA8FromLCD", &needSeparator);
+ }
+ str->append(") ");
+
+ str->append("paintBits: (");
+ if (0 == rec->fInfo.fPaintBits) {
+ str->append("None");
+ } else if (kEntirePaint_Bits == rec->fInfo.fPaintBits) {
+ str->append("EntirePaint");
+ } else {
+ bool needSeparator = false;
+ SkAddFlagToString(str, SkToBool(kStyle_Bit & rec->fInfo.fPaintBits), "Style",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kTextSkewX_Bit & rec->fInfo.fPaintBits), "TextSkewX",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kPathEffect_Bit & rec->fInfo.fPaintBits), "PathEffect",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kMaskFilter_Bit & rec->fInfo.fPaintBits), "MaskFilter",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kShader_Bit & rec->fInfo.fPaintBits), "Shader",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kColorFilter_Bit & rec->fInfo.fPaintBits), "ColorFilter",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kXfermode_Bit & rec->fInfo.fPaintBits), "Xfermode",
+ &needSeparator);
+ }
+ str->append(") ");
+
+ static const char* gModeStrings[SkXfermode::kLastMode+1] = {
+ "kClear", "kSrc", "kDst", "kSrcOver", "kDstOver", "kSrcIn", "kDstIn",
+ "kSrcOut", "kDstOut", "kSrcATop", "kDstATop", "kXor", "kPlus",
+ "kMultiply", "kScreen", "kOverlay", "kDarken", "kLighten", "kColorDodge",
+ "kColorBurn", "kHardLight", "kSoftLight", "kDifference", "kExclusion"
+ };
+
+ str->appendf("mode: %s ", gModeStrings[rec->fInfo.fColorMode]);
+
+ str->append("offset: (");
+ str->appendScalar(rec->fInfo.fOffset.fX);
+ str->append(", ");
+ str->appendScalar(rec->fInfo.fOffset.fY);
+ str->append(") ");
+
+ str->append("postTranslate: ");
+ if (rec->fInfo.fPostTranslate) {
+ str->append("true ");
+ } else {
+ str->append("false ");
+ }
+
+ rec->fPaint.toString(str);
+ rec = rec->fNext;
+ }
+}
+#endif
diff --git a/effects/SkLayerRasterizer.cpp b/effects/SkLayerRasterizer.cpp
new file mode 100644
index 00000000..ea5808c2
--- /dev/null
+++ b/effects/SkLayerRasterizer.cpp
@@ -0,0 +1,171 @@
+
+/*
+ * 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 "SkLayerRasterizer.h"
+#include "SkDraw.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMask.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "../core/SkRasterClip.h"
+#include "SkXfermode.h"
+#include <new>
+
+struct SkLayerRasterizer_Rec {
+ SkPaint fPaint;
+ SkVector fOffset;
+};
+
+SkLayerRasterizer::SkLayerRasterizer() : fLayers(sizeof(SkLayerRasterizer_Rec))
+{
+}
+
+SkLayerRasterizer::~SkLayerRasterizer() {
+ SkDeque::F2BIter iter(fLayers);
+ SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL)
+ rec->fPaint.~SkPaint();
+}
+
+void SkLayerRasterizer::addLayer(const SkPaint& paint, SkScalar dx,
+ SkScalar dy) {
+ SkLayerRasterizer_Rec* rec = (SkLayerRasterizer_Rec*)fLayers.push_back();
+
+ SkNEW_PLACEMENT_ARGS(&rec->fPaint, SkPaint, (paint));
+ rec->fOffset.set(dx, dy);
+}
+
+static bool compute_bounds(const SkDeque& layers, const SkPath& path,
+ const SkMatrix& matrix,
+ const SkIRect* clipBounds, SkIRect* bounds) {
+ SkDeque::F2BIter iter(layers);
+ SkLayerRasterizer_Rec* rec;
+
+ bounds->set(SK_MaxS32, SK_MaxS32, SK_MinS32, SK_MinS32);
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ const SkPaint& paint = rec->fPaint;
+ SkPath fillPath, devPath;
+ const SkPath* p = &path;
+
+ if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+ paint.getFillPath(path, &fillPath);
+ p = &fillPath;
+ }
+ if (p->isEmpty()) {
+ continue;
+ }
+
+ // apply the matrix and offset
+ {
+ SkMatrix m = matrix;
+ m.preTranslate(rec->fOffset.fX, rec->fOffset.fY);
+ p->transform(m, &devPath);
+ }
+
+ SkMask mask;
+ if (!SkDraw::DrawToMask(devPath, clipBounds, paint.getMaskFilter(),
+ &matrix, &mask,
+ SkMask::kJustComputeBounds_CreateMode,
+ SkPaint::kFill_Style)) {
+ return false;
+ }
+
+ bounds->join(mask.fBounds);
+ }
+ return true;
+}
+
+bool SkLayerRasterizer::onRasterize(const SkPath& path, const SkMatrix& matrix,
+ const SkIRect* clipBounds,
+ SkMask* mask, SkMask::CreateMode mode) const {
+ if (fLayers.empty()) {
+ return false;
+ }
+
+ if (SkMask::kJustRenderImage_CreateMode != mode) {
+ if (!compute_bounds(fLayers, path, matrix, clipBounds, &mask->fBounds))
+ return false;
+ }
+
+ if (SkMask::kComputeBoundsAndRenderImage_CreateMode == mode) {
+ mask->fFormat = SkMask::kA8_Format;
+ mask->fRowBytes = mask->fBounds.width();
+ size_t size = mask->computeImageSize();
+ if (0 == size) {
+ return false; // too big to allocate, abort
+ }
+ mask->fImage = SkMask::AllocImage(size);
+ memset(mask->fImage, 0, size);
+ }
+
+ if (SkMask::kJustComputeBounds_CreateMode != mode) {
+ SkBitmap device;
+ SkRasterClip rectClip;
+ SkDraw draw;
+ SkMatrix translatedMatrix; // this translates us to our local pixels
+ SkMatrix drawMatrix; // this translates the path by each layer's offset
+
+ rectClip.setRect(SkIRect::MakeWH(mask->fBounds.width(), mask->fBounds.height()));
+
+ translatedMatrix = matrix;
+ translatedMatrix.postTranslate(-SkIntToScalar(mask->fBounds.fLeft),
+ -SkIntToScalar(mask->fBounds.fTop));
+
+ device.setConfig(SkBitmap::kA8_Config, mask->fBounds.width(), mask->fBounds.height(), mask->fRowBytes);
+ device.setPixels(mask->fImage);
+
+ draw.fBitmap = &device;
+ draw.fMatrix = &drawMatrix;
+ draw.fRC = &rectClip;
+ draw.fClip = &rectClip.bwRgn();
+ // we set the matrixproc in the loop, as the matrix changes each time (potentially)
+ draw.fBounder = NULL;
+
+ SkDeque::F2BIter iter(fLayers);
+ SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ drawMatrix = translatedMatrix;
+ drawMatrix.preTranslate(rec->fOffset.fX, rec->fOffset.fY);
+ draw.drawPath(path, rec->fPaint);
+ }
+ }
+ return true;
+}
+
+SkLayerRasterizer::SkLayerRasterizer(SkFlattenableReadBuffer& buffer)
+ : SkRasterizer(buffer), fLayers(sizeof(SkLayerRasterizer_Rec)) {
+ int count = buffer.readInt();
+
+ for (int i = 0; i < count; i++) {
+ SkLayerRasterizer_Rec* rec = (SkLayerRasterizer_Rec*)fLayers.push_back();
+
+ SkNEW_PLACEMENT(&rec->fPaint, SkPaint);
+ buffer.readPaint(&rec->fPaint);
+ buffer.readPoint(&rec->fOffset);
+ }
+}
+
+void SkLayerRasterizer::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeInt(fLayers.count());
+
+ SkDeque::F2BIter iter(fLayers);
+ const SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (const SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ buffer.writePaint(rec->fPaint);
+ buffer.writePoint(rec->fOffset);
+ }
+}
diff --git a/effects/SkLerpXfermode.cpp b/effects/SkLerpXfermode.cpp
new file mode 100644
index 00000000..d73ecf4c
--- /dev/null
+++ b/effects/SkLerpXfermode.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLerpXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkXfermode* SkLerpXfermode::Create(SkScalar scale) {
+ int scale256 = SkScalarRoundToInt(scale * 256);
+ if (scale256 >= 256) {
+ return SkXfermode::Create(SkXfermode::kSrc_Mode);
+ } else if (scale256 <= 0) {
+ return SkXfermode::Create(SkXfermode::kDst_Mode);
+ }
+ return SkNEW_ARGS(SkLerpXfermode, (scale256));
+}
+
+SkLerpXfermode::SkLerpXfermode(unsigned scale256) : fScale256(scale256) {}
+
+SkLerpXfermode::SkLerpXfermode(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fScale256 = buffer.readUInt();
+}
+
+void SkLerpXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeUInt(fScale256);
+}
+
+void SkLerpXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ SkPMColor dstC = dst[i];
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ if (a < 255) {
+ resC = SkFastFourByteInterp256(resC, dstC, a + (a >> 7));
+ }
+ dst[i] = resC;
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ dst[i] = SkFastFourByteInterp256(src[i], dst[i], scale);
+ }
+ }
+}
+
+void SkLerpXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ if (a < 255) {
+ resC = SkFastFourByteInterp256(resC, dstC, a + (a >> 7));
+ }
+ dst[i] = SkPixel32ToPixel16(resC);
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ dst[i] = SkPixel32ToPixel16(resC);
+ }
+ }
+}
+
+void SkLerpXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ unsigned dstA = dst[i];
+ unsigned resA = SkAlphaBlend(SkGetPackedA32(src[i]), dstA, scale);
+ if (a < 255) {
+ resA = SkAlphaBlend(resA, dstA, a + (a >> 7));
+ }
+ dst[i] = resA;
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ dst[i] = SkAlphaBlend(SkGetPackedA32(src[i]), dst[i], scale);
+ }
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkLerpXfermode::toString(SkString* str) const {
+ str->printf("SkLerpXfermode: scale: %g", fScale256 / 256.0);
+}
+#endif
diff --git a/effects/SkLightingImageFilter.cpp b/effects/SkLightingImageFilter.cpp
new file mode 100644
index 00000000..7a74f736
--- /dev/null
+++ b/effects/SkLightingImageFilter.cpp
@@ -0,0 +1,1553 @@
+/*
+ * 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 "SkLightingImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+#include "effects/GrSingleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+
+class GrGLDiffuseLightingEffect;
+class GrGLSpecularLightingEffect;
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+#endif
+
+namespace {
+
+const SkScalar gOneThird = SkScalarInvert(SkIntToScalar(3));
+const SkScalar gTwoThirds = SkScalarDiv(SkIntToScalar(2), SkIntToScalar(3));
+const SkScalar gOneHalf = SkFloatToScalar(0.5f);
+const SkScalar gOneQuarter = SkFloatToScalar(0.25f);
+
+#if SK_SUPPORT_GPU
+void setUniformPoint3(const GrGLUniformManager& uman, UniformHandle uni, const SkPoint3& point) {
+ GR_STATIC_ASSERT(sizeof(SkPoint3) == 3 * sizeof(GrGLfloat));
+ uman.set3fv(uni, 0, 1, &point.fX);
+}
+
+void setUniformNormal3(const GrGLUniformManager& uman, UniformHandle uni, const SkPoint3& point) {
+ setUniformPoint3(uman, uni, SkPoint3(point.fX, point.fY, point.fZ));
+}
+#endif
+
+// Shift matrix components to the left, as we advance pixels to the right.
+inline void shiftMatrixLeft(int m[9]) {
+ m[0] = m[1];
+ m[3] = m[4];
+ m[6] = m[7];
+ m[1] = m[2];
+ m[4] = m[5];
+ m[7] = m[8];
+}
+
+class DiffuseLightingType {
+public:
+ DiffuseLightingType(SkScalar kd)
+ : fKD(kd) {}
+ SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+ SkScalar colorScale = SkScalarMul(fKD, normal.dot(surfaceTolight));
+ colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+ SkPoint3 color(lightColor * colorScale);
+ return SkPackARGB32(255,
+ SkScalarFloorToInt(color.fX),
+ SkScalarFloorToInt(color.fY),
+ SkScalarFloorToInt(color.fZ));
+ }
+private:
+ SkScalar fKD;
+};
+
+class SpecularLightingType {
+public:
+ SpecularLightingType(SkScalar ks, SkScalar shininess)
+ : fKS(ks), fShininess(shininess) {}
+ SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+ SkPoint3 halfDir(surfaceTolight);
+ halfDir.fZ += SK_Scalar1; // eye position is always (0, 0, 1)
+ halfDir.normalize();
+ SkScalar colorScale = SkScalarMul(fKS,
+ SkScalarPow(normal.dot(halfDir), fShininess));
+ colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+ SkPoint3 color(lightColor * colorScale);
+ return SkPackARGB32(SkScalarFloorToInt(color.maxComponent()),
+ SkScalarFloorToInt(color.fX),
+ SkScalarFloorToInt(color.fY),
+ SkScalarFloorToInt(color.fZ));
+ }
+private:
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+inline SkScalar sobel(int a, int b, int c, int d, int e, int f, SkScalar scale) {
+ return SkScalarMul(SkIntToScalar(-a + b - 2 * c + 2 * d -e + f), scale);
+}
+
+inline SkPoint3 pointToNormal(SkScalar x, SkScalar y, SkScalar surfaceScale) {
+ SkPoint3 vector(SkScalarMul(-x, surfaceScale),
+ SkScalarMul(-y, surfaceScale),
+ SK_Scalar1);
+ vector.normalize();
+ return vector;
+}
+
+inline SkPoint3 topLeftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(0, 0, m[4], m[5], m[7], m[8], gTwoThirds),
+ sobel(0, 0, m[4], m[7], m[5], m[8], gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 topNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel( 0, 0, m[3], m[5], m[6], m[8], gOneThird),
+ sobel(m[3], m[6], m[4], m[7], m[5], m[8], gOneHalf),
+ surfaceScale);
+}
+
+inline SkPoint3 topRightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel( 0, 0, m[3], m[4], m[6], m[7], gTwoThirds),
+ sobel(m[3], m[6], m[4], m[7], 0, 0, gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 leftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[1], m[2], m[4], m[5], m[7], m[8], gOneHalf),
+ sobel( 0, 0, m[1], m[7], m[2], m[8], gOneThird),
+ surfaceScale);
+}
+
+
+inline SkPoint3 interiorNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[2], m[3], m[5], m[6], m[8], gOneQuarter),
+ sobel(m[0], m[6], m[1], m[7], m[2], m[8], gOneQuarter),
+ surfaceScale);
+}
+
+inline SkPoint3 rightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[1], m[3], m[4], m[6], m[7], gOneHalf),
+ sobel(m[0], m[6], m[1], m[7], 0, 0, gOneThird),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomLeftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[1], m[2], m[4], m[5], 0, 0, gTwoThirds),
+ sobel( 0, 0, m[1], m[4], m[2], m[5], gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[2], m[3], m[5], 0, 0, gOneThird),
+ sobel(m[0], m[3], m[1], m[4], m[2], m[5], gOneHalf),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomRightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[1], m[3], m[4], 0, 0, gTwoThirds),
+ sobel(m[0], m[3], m[1], m[4], 0, 0, gTwoThirds),
+ surfaceScale);
+}
+
+template <class LightingType, class LightType> void lightBitmap(const LightingType& lightingType, const SkLight* light, const SkBitmap& src, SkBitmap* dst, SkScalar surfaceScale, const SkIRect& bounds) {
+ SkASSERT(dst->width() == bounds.width() && dst->height() == bounds.height());
+ const LightType* l = static_cast<const LightType*>(light);
+ int left = bounds.left(), right = bounds.right();
+ int bottom = bounds.bottom();
+ int y = bounds.top();
+ SkPMColor* dptr = dst->getAddr32(0, 0);
+ {
+ int x = left;
+ const SkPMColor* row1 = src.getAddr32(x, y);
+ const SkPMColor* row2 = src.getAddr32(x, y + 1);
+ int m[9];
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[7] = SkGetPackedA32(*row2++);
+ m[8] = SkGetPackedA32(*row2++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topLeftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x)
+ {
+ shiftMatrixLeft(m);
+ m[5] = SkGetPackedA32(*row1++);
+ m[8] = SkGetPackedA32(*row2++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topRightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+
+ for (++y; y < bottom - 1; ++y) {
+ int x = left;
+ const SkPMColor* row0 = src.getAddr32(x, y - 1);
+ const SkPMColor* row1 = src.getAddr32(x, y);
+ const SkPMColor* row2 = src.getAddr32(x, y + 1);
+ int m[9];
+ m[1] = SkGetPackedA32(*row0++);
+ m[2] = SkGetPackedA32(*row0++);
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[7] = SkGetPackedA32(*row2++);
+ m[8] = SkGetPackedA32(*row2++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(leftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x) {
+ shiftMatrixLeft(m);
+ m[2] = SkGetPackedA32(*row0++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[8] = SkGetPackedA32(*row2++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(interiorNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(rightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+
+ {
+ int x = left;
+ const SkPMColor* row0 = src.getAddr32(x, bottom - 2);
+ const SkPMColor* row1 = src.getAddr32(x, bottom - 1);
+ int m[9];
+ m[1] = SkGetPackedA32(*row0++);
+ m[2] = SkGetPackedA32(*row0++);
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomLeftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x)
+ {
+ shiftMatrixLeft(m);
+ m[2] = SkGetPackedA32(*row0++);
+ m[5] = SkGetPackedA32(*row1++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomRightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+}
+
+SkPoint3 readPoint3(SkFlattenableReadBuffer& buffer) {
+ SkPoint3 point;
+ point.fX = buffer.readScalar();
+ point.fY = buffer.readScalar();
+ point.fZ = buffer.readScalar();
+ return point;
+};
+
+void writePoint3(const SkPoint3& point, SkFlattenableWriteBuffer& buffer) {
+ buffer.writeScalar(point.fX);
+ buffer.writeScalar(point.fY);
+ buffer.writeScalar(point.fZ);
+};
+
+class SkDiffuseLightingImageFilter : public SkLightingImageFilter {
+public:
+ SkDiffuseLightingImageFilter(SkLight* light, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect);
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiffuseLightingImageFilter)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffect(GrEffectRef** effect, GrTexture*, const SkIPoint& offset) const SK_OVERRIDE;
+#endif
+ SkScalar kd() const { return fKD; }
+
+protected:
+ explicit SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+
+private:
+ typedef SkLightingImageFilter INHERITED;
+ SkScalar fKD;
+};
+
+class SkSpecularLightingImageFilter : public SkLightingImageFilter {
+public:
+ SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect);
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpecularLightingImageFilter)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffect(GrEffectRef** effect, GrTexture*, const SkIPoint& offset) const SK_OVERRIDE;
+#endif
+
+ SkScalar ks() const { return fKS; }
+ SkScalar shininess() const { return fShininess; }
+
+protected:
+ explicit SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+private:
+ typedef SkLightingImageFilter INHERITED;
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+#if SK_SUPPORT_GPU
+
+class GrLightingEffect : public GrSingleTextureEffect {
+public:
+ GrLightingEffect(GrTexture* texture, const SkLight* light, SkScalar surfaceScale, const SkIPoint& offset);
+ virtual ~GrLightingEffect();
+
+ const SkLight* light() const { return fLight; }
+ SkScalar surfaceScale() const { return fSurfaceScale; }
+ const SkIPoint& offset() const { return fOffset; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // lighting shaders are complicated. We just throw up our hands.
+ *validFlags = 0;
+ }
+
+protected:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+private:
+ typedef GrSingleTextureEffect INHERITED;
+ const SkLight* fLight;
+ SkScalar fSurfaceScale;
+ SkIPoint fOffset;
+};
+
+class GrDiffuseLightingEffect : public GrLightingEffect {
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrDiffuseLightingEffect, (texture,
+ light,
+ surfaceScale,
+ offset,
+ kd)));
+ return CreateEffectRef(effect);
+ }
+
+ static const char* Name() { return "DiffuseLighting"; }
+
+ typedef GrGLDiffuseLightingEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkScalar kd() const { return fKD; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrDiffuseLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd);
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrLightingEffect INHERITED;
+ SkScalar fKD;
+};
+
+class GrSpecularLightingEffect : public GrLightingEffect {
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSpecularLightingEffect, (texture,
+ light,
+ surfaceScale,
+ offset,
+ ks,
+ shininess)));
+ return CreateEffectRef(effect);
+ }
+ static const char* Name() { return "SpecularLighting"; }
+
+ typedef GrGLSpecularLightingEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkScalar ks() const { return fKS; }
+ SkScalar shininess() const { return fShininess; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrSpecularLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess);
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrLightingEffect INHERITED;
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLLight {
+public:
+ virtual ~GrGLLight() {}
+
+ /**
+ * 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*, 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 SkIPoint& offset) 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;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLDistantLight : public GrGLLight {
+public:
+ virtual ~GrGLDistantLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+ UniformHandle fDirectionUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLPointLight : public GrGLLight {
+public:
+ virtual ~GrGLPointLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+ UniformHandle fLocationUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLSpotLight : public GrGLLight {
+public:
+ virtual ~GrGLSpotLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+ virtual void emitLightColor(GrGLShaderBuilder*, const char *surfaceToLight) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+
+ SkString fLightColorFunc;
+ UniformHandle fLocationUni;
+ UniformHandle fExponentUni;
+ UniformHandle fCosOuterConeAngleUni;
+ UniformHandle fCosInnerConeAngleUni;
+ UniformHandle fConeScaleUni;
+ UniformHandle fSUni;
+};
+#else
+
+class GrGLLight;
+
+#endif
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkLight : public SkFlattenable {
+public:
+ SK_DECLARE_INST_COUNT(SkLight)
+
+ enum LightType {
+ kDistant_LightType,
+ kPoint_LightType,
+ kSpot_LightType,
+ };
+ virtual LightType type() const = 0;
+ const SkPoint3& color() const { return fColor; }
+ virtual GrGLLight* createGLLight() const = 0;
+ virtual bool isEqual(const SkLight& other) const {
+ return fColor == other.fColor;
+ }
+ // Called to know whether the generated GrGLLight will require access to the fragment position.
+ virtual bool requiresFragmentPosition() const = 0;
+
+protected:
+ SkLight(SkColor color)
+ : fColor(SkIntToScalar(SkColorGetR(color)),
+ SkIntToScalar(SkColorGetG(color)),
+ SkIntToScalar(SkColorGetB(color))) {}
+ SkLight(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fColor = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ INHERITED::flatten(buffer);
+ writePoint3(fColor, buffer);
+ }
+
+private:
+ typedef SkFlattenable INHERITED;
+ SkPoint3 fColor;
+};
+
+SK_DEFINE_INST_COUNT(SkLight)
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkDistantLight : public SkLight {
+public:
+ SkDistantLight(const SkPoint3& direction, SkColor color)
+ : INHERITED(color), fDirection(direction) {
+ }
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ return fDirection;
+ };
+ SkPoint3 lightColor(const SkPoint3&) const { return color(); }
+ virtual LightType type() const { return kDistant_LightType; }
+ const SkPoint3& direction() const { return fDirection; }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLDistantLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return false; }
+
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kDistant_LightType) {
+ return false;
+ }
+
+ const SkDistantLight& o = static_cast<const SkDistantLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fDirection == o.fDirection;
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDistantLight)
+
+protected:
+ SkDistantLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fDirection = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fDirection, buffer);
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fDirection;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkPointLight : public SkLight {
+public:
+ SkPointLight(const SkPoint3& location, SkColor color)
+ : INHERITED(color), fLocation(location) {}
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+ fLocation.fY - SkIntToScalar(y),
+ fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+ direction.normalize();
+ return direction;
+ };
+ SkPoint3 lightColor(const SkPoint3&) const { return color(); }
+ virtual LightType type() const { return kPoint_LightType; }
+ const SkPoint3& location() const { return fLocation; }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLPointLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return true; }
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kPoint_LightType) {
+ return false;
+ }
+ const SkPointLight& o = static_cast<const SkPointLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fLocation == o.fLocation;
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPointLight)
+
+protected:
+ SkPointLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fLocation = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fLocation, buffer);
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fLocation;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSpotLight : public SkLight {
+public:
+ SkSpotLight(const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle, SkColor color)
+ : INHERITED(color),
+ fLocation(location),
+ fTarget(target),
+ fSpecularExponent(specularExponent)
+ {
+ fS = target - location;
+ fS.normalize();
+ fCosOuterConeAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle));
+ const SkScalar antiAliasThreshold = SkFloatToScalar(0.016f);
+ fCosInnerConeAngle = fCosOuterConeAngle + antiAliasThreshold;
+ fConeScale = SkScalarInvert(antiAliasThreshold);
+ }
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+ fLocation.fY - SkIntToScalar(y),
+ fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+ direction.normalize();
+ return direction;
+ };
+ SkPoint3 lightColor(const SkPoint3& surfaceToLight) const {
+ SkScalar cosAngle = -surfaceToLight.dot(fS);
+ if (cosAngle < fCosOuterConeAngle) {
+ return SkPoint3(0, 0, 0);
+ }
+ SkScalar scale = SkScalarPow(cosAngle, fSpecularExponent);
+ if (cosAngle < fCosInnerConeAngle) {
+ scale = SkScalarMul(scale, cosAngle - fCosOuterConeAngle);
+ return color() * SkScalarMul(scale, fConeScale);
+ }
+ return color() * scale;
+ }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLSpotLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return true; }
+ virtual LightType type() const { return kSpot_LightType; }
+ const SkPoint3& location() const { return fLocation; }
+ const SkPoint3& target() const { return fTarget; }
+ SkScalar specularExponent() const { return fSpecularExponent; }
+ SkScalar cosInnerConeAngle() const { return fCosInnerConeAngle; }
+ SkScalar cosOuterConeAngle() const { return fCosOuterConeAngle; }
+ SkScalar coneScale() const { return fConeScale; }
+ const SkPoint3& s() const { return fS; }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpotLight)
+
+protected:
+ SkSpotLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fLocation = readPoint3(buffer);
+ fTarget = readPoint3(buffer);
+ fSpecularExponent = buffer.readScalar();
+ fCosOuterConeAngle = buffer.readScalar();
+ fCosInnerConeAngle = buffer.readScalar();
+ fConeScale = buffer.readScalar();
+ fS = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fLocation, buffer);
+ writePoint3(fTarget, buffer);
+ buffer.writeScalar(fSpecularExponent);
+ buffer.writeScalar(fCosOuterConeAngle);
+ buffer.writeScalar(fCosInnerConeAngle);
+ buffer.writeScalar(fConeScale);
+ writePoint3(fS, buffer);
+ }
+
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kSpot_LightType) {
+ return false;
+ }
+
+ const SkSpotLight& o = static_cast<const SkSpotLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fLocation == o.fLocation &&
+ fTarget == o.fTarget &&
+ fSpecularExponent == o.fSpecularExponent &&
+ fCosOuterConeAngle == o.fCosOuterConeAngle;
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fLocation;
+ SkPoint3 fTarget;
+ SkScalar fSpecularExponent;
+ SkScalar fCosOuterConeAngle;
+ SkScalar fCosInnerConeAngle;
+ SkScalar fConeScale;
+ SkPoint3 fS;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLightingImageFilter::SkLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkImageFilter* input, const SkIRect* cropRect)
+ : INHERITED(input, cropRect),
+ fLight(light),
+ fSurfaceScale(SkScalarDiv(surfaceScale, SkIntToScalar(255)))
+{
+ SkASSERT(fLight);
+ // our caller knows that we take ownership of the light, so we don't
+ // need to call ref() here.
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitDiffuse(
+ const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkDistantLight, (direction, lightColor)), surfaceScale, kd,
+ input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitDiffuse(
+ const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkPointLight, (location, lightColor)), surfaceScale, kd,
+ input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitDiffuse(
+ const SkPoint3& location, const SkPoint3& target,
+ SkScalar specularExponent, SkScalar cutoffAngle,
+ SkColor lightColor, SkScalar surfaceScale, SkScalar kd,
+ SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkSpotLight, (location, target, specularExponent,
+ cutoffAngle, lightColor)),
+ surfaceScale, kd, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitSpecular(
+ const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkDistantLight, (direction, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitSpecular(
+ const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkPointLight, (location, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitSpecular(
+ const SkPoint3& location, const SkPoint3& target,
+ SkScalar specularExponent, SkScalar cutoffAngle,
+ SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkSpotLight, (location, target, specularExponent, cutoffAngle, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkLightingImageFilter::~SkLightingImageFilter() {
+ fLight->unref();
+}
+
+SkLightingImageFilter::SkLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fLight = buffer.readFlattenableT<SkLight>();
+ fSurfaceScale = buffer.readScalar();
+}
+
+void SkLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fLight);
+ buffer.writeScalar(fSurfaceScale);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar kd, SkImageFilter* input, const SkIRect* cropRect = NULL)
+ : SkLightingImageFilter(light, surfaceScale, input, cropRect),
+ fKD(kd)
+{
+}
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fKD = buffer.readScalar();
+}
+
+void SkDiffuseLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fKD);
+}
+
+bool SkDiffuseLightingImageFilter::onFilterImage(Proxy*,
+ const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ if (bounds.width() < 2 || bounds.height() < 2) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), bounds.width(), bounds.height());
+ dst->allocPixels();
+
+ DiffuseLightingType lightingType(fKD);
+ switch (light()->type()) {
+ case SkLight::kDistant_LightType:
+ lightBitmap<DiffuseLightingType, SkDistantLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kPoint_LightType:
+ lightBitmap<DiffuseLightingType, SkPointLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kSpot_LightType:
+ lightBitmap<DiffuseLightingType, SkSpotLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ }
+
+ offset->fX += bounds.left();
+ offset->fY += bounds.top();
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+bool SkDiffuseLightingImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint& offset) const {
+ if (effect) {
+ SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
+ *effect = GrDiffuseLightingEffect::Create(texture, light(), scale, offset, kd());
+ }
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect)
+ : SkLightingImageFilter(light, surfaceScale, input, cropRect),
+ fKS(ks),
+ fShininess(shininess)
+{
+}
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fKS = buffer.readScalar();
+ fShininess = buffer.readScalar();
+}
+
+void SkSpecularLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fKS);
+ buffer.writeScalar(fShininess);
+}
+
+bool SkSpecularLightingImageFilter::onFilterImage(Proxy*,
+ const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ if (bounds.width() < 2 || bounds.height() < 2) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), bounds.width(), bounds.height());
+ dst->allocPixels();
+
+ SpecularLightingType lightingType(fKS, fShininess);
+ switch (light()->type()) {
+ case SkLight::kDistant_LightType:
+ lightBitmap<SpecularLightingType, SkDistantLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kPoint_LightType:
+ lightBitmap<SpecularLightingType, SkPointLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kSpot_LightType:
+ lightBitmap<SpecularLightingType, SkSpotLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ }
+ offset->fX += bounds.left();
+ offset->fY += bounds.top();
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+bool SkSpecularLightingImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint& offset) const {
+ if (effect) {
+ SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
+ *effect = GrSpecularLightingEffect::Create(texture, light(), scale, offset, ks(), shininess());
+ }
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+namespace {
+SkPoint3 random_point3(SkMWCRandom* random) {
+ return SkPoint3(SkScalarToFloat(random->nextSScalar1()),
+ SkScalarToFloat(random->nextSScalar1()),
+ SkScalarToFloat(random->nextSScalar1()));
+}
+
+SkLight* create_random_light(SkMWCRandom* random) {
+ int type = random->nextULessThan(3);
+ switch (type) {
+ case 0: {
+ return SkNEW_ARGS(SkDistantLight, (random_point3(random), random->nextU()));
+ }
+ case 1: {
+ return SkNEW_ARGS(SkPointLight, (random_point3(random), random->nextU()));
+ }
+ case 2: {
+ return SkNEW_ARGS(SkSpotLight, (random_point3(random),
+ random_point3(random),
+ random->nextUScalar1(),
+ random->nextUScalar1(),
+ random->nextU()));
+ }
+ default:
+ GrCrash();
+ return NULL;
+ }
+}
+
+}
+
+class GrGLLightingEffect : public GrGLEffect {
+public:
+ GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual ~GrGLLightingEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ /**
+ * Subclasses of GrGLLightingEffect must call INHERITED::setData();
+ */
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) = 0;
+
+private:
+ typedef GrGLEffect INHERITED;
+
+ UniformHandle fImageIncrementUni;
+ UniformHandle fSurfaceScaleUni;
+ GrGLLight* fLight;
+ GrGLEffectMatrix fEffectMatrix;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLDiffuseLightingEffect : public GrGLLightingEffect {
+public:
+ GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLLightingEffect INHERITED;
+
+ UniformHandle fKDUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLSpecularLightingEffect : public GrGLLightingEffect {
+public:
+ GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLLightingEffect INHERITED;
+
+ UniformHandle fKSUni;
+ UniformHandle fShininessUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrLightingEffect::GrLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset)
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture))
+ , fLight(light)
+ , fSurfaceScale(surfaceScale)
+ , fOffset(offset) {
+ fLight->ref();
+ if (light->requiresFragmentPosition()) {
+ this->setWillReadFragmentPosition();
+ }
+}
+
+GrLightingEffect::~GrLightingEffect() {
+ fLight->unref();
+}
+
+bool GrLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrLightingEffect& s = CastEffect<GrLightingEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ fLight->isEqual(*s.fLight) &&
+ fSurfaceScale == s.fSurfaceScale;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDiffuseLightingEffect::GrDiffuseLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd)
+ : INHERITED(texture, light, surfaceScale, offset), fKD(kd) {
+}
+
+const GrBackendEffectFactory& GrDiffuseLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrDiffuseLightingEffect>::getInstance();
+}
+
+bool GrDiffuseLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrDiffuseLightingEffect& s = CastEffect<GrDiffuseLightingEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ this->kd() == s.kd();
+}
+
+GR_DEFINE_EFFECT_TEST(GrDiffuseLightingEffect);
+
+GrEffectRef* GrDiffuseLightingEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ SkScalar surfaceScale = random->nextSScalar1();
+ SkScalar kd = random->nextUScalar1();
+ SkAutoTUnref<SkLight> light(create_random_light(random));
+ SkIPoint offset = SkIPoint::Make(random->nextS(), random->nextS());
+ return GrDiffuseLightingEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx],
+ light, surfaceScale, offset, kd);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLLightingEffect::GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fImageIncrementUni(kInvalidUniformHandle)
+ , fSurfaceScaleUni(kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrLightingEffect>().coordsType()) {
+ const GrLightingEffect& m = drawEffect.castEffect<GrLightingEffect>();
+ fLight = m.light()->createGLLight();
+}
+
+GrGLLightingEffect::~GrGLLightingEffect() {
+ delete fLight;
+}
+
+void GrGLLightingEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType,
+ "ImageIncrement");
+ fSurfaceScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "SurfaceScale");
+ fLight->emitLightColorUniform(builder);
+ SkString lightFunc;
+ this->emitLightFunc(builder, &lightFunc);
+ static const GrGLShaderVar gSobelArgs[] = {
+ GrGLShaderVar("a", kFloat_GrSLType),
+ GrGLShaderVar("b", kFloat_GrSLType),
+ GrGLShaderVar("c", kFloat_GrSLType),
+ GrGLShaderVar("d", kFloat_GrSLType),
+ GrGLShaderVar("e", kFloat_GrSLType),
+ GrGLShaderVar("f", kFloat_GrSLType),
+ GrGLShaderVar("scale", kFloat_GrSLType),
+ };
+ SkString sobelFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "sobel",
+ SK_ARRAY_COUNT(gSobelArgs),
+ gSobelArgs,
+ "\treturn (-a + b - 2.0 * c + 2.0 * d -e + f) * scale;\n",
+ &sobelFuncName);
+ static const GrGLShaderVar gPointToNormalArgs[] = {
+ GrGLShaderVar("x", kFloat_GrSLType),
+ GrGLShaderVar("y", kFloat_GrSLType),
+ GrGLShaderVar("scale", kFloat_GrSLType),
+ };
+ SkString pointToNormalName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "pointToNormal",
+ SK_ARRAY_COUNT(gPointToNormalArgs),
+ gPointToNormalArgs,
+ "\treturn normalize(vec3(-x * scale, y * scale, 1));\n",
+ &pointToNormalName);
+
+ static const GrGLShaderVar gInteriorNormalArgs[] = {
+ GrGLShaderVar("m", kFloat_GrSLType, 9),
+ GrGLShaderVar("surfaceScale", kFloat_GrSLType),
+ };
+ SkString interiorNormalBody;
+ interiorNormalBody.appendf("\treturn %s(%s(m[0], m[2], m[3], m[5], m[6], m[8], 0.25),\n"
+ "\t %s(m[0], m[6], m[1], m[7], m[2], m[8], 0.25),\n"
+ "\t surfaceScale);\n",
+ pointToNormalName.c_str(),
+ sobelFuncName.c_str(),
+ sobelFuncName.c_str());
+ SkString interiorNormalName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "interiorNormal",
+ SK_ARRAY_COUNT(gInteriorNormalArgs),
+ gInteriorNormalArgs,
+ interiorNormalBody.c_str(),
+ &interiorNormalName);
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s;\n", coords);
+ builder->fsCodeAppend("\t\tfloat m[9];\n");
+
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* surfScale = builder->getUniformCStr(fSurfaceScaleUni);
+
+ int index = 0;
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++) {
+ SkString texCoords;
+ texCoords.appendf("coord + vec2(%d, %d) * %s", dx, dy, imgInc);
+ builder->fsCodeAppendf("\t\tm[%d] = ", index++);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ texCoords.c_str());
+ builder->fsCodeAppend(".a;\n");
+ }
+ }
+ builder->fsCodeAppend("\t\tvec3 surfaceToLight = ");
+ SkString arg;
+ arg.appendf("%s * m[4]", surfScale);
+ fLight->emitSurfaceToLight(builder, arg.c_str());
+ builder->fsCodeAppend(";\n");
+ builder->fsCodeAppendf("\t\t%s = %s(%s(m, %s), surfaceToLight, ",
+ outputColor, lightFunc.c_str(), interiorNormalName.c_str(), surfScale);
+ fLight->emitLightColor(builder, "surfaceToLight");
+ builder->fsCodeAppend(");\n");
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+GrGLEffect::EffectKey GrGLLightingEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps& caps) {
+ const GrLightingEffect& lighting = drawEffect.castEffect<GrLightingEffect>();
+ EffectKey key = lighting.light()->type();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(lighting.getMatrix(),
+ drawEffect,
+ lighting.coordsType(),
+ lighting.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrLightingEffect& lighting = drawEffect.castEffect<GrLightingEffect>();
+ GrTexture* texture = lighting.texture(0);
+ float ySign = texture->origin() == kTopLeft_GrSurfaceOrigin ? -1.0f : 1.0f;
+ uman.set2f(fImageIncrementUni, 1.0f / texture->width(), ySign / texture->height());
+ uman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
+ fLight->setData(uman, lighting.light(), lighting.offset());
+ fEffectMatrix.setData(uman,
+ lighting.getMatrix(),
+ drawEffect,
+ lighting.texture(0));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLDiffuseLightingEffect::GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory, drawEffect)
+ , fKDUni(kInvalidUniformHandle) {
+}
+
+void GrGLDiffuseLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
+ 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),
+ GrGLShaderVar("lightColor", kVec3f_GrSLType)
+ };
+ SkString lightBody;
+ lightBody.appendf("\tfloat colorScale = %s * dot(normal, surfaceToLight);\n", kd);
+ lightBody.appendf("\treturn vec4(lightColor * clamp(colorScale, 0.0, 1.0), 1.0);\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "light",
+ SK_ARRAY_COUNT(gLightArgs),
+ gLightArgs,
+ lightBody.c_str(),
+ funcName);
+}
+
+void GrGLDiffuseLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrDiffuseLightingEffect& diffuse = drawEffect.castEffect<GrDiffuseLightingEffect>();
+ uman.set1f(fKDUni, diffuse.kd());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrSpecularLightingEffect::GrSpecularLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess)
+ : INHERITED(texture, light, surfaceScale, offset),
+ fKS(ks),
+ fShininess(shininess) {
+}
+
+const GrBackendEffectFactory& GrSpecularLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrSpecularLightingEffect>::getInstance();
+}
+
+bool GrSpecularLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrSpecularLightingEffect& s = CastEffect<GrSpecularLightingEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ this->ks() == s.ks() &&
+ this->shininess() == s.shininess();
+}
+
+GR_DEFINE_EFFECT_TEST(GrSpecularLightingEffect);
+
+GrEffectRef* GrSpecularLightingEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ SkScalar surfaceScale = random->nextSScalar1();
+ SkScalar ks = random->nextUScalar1();
+ SkScalar shininess = random->nextUScalar1();
+ SkAutoTUnref<SkLight> light(create_random_light(random));
+ SkIPoint offset = SkIPoint::Make(random->nextS(), random->nextS());
+ return GrSpecularLightingEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx],
+ light, surfaceScale, offset, ks, shininess);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLSpecularLightingEffect::GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLLightingEffect(factory, drawEffect)
+ , fKSUni(kInvalidUniformHandle)
+ , fShininessUni(kInvalidUniformHandle) {
+}
+
+void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
+ const char* ks;
+ const char* shininess;
+
+ fKSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "KS", &ks);
+ fShininessUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Shininess", &shininess);
+
+ static const GrGLShaderVar gLightArgs[] = {
+ GrGLShaderVar("normal", kVec3f_GrSLType),
+ GrGLShaderVar("surfaceToLight", kVec3f_GrSLType),
+ GrGLShaderVar("lightColor", kVec3f_GrSLType)
+ };
+ 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("\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",
+ SK_ARRAY_COUNT(gLightArgs),
+ gLightArgs,
+ lightBody.c_str(),
+ funcName);
+}
+
+void GrGLSpecularLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrSpecularLightingEffect& spec = drawEffect.castEffect<GrSpecularLightingEffect>();
+ uman.set1f(fKSUni, spec.ks());
+ uman.set1f(fShininessUni, spec.shininess());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void GrGLLight::emitLightColorUniform(GrGLShaderBuilder* builder) {
+ fColorUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "LightColor");
+}
+
+void GrGLLight::emitLightColor(GrGLShaderBuilder* builder,
+ const char *surfaceToLight) {
+ builder->fsCodeAppend(builder->getUniformCStr(this->lightColorUni()));
+}
+
+void GrGLLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint&) const {
+ setUniformPoint3(uman, fColorUni, light->color() * SkScalarInvert(SkIntToScalar(255)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLDistantLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kDistant_LightType);
+ const SkDistantLight* distantLight = static_cast<const SkDistantLight*>(light);
+ setUniformNormal3(uman, fDirectionUni, distantLight->direction());
+}
+
+void GrGLDistantLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* dir;
+ fDirectionUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightDirection", &dir);
+ builder->fsCodeAppend(dir);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLPointLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kPoint_LightType);
+ const SkPointLight* pointLight = static_cast<const SkPointLight*>(light);
+ SkPoint3 location = pointLight->location();
+ location.fX -= offset.fX;
+ location.fY -= offset.fY;
+ setUniformPoint3(uman, fLocationUni, location);
+}
+
+void GrGLPointLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* loc;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightLocation", &loc);
+ builder->fsCodeAppendf("normalize(%s - vec3(%s.xy, %s))", loc, builder->fragmentPosition(), z);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLSpotLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kSpot_LightType);
+ const SkSpotLight* spotLight = static_cast<const SkSpotLight *>(light);
+ SkPoint3 location = spotLight->location();
+ location.fX -= offset.fX;
+ location.fY -= offset.fY;
+ setUniformPoint3(uman, fLocationUni, location);
+ uman.set1f(fExponentUni, spotLight->specularExponent());
+ uman.set1f(fCosInnerConeAngleUni, spotLight->cosInnerConeAngle());
+ uman.set1f(fCosOuterConeAngleUni, spotLight->cosOuterConeAngle());
+ uman.set1f(fConeScaleUni, spotLight->coneScale());
+ setUniformNormal3(uman, fSUni, spotLight->s());
+}
+
+void GrGLSpotLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* location;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "LightLocation", &location);
+ builder->fsCodeAppendf("normalize(%s - vec3(%s.xy, %s))",
+ location, builder->fragmentPosition(), z);
+}
+
+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)
+ };
+ SkString lightColorBody;
+ lightColorBody.appendf("\tfloat cosAngle = -dot(surfaceToLight, %s);\n", s);
+ lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosOuter);
+ lightColorBody.appendf("\t\treturn vec3(0);\n");
+ lightColorBody.appendf("\t}\n");
+ lightColorBody.appendf("\tfloat scale = pow(cosAngle, %s);\n", exponent);
+ lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosInner);
+ lightColorBody.appendf("\t\treturn %s * scale * (cosAngle - %s) * %s;\n",
+ color, cosOuter, coneScale);
+ lightColorBody.appendf("\t}\n");
+ lightColorBody.appendf("\treturn %s;\n", color);
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "lightColor",
+ SK_ARRAY_COUNT(gLightColorArgs),
+ gLightColorArgs,
+ lightColorBody.c_str(),
+ &fLightColorFunc);
+
+ builder->fsCodeAppendf("%s(%s)", fLightColorFunc.c_str(), surfaceToLight);
+}
+
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDiffuseLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSpecularLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDistantLight)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPointLight)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSpotLight)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkMagnifierImageFilter.cpp b/effects/SkMagnifierImageFilter.cpp
new file mode 100644
index 00000000..f4a72b8d
--- /dev/null
+++ b/effects/SkMagnifierImageFilter.cpp
@@ -0,0 +1,354 @@
+/*
+ * 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 "SkBitmap.h"
+#include "SkMagnifierImageFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+
+////////////////////////////////////////////////////////////////////////////////
+#if SK_SUPPORT_GPU
+#include "effects/GrSingleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrTBackendEffectFactory.h"
+
+class GrGLMagnifierEffect;
+
+class GrMagnifierEffect : public GrSingleTextureEffect {
+
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ float xOffset,
+ float yOffset,
+ float xZoom,
+ float yZoom,
+ float xInset,
+ float yInset) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMagnifierEffect, (texture,
+ xOffset,
+ yOffset,
+ xZoom,
+ yZoom,
+ xInset,
+ yInset)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrMagnifierEffect() {};
+
+ static const char* Name() { return "Magnifier"; }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ float x_offset() const { return fXOffset; }
+ float y_offset() const { return fYOffset; }
+ float x_zoom() const { return fXZoom; }
+ float y_zoom() const { return fYZoom; }
+ float x_inset() const { return fXInset; }
+ float y_inset() const { return fYInset; }
+
+ typedef GrGLMagnifierEffect GLEffect;
+
+private:
+ GrMagnifierEffect(GrTexture* texture,
+ float xOffset,
+ float yOffset,
+ float xZoom,
+ float yZoom,
+ float xInset,
+ float yInset)
+ : GrSingleTextureEffect(texture, MakeDivByTextureWHMatrix(texture))
+ , fXOffset(xOffset)
+ , fYOffset(yOffset)
+ , fXZoom(xZoom)
+ , fYZoom(yZoom)
+ , fXInset(xInset)
+ , fYInset(yInset) {}
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ float fXOffset;
+ float fYOffset;
+ float fXZoom;
+ float fYZoom;
+ float fXInset;
+ float fYInset;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+
+class GrGLMagnifierEffect : public GrGLEffect {
+public:
+ GrGLMagnifierEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+ UniformHandle fOffsetVar;
+ UniformHandle fZoomVar;
+ UniformHandle fInsetVar;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMagnifierEffect::GrGLMagnifierEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fOffsetVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fZoomVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fInsetVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMagnifierEffect>().coordsType()) {
+}
+
+void GrGLMagnifierEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fOffsetVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uOffset");
+ fZoomVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uZoom");
+ fInsetVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uInset");
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s;\n", coords);
+ builder->fsCodeAppendf("\t\tvec2 zoom_coord = %s + %s / %s;\n",
+ builder->getUniformCStr(fOffsetVar),
+ coords,
+ builder->getUniformCStr(fZoomVar));
+
+ builder->fsCodeAppend("\t\tvec2 delta = min(coord, vec2(1.0, 1.0) - coord);\n");
+
+ builder->fsCodeAppendf("\t\tdelta = delta / %s;\n", builder->getUniformCStr(fInsetVar));
+
+ builder->fsCodeAppend("\t\tfloat weight = 0.0;\n");
+ builder->fsCodeAppend("\t\tif (delta.s < 2.0 && delta.t < 2.0) {\n");
+ builder->fsCodeAppend("\t\t\tdelta = vec2(2.0, 2.0) - delta;\n");
+ builder->fsCodeAppend("\t\t\tfloat dist = length(delta);\n");
+ builder->fsCodeAppend("\t\t\tdist = max(2.0 - dist, 0.0);\n");
+ builder->fsCodeAppend("\t\t\tweight = min(dist * dist, 1.0);\n");
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppend("\t\t\tvec2 delta_squared = delta * delta;\n");
+ builder->fsCodeAppend("\t\t\tweight = min(min(delta_squared.s, delta_squared.y), 1.0);\n");
+ builder->fsCodeAppend("\t\t}\n");
+
+ builder->fsCodeAppend("\t\tvec2 mix_coord = mix(coord, zoom_coord, weight);\n");
+ builder->fsCodeAppend("\t\tvec4 output_color = ");
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "mix_coord");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s = output_color;", outputColor);
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+void GrGLMagnifierEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrMagnifierEffect& zoom = drawEffect.castEffect<GrMagnifierEffect>();
+ 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(), drawEffect, zoom.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLMagnifierEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMagnifierEffect& zoom = drawEffect.castEffect<GrMagnifierEffect>();
+ return GrGLEffectMatrix::GenKey(zoom.getMatrix(),
+ drawEffect,
+ zoom.coordsType(),
+ zoom.texture(0));
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrMagnifierEffect);
+
+GrEffectRef* GrMagnifierEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture** textures) {
+ const int kMaxWidth = 200;
+ const int kMaxHeight = 200;
+ const int kMaxInset = 20;
+ uint32_t width = random->nextULessThan(kMaxWidth);
+ uint32_t height = random->nextULessThan(kMaxHeight);
+ uint32_t x = random->nextULessThan(kMaxWidth - width);
+ uint32_t y = random->nextULessThan(kMaxHeight - height);
+ SkScalar inset = SkIntToScalar(random->nextULessThan(kMaxInset));
+
+ SkAutoTUnref<SkMagnifierImageFilter> filter(
+ new SkMagnifierImageFilter(
+ SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
+ SkIntToScalar(width), SkIntToScalar(height)),
+ inset));
+ GrEffectRef* effect;
+ filter->asNewEffect(&effect, textures[0], SkIPoint::Make(0, 0));
+ GrAssert(NULL != effect);
+ return effect;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const GrBackendEffectFactory& GrMagnifierEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMagnifierEffect>::getInstance();
+}
+
+bool GrMagnifierEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMagnifierEffect& s = CastEffect<GrMagnifierEffect>(sBase);
+ return (this->texture(0) == s.texture(0) &&
+ this->fXOffset == s.fXOffset &&
+ this->fYOffset == s.fYOffset &&
+ this->fXZoom == s.fXZoom &&
+ this->fYZoom == s.fYZoom &&
+ this->fXInset == s.fXInset &&
+ this->fYInset == s.fYInset);
+}
+
+void GrMagnifierEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+SkMagnifierImageFilter::SkMagnifierImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ float x = buffer.readScalar();
+ float y = buffer.readScalar();
+ float width = buffer.readScalar();
+ float height = buffer.readScalar();
+ fSrcRect = SkRect::MakeXYWH(x, y, width, height);
+ fInset = buffer.readScalar();
+}
+
+// FIXME: implement single-input semantics
+SkMagnifierImageFilter::SkMagnifierImageFilter(SkRect srcRect, SkScalar inset)
+ : INHERITED(0), fSrcRect(srcRect), fInset(inset) {
+ SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0);
+}
+
+#if SK_SUPPORT_GPU
+bool SkMagnifierImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint&) const {
+ if (effect) {
+ *effect = GrMagnifierEffect::Create(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;
+}
+#endif
+
+void SkMagnifierImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSrcRect.x());
+ buffer.writeScalar(fSrcRect.y());
+ buffer.writeScalar(fSrcRect.width());
+ buffer.writeScalar(fSrcRect.height());
+ buffer.writeScalar(fInset);
+}
+
+bool SkMagnifierImageFilter::onFilterImage(Proxy*, const SkBitmap& src,
+ const SkMatrix&, SkBitmap* dst,
+ SkIPoint* offset) {
+ SkASSERT(src.config() == SkBitmap::kARGB_8888_Config);
+ SkASSERT(fSrcRect.width() < src.width());
+ SkASSERT(fSrcRect.height() < src.height());
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ SkASSERT(src.getPixels());
+ if (!src.getPixels() || src.width() <= 0 || src.height() <= 0) {
+ return false;
+ }
+
+ SkScalar inv_inset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1;
+
+ SkScalar inv_x_zoom = fSrcRect.width() / src.width();
+ SkScalar inv_y_zoom = fSrcRect.height() / src.height();
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+ SkColor* sptr = src.getAddr32(0, 0);
+ SkColor* dptr = dst->getAddr32(0, 0);
+ int width = src.width(), height = src.height();
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ SkScalar x_dist = SkMin32(x, width - x - 1) * inv_inset;
+ SkScalar y_dist = SkMin32(y, height - y - 1) * inv_inset;
+ SkScalar weight = 0;
+
+ static const SkScalar kScalar2 = SkScalar(2);
+
+ // To create a smooth curve at the corners, we need to work on
+ // a square twice the size of the inset.
+ if (x_dist < kScalar2 && y_dist < kScalar2) {
+ x_dist = kScalar2 - x_dist;
+ y_dist = kScalar2 - y_dist;
+
+ SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) +
+ SkScalarSquare(y_dist));
+ dist = SkMaxScalar(kScalar2 - dist, 0);
+ weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1);
+ } else {
+ SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist),
+ SkScalarSquare(y_dist));
+ weight = SkMinScalar(sqDist, SK_Scalar1);
+ }
+
+ SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * inv_x_zoom)) +
+ (SK_Scalar1 - weight) * x;
+ SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * inv_y_zoom)) +
+ (SK_Scalar1 - weight) * y;
+
+ int x_val = SkMin32(SkScalarFloorToInt(x_interp), width - 1);
+ int y_val = SkMin32(SkScalarFloorToInt(y_interp), height - 1);
+
+ *dptr = sptr[y_val * width + x_val];
+ dptr++;
+ }
+ }
+ return true;
+}
diff --git a/effects/SkMatrixConvolutionImageFilter.cpp b/effects/SkMatrixConvolutionImageFilter.cpp
new file mode 100644
index 00000000..b3ce7ecc
--- /dev/null
+++ b/effects/SkMatrixConvolutionImageFilter.cpp
@@ -0,0 +1,587 @@
+/*
+ * 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 "SkMatrixConvolutionImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+#include "SkUnPreMultiply.h"
+
+#if SK_SUPPORT_GPU
+#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)
+ : INHERITED(input),
+ fKernelSize(kernelSize),
+ fGain(gain),
+ fBias(bias),
+ fTarget(target),
+ fTileMode(tileMode),
+ fConvolveAlpha(convolveAlpha) {
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ memcpy(fKernel, kernel, size * sizeof(SkScalar));
+ SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1);
+ SkASSERT(target.fX >= 0 && target.fX < kernelSize.fWidth);
+ SkASSERT(target.fY >= 0 && target.fY < kernelSize.fHeight);
+}
+
+SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fKernelSize.fWidth = buffer.readInt();
+ fKernelSize.fHeight = buffer.readInt();
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ SkDEBUGCODE(uint32_t readSize = )buffer.readScalarArray(fKernel);
+ SkASSERT(readSize == size);
+ fGain = buffer.readScalar();
+ fBias = buffer.readScalar();
+ fTarget.fX = buffer.readInt();
+ fTarget.fY = buffer.readInt();
+ fTileMode = (TileMode) buffer.readInt();
+ fConvolveAlpha = buffer.readBool();
+}
+
+void SkMatrixConvolutionImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fKernelSize.fWidth);
+ buffer.writeInt(fKernelSize.fHeight);
+ buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight);
+ buffer.writeScalar(fGain);
+ buffer.writeScalar(fBias);
+ buffer.writeInt(fTarget.fX);
+ buffer.writeInt(fTarget.fY);
+ buffer.writeInt((int) fTileMode);
+ buffer.writeBool(fConvolveAlpha);
+}
+
+SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
+ delete[] fKernel;
+}
+
+class UncheckedPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x = SkClampMax(x, src.width() - 1);
+ y = SkClampMax(y, src.height() - 1);
+ return *src.getAddr32(x, y);
+ }
+};
+
+class RepeatPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x %= src.width();
+ y %= src.height();
+ if (x < 0) {
+ x += src.width();
+ }
+ if (y < 0) {
+ y += src.height();
+ }
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampToBlackPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ if (x < 0 || x >= src.width() || y < 0 || y >= src.height()) {
+ return 0;
+ } else {
+ return *src.getAddr32(x, y);
+ }
+ }
+};
+
+template<class PixelFetcher, bool convolveAlpha>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(rect.fLeft, y);
+ for (int x = rect.fLeft; x < rect.fRight; ++x) {
+ SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ for (int cy = 0; cy < fKernelSize.fHeight; cy++) {
+ for (int cx = 0; cx < fKernelSize.fWidth; cx++) {
+ SkPMColor s = PixelFetcher::fetch(src, x + cx - fTarget.fX, y + cy - fTarget.fY);
+ SkScalar k = fKernel[cy * fKernelSize.fWidth + cx];
+ if (convolveAlpha) {
+ sumA += SkScalarMul(SkIntToScalar(SkGetPackedA32(s)), k);
+ }
+ sumR += SkScalarMul(SkIntToScalar(SkGetPackedR32(s)), k);
+ sumG += SkScalarMul(SkIntToScalar(SkGetPackedG32(s)), k);
+ sumB += SkScalarMul(SkIntToScalar(SkGetPackedB32(s)), k);
+ }
+ }
+ int a = convolveAlpha
+ ? SkClampMax(SkScalarFloorToInt(SkScalarMul(sumA, fGain) + fBias), 255)
+ : 255;
+ int r = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumR, fGain) + fBias), a);
+ int g = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumG, fGain) + fBias), a);
+ int b = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumB, fGain) + fBias), a);
+ if (!convolveAlpha) {
+ a = SkGetPackedA32(PixelFetcher::fetch(src, x, y));
+ *dptr++ = SkPreMultiplyARGB(a, r, g, b);
+ } else {
+ *dptr++ = SkPackARGB32(a, r, g, b);
+ }
+ }
+ }
+}
+
+template<class PixelFetcher>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ if (fConvolveAlpha) {
+ filterPixels<PixelFetcher, true>(src, result, rect);
+ } else {
+ filterPixels<PixelFetcher, false>(src, result, rect);
+ }
+}
+
+void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ filterPixels<UncheckedPixelFetcher>(src, result, rect);
+}
+
+void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ switch (fTileMode) {
+ case kClamp_TileMode:
+ filterPixels<ClampPixelFetcher>(src, result, rect);
+ break;
+ case kRepeat_TileMode:
+ filterPixels<RepeatPixelFetcher>(src, result, rect);
+ break;
+ case kClampToBlack_TileMode:
+ filterPixels<ClampToBlackPixelFetcher>(src, result, rect);
+ break;
+ }
+}
+
+// FIXME: This should be refactored to SkImageFilterUtils for
+// use by other filters. For now, we assume the input is always
+// premultiplied and unpremultiply it
+static SkBitmap unpremultiplyBitmap(const SkBitmap& src)
+{
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return SkBitmap();
+ }
+ SkBitmap result;
+ result.setConfig(src.config(), src.width(), src.height());
+ result.allocPixels();
+ if (!result.getPixels()) {
+ return SkBitmap();
+ }
+ for (int y = 0; y < src.height(); ++y) {
+ const uint32_t* srcRow = src.getAddr32(0, y);
+ uint32_t* dstRow = result.getAddr32(0, y);
+ for (int x = 0; x < src.width(); ++x) {
+ dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]);
+ }
+ }
+ return result;
+}
+
+bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ if (!fConvolveAlpha && !src.isOpaque()) {
+ src = unpremultiplyBitmap(src);
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ result->setConfig(src.config(), src.width(), src.height());
+ result->allocPixels();
+
+ SkIRect interior = SkIRect::MakeXYWH(fTarget.fX, fTarget.fY,
+ src.width() - fKernelSize.fWidth + 1,
+ src.height() - fKernelSize.fHeight + 1);
+ SkIRect top = SkIRect::MakeWH(src.width(), fTarget.fY);
+ SkIRect bottom = SkIRect::MakeLTRB(0, interior.bottom(),
+ src.width(), src.height());
+ SkIRect left = SkIRect::MakeXYWH(0, interior.top(),
+ fTarget.fX, interior.height());
+ SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(),
+ src.width(), interior.bottom());
+ filterBorderPixels(src, result, top);
+ filterBorderPixels(src, result, left);
+ filterInteriorPixels(src, result, interior);
+ filterBorderPixels(src, result, right);
+ filterBorderPixels(src, result, bottom);
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMatrixConvolutionEffect;
+
+class GrMatrixConvolutionEffect : public GrSingleTextureEffect {
+public:
+ typedef SkMatrixConvolutionImageFilter::TileMode TileMode;
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
+ kernelSize,
+ kernel,
+ gain,
+ bias,
+ target,
+ tileMode,
+ convolveAlpha)));
+ return CreateEffectRef(effect);
+ }
+ virtual ~GrMatrixConvolutionEffect();
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // TODO: Try to do better?
+ *validFlags = 0;
+ }
+
+ static const char* Name() { return "MatrixConvolution"; }
+ const SkISize& kernelSize() const { return fKernelSize; }
+ const float* target() const { return fTarget; }
+ const float* kernel() const { return fKernel; }
+ float gain() const { return fGain; }
+ float bias() const { return fBias; }
+ TileMode tileMode() const { return fTileMode; }
+ bool convolveAlpha() const { return fConvolveAlpha; }
+
+ typedef GrGLMatrixConvolutionEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+private:
+ GrMatrixConvolutionEffect(GrTexture*,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha);
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ SkISize fKernelSize;
+ float *fKernel;
+ float fGain;
+ float fBias;
+ float fTarget[2];
+ TileMode fTileMode;
+ bool fConvolveAlpha;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+class GrGLMatrixConvolutionEffect : public GrGLEffect {
+public:
+ GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) 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;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMatrixConvolutionEffect::GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fKernelUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fTargetUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fGainUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fBiasUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMatrixConvolutionEffect>().coordsType()) {
+ const GrMatrixConvolutionEffect& m = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ fKernelSize = m.kernelSize();
+ fTileMode = m.tileMode();
+ fConvolveAlpha = m.convolveAlpha();
+}
+
+static void appendTextureLookup(GrGLShaderBuilder* builder,
+ const GrGLShaderBuilder::TextureSampler& sampler,
+ const char* coord,
+ SkMatrixConvolutionImageFilter::TileMode tileMode) {
+ SkString clampedCoord;
+ switch (tileMode) {
+ case SkMatrixConvolutionImageFilter::kClamp_TileMode:
+ clampedCoord.printf("clamp(%s, 0.0, 1.0)", coord);
+ coord = clampedCoord.c_str();
+ break;
+ case SkMatrixConvolutionImageFilter::kRepeat_TileMode:
+ clampedCoord.printf("fract(%s)", coord);
+ coord = clampedCoord.c_str();
+ break;
+ case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode:
+ builder->fsCodeAppendf("clamp(%s, 0.0, 1.0) != %s ? vec4(0, 0, 0, 0) : ", coord, coord);
+ break;
+ }
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, sampler, coord);
+}
+
+void GrGLMatrixConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &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");
+
+ const char* target = builder->getUniformCStr(fTargetUni);
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* kernel = builder->getUniformCStr(fKernelUni);
+ const char* gain = builder->getUniformCStr(fGainUni);
+ const char* bias = builder->getUniformCStr(fBiasUni);
+ int kWidth = fKernelSize.width();
+ int kHeight = fKernelSize.height();
+
+ builder->fsCodeAppend("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
+ builder->fsCodeAppendf("\t\tvec2 coord = %s - %s * %s;\n", coords, target, imgInc);
+ builder->fsCodeAppendf("\t\tfor (int y = 0; y < %d; y++) {\n", kHeight);
+ builder->fsCodeAppendf("\t\t\tfor (int x = 0; x < %d; x++) {\n", kWidth);
+ builder->fsCodeAppendf("\t\t\t\tfloat k = %s[y * %d + x];\n", kernel, kWidth);
+ builder->fsCodeAppendf("\t\t\t\tvec2 coord2 = coord + vec2(x, y) * %s;\n", imgInc);
+ builder->fsCodeAppend("\t\t\t\tvec4 c = ");
+ appendTextureLookup(builder, samplers[0], "coord2", fTileMode);
+ builder->fsCodeAppend(";\n");
+ if (!fConvolveAlpha) {
+ builder->fsCodeAppend("\t\t\t\tc.rgb /= c.a;\n");
+ }
+ builder->fsCodeAppend("\t\t\t\tsum += c * k;\n");
+ builder->fsCodeAppend("\t\t\t}\n");
+ builder->fsCodeAppend("\t\t}\n");
+ if (fConvolveAlpha) {
+ builder->fsCodeAppendf("\t\t%s = sum * %s + %s;\n", outputColor, gain, bias);
+ builder->fsCodeAppendf("\t\t%s.rgb = clamp(%s.rgb, 0.0, %s.a);\n", outputColor, outputColor, outputColor);
+ } else {
+ builder->fsCodeAppend("\t\tvec4 c = ");
+ appendTextureLookup(builder, samplers[0], coords, fTileMode);
+ builder->fsCodeAppend(";\n");
+ builder->fsCodeAppendf("\t\t%s.a = c.a;\n", outputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = sum.rgb * %s + %s;\n", outputColor, gain, bias);
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ }
+}
+
+namespace {
+
+int encodeXY(int x, int y) {
+ SkASSERT(x >= 1 && y >= 1 && x * y <= 32);
+ if (y < x)
+ return 0x40 | encodeXY(y, x);
+ else
+ return (0x40 >> x) | (y - x);
+}
+
+};
+
+GrGLEffect::EffectKey GrGLMatrixConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMatrixConvolutionEffect& m = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ EffectKey key = encodeXY(m.kernelSize().width(), m.kernelSize().height());
+ key |= m.tileMode() << 7;
+ key |= m.convolveAlpha() ? 1 << 9 : 0;
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ drawEffect,
+ m.coordsType(),
+ m.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLMatrixConvolutionEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrMatrixConvolutionEffect& conv = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ GrTexture& texture = *conv.texture(0);
+ // the code we generated was for a specific kernel size
+ GrAssert(conv.kernelSize() == fKernelSize);
+ GrAssert(conv.tileMode() == fTileMode);
+ float imageIncrement[2];
+ float ySign = texture.origin() == kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
+ imageIncrement[0] = 1.0f / texture.width();
+ imageIncrement[1] = ySign / texture.height();
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ uman.set2fv(fTargetUni, 0, 1, conv.target());
+ uman.set1fv(fKernelUni, 0, fKernelSize.width() * fKernelSize.height(), conv.kernel());
+ uman.set1f(fGainUni, conv.gain());
+ uman.set1f(fBiasUni, conv.bias());
+ fEffectMatrix.setData(uman,
+ conv.getMatrix(),
+ drawEffect,
+ conv.texture(0));
+}
+
+GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha)
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture)),
+ fKernelSize(kernelSize),
+ fGain(SkScalarToFloat(gain)),
+ fBias(SkScalarToFloat(bias) / 255.0f),
+ fTileMode(tileMode),
+ fConvolveAlpha(convolveAlpha) {
+ fKernel = new float[kernelSize.width() * kernelSize.height()];
+ for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
+ fKernel[i] = SkScalarToFloat(kernel[i]);
+ }
+ fTarget[0] = static_cast<float>(target.x());
+ fTarget[1] = static_cast<float>(target.y());
+}
+
+GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() {
+ delete[] fKernel;
+}
+
+const GrBackendEffectFactory& GrMatrixConvolutionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMatrixConvolutionEffect>::getInstance();
+}
+
+bool GrMatrixConvolutionEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMatrixConvolutionEffect& s = CastEffect<GrMatrixConvolutionEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ fKernelSize == s.kernelSize() &&
+ !memcmp(fKernel, s.kernel(), fKernelSize.width() * fKernelSize.height() * sizeof(float)) &&
+ fGain == s.gain() &&
+ fBias == s.bias() &&
+ fTarget == s.target() &&
+ fTileMode == s.tileMode() &&
+ fConvolveAlpha == s.convolveAlpha();
+}
+
+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
+
+GrEffectRef* GrMatrixConvolutionEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ 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);
+ SkAutoTDeleteArray<SkScalar> kernel(new SkScalar[width * height]);
+ for (int i = 0; i < width * height; i++) {
+ kernel.get()[i] = random->nextSScalar1();
+ }
+ SkScalar gain = random->nextSScalar1();
+ SkScalar bias = random->nextSScalar1();
+ SkIPoint target = SkIPoint::Make(random->nextRangeU(0, kernelSize.width()),
+ random->nextRangeU(0, kernelSize.height()));
+ TileMode tileMode = static_cast<TileMode>(random->nextRangeU(0, 2));
+ bool convolveAlpha = random->nextBool();
+ return GrMatrixConvolutionEffect::Create(textures[texIdx],
+ kernelSize,
+ kernel.get(),
+ gain,
+ bias,
+ target,
+ tileMode,
+ convolveAlpha);
+}
+
+bool SkMatrixConvolutionImageFilter::asNewEffect(GrEffectRef** effect,
+ GrTexture* texture,
+ const SkIPoint&) const {
+ if (!effect) {
+ return fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE;
+ }
+ SkASSERT(fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE);
+ *effect = GrMatrixConvolutionEffect::Create(texture,
+ fKernelSize,
+ fKernel,
+ fGain,
+ fBias,
+ fTarget,
+ fTileMode,
+ fConvolveAlpha);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#endif
diff --git a/effects/SkMergeImageFilter.cpp b/effects/SkMergeImageFilter.cpp
new file mode 100755
index 00000000..0c1388f9
--- /dev/null
+++ b/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/effects/SkMorphologyImageFilter.cpp b/effects/SkMorphologyImageFilter.cpp
new file mode 100644
index 00000000..fb8fd000
--- /dev/null
+++ b/effects/SkMorphologyImageFilter.cpp
@@ -0,0 +1,533 @@
+/*
+ * 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 "SkMorphologyImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/Gr1DKernelEffect.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fRadius.fWidth = buffer.readInt();
+ fRadius.fHeight = buffer.readInt();
+}
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input)
+ : INHERITED(input), fRadius(SkISize::Make(radiusX, radiusY)) {
+}
+
+
+void SkMorphologyImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fRadius.fWidth);
+ buffer.writeInt(fRadius.fHeight);
+}
+
+static void erode(const SkPMColor* src, SkPMColor* dst,
+ int radius, int width, int height,
+ int srcStrideX, int srcStrideY,
+ int dstStrideX, int dstStrideY)
+{
+ radius = SkMin32(radius, width - 1);
+ const SkPMColor* upperSrc = src + radius * srcStrideX;
+ for (int x = 0; x < width; ++x) {
+ const SkPMColor* lp = src;
+ const SkPMColor* up = upperSrc;
+ SkPMColor* dptr = dst;
+ for (int y = 0; y < height; ++y) {
+ int minB = 255, minG = 255, minR = 255, minA = 255;
+ for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+ int b = SkGetPackedB32(*p);
+ int g = SkGetPackedG32(*p);
+ int r = SkGetPackedR32(*p);
+ int a = SkGetPackedA32(*p);
+ if (b < minB) minB = b;
+ if (g < minG) minG = g;
+ if (r < minR) minR = r;
+ if (a < minA) minA = a;
+ }
+ *dptr = SkPackARGB32(minA, minR, minG, minB);
+ dptr += dstStrideY;
+ lp += srcStrideY;
+ up += srcStrideY;
+ }
+ if (x >= radius) src += srcStrideX;
+ if (x + radius < width - 1) upperSrc += srcStrideX;
+ dst += dstStrideX;
+ }
+}
+
+static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+ erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusX, src.width(), src.height(),
+ 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+ erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusY, src.height(), src.width(),
+ src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+static void dilate(const SkPMColor* src, SkPMColor* dst,
+ int radius, int width, int height,
+ int srcStrideX, int srcStrideY,
+ int dstStrideX, int dstStrideY)
+{
+ radius = SkMin32(radius, width - 1);
+ const SkPMColor* upperSrc = src + radius * srcStrideX;
+ for (int x = 0; x < width; ++x) {
+ const SkPMColor* lp = src;
+ const SkPMColor* up = upperSrc;
+ SkPMColor* dptr = dst;
+ for (int y = 0; y < height; ++y) {
+ int maxB = 0, maxG = 0, maxR = 0, maxA = 0;
+ for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+ int b = SkGetPackedB32(*p);
+ int g = SkGetPackedG32(*p);
+ int r = SkGetPackedR32(*p);
+ int a = SkGetPackedA32(*p);
+ if (b > maxB) maxB = b;
+ if (g > maxG) maxG = g;
+ if (r > maxR) maxR = r;
+ if (a > maxA) maxA = a;
+ }
+ *dptr = SkPackARGB32(maxA, maxR, maxG, maxB);
+ dptr += dstStrideY;
+ lp += srcStrideY;
+ up += srcStrideY;
+ }
+ if (x >= radius) src += srcStrideX;
+ if (x + radius < width - 1) upperSrc += srcStrideX;
+ dst += dstStrideX;
+ }
+}
+
+static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+ dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusX, src.width(), src.height(),
+ 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+ dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusY, src.height(), src.width(),
+ src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+bool SkErodeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+
+ int width = radius().width();
+ int height = radius().height();
+
+ if (width < 0 || height < 0) {
+ return false;
+ }
+
+ if (width == 0 && height == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (width > 0 && height > 0) {
+ erodeX(src, &temp, width);
+ erodeY(temp, dst, height);
+ } else if (width > 0) {
+ erodeX(src, dst, width);
+ } else if (height > 0) {
+ erodeY(src, dst, height);
+ }
+ return true;
+}
+
+bool SkDilateImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+
+ int width = radius().width();
+ int height = radius().height();
+
+ if (width < 0 || height < 0) {
+ return false;
+ }
+
+ if (width == 0 && height == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (width > 0 && height > 0) {
+ dilateX(src, &temp, width);
+ dilateY(temp, dst, height);
+ } else if (width > 0) {
+ dilateX(src, dst, width);
+ } else if (height > 0) {
+ dilateY(src, dst, height);
+ }
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMorphologyEffect;
+
+/**
+ * Morphology effects. Depending upon the type of morphology, either the
+ * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the
+ * kernel is selected as the new color. The new color is modulated by the input
+ * color.
+ */
+class GrMorphologyEffect : public Gr1DKernelEffect {
+
+public:
+
+ enum MorphologyType {
+ kErode_MorphologyType,
+ kDilate_MorphologyType,
+ };
+
+ static GrEffectRef* Create(GrTexture* tex, Direction dir, int radius, MorphologyType type) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMorphologyEffect, (tex, dir, radius, type)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrMorphologyEffect();
+
+ MorphologyType type() const { return fType; }
+
+ static const char* Name() { return "Morphology"; }
+
+ typedef GrGLMorphologyEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+protected:
+
+ MorphologyType fType;
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef Gr1DKernelEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMorphologyEffect : public GrGLEffect {
+public:
+ GrGLMorphologyEffect (const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
+
+ int fRadius;
+ GrMorphologyEffect::MorphologyType fType;
+ GrGLUniformManager::UniformHandle fImageIncrementUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMorphologyEffect::GrGLMorphologyEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMorphologyEffect>().coordsType()) {
+ const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
+ fRadius = m.radius();
+ fType = m.type();
+}
+
+void GrGLMorphologyEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+
+ const char* func;
+ switch (fType) {
+ case GrMorphologyEffect::kErode_MorphologyType:
+ builder->fsCodeAppendf("\t\t%s = vec4(1, 1, 1, 1);\n", outputColor);
+ func = "min";
+ break;
+ case GrMorphologyEffect::kDilate_MorphologyType:
+ builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
+ func = "max";
+ break;
+ default:
+ GrCrash("Unexpected type");
+ func = ""; // suppress warning
+ break;
+ }
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords, fRadius, imgInc);
+ builder->fsCodeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
+ builder->fsCodeAppendf("\t\t\t%s = %s(%s, ", outputColor, func, outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "coord");
+ builder->fsCodeAppend(");\n");
+ builder->fsCodeAppendf("\t\t\tcoord += %s;\n", imgInc);
+ builder->fsCodeAppend("\t\t}\n");
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+GrGLEffect::EffectKey GrGLMorphologyEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
+ EffectKey key = static_cast<EffectKey>(m.radius());
+ key |= (m.type() << 8);
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ drawEffect,
+ m.coordsType(),
+ m.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const Gr1DKernelEffect& kern = drawEffect.castEffect<Gr1DKernelEffect>();
+ GrTexture& texture = *kern.texture(0);
+ // the code we generated was for a specific kernel radius
+ GrAssert(kern.radius() == fRadius);
+ float imageIncrement[2] = { 0 };
+ switch (kern.direction()) {
+ case Gr1DKernelEffect::kX_Direction:
+ imageIncrement[0] = 1.0f / texture.width();
+ break;
+ case Gr1DKernelEffect::kY_Direction:
+ imageIncrement[1] = 1.0f / texture.height();
+ break;
+ default:
+ GrCrash("Unknown filter direction.");
+ }
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ fEffectMatrix.setData(uman, kern.getMatrix(), drawEffect, kern.texture(0));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
+ Direction direction,
+ int radius,
+ MorphologyType type)
+ : Gr1DKernelEffect(texture, direction, radius)
+ , fType(type) {
+}
+
+GrMorphologyEffect::~GrMorphologyEffect() {
+}
+
+const GrBackendEffectFactory& GrMorphologyEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMorphologyEffect>::getInstance();
+}
+
+bool GrMorphologyEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMorphologyEffect& s = CastEffect<GrMorphologyEffect>(sBase);
+ return (this->texture(0) == s.texture(0) &&
+ this->radius() == s.radius() &&
+ this->direction() == s.direction() &&
+ this->type() == s.type());
+}
+
+void GrMorphologyEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // This is valid because the color components of the result of the kernel all come
+ // exactly from existing values in the source texture.
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrMorphologyEffect);
+
+GrEffectRef* GrMorphologyEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ 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);
+ MorphologyType type = random->nextBool() ? GrMorphologyEffect::kErode_MorphologyType :
+ GrMorphologyEffect::kDilate_MorphologyType;
+
+ return GrMorphologyEffect::Create(textures[texIdx], dir, radius, type);
+}
+
+namespace {
+
+void apply_morphology_pass(GrContext* context,
+ GrTexture* texture,
+ const SkIRect& rect,
+ int radius,
+ GrMorphologyEffect::MorphologyType morphType,
+ Gr1DKernelEffect::Direction direction) {
+ GrPaint paint;
+ paint.addColorEffect(GrMorphologyEffect::Create(texture,
+ direction,
+ radius,
+ morphType))->unref();
+ context->drawRect(paint, SkRect::MakeFromIRect(rect));
+}
+
+GrTexture* apply_morphology(GrTexture* srcTexture,
+ const SkIRect& rect,
+ GrMorphologyEffect::MorphologyType morphType,
+ SkISize radius) {
+ GrContext* context = srcTexture->getContext();
+ srcTexture->ref();
+
+ GrContext::AutoMatrix am;
+ am.setIdentity(context);
+
+ GrContext::AutoClip acs(context, SkRect::MakeWH(SkIntToScalar(srcTexture->width()),
+ SkIntToScalar(srcTexture->height())));
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = rect.width();
+ desc.fHeight = rect.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ if (radius.fWidth > 0) {
+ GrAutoScratchTexture ast(context, desc);
+ GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
+ apply_morphology_pass(context, srcTexture, rect, radius.fWidth,
+ morphType, Gr1DKernelEffect::kX_Direction);
+ SkIRect clearRect = SkIRect::MakeXYWH(rect.fLeft, rect.fBottom,
+ rect.width(), radius.fHeight);
+ context->clear(&clearRect, 0x0);
+ srcTexture->unref();
+ srcTexture = ast.detach();
+ }
+ if (radius.fHeight > 0) {
+ GrAutoScratchTexture ast(context, desc);
+ GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
+ apply_morphology_pass(context, srcTexture, rect, radius.fHeight,
+ morphType, Gr1DKernelEffect::kY_Direction);
+ srcTexture->unref();
+ srcTexture = ast.detach();
+ }
+ return srcTexture;
+}
+
+};
+
+bool SkDilateImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap inputBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+ return false;
+ }
+ GrTexture* input = inputBM.getTexture();
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
+ GrMorphologyEffect::kDilate_MorphologyType, radius()));
+ return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+}
+
+bool SkErodeImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap inputBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+ return false;
+ }
+ GrTexture* input = inputBM.getTexture();
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
+ GrMorphologyEffect::kErode_MorphologyType, radius()));
+ return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+}
+
+#endif
diff --git a/effects/SkOffsetImageFilter.cpp b/effects/SkOffsetImageFilter.cpp
new file mode 100644
index 00000000..ad5e49d7
--- /dev/null
+++ b/effects/SkOffsetImageFilter.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ 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/effects/SkPaintFlagsDrawFilter.cpp b/effects/SkPaintFlagsDrawFilter.cpp
new file mode 100644
index 00000000..dc1c0074
--- /dev/null
+++ b/effects/SkPaintFlagsDrawFilter.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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 "SkPaintFlagsDrawFilter.h"
+#include "SkPaint.h"
+
+SkPaintFlagsDrawFilter::SkPaintFlagsDrawFilter(uint32_t clearFlags,
+ uint32_t setFlags) {
+ fClearFlags = SkToU16(clearFlags & SkPaint::kAllFlags);
+ fSetFlags = SkToU16(setFlags & SkPaint::kAllFlags);
+}
+
+bool SkPaintFlagsDrawFilter::filter(SkPaint* paint, Type) {
+ paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ return true;
+}
diff --git a/effects/SkPerlinNoiseShader.cpp b/effects/SkPerlinNoiseShader.cpp
new file mode 100644
index 00000000..1d26920c
--- /dev/null
+++ b/effects/SkPerlinNoiseShader.cpp
@@ -0,0 +1,1378 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDither.h"
+#include "SkPerlinNoiseShader.h"
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkGr.h"
+#endif
+
+static const int kBlockSize = 256;
+static const int kBlockMask = kBlockSize - 1;
+static const int kPerlinNoise = 4096;
+static const int kRandMaximum = SK_MaxS32; // 2**31 - 1
+
+namespace {
+
+// noiseValue is the color component's value (or color)
+// limitValue is the maximum perlin noise array index value allowed
+// newValue is the current noise dimension (either width or height)
+inline int checkNoise(int noiseValue, int limitValue, int newValue) {
+ // If the noise value would bring us out of bounds of the current noise array while we are
+ // stiching noise tiles together, wrap the noise around the current dimension of the noise to
+ // stay within the array bounds in a continuous fashion (so that tiling lines are not visible)
+ if (noiseValue >= limitValue) {
+ noiseValue -= newValue;
+ }
+ if (noiseValue >= limitValue - 1) {
+ noiseValue -= newValue - 1;
+ }
+ return noiseValue;
+}
+
+inline SkScalar smoothCurve(SkScalar t) {
+ static const SkScalar SK_Scalar3 = SkFloatToScalar(3.0f);
+
+ // returns t * t * (3 - 2 * t)
+ return SkScalarMul(SkScalarSquare(t), SK_Scalar3 - 2 * t);
+}
+
+} // end namespace
+
+struct SkPerlinNoiseShader::StitchData {
+ StitchData()
+ : fWidth(0)
+ , fWrapX(0)
+ , fHeight(0)
+ , fWrapY(0)
+ {}
+
+ bool operator==(const StitchData& other) const {
+ return fWidth == other.fWidth &&
+ fWrapX == other.fWrapX &&
+ fHeight == other.fHeight &&
+ fWrapY == other.fWrapY;
+ }
+
+ int fWidth; // How much to subtract to wrap for stitching.
+ int fWrapX; // Minimum value to wrap.
+ int fHeight;
+ int fWrapY;
+};
+
+struct SkPerlinNoiseShader::PaintingData {
+ PaintingData(const SkISize& tileSize)
+ : fSeed(0)
+ , fTileSize(tileSize)
+ , fPermutationsBitmap(NULL)
+ , fNoiseBitmap(NULL)
+ {}
+
+ ~PaintingData()
+ {
+ SkDELETE(fPermutationsBitmap);
+ SkDELETE(fNoiseBitmap);
+ }
+
+ int fSeed;
+ uint8_t fLatticeSelector[kBlockSize];
+ uint16_t fNoise[4][kBlockSize][2];
+ SkPoint fGradient[4][kBlockSize];
+ SkISize fTileSize;
+ SkVector fBaseFrequency;
+ StitchData fStitchDataInit;
+
+private:
+
+ SkBitmap* fPermutationsBitmap;
+ SkBitmap* fNoiseBitmap;
+
+public:
+
+ inline int random() {
+ static const int gRandAmplitude = 16807; // 7**5; primitive root of m
+ static const int gRandQ = 127773; // m / a
+ static const int gRandR = 2836; // m % a
+
+ int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ);
+ if (result <= 0)
+ result += kRandMaximum;
+ fSeed = result;
+ return result;
+ }
+
+ void init(SkScalar seed)
+ {
+ static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize));
+
+ // The seed value clamp to the range [1, kRandMaximum - 1].
+ fSeed = SkScalarRoundToInt(seed);
+ if (fSeed <= 0) {
+ fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
+ }
+ if (fSeed > kRandMaximum - 1) {
+ fSeed = kRandMaximum - 1;
+ }
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int i = 0; i < kBlockSize; ++i) {
+ fLatticeSelector[i] = i;
+ fNoise[channel][i][0] = (random() % (2 * kBlockSize));
+ fNoise[channel][i][1] = (random() % (2 * kBlockSize));
+ }
+ }
+ for (int i = kBlockSize - 1; i > 0; --i) {
+ int k = fLatticeSelector[i];
+ int j = random() % kBlockSize;
+ SkASSERT(j >= 0);
+ SkASSERT(j < kBlockSize);
+ fLatticeSelector[i] = fLatticeSelector[j];
+ fLatticeSelector[j] = k;
+ }
+
+ // Perform the permutations now
+ {
+ // Copy noise data
+ uint16_t noise[4][kBlockSize][2];
+ for (int i = 0; i < kBlockSize; ++i) {
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int j = 0; j < 2; ++j) {
+ noise[channel][i][j] = fNoise[channel][i][j];
+ }
+ }
+ }
+ // Do permutations on noise data
+ for (int i = 0; i < kBlockSize; ++i) {
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int j = 0; j < 2; ++j) {
+ fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
+ }
+ }
+ }
+ }
+
+ // Half of the largest possible value for 16 bit unsigned int
+ static const SkScalar gHalfMax16bits = SkFloatToScalar(32767.5f);
+
+ // Compute gradients from permutated noise data
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int i = 0; i < kBlockSize; ++i) {
+ fGradient[channel][i] = SkPoint::Make(
+ SkScalarMul(SkIntToScalar(fNoise[channel][i][0] - kBlockSize),
+ gInvBlockSizef),
+ SkScalarMul(SkIntToScalar(fNoise[channel][i][1] - kBlockSize),
+ gInvBlockSizef));
+ fGradient[channel][i].normalize();
+ // Put the normalized gradient back into the noise data
+ fNoise[channel][i][0] = SkScalarRoundToInt(SkScalarMul(
+ fGradient[channel][i].fX + SK_Scalar1, gHalfMax16bits));
+ fNoise[channel][i][1] = SkScalarRoundToInt(SkScalarMul(
+ fGradient[channel][i].fY + SK_Scalar1, gHalfMax16bits));
+ }
+ }
+
+ // Invalidate bitmaps
+ SkDELETE(fPermutationsBitmap);
+ fPermutationsBitmap = NULL;
+ SkDELETE(fNoiseBitmap);
+ fNoiseBitmap = NULL;
+ }
+
+ void stitch() {
+ SkScalar tileWidth = SkIntToScalar(fTileSize.width());
+ SkScalar tileHeight = SkIntToScalar(fTileSize.height());
+ SkASSERT(tileWidth > 0 && tileHeight > 0);
+ // When stitching tiled turbulence, the frequencies must be adjusted
+ // so that the tile borders will be continuous.
+ if (fBaseFrequency.fX) {
+ SkScalar lowFrequencx = SkScalarDiv(
+ SkScalarMulFloor(tileWidth, fBaseFrequency.fX), tileWidth);
+ SkScalar highFrequencx = SkScalarDiv(
+ SkScalarMulCeil(tileWidth, fBaseFrequency.fX), tileWidth);
+ // BaseFrequency should be non-negative according to the standard.
+ if (SkScalarDiv(fBaseFrequency.fX, lowFrequencx) <
+ SkScalarDiv(highFrequencx, fBaseFrequency.fX)) {
+ fBaseFrequency.fX = lowFrequencx;
+ } else {
+ fBaseFrequency.fX = highFrequencx;
+ }
+ }
+ if (fBaseFrequency.fY) {
+ SkScalar lowFrequency = SkScalarDiv(
+ SkScalarMulFloor(tileHeight, fBaseFrequency.fY), tileHeight);
+ SkScalar highFrequency = SkScalarDiv(
+ SkScalarMulCeil(tileHeight, fBaseFrequency.fY), tileHeight);
+ if (SkScalarDiv(fBaseFrequency.fY, lowFrequency) <
+ SkScalarDiv(highFrequency, fBaseFrequency.fY)) {
+ fBaseFrequency.fY = lowFrequency;
+ } else {
+ fBaseFrequency.fY = highFrequency;
+ }
+ }
+ // Set up TurbulenceInitial stitch values.
+ fStitchDataInit.fWidth =
+ SkScalarMulRound(tileWidth, fBaseFrequency.fX);
+ fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth;
+ fStitchDataInit.fHeight =
+ SkScalarMulRound(tileHeight, fBaseFrequency.fY);
+ fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight;
+ }
+
+ SkBitmap* getPermutationsBitmap()
+ {
+ if (!fPermutationsBitmap) {
+ fPermutationsBitmap = SkNEW(SkBitmap);
+ fPermutationsBitmap->setConfig(SkBitmap::kA8_Config, kBlockSize, 1);
+ fPermutationsBitmap->allocPixels();
+ uint8_t* bitmapPixels = fPermutationsBitmap->getAddr8(0, 0);
+ memcpy(bitmapPixels, fLatticeSelector, sizeof(uint8_t) * kBlockSize);
+ }
+ return fPermutationsBitmap;
+ }
+
+ SkBitmap* getNoiseBitmap()
+ {
+ if (!fNoiseBitmap) {
+ fNoiseBitmap = SkNEW(SkBitmap);
+ fNoiseBitmap->setConfig(SkBitmap::kARGB_8888_Config, kBlockSize, 4);
+ fNoiseBitmap->allocPixels();
+ uint32_t* bitmapPixels = fNoiseBitmap->getAddr32(0, 0);
+ memcpy(bitmapPixels, fNoise[0][0], sizeof(uint16_t) * kBlockSize * 4 * 2);
+ }
+ return fNoiseBitmap;
+ }
+};
+
+SkShader* SkPerlinNoiseShader::CreateFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return SkNEW_ARGS(SkPerlinNoiseShader, (kFractalNoise_Type, baseFrequencyX, baseFrequencyY,
+ numOctaves, seed, tileSize));
+}
+
+SkShader* SkPerlinNoiseShader::CreateTubulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return SkNEW_ARGS(SkPerlinNoiseShader, (kTurbulence_Type, baseFrequencyX, baseFrequencyY,
+ numOctaves, seed, tileSize));
+}
+
+SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
+ SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves,
+ SkScalar seed,
+ const SkISize* tileSize)
+ : fType(type)
+ , fBaseFrequencyX(baseFrequencyX)
+ , fBaseFrequencyY(baseFrequencyY)
+ , fNumOctaves(numOctaves & 0xFF /*[0,255] octaves allowed*/)
+ , fSeed(seed)
+ , fStitchTiles((tileSize != NULL) && !tileSize->isEmpty())
+ , fPaintingData(NULL)
+{
+ SkASSERT(numOctaves >= 0 && numOctaves < 256);
+ setTileSize(fStitchTiles ? *tileSize : SkISize::Make(0,0));
+ fMatrix.reset();
+}
+
+SkPerlinNoiseShader::SkPerlinNoiseShader(SkFlattenableReadBuffer& buffer) :
+ INHERITED(buffer), fPaintingData(NULL) {
+ fType = (SkPerlinNoiseShader::Type) buffer.readInt();
+ fBaseFrequencyX = buffer.readScalar();
+ fBaseFrequencyY = buffer.readScalar();
+ fNumOctaves = buffer.readInt();
+ fSeed = buffer.readScalar();
+ fStitchTiles = buffer.readBool();
+ fTileSize.fWidth = buffer.readInt();
+ fTileSize.fHeight = buffer.readInt();
+ setTileSize(fTileSize);
+ fMatrix.reset();
+}
+
+SkPerlinNoiseShader::~SkPerlinNoiseShader() {
+ // Safety, should have been done in endContext()
+ SkDELETE(fPaintingData);
+}
+
+void SkPerlinNoiseShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt((int) fType);
+ buffer.writeScalar(fBaseFrequencyX);
+ buffer.writeScalar(fBaseFrequencyY);
+ buffer.writeInt(fNumOctaves);
+ buffer.writeScalar(fSeed);
+ buffer.writeBool(fStitchTiles);
+ buffer.writeInt(fTileSize.fWidth);
+ buffer.writeInt(fTileSize.fHeight);
+}
+
+void SkPerlinNoiseShader::initPaint(PaintingData& paintingData)
+{
+ paintingData.init(fSeed);
+
+ // Set frequencies to original values
+ paintingData.fBaseFrequency.set(fBaseFrequencyX, fBaseFrequencyY);
+ // Adjust frequecies based on size if stitching is enabled
+ if (fStitchTiles) {
+ paintingData.stitch();
+ }
+}
+
+void SkPerlinNoiseShader::setTileSize(const SkISize& tileSize) {
+ fTileSize = tileSize;
+
+ if (NULL == fPaintingData) {
+ fPaintingData = SkNEW_ARGS(PaintingData, (fTileSize));
+ initPaint(*fPaintingData);
+ } else {
+ // Set Size
+ fPaintingData->fTileSize = fTileSize;
+ // Set frequencies to original values
+ fPaintingData->fBaseFrequency.set(fBaseFrequencyX, fBaseFrequencyY);
+ // Adjust frequecies based on size if stitching is enabled
+ if (fStitchTiles) {
+ fPaintingData->stitch();
+ }
+ }
+}
+
+SkScalar SkPerlinNoiseShader::noise2D(int channel, const PaintingData& paintingData,
+ const StitchData& stitchData, const SkPoint& noiseVector)
+{
+ struct Noise {
+ int noisePositionIntegerValue;
+ SkScalar noisePositionFractionValue;
+ Noise(SkScalar component)
+ {
+ SkScalar position = component + kPerlinNoise;
+ noisePositionIntegerValue = SkScalarFloorToInt(position);
+ noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
+ }
+ };
+ Noise noiseX(noiseVector.x());
+ Noise noiseY(noiseVector.y());
+ SkScalar u, v;
+ // If stitching, adjust lattice points accordingly.
+ if (fStitchTiles) {
+ noiseX.noisePositionIntegerValue =
+ checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
+ noiseY.noisePositionIntegerValue =
+ checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
+ }
+ noiseX.noisePositionIntegerValue &= kBlockMask;
+ noiseY.noisePositionIntegerValue &= kBlockMask;
+ int latticeIndex =
+ paintingData.fLatticeSelector[noiseX.noisePositionIntegerValue] +
+ noiseY.noisePositionIntegerValue;
+ int nextLatticeIndex =
+ paintingData.fLatticeSelector[(noiseX.noisePositionIntegerValue + 1) & kBlockMask] +
+ noiseY.noisePositionIntegerValue;
+ SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
+ SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
+ // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
+ SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
+ noiseY.noisePositionFractionValue); // Offset (0,0)
+ u = paintingData.fGradient[channel][latticeIndex & kBlockMask].dot(fractionValue);
+ fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
+ v = paintingData.fGradient[channel][nextLatticeIndex & kBlockMask].dot(fractionValue);
+ SkScalar a = SkScalarInterp(u, v, sx);
+ fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
+ v = paintingData.fGradient[channel][(nextLatticeIndex + 1) & kBlockMask].dot(fractionValue);
+ fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
+ u = paintingData.fGradient[channel][(latticeIndex + 1) & kBlockMask].dot(fractionValue);
+ SkScalar b = SkScalarInterp(u, v, sx);
+ return SkScalarInterp(a, b, sy);
+}
+
+SkScalar SkPerlinNoiseShader::calculateTurbulenceValueForPoint(
+ int channel, const PaintingData& paintingData, StitchData& stitchData, const SkPoint& point)
+{
+ if (fStitchTiles) {
+ // Set up TurbulenceInitial stitch values.
+ stitchData = paintingData.fStitchDataInit;
+ }
+ SkScalar turbulenceFunctionResult = 0;
+ SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), paintingData.fBaseFrequency.fX),
+ SkScalarMul(point.y(), paintingData.fBaseFrequency.fY)));
+ SkScalar ratio = SK_Scalar1;
+ for (int octave = 0; octave < fNumOctaves; ++octave) {
+ SkScalar noise = noise2D(channel, paintingData, stitchData, noiseVector);
+ turbulenceFunctionResult += SkScalarDiv(
+ (fType == kFractalNoise_Type) ? noise : SkScalarAbs(noise), ratio);
+ noiseVector.fX *= 2;
+ noiseVector.fY *= 2;
+ ratio *= 2;
+ if (fStitchTiles) {
+ // Update stitch values
+ stitchData.fWidth *= 2;
+ stitchData.fWrapX = stitchData.fWidth + kPerlinNoise;
+ stitchData.fHeight *= 2;
+ stitchData.fWrapY = stitchData.fHeight + kPerlinNoise;
+ }
+ }
+
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ if (fType == kFractalNoise_Type) {
+ turbulenceFunctionResult =
+ SkScalarMul(turbulenceFunctionResult, SK_ScalarHalf) + SK_ScalarHalf;
+ }
+
+ if (channel == 3) { // Scale alpha by paint value
+ turbulenceFunctionResult = SkScalarMul(turbulenceFunctionResult,
+ SkScalarDiv(SkIntToScalar(getPaintAlpha()), SkIntToScalar(255)));
+ }
+
+ // Clamp result
+ return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1);
+}
+
+SkPMColor SkPerlinNoiseShader::shade(const SkPoint& point, StitchData& stitchData) {
+ SkMatrix matrix = fMatrix;
+ SkMatrix invMatrix;
+ if (!matrix.invert(&invMatrix)) {
+ invMatrix.reset();
+ } else {
+ invMatrix.postConcat(invMatrix); // Square the matrix
+ }
+ // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+ // (as opposed to 0 based, usually). The same adjustment is in the setData() function.
+ matrix.postTranslate(SK_Scalar1, SK_Scalar1);
+ SkPoint newPoint;
+ matrix.mapPoints(&newPoint, &point, 1);
+ invMatrix.mapPoints(&newPoint, &newPoint, 1);
+ newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
+ newPoint.fY = SkScalarRoundToScalar(newPoint.fY);
+
+ U8CPU rgba[4];
+ for (int channel = 3; channel >= 0; --channel) {
+ rgba[channel] = SkScalarFloorToInt(255 *
+ calculateTurbulenceValueForPoint(channel, *fPaintingData, stitchData, newPoint));
+ }
+ return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
+}
+
+bool SkPerlinNoiseShader::setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) {
+ fMatrix = matrix;
+ return INHERITED::setContext(device, paint, matrix);
+}
+
+void SkPerlinNoiseShader::shadeSpan(int x, int y, SkPMColor result[], int count) {
+ SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ StitchData stitchData;
+ for (int i = 0; i < count; ++i) {
+ result[i] = shade(point, stitchData);
+ point.fX += SK_Scalar1;
+ }
+}
+
+void SkPerlinNoiseShader::shadeSpan16(int x, int y, uint16_t result[], int count) {
+ SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ StitchData stitchData;
+ DITHER_565_SCAN(y);
+ for (int i = 0; i < count; ++i) {
+ unsigned dither = DITHER_VALUE(x);
+ result[i] = SkDitherRGB32To565(shade(point, stitchData), dither);
+ DITHER_INC_X(x);
+ point.fX += SK_Scalar1;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLNoise : public GrGLEffect {
+public:
+ GrGLNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual ~GrGLNoise() {}
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ SkPerlinNoiseShader::Type fType;
+ bool fStitchTiles;
+ int fNumOctaves;
+ GrGLUniformManager::UniformHandle fBaseFrequencyUni;
+ GrGLUniformManager::UniformHandle fAlphaUni;
+ GrGLUniformManager::UniformHandle fInvMatrixUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+private:
+ typedef GrGLEffect INHERITED;
+};
+
+class GrGLPerlinNoise : public GrGLNoise {
+public:
+ GrGLPerlinNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLNoise(factory, drawEffect) {}
+ virtual ~GrGLPerlinNoise() {}
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ GrGLUniformManager::UniformHandle fStitchDataUni;
+
+ typedef GrGLNoise INHERITED;
+};
+
+class GrGLSimplexNoise : public GrGLNoise {
+ // Note : This is for reference only. GrGLPerlinNoise is used for processing.
+public:
+ GrGLSimplexNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLNoise(factory, drawEffect) {}
+
+ virtual ~GrGLSimplexNoise() {}
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ GrGLUniformManager::UniformHandle fSeedUni;
+
+ typedef GrGLNoise INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrNoiseEffect : public GrEffect {
+public:
+ virtual ~GrNoiseEffect() { }
+
+ SkPerlinNoiseShader::Type type() const { return fType; }
+ bool stitchTiles() const { return fStitchTiles; }
+ const SkVector& baseFrequency() const { return fBaseFrequency; }
+ int numOctaves() const { return fNumOctaves; }
+ const SkMatrix& matrix() const { return fMatrix; }
+ uint8_t alpha() const { return fAlpha; }
+ GrGLEffectMatrix::CoordsType coordsType() const { return GrEffect::kLocal_CoordsType; }
+
+ void getConstantColorComponents(GrColor*, uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0; // This is noise. Nothing is constant.
+ }
+
+protected:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrNoiseEffect& s = CastEffect<GrNoiseEffect>(sBase);
+ return fType == s.fType &&
+ fBaseFrequency == s.fBaseFrequency &&
+ fNumOctaves == s.fNumOctaves &&
+ fStitchTiles == s.fStitchTiles &&
+ fMatrix == s.fMatrix &&
+ fAlpha == s.fAlpha;
+ }
+
+ GrNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency, int numOctaves,
+ bool stitchTiles, const SkMatrix& matrix, uint8_t alpha)
+ : fType(type)
+ , fBaseFrequency(baseFrequency)
+ , fNumOctaves(numOctaves)
+ , fStitchTiles(stitchTiles)
+ , fMatrix(matrix)
+ , fAlpha(alpha) {
+ }
+
+ SkPerlinNoiseShader::Type fType;
+ SkVector fBaseFrequency;
+ int fNumOctaves;
+ bool fStitchTiles;
+ SkMatrix fMatrix;
+ uint8_t fAlpha;
+
+private:
+ typedef GrEffect INHERITED;
+};
+
+class GrPerlinNoiseEffect : public GrNoiseEffect {
+public:
+ static GrEffectRef* Create(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles,
+ const SkPerlinNoiseShader::StitchData& stitchData,
+ GrTexture* permutationsTexture, GrTexture* noiseTexture,
+ const SkMatrix& matrix, uint8_t alpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrPerlinNoiseEffect, (type, baseFrequency, numOctaves,
+ stitchTiles, stitchData, permutationsTexture, noiseTexture, matrix, alpha)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrPerlinNoiseEffect() { }
+
+ static const char* Name() { return "PerlinNoise"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrPerlinNoiseEffect>::getInstance();
+ }
+ const SkPerlinNoiseShader::StitchData& stitchData() const { return fStitchData; }
+
+ typedef GrGLPerlinNoise GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrPerlinNoiseEffect& s = CastEffect<GrPerlinNoiseEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ fPermutationsAccess.getTexture() == s.fPermutationsAccess.getTexture() &&
+ fNoiseAccess.getTexture() == s.fNoiseAccess.getTexture() &&
+ fStitchData == s.fStitchData;
+ }
+
+ GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles,
+ const SkPerlinNoiseShader::StitchData& stitchData,
+ GrTexture* permutationsTexture, GrTexture* noiseTexture,
+ const SkMatrix& matrix, uint8_t alpha)
+ : GrNoiseEffect(type, baseFrequency, numOctaves, stitchTiles, matrix, alpha)
+ , fPermutationsAccess(permutationsTexture)
+ , fNoiseAccess(noiseTexture)
+ , fStitchData(stitchData) {
+ this->addTextureAccess(&fPermutationsAccess);
+ this->addTextureAccess(&fNoiseAccess);
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fPermutationsAccess;
+ GrTextureAccess fNoiseAccess;
+ SkPerlinNoiseShader::StitchData fStitchData;
+
+ typedef GrNoiseEffect INHERITED;
+};
+
+class GrSimplexNoiseEffect : public GrNoiseEffect {
+ // Note : This is for reference only. GrPerlinNoiseEffect is used for processing.
+public:
+ static GrEffectRef* Create(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles, const SkScalar seed,
+ const SkMatrix& matrix, uint8_t alpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSimplexNoiseEffect, (type, baseFrequency, numOctaves,
+ stitchTiles, seed, matrix, alpha)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrSimplexNoiseEffect() { }
+
+ static const char* Name() { return "SimplexNoise"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrSimplexNoiseEffect>::getInstance();
+ }
+ const SkScalar& seed() const { return fSeed; }
+
+ typedef GrGLSimplexNoise GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrSimplexNoiseEffect& s = CastEffect<GrSimplexNoiseEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) && fSeed == s.fSeed;
+ }
+
+ GrSimplexNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles, const SkScalar seed,
+ const SkMatrix& matrix, uint8_t alpha)
+ : GrNoiseEffect(type, baseFrequency, numOctaves, stitchTiles, matrix, alpha)
+ , fSeed(seed) {
+ }
+
+ SkScalar fSeed;
+
+ typedef GrNoiseEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+GR_DEFINE_EFFECT_TEST(GrPerlinNoiseEffect);
+
+GrEffectRef* GrPerlinNoiseEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ int numOctaves = random->nextRangeU(2, 10);
+ bool stitchTiles = random->nextBool();
+ SkScalar seed = SkIntToScalar(random->nextU());
+ SkISize tileSize = SkISize::Make(random->nextRangeU(4, 4096), random->nextRangeU(4, 4096));
+ SkScalar baseFrequencyX = random->nextRangeScalar(SkFloatToScalar(0.01f),
+ SkFloatToScalar(0.99f));
+ SkScalar baseFrequencyY = random->nextRangeScalar(SkFloatToScalar(0.01f),
+ SkFloatToScalar(0.99f));
+
+ SkShader* shader = random->nextBool() ?
+ SkPerlinNoiseShader::CreateFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ stitchTiles ? &tileSize : NULL) :
+ SkPerlinNoiseShader::CreateTubulence(baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ stitchTiles ? &tileSize : NULL);
+
+ SkPaint paint;
+ GrEffectRef* effect = shader->asNewEffect(context, paint);
+
+ SkDELETE(shader);
+
+ return effect;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLSimplexNoise::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) {
+ sk_ignore_unused_variable(inputColor);
+
+ const char* vCoords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &vCoords);
+
+ fSeedUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "seed");
+ const char* seedUni = builder->getUniformCStr(fSeedUni);
+ fInvMatrixUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat33f_GrSLType, "invMatrix");
+ const char* invMatrixUni = builder->getUniformCStr(fInvMatrixUni);
+ fBaseFrequencyUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "baseFrequency");
+ const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni);
+ fAlphaUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "alpha");
+ const char* alphaUni = builder->getUniformCStr(fAlphaUni);
+
+ // Add vec3 modulo 289 function
+ static const GrGLShaderVar gVec3Args[] = {
+ GrGLShaderVar("x", kVec3f_GrSLType)
+ };
+
+ SkString mod289_3_funcName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "mod289", SK_ARRAY_COUNT(gVec3Args), gVec3Args,
+ "const vec2 C = vec2(1.0 / 289.0, 289.0);\n"
+ "return x - floor(x * C.xxx) * C.yyy;", &mod289_3_funcName);
+
+ // Add vec4 modulo 289 function
+ static const GrGLShaderVar gVec4Args[] = {
+ GrGLShaderVar("x", kVec4f_GrSLType)
+ };
+
+ SkString mod289_4_funcName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "mod289", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ "const vec2 C = vec2(1.0 / 289.0, 289.0);\n"
+ "return x - floor(x * C.xxxx) * C.yyyy;", &mod289_4_funcName);
+
+ // Add vec4 permute function
+ SkString permuteCode;
+ permuteCode.appendf("const vec2 C = vec2(34.0, 1.0);\n"
+ "return %s(((x * C.xxxx) + C.yyyy) * x);", mod289_4_funcName.c_str());
+ SkString permuteFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "permute", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ permuteCode.c_str(), &permuteFuncName);
+
+ // Add vec4 taylorInvSqrt function
+ SkString taylorInvSqrtFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "taylorInvSqrt", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ "const vec2 C = vec2(-0.85373472095314, 1.79284291400159);\n"
+ "return x * C.xxxx + C.yyyy;", &taylorInvSqrtFuncName);
+
+ // Add vec3 noise function
+ static const GrGLShaderVar gNoiseVec3Args[] = {
+ GrGLShaderVar("v", kVec3f_GrSLType)
+ };
+
+ SkString noiseCode;
+ noiseCode.append(
+ "const vec2 C = vec2(1.0/6.0, 1.0/3.0);\n"
+ "const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n"
+
+ // First corner
+ "vec3 i = floor(v + dot(v, C.yyy));\n"
+ "vec3 x0 = v - i + dot(i, C.xxx);\n"
+
+ // Other corners
+ "vec3 g = step(x0.yzx, x0.xyz);\n"
+ "vec3 l = 1.0 - g;\n"
+ "vec3 i1 = min(g.xyz, l.zxy);\n"
+ "vec3 i2 = max(g.xyz, l.zxy);\n"
+
+ "vec3 x1 = x0 - i1 + C.xxx;\n"
+ "vec3 x2 = x0 - i2 + C.yyy;\n" // 2.0*C.x = 1/3 = C.y
+ "vec3 x3 = x0 - D.yyy;\n" // -1.0+3.0*C.x = -0.5 = -D.y
+ );
+
+ noiseCode.appendf(
+ // Permutations
+ "i = %s(i);\n"
+ "vec4 p = %s(%s(%s(\n"
+ " i.z + vec4(0.0, i1.z, i2.z, 1.0)) +\n"
+ " i.y + vec4(0.0, i1.y, i2.y, 1.0)) +\n"
+ " i.x + vec4(0.0, i1.x, i2.x, 1.0));\n",
+ mod289_3_funcName.c_str(), permuteFuncName.c_str(), permuteFuncName.c_str(),
+ permuteFuncName.c_str());
+
+ noiseCode.append(
+ // Gradients: 7x7 points over a square, mapped onto an octahedron.
+ // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+ "float n_ = 0.142857142857;\n" // 1.0/7.0
+ "vec3 ns = n_ * D.wyz - D.xzx;\n"
+
+ "vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n" // mod(p,7*7)
+
+ "vec4 x_ = floor(j * ns.z);\n"
+ "vec4 y_ = floor(j - 7.0 * x_);" // mod(j,N)
+
+ "vec4 x = x_ *ns.x + ns.yyyy;\n"
+ "vec4 y = y_ *ns.x + ns.yyyy;\n"
+ "vec4 h = 1.0 - abs(x) - abs(y);\n"
+
+ "vec4 b0 = vec4(x.xy, y.xy);\n"
+ "vec4 b1 = vec4(x.zw, y.zw);\n"
+ );
+
+ noiseCode.append(
+ "vec4 s0 = floor(b0) * 2.0 + 1.0;\n"
+ "vec4 s1 = floor(b1) * 2.0 + 1.0;\n"
+ "vec4 sh = -step(h, vec4(0.0));\n"
+
+ "vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n"
+ "vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n"
+
+ "vec3 p0 = vec3(a0.xy, h.x);\n"
+ "vec3 p1 = vec3(a0.zw, h.y);\n"
+ "vec3 p2 = vec3(a1.xy, h.z);\n"
+ "vec3 p3 = vec3(a1.zw, h.w);\n"
+ );
+
+ noiseCode.appendf(
+ // Normalise gradients
+ "vec4 norm = %s(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\n"
+ "p0 *= norm.x;\n"
+ "p1 *= norm.y;\n"
+ "p2 *= norm.z;\n"
+ "p3 *= norm.w;\n"
+
+ // Mix final noise value
+ "vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n"
+ "m = m * m;\n"
+ "return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));",
+ taylorInvSqrtFuncName.c_str());
+
+ SkString noiseFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "snoise", SK_ARRAY_COUNT(gNoiseVec3Args), gNoiseVec3Args,
+ noiseCode.c_str(), &noiseFuncName);
+
+ const char* noiseVecIni = "noiseVecIni";
+ const char* factors = "factors";
+ const char* sum = "sum";
+ const char* xOffsets = "xOffsets";
+ const char* yOffsets = "yOffsets";
+ const char* channel = "channel";
+
+ // Fill with some prime numbers
+ builder->fsCodeAppendf("\t\tconst vec4 %s = vec4(13.0, 53.0, 101.0, 151.0);\n", xOffsets);
+ builder->fsCodeAppendf("\t\tconst vec4 %s = vec4(109.0, 167.0, 23.0, 67.0);\n", yOffsets);
+
+ // There are rounding errors if the floor operation is not performed here
+ builder->fsCodeAppendf(
+ "\t\tvec3 %s = vec3(floor((%s*vec3(%s, 1.0)).xy) * vec2(0.66) * %s, 0.0);\n",
+ noiseVecIni, invMatrixUni, vCoords, baseFrequencyUni);
+
+ // Perturb the texcoords with three components of noise
+ builder->fsCodeAppendf("\t\t%s += 0.1 * vec3(%s(%s + vec3( 0.0, 0.0, %s)),"
+ "%s(%s + vec3( 43.0, 17.0, %s)),"
+ "%s(%s + vec3(-17.0, -43.0, %s)));\n",
+ noiseVecIni, noiseFuncName.c_str(), noiseVecIni, seedUni,
+ noiseFuncName.c_str(), noiseVecIni, seedUni,
+ noiseFuncName.c_str(), noiseVecIni, seedUni);
+
+ builder->fsCodeAppendf("\t\t%s = vec4(0.0);\n", outputColor);
+
+ builder->fsCodeAppendf("\t\tvec3 %s = vec3(1.0);\n", factors);
+ builder->fsCodeAppendf("\t\tfloat %s = 0.0;\n", sum);
+
+ // Loop over all octaves
+ builder->fsCodeAppendf("\t\tfor (int octave = 0; octave < %d; ++octave) {\n", fNumOctaves);
+
+ // Loop over the 4 channels
+ builder->fsCodeAppendf("\t\t\tfor (int %s = 3; %s >= 0; --%s) {\n", channel, channel, channel);
+
+ builder->fsCodeAppendf(
+ "\t\t\t\t%s[channel] += %s.x * %s(%s * %s.yyy - vec3(%s[%s], %s[%s], %s * %s.z));\n",
+ outputColor, factors, noiseFuncName.c_str(), noiseVecIni, factors, xOffsets, channel,
+ yOffsets, channel, seedUni, factors);
+
+ builder->fsCodeAppend("\t\t\t}\n"); // end of the for loop on channels
+
+ builder->fsCodeAppendf("\t\t\t%s += %s.x;\n", sum, factors);
+ builder->fsCodeAppendf("\t\t\t%s *= vec3(0.5, 2.0, 0.75);\n", factors);
+
+ builder->fsCodeAppend("\t\t}\n"); // end of the for loop on octaves
+
+ if (fType == SkPerlinNoiseShader::kFractalNoise_Type) {
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ builder->fsCodeAppendf("\t\t%s = %s * vec4(0.5 / %s) + vec4(0.5);\n",
+ outputColor, outputColor, sum);
+ } else {
+ builder->fsCodeAppendf("\t\t%s = abs(%s / vec4(%s));\n",
+ outputColor, outputColor, sum);
+ }
+
+ builder->fsCodeAppendf("\t\t%s.a *= %s;\n", outputColor, alphaUni);
+
+ // Clamp values
+ builder->fsCodeAppendf("\t\t%s = clamp(%s, 0.0, 1.0);\n", outputColor, outputColor);
+
+ // Pre-multiply the result
+ builder->fsCodeAppendf("\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n",
+ outputColor, outputColor, outputColor, outputColor);
+}
+
+void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ sk_ignore_unused_variable(inputColor);
+
+ const char* vCoords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &vCoords);
+
+ fInvMatrixUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat33f_GrSLType, "invMatrix");
+ const char* invMatrixUni = builder->getUniformCStr(fInvMatrixUni);
+ fBaseFrequencyUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "baseFrequency");
+ const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni);
+ fAlphaUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "alpha");
+ const char* alphaUni = builder->getUniformCStr(fAlphaUni);
+
+ const char* stitchDataUni = NULL;
+ if (fStitchTiles) {
+ fStitchDataUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "stitchData");
+ stitchDataUni = builder->getUniformCStr(fStitchDataUni);
+ }
+
+ // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8
+ const char* chanCoordR = "0.125";
+ const char* chanCoordG = "0.375";
+ const char* chanCoordB = "0.625";
+ const char* chanCoordA = "0.875";
+ const char* chanCoord = "chanCoord";
+ const char* stitchData = "stitchData";
+ const char* ratio = "ratio";
+ const char* noiseXY = "noiseXY";
+ const char* noiseVec = "noiseVec";
+ const char* noiseSmooth = "noiseSmooth";
+ const char* fractVal = "fractVal";
+ const char* uv = "uv";
+ const char* ab = "ab";
+ const char* latticeIdx = "latticeIdx";
+ const char* lattice = "lattice";
+ const char* inc8bit = "0.00390625"; // 1.0 / 256.0
+ // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
+ // [-1,1] vector and perform a dot product between that vector and the provided vector.
+ const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);";
+
+ // Add noise function
+ static const GrGLShaderVar gPerlinNoiseArgs[] = {
+ GrGLShaderVar(chanCoord, kFloat_GrSLType),
+ GrGLShaderVar(noiseVec, kVec2f_GrSLType)
+ };
+
+ static const GrGLShaderVar gPerlinNoiseStitchArgs[] = {
+ GrGLShaderVar(chanCoord, kFloat_GrSLType),
+ GrGLShaderVar(noiseVec, kVec2f_GrSLType),
+ GrGLShaderVar(stitchData, kVec2f_GrSLType)
+ };
+
+ SkString noiseCode;
+
+ noiseCode.appendf("\tvec4 %s = vec4(floor(%s), fract(%s));", noiseXY, noiseVec, noiseVec);
+
+ // smooth curve : t * t * (3 - 2 * t)
+ noiseCode.appendf("\n\tvec2 %s = %s.zw * %s.zw * (vec2(3.0) - vec2(2.0) * %s.zw);",
+ noiseSmooth, noiseXY, noiseXY, noiseXY);
+
+ // Adjust frequencies if we're stitching tiles
+ if (fStitchTiles) {
+ noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.x >= (%s.x - 1.0)) { %s.x -= (%s.x - 1.0); }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.y >= (%s.y - 1.0)) { %s.y -= (%s.y - 1.0); }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ }
+
+ // Get texture coordinates and normalize
+ noiseCode.appendf("\n\t%s.xy = fract(floor(mod(%s.xy, 256.0)) / vec2(256.0));\n",
+ noiseXY, noiseXY);
+
+ // Get permutation for x
+ {
+ SkString xCoords("");
+ xCoords.appendf("vec2(%s.x, 0.5)", noiseXY);
+
+ noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx);
+ builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
+ noiseCode.append(".r;");
+ }
+
+ // Get permutation for x + 1
+ {
+ SkString xCoords("");
+ xCoords.appendf("vec2(fract(%s.x + %s), 0.5)", noiseXY, inc8bit);
+
+ noiseCode.appendf("\n\t%s.y = ", latticeIdx);
+ builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
+ noiseCode.append(".r;");
+ }
+
+#if defined(SK_BUILD_FOR_ANDROID)
+ // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
+ // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit
+ // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725
+ // (or 0.484368 here). The following rounding operation prevents these precision issues from
+ // affecting the result of the noise by making sure that we only have multiples of 1/255.
+ // (Note that 1/255 is about 0.003921569, which is the value used here).
+ noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);",
+ latticeIdx, latticeIdx);
+#endif
+
+ // Get (x,y) coordinates with the permutated x
+ noiseCode.appendf("\n\t%s = fract(%s + %s.yy);", latticeIdx, latticeIdx, noiseXY);
+
+ noiseCode.appendf("\n\tvec2 %s = %s.zw;", fractVal, noiseXY);
+
+ noiseCode.appendf("\n\n\tvec2 %s;", uv);
+ // Compute u, at offset (0,0)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(%s.x, %s)", latticeIdx, chanCoord);
+ noiseCode.appendf("\n\tvec4 %s = ", lattice);
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal);
+ // Compute v, at offset (-1,0)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(%s.y, %s)", latticeIdx, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ // Compute 'a' as a linear interpolation of 'u' and 'v'
+ noiseCode.appendf("\n\tvec2 %s;", ab);
+ noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
+
+ noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal);
+ // Compute v, at offset (-1,-1)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(fract(%s.y + %s), %s)", latticeIdx, inc8bit, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ noiseCode.appendf("\n\t%s.x += 1.0;", fractVal);
+ // Compute u, at offset (0,-1)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(fract(%s.x + %s), %s)", latticeIdx, inc8bit, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ // Compute 'b' as a linear interpolation of 'u' and 'v'
+ noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
+ // Compute the noise as a linear interpolation of 'a' and 'b'
+ noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth);
+
+ SkString noiseFuncName;
+ if (fStitchTiles) {
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs),
+ gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName);
+ } else {
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs),
+ gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName);
+ }
+
+ // There are rounding errors if the floor operation is not performed here
+ builder->fsCodeAppendf("\n\t\tvec2 %s = floor((%s * vec3(%s, 1.0)).xy) * %s;",
+ noiseVec, invMatrixUni, vCoords, baseFrequencyUni);
+
+ // Clear the color accumulator
+ builder->fsCodeAppendf("\n\t\t%s = vec4(0.0);", outputColor);
+
+ if (fStitchTiles) {
+ // Set up TurbulenceInitial stitch values.
+ builder->fsCodeAppendf("\n\t\tvec2 %s = %s;", stitchData, stitchDataUni);
+ }
+
+ builder->fsCodeAppendf("\n\t\tfloat %s = 1.0;", ratio);
+
+ // Loop over all octaves
+ builder->fsCodeAppendf("\n\t\tfor (int octave = 0; octave < %d; ++octave) {", fNumOctaves);
+
+ builder->fsCodeAppendf("\n\t\t\t%s += ", outputColor);
+ if (fType != SkPerlinNoiseShader::kFractalNoise_Type) {
+ builder->fsCodeAppend("abs(");
+ }
+ if (fStitchTiles) {
+ builder->fsCodeAppendf(
+ "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s),"
+ "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))",
+ noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData);
+ } else {
+ builder->fsCodeAppendf(
+ "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s),"
+ "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))",
+ noiseFuncName.c_str(), chanCoordR, noiseVec,
+ noiseFuncName.c_str(), chanCoordG, noiseVec,
+ noiseFuncName.c_str(), chanCoordB, noiseVec,
+ noiseFuncName.c_str(), chanCoordA, noiseVec);
+ }
+ if (fType != SkPerlinNoiseShader::kFractalNoise_Type) {
+ builder->fsCodeAppendf(")"); // end of "abs("
+ }
+ builder->fsCodeAppendf(" * %s;", ratio);
+
+ builder->fsCodeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec);
+ builder->fsCodeAppendf("\n\t\t\t%s *= 0.5;", ratio);
+
+ if (fStitchTiles) {
+ builder->fsCodeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData);
+ }
+ builder->fsCodeAppend("\n\t\t}"); // end of the for loop on octaves
+
+ if (fType == SkPerlinNoiseShader::kFractalNoise_Type) {
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ builder->fsCodeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);", outputColor, outputColor);
+ }
+
+ builder->fsCodeAppendf("\n\t\t%s.a *= %s;", outputColor, alphaUni);
+
+ // Clamp values
+ builder->fsCodeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", outputColor, outputColor);
+
+ // Pre-multiply the result
+ builder->fsCodeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n",
+ outputColor, outputColor, outputColor, outputColor);
+}
+
+GrGLNoise::GrGLNoise(const GrBackendEffectFactory& factory, const GrDrawEffect& drawEffect)
+ : INHERITED (factory)
+ , fType(drawEffect.castEffect<GrPerlinNoiseEffect>().type())
+ , fStitchTiles(drawEffect.castEffect<GrPerlinNoiseEffect>().stitchTiles())
+ , fNumOctaves(drawEffect.castEffect<GrPerlinNoiseEffect>().numOctaves())
+ , fEffectMatrix(drawEffect.castEffect<GrPerlinNoiseEffect>().coordsType()) {
+}
+
+GrGLEffect::EffectKey GrGLNoise::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+
+ EffectKey key = turbulence.numOctaves();
+
+ key = key << 3; // Make room for next 3 bits
+
+ switch (turbulence.type()) {
+ case SkPerlinNoiseShader::kFractalNoise_Type:
+ key |= 0x1;
+ break;
+ case SkPerlinNoiseShader::kTurbulence_Type:
+ key |= 0x2;
+ break;
+ default:
+ // leave key at 0
+ break;
+ }
+
+ if (turbulence.stitchTiles()) {
+ key |= 0x4; // Flip the 3rd bit if tile stitching is on
+ }
+
+ key = key << GrGLEffectMatrix::kKeyBits;
+
+ SkMatrix m = turbulence.matrix();
+ m.postTranslate(SK_Scalar1, SK_Scalar1);
+ return key | GrGLEffectMatrix::GenKey(m, drawEffect,
+ drawEffect.castEffect<GrPerlinNoiseEffect>().coordsType(), NULL);
+}
+
+void GrGLNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+
+ const SkVector& baseFrequency = turbulence.baseFrequency();
+ uman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
+ uman.set1f(fAlphaUni, SkScalarDiv(SkIntToScalar(turbulence.alpha()), SkIntToScalar(255)));
+
+ SkMatrix m = turbulence.matrix();
+ SkMatrix invM;
+ if (!m.invert(&invM)) {
+ invM.reset();
+ } else {
+ invM.postConcat(invM); // Square the matrix
+ }
+ uman.setSkMatrix(fInvMatrixUni, invM);
+
+ // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+ // (as opposed to 0 based, usually). The same adjustment is in the shadeSpan() functions.
+ m.postTranslate(SK_Scalar1, SK_Scalar1);
+ fEffectMatrix.setData(uman, m, drawEffect, NULL);
+}
+
+void GrGLPerlinNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+ if (turbulence.stitchTiles()) {
+ const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
+ uman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth),
+ SkIntToScalar(stitchData.fHeight));
+ }
+}
+
+void GrGLSimplexNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+
+ const GrSimplexNoiseEffect& turbulence = drawEffect.castEffect<GrSimplexNoiseEffect>();
+ uman.set1f(fSeedUni, turbulence.seed());
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkPerlinNoiseShader::asNewEffect(GrContext* context, const SkPaint& paint) const {
+ SkASSERT(NULL != context);
+
+ // Either we don't stitch tiles, either we have a valid tile size
+ SkASSERT(!fStitchTiles || !fTileSize.isEmpty());
+
+#ifdef SK_USE_SIMPLEX_NOISE
+ // Simplex noise is currently disabled but can be enabled by defining SK_USE_SIMPLEX_NOISE
+ sk_ignore_unused_variable(context);
+ GrEffectRef* effect =
+ GrSimplexNoiseEffect::Create(fType, fPaintingData->fBaseFrequency,
+ fNumOctaves, fStitchTiles, fSeed,
+ this->getLocalMatrix(), paint.getAlpha());
+#else
+ GrTexture* permutationsTexture = GrLockAndRefCachedBitmapTexture(
+ context, *fPaintingData->getPermutationsBitmap(), NULL);
+ GrTexture* noiseTexture = GrLockAndRefCachedBitmapTexture(
+ context, *fPaintingData->getNoiseBitmap(), NULL);
+
+ GrEffectRef* effect = (NULL != permutationsTexture) && (NULL != noiseTexture) ?
+ GrPerlinNoiseEffect::Create(fType, fPaintingData->fBaseFrequency,
+ fNumOctaves, fStitchTiles,
+ fPaintingData->fStitchDataInit,
+ permutationsTexture, noiseTexture,
+ this->getLocalMatrix(), paint.getAlpha()) :
+ NULL;
+
+ // 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.
+ if (NULL != permutationsTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(permutationsTexture);
+ }
+ if (NULL != noiseTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(noiseTexture);
+ }
+#endif
+
+ return effect;
+}
+
+#else
+
+GrEffectRef* SkPerlinNoiseShader::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkPerlinNoiseShader::toString(SkString* str) const {
+ str->append("SkPerlinNoiseShader: (");
+
+ str->append("type: ");
+ switch (fType) {
+ case kFractalNoise_Type:
+ str->append("\"fractal noise\"");
+ break;
+ case kTurbulence_Type:
+ str->append("\"turbulence\"");
+ break;
+ default:
+ str->append("\"unknown\"");
+ break;
+ }
+ str->append(" base frequency: (");
+ str->appendScalar(fBaseFrequencyX);
+ str->append(", ");
+ str->appendScalar(fBaseFrequencyY);
+ str->append(") number of octaves: ");
+ str->appendS32(fNumOctaves);
+ str->append(" seed: ");
+ str->appendScalar(fSeed);
+ str->append(" stitch tiles: ");
+ str->append(fStitchTiles ? "true " : "false ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkPixelXorXfermode.cpp b/effects/SkPixelXorXfermode.cpp
new file mode 100644
index 00000000..b9c96dc3
--- /dev/null
+++ b/effects/SkPixelXorXfermode.cpp
@@ -0,0 +1,38 @@
+
+/*
+ * 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 "SkPixelXorXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+// we always return an opaque color, 'cause I don't know what to do with
+// the alpha-component and still return a valid premultiplied color.
+SkPMColor SkPixelXorXfermode::xferColor(SkPMColor src, SkPMColor dst) const {
+ SkPMColor res = src ^ dst ^ fOpColor;
+ res |= (SK_A32_MASK << SK_A32_SHIFT); // force it to be opaque
+ return res;
+}
+
+void SkPixelXorXfermode::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeColor(fOpColor);
+}
+
+SkPixelXorXfermode::SkPixelXorXfermode(SkFlattenableReadBuffer& rb)
+ : INHERITED(rb) {
+ fOpColor = rb.readColor();
+}
+
+#ifdef SK_DEVELOPER
+void SkPixelXorXfermode::toString(SkString* str) const {
+ str->append("SkPixelXorXfermode: ");
+ str->appendHex(fOpColor);
+}
+#endif
diff --git a/effects/SkPorterDuff.cpp b/effects/SkPorterDuff.cpp
new file mode 100644
index 00000000..816ddae0
--- /dev/null
+++ b/effects/SkPorterDuff.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * 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 "SkPorterDuff.h"
+#include "SkXfermode.h"
+
+/* This file just exists as a compatibility layer, gluing the PorterDuff API
+ into the (extended) SkXfermode API
+ */
+
+#define MAKE_PAIR(mode) { SkPorterDuff::k##mode##_Mode, SkXfermode::k##mode##_Mode }
+
+// this table must be in SkPorterDuff::Mode order, so it can be indexed directly
+// with a porterduff mode.
+static const struct Pair {
+ SkPorterDuff::Mode fPD;
+ SkXfermode::Mode fXF;
+} gPairs[] = {
+ MAKE_PAIR(Clear),
+ MAKE_PAIR(Src),
+ MAKE_PAIR(Dst),
+ MAKE_PAIR(SrcOver),
+ MAKE_PAIR(DstOver),
+ MAKE_PAIR(SrcIn),
+ MAKE_PAIR(DstIn),
+ MAKE_PAIR(SrcOut),
+ MAKE_PAIR(DstOut),
+ MAKE_PAIR(SrcATop),
+ MAKE_PAIR(DstATop),
+ MAKE_PAIR(Xor),
+ MAKE_PAIR(Darken),
+ MAKE_PAIR(Lighten),
+ MAKE_PAIR(Modulate),
+ MAKE_PAIR(Screen),
+ { SkPorterDuff::kAdd_Mode, SkXfermode::kPlus_Mode },
+#ifdef SK_BUILD_FOR_ANDROID
+ MAKE_PAIR(Overlay),
+#endif
+};
+
+static bool find_pdmode(SkXfermode::Mode src, SkPorterDuff::Mode* dst) {
+ const Pair* pairs = gPairs;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
+ if (pairs[i].fXF == src) {
+ if (dst) {
+ *dst = pairs[i].fPD;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+SkXfermode::Mode SkPorterDuff::ToXfermodeMode(Mode mode) {
+ SkASSERT((unsigned)mode < SkPorterDuff::kModeCount);
+ const Pair& pair = gPairs[mode];
+ SkASSERT(pair.fPD == mode);
+ return pair.fXF;
+}
+
+SkXfermode* SkPorterDuff::CreateXfermode(SkPorterDuff::Mode mode) {
+ const Pair& pair = gPairs[mode];
+ SkASSERT(pair.fPD == mode);
+ return SkXfermode::Create(pair.fXF);
+}
+
+bool SkPorterDuff::IsMode(SkXfermode* xfer, Mode* pdmode) {
+ SkXfermode::Mode xfmode;
+ if (!SkXfermode::IsMode(xfer, &xfmode)) {
+ return false;
+ }
+ return find_pdmode(xfmode, pdmode);
+}
+
+SkXfermodeProc SkPorterDuff::GetXfermodeProc(Mode mode) {
+ return SkXfermode::GetProc(gPairs[mode].fXF);
+}
+
+SkXfermodeProc16 SkPorterDuff::GetXfermodeProc16(Mode mode, SkColor srcColor) {
+ return SkXfermode::GetProc16(gPairs[mode].fXF, srcColor);
+}
diff --git a/effects/SkRectShaderImageFilter.cpp b/effects/SkRectShaderImageFilter.cpp
new file mode 100644
index 00000000..ada861fd
--- /dev/null
+++ b/effects/SkRectShaderImageFilter.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRectShaderImageFilter.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+
+SkRectShaderImageFilter* SkRectShaderImageFilter::Create(SkShader* s, const SkRect& rect) {
+ SkASSERT(s);
+ return SkNEW_ARGS(SkRectShaderImageFilter, (s, rect));
+}
+
+SkRectShaderImageFilter::SkRectShaderImageFilter(SkShader* s, const SkRect& rect)
+ : INHERITED(NULL)
+ , fShader(s)
+ , fRect(rect) {
+ SkASSERT(s);
+ s->ref();
+}
+
+SkRectShaderImageFilter::SkRectShaderImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fShader = buffer.readFlattenableT<SkShader>();
+ buffer.readRect(&fRect);
+}
+
+void SkRectShaderImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeFlattenable(fShader);
+ buffer.writeRect(fRect);
+}
+
+SkRectShaderImageFilter::~SkRectShaderImageFilter() {
+ SkSafeUnref(fShader);
+}
+
+bool SkRectShaderImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix&,
+ SkBitmap* result,
+ SkIPoint*) {
+ SkRect rect(fRect);
+ if (rect.isEmpty()) {
+ rect = SkRect::MakeWH(SkIntToScalar(source.width()), SkIntToScalar(source.height()));
+ }
+
+ if (rect.isEmpty()) {
+ return false;
+ }
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(SkScalarCeilToInt(rect.width()),
+ SkScalarCeilToInt(rect.height())));
+ SkCanvas canvas(device.get());
+ SkPaint paint;
+ paint.setShader(fShader);
+ canvas.drawRect(rect, paint);
+ *result = device.get()->accessBitmap(false);
+ return true;
+}
diff --git a/effects/SkStippleMaskFilter.cpp b/effects/SkStippleMaskFilter.cpp
new file mode 100644
index 00000000..14f30ec6
--- /dev/null
+++ b/effects/SkStippleMaskFilter.cpp
@@ -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.
+ */
+
+#include "SkStippleMaskFilter.h"
+#include "SkString.h"
+
+bool SkStippleMaskFilter::filterMask(SkMask* dst,
+ const SkMask& src,
+ const SkMatrix& matrix,
+ SkIPoint* margin) const {
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (NULL != src.fImage) {
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ dst->fImage = SkMask::AllocImage(dstSize);
+
+ uint8_t* srcScanLine = src.fImage;
+ uint8_t* scanline = dst->fImage;
+
+ for (int y = 0; y < src.fBounds.height(); ++y) {
+ for (int x = 0; x < src.fBounds.width(); ++x) {
+ scanline[x] = srcScanLine[x] && ((x+y) & 0x1) ? 0xFF : 0x00;
+ }
+ scanline += dst->fRowBytes;
+ srcScanLine += src.fRowBytes;
+ }
+ }
+
+ return true;
+}
+
+#ifdef SK_DEVELOPER
+void SkStippleMaskFilter::toString(SkString* str) const {
+ str->append("SkStippleMaskFilter: ()");
+}
+#endif
diff --git a/effects/SkTableColorFilter.cpp b/effects/SkTableColorFilter.cpp
new file mode 100644
index 00000000..cbcc6bc4
--- /dev/null
+++ b/effects/SkTableColorFilter.cpp
@@ -0,0 +1,431 @@
+
+#include "SkBitmap.h"
+#include "SkTableColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+class SkTable_ColorFilter : public SkColorFilter {
+public:
+ SkTable_ColorFilter(const uint8_t tableA[], const uint8_t tableR[],
+ const uint8_t tableG[], const uint8_t tableB[]) {
+ fBitmap = NULL;
+ fFlags = 0;
+
+ uint8_t* dst = fStorage;
+ if (tableA) {
+ memcpy(dst, tableA, 256);
+ dst += 256;
+ fFlags |= kA_Flag;
+ }
+ if (tableR) {
+ memcpy(dst, tableR, 256);
+ dst += 256;
+ fFlags |= kR_Flag;
+ }
+ if (tableG) {
+ memcpy(dst, tableG, 256);
+ dst += 256;
+ fFlags |= kG_Flag;
+ }
+ if (tableB) {
+ memcpy(dst, tableB, 256);
+ fFlags |= kB_Flag;
+ }
+ }
+
+ virtual ~SkTable_ColorFilter() {
+ SkDELETE(fBitmap);
+ }
+
+ virtual bool asComponentTable(SkBitmap* table) const SK_OVERRIDE;
+
+#if SK_SUPPORT_GPU
+ virtual GrEffectRef* asNewEffect(GrContext* context) const SK_OVERRIDE;
+#endif
+
+ virtual void filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const SK_OVERRIDE;
+
+ SkDEVCODE(virtual void toString(SkString* str) const SK_OVERRIDE;)
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTable_ColorFilter)
+
+ enum {
+ kA_Flag = 1 << 0,
+ kR_Flag = 1 << 1,
+ kG_Flag = 1 << 2,
+ kB_Flag = 1 << 3,
+ };
+
+protected:
+ SkTable_ColorFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+private:
+ mutable const SkBitmap* fBitmap; // lazily allocated
+
+ uint8_t fStorage[256 * 4];
+ unsigned fFlags;
+
+ typedef SkColorFilter INHERITED;
+};
+
+static const uint8_t gIdentityTable[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+ 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+ 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const {
+ const uint8_t* table = fStorage;
+ const uint8_t* tableA = gIdentityTable;
+ const uint8_t* tableR = gIdentityTable;
+ const uint8_t* tableG = gIdentityTable;
+ const uint8_t* tableB = gIdentityTable;
+ if (fFlags & kA_Flag) {
+ tableA = table; table += 256;
+ }
+ if (fFlags & kR_Flag) {
+ tableR = table; table += 256;
+ }
+ if (fFlags & kG_Flag) {
+ tableG = table; table += 256;
+ }
+ if (fFlags & kB_Flag) {
+ tableB = table;
+ }
+
+ const SkUnPreMultiply::Scale* scaleTable = SkUnPreMultiply::GetScaleTable();
+ for (int i = 0; i < count; ++i) {
+ SkPMColor c = src[i];
+ unsigned a, r, g, b;
+ if (0 == c) {
+ a = r = g = b = 0;
+ } else {
+ a = SkGetPackedA32(c);
+ r = SkGetPackedR32(c);
+ g = SkGetPackedG32(c);
+ b = SkGetPackedB32(c);
+
+ if (a < 255) {
+ SkUnPreMultiply::Scale scale = scaleTable[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+ }
+ }
+ dst[i] = SkPremultiplyARGBInline(tableA[a], tableR[r],
+ tableG[g], tableB[b]);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkTable_ColorFilter::toString(SkString* str) const {
+ str->append("SkTable_ColorFilter");
+}
+#endif
+
+static const uint8_t gCountNibBits[] = {
+ 0, 1, 1, 2,
+ 1, 2, 2, 3,
+ 1, 2, 2, 3,
+ 2, 3, 3, 4
+};
+
+#include "SkPackBits.h"
+
+void SkTable_ColorFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ uint8_t storage[5*256];
+ int count = gCountNibBits[fFlags & 0xF];
+ size_t size = SkPackBits::Pack8(fStorage, count * 256, storage);
+ SkASSERT(size <= sizeof(storage));
+
+// SkDebugf("raw %d packed %d\n", count * 256, size);
+
+ buffer.writeInt(fFlags);
+ buffer.writeByteArray(storage, size);
+}
+
+SkTable_ColorFilter::SkTable_ColorFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fBitmap = NULL;
+
+ uint8_t storage[5*256];
+
+ fFlags = buffer.readInt();
+
+ size_t size = buffer.getArrayCount();
+ SkASSERT(size <= sizeof(storage));
+ buffer.readByteArray(storage);
+
+ SkDEBUGCODE(size_t raw = ) SkPackBits::Unpack8(storage, size, fStorage);
+
+ SkASSERT(raw <= sizeof(fStorage));
+ SkDEBUGCODE(size_t count = gCountNibBits[fFlags & 0xF]);
+ SkASSERT(raw == count * 256);
+}
+
+bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const {
+ if (table) {
+ if (NULL == fBitmap) {
+ 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 };
+
+ for (int x = 0; x < 4; ++x) {
+ if (!(fFlags & kFlags[x])) {
+ memcpy(bitmapPixels, gIdentityTable, sizeof(gIdentityTable));
+ } else {
+ memcpy(bitmapPixels, fStorage + offset, 256);
+ offset += 256;
+ }
+ 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:
+ static GrEffectRef* Create(GrTexture* texture, unsigned flags) {
+ AutoEffectUnref effect(SkNEW_ARGS(ColorTableEffect, (texture, flags)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~ColorTableEffect();
+
+ static const char* Name() { return "ColorTable"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ typedef GLColorTableEffect GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ explicit ColorTableEffect(GrTexture* texture, unsigned flags);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fTextureAccess;
+ unsigned fFlags; // currently not used in shader code, just to assist
+ // getConstantColorComponents().
+
+ typedef GrEffect INHERITED;
+};
+
+class GLColorTableEffect : public GrGLEffect {
+public:
+ GLColorTableEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+
+ typedef GrGLEffect INHERITED;
+};
+
+GLColorTableEffect::GLColorTableEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED(factory) {
+ }
+
+void GLColorTableEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey,
+ 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;
+ if (NULL == inputColor) {
+ // the input color is solid white (all ones).
+ static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
+ builder->fsCodeAppendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
+ kMaxValue, kMaxValue, kMaxValue, kMaxValue);
+
+ } else {
+ builder->fsCodeAppendf("\t\tfloat nonZeroAlpha = max(%s.a, .0001);\n", inputColor);
+ builder->fsCodeAppendf("\t\tvec4 coord = vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha);\n", inputColor);
+ builder->fsCodeAppendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
+ kColorScaleFactor,
+ kColorOffsetFactor, kColorOffsetFactor,
+ kColorOffsetFactor, kColorOffsetFactor);
+ }
+
+ builder->fsCodeAppendf("\t\t%s.a = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.a, 0.125)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.r = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.r, 0.375)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.g = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.g, 0.625)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.b = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.b, 0.875)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+}
+
+GrGLEffect::EffectKey GLColorTableEffect::GenKey(const GrDrawEffect&, const GrGLCaps&) {
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorTableEffect::ColorTableEffect(GrTexture* texture, unsigned flags)
+ : fTextureAccess(texture, "a")
+ , fFlags(flags) {
+ this->addTextureAccess(&fTextureAccess);
+}
+
+ColorTableEffect::~ColorTableEffect() {
+}
+
+const GrBackendEffectFactory& ColorTableEffect::getFactory() const {
+ return GrTBackendEffectFactory<ColorTableEffect>::getInstance();
+}
+
+bool ColorTableEffect::onIsEqual(const GrEffect& sBase) const {
+ return this->texture(0) == sBase.texture(0);
+}
+
+void ColorTableEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // If we kept the table in the effect then we could actually run known inputs through the
+ // table.
+ if (fFlags & SkTable_ColorFilter::kR_Flag) {
+ *validFlags &= ~kR_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kG_Flag) {
+ *validFlags &= ~kG_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kB_Flag) {
+ *validFlags &= ~kB_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kA_Flag) {
+ *validFlags &= ~kA_GrColorComponentFlag;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(ColorTableEffect);
+
+GrEffectRef* ColorTableEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ static unsigned kAllFlags = SkTable_ColorFilter::kR_Flag | SkTable_ColorFilter::kG_Flag |
+ SkTable_ColorFilter::kB_Flag | SkTable_ColorFilter::kA_Flag;
+ return ColorTableEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx], kAllFlags);
+}
+
+GrEffectRef* SkTable_ColorFilter::asNewEffect(GrContext* context) const {
+ SkBitmap bitmap;
+ GrEffectRef* effect = NULL;
+ this->asComponentTable(&bitmap);
+ // passing NULL because this effect does no tiling or filtering.
+ GrTexture* texture = GrLockAndRefCachedBitmapTexture(context, bitmap, NULL);
+ if (NULL != texture) {
+ effect = ColorTableEffect::Create(texture, fFlags);
+
+ // 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.
+ GrUnlockAndUnrefCachedBitmapTexture(texture);
+ }
+ return effect;
+}
+
+#endif // SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_CPU_BENDIAN
+#else
+ #define SK_A32_INDEX (3 - (SK_A32_SHIFT >> 3))
+ #define SK_R32_INDEX (3 - (SK_R32_SHIFT >> 3))
+ #define SK_G32_INDEX (3 - (SK_G32_SHIFT >> 3))
+ #define SK_B32_INDEX (3 - (SK_B32_SHIFT >> 3))
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorFilter* SkTableColorFilter::Create(const uint8_t table[256]) {
+ return SkNEW_ARGS(SkTable_ColorFilter, (table, table, table, table));
+}
+
+SkColorFilter* SkTableColorFilter::CreateARGB(const uint8_t tableA[256],
+ const uint8_t tableR[256],
+ const uint8_t tableG[256],
+ const uint8_t tableB[256]) {
+ return SkNEW_ARGS(SkTable_ColorFilter, (tableA, tableR, tableG, tableB));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkTableColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTable_ColorFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkTableMaskFilter.cpp b/effects/SkTableMaskFilter.cpp
new file mode 100644
index 00000000..5bff4def
--- /dev/null
+++ b/effects/SkTableMaskFilter.cpp
@@ -0,0 +1,143 @@
+
+/*
+ * 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 "SkTableMaskFilter.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkTableMaskFilter::SkTableMaskFilter() {
+ for (int i = 0; i < 256; i++) {
+ fTable[i] = i;
+ }
+}
+
+SkTableMaskFilter::SkTableMaskFilter(const uint8_t table[256]) {
+ memcpy(fTable, table, sizeof(fTable));
+}
+
+SkTableMaskFilter::~SkTableMaskFilter() {}
+
+bool SkTableMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix&, SkIPoint* margin) const {
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fRowBytes = SkAlign4(dst->fBounds.width());
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+ dst->fImage = SkMask::AllocImage(dst->computeImageSize());
+
+ const uint8_t* srcP = src.fImage;
+ uint8_t* dstP = dst->fImage;
+ const uint8_t* table = fTable;
+ int dstWidth = dst->fBounds.width();
+ int extraZeros = dst->fRowBytes - dstWidth;
+
+ for (int y = dst->fBounds.height() - 1; y >= 0; --y) {
+ for (int x = dstWidth - 1; x >= 0; --x) {
+ dstP[x] = table[srcP[x]];
+ }
+ srcP += src.fRowBytes;
+ // we can't just inc dstP by rowbytes, because if it has any
+ // padding between its width and its rowbytes, we need to zero those
+ // so that the bitters can read those safely if that is faster for
+ // them
+ dstP += dstWidth;
+ for (int i = extraZeros - 1; i >= 0; --i) {
+ *dstP++ = 0;
+ }
+ }
+ }
+
+ if (margin) {
+ margin->set(0, 0);
+ }
+ return true;
+}
+
+SkMask::Format SkTableMaskFilter::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+void SkTableMaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeByteArray(fTable, 256);
+}
+
+SkTableMaskFilter::SkTableMaskFilter(SkFlattenableReadBuffer& rb)
+ : INHERITED(rb) {
+ SkASSERT(256 == rb.getArrayCount());
+ rb.readByteArray(fTable);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkTableMaskFilter::MakeGammaTable(uint8_t table[256], SkScalar gamma) {
+ const float dx = 1 / 255.0f;
+ const float g = SkScalarToFloat(gamma);
+
+ float x = 0;
+ for (int i = 0; i < 256; i++) {
+ // float ee = powf(x, g) * 255;
+ table[i] = SkPin32(sk_float_round2int(powf(x, g) * 255), 0, 255);
+ x += dx;
+ }
+}
+
+void SkTableMaskFilter::MakeClipTable(uint8_t table[256], uint8_t min,
+ uint8_t max) {
+ if (0 == max) {
+ max = 1;
+ }
+ if (min >= max) {
+ min = max - 1;
+ }
+ SkASSERT(min < max);
+
+ SkFixed scale = (1 << 16) * 255 / (max - min);
+ memset(table, 0, min + 1);
+ for (int i = min + 1; i < max; i++) {
+ int value = SkFixedRound(scale * (i - min));
+ SkASSERT(value <= 255);
+ table[i] = value;
+ }
+ memset(table + max, 255, 256 - max);
+
+#if 0
+ int j;
+ for (j = 0; j < 256; j++) {
+ if (table[j]) {
+ break;
+ }
+ }
+ SkDebugf("%d %d start [%d]", min, max, j);
+ for (; j < 256; j++) {
+ SkDebugf(" %d", table[j]);
+ }
+ SkDebugf("\n\n");
+#endif
+}
+
+#ifdef SK_DEVELOPER
+void SkTableMaskFilter::toString(SkString* str) const {
+ str->append("SkTableMaskFilter: (");
+
+ str->append("table: ");
+ for (int i = 0; i < 255; ++i) {
+ str->appendf("%d, ", fTable[i]);
+ }
+ str->appendf("%d", fTable[255]);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkTestImageFilters.cpp b/effects/SkTestImageFilters.cpp
new file mode 100755
index 00000000..a919dedd
--- /dev/null
+++ b/effects/SkTestImageFilters.cpp
@@ -0,0 +1,81 @@
+
+#include "SkTestImageFilters.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+// Simple helper canvas that "takes ownership" of the provided device, so that
+// when this canvas goes out of scope, so will its device. Could be replaced
+// with the following:
+//
+// SkCanvas canvas(device);
+// SkAutoTUnref<SkDevice> aur(device);
+//
+class OwnDeviceCanvas : public SkCanvas {
+public:
+ OwnDeviceCanvas(SkDevice* device) : SkCanvas(device) {
+ SkSafeUnref(device);
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDownSampleImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* result, SkIPoint*) {
+ SkScalar scale = fScale;
+ if (scale > SK_Scalar1 || scale <= 0) {
+ return false;
+ }
+
+ int dstW = SkScalarRoundToInt(src.width() * scale);
+ int dstH = SkScalarRoundToInt(src.height() * scale);
+ if (dstW < 1) {
+ dstW = 1;
+ }
+ if (dstH < 1) {
+ dstH = 1;
+ }
+
+ SkBitmap tmp;
+
+ // downsample
+ {
+ SkDevice* dev = proxy->createDevice(dstW, dstH);
+ if (NULL == dev) {
+ return false;
+ }
+ OwnDeviceCanvas canvas(dev);
+ SkPaint paint;
+
+ paint.setFilterBitmap(true);
+ canvas.scale(scale, scale);
+ canvas.drawBitmap(src, 0, 0, &paint);
+ tmp = dev->accessBitmap(false);
+ }
+
+ // upscale
+ {
+ SkDevice* dev = proxy->createDevice(src.width(), src.height());
+ if (NULL == dev) {
+ return false;
+ }
+ OwnDeviceCanvas canvas(dev);
+
+ SkRect r = SkRect::MakeWH(SkIntToScalar(src.width()),
+ SkIntToScalar(src.height()));
+ canvas.drawBitmapRect(tmp, NULL, r, NULL);
+ *result = dev->accessBitmap(false);
+ }
+ return true;
+}
+
+void SkDownSampleImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeScalar(fScale);
+}
+
+SkDownSampleImageFilter::SkDownSampleImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fScale = buffer.readScalar();
+}
diff --git a/effects/SkTransparentShader.cpp b/effects/SkTransparentShader.cpp
new file mode 100644
index 00000000..02744816
--- /dev/null
+++ b/effects/SkTransparentShader.cpp
@@ -0,0 +1,122 @@
+
+/*
+ * 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 "SkTransparentShader.h"
+#include "SkColorPriv.h"
+#include "SkString.h"
+
+bool SkTransparentShader::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ fDevice = &device;
+ fAlpha = paint.getAlpha();
+
+ return this->INHERITED::setContext(device, paint, matrix);
+}
+
+uint32_t SkTransparentShader::getFlags() {
+ uint32_t flags = this->INHERITED::getFlags();
+
+ switch (fDevice->getConfig()) {
+ case SkBitmap::kRGB_565_Config:
+ flags |= kHasSpan16_Flag;
+ if (fAlpha == 255)
+ flags |= kOpaqueAlpha_Flag;
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ if (fAlpha == 255 && fDevice->isOpaque())
+ flags |= kOpaqueAlpha_Flag;
+ break;
+ default:
+ break;
+ }
+ return flags;
+}
+
+void SkTransparentShader::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ unsigned scale = SkAlpha255To256(fAlpha);
+
+ switch (fDevice->getConfig()) {
+ case SkBitmap::kARGB_8888_Config:
+ if (scale == 256) {
+ SkPMColor* src = fDevice->getAddr32(x, y);
+ if (src != span) {
+ memcpy(span, src, count * sizeof(SkPMColor));
+ }
+ } else {
+ const SkPMColor* src = fDevice->getAddr32(x, y);
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkAlphaMulQ(src[i], scale);
+ }
+ }
+ break;
+ case SkBitmap::kRGB_565_Config: {
+ const uint16_t* src = fDevice->getAddr16(x, y);
+ if (scale == 256) {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPixel16ToPixel32(src[i]);
+ }
+ } else {
+ unsigned alpha = fAlpha;
+ for (int i = count - 1; i >= 0; --i) {
+ uint16_t c = src[i];
+ unsigned r = SkPacked16ToR32(c);
+ unsigned g = SkPacked16ToG32(c);
+ unsigned b = SkPacked16ToB32(c);
+
+ span[i] = SkPackARGB32( alpha,
+ SkAlphaMul(r, scale),
+ SkAlphaMul(g, scale),
+ SkAlphaMul(b, scale));
+ }
+ }
+ break;
+ }
+ case SkBitmap::kIndex8_Config:
+ SkDEBUGFAIL("index8 not supported as a destination device");
+ break;
+ case SkBitmap::kA8_Config: {
+ const uint8_t* src = fDevice->getAddr8(x, y);
+ if (scale == 256) {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPackARGB32(src[i], 0, 0, 0);
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPackARGB32(SkAlphaMul(src[i], scale), 0, 0, 0);
+ }
+ }
+ break;
+ }
+ case SkBitmap::kA1_Config:
+ SkDEBUGFAIL("kA1_Config umimplemented at this time");
+ break;
+ default: // to avoid warnings
+ break;
+ }
+}
+
+void SkTransparentShader::shadeSpan16(int x, int y, uint16_t span[], int count) {
+ SkASSERT(fDevice->getConfig() == SkBitmap::kRGB_565_Config);
+
+ uint16_t* src = fDevice->getAddr16(x, y);
+ if (src != span) {
+ memcpy(span, src, count << 1);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkTransparentShader::toString(SkString* str) const {
+ str->append("SkTransparentShader: (");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkXfermodeImageFilter.cpp b/effects/SkXfermodeImageFilter.cpp
new file mode 100644
index 00000000..898bad11
--- /dev/null
+++ b/effects/SkXfermodeImageFilter.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 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 "SkXfermodeImageFilter.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkXfermode.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGr.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkXfermodeImageFilter::SkXfermodeImageFilter(SkXfermode* mode,
+ SkImageFilter* background,
+ SkImageFilter* foreground)
+ : INHERITED(background, foreground), fMode(mode) {
+ SkSafeRef(fMode);
+}
+
+SkXfermodeImageFilter::~SkXfermodeImageFilter() {
+ SkSafeUnref(fMode);
+}
+
+SkXfermodeImageFilter::SkXfermodeImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fMode = buffer.readFlattenableT<SkXfermode>();
+}
+
+void SkXfermodeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fMode);
+}
+
+bool SkXfermodeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ SkBitmap background = src, foreground = src;
+ SkImageFilter* backgroundInput = getInput(0);
+ SkImageFilter* foregroundInput = getInput(1);
+ SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
+ if (backgroundInput &&
+ !backgroundInput->filterImage(proxy, src, ctm, &background, &backgroundOffset)) {
+ return false;
+ }
+ SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
+ if (foregroundInput &&
+ !foregroundInput->filterImage(proxy, src, ctm, &foreground, &foregroundOffset)) {
+ return false;
+ }
+ dst->setConfig(background.config(), background.width(), background.height());
+ dst->allocPixels();
+ SkCanvas canvas(*dst);
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ canvas.drawBitmap(background, 0, 0, &paint);
+ paint.setXfermode(fMode);
+ canvas.drawBitmap(foreground,
+ SkIntToScalar(foregroundOffset.fX - backgroundOffset.fX),
+ SkIntToScalar(foregroundOffset.fY - backgroundOffset.fY),
+ &paint);
+ offset->fX += backgroundOffset.fX;
+ offset->fY += backgroundOffset.fY;
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+bool SkXfermodeImageFilter::filterImageGPU(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result,
+ SkIPoint* offset) {
+ SkBitmap background;
+ SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &background,
+ &backgroundOffset)) {
+ return false;
+ }
+ GrTexture* backgroundTex = background.getTexture();
+ SkBitmap foreground;
+ SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(1), proxy, src, ctm, &foreground,
+ &foregroundOffset)) {
+ return false;
+ }
+ GrTexture* foregroundTex = foreground.getTexture();
+ GrContext* context = foregroundTex->getContext();
+
+ GrEffectRef* xferEffect = NULL;
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = src.width();
+ desc.fHeight = src.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+
+ SkXfermode::Coeff sm, dm;
+ if (!SkXfermode::AsNewEffectOrCoeff(fMode, context, &xferEffect, &sm, &dm, backgroundTex)) {
+ return false;
+ }
+
+ SkMatrix foregroundMatrix = GrEffect::MakeDivByTextureWHMatrix(foregroundTex);
+ foregroundMatrix.preTranslate(SkIntToScalar(backgroundOffset.fX-foregroundOffset.fX),
+ SkIntToScalar(backgroundOffset.fY-foregroundOffset.fY));
+
+
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ if (NULL != xferEffect) {
+ GrPaint paint;
+ paint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+ paint.addColorEffect(xferEffect)->unref();
+ context->drawRect(paint, srcRect);
+ } else {
+ GrPaint backgroundPaint;
+ SkMatrix backgroundMatrix = GrEffect::MakeDivByTextureWHMatrix(backgroundTex);
+ backgroundPaint.addColorTextureEffect(backgroundTex, backgroundMatrix);
+ context->drawRect(backgroundPaint, srcRect);
+
+ GrPaint foregroundPaint;
+ foregroundPaint.setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
+ foregroundPaint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+ context->drawRect(foregroundPaint, srcRect);
+ }
+ offset->fX += backgroundOffset.fX;
+ offset->fY += backgroundOffset.fY;
+ return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
+}
+
+#endif
diff --git a/effects/gradients/SkBitmapCache.cpp b/effects/gradients/SkBitmapCache.cpp
new file mode 100644
index 00000000..95175e4f
--- /dev/null
+++ b/effects/gradients/SkBitmapCache.cpp
@@ -0,0 +1,153 @@
+
+/*
+ * 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 "SkBitmapCache.h"
+
+struct SkBitmapCache::Entry {
+ Entry* fPrev;
+ Entry* fNext;
+
+ void* fBuffer;
+ size_t fSize;
+ SkBitmap fBitmap;
+
+ Entry(const void* buffer, size_t size, const SkBitmap& bm)
+ : fPrev(NULL),
+ fNext(NULL),
+ fBitmap(bm) {
+ fBuffer = sk_malloc_throw(size);
+ fSize = size;
+ memcpy(fBuffer, buffer, size);
+ }
+
+ ~Entry() { sk_free(fBuffer); }
+
+ bool equals(const void* buffer, size_t size) const {
+ return (fSize == size) && !memcmp(fBuffer, buffer, size);
+ }
+};
+
+SkBitmapCache::SkBitmapCache(int max) : fMaxEntries(max) {
+ fEntryCount = 0;
+ fHead = fTail = NULL;
+
+ this->validate();
+}
+
+SkBitmapCache::~SkBitmapCache() {
+ this->validate();
+
+ Entry* entry = fHead;
+ while (entry) {
+ Entry* next = entry->fNext;
+ delete entry;
+ entry = next;
+ }
+}
+
+SkBitmapCache::Entry* SkBitmapCache::detach(Entry* entry) const {
+ if (entry->fPrev) {
+ SkASSERT(fHead != entry);
+ entry->fPrev->fNext = entry->fNext;
+ } else {
+ SkASSERT(fHead == entry);
+ fHead = entry->fNext;
+ }
+ if (entry->fNext) {
+ SkASSERT(fTail != entry);
+ entry->fNext->fPrev = entry->fPrev;
+ } else {
+ SkASSERT(fTail == entry);
+ fTail = entry->fPrev;
+ }
+ return entry;
+}
+
+void SkBitmapCache::attachToHead(Entry* entry) const {
+ entry->fPrev = NULL;
+ entry->fNext = fHead;
+ if (fHead) {
+ fHead->fPrev = entry;
+ } else {
+ fTail = entry;
+ }
+ fHead = entry;
+}
+
+bool SkBitmapCache::find(const void* buffer, size_t size, SkBitmap* bm) const {
+ AutoValidate av(this);
+
+ Entry* entry = fHead;
+ while (entry) {
+ if (entry->equals(buffer, size)) {
+ if (bm) {
+ *bm = entry->fBitmap;
+ }
+ // move to the head of our list, so we purge it last
+ this->detach(entry);
+ this->attachToHead(entry);
+ return true;
+ }
+ entry = entry->fNext;
+ }
+ return false;
+}
+
+void SkBitmapCache::add(const void* buffer, size_t len, const SkBitmap& bm) {
+ AutoValidate av(this);
+
+ if (fEntryCount == fMaxEntries) {
+ SkASSERT(fTail);
+ delete this->detach(fTail);
+ fEntryCount -= 1;
+ }
+
+ Entry* entry = SkNEW_ARGS(Entry, (buffer, len, bm));
+ this->attachToHead(entry);
+ fEntryCount += 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkBitmapCache::validate() const {
+ SkASSERT(fEntryCount >= 0 && fEntryCount <= fMaxEntries);
+
+ if (fEntryCount > 0) {
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL == fTail->fNext);
+
+ if (fEntryCount == 1) {
+ SkASSERT(fHead == fTail);
+ } else {
+ SkASSERT(fHead != fTail);
+ }
+
+ Entry* entry = fHead;
+ int count = 0;
+ while (entry) {
+ count += 1;
+ entry = entry->fNext;
+ }
+ SkASSERT(count == fEntryCount);
+
+ entry = fTail;
+ while (entry) {
+ count -= 1;
+ entry = entry->fPrev;
+ }
+ SkASSERT(0 == count);
+ } else {
+ SkASSERT(NULL == fHead);
+ SkASSERT(NULL == fTail);
+ }
+}
+
+#endif
diff --git a/effects/gradients/SkBitmapCache.h b/effects/gradients/SkBitmapCache.h
new file mode 100644
index 00000000..d2af5e13
--- /dev/null
+++ b/effects/gradients/SkBitmapCache.h
@@ -0,0 +1,49 @@
+
+/*
+ * 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 SkBitmapCache_DEFINED
+#define SkBitmapCache_DEFINED
+
+#include "SkBitmap.h"
+
+class SkBitmapCache : SkNoncopyable {
+public:
+ SkBitmapCache(int maxEntries);
+ ~SkBitmapCache();
+
+ bool find(const void* buffer, size_t len, SkBitmap*) const;
+ void add(const void* buffer, size_t len, const SkBitmap&);
+
+private:
+ int fEntryCount;
+ const int fMaxEntries;
+
+ struct Entry;
+ mutable Entry* fHead;
+ mutable Entry* fTail;
+
+ inline Entry* detach(Entry*) const;
+ inline void attachToHead(Entry*) const;
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+ class AutoValidate : SkNoncopyable {
+ public:
+ AutoValidate(const SkBitmapCache* bc) : fBC(bc) { bc->validate(); }
+ ~AutoValidate() { fBC->validate(); }
+ private:
+ const SkBitmapCache* fBC;
+ };
+};
+
+#endif
diff --git a/effects/gradients/SkClampRange.cpp b/effects/gradients/SkClampRange.cpp
new file mode 100644
index 00000000..3e2ca8e9
--- /dev/null
+++ b/effects/gradients/SkClampRange.cpp
@@ -0,0 +1,165 @@
+
+/*
+ * 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 "SkClampRange.h"
+
+/*
+ * returns [0..count] for the number of steps (<= count) for which x0 <= edge
+ * given each step is followed by x0 += dx
+ */
+static int chop(int64_t x0, SkFixed edge, int64_t x1, int64_t dx, int count) {
+ SkASSERT(dx > 0);
+ SkASSERT(count >= 0);
+
+ if (x0 >= edge) {
+ return 0;
+ }
+ if (x1 <= edge) {
+ return count;
+ }
+ int64_t n = (edge - x0 + dx - 1) / dx;
+ SkASSERT(n >= 0);
+ SkASSERT(n <= count);
+ return (int)n;
+}
+
+static bool overflows_fixed(int64_t x) {
+ return x < -SK_FixedMax || x > SK_FixedMax;
+}
+
+void SkClampRange::initFor1(SkFixed fx) {
+ fCount0 = fCount1 = fCount2 = 0;
+ if (fx <= 0) {
+ fCount0 = 1;
+ } else if (fx < 0xFFFF) {
+ fCount1 = 1;
+ fFx1 = fx;
+ } else {
+ fCount2 = 1;
+ }
+}
+
+void SkClampRange::init(SkFixed fx0, SkFixed dx0, int count, int v0, int v1) {
+ SkASSERT(count > 0);
+
+ fV0 = v0;
+ fV1 = v1;
+ fOverflowed = false;
+
+ // special case 1 == count, as it is slightly common for skia
+ // and avoids us ever calling divide or 64bit multiply
+ if (1 == count) {
+ this->initFor1(fx0);
+ return;
+ }
+
+ int64_t fx = fx0;
+ int64_t dx = dx0;
+ // start with ex equal to the last computed value
+ int64_t ex = fx + (count - 1) * dx;
+ fOverflowed = overflows_fixed(ex);
+
+ if ((uint64_t)(fx | ex) <= 0xFFFF) {
+ fCount0 = fCount2 = 0;
+ fCount1 = count;
+ fFx1 = fx0;
+ return;
+ }
+ if (fx <= 0 && ex <= 0) {
+ fCount1 = fCount2 = 0;
+ fCount0 = count;
+ return;
+ }
+ if (fx >= 0xFFFF && ex >= 0xFFFF) {
+ fCount0 = fCount1 = 0;
+ fCount2 = count;
+ return;
+ }
+
+ int extraCount = 0;
+
+ // now make ex be 1 past the last computed value
+ ex += dx;
+ fOverflowed = overflows_fixed(ex);
+ // now check for over/under flow
+ if (fOverflowed) {
+ int originalCount = count;
+ int64_t ccount;
+ bool swap = dx < 0;
+ if (swap) {
+ dx = -dx;
+ fx = -fx;
+ }
+ ccount = (SK_FixedMax - fx + dx - 1) / dx;
+ if (swap) {
+ dx = -dx;
+ fx = -fx;
+ }
+ SkASSERT(ccount > 0 && ccount <= SK_FixedMax);
+
+ count = (int)ccount;
+ if (0 == count) {
+ this->initFor1(fx0);
+ if (dx > 0) {
+ fCount2 += originalCount - 1;
+ } else {
+ fCount0 += originalCount - 1;
+ }
+ return;
+ }
+ extraCount = originalCount - count;
+ ex = fx + dx * count;
+ }
+
+ bool doSwap = dx < 0;
+
+ if (doSwap) {
+ ex -= dx;
+ fx -= dx;
+ SkTSwap(fx, ex);
+ dx = -dx;
+ }
+
+
+ fCount0 = chop(fx, 0, ex, dx, count);
+ count -= fCount0;
+ fx += fCount0 * dx;
+ SkASSERT(fx >= 0);
+ SkASSERT(fCount0 == 0 || (fx - dx) < 0);
+ fCount1 = chop(fx, 0xFFFF, ex, dx, count);
+ count -= fCount1;
+ fCount2 = count;
+
+#ifdef SK_DEBUG
+ fx += fCount1 * dx;
+ SkASSERT(fx <= ex);
+ if (fCount2 > 0) {
+ SkASSERT(fx >= 0xFFFF);
+ if (fCount1 > 0) {
+ SkASSERT(fx - dx < 0xFFFF);
+ }
+ }
+#endif
+
+ if (doSwap) {
+ SkTSwap(fCount0, fCount2);
+ SkTSwap(fV0, fV1);
+ dx = -dx;
+ }
+
+ if (fCount1 > 0) {
+ fFx1 = fx0 + fCount0 * (int)dx;
+ }
+
+ if (dx > 0) {
+ fCount2 += extraCount;
+ } else {
+ fCount0 += extraCount;
+ }
+}
diff --git a/effects/gradients/SkClampRange.h b/effects/gradients/SkClampRange.h
new file mode 100644
index 00000000..376dc93b
--- /dev/null
+++ b/effects/gradients/SkClampRange.h
@@ -0,0 +1,38 @@
+
+/*
+ * 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 SkClampRange_DEFINED
+#define SkClampRange_DEFINED
+
+#include "SkFixed.h"
+
+/**
+ * Iteration fixed fx by dx, clamping as you go to [0..0xFFFF], this class
+ * computes the (up to) 3 spans there are:
+ *
+ * range0: use constant value V0
+ * range1: iterate as usual fx += dx
+ * range2: use constant value V1
+ */
+struct SkClampRange {
+ int fCount0; // count for fV0
+ int fCount1; // count for interpolating (fV0...fV1)
+ int fCount2; // count for fV1
+ SkFixed fFx1; // initial fx value for the fCount1 range.
+ // only valid if fCount1 > 0
+ int fV0, fV1;
+ bool fOverflowed; // true if we had to clamp due to numerical overflow
+
+ void init(SkFixed fx, SkFixed dx, int count, int v0, int v1);
+
+private:
+ void initFor1(SkFixed fx);
+};
+
+#endif
diff --git a/effects/gradients/SkGradientShader.cpp b/effects/gradients/SkGradientShader.cpp
new file mode 100644
index 00000000..de43b69a
--- /dev/null
+++ b/effects/gradients/SkGradientShader.cpp
@@ -0,0 +1,986 @@
+/*
+ * 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 "SkGradientShaderPriv.h"
+#include "SkLinearGradient.h"
+#include "SkRadialGradient.h"
+#include "SkTwoPointRadialGradient.h"
+#include "SkTwoPointConicalGradient.h"
+#include "SkSweepGradient.h"
+
+SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc) {
+ SkASSERT(desc.fCount > 1);
+
+ fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
+
+ fMapper = desc.fMapper;
+ SkSafeRef(fMapper);
+ fGradFlags = SkToU8(desc.fFlags);
+
+ SkASSERT((unsigned)desc.fTileMode < SkShader::kTileModeCount);
+ SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
+ fTileMode = desc.fTileMode;
+ fTileProc = gTileProcs[desc.fTileMode];
+
+ fCache16 = fCache16Storage = NULL;
+ fCache32 = NULL;
+ fCache32PixelRef = NULL;
+
+ /* Note: we let the caller skip the first and/or last position.
+ i.e. pos[0] = 0.3, pos[1] = 0.7
+ In these cases, we insert dummy entries to ensure that the final data
+ will be bracketed by [0, 1].
+ i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
+
+ Thus colorCount (the caller's value, and fColorCount (our value) may
+ differ by up to 2. In the above example:
+ colorCount = 2
+ fColorCount = 4
+ */
+ fColorCount = desc.fCount;
+ // check if we need to add in dummy start and/or end position/colors
+ bool dummyFirst = false;
+ bool dummyLast = false;
+ if (desc.fPos) {
+ dummyFirst = desc.fPos[0] != 0;
+ dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1;
+ fColorCount += dummyFirst + dummyLast;
+ }
+
+ if (fColorCount > kColorStorageCount) {
+ size_t size = sizeof(SkColor) + sizeof(Rec);
+ fOrigColors = reinterpret_cast<SkColor*>(
+ sk_malloc_throw(size * fColorCount));
+ }
+ else {
+ fOrigColors = fStorage;
+ }
+
+ // Now copy over the colors, adding the dummies as needed
+ {
+ SkColor* origColors = fOrigColors;
+ if (dummyFirst) {
+ *origColors++ = desc.fColors[0];
+ }
+ memcpy(origColors, desc.fColors, desc.fCount * sizeof(SkColor));
+ if (dummyLast) {
+ origColors += desc.fCount;
+ *origColors = desc.fColors[desc.fCount - 1];
+ }
+ }
+
+ fRecs = (Rec*)(fOrigColors + fColorCount);
+ if (fColorCount > 2) {
+ Rec* recs = fRecs;
+ recs->fPos = 0;
+ // recs->fScale = 0; // unused;
+ recs += 1;
+ if (desc.fPos) {
+ /* We need to convert the user's array of relative positions into
+ fixed-point positions and scale factors. We need these results
+ to be strictly monotonic (no two values equal or out of order).
+ Hence this complex loop that just jams a zero for the scale
+ value if it sees a segment out of order, and it assures that
+ we start at 0 and end at 1.0
+ */
+ SkFixed prev = 0;
+ int startIndex = dummyFirst ? 0 : 1;
+ int count = desc.fCount + dummyLast;
+ for (int i = startIndex; i < count; i++) {
+ // force the last value to be 1.0
+ SkFixed curr;
+ if (i == desc.fCount) { // we're really at the dummyLast
+ curr = SK_Fixed1;
+ } else {
+ curr = SkScalarToFixed(desc.fPos[i]);
+ }
+ // pin curr withing range
+ if (curr < 0) {
+ curr = 0;
+ } else if (curr > SK_Fixed1) {
+ curr = SK_Fixed1;
+ }
+ recs->fPos = curr;
+ if (curr > prev) {
+ recs->fScale = (1 << 24) / (curr - prev);
+ } else {
+ recs->fScale = 0; // ignore this segment
+ }
+ // get ready for the next value
+ prev = curr;
+ recs += 1;
+ }
+ } else { // assume even distribution
+ SkFixed dp = SK_Fixed1 / (desc.fCount - 1);
+ SkFixed p = dp;
+ SkFixed scale = (desc.fCount - 1) << 8; // (1 << 24) / dp
+ for (int i = 1; i < desc.fCount; i++) {
+ recs->fPos = p;
+ recs->fScale = scale;
+ recs += 1;
+ p += dp;
+ }
+ }
+ }
+ this->initCommon();
+}
+
+static uint32_t pack_mode_flags(SkShader::TileMode mode, uint32_t flags) {
+ SkASSERT(0 == (flags >> 28));
+ SkASSERT(0 == ((uint32_t)mode >> 4));
+ return (flags << 4) | mode;
+}
+
+static SkShader::TileMode unpack_mode(uint32_t packed) {
+ return (SkShader::TileMode)(packed & 0xF);
+}
+
+static uint32_t unpack_flags(uint32_t packed) {
+ return packed >> 4;
+}
+
+SkGradientShaderBase::SkGradientShaderBase(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fCacheAlpha = 256;
+
+ fMapper = buffer.readFlattenableT<SkUnitMapper>();
+
+ fCache16 = fCache16Storage = NULL;
+ fCache32 = NULL;
+ fCache32PixelRef = NULL;
+
+ int colorCount = fColorCount = buffer.getArrayCount();
+ if (colorCount > kColorStorageCount) {
+ size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
+ fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
+ } else {
+ fOrigColors = fStorage;
+ }
+ buffer.readColorArray(fOrigColors);
+
+ {
+ uint32_t packed = buffer.readUInt();
+ fGradFlags = SkToU8(unpack_flags(packed));
+ fTileMode = unpack_mode(packed);
+ }
+ fTileProc = gTileProcs[fTileMode];
+ fRecs = (Rec*)(fOrigColors + colorCount);
+ if (colorCount > 2) {
+ Rec* recs = fRecs;
+ recs[0].fPos = 0;
+ for (int i = 1; i < colorCount; i++) {
+ recs[i].fPos = buffer.readInt();
+ recs[i].fScale = buffer.readUInt();
+ }
+ }
+ buffer.readMatrix(&fPtsToUnit);
+ this->initCommon();
+}
+
+SkGradientShaderBase::~SkGradientShaderBase() {
+ if (fCache16Storage) {
+ sk_free(fCache16Storage);
+ }
+ SkSafeUnref(fCache32PixelRef);
+ if (fOrigColors != fStorage) {
+ sk_free(fOrigColors);
+ }
+ SkSafeUnref(fMapper);
+}
+
+void SkGradientShaderBase::initCommon() {
+ fFlags = 0;
+ unsigned colorAlpha = 0xFF;
+ for (int i = 0; i < fColorCount; i++) {
+ colorAlpha &= SkColorGetA(fOrigColors[i]);
+ }
+ fColorsAreOpaque = colorAlpha == 0xFF;
+}
+
+void SkGradientShaderBase::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fMapper);
+ buffer.writeColorArray(fOrigColors, fColorCount);
+ buffer.writeUInt(pack_mode_flags(fTileMode, fGradFlags));
+ if (fColorCount > 2) {
+ Rec* recs = fRecs;
+ for (int i = 1; i < fColorCount; i++) {
+ buffer.writeInt(recs[i].fPos);
+ buffer.writeUInt(recs[i].fScale);
+ }
+ }
+ buffer.writeMatrix(fPtsToUnit);
+}
+
+bool SkGradientShaderBase::isOpaque() const {
+ return fColorsAreOpaque;
+}
+
+bool SkGradientShaderBase::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ const SkMatrix& inverse = this->getTotalInverse();
+
+ if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
+ // need to keep our set/end context calls balanced.
+ this->INHERITED::endContext();
+ return false;
+ }
+
+ fDstToIndexProc = fDstToIndex.getMapXYProc();
+ fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
+
+ // now convert our colors in to PMColors
+ unsigned paintAlpha = this->getPaintAlpha();
+
+ fFlags = this->INHERITED::getFlags();
+ if (fColorsAreOpaque && paintAlpha == 0xFF) {
+ fFlags |= kOpaqueAlpha_Flag;
+ }
+ // we can do span16 as long as our individual colors are opaque,
+ // regardless of the paint's alpha
+ if (fColorsAreOpaque) {
+ fFlags |= kHasSpan16_Flag;
+ }
+
+ this->setCacheAlpha(paintAlpha);
+ return true;
+}
+
+void SkGradientShaderBase::setCacheAlpha(U8CPU alpha) const {
+ // if the new alpha differs from the previous time we were called, inval our cache
+ // this will trigger the cache to be rebuilt.
+ // we don't care about the first time, since the cache ptrs will already be NULL
+ if (fCacheAlpha != alpha) {
+ fCache16 = NULL; // inval the cache
+ fCache32 = NULL; // inval the cache
+ fCacheAlpha = alpha; // record the new alpha
+ // inform our subclasses
+ if (fCache32PixelRef) {
+ fCache32PixelRef->notifyPixelsChanged();
+ }
+ }
+}
+
+#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
+
+/** We take the original colors, not our premultiplied PMColors, since we can
+ build a 16bit table as long as the original colors are opaque, even if the
+ paint specifies a non-opaque alpha.
+*/
+void SkGradientShaderBase::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
+ int count) {
+ SkASSERT(count > 1);
+ SkASSERT(SkColorGetA(c0) == 0xFF);
+ SkASSERT(SkColorGetA(c1) == 0xFF);
+
+ SkFixed r = SkColorGetR(c0);
+ SkFixed g = SkColorGetG(c0);
+ SkFixed b = SkColorGetB(c0);
+
+ SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
+ SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
+ SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
+
+ r = SkIntToFixed(r) + 0x8000;
+ g = SkIntToFixed(g) + 0x8000;
+ b = SkIntToFixed(b) + 0x8000;
+
+ do {
+ unsigned rr = r >> 16;
+ unsigned gg = g >> 16;
+ unsigned bb = b >> 16;
+ cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
+ cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
+ cache += 1;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+}
+
+/*
+ * r,g,b used to be SkFixed, but on gcc (4.2.1 mac and 4.6.3 goobuntu) in
+ * release builds, we saw a compiler error where the 0xFF parameter in
+ * SkPackARGB32() was being totally ignored whenever it was called with
+ * a non-zero add (e.g. 0x8000).
+ *
+ * We found two work-arounds:
+ * 1. change r,g,b to unsigned (or just one of them)
+ * 2. change SkPackARGB32 to + its (a << SK_A32_SHIFT) value instead
+ * of using |
+ *
+ * We chose #1 just because it was more localized.
+ * See http://code.google.com/p/skia/issues/detail?id=1113
+ *
+ * The type SkUFixed encapsulate this need for unsigned, but logically Fixed.
+ */
+typedef uint32_t SkUFixed;
+
+void SkGradientShaderBase::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
+ int count, U8CPU paintAlpha, uint32_t gradFlags) {
+ SkASSERT(count > 1);
+
+ // need to apply paintAlpha to our two endpoints
+ uint32_t a0 = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
+ uint32_t a1 = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
+
+
+ const bool interpInPremul = SkToBool(gradFlags &
+ SkGradientShader::kInterpolateColorsInPremul_Flag);
+
+ uint32_t r0 = SkColorGetR(c0);
+ uint32_t g0 = SkColorGetG(c0);
+ uint32_t b0 = SkColorGetB(c0);
+
+ uint32_t r1 = SkColorGetR(c1);
+ uint32_t g1 = SkColorGetG(c1);
+ uint32_t b1 = SkColorGetB(c1);
+
+ if (interpInPremul) {
+ r0 = SkMulDiv255Round(r0, a0);
+ g0 = SkMulDiv255Round(g0, a0);
+ b0 = SkMulDiv255Round(b0, a0);
+
+ r1 = SkMulDiv255Round(r1, a1);
+ g1 = SkMulDiv255Round(g1, a1);
+ b1 = SkMulDiv255Round(b1, a1);
+ }
+
+ SkFixed da = SkIntToFixed(a1 - a0) / (count - 1);
+ SkFixed dr = SkIntToFixed(r1 - r0) / (count - 1);
+ SkFixed dg = SkIntToFixed(g1 - g0) / (count - 1);
+ SkFixed db = SkIntToFixed(b1 - b0) / (count - 1);
+
+ /* We pre-add 1/8 to avoid having to add this to our [0] value each time
+ in the loop. Without this, the bias for each would be
+ 0x2000 0xA000 0xE000 0x6000
+ With this trick, we can add 0 for the first (no-op) and just adjust the
+ others.
+ */
+ SkUFixed a = SkIntToFixed(a0) + 0x2000;
+ SkUFixed r = SkIntToFixed(r0) + 0x2000;
+ SkUFixed g = SkIntToFixed(g0) + 0x2000;
+ SkUFixed b = SkIntToFixed(b0) + 0x2000;
+
+ /*
+ * Our dither-cell (spatially) is
+ * 0 2
+ * 3 1
+ * Where
+ * [0] -> [-1/8 ... 1/8 ) values near 0
+ * [1] -> [ 1/8 ... 3/8 ) values near 1/4
+ * [2] -> [ 3/8 ... 5/8 ) values near 1/2
+ * [3] -> [ 5/8 ... 7/8 ) values near 3/4
+ */
+
+ if (0xFF == a0 && 0 == da) {
+ do {
+ cache[kCache32Count*0] = SkPackARGB32(0xFF, (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPackARGB32(0xFF, (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPackARGB32(0xFF, (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPackARGB32(0xFF, (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ } else if (interpInPremul) {
+ do {
+ cache[kCache32Count*0] = SkPackARGB32((a + 0 ) >> 16,
+ (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPackARGB32((a + 0x8000) >> 16,
+ (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPackARGB32((a + 0xC000) >> 16,
+ (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPackARGB32((a + 0x4000) >> 16,
+ (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ a += da;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ } else { // interpolate in unpreml space
+ do {
+ cache[kCache32Count*0] = SkPremultiplyARGBInline((a + 0 ) >> 16,
+ (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPremultiplyARGBInline((a + 0x8000) >> 16,
+ (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPremultiplyARGBInline((a + 0xC000) >> 16,
+ (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPremultiplyARGBInline((a + 0x4000) >> 16,
+ (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ a += da;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ }
+}
+
+static inline int SkFixedToFFFF(SkFixed x) {
+ SkASSERT((unsigned)x <= SK_Fixed1);
+ return x - (x >> 16);
+}
+
+static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
+ SkASSERT(x < (1U << bits));
+ if (6 == bits) {
+ return (x << 10) | (x << 4) | (x >> 2);
+ }
+ if (8 == bits) {
+ return (x << 8) | x;
+ }
+ sk_throw();
+ return 0;
+}
+
+const uint16_t* SkGradientShaderBase::getCache16() const {
+ if (fCache16 == NULL) {
+ // double the count for dither entries
+ const int entryCount = kCache16Count * 2;
+ const size_t allocSize = sizeof(uint16_t) * entryCount;
+
+ if (fCache16Storage == NULL) { // set the storage and our working ptr
+ fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
+ }
+ fCache16 = fCache16Storage;
+ if (fColorCount == 2) {
+ Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
+ kCache16Count);
+ } else {
+ Rec* rec = fRecs;
+ int prevIndex = 0;
+ for (int i = 1; i < fColorCount; i++) {
+ int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
+ SkASSERT(nextIndex < kCache16Count);
+
+ if (nextIndex > prevIndex)
+ Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
+ prevIndex = nextIndex;
+ }
+ }
+
+ if (fMapper) {
+ fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
+ uint16_t* linear = fCache16; // just computed linear data
+ uint16_t* mapped = fCache16Storage; // storage for mapped data
+ SkUnitMapper* map = fMapper;
+ for (int i = 0; i < kCache16Count; i++) {
+ int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
+ mapped[i] = linear[index];
+ mapped[i + kCache16Count] = linear[index + kCache16Count];
+ }
+ sk_free(fCache16);
+ fCache16 = fCache16Storage;
+ }
+ }
+ return fCache16;
+}
+
+const SkPMColor* SkGradientShaderBase::getCache32() const {
+ if (fCache32 == NULL) {
+ // double the count for dither entries
+ const int entryCount = kCache32Count * 4;
+ const size_t allocSize = sizeof(SkPMColor) * entryCount;
+
+ if (NULL == fCache32PixelRef) {
+ fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
+ (NULL, allocSize, NULL));
+ }
+ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
+ if (fColorCount == 2) {
+ Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
+ kCache32Count, fCacheAlpha, fGradFlags);
+ } else {
+ Rec* rec = fRecs;
+ int prevIndex = 0;
+ for (int i = 1; i < fColorCount; i++) {
+ int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
+ SkASSERT(nextIndex < kCache32Count);
+
+ if (nextIndex > prevIndex)
+ Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
+ fOrigColors[i], nextIndex - prevIndex + 1,
+ fCacheAlpha, fGradFlags);
+ prevIndex = nextIndex;
+ }
+ }
+
+ if (fMapper) {
+ SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
+ (NULL, allocSize, NULL));
+ SkPMColor* linear = fCache32; // just computed linear data
+ SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
+ SkUnitMapper* map = fMapper;
+ for (int i = 0; i < kCache32Count; i++) {
+ int index = map->mapUnit16((i << 8) | i) >> 8;
+ mapped[i + kCache32Count*0] = linear[index + kCache32Count*0];
+ mapped[i + kCache32Count*1] = linear[index + kCache32Count*1];
+ mapped[i + kCache32Count*2] = linear[index + kCache32Count*2];
+ mapped[i + kCache32Count*3] = linear[index + kCache32Count*3];
+ }
+ fCache32PixelRef->unref();
+ fCache32PixelRef = newPR;
+ fCache32 = (SkPMColor*)newPR->getAddr();
+ }
+ }
+ return fCache32;
+}
+
+/*
+ * Because our caller might rebuild the same (logically the same) gradient
+ * over and over, we'd like to return exactly the same "bitmap" if possible,
+ * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
+ * To do that, we maintain a private cache of built-bitmaps, based on our
+ * colors and positions. Note: we don't try to flatten the fMapper, so if one
+ * is present, we skip the cache for now.
+ */
+void SkGradientShaderBase::getGradientTableBitmap(SkBitmap* bitmap) const {
+ // our caller assumes no external alpha, so we ensure that our cache is
+ // built with 0xFF
+ this->setCacheAlpha(0xFF);
+
+ // don't have a way to put the mapper into our cache-key yet
+ if (fMapper) {
+ // force our cahce32pixelref to be built
+ (void)this->getCache32();
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
+ bitmap->setPixelRef(fCache32PixelRef);
+ return;
+ }
+
+ // build our key: [numColors + colors[] + {positions[]} + flags ]
+ int count = 1 + fColorCount + 1;
+ if (fColorCount > 2) {
+ count += fColorCount - 1; // fRecs[].fPos
+ }
+
+ SkAutoSTMalloc<16, int32_t> storage(count);
+ int32_t* buffer = storage.get();
+
+ *buffer++ = fColorCount;
+ memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
+ buffer += fColorCount;
+ if (fColorCount > 2) {
+ for (int i = 1; i < fColorCount; i++) {
+ *buffer++ = fRecs[i].fPos;
+ }
+ }
+ *buffer++ = fGradFlags;
+ SkASSERT(buffer - storage.get() == count);
+
+ ///////////////////////////////////
+
+ SK_DECLARE_STATIC_MUTEX(gMutex);
+ static SkBitmapCache* gCache;
+ // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
+ static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
+ SkAutoMutexAcquire ama(gMutex);
+
+ if (NULL == gCache) {
+ gCache = SkNEW_ARGS(SkBitmapCache, (MAX_NUM_CACHED_GRADIENT_BITMAPS));
+ }
+ size_t size = count * sizeof(int32_t);
+
+ if (!gCache->find(storage.get(), size, bitmap)) {
+ // force our cahce32pixelref to be built
+ (void)this->getCache32();
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
+ bitmap->setPixelRef(fCache32PixelRef);
+
+ gCache->add(storage.get(), size, *bitmap);
+ }
+}
+
+void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
+ if (info) {
+ if (info->fColorCount >= fColorCount) {
+ if (info->fColors) {
+ memcpy(info->fColors, fOrigColors, fColorCount * sizeof(SkColor));
+ }
+ if (info->fColorOffsets) {
+ if (fColorCount == 2) {
+ info->fColorOffsets[0] = 0;
+ info->fColorOffsets[1] = SK_Scalar1;
+ } else if (fColorCount > 2) {
+ for (int i = 0; i < fColorCount; ++i) {
+ info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
+ }
+ }
+ }
+ }
+ info->fColorCount = fColorCount;
+ info->fTileMode = fTileMode;
+ info->fGradientFlags = fGradFlags;
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkGradientShaderBase::toString(SkString* str) const {
+
+ str->appendf("%d colors: ", fColorCount);
+
+ for (int i = 0; i < fColorCount; ++i) {
+ str->appendHex(fOrigColors[i]);
+ if (i < fColorCount-1) {
+ str->append(", ");
+ }
+ }
+
+ if (fColorCount > 2) {
+ str->append(" points: (");
+ for (int i = 0; i < fColorCount; ++i) {
+ str->appendScalar(SkFixedToScalar(fRecs[i].fPos));
+ if (i < fColorCount-1) {
+ str->append(", ");
+ }
+ }
+ str->append(")");
+ }
+
+ static const char* gTileModeName[SkShader::kTileModeCount] = {
+ "clamp", "repeat", "mirror"
+ };
+
+ str->append(" ");
+ str->append(gTileModeName[fTileMode]);
+
+ // TODO: add "fMapper->toString(str);" when SkUnitMapper::toString is added
+
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkEmptyShader.h"
+
+// assumes colors is SkColor* and pos is SkScalar*
+#define EXPAND_1_COLOR(count) \
+ SkColor tmp[2]; \
+ do { \
+ if (1 == count) { \
+ tmp[0] = tmp[1] = colors[0]; \
+ colors = tmp; \
+ pos = NULL; \
+ count = 2; \
+ } \
+ } while (0)
+
+static void desc_init(SkGradientShaderBase::Descriptor* desc,
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper, uint32_t flags) {
+ desc->fColors = colors;
+ desc->fPos = pos;
+ desc->fCount = colorCount;
+ desc->fTileMode = mode;
+ desc->fMapper = mapper;
+ desc->fFlags = flags;
+}
+
+SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (NULL == pts || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkLinearGradient, (pts, desc));
+}
+
+SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (radius <= 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkRadialGradient, (center, radius, desc));
+}
+
+SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
+ SkScalar startRadius,
+ const SkPoint& end,
+ SkScalar endRadius,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkTwoPointRadialGradient,
+ (start, startRadius, end, endRadius, desc));
+}
+
+SkShader* SkGradientShader::CreateTwoPointConical(const SkPoint& start,
+ SkScalar startRadius,
+ const SkPoint& end,
+ SkScalar endRadius,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ if (start == end && startRadius == endRadius) {
+ return SkNEW(SkEmptyShader);
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkTwoPointConicalGradient,
+ (start, startRadius, end, endRadius, desc));
+}
+
+SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount, SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, SkShader::kClamp_TileMode, mapper, flags);
+ return SkNEW_ARGS(SkSweepGradient, (cx, cy, desc));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLinearGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRadialGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSweepGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointRadialGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointConicalGradient)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "effects/GrTextureStripAtlas.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkGr.h"
+
+GrGLGradientEffect::GrGLGradientEffect(const GrBackendEffectFactory& factory)
+ : INHERITED(factory)
+ , fCachedYCoord(SK_ScalarMax)
+ , fFSYUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(kCoordsType) {
+}
+
+GrGLGradientEffect::~GrGLGradientEffect() { }
+
+void GrGLGradientEffect::emitYCoordUniform(GrGLShaderBuilder* builder) {
+ fFSYUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "GradientYCoordFS");
+}
+
+void GrGLGradientEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrGradientEffect& e = drawEffect.castEffect<GrGradientEffect>();
+ const GrTexture* texture = e.texture(0);
+ fEffectMatrix.setData(uman, e.getMatrix(), drawEffect, texture);
+
+ SkScalar yCoord = e.getYCoord();
+ if (yCoord != fCachedYCoord) {
+ uman.set1f(fFSYUni, yCoord);
+ fCachedYCoord = yCoord;
+ }
+}
+
+GrGLEffect::EffectKey GrGLGradientEffect::GenMatrixKey(const GrDrawEffect& drawEffect) {
+ const GrGradientEffect& e = drawEffect.castEffect<GrGradientEffect>();
+ const GrTexture* texture = e.texture(0);
+ return GrGLEffectMatrix::GenKey(e.getMatrix(), drawEffect, kCoordsType, texture);
+}
+
+void GrGLGradientEffect::setupMatrix(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char** fsCoordName,
+ const char** vsVaryingName,
+ GrSLType* vsVaryingType) {
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder,
+ key & kMatrixKeyMask,
+ fsCoordName,
+ vsVaryingName,
+ vsVaryingType);
+}
+
+void GrGLGradientEffect::emitColorLookup(GrGLShaderBuilder* builder,
+ const char* gradientTValue,
+ const char* outputColor,
+ const char* inputColor,
+ const GrGLShaderBuilder::TextureSampler& sampler) {
+
+ builder->fsCodeAppendf("\tvec2 coord = vec2(%s, %s);\n",
+ gradientTValue,
+ builder->getUniformVariable(fFSYUni).c_str());
+ builder->fsCodeAppendf("\t%s = ", outputColor);
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ sampler,
+ "coord");
+ builder->fsCodeAppend(";\n");
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrGradientEffect::GrGradientEffect(GrContext* ctx,
+ const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tileMode) {
+ // TODO: check for simple cases where we don't need a texture:
+ //GradientInfo info;
+ //shader.asAGradient(&info);
+ //if (info.fColorCount == 2) { ...
+
+ fMatrix = matrix;
+
+ SkBitmap bitmap;
+ shader.getGradientTableBitmap(&bitmap);
+
+ fIsOpaque = shader.isOpaque();
+
+ GrTextureStripAtlas::Desc desc;
+ desc.fWidth = bitmap.width();
+ desc.fHeight = 32;
+ desc.fRowHeight = bitmap.height();
+ desc.fContext = ctx;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(bitmap.config());
+ fAtlas = GrTextureStripAtlas::GetAtlas(desc);
+ GrAssert(NULL != fAtlas);
+
+ // We always filter the gradient table. Each table is one row of a texture, so always y-clamp.
+ GrTextureParams params;
+ params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
+ params.setTileModeX(tileMode);
+
+ fRow = fAtlas->lockRow(bitmap);
+ if (-1 != fRow) {
+ fYCoord = fAtlas->getYOffset(fRow) + SK_ScalarHalf *
+ fAtlas->getVerticalScaleFactor();
+ fTextureAccess.reset(fAtlas->getTexture(), params);
+ } else {
+ GrTexture* texture = GrLockAndRefCachedBitmapTexture(ctx, bitmap, &params);
+ fTextureAccess.reset(texture, params);
+ 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
+ // the cache, but it'll still be ref'd until it's no longer being used.
+ GrUnlockAndUnrefCachedBitmapTexture(texture);
+ }
+ this->addTextureAccess(&fTextureAccess);
+}
+
+GrGradientEffect::~GrGradientEffect() {
+ if (this->useAtlas()) {
+ fAtlas->unlockRow(fRow);
+ }
+}
+
+bool GrGradientEffect::onIsEqual(const GrEffect& effect) const {
+ const GrGradientEffect& s = CastEffect<GrGradientEffect>(effect);
+ return fTextureAccess.getTexture() == s.fTextureAccess.getTexture() &&
+ fTextureAccess.getParams().getTileModeX() ==
+ s.fTextureAccess.getParams().getTileModeX() &&
+ this->useAtlas() == s.useAtlas() &&
+ fYCoord == s.getYCoord() &&
+ fMatrix.cheapEqualTo(s.getMatrix());
+}
+
+void GrGradientEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ if (fIsOpaque && (kA_GrColorComponentFlag & *validFlags) && 0xff == GrColorUnpackA(*color)) {
+ *validFlags = kA_GrColorComponentFlag;
+ } else {
+ *validFlags = 0;
+ }
+}
+
+int GrGradientEffect::RandomGradientParams(SkMWCRandom* random,
+ SkColor colors[],
+ SkScalar** stops,
+ SkShader::TileMode* tm) {
+ int outColors = random->nextRangeU(1, kMaxRandomGradientColors);
+
+ // if one color, omit stops, otherwise randomly decide whether or not to
+ if (outColors == 1 || (outColors >= 2 && random->nextBool())) {
+ *stops = NULL;
+ }
+
+ SkScalar stop = 0.f;
+ for (int i = 0; i < outColors; ++i) {
+ colors[i] = random->nextU();
+ if (NULL != *stops) {
+ (*stops)[i] = stop;
+ stop = i < outColors - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
+ }
+ }
+ *tm = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount));
+
+ return outColors;
+}
+
+#endif
diff --git a/effects/gradients/SkGradientShaderPriv.h b/effects/gradients/SkGradientShaderPriv.h
new file mode 100644
index 00000000..31cc9f26
--- /dev/null
+++ b/effects/gradients/SkGradientShaderPriv.h
@@ -0,0 +1,349 @@
+/*
+ * 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 SkGradientShaderPriv_DEFINED
+#define SkGradientShaderPriv_DEFINED
+
+#include "SkGradientShader.h"
+#include "SkClampRange.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMallocPixelRef.h"
+#include "SkUnitMapper.h"
+#include "SkUtils.h"
+#include "SkTemplates.h"
+#include "SkBitmapCache.h"
+#include "SkShader.h"
+
+static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
+ int count) {
+ if (count > 0) {
+ if (v0 == v1) {
+ sk_memset32(dst, v0, count);
+ } else {
+ int pairs = count >> 1;
+ for (int i = 0; i < pairs; i++) {
+ *dst++ = v0;
+ *dst++ = v1;
+ }
+ if (count & 1) {
+ *dst = v0;
+ }
+ }
+ }
+}
+
+// Clamp
+
+static inline SkFixed clamp_tileproc(SkFixed x) {
+ return SkClampMax(x, 0xFFFF);
+}
+
+// Repeat
+
+static inline SkFixed repeat_tileproc(SkFixed x) {
+ return x & 0xFFFF;
+}
+
+// Mirror
+
+// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
+// See http://code.google.com/p/skia/issues/detail?id=472
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", off)
+#endif
+
+static inline SkFixed mirror_tileproc(SkFixed x) {
+ int s = x << 15 >> 31;
+ return (x ^ s) & 0xFFFF;
+}
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", on)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef SkFixed (*TileProc)(SkFixed);
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const TileProc gTileProcs[] = {
+ clamp_tileproc,
+ repeat_tileproc,
+ mirror_tileproc
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkGradientShaderBase : public SkShader {
+public:
+ struct Descriptor {
+ Descriptor() {
+ sk_bzero(this, sizeof(*this));
+ fTileMode = SkShader::kClamp_TileMode;
+ }
+
+ const SkColor* fColors;
+ const SkScalar* fPos;
+ int fCount;
+ SkShader::TileMode fTileMode;
+ SkUnitMapper* fMapper;
+ uint32_t fFlags;
+ };
+
+public:
+ SkGradientShaderBase(const Descriptor& desc);
+ virtual ~SkGradientShaderBase();
+
+ 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;
+
+ void getGradientTableBitmap(SkBitmap*) const;
+
+ enum {
+ /// Seems like enough for visual accuracy. TODO: if pos[] deserves
+ /// it, use a larger cache.
+ kCache16Bits = 8,
+ kCache16Count = (1 << kCache16Bits),
+ kCache16Shift = 16 - kCache16Bits,
+ kSqrt16Shift = 8 - kCache16Bits,
+
+ /// Seems like enough for visual accuracy. TODO: if pos[] deserves
+ /// it, use a larger cache.
+ kCache32Bits = 8,
+ kCache32Count = (1 << kCache32Bits),
+ kCache32Shift = 16 - kCache32Bits,
+ kSqrt32Shift = 8 - kCache32Bits,
+
+ /// This value is used to *read* the dither cache; it may be 0
+ /// if dithering is disabled.
+ kDitherStride32 = kCache32Count,
+ kDitherStride16 = kCache16Count,
+ };
+
+
+protected:
+ SkGradientShaderBase(SkFlattenableReadBuffer& );
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+ SK_DEVELOPER_TO_STRING()
+
+ SkUnitMapper* fMapper;
+ SkMatrix fPtsToUnit; // set by subclass
+ SkMatrix fDstToIndex;
+ SkMatrix::MapXYProc fDstToIndexProc;
+ TileMode fTileMode;
+ TileProc fTileProc;
+ int fColorCount;
+ uint8_t fDstToIndexClass;
+ uint8_t fFlags;
+ uint8_t fGradFlags;
+
+ struct Rec {
+ SkFixed fPos; // 0...1
+ uint32_t fScale; // (1 << 24) / range
+ };
+ Rec* fRecs;
+
+ const uint16_t* getCache16() const;
+ const SkPMColor* getCache32() const;
+
+ void commonAsAGradient(GradientInfo*) const;
+
+private:
+ enum {
+ kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
+
+ kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
+ };
+ SkColor fStorage[(kStorageSize + 3) >> 2];
+ SkColor* fOrigColors; // original colors, before modulation by paint in setContext
+ bool fColorsAreOpaque;
+
+ mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
+ mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
+
+ mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
+ mutable SkMallocPixelRef* fCache32PixelRef;
+ mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
+
+ static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
+ static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
+ U8CPU alpha, uint32_t gradFlags);
+ void setCacheAlpha(U8CPU alpha) const;
+ void initCommon();
+
+ typedef SkShader INHERITED;
+};
+
+static inline int init_dither_toggle(int x, int y) {
+ x &= 1;
+ y = (y & 1) << 1;
+ return (x | y) * SkGradientShaderBase::kDitherStride32;
+}
+
+static inline int next_dither_toggle(int toggle) {
+ return toggle ^ SkGradientShaderBase::kDitherStride32;
+}
+
+static inline int init_dither_toggle16(int x, int y) {
+ return ((x ^ y) & 1) * SkGradientShaderBase::kDitherStride16;
+}
+
+static inline int next_dither_toggle16(int toggle) {
+ return toggle ^ SkGradientShaderBase::kDitherStride16;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+
+class GrEffectStage;
+class GrBackendEffectFactory;
+
+/*
+ * 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
+ * values.
+ *
+ * Normal SampleMode
+ * The post-matrix texture coordinates are in normalize space with (0,0) at
+ * the top-left and (1,1) at the bottom right.
+ * RadialGradient
+ * The matrix specifies the radial gradient parameters.
+ * (0,0) in the post-matrix space is center of the radial gradient.
+ * Radial2Gradient
+ * Matrix transforms to space where first circle is centered at the
+ * origin. The second circle will be centered (x, 0) where x may be
+ * 0 and is provided by setRadial2Params. The post-matrix space is
+ * normalized such that 1 is the second radius - first radius.
+ * SweepGradient
+ * The angle from the origin of texture coordinates in post-matrix space
+ * determines the gradient value.
+ */
+
+ class GrTextureStripAtlas;
+
+// Base class for Gr gradient effects
+class GrGradientEffect : public GrEffect {
+public:
+
+ GrGradientEffect(GrContext* ctx,
+ const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tileMode);
+
+ virtual ~GrGradientEffect();
+
+ bool useAtlas() const { return SkToBool(-1 != fRow); }
+ SkScalar getYCoord() const { return fYCoord; };
+ const SkMatrix& getMatrix() const { return fMatrix;}
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+protected:
+
+ /** Populates a pair of arrays with colors and stop info to construct a random gradient.
+ The function decides whether stop values should be used or not. The return value indicates
+ the number of colors, which will be capped by kMaxRandomGradientColors. colors should be
+ sized to be at least kMaxRandomGradientColors. stops is a pointer to an array of at least
+ size kMaxRandomGradientColors. It may be updated to NULL, indicating that NULL should be
+ passed to the gradient factory rather than the array.
+ */
+ static const int kMaxRandomGradientColors = 4;
+ static int RandomGradientParams(SkMWCRandom* r,
+ SkColor colors[kMaxRandomGradientColors],
+ SkScalar** stops,
+ SkShader::TileMode* tm);
+
+ virtual bool onIsEqual(const GrEffect& effect) const SK_OVERRIDE;
+
+private:
+
+ GrTextureAccess fTextureAccess;
+ SkScalar fYCoord;
+ GrTextureStripAtlas* fAtlas;
+ int fRow;
+ SkMatrix fMatrix;
+ bool fIsOpaque;
+
+ typedef GrEffect INHERITED;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class for GL gradient effects
+class GrGLGradientEffect : public GrGLEffect {
+public:
+ GrGLGradientEffect(const GrBackendEffectFactory& factory);
+ virtual ~GrGLGradientEffect();
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ /**
+ * Subclasses must reserve the lower kMatrixKeyBitCnt of their key for use by
+ * GrGLGradientEffect.
+ */
+ enum {
+ kMatrixKeyBitCnt = GrGLEffectMatrix::kKeyBits,
+ kMatrixKeyMask = (1 << kMatrixKeyBitCnt) - 1,
+ };
+
+ /**
+ * Subclasses must call this. It will return a value restricted to the lower kMatrixKeyBitCnt
+ * bits.
+ */
+ static EffectKey GenMatrixKey(const GrDrawEffect&);
+
+ /**
+ * 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** 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,
+ const char* inputColor,
+ const GrGLShaderBuilder::TextureSampler&);
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+
+ SkScalar fCachedYCoord;
+ GrGLUniformManager::UniformHandle fFSYUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+#endif
+
+#endif
diff --git a/effects/gradients/SkLinearGradient.cpp b/effects/gradients/SkLinearGradient.cpp
new file mode 100644
index 00000000..2f56cb49
--- /dev/null
+++ b/effects/gradients/SkLinearGradient.cpp
@@ -0,0 +1,568 @@
+
+/*
+ * 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 "SkLinearGradient.h"
+
+static inline int repeat_bits(int x, const int bits) {
+ return x & ((1 << bits) - 1);
+}
+
+static inline int repeat_8bits(int x) {
+ return x & 0xFF;
+}
+
+// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
+// See http://code.google.com/p/skia/issues/detail?id=472
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", off)
+#endif
+
+static inline int mirror_bits(int x, const int bits) {
+ if (x & (1 << bits)) {
+ x = ~x;
+ }
+ return x & ((1 << bits) - 1);
+}
+
+static inline int mirror_8bits(int x) {
+ if (x & 256) {
+ x = ~x;
+ }
+ return x & 255;
+}
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", on)
+#endif
+
+static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
+ SkVector vec = pts[1] - pts[0];
+ SkScalar mag = vec.length();
+ SkScalar inv = mag ? SkScalarInvert(mag) : 0;
+
+ vec.scale(inv);
+ matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
+ matrix->postTranslate(-pts[0].fX, -pts[0].fY);
+ matrix->postScale(inv, inv);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
+ : SkGradientShaderBase(desc)
+ , fStart(pts[0])
+ , fEnd(pts[1]) {
+ pts_to_unit_matrix(pts, &fPtsToUnit);
+}
+
+SkLinearGradient::SkLinearGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+ , fStart(buffer.readPoint())
+ , fEnd(buffer.readPoint()) {
+}
+
+void SkLinearGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fStart);
+ buffer.writePoint(fEnd);
+}
+
+bool SkLinearGradient::setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
+ if ((fDstToIndex.getType() & ~mask) == 0) {
+ // when we dither, we are (usually) not const-in-Y
+ if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
+ // only claim this if we do have a 16bit mode (i.e. none of our
+ // colors have alpha), and if we are not dithering (which obviously
+ // is not const in Y).
+ fFlags |= SkShader::kConstInY16_Flag;
+ }
+ }
+ return true;
+}
+
+#define NO_CHECK_ITER \
+ do { \
+ unsigned fi = fx >> SkGradientShaderBase::kCache32Shift; \
+ SkASSERT(fi <= 0xFF); \
+ fx += dx; \
+ *dstC++ = cache[toggle + fi]; \
+ toggle = next_dither_toggle(toggle); \
+ } while (0)
+
+namespace {
+
+typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
+ SkPMColor* dstC, const SkPMColor* cache,
+ int toggle, int count);
+
+// Linear interpolation (lerp) is unnecessary if there are no sharp
+// discontinuities in the gradient - which must be true if there are
+// only 2 colors - but it's cheap.
+void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ // We're a vertical gradient, so no change in a span.
+ // If colors change sharply across the gradient, dithering is
+ // insufficient (it subsamples the color space) and we need to lerp.
+ unsigned fullIndex = proc(fx);
+ unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift;
+ unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1);
+
+ int index0 = fi + toggle;
+ int index1 = index0;
+ if (fi < SkGradientShaderBase::kCache32Count - 1) {
+ index1 += 1;
+ }
+ SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
+ index0 ^= SkGradientShaderBase::kDitherStride32;
+ index1 ^= SkGradientShaderBase::kDitherStride32;
+ SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
+ sk_memset32_dither(dstC, lerp, dlerp, count);
+}
+
+void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkClampRange range;
+ range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
+
+ if ((count = range.fCount0) > 0) {
+ sk_memset32_dither(dstC,
+ cache[toggle + range.fV0],
+ cache[next_dither_toggle(toggle) + range.fV0],
+ count);
+ dstC += count;
+ }
+ if ((count = range.fCount1) > 0) {
+ int unroll = count >> 3;
+ fx = range.fFx1;
+ for (int i = 0; i < unroll; i++) {
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ }
+ if ((count &= 7) > 0) {
+ do {
+ NO_CHECK_ITER;
+ } while (--count != 0);
+ }
+ }
+ if ((count = range.fCount2) > 0) {
+ sk_memset32_dither(dstC,
+ cache[toggle + range.fV1],
+ cache[next_dither_toggle(toggle) + range.fV1],
+ count);
+ }
+}
+
+void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = mirror_8bits(fx >> 8);
+ SkASSERT(fi <= 0xFF);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle(toggle);
+ } while (--count != 0);
+}
+
+void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = repeat_8bits(fx >> 8);
+ SkASSERT(fi <= 0xFF);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle(toggle);
+ } while (--count != 0);
+}
+
+}
+
+void SkLinearGradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
+ int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed dxStorage[1];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
+ dx = dxStorage[0];
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = SkScalarToFixed(fDstToIndex.getScaleX());
+ }
+
+ LinearShadeProc shadeProc = shadeSpan_linear_repeat;
+ if (0 == dx) {
+ shadeProc = shadeSpan_linear_vertical_lerp;
+ } else if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_linear_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_linear_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
+ } else {
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.fX));
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+SkShader::BitmapType SkLinearGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode xy[]) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kLinear_BitmapType;
+}
+
+SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fStart;
+ info->fPoint[1] = fEnd;
+ }
+ return kLinear_GradientType;
+}
+
+static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
+ int count) {
+ if (reinterpret_cast<uintptr_t>(dst) & 2) {
+ *dst++ = value;
+ count -= 1;
+ SkTSwap(value, other);
+ }
+
+ sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
+
+ if (count & 1) {
+ dst[count - 1] = value;
+ }
+}
+
+#define NO_CHECK_ITER_16 \
+ do { \
+ unsigned fi = fx >> SkGradientShaderBase::kCache16Shift; \
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count); \
+ fx += dx; \
+ *dstC++ = cache[toggle + fi]; \
+ toggle = next_dither_toggle16(toggle); \
+ } while (0)
+
+namespace {
+
+typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* dstC, const uint16_t* cache,
+ int toggle, int count);
+
+void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ // we're a vertical gradient, so no change in a span
+ unsigned fi = proc(fx) >> SkGradientShaderBase::kCache16Shift;
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ dither_memset16(dstC, cache[toggle + fi],
+ cache[next_dither_toggle16(toggle) + fi], count);
+}
+
+void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkClampRange range;
+ range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
+
+ if ((count = range.fCount0) > 0) {
+ dither_memset16(dstC,
+ cache[toggle + range.fV0],
+ cache[next_dither_toggle16(toggle) + range.fV0],
+ count);
+ dstC += count;
+ }
+ if ((count = range.fCount1) > 0) {
+ int unroll = count >> 3;
+ fx = range.fFx1;
+ for (int i = 0; i < unroll; i++) {
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ }
+ if ((count &= 7) > 0) {
+ do {
+ NO_CHECK_ITER_16;
+ } while (--count != 0);
+ }
+ }
+ if ((count = range.fCount2) > 0) {
+ dither_memset16(dstC,
+ cache[toggle + range.fV1],
+ cache[next_dither_toggle16(toggle) + range.fV1],
+ count);
+ }
+}
+
+void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = mirror_bits(fx >> SkGradientShaderBase::kCache16Shift,
+ SkGradientShaderBase::kCache16Bits);
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+
+void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = repeat_bits(fx >> SkGradientShaderBase::kCache16Shift,
+ SkGradientShaderBase::kCache16Bits);
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+}
+
+void SkLinearGradient::shadeSpan16(int x, int y,
+ uint16_t* SK_RESTRICT dstC, int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed dxStorage[1];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
+ dx = dxStorage[0];
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = SkScalarToFixed(fDstToIndex.getScaleX());
+ }
+
+ LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
+ if (SkFixedNearlyZero(dx)) {
+ shadeProc = shadeSpan16_linear_vertical;
+ } else if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_linear_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_linear_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
+ } else {
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.fX));
+ SkASSERT(fi <= 0xFFFF);
+
+ int index = fi >> kCache16Shift;
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+/////////////////////////////////////////////////////////////////////
+
+class GrGLLinearGradient : public GrGLGradientEffect {
+public:
+
+ GrGLLinearGradient(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) { }
+
+ virtual ~GrGLLinearGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrLinearGradient : public GrGradientEffect {
+public:
+
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkLinearGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrLinearGradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrLinearGradient() { }
+
+ static const char* Name() { return "Linear Gradient"; }
+ const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrLinearGradient>::getInstance();
+ }
+
+ typedef GrGLLinearGradient GLEffect;
+
+private:
+ GrLinearGradient(GrContext* ctx,
+ const SkLinearGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) { }
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrLinearGradient);
+
+GrEffectRef* GrLinearGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint points[] = {{random->nextUScalar1(), random->nextUScalar1()},
+ {random->nextUScalar1(), random->nextUScalar1()}};
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(points,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLLinearGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t;
+ t.append(coords);
+ t.append(".x");
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkLinearGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrLinearGradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkLinearGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkLinearGradient::toString(SkString* str) const {
+ str->append("SkLinearGradient (");
+
+ str->appendf("start: (%f, %f)", fStart.fX, fStart.fY);
+ str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkLinearGradient.h b/effects/gradients/SkLinearGradient.h
new file mode 100644
index 00000000..24c6caba
--- /dev/null
+++ b/effects/gradients/SkLinearGradient.h
@@ -0,0 +1,38 @@
+
+/*
+ * 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 SkLinearGradient_DEFINED
+#define SkLinearGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkLinearGradient : public SkGradientShaderBase {
+public:
+ SkLinearGradient(const SkPoint pts[2], const Descriptor&);
+
+ virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+ 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 GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient)
+
+protected:
+ SkLinearGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fStart;
+ const SkPoint fEnd;
+};
+
+#endif
diff --git a/effects/gradients/SkRadialGradient.cpp b/effects/gradients/SkRadialGradient.cpp
new file mode 100644
index 00000000..ca659699
--- /dev/null
+++ b/effects/gradients/SkRadialGradient.cpp
@@ -0,0 +1,608 @@
+
+/*
+ * 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 "SkRadialGradient.h"
+#include "SkRadialGradient_Table.h"
+
+#define kSQRT_TABLE_BITS 11
+#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
+
+#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
+
+#include <stdio.h>
+
+void SkRadialGradient_BuildTable() {
+ // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
+
+ FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
+ SkASSERT(file);
+ ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
+
+ for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
+ if ((i & 15) == 0) {
+ ::fprintf(file, "\t");
+ }
+
+ uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
+
+ ::fprintf(file, "0x%02X", value);
+ if (i < kSQRT_TABLE_SIZE-1) {
+ ::fprintf(file, ", ");
+ }
+ if ((i & 15) == 15) {
+ ::fprintf(file, "\n");
+ }
+ }
+ ::fprintf(file, "};\n");
+ ::fclose(file);
+}
+
+#endif
+
+namespace {
+
+void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
+ SkMatrix* matrix) {
+ SkScalar inv = SkScalarInvert(radius);
+
+ matrix->setTranslate(-center.fX, -center.fY);
+ matrix->postScale(inv, inv);
+}
+
+typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* dstC, const uint16_t* cache,
+ int toggle, int count);
+
+void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+
+ /* knock these down so we can pin against +- 0x7FFF, which is an
+ immediate load, rather than 0xFFFF which is slower. This is a
+ compromise, since it reduces our precision, but that appears
+ to be visually OK. If we decide this is OK for all of our cases,
+ we could (it seems) put this scale-down into fDstToIndex,
+ to avoid having to do these extra shifts each time.
+ */
+ SkFixed fx = SkScalarToFixed(sfx) >> 1;
+ SkFixed dx = SkScalarToFixed(sdx) >> 1;
+ SkFixed fy = SkScalarToFixed(sfy) >> 1;
+ SkFixed dy = SkScalarToFixed(sdy) >> 1;
+ // might perform this check for the other modes,
+ // but the win will be a smaller % of the total
+ if (dy == 0) {
+ fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fy *= fy;
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ fx += dx;
+ *dstC++ = cache[toggle +
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+ } else {
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ fx += dx;
+ fy += dy;
+ *dstC++ = cache[toggle +
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+ }
+}
+
+void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+#ifdef SK_SCALAR_IS_FLOAT
+ float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
+ SkFixed dist = SkFloatToFixed(fdist);
+#else
+ SkFixed magnitudeSquared = SkFixedSquare(sfx) +
+ SkFixedSquare(sfy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+#endif
+ unsigned fi = mirror_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ sfx += sdx;
+ sfy += sdy;
+ } while (--count != 0);
+}
+
+void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkFixed fx = SkScalarToFixed(sfx);
+ SkFixed dx = SkScalarToFixed(sdx);
+ SkFixed fy = SkScalarToFixed(sfy);
+ SkFixed dy = SkScalarToFixed(sdy);
+ do {
+ SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+ unsigned fi = repeat_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ fx += dx;
+ fy += dy;
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter(center),
+ fRadius(radius)
+{
+ // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
+ SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
+
+ rad_to_unit_matrix(center, radius, &fPtsToUnit);
+}
+
+void SkRadialGradient::shadeSpan16(int x, int y, uint16_t* dstCParam,
+ int count) {
+ SkASSERT(count > 0);
+
+ uint16_t* SK_RESTRICT dstC = dstCParam;
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkScalar sdx = fDstToIndex.getScaleX();
+ SkScalar sdy = fDstToIndex.getSkewY();
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
+ &storage[0], &storage[1]);
+ sdx = SkFixedToScalar(storage[0]);
+ sdy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ }
+
+ RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_radial_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_radial_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
+ cache, toggle, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+ SkASSERT(fi <= 0xFFFF);
+
+ int index = fi >> (16 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix, SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ matrix->setScale(SkIntToScalar(kCache32Count),
+ SkIntToScalar(kCache32Count));
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kRadial_BitmapType;
+}
+
+SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter;
+ info->fRadius[0] = fRadius;
+ }
+ return kRadial_GradientType;
+}
+
+SkRadialGradient::SkRadialGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter(buffer.readPoint()),
+ fRadius(buffer.readScalar()) {
+}
+
+void SkRadialGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter);
+ buffer.writeScalar(fRadius);
+}
+
+namespace {
+
+inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
+ // fast, overly-conservative test: checks unit square instead
+ // of unit circle
+ bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
+ (fx <= -SK_FixedHalf && dx <= 0);
+ bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
+ (fy <= -SK_FixedHalf && dy <= 0);
+
+ return xClamped || yClamped;
+}
+
+// Return true if (fx * fy) is always inside the unit circle
+// SkPin32 is expensive, but so are all the SkFixedMul in this test,
+// so it shouldn't be run if count is small.
+inline bool no_need_for_radial_pin(int fx, int dx,
+ int fy, int dy, int count) {
+ SkASSERT(count > 0);
+ if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
+ return false;
+ }
+ if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
+ return false;
+ }
+ fx += (count - 1) * dx;
+ fy += (count - 1) * dy;
+ if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
+ return false;
+ }
+ return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
+}
+
+#define UNPINNED_RADIAL_STEP \
+ fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
+ *dstC++ = cache[toggle + \
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
+ toggle = next_dither_toggle(toggle); \
+ fx += dx; \
+ fy += dy;
+
+typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* dstC, const SkPMColor* cache,
+ int count, int toggle);
+
+// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
+void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ // Floating point seems to be slower than fixed point,
+ // even when we have float hardware.
+ const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+ SkFixed fx = SkScalarToFixed(sfx) >> 1;
+ SkFixed dx = SkScalarToFixed(sdx) >> 1;
+ SkFixed fy = SkScalarToFixed(sfy) >> 1;
+ SkFixed dy = SkScalarToFixed(sdy) >> 1;
+ if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
+ unsigned fi = SkGradientShaderBase::kCache32Count - 1;
+ sk_memset32_dither(dstC,
+ cache[toggle + fi],
+ cache[next_dither_toggle(toggle) + fi],
+ count);
+ } else if ((count > 4) &&
+ no_need_for_radial_pin(fx, dx, fy, dy, count)) {
+ unsigned fi;
+ // 4x unroll appears to be no faster than 2x unroll on Linux
+ while (count > 1) {
+ UNPINNED_RADIAL_STEP;
+ UNPINNED_RADIAL_STEP;
+ count -= 2;
+ }
+ if (count) {
+ UNPINNED_RADIAL_STEP;
+ }
+ } else {
+ // Specializing for dy == 0 gains us 25% on Skia benchmarks
+ if (dy == 0) {
+ unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ yy *= yy;
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ *dstC++ = cache[toggle + (sqrt_table[fi] >>
+ SkGradientShaderBase::kSqrt32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ } while (--count != 0);
+ } else {
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ *dstC++ = cache[toggle + (sqrt_table[fi] >>
+ SkGradientShaderBase::kSqrt32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ }
+}
+
+// Unrolling this loop doesn't seem to help (when float); we're stalling to
+// get the results of the sqrt (?), and don't have enough extra registers to
+// have many in flight.
+void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ do {
+#ifdef SK_SCALAR_IS_FLOAT
+ float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
+ SkFixed dist = SkFloatToFixed(fdist);
+#else
+ SkFixed magnitudeSquared = SkFixedSquare(sfx) +
+ SkFixedSquare(sfy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+#endif
+ unsigned fi = mirror_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ sfx += sdx;
+ sfy += sdy;
+ } while (--count != 0);
+}
+
+void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ SkFixed fx = SkScalarToFixed(sfx);
+ SkFixed dx = SkScalarToFixed(sdx);
+ SkFixed fy = SkScalarToFixed(sfy);
+ SkFixed dy = SkScalarToFixed(sdy);
+ do {
+ SkFixed magnitudeSquared = SkFixedSquare(fx) +
+ SkFixedSquare(fy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+ unsigned fi = repeat_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+}
+}
+
+void SkRadialGradient::shadeSpan(int x, int y,
+ SkPMColor* SK_RESTRICT dstC, int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar sdx = fDstToIndex.getScaleX();
+ SkScalar sdy = fDstToIndex.getSkewY();
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
+ &storage[0], &storage[1]);
+ sdx = SkFixedToScalar(storage[0]);
+ sdy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ }
+
+ RadialShadeProc shadeProc = shadeSpan_radial_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_radial_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_radial_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLRadialGradient : public GrGLGradientEffect {
+public:
+
+ GrGLRadialGradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&) : INHERITED (factory) { }
+ virtual ~GrGLRadialGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrRadialGradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrRadialGradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrRadialGradient() { }
+
+ static const char* Name() { return "Radial Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadialGradient>::getInstance();
+ }
+
+ typedef GrGLRadialGradient GLEffect;
+
+private:
+ GrRadialGradient(GrContext* ctx,
+ const SkRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) {
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrRadialGradient);
+
+GrEffectRef* GrRadialGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius = random->nextUScalar1();
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLRadialGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t("length(");
+ t.append(coords);
+ t.append(")");
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkRadialGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrRadialGradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkRadialGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkRadialGradient::toString(SkString* str) const {
+ str->append("SkRadialGradient: (");
+
+ str->append("center: (");
+ str->appendScalar(fCenter.fX);
+ str->append(", ");
+ str->appendScalar(fCenter.fY);
+ str->append(") radius: ");
+ str->appendScalar(fRadius);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkRadialGradient.h b/effects/gradients/SkRadialGradient.h
new file mode 100644
index 00000000..fa0a969c
--- /dev/null
+++ b/effects/gradients/SkRadialGradient.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.
+ */
+
+#ifndef SkRadialGradient_DEFINED
+#define SkRadialGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkRadialGradient : public SkGradientShaderBase {
+public:
+ SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&);
+ virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
+ SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient)
+
+protected:
+ SkRadialGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter;
+ const SkScalar fRadius;
+};
+
+#endif
diff --git a/effects/gradients/SkRadialGradient_Table.h b/effects/gradients/SkRadialGradient_Table.h
new file mode 100644
index 00000000..9a8a5f81
--- /dev/null
+++ b/effects/gradients/SkRadialGradient_Table.h
@@ -0,0 +1,139 @@
+
+/*
+ * 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.
+ */
+
+
+static const uint8_t gSqrt8Table[] = {
+ 0x00, 0x05, 0x08, 0x09, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x15,
+ 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F,
+ 0x20, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x25, 0x26, 0x26,
+ 0x27, 0x27, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2B, 0x2C, 0x2C, 0x2C,
+ 0x2D, 0x2D, 0x2D, 0x2E, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32,
+ 0x32, 0x32, 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37,
+ 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x3A, 0x3A, 0x3A, 0x3B, 0x3B, 0x3B,
+ 0x3B, 0x3C, 0x3C, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
+ 0x43, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x46, 0x47, 0x47,
+ 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4A, 0x4A, 0x4A, 0x4A,
+ 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4E,
+ 0x4E, 0x4E, 0x4E, 0x4E, 0x4F, 0x4F, 0x4F, 0x4F, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51,
+ 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54,
+ 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57,
+ 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5A, 0x5A,
+ 0x5A, 0x5A, 0x5A, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5D,
+ 0x5D, 0x5D, 0x5D, 0x5D, 0x5D, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62,
+ 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x65,
+ 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67,
+ 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
+ 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6C, 0x6C, 0x6C,
+ 0x6C, 0x6C, 0x6C, 0x6C, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E,
+ 0x6E, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
+ 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73,
+ 0x73, 0x73, 0x73, 0x73, 0x73, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75,
+ 0x75, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
+ 0x79, 0x79, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B,
+ 0x7B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D,
+ 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8B, 0x8B, 0x8B, 0x8B,
+ 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8D, 0x8D,
+ 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E,
+ 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B,
+ 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C,
+ 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9E, 0x9E,
+ 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F,
+ 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1,
+ 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA3,
+ 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6,
+ 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7,
+ 0xA7, 0xA7, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA9, 0xA9, 0xA9,
+ 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ 0xAA, 0xAA, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAC, 0xAC, 0xAC,
+ 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD,
+ 0xAD, 0xAD, 0xAD, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAF, 0xAF,
+ 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0,
+ 0xB0, 0xB0, 0xB0, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2,
+ 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3,
+ 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
+ 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6,
+ 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7,
+ 0xB7, 0xB7, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB9, 0xB9,
+ 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA,
+ 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+ 0xBB, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBD, 0xBD, 0xBD,
+ 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE,
+ 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC1, 0xC1, 0xC1,
+ 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2,
+ 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC5, 0xC5, 0xC5,
+ 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
+ 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC9,
+ 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xCA, 0xCA, 0xCA, 0xCA,
+ 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB,
+ 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+ 0xCC, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCE,
+ 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCF, 0xCF, 0xCF, 0xCF,
+ 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0,
+ 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1,
+ 0xD1, 0xD1, 0xD1, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2,
+ 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD4, 0xD4, 0xD4,
+ 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5,
+ 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6,
+ 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7,
+ 0xD7, 0xD7, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
+ 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA,
+ 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,
+ 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC,
+ 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE,
+ 0xDE, 0xDE, 0xDE, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE1,
+ 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2,
+ 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
+ 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5,
+ 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6,
+ 0xE6, 0xE6, 0xE6, 0xE6, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7,
+ 0xE7, 0xE7, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8,
+ 0xE8, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9,
+ 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEB, 0xEB,
+ 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEC, 0xEC, 0xEC,
+ 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xED, 0xED, 0xED, 0xED,
+ 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
+ 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF,
+ 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1,
+ 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+ 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3,
+ 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4,
+ 0xF4, 0xF4, 0xF4, 0xF4, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5,
+ 0xF5, 0xF5, 0xF5, 0xF5, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6,
+ 0xF6, 0xF6, 0xF6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7,
+ 0xF7, 0xF7, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
+ 0xF8, 0xF8, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9,
+ 0xF9, 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA,
+ 0xFA, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB,
+ 0xFB, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,
+ 0xFC, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
+ 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
diff --git a/effects/gradients/SkSweepGradient.cpp b/effects/gradients/SkSweepGradient.cpp
new file mode 100644
index 00000000..f975a188
--- /dev/null
+++ b/effects/gradients/SkSweepGradient.cpp
@@ -0,0 +1,517 @@
+
+/*
+ * 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 "SkSweepGradient.h"
+
+SkSweepGradient::SkSweepGradient(SkScalar cx, SkScalar cy,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc)
+ , fCenter(SkPoint::Make(cx, cy))
+{
+ fPtsToUnit.setTranslate(-cx, -cy);
+
+ // overwrite the tilemode to a canonical value (since sweep ignores it)
+ fTileMode = SkShader::kClamp_TileMode;
+}
+
+SkShader::BitmapType SkSweepGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix, SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ *matrix = fPtsToUnit;
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kSweep_BitmapType;
+}
+
+SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter;
+ }
+ return kSweep_GradientType;
+}
+
+SkSweepGradient::SkSweepGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter(buffer.readPoint()) {
+}
+
+void SkSweepGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter);
+}
+
+#ifndef SK_SCALAR_IS_FLOAT
+#ifdef COMPUTE_SWEEP_TABLE
+#define PI 3.14159265
+static bool gSweepTableReady;
+static uint8_t gSweepTable[65];
+
+/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
+ We scale the results to [0..32]
+*/
+static const uint8_t* build_sweep_table() {
+ if (!gSweepTableReady) {
+ const int N = 65;
+ const double DENOM = N - 1;
+
+ for (int i = 0; i < N; i++)
+ {
+ double arg = i / DENOM;
+ double v = atan(arg);
+ int iv = (int)round(v * DENOM * 2 / PI);
+// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
+ printf("%d, ", iv);
+ gSweepTable[i] = iv;
+ }
+ gSweepTableReady = true;
+ }
+ return gSweepTable;
+}
+#else
+static const uint8_t gSweepTable[] = {
+ 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
+ 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
+ 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
+ 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
+ 32
+};
+static const uint8_t* build_sweep_table() { return gSweepTable; }
+#endif
+#endif
+
+// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
+// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
+// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
+
+//unsigned div_64(int numer, int denom);
+#ifndef SK_SCALAR_IS_FLOAT
+static unsigned div_64(int numer, int denom) {
+ SkASSERT(numer <= denom);
+ SkASSERT(numer > 0);
+ SkASSERT(denom > 0);
+
+ int nbits = SkCLZ(numer);
+ int dbits = SkCLZ(denom);
+ int bits = 6 - nbits + dbits;
+ SkASSERT(bits <= 6);
+
+ if (bits < 0) { // detect underflow
+ return 0;
+ }
+
+ denom <<= dbits - 1;
+ numer <<= nbits - 1;
+
+ unsigned result = 0;
+
+ // do the first one
+ if ((numer -= denom) >= 0) {
+ result = 1;
+ } else {
+ numer += denom;
+ }
+
+ // Now fall into our switch statement if there are more bits to compute
+ if (bits > 0) {
+ // make room for the rest of the answer bits
+ result <<= bits;
+ switch (bits) {
+ case 6:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 32;
+ else
+ numer += denom;
+ case 5:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 16;
+ else
+ numer += denom;
+ case 4:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 8;
+ else
+ numer += denom;
+ case 3:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 4;
+ else
+ numer += denom;
+ case 2:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 2;
+ else
+ numer += denom;
+ case 1:
+ default: // not strictly need, but makes GCC make better ARM code
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 1;
+ else
+ numer += denom;
+ }
+ }
+ return result;
+}
+#endif
+
+// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
+#ifndef SK_SCALAR_IS_FLOAT
+static unsigned atan_0_90(SkFixed y, SkFixed x) {
+#ifdef SK_DEBUG
+ {
+ static bool gOnce;
+ if (!gOnce) {
+ gOnce = true;
+ SkASSERT(div_64(55, 55) == 64);
+ SkASSERT(div_64(128, 256) == 32);
+ SkASSERT(div_64(2326528, 4685824) == 31);
+ SkASSERT(div_64(753664, 5210112) == 9);
+ SkASSERT(div_64(229376, 4882432) == 3);
+ SkASSERT(div_64(2, 64) == 2);
+ SkASSERT(div_64(1, 64) == 1);
+ // test that we handle underflow correctly
+ SkASSERT(div_64(12345, 0x54321234) == 0);
+ }
+ }
+#endif
+
+ SkASSERT(y > 0 && x > 0);
+ const uint8_t* table = build_sweep_table();
+
+ unsigned result;
+ bool swap = (x < y);
+ if (swap) {
+ // first part of the atan(v) = PI/2 - atan(1/v) identity
+ // since our div_64 and table want v <= 1, where v = y/x
+ SkTSwap<SkFixed>(x, y);
+ }
+
+ result = div_64(y, x);
+
+#ifdef SK_DEBUG
+ {
+ unsigned result2 = SkDivBits(y, x, 6);
+ SkASSERT(result2 == result ||
+ (result == 1 && result2 == 0));
+ }
+#endif
+
+ SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
+ result = table[result];
+
+ if (swap) {
+ // complete the atan(v) = PI/2 - atan(1/v) identity
+ result = 64 - result;
+ // pin to 63
+ result -= result >> 6;
+ }
+
+ SkASSERT(result <= 63);
+ return result;
+}
+#endif
+
+// returns angle in a circle [0..2PI) -> [0..255]
+#ifdef SK_SCALAR_IS_FLOAT
+static unsigned SkATan2_255(float y, float x) {
+ // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
+ static const float g255Over2PI = 40.584510488433314f;
+
+ float result = sk_float_atan2(y, x);
+ if (result < 0) {
+ result += 2 * SK_ScalarPI;
+ }
+ SkASSERT(result >= 0);
+ // since our value is always >= 0, we can cast to int, which is faster than
+ // calling floorf()
+ int ir = (int)(result * g255Over2PI);
+ SkASSERT(ir >= 0 && ir <= 255);
+ return ir;
+}
+#else
+static unsigned SkATan2_255(SkFixed y, SkFixed x) {
+ if (x == 0) {
+ if (y == 0) {
+ return 0;
+ }
+ return y < 0 ? 192 : 64;
+ }
+ if (y == 0) {
+ return x < 0 ? 128 : 0;
+ }
+
+ /* Find the right quadrant for x,y
+ Since atan_0_90 only handles the first quadrant, we rotate x,y
+ appropriately before calling it, and then add the right amount
+ to account for the real quadrant.
+ quadrant 0 : add 0 | x > 0 && y > 0
+ quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
+ quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
+ quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
+
+ map x<0 to (1 << 6)
+ map y<0 to (3 << 6)
+ add = map_x ^ map_y
+ */
+ int xsign = x >> 31;
+ int ysign = y >> 31;
+ int add = ((-xsign) ^ (ysign & 3)) << 6;
+
+#ifdef SK_DEBUG
+ if (0 == add)
+ SkASSERT(x > 0 && y > 0);
+ else if (64 == add)
+ SkASSERT(x < 0 && y > 0);
+ else if (128 == add)
+ SkASSERT(x < 0 && y < 0);
+ else if (192 == add)
+ SkASSERT(x > 0 && y < 0);
+ else
+ SkDEBUGFAIL("bad value for add");
+#endif
+
+ /* This ^ trick makes x, y positive, and the swap<> handles quadrants
+ where we need to rotate x,y by 90 or -90
+ */
+ x = (x ^ xsign) - xsign;
+ y = (y ^ ysign) - ysign;
+ if (add & 64) { // quads 1 or 3 need to swap x,y
+ SkTSwap<SkFixed>(x, y);
+ }
+
+ unsigned result = add + atan_0_90(y, x);
+ SkASSERT(result < 256);
+ return result;
+}
+#endif
+
+void SkSweepGradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
+ int count) {
+ SkMatrix::MapXYProc proc = fDstToIndexProc;
+ const SkMatrix& matrix = fDstToIndex;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+ SkPoint srcPt;
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
+ &storage[0], &storage[1]);
+ dx = SkFixedToScalar(storage[0]);
+ dy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = matrix.getScaleX();
+ dy = matrix.getSkewY();
+ }
+
+ for (; count > 0; --count) {
+ *dstC++ = cache[toggle + SkATan2_255(fy, fx)];
+ fx += dx;
+ fy += dy;
+ toggle = next_dither_toggle(toggle);
+ }
+ } else { // perspective case
+ for (int stop = x + count; x < stop; x++) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ *dstC++ = cache[toggle + SkATan2_255(srcPt.fY, srcPt.fX)];
+ toggle = next_dither_toggle(toggle);
+ }
+ }
+}
+
+void SkSweepGradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
+ int count) {
+ SkMatrix::MapXYProc proc = fDstToIndexProc;
+ const SkMatrix& matrix = fDstToIndex;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+ SkPoint srcPt;
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
+ &storage[0], &storage[1]);
+ dx = SkFixedToScalar(storage[0]);
+ dy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = matrix.getScaleX();
+ dy = matrix.getSkewY();
+ }
+
+ for (; count > 0; --count) {
+ int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+ fx += dx;
+ fy += dy;
+ }
+ } else { // perspective case
+ for (int stop = x + count; x < stop; x++) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ int index = SkATan2_255(srcPt.fY, srcPt.fX);
+ index >>= (8 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLSweepGradient : public GrGLGradientEffect {
+public:
+
+ GrGLSweepGradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&) : INHERITED (factory) { }
+ virtual ~GrGLSweepGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrSweepGradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkSweepGradient& shader,
+ const SkMatrix& matrix) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSweepGradient, (ctx, shader, matrix)));
+ return CreateEffectRef(effect);
+ }
+ virtual ~GrSweepGradient() { }
+
+ static const char* Name() { return "Sweep Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrSweepGradient>::getInstance();
+ }
+
+ typedef GrGLSweepGradient GLEffect;
+
+private:
+ GrSweepGradient(GrContext* ctx,
+ const SkSweepGradient& shader,
+ const SkMatrix& matrix)
+ : INHERITED(ctx, shader, matrix, SkShader::kClamp_TileMode) { }
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrSweepGradient);
+
+GrEffectRef* GrSweepGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tmIgnored;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tmIgnored);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateSweep(center.fX, center.fY,
+ colors, stops, colorCount));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLSweepGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t;
+ t.printf("atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5", coords, coords);
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkSweepGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrSweepGradient::Create(context, *this, matrix);
+}
+
+#else
+
+GrEffectRef* SkSweepGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkSweepGradient::toString(SkString* str) const {
+ str->append("SkSweepGradient: (");
+
+ str->append("center: (");
+ str->appendScalar(fCenter.fX);
+ str->append(", ");
+ str->appendScalar(fCenter.fY);
+ str->append(") ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkSweepGradient.h b/effects/gradients/SkSweepGradient.h
new file mode 100644
index 00000000..8b685bc2
--- /dev/null
+++ b/effects/gradients/SkSweepGradient.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.
+ */
+
+#ifndef SkSweepGradient_DEFINED
+#define SkSweepGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkSweepGradient : public SkGradientShaderBase {
+public:
+ SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&);
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient)
+
+protected:
+ SkSweepGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter;
+};
+
+#endif
diff --git a/effects/gradients/SkTwoPointConicalGradient.cpp b/effects/gradients/SkTwoPointConicalGradient.cpp
new file mode 100644
index 00000000..37b49f0d
--- /dev/null
+++ b/effects/gradients/SkTwoPointConicalGradient.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 "SkTwoPointConicalGradient.h"
+
+static int valid_divide(float numer, float denom, float* ratio) {
+ SkASSERT(ratio);
+ if (0 == denom) {
+ return 0;
+ }
+ *ratio = numer / denom;
+ return 1;
+}
+
+// Return the number of distinct real roots, and write them into roots[] in
+// ascending order
+static int find_quad_roots(float A, float B, float C, float roots[2]) {
+ SkASSERT(roots);
+
+ if (A == 0) {
+ return valid_divide(-C, B, roots);
+ }
+
+ float R = B*B - 4*A*C;
+ if (R < 0) {
+ return 0;
+ }
+ R = sk_float_sqrt(R);
+
+#if 1
+ float Q = B;
+ if (Q < 0) {
+ Q -= R;
+ } else {
+ Q += R;
+ }
+#else
+ // on 10.6 this was much slower than the above branch :(
+ float Q = B + copysignf(R, B);
+#endif
+ Q *= -0.5f;
+ if (0 == Q) {
+ roots[0] = 0;
+ return 1;
+ }
+
+ float r0 = Q / A;
+ float r1 = C / Q;
+ roots[0] = r0 < r1 ? r0 : r1;
+ roots[1] = r0 > r1 ? r0 : r1;
+ return 2;
+}
+
+static float lerp(float x, float dx, float t) {
+ return x + t * dx;
+}
+
+static float sqr(float x) { return x * x; }
+
+void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
+ const SkPoint& center1, SkScalar rad1) {
+ fCenterX = SkScalarToFloat(center0.fX);
+ fCenterY = SkScalarToFloat(center0.fY);
+ fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
+ fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
+ fRadius = SkScalarToFloat(rad0);
+ fDRadius = SkScalarToFloat(rad1) - fRadius;
+
+ fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
+ fRadius2 = sqr(fRadius);
+ fRDR = fRadius * fDRadius;
+}
+
+void TwoPtRadial::setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy) {
+ fRelX = SkScalarToFloat(fx) - fCenterX;
+ fRelY = SkScalarToFloat(fy) - fCenterY;
+ fIncX = SkScalarToFloat(dfx);
+ fIncY = SkScalarToFloat(dfy);
+ fB = -2 * (fDCenterX * fRelX + fDCenterY * fRelY + fRDR);
+ fDB = -2 * (fDCenterX * fIncX + fDCenterY * fIncY);
+}
+
+SkFixed TwoPtRadial::nextT() {
+ float roots[2];
+
+ float C = sqr(fRelX) + sqr(fRelY) - fRadius2;
+ int countRoots = find_quad_roots(fA, fB, C, roots);
+
+ fRelX += fIncX;
+ fRelY += fIncY;
+ fB += fDB;
+
+ if (0 == countRoots) {
+ return kDontDrawT;
+ }
+
+ // Prefer the bigger t value if both give a radius(t) > 0
+ // find_quad_roots returns the values sorted, so we start with the last
+ float t = roots[countRoots - 1];
+ float r = lerp(fRadius, fDRadius, t);
+ if (r <= 0) {
+ t = roots[0]; // might be the same as roots[countRoots-1]
+ r = lerp(fRadius, fDRadius, t);
+ if (r <= 0) {
+ return kDontDrawT;
+ }
+ }
+ return SkFloatToFixed(t);
+}
+
+typedef void (*TwoPointConicalProc)(TwoPtRadial* rec, SkPMColor* dstC,
+ const SkPMColor* cache, int toggle, int count);
+
+static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = SkClampMax(t, 0xFFFF);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+static void twopoint_repeat(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = repeat_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+static void twopoint_mirror(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = mirror_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+void SkTwoPointConicalGradient::init() {
+ fRec.init(fCenter1, fRadius1, fCenter2, fRadius2);
+ fPtsToUnit.reset();
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkTwoPointConicalGradient::SkTwoPointConicalGradient(
+ const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter1(start),
+ fCenter2(end),
+ fRadius1(startRadius),
+ fRadius2(endRadius) {
+ // this is degenerate, and should be caught by our caller
+ SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
+ this->init();
+}
+
+bool SkTwoPointConicalGradient::isOpaque() const {
+ // Because areas outside the cone are left untouched, we cannot treat the
+ // shader as opaque even if the gradient itself is opaque.
+ // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
+ return false;
+}
+
+void SkTwoPointConicalGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) {
+ int toggle = init_dither_toggle(x, y);
+
+ SkASSERT(count > 0);
+
+ SkPMColor* SK_RESTRICT dstC = dstCParam;
+
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+
+ TwoPointConicalProc shadeProc = twopoint_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = twopoint_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = twopoint_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed fixedX, fixedY;
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+ dx = SkFixedToScalar(fixedX);
+ dy = SkFixedToScalar(fixedY);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = fDstToIndex.getScaleX();
+ dy = fDstToIndex.getSkewY();
+ }
+
+ fRec.setup(fx, fy, dx, dy);
+ (*shadeProc)(&fRec, dstC, cache, toggle, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ for (; count > 0; --count) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ dstX += SK_Scalar1;
+
+ fRec.setup(srcPt.fX, srcPt.fY, 0, 0);
+ (*shadeProc)(&fRec, dstC, cache, toggle, 1);
+ toggle = next_dither_toggle(toggle);
+ }
+ }
+}
+
+bool SkTwoPointConicalGradient::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ // we don't have a span16 proc
+ fFlags &= ~kHasSpan16_Flag;
+
+ // in general, we might discard based on computed-radius, so clear
+ // this flag (todo: sometimes we can detect that we never discard...)
+ fFlags &= ~kOpaqueAlpha_Flag;
+
+ return true;
+}
+
+SkShader::BitmapType SkTwoPointConicalGradient::asABitmap(
+ SkBitmap* bitmap, SkMatrix* matrix, SkShader::TileMode* xy) const {
+ SkPoint diff = fCenter2 - fCenter1;
+ SkScalar diffLen = 0;
+
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ diffLen = diff.length();
+ }
+ if (matrix) {
+ if (diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ // rotate to align circle centers with the x-axis
+ matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
+ SkScalarMul(invDiffLen, diff.fX));
+ } else {
+ matrix->reset();
+ }
+ matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kTwoPointConical_BitmapType;
+}
+
+SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
+ GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter1;
+ info->fPoint[1] = fCenter2;
+ info->fRadius[0] = fRadius1;
+ info->fRadius[1] = fRadius2;
+ }
+ return kConical_GradientType;
+}
+
+SkTwoPointConicalGradient::SkTwoPointConicalGradient(
+ SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter1(buffer.readPoint()),
+ fCenter2(buffer.readPoint()),
+ fRadius1(buffer.readScalar()),
+ fRadius2(buffer.readScalar()) {
+ this->init();
+};
+
+void SkTwoPointConicalGradient::flatten(
+ SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter1);
+ buffer.writePoint(fCenter2);
+ buffer.writeScalar(fRadius1);
+ buffer.writeScalar(fRadius2);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+class GrGLConical2Gradient : public GrGLGradientEffect {
+public:
+
+ GrGLConical2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffect&);
+ virtual ~GrGLConical2Gradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
+
+protected:
+
+ UniformHandle fVSParamUni;
+ UniformHandle fFSParamUni;
+
+ const char* fVSVaryingName;
+ const char* fFSVaryingName;
+
+ bool fIsDegenerate;
+
+ // @{
+ /// Values last uploaded as uniforms
+
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
+ SkScalar fCachedDiffRadius;
+
+ // @}
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrConical2Gradient : public GrGradientEffect {
+public:
+
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkTwoPointConicalGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrConical2Gradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrConical2Gradient() { }
+
+ static const char* Name() { return "Two-Point Conical Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrConical2Gradient>::getInstance();
+ }
+
+ // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
+ bool isDegenerate() const { return SkScalarAbs(fDiffRadius) == SkScalarAbs(fCenterX1); }
+ SkScalar center() const { return fCenterX1; }
+ SkScalar diffRadius() const { return fDiffRadius; }
+ SkScalar radius() const { return fRadius0; }
+
+ typedef GrGLConical2Gradient GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrConical2Gradient& s = CastEffect<GrConical2Gradient>(sBase);
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fCenterX1 == s.fCenterX1 &&
+ this->fRadius0 == s.fRadius0 &&
+ this->fDiffRadius == s.fDiffRadius);
+ }
+
+ GrConical2Gradient(GrContext* ctx,
+ const SkTwoPointConicalGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm)
+ , fCenterX1(shader.getCenterX1())
+ , fRadius0(shader.getStartRadius())
+ , fDiffRadius(shader.getDiffRadius()) { }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ // @{
+ // Cache of values - these can change arbitrarily, EXCEPT
+ // we shouldn't change between degenerate and non-degenerate?!
+
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
+ SkScalar fDiffRadius;
+
+ // @}
+
+ typedef GrGradientEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(GrConical2Gradient);
+
+GrEffectRef* GrConical2Gradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius1 = random->nextUScalar1();
+ SkPoint center2;
+ SkScalar radius2;
+ do {
+ 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);
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointConical(center1, radius1,
+ center2, radius2,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+
+/////////////////////////////////////////////////////////////////////
+
+GrGLConical2Gradient::GrGLConical2Gradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fVSParamUni(kInvalidUniformHandle)
+ , fFSParamUni(kInvalidUniformHandle)
+ , fVSVaryingName(NULL)
+ , fFSVaryingName(NULL)
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
+ , fCachedDiffRadius(-SK_ScalarMax) {
+
+ const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>();
+ fIsDegenerate = data.isDegenerate();
+}
+
+void GrGLConical2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, &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.
+ fVSParamUni = builder->addUniformArray(GrGLShaderBuilder::kVertex_ShaderType,
+ kFloat_GrSLType, "Conical2VSParams", 6);
+ fFSParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Conical2FSParams", 6);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ builder->addVarying(kFloat_GrSLType, "Conical2BCoeff",
+ &fVSVaryingName, &fFSVaryingName);
+ }
+
+ // VS
+ {
+ 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])
+ builder->vsCodeAppendf("\t%s = -2.0 * (%s * %s.x + %s * %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str(), p5.c_str());
+ }
+ }
+
+ // FS
+ {
+
+ 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";
+ builder->fsCodeAppendf("\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)
+ builder->fsCodeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor);
+
+ // c = (x^2)+(y^2) - params[4]
+ builder->fsCodeAppendf("\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
+ builder->fsCodeAppendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_str(),
+ cName.c_str());
+
+ // d = b^2 - ac4
+ builder->fsCodeAppendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(),
+ bVar.c_str(), bVar.c_str(), ac4Name.c_str());
+
+ // only proceed if discriminant is >= 0
+ builder->fsCodeAppendf("\tif (%s >= 0.0) {\n", dName.c_str());
+
+ // intermediate value we'll use to compute the roots
+ // q = -0.5 * (b +/- sqrt(d))
+ builder->fsCodeAppendf("\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]
+ builder->fsCodeAppendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(),
+ qName.c_str(), p1.c_str());
+ // r1 = c / q
+ builder->fsCodeAppendf("\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:
+ builder->fsCodeAppendf("\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
+ builder->fsCodeAppendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+
+ builder->fsCodeAppend("\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
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppendf("\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
+ builder->fsCodeAppendf("\t\t\tif (%s * %s + %s > 0.0) {\n",
+ tName.c_str(), p5.c_str(), p3.c_str());
+
+ builder->fsCodeAppend("\t\t\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+
+ // end if (r(t) > 0) for smaller root
+ builder->fsCodeAppend("\t\t\t}\n");
+ // end if (r(t) > 0), else, for larger root
+ builder->fsCodeAppend("\t\t}\n");
+ // end if (discriminant >= 0)
+ builder->fsCodeAppend("\t}\n");
+ } else {
+
+ // linear case: t = -c/b
+ builder->fsCodeAppendf("\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
+ builder->fsCodeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+ builder->fsCodeAppend("\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+ builder->fsCodeAppend("\t}\n");
+ }
+ }
+}
+
+void GrGLConical2Gradient::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>();
+ GrAssert(data.isDegenerate() == fIsDegenerate);
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
+ SkScalar diffRadius = data.diffRadius();
+
+ if (fCachedCenter != centerX1 ||
+ fCachedRadius != radius0 ||
+ fCachedDiffRadius != 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
+ // use the same 6 uniforms even though we don't need them
+ // all in the linear case just to keep the code complexity
+ // down).
+ float values[6] = {
+ SkScalarToFloat(a * 4),
+ 1.f / (SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
+ SkScalarToFloat(diffRadius)
+ };
+
+ uman.set1fv(fVSParamUni, 0, 6, values);
+ uman.set1fv(fFSParamUni, 0, 6, values);
+ fCachedCenter = centerX1;
+ fCachedRadius = radius0;
+ fCachedDiffRadius = diffRadius;
+ }
+}
+
+GrGLEffect::EffectKey GrGLConical2Gradient::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(drawEffect);
+ if (drawEffect.castEffect<GrConical2Gradient>().isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ SkASSERT(fPtsToUnit.isIdentity());
+ // invert the localM, translate to center1, rotate so center2 is on x axis.
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postTranslate(-fCenter1.fX, -fCenter1.fY);
+
+ SkPoint diff = fCenter2 - fCenter1;
+ SkScalar diffLen = diff.length();
+ if (0 != diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ SkMatrix rot;
+ rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
+ SkScalarMul(invDiffLen, diff.fX));
+ matrix.postConcat(rot);
+ }
+
+ return GrConical2Gradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkTwoPointConicalGradient::toString(SkString* str) const {
+ str->append("SkTwoPointConicalGradient: (");
+
+ str->append("center1: (");
+ str->appendScalar(fCenter1.fX);
+ str->append(", ");
+ str->appendScalar(fCenter1.fY);
+ str->append(") radius1: ");
+ str->appendScalar(fRadius1);
+ str->append(" ");
+
+ str->append("center2: (");
+ str->appendScalar(fCenter2.fX);
+ str->append(", ");
+ str->appendScalar(fCenter2.fY);
+ str->append(") radius2: ");
+ str->appendScalar(fRadius2);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkTwoPointConicalGradient.h b/effects/gradients/SkTwoPointConicalGradient.h
new file mode 100644
index 00000000..1358f0b2
--- /dev/null
+++ b/effects/gradients/SkTwoPointConicalGradient.h
@@ -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.
+ */
+
+ #ifndef SkTwoPointConicalGradient_DEFINED
+ #define SkTwoPointConicalGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+struct TwoPtRadial {
+ enum {
+ kDontDrawT = 0x80000000
+ };
+
+ float fCenterX, fCenterY;
+ float fDCenterX, fDCenterY;
+ float fRadius;
+ float fDRadius;
+ float fA;
+ float fRadius2;
+ float fRDR;
+
+ void init(const SkPoint& center0, SkScalar rad0,
+ const SkPoint& center1, SkScalar rad1);
+
+ // used by setup and nextT
+ float fRelX, fRelY, fIncX, fIncY;
+ float fB, fDB;
+
+ void setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy);
+ SkFixed nextT();
+
+ static bool DontDrawT(SkFixed t) {
+ return kDontDrawT == (uint32_t)t;
+ }
+};
+
+
+class SkTwoPointConicalGradient : public SkGradientShaderBase {
+ TwoPtRadial fRec;
+ void init();
+
+public:
+ SkTwoPointConicalGradient(const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor&);
+
+ virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) SK_OVERRIDE;
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const;
+ virtual SkShader::GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint& paint) const SK_OVERRIDE;
+ virtual bool isOpaque() const SK_OVERRIDE;
+
+ SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
+ SkScalar getStartRadius() const { return fRadius1; }
+ SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointConicalGradient)
+
+protected:
+ SkTwoPointConicalGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter1;
+ const SkPoint fCenter2;
+ const SkScalar fRadius1;
+ const SkScalar fRadius2;
+};
+
+#endif
diff --git a/effects/gradients/SkTwoPointRadialGradient.cpp b/effects/gradients/SkTwoPointRadialGradient.cpp
new file mode 100644
index 00000000..989c1395
--- /dev/null
+++ b/effects/gradients/SkTwoPointRadialGradient.cpp
@@ -0,0 +1,717 @@
+
+/*
+ * 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 "SkTwoPointRadialGradient.h"
+
+/* Two-point radial gradients are specified by two circles, each with a center
+ point and radius. The gradient can be considered to be a series of
+ concentric circles, with the color interpolated from the start circle
+ (at t=0) to the end circle (at t=1).
+
+ For each point (x, y) in the span, we want to find the
+ interpolated circle that intersects that point. The center
+ of the desired circle (Cx, Cy) falls at some distance t
+ along the line segment between the start point (Sx, Sy) and
+ end point (Ex, Ey):
+
+ Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
+ Cy = (1 - t) * Sy + t * Ey
+
+ The radius of the desired circle (r) is also a linear interpolation t
+ between the start and end radii (Sr and Er):
+
+ r = (1 - t) * Sr + t * Er
+
+ But
+
+ (x - Cx)^2 + (y - Cy)^2 = r^2
+
+ so
+
+ (x - ((1 - t) * Sx + t * Ex))^2
+ + (y - ((1 - t) * Sy + t * Ey))^2
+ = ((1 - t) * Sr + t * Er)^2
+
+ Solving for t yields
+
+ [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
+ + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
+ + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
+
+ To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
+
+ [Dx^2 + Dy^2 - Dr^2)] * t^2
+ + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
+ + [dx^2 + dy^2 - Sr^2] = 0
+
+ A quadratic in t. The two roots of the quadratic reflect the two
+ possible circles on which the point may fall. Solving for t yields
+ the gradient value to use.
+
+ If a<0, the start circle is entirely contained in the
+ end circle, and one of the roots will be <0 or >1 (off the line
+ segment). If a>0, the start circle falls at least partially
+ outside the end circle (or vice versa), and the gradient
+ defines a "tube" where a point may be on one circle (on the
+ inside of the tube) or the other (outside of the tube). We choose
+ one arbitrarily.
+
+ In order to keep the math to within the limits of fixed point,
+ we divide the entire quadratic by Dr^2, and replace
+ (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
+
+ [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
+ + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
+ + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
+
+ (x' and y' are computed by appending the subtract and scale to the
+ fDstToIndex matrix in the constructor).
+
+ Since the 'A' component of the quadratic is independent of x' and y', it
+ is precomputed in the constructor. Since the 'B' component is linear in
+ x' and y', if x and y are linear in the span, 'B' can be computed
+ incrementally with a simple delta (db below). If it is not (e.g.,
+ a perspective projection), it must be computed in the loop.
+
+*/
+
+namespace {
+
+inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
+ SkScalar sr2d2, SkScalar foura,
+ SkScalar oneOverTwoA, bool posRoot) {
+ SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
+ if (0 == foura) {
+ return SkScalarToFixed(SkScalarDiv(-c, b));
+ }
+
+ SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
+ if (discrim < 0) {
+ discrim = -discrim;
+ }
+ SkScalar rootDiscrim = SkScalarSqrt(discrim);
+ SkScalar result;
+ if (posRoot) {
+ result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
+ } else {
+ result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
+ }
+ return SkScalarToFixed(result);
+}
+
+typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count);
+
+void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = SkClampMax(t, 0xFFFF);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = mirror_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+
+void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = repeat_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkTwoPointRadialGradient::SkTwoPointRadialGradient(
+ const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter1(start),
+ fCenter2(end),
+ fRadius1(startRadius),
+ fRadius2(endRadius) {
+ init();
+}
+
+SkShader::BitmapType SkTwoPointRadialGradient::asABitmap(
+ SkBitmap* bitmap,
+ SkMatrix* matrix,
+ SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ SkScalar diffL = 0; // just to avoid gcc warning
+ if (matrix) {
+ diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
+ SkScalarSquare(fDiff.fY));
+ }
+ if (matrix) {
+ if (diffL) {
+ SkScalar invDiffL = SkScalarInvert(diffL);
+ matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
+ SkScalarMul(invDiffL, fDiff.fX));
+ } else {
+ matrix->reset();
+ }
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kTwoPointRadial_BitmapType;
+}
+
+SkShader::GradientType SkTwoPointRadialGradient::asAGradient(
+ SkShader::GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter1;
+ info->fPoint[1] = fCenter2;
+ info->fRadius[0] = fRadius1;
+ info->fRadius[1] = fRadius2;
+ }
+ return kRadial2_GradientType;
+}
+
+void SkTwoPointRadialGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) {
+ SkASSERT(count > 0);
+
+ SkPMColor* SK_RESTRICT dstC = dstCParam;
+
+ // Zero difference between radii: fill with transparent black.
+ if (fDiffRadius == 0) {
+ sk_bzero(dstC, count * sizeof(*dstC));
+ return;
+ }
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+
+ SkScalar foura = fA * 4;
+ bool posRoot = fDiffRadius < 0;
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed fixedX, fixedY;
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+ dx = SkFixedToScalar(fixedX);
+ dy = SkFixedToScalar(fixedY);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = fDstToIndex.getScaleX();
+ dy = fDstToIndex.getSkewY();
+ }
+ SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+ SkScalar db = (SkScalarMul(fDiff.fX, dx) +
+ SkScalarMul(fDiff.fY, dy)) * 2;
+
+ TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_twopoint_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_twopoint_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(fx, dx, fy, dy, b, db,
+ fSr2D2, foura, fOneOverTwoA, posRoot,
+ dstC, cache, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ for (; count > 0; --count) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ SkScalar fx = srcPt.fX;
+ SkScalar fy = srcPt.fY;
+ SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = proc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ dstX += SK_Scalar1;
+ }
+ }
+}
+
+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;
+ }
+
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ // we don't have a span16 proc
+ fFlags &= ~kHasSpan16_Flag;
+ return true;
+}
+
+#ifdef SK_DEVELOPER
+void SkTwoPointRadialGradient::toString(SkString* str) const {
+ str->append("SkTwoPointRadialGradient: (");
+
+ str->append("center1: (");
+ str->appendScalar(fCenter1.fX);
+ str->append(", ");
+ str->appendScalar(fCenter1.fY);
+ str->append(") radius1: ");
+ str->appendScalar(fRadius1);
+ str->append(" ");
+
+ str->append("center2: (");
+ str->appendScalar(fCenter2.fX);
+ str->append(", ");
+ str->appendScalar(fCenter2.fY);
+ str->append(") radius2: ");
+ str->appendScalar(fRadius2);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+SkTwoPointRadialGradient::SkTwoPointRadialGradient(
+ SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter1(buffer.readPoint()),
+ fCenter2(buffer.readPoint()),
+ fRadius1(buffer.readScalar()),
+ fRadius2(buffer.readScalar()) {
+ init();
+};
+
+void SkTwoPointRadialGradient::flatten(
+ SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter1);
+ buffer.writePoint(fCenter2);
+ buffer.writeScalar(fRadius1);
+ buffer.writeScalar(fRadius2);
+}
+
+void SkTwoPointRadialGradient::init() {
+ fDiff = fCenter1 - fCenter2;
+ fDiffRadius = fRadius2 - fRadius1;
+ // hack to avoid zero-divide for now
+ SkScalar inv = fDiffRadius ? SkScalarInvert(fDiffRadius) : 0;
+ fDiff.fX = SkScalarMul(fDiff.fX, inv);
+ fDiff.fY = SkScalarMul(fDiff.fY, inv);
+ fStartRadius = SkScalarMul(fRadius1, inv);
+ fSr2D2 = SkScalarSquare(fStartRadius);
+ fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
+ fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
+
+ fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
+ fPtsToUnit.postScale(inv, inv);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+class GrGLRadial2Gradient : public GrGLGradientEffect {
+
+public:
+
+ GrGLRadial2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffect&);
+ virtual ~GrGLRadial2Gradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
+
+protected:
+
+ UniformHandle fVSParamUni;
+ UniformHandle fFSParamUni;
+
+ const char* fVSVaryingName;
+ const char* fFSVaryingName;
+
+ bool fIsDegenerate;
+
+ // @{
+ /// Values last uploaded as uniforms
+
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
+ bool fCachedPosRoot;
+
+ // @}
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrRadial2Gradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkTwoPointRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrRadial2Gradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrRadial2Gradient() { }
+
+ static const char* Name() { return "Two-Point Radial Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadial2Gradient>::getInstance();
+ }
+
+ // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
+ 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 GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrRadial2Gradient& s = CastEffect<GrRadial2Gradient>(sBase);
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fCenterX1 == s.fCenterX1 &&
+ this->fRadius0 == s.fRadius0 &&
+ this->fPosRoot == s.fPosRoot);
+ }
+
+ 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) { }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ // @{
+ // Cache of values - these can change arbitrarily, EXCEPT
+ // we shouldn't change between degenerate and non-degenerate?!
+
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
+ SkBool8 fPosRoot;
+
+ // @}
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrRadial2Gradient);
+
+GrEffectRef* GrRadial2Gradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius1 = random->nextUScalar1();
+ SkPoint center2;
+ SkScalar radius2;
+ do {
+ center2.set(random->nextUScalar1(), random->nextUScalar1());
+ radius2 = random->nextUScalar1 ();
+ // There is a bug in two point radial gradients with identical radii
+ } while (radius1 == radius2);
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointRadial(center1, radius1,
+ center2, radius2,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrGLRadial2Gradient::GrGLRadial2Gradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fVSParamUni(kInvalidUniformHandle)
+ , fFSParamUni(kInvalidUniformHandle)
+ , fVSVaryingName(NULL)
+ , fFSVaryingName(NULL)
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
+ , fCachedPosRoot(0) {
+
+ const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
+ fIsDegenerate = data.isDegenerate();
+}
+
+void GrGLRadial2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ this->emitYCoordUniform(builder);
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, &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.
+ fVSParamUni = builder->addUniformArray(GrGLShaderBuilder::kVertex_ShaderType,
+ kFloat_GrSLType, "Radial2VSParams", 6);
+ fFSParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Radial2FSParams", 6);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ builder->addVarying(kFloat_GrSLType, "Radial2BCoeff", &fVSVaryingName, &fFSVaryingName);
+ }
+
+ // VS
+ {
+ 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])
+ builder->vsCodeAppendf("\t%s = 2.0 *(%s * %s.x - %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str());
+ }
+ }
+
+ // FS
+ {
+ 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";
+ builder->fsCodeAppendf("\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]
+ builder->fsCodeAppendf("\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
+ builder->fsCodeAppendf("\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)
+ builder->fsCodeAppendf("\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]);
+ }
+}
+
+void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
+ GrAssert(data.isDegenerate() == fIsDegenerate);
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
+ if (fCachedCenter != centerX1 ||
+ fCachedRadius != radius0 ||
+ fCachedPosRoot != data.isPosRoot()) {
+
+ 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
+ // use the same 6 uniforms even though we don't need them
+ // all in the linear case just to keep the code complexity
+ // down).
+ float values[6] = {
+ SkScalarToFloat(a),
+ 1 / (2.f * SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
+ data.isPosRoot() ? 1.f : -1.f
+ };
+
+ uman.set1fv(fVSParamUni, 0, 6, values);
+ uman.set1fv(fFSParamUni, 0, 6, values);
+ fCachedCenter = centerX1;
+ fCachedRadius = radius0;
+ fCachedPosRoot = data.isPosRoot();
+ }
+}
+
+GrGLEffect::EffectKey GrGLRadial2Gradient::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(drawEffect);
+ if (drawEffect.castEffect<GrRadial2Gradient>().isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ // invert the localM, translate to center1 (fPtsToUni), rotate so center2 is on x axis.
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+
+ 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);
+ }
+
+ return GrRadial2Gradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
diff --git a/effects/gradients/SkTwoPointRadialGradient.h b/effects/gradients/SkTwoPointRadialGradient.h
new file mode 100644
index 00000000..444c23dd
--- /dev/null
+++ b/effects/gradients/SkTwoPointRadialGradient.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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 SkTwoPointRadialGradient_DEFINED
+ #define SkTwoPointRadialGradient_DEFINED
+
+ #include "SkGradientShaderPriv.h"
+
+class SkTwoPointRadialGradient : public SkGradientShaderBase {
+public:
+ SkTwoPointRadialGradient(const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor&);
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) SK_OVERRIDE;
+
+ SkScalar getCenterX1() const { return fDiff.length(); }
+ SkScalar getStartRadius() const { return fStartRadius; }
+ SkScalar getDiffRadius() const { return fDiffRadius; }
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointRadialGradient)
+
+protected:
+ SkTwoPointRadialGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter1;
+ const SkPoint fCenter2;
+ const SkScalar fRadius1;
+ const SkScalar fRadius2;
+ SkPoint fDiff;
+ SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
+
+ void init();
+};
+
+#endif
diff --git a/fonts/SkFontMgr_fontconfig.cpp b/fonts/SkFontMgr_fontconfig.cpp
new file mode 100644
index 00000000..367faf85
--- /dev/null
+++ b/fonts/SkFontMgr_fontconfig.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFontMgr.h"
+#include "SkFontStyle.h"
+#include "SkFontConfigInterface.h"
+#include "SkFontConfigTypeface.h"
+#include "SkMath.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+
+// for now we pull these in directly. eventually we will solely rely on the
+// SkFontConfigInterface instance.
+#include <fontconfig/fontconfig.h>
+#include <unistd.h>
+
+// borrow this global from SkFontHost_fontconfig. eventually that file should
+// go away, and be replaced with this one.
+extern SkFontConfigInterface* SkFontHost_fontconfig_ref_global();
+static SkFontConfigInterface* RefFCI() {
+ return SkFontHost_fontconfig_ref_global();
+}
+
+// look for the last substring after a '/' and return that, or return null.
+static const char* find_just_name(const char* str) {
+ const char* last = strrchr(str, '/');
+ return last ? last + 1 : NULL;
+}
+
+static bool is_lower(char c) {
+ return c >= 'a' && c <= 'z';
+}
+
+static int get_int(FcPattern* pattern, const char field[]) {
+ int value;
+ if (FcPatternGetInteger(pattern, field, 0, &value) != FcResultMatch) {
+ value = SK_MinS32;
+ }
+ return value;
+}
+
+static const char* get_name(FcPattern* pattern, const char field[]) {
+ const char* name;
+ if (FcPatternGetString(pattern, field, 0, (FcChar8**)&name) != FcResultMatch) {
+ name = "";
+ }
+ return name;
+}
+
+static bool valid_pattern(FcPattern* pattern) {
+ FcBool is_scalable;
+ if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &is_scalable) != FcResultMatch || !is_scalable) {
+ return false;
+ }
+
+ // fontconfig can also return fonts which are unreadable
+ const char* c_filename = get_name(pattern, FC_FILE);
+ if (0 == *c_filename) {
+ return false;
+ }
+ if (access(c_filename, R_OK) != 0) {
+ return false;
+ }
+ return true;
+}
+
+static bool match_name(FcPattern* pattern, const char family_name[]) {
+ return !strcasecmp(family_name, get_name(pattern, FC_FAMILY));
+}
+
+static FcPattern** MatchFont(FcFontSet* font_set,
+ const char post_config_family[],
+ int* count) {
+ // Older versions of fontconfig have a bug where they cannot select
+ // only scalable fonts so we have to manually filter the results.
+
+ FcPattern** iter = font_set->fonts;
+ FcPattern** stop = iter + font_set->nfont;
+ // find the first good match
+ for (; iter < stop; ++iter) {
+ if (valid_pattern(*iter)) {
+ break;
+ }
+ }
+
+ if (iter == stop || !match_name(*iter, post_config_family)) {
+ return NULL;
+ }
+
+ FcPattern** firstIter = iter++;
+ for (; iter < stop; ++iter) {
+ if (!valid_pattern(*iter) || !match_name(*iter, post_config_family)) {
+ break;
+ }
+ }
+
+ *count = iter - firstIter;
+ return firstIter;
+}
+
+class SkFontStyleSet_FC : public SkFontStyleSet {
+public:
+ SkFontStyleSet_FC(FcPattern** matches, int count);
+ virtual ~SkFontStyleSet_FC();
+
+ virtual int count() SK_OVERRIDE { return fRecCount; }
+ virtual void getStyle(int index, SkFontStyle*, SkString* style) SK_OVERRIDE;
+ virtual SkTypeface* createTypeface(int index) SK_OVERRIDE;
+ virtual SkTypeface* matchStyle(const SkFontStyle& pattern) SK_OVERRIDE;
+
+private:
+ struct Rec {
+ SkString fStyleName;
+ SkString fFileName;
+ SkFontStyle fStyle;
+ };
+ Rec* fRecs;
+ int fRecCount;
+};
+
+static int map_range(int value,
+ int old_min, int old_max, int new_min, int new_max) {
+ SkASSERT(old_min < old_max);
+ SkASSERT(new_min < new_max);
+ return new_min + SkMulDiv(value - old_min,
+ new_max - new_min, old_max - old_min);
+}
+
+static SkFontStyle make_fontconfig_style(FcPattern* match) {
+ int weight = get_int(match, FC_WEIGHT);
+ int width = get_int(match, FC_WIDTH);
+ int slant = get_int(match, FC_SLANT);
+// SkDebugf("old weight %d new weight %d\n", weight, map_range(weight, 0, 80, 0, 400));
+
+ // fontconfig weight seems to be 0..200 or so, so we remap it here
+ weight = map_range(weight, 0, 80, 0, 400);
+ width = map_range(width, 0, 200, 0, 9);
+ return SkFontStyle(weight, width, slant > 0 ? SkFontStyle::kItalic_Slant
+ : SkFontStyle::kUpright_Slant);
+}
+
+SkFontStyleSet_FC::SkFontStyleSet_FC(FcPattern** matches, int count) {
+ fRecCount = count;
+ fRecs = SkNEW_ARRAY(Rec, count);
+ for (int i = 0; i < count; ++i) {
+ fRecs[i].fStyleName.set(get_name(matches[i], FC_STYLE));
+ fRecs[i].fFileName.set(get_name(matches[i], FC_FILE));
+ fRecs[i].fStyle = make_fontconfig_style(matches[i]);
+ }
+}
+
+SkFontStyleSet_FC::~SkFontStyleSet_FC() {
+ SkDELETE_ARRAY(fRecs);
+}
+
+void SkFontStyleSet_FC::getStyle(int index, SkFontStyle* style,
+ SkString* styleName) {
+ SkASSERT((unsigned)index < (unsigned)fRecCount);
+ if (style) {
+ *style = fRecs[index].fStyle;
+ }
+ if (styleName) {
+ *styleName = fRecs[index].fStyleName;
+ }
+}
+
+SkTypeface* SkFontStyleSet_FC::createTypeface(int index) {
+ return NULL;
+}
+
+SkTypeface* SkFontStyleSet_FC::matchStyle(const SkFontStyle& pattern) {
+ return NULL;
+}
+
+class SkFontMgr_fontconfig : public SkFontMgr {
+ SkAutoTUnref<SkFontConfigInterface> fFCI;
+ SkDataTable* fFamilyNames;
+
+ void init() {
+ if (!fFamilyNames) {
+ fFamilyNames = fFCI->getFamilyNames();
+ }
+ }
+
+public:
+ SkFontMgr_fontconfig(SkFontConfigInterface* fci)
+ : fFCI(fci)
+ , fFamilyNames(NULL) {}
+
+ virtual ~SkFontMgr_fontconfig() {
+ SkSafeUnref(fFamilyNames);
+ }
+
+protected:
+ virtual int onCountFamilies() {
+ this->init();
+ return fFamilyNames->count();
+ }
+
+ virtual void onGetFamilyName(int index, SkString* familyName) {
+ this->init();
+ familyName->set(fFamilyNames->atStr(index));
+ }
+
+ virtual SkFontStyleSet* onCreateStyleSet(int index) {
+ this->init();
+ return this->onMatchFamily(fFamilyNames->atStr(index));
+ }
+
+ virtual SkFontStyleSet* onMatchFamily(const char familyName[]) {
+ this->init();
+
+ FcPattern* pattern = FcPatternCreate();
+
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
+#if 0
+ FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
+#endif
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+
+ const char* post_config_family = get_name(pattern, FC_FAMILY);
+
+ FcResult result;
+ FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
+ if (!font_set) {
+ FcPatternDestroy(pattern);
+ return NULL;
+ }
+
+ int count;
+ FcPattern** match = MatchFont(font_set, post_config_family, &count);
+ if (!match) {
+ FcPatternDestroy(pattern);
+ FcFontSetDestroy(font_set);
+ return NULL;
+ }
+
+ FcPatternDestroy(pattern);
+
+ SkTDArray<FcPattern*> trimmedMatches;
+ for (int i = 0; i < count; ++i) {
+ const char* justName = find_just_name(get_name(match[i], FC_FILE));
+ if (!is_lower(*justName)) {
+ *trimmedMatches.append() = match[i];
+ }
+ }
+
+ SkFontStyleSet_FC* sset = SkNEW_ARGS(SkFontStyleSet_FC,
+ (trimmedMatches.begin(),
+ trimmedMatches.count()));
+ return sset;
+ }
+
+ virtual SkTypeface* onMatchFamilyStyle(const char familyName[],
+ const SkFontStyle&) { return NULL; }
+ virtual SkTypeface* onMatchFaceStyle(const SkTypeface*,
+ const SkFontStyle&) { return NULL; }
+
+ virtual SkTypeface* onCreateFromData(SkData*, int ttcIndex) { return NULL; }
+
+ virtual SkTypeface* onCreateFromStream(SkStream* stream, int ttcIndex) {
+ const size_t length = stream->getLength();
+ if (!length) {
+ return NULL;
+ }
+ if (length >= 1024 * 1024 * 1024) {
+ return NULL; // don't accept too large fonts (>= 1GB) for safety.
+ }
+
+ // TODO should the caller give us the style?
+ SkTypeface::Style style = SkTypeface::kNormal;
+ SkTypeface* face = SkNEW_ARGS(FontConfigTypeface, (style, stream));
+ return face;
+ }
+
+ virtual SkTypeface* onCreateFromFile(const char path[], int ttcIndex) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return stream.get() ? this->createFromStream(stream, ttcIndex) : NULL;
+ }
+
+ virtual SkTypeface* onLegacyCreateTypeface(const char familyName[],
+ unsigned styleBits) SK_OVERRIDE {
+ return FontConfigTypeface::LegacyCreateTypeface(NULL, familyName,
+ (SkTypeface::Style)styleBits);
+ }
+};
+
+SkFontMgr* SkFontMgr::Factory() {
+ SkFontConfigInterface* fci = RefFCI();
+ return fci ? SkNEW_ARGS(SkFontMgr_fontconfig, (fci)) : NULL;
+}
diff --git a/fonts/SkGScalerContext.cpp b/fonts/SkGScalerContext.cpp
new file mode 100644
index 00000000..2c4ebe50
--- /dev/null
+++ b/fonts/SkGScalerContext.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkGScalerContext.h"
+#include "SkGlyph.h"
+#include "SkPath.h"
+#include "SkCanvas.h"
+
+class SkGScalerContext : public SkScalerContext {
+public:
+ SkGScalerContext(SkGTypeface*, const SkDescriptor*);
+ virtual ~SkGScalerContext();
+
+protected:
+ virtual unsigned generateGlyphCount() SK_OVERRIDE;
+ virtual uint16_t generateCharToGlyph(SkUnichar) SK_OVERRIDE;
+ virtual void generateAdvance(SkGlyph*) SK_OVERRIDE;
+ virtual void generateMetrics(SkGlyph*) SK_OVERRIDE;
+ virtual void generateImage(const SkGlyph&) SK_OVERRIDE;
+ virtual void generatePath(const SkGlyph&, SkPath*) SK_OVERRIDE;
+ virtual void generateFontMetrics(SkPaint::FontMetrics* mX,
+ SkPaint::FontMetrics* mY) SK_OVERRIDE;
+
+private:
+ SkGTypeface* fFace;
+ SkScalerContext* fProxy;
+ SkMatrix fMatrix;
+};
+
+#define STD_SIZE 1
+
+#include "SkDescriptor.h"
+
+SkGScalerContext::SkGScalerContext(SkGTypeface* face, const SkDescriptor* desc)
+ : SkScalerContext(face, desc)
+ , fFace(face)
+{
+
+ size_t descSize = SkDescriptor::ComputeOverhead(1) + sizeof(SkScalerContext::Rec);
+ SkAutoDescriptor ad(descSize);
+ SkDescriptor* newDesc = ad.getDesc();
+
+ newDesc->init();
+ void* entry = newDesc->addEntry(kRec_SkDescriptorTag,
+ sizeof(SkScalerContext::Rec), &fRec);
+ {
+ SkScalerContext::Rec* rec = (SkScalerContext::Rec*)entry;
+ rec->fTextSize = STD_SIZE;
+ rec->fPreScaleX = SK_Scalar1;
+ rec->fPreSkewX = 0;
+ rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1;
+ rec->fPost2x2[1][0] = rec->fPost2x2[0][1] = 0;
+ }
+ SkASSERT(descSize == newDesc->getLength());
+ newDesc->computeChecksum();
+
+ fProxy = face->proxy()->createScalerContext(newDesc);
+
+ fRec.getSingleMatrix(&fMatrix);
+ fMatrix.preScale(SK_Scalar1 / STD_SIZE, SK_Scalar1 / STD_SIZE);
+}
+
+SkGScalerContext::~SkGScalerContext() {
+ SkDELETE(fProxy);
+}
+
+unsigned SkGScalerContext::generateGlyphCount() {
+ return fProxy->getGlyphCount();
+}
+
+uint16_t SkGScalerContext::generateCharToGlyph(SkUnichar uni) {
+ return fProxy->charToGlyphID(uni);
+}
+
+void SkGScalerContext::generateAdvance(SkGlyph* glyph) {
+ fProxy->getAdvance(glyph);
+
+ SkVector advance;
+ fMatrix.mapXY(SkFixedToScalar(glyph->fAdvanceX),
+ SkFixedToScalar(glyph->fAdvanceY), &advance);
+ glyph->fAdvanceX = SkScalarToFixed(advance.fX);
+ glyph->fAdvanceY = SkScalarToFixed(advance.fY);
+}
+
+void SkGScalerContext::generateMetrics(SkGlyph* glyph) {
+ fProxy->getMetrics(glyph);
+
+ SkVector advance;
+ fMatrix.mapXY(SkFixedToScalar(glyph->fAdvanceX),
+ SkFixedToScalar(glyph->fAdvanceY), &advance);
+ glyph->fAdvanceX = SkScalarToFixed(advance.fX);
+ glyph->fAdvanceY = SkScalarToFixed(advance.fY);
+
+ SkPath path;
+ fProxy->getPath(*glyph, &path);
+ path.transform(fMatrix);
+
+ SkRect storage;
+ const SkPaint& paint = fFace->paint();
+ const SkRect& newBounds = paint.doComputeFastBounds(path.getBounds(),
+ &storage,
+ SkPaint::kFill_Style);
+ SkIRect ibounds;
+ newBounds.roundOut(&ibounds);
+ glyph->fLeft = ibounds.fLeft;
+ glyph->fTop = ibounds.fTop;
+ glyph->fWidth = ibounds.width();
+ glyph->fHeight = ibounds.height();
+ glyph->fMaskFormat = SkMask::kARGB32_Format;
+}
+
+void SkGScalerContext::generateImage(const SkGlyph& glyph) {
+ if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
+ SkPath path;
+ fProxy->getPath(glyph, &path);
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, glyph.fWidth, glyph.fHeight,
+ glyph.rowBytes());
+ bm.setPixels(glyph.fImage);
+ bm.eraseColor(0);
+
+ SkCanvas canvas(bm);
+ canvas.translate(-SkIntToScalar(glyph.fLeft),
+ -SkIntToScalar(glyph.fTop));
+ canvas.concat(fMatrix);
+ canvas.drawPath(path, fFace->paint());
+ } else {
+ fProxy->getImage(glyph);
+ }
+}
+
+void SkGScalerContext::generatePath(const SkGlyph& glyph, SkPath* path) {
+ fProxy->getPath(glyph, path);
+ path->transform(fMatrix);
+}
+
+void SkGScalerContext::generateFontMetrics(SkPaint::FontMetrics*,
+ SkPaint::FontMetrics* metrics) {
+ fProxy->getFontMetrics(metrics);
+ if (metrics) {
+ SkScalar scale = fMatrix.getScaleY();
+ metrics->fTop = SkScalarMul(metrics->fTop, scale);
+ metrics->fAscent = SkScalarMul(metrics->fAscent, scale);
+ metrics->fDescent = SkScalarMul(metrics->fDescent, scale);
+ metrics->fBottom = SkScalarMul(metrics->fBottom, scale);
+ metrics->fLeading = SkScalarMul(metrics->fLeading, scale);
+ metrics->fAvgCharWidth = SkScalarMul(metrics->fAvgCharWidth, scale);
+ metrics->fXMin = SkScalarMul(metrics->fXMin, scale);
+ metrics->fXMax = SkScalarMul(metrics->fXMax, scale);
+ metrics->fXHeight = SkScalarMul(metrics->fXHeight, scale);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTypefaceCache.h"
+
+SkGTypeface::SkGTypeface(SkTypeface* proxy, const SkPaint& paint)
+ : SkTypeface(proxy->style(), SkTypefaceCache::NewFontID(), false)
+ , fProxy(SkRef(proxy))
+ , fPaint(paint) {}
+
+SkGTypeface::~SkGTypeface() {
+ fProxy->unref();
+}
+
+SkScalerContext* SkGTypeface::onCreateScalerContext(
+ const SkDescriptor* desc) const {
+ return SkNEW_ARGS(SkGScalerContext, (const_cast<SkGTypeface*>(this), desc));
+}
+
+void SkGTypeface::onFilterRec(SkScalerContextRec* rec) const {
+ fProxy->filterRec(rec);
+}
+
+SkAdvancedTypefaceMetrics* SkGTypeface::onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo info,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+ return fProxy->getAdvancedTypefaceMetrics(info, glyphIDs, glyphIDsCount);
+}
+
+SkStream* SkGTypeface::onOpenStream(int* ttcIndex) const {
+ return fProxy->openStream(ttcIndex);
+}
+
+void SkGTypeface::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocal) const {
+ fProxy->getFontDescriptor(desc, isLocal);
+}
+
+int SkGTypeface::onCountGlyphs() const {
+ return fProxy->countGlyphs();
+}
+
+int SkGTypeface::onGetUPEM() const {
+ return fProxy->getUnitsPerEm();
+}
+
+SkTypeface::LocalizedStrings* SkGTypeface::onCreateFamilyNameIterator() const {
+ return fProxy->createFamilyNameIterator();
+}
+
+int SkGTypeface::onGetTableTags(SkFontTableTag tags[]) const {
+ return fProxy->getTableTags(tags);
+}
+
+size_t SkGTypeface::onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* data) const {
+ return fProxy->getTableData(tag, offset, length, data);
+}
+
+SkTypeface* SkGTypeface::onRefMatchingStyle(Style style) const {
+ if (this->style() == style) {
+ return const_cast<SkGTypeface*>(SkRef(this));
+ }
+
+ SkAutoTUnref<SkTypeface> other(fProxy->refMatchingStyle(style));
+ return SkNEW_ARGS(SkGTypeface, (other, fPaint));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if 0
+// under construction -- defining a font purely in terms of skia primitives
+// ala an SVG-font.
+class SkGFont : public SkRefCnt {
+public:
+ virtual ~SkGFont();
+
+ int unicharToGlyph(SkUnichar) const;
+
+ int countGlyphs() const { return fCount; }
+
+ float getAdvance(int index) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ return fGlyphs[index].fAdvance;
+ }
+
+ const SkPath& getPath(int index) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ return fGlyphs[index].fPath;
+ }
+
+private:
+ struct Glyph {
+ SkUnichar fUni;
+ float fAdvance;
+ SkPath fPath;
+ };
+ int fCount;
+ Glyph* fGlyphs;
+
+ friend class SkGFontBuilder;
+ SkGFont(int count, Glyph* array);
+};
+
+class SkGFontBuilder {
+public:
+
+};
+#endif
diff --git a/fonts/SkGScalerContext.h b/fonts/SkGScalerContext.h
new file mode 100644
index 00000000..5e73850a
--- /dev/null
+++ b/fonts/SkGScalerContext.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkGScalerContext_DEFINED
+#define SkGScalerContext_DEFINED
+
+#include "SkScalerContext.h"
+#include "SkTypeface.h"
+
+class SkGTypeface : public SkTypeface {
+public:
+ SkGTypeface(SkTypeface* proxy, const SkPaint&);
+ virtual ~SkGTypeface();
+
+ SkTypeface* proxy() const { return fProxy; }
+ const SkPaint& paint() const { return fPaint; }
+
+protected:
+ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor*) const SK_OVERRIDE;
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE;
+ virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const SK_OVERRIDE;
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE;
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool* isLocal) const SK_OVERRIDE;
+
+ virtual int onCountGlyphs() const SK_OVERRIDE;
+ virtual int onGetUPEM() const SK_OVERRIDE;
+
+ virtual SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE;
+
+ virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE;
+ virtual size_t onGetTableData(SkFontTableTag, size_t offset,
+ size_t length, void* data) const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style) const SK_OVERRIDE;
+
+private:
+ SkTypeface* fProxy;
+ SkPaint fPaint;
+};
+
+#endif
diff --git a/gpu/FlingState.cpp b/gpu/FlingState.cpp
new file mode 100644
index 00000000..f0db5016
--- /dev/null
+++ b/gpu/FlingState.cpp
@@ -0,0 +1,125 @@
+
+/*
+ * 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 "FlingState.h"
+#include "SkMatrix.h"
+#include "SkTime.h"
+
+#define DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER true
+
+static const float MAX_FLING_SPEED = 1500;
+
+static float pin_max_fling(float speed) {
+ if (speed > MAX_FLING_SPEED) {
+ speed = MAX_FLING_SPEED;
+ }
+ return speed;
+}
+
+static double getseconds() {
+ return SkTime::GetMSecs() * 0.001;
+}
+
+// returns +1 or -1, depending on the sign of x
+// returns +1 if x is zero
+static SkScalar SkScalarSign(SkScalar x) {
+ SkScalar sign = SK_Scalar1;
+ if (x < 0) {
+ sign = -sign;
+ }
+ return sign;
+}
+
+static void unit_axis_align(SkVector* unit) {
+ const SkScalar TOLERANCE = SkDoubleToScalar(0.15);
+ if (SkScalarAbs(unit->fX) < TOLERANCE) {
+ unit->fX = 0;
+ unit->fY = SkScalarSign(unit->fY);
+ } else if (SkScalarAbs(unit->fY) < TOLERANCE) {
+ unit->fX = SkScalarSign(unit->fX);
+ unit->fY = 0;
+ }
+}
+
+void FlingState::reset(float sx, float sy) {
+ fActive = true;
+ fDirection.set(sx, sy);
+ fSpeed0 = SkPoint::Normalize(&fDirection);
+ fSpeed0 = pin_max_fling(fSpeed0);
+ fTime0 = getseconds();
+
+ unit_axis_align(&fDirection);
+// printf("---- speed %g dir %g %g\n", fSpeed0, fDirection.fX, fDirection.fY);
+}
+
+bool FlingState::evaluateMatrix(SkMatrix* matrix) {
+ if (!fActive) {
+ return false;
+ }
+
+ const float t = getseconds() - fTime0;
+ const float MIN_SPEED = 2;
+ const float K0 = 5.0;
+ const float K1 = 0.02;
+ const float speed = fSpeed0 * (sk_float_exp(- K0 * t) - K1);
+ if (speed <= MIN_SPEED) {
+ fActive = false;
+ return false;
+ }
+ float dist = (fSpeed0 - speed) / K0;
+
+// printf("---- time %g speed %g dist %g\n", t, speed, dist);
+ float tx = fDirection.fX * dist;
+ float ty = fDirection.fY * dist;
+ if (DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER) {
+ tx = sk_float_round2int(tx);
+ ty = sk_float_round2int(ty);
+ }
+ matrix->setTranslate(tx, ty);
+// printf("---- evaluate (%g %g)\n", tx, ty);
+
+ return true;
+}
+
+////////////////////////////////////////
+
+GrAnimateFloat::GrAnimateFloat() : fTime0(0) {}
+
+void GrAnimateFloat::start(float v0, float v1, float duration) {
+ fValue0 = v0;
+ fValue1 = v1;
+ fDuration = duration;
+ if (duration > 0) {
+ fTime0 = SkTime::GetMSecs();
+ if (!fTime0) {
+ fTime0 = 1; // time0 is our sentinel
+ }
+ } else {
+ fTime0 = 0;
+ }
+}
+
+float GrAnimateFloat::evaluate() {
+ if (!fTime0) {
+ return fValue1;
+ }
+
+ double elapsed = (SkTime::GetMSecs() - fTime0) * 0.001;
+ if (elapsed >= fDuration) {
+ fTime0 = 0;
+ return fValue1;
+ }
+
+ double t = elapsed / fDuration;
+ if (true) {
+ t = (3 - 2 * t) * t * t;
+ }
+ return fValue0 + t * (fValue1 - fValue0);
+}
diff --git a/gpu/GrAAConvexPathRenderer.cpp b/gpu/GrAAConvexPathRenderer.cpp
new file mode 100644
index 00000000..408fcb54
--- /dev/null
+++ b/gpu/GrAAConvexPathRenderer.cpp
@@ -0,0 +1,686 @@
+
+/*
+ * 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 "GrAAConvexPathRenderer.h"
+
+#include "GrContext.h"
+#include "GrDrawState.h"
+#include "GrDrawTargetCaps.h"
+#include "GrEffect.h"
+#include "GrPathUtils.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkString.h"
+#include "SkStrokeRec.h"
+#include "SkTrace.h"
+
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLSL.h"
+
+GrAAConvexPathRenderer::GrAAConvexPathRenderer() {
+}
+
+namespace {
+
+struct Segment {
+ enum {
+ // These enum values are assumed in member functions below.
+ kLine = 0,
+ kQuad = 1,
+ } fType;
+
+ // line uses one pt, quad uses 2 pts
+ GrPoint fPts[2];
+ // normal to edge ending at each pt
+ GrVec fNorms[2];
+ // is the corner where the previous segment meets this segment
+ // sharp. If so, fMid is a normalized bisector facing outward.
+ GrVec fMid;
+
+ int countPoints() {
+ GR_STATIC_ASSERT(0 == kLine && 1 == kQuad);
+ return fType + 1;
+ }
+ const SkPoint& endPt() const {
+ GR_STATIC_ASSERT(0 == kLine && 1 == kQuad);
+ return fPts[fType];
+ };
+ const SkPoint& endNorm() const {
+ GR_STATIC_ASSERT(0 == kLine && 1 == kQuad);
+ return fNorms[fType];
+ };
+};
+
+typedef SkTArray<Segment, true> SegmentArray;
+
+void center_of_mass(const SegmentArray& segments, SkPoint* c) {
+ SkScalar area = 0;
+ SkPoint center = {0, 0};
+ int count = segments.count();
+ SkPoint p0 = {0, 0};
+ if (count > 2) {
+ // We translate the polygon so that the first point is at the origin.
+ // This avoids some precision issues with small area polygons far away
+ // from the origin.
+ p0 = segments[0].endPt();
+ SkPoint pi;
+ SkPoint pj;
+ // the first and last iteration of the below loop would compute
+ // zeros since the starting / ending point is (0,0). So instead we start
+ // at i=1 and make the last iteration i=count-2.
+ pj = segments[1].endPt() - p0;
+ for (int i = 1; i < count - 1; ++i) {
+ pi = pj;
+ const SkPoint pj = segments[i + 1].endPt() - p0;
+
+ 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;
+
+ }
+ }
+ // If the poly has no area then we instead return the average of
+ // its points.
+ if (SkScalarNearlyZero(area)) {
+ SkPoint avg;
+ avg.set(0, 0);
+ for (int i = 0; i < count; ++i) {
+ const SkPoint& pt = segments[i].endPt();
+ avg.fX += pt.fX;
+ avg.fY += pt.fY;
+ }
+ SkScalar denom = SK_Scalar1 / count;
+ avg.scale(denom);
+ *c = avg;
+ } else {
+ area *= 3;
+ 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;
+ }
+ GrAssert(!SkScalarIsNaN(c->fX) && !SkScalarIsNaN(c->fY));
+}
+
+void compute_vectors(SegmentArray* segments,
+ SkPoint* fanPt,
+ SkPath::Direction dir,
+ int* vCount,
+ int* iCount) {
+ center_of_mass(*segments, fanPt);
+ int count = segments->count();
+
+ // Make the normals point towards the outside
+ GrPoint::Side normSide;
+ if (dir == SkPath::kCCW_Direction) {
+ normSide = GrPoint::kRight_Side;
+ } else {
+ normSide = GrPoint::kLeft_Side;
+ }
+
+ *vCount = 0;
+ *iCount = 0;
+ // compute normals at all points
+ for (int a = 0; a < count; ++a) {
+ Segment& sega = (*segments)[a];
+ int b = (a + 1) % count;
+ Segment& segb = (*segments)[b];
+
+ const GrPoint* prevPt = &sega.endPt();
+ int n = segb.countPoints();
+ for (int p = 0; p < n; ++p) {
+ segb.fNorms[p] = segb.fPts[p] - *prevPt;
+ segb.fNorms[p].normalize();
+ segb.fNorms[p].setOrthog(segb.fNorms[p], normSide);
+ prevPt = &segb.fPts[p];
+ }
+ if (Segment::kLine == segb.fType) {
+ *vCount += 5;
+ *iCount += 9;
+ } else {
+ *vCount += 6;
+ *iCount += 12;
+ }
+ }
+
+ // compute mid-vectors where segments meet. TODO: Detect shallow corners
+ // and leave out the wedges and close gaps by stitching segments together.
+ for (int a = 0; a < count; ++a) {
+ const Segment& sega = (*segments)[a];
+ int b = (a + 1) % count;
+ Segment& segb = (*segments)[b];
+ segb.fMid = segb.fNorms[0] + sega.endNorm();
+ segb.fMid.normalize();
+ // corner wedges
+ *vCount += 4;
+ *iCount += 6;
+ }
+}
+
+struct DegenerateTestData {
+ DegenerateTestData() { fStage = kInitial; }
+ bool isDegenerate() const { return kNonDegenerate != fStage; }
+ enum {
+ kInitial,
+ kPoint,
+ kLine,
+ kNonDegenerate
+ } fStage;
+ GrPoint fFirstPoint;
+ GrVec fLineNormal;
+ SkScalar fLineC;
+};
+
+void update_degenerate_test(DegenerateTestData* data, const GrPoint& pt) {
+ static const SkScalar TOL = (SK_Scalar1 / 16);
+ static const SkScalar TOL_SQD = SkScalarMul(TOL, TOL);
+
+ switch (data->fStage) {
+ case DegenerateTestData::kInitial:
+ data->fFirstPoint = pt;
+ data->fStage = DegenerateTestData::kPoint;
+ break;
+ case DegenerateTestData::kPoint:
+ if (pt.distanceToSqd(data->fFirstPoint) > TOL_SQD) {
+ data->fLineNormal = pt - data->fFirstPoint;
+ data->fLineNormal.normalize();
+ data->fLineNormal.setOrthog(data->fLineNormal);
+ data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
+ data->fStage = DegenerateTestData::kLine;
+ }
+ break;
+ case DegenerateTestData::kLine:
+ if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > TOL) {
+ data->fStage = DegenerateTestData::kNonDegenerate;
+ }
+ case DegenerateTestData::kNonDegenerate:
+ break;
+ default:
+ GrCrash("Unexpected degenerate test stage.");
+ }
+}
+
+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());
+ SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
+ SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
+ if (det2x2 < 0) {
+ *dir = SkPath::OppositeDirection(*dir);
+ }
+ return true;
+}
+
+bool get_segments(const SkPath& path,
+ const SkMatrix& m,
+ SegmentArray* segments,
+ SkPoint* fanPt,
+ int* vCount,
+ int* iCount) {
+ SkPath::Iter iter(path, true);
+ // 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
+ // thus should be very light. This is particularly egregious for degenerate
+ // line paths. We detect paths that are very close to a line (zero area) and
+ // draw nothing.
+ DegenerateTestData degenerateData;
+ SkPath::Direction dir;
+ // get_direction can fail for some degenerate paths.
+ if (!get_direction(path, m, &dir)) {
+ return false;
+ }
+
+ for (;;) {
+ GrPoint pts[4];
+ SkPath::Verb verb = iter.next(pts);
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ m.mapPoints(pts, 1);
+ update_degenerate_test(&degenerateData, pts[0]);
+ break;
+ case SkPath::kLine_Verb: {
+ m.mapPoints(pts + 1, 1);
+ update_degenerate_test(&degenerateData, pts[1]);
+ segments->push_back();
+ segments->back().fType = Segment::kLine;
+ segments->back().fPts[0] = pts[1];
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ m.mapPoints(pts + 1, 2);
+ update_degenerate_test(&degenerateData, pts[1]);
+ update_degenerate_test(&degenerateData, pts[2]);
+ segments->push_back();
+ segments->back().fType = Segment::kQuad;
+ segments->back().fPts[0] = pts[1];
+ segments->back().fPts[1] = pts[2];
+ break;
+ case SkPath::kCubic_Verb: {
+ m.mapPoints(pts, 4);
+ update_degenerate_test(&degenerateData, pts[1]);
+ update_degenerate_test(&degenerateData, pts[2]);
+ update_degenerate_test(&degenerateData, pts[3]);
+ // unlike quads and lines, the pts[0] will also be read (in
+ // convertCubicToQuads).
+ SkSTArray<15, SkPoint, true> quads;
+ GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, true, dir, &quads);
+ int count = quads.count();
+ for (int q = 0; q < count; q += 3) {
+ segments->push_back();
+ segments->back().fType = Segment::kQuad;
+ segments->back().fPts[0] = quads[q + 1];
+ segments->back().fPts[1] = quads[q + 2];
+ }
+ break;
+ };
+ case SkPath::kDone_Verb:
+ if (degenerateData.isDegenerate()) {
+ return false;
+ } else {
+ compute_vectors(segments, fanPt, dir, vCount, iCount);
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+struct QuadVertex {
+ GrPoint fPos;
+ GrPoint fUV;
+ SkScalar fD0;
+ SkScalar fD1;
+};
+
+struct Draw {
+ Draw() : fVertexCnt(0), fIndexCnt(0) {}
+ int fVertexCnt;
+ int fIndexCnt;
+};
+
+typedef SkTArray<Draw, true> DrawArray;
+
+void create_vertices(const SegmentArray& segments,
+ const SkPoint& fanPt,
+ DrawArray* draws,
+ QuadVertex* verts,
+ uint16_t* idxs) {
+ Draw* draw = &draws->push_back();
+ // alias just to make vert/index assignments easier to read.
+ int* v = &draw->fVertexCnt;
+ int* i = &draw->fIndexCnt;
+
+ int count = segments.count();
+ for (int a = 0; a < count; ++a) {
+ const Segment& sega = segments[a];
+ int b = (a + 1) % count;
+ const Segment& segb = segments[b];
+
+ // Check whether adding the verts for this segment to the current draw would cause index
+ // values to overflow.
+ int vCount = 4;
+ if (Segment::kLine == segb.fType) {
+ vCount += 5;
+ } else {
+ vCount += 6;
+ }
+ if (draw->fVertexCnt + vCount > (1 << 16)) {
+ verts += *v;
+ idxs += *i;
+ draw = &draws->push_back();
+ v = &draw->fVertexCnt;
+ i = &draw->fIndexCnt;
+ }
+
+ // FIXME: These tris are inset in the 1 unit arc around the corner
+ verts[*v + 0].fPos = sega.endPt();
+ verts[*v + 1].fPos = verts[*v + 0].fPos + sega.endNorm();
+ verts[*v + 2].fPos = verts[*v + 0].fPos + segb.fMid;
+ verts[*v + 3].fPos = verts[*v + 0].fPos + segb.fNorms[0];
+ verts[*v + 0].fUV.set(0,0);
+ verts[*v + 1].fUV.set(0,-SK_Scalar1);
+ verts[*v + 2].fUV.set(0,-SK_Scalar1);
+ verts[*v + 3].fUV.set(0,-SK_Scalar1);
+ verts[*v + 0].fD0 = verts[*v + 0].fD1 = -SK_Scalar1;
+ verts[*v + 1].fD0 = verts[*v + 1].fD1 = -SK_Scalar1;
+ verts[*v + 2].fD0 = verts[*v + 2].fD1 = -SK_Scalar1;
+ verts[*v + 3].fD0 = verts[*v + 3].fD1 = -SK_Scalar1;
+
+ idxs[*i + 0] = *v + 0;
+ idxs[*i + 1] = *v + 2;
+ idxs[*i + 2] = *v + 1;
+ idxs[*i + 3] = *v + 0;
+ idxs[*i + 4] = *v + 3;
+ idxs[*i + 5] = *v + 2;
+
+ *v += 4;
+ *i += 6;
+
+ if (Segment::kLine == segb.fType) {
+ verts[*v + 0].fPos = fanPt;
+ verts[*v + 1].fPos = sega.endPt();
+ verts[*v + 2].fPos = segb.fPts[0];
+
+ verts[*v + 3].fPos = verts[*v + 1].fPos + segb.fNorms[0];
+ verts[*v + 4].fPos = verts[*v + 2].fPos + segb.fNorms[0];
+
+ // we draw the line edge as a degenerate quad (u is 0, v is the
+ // signed distance to the edge)
+ 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);
+ verts[*v + 2].fUV.set(0, 0);
+ verts[*v + 3].fUV.set(0, -SK_Scalar1);
+ verts[*v + 4].fUV.set(0, -SK_Scalar1);
+
+ verts[*v + 0].fD0 = verts[*v + 0].fD1 = -SK_Scalar1;
+ verts[*v + 1].fD0 = verts[*v + 1].fD1 = -SK_Scalar1;
+ verts[*v + 2].fD0 = verts[*v + 2].fD1 = -SK_Scalar1;
+ verts[*v + 3].fD0 = verts[*v + 3].fD1 = -SK_Scalar1;
+ verts[*v + 4].fD0 = verts[*v + 4].fD1 = -SK_Scalar1;
+
+ idxs[*i + 0] = *v + 0;
+ idxs[*i + 1] = *v + 2;
+ idxs[*i + 2] = *v + 1;
+
+ idxs[*i + 3] = *v + 3;
+ idxs[*i + 4] = *v + 1;
+ idxs[*i + 5] = *v + 2;
+
+ idxs[*i + 6] = *v + 4;
+ idxs[*i + 7] = *v + 3;
+ idxs[*i + 8] = *v + 2;
+
+ *v += 5;
+ *i += 9;
+ } else {
+ GrPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
+
+ GrVec midVec = segb.fNorms[0] + segb.fNorms[1];
+ midVec.normalize();
+
+ verts[*v + 0].fPos = fanPt;
+ verts[*v + 1].fPos = qpts[0];
+ verts[*v + 2].fPos = qpts[2];
+ verts[*v + 3].fPos = qpts[0] + segb.fNorms[0];
+ verts[*v + 4].fPos = qpts[2] + segb.fNorms[1];
+ verts[*v + 5].fPos = qpts[1] + midVec;
+
+ 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 = -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 = -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);
+
+ idxs[*i + 0] = *v + 3;
+ idxs[*i + 1] = *v + 1;
+ idxs[*i + 2] = *v + 2;
+ idxs[*i + 3] = *v + 4;
+ idxs[*i + 4] = *v + 3;
+ idxs[*i + 5] = *v + 2;
+
+ idxs[*i + 6] = *v + 5;
+ idxs[*i + 7] = *v + 3;
+ idxs[*i + 8] = *v + 4;
+
+ idxs[*i + 9] = *v + 0;
+ idxs[*i + 10] = *v + 2;
+ idxs[*i + 11] = *v + 1;
+
+ *v += 6;
+ *i += 12;
+ }
+ }
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Quadratic specified by 0=u^2-v canonical coords. u and v are the first
+ * two components of the vertex attribute. Coverage is based on signed
+ * distance with negative being inside, positive outside. The edge is specified in
+ * window space (y-down). If either the third or fourth component of the interpolated
+ * vertex coord is > 0 then the pixel is considered outside the edge. This is used to
+ * attempt to trim to a portion of the infinite quad.
+ * Requires shader derivative instruction support.
+ */
+
+class QuadEdgeEffect : public GrEffect {
+public:
+
+ static GrEffectRef* Create() {
+ GR_CREATE_STATIC_EFFECT(gQuadEdgeEffect, QuadEdgeEffect, ());
+ gQuadEdgeEffect->ref();
+ return gQuadEdgeEffect;
+ }
+
+ virtual ~QuadEdgeEffect() {}
+
+ static const char* Name() { return "QuadEdge"; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<QuadEdgeEffect>::getInstance();
+ }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char *vsName, *fsName;
+ const SkString* attrName =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n");
+
+ SkAssertResult(builder->enableFeature(
+ GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
+ builder->addVarying(kVec4f_GrSLType, "QuadEdge", &vsName, &fsName);
+
+ // keep the derivative instructions outside the conditional
+ builder->fsCodeAppendf("\t\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
+ builder->fsCodeAppendf("\t\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
+ builder->fsCodeAppendf("\t\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
+ builder->fsCodeAppendf("\t\t\tedgeAlpha = min(min(%s.z, %s.w) + 0.5, 1.0);\n", fsName,
+ fsName);
+ builder->fsCodeAppendf ("\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
+ "\t\t\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
+ fsName, fsName);
+ builder->fsCodeAppendf("\t\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName,
+ fsName);
+ builder->fsCodeAppendf("\t\t\tedgeAlpha = "
+ "clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);\n\t\t}\n");
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+
+ builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return 0x0;
+ }
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+private:
+ QuadEdgeEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ return true;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(QuadEdgeEffect);
+
+GrEffectRef* QuadEdgeEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps& caps,
+ GrTexture*[]) {
+ // Doesn't work without derivative instructions.
+ return caps.shaderDerivativeSupport() ? QuadEdgeEffect::Create() : NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GrAAConvexPathRenderer::canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const {
+ return (target->caps()->shaderDerivativeSupport() && antiAlias &&
+ stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex());
+}
+
+namespace {
+
+// position + edge
+extern const GrVertexAttrib gPathAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
+};
+
+};
+
+bool GrAAConvexPathRenderer::onDrawPath(const SkPath& origPath,
+ const SkStrokeRec&,
+ GrDrawTarget* target,
+ bool antiAlias) {
+
+ const SkPath* path = &origPath;
+ if (path->isEmpty()) {
+ return true;
+ }
+
+ SkMatrix viewMatrix = target->getDrawState().getViewMatrix();
+ GrDrawTarget::AutoStateRestore asr;
+ if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
+ return false;
+ }
+ GrDrawState* drawState = target->drawState();
+
+ // We use the fact that SkPath::transform path does subdivision based on
+ // perspective. Otherwise, we apply the view matrix when copying to the
+ // segment representation.
+ SkPath tmpPath;
+ if (viewMatrix.hasPerspective()) {
+ origPath.transform(viewMatrix, &tmpPath);
+ path = &tmpPath;
+ viewMatrix = SkMatrix::I();
+ }
+
+ QuadVertex *verts;
+ uint16_t* idxs;
+
+ int vCount;
+ int iCount;
+ enum {
+ kPreallocSegmentCnt = 512 / sizeof(Segment),
+ kPreallocDrawCnt = 4,
+ };
+ SkSTArray<kPreallocSegmentCnt, Segment, true> segments;
+ SkPoint fanPt;
+
+ if (!get_segments(*path, viewMatrix, &segments, &fanPt, &vCount, &iCount)) {
+ return false;
+ }
+
+ drawState->setVertexAttribs<gPathAttribs>(SK_ARRAY_COUNT(gPathAttribs));
+
+ static const int kEdgeAttrIndex = 1;
+ GrEffectRef* quadEffect = QuadEdgeEffect::Create();
+ drawState->addCoverageEffect(quadEffect, kEdgeAttrIndex)->unref();
+
+ GrDrawTarget::AutoReleaseGeometry arg(target, vCount, iCount);
+ if (!arg.succeeded()) {
+ return false;
+ }
+ GrAssert(sizeof(QuadVertex) == drawState->getVertexSize());
+ verts = reinterpret_cast<QuadVertex*>(arg.vertices());
+ idxs = reinterpret_cast<uint16_t*>(arg.indices());
+
+ SkSTArray<kPreallocDrawCnt, Draw, true> draws;
+ create_vertices(segments, fanPt, &draws, verts, idxs);
+
+ // This is valid because all the computed verts are within 1 pixel of the path control points.
+ SkRect devBounds;
+ devBounds = path->getBounds();
+ viewMatrix.mapRect(&devBounds);
+ devBounds.outset(SK_Scalar1, SK_Scalar1);
+
+ // Check devBounds
+#if GR_DEBUG
+ SkRect tolDevBounds = devBounds;
+ tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
+ SkRect actualBounds;
+ actualBounds.set(verts[0].fPos, verts[1].fPos);
+ for (int i = 2; i < vCount; ++i) {
+ actualBounds.growToInclude(verts[i].fPos.fX, verts[i].fPos.fY);
+ }
+ GrAssert(tolDevBounds.contains(actualBounds));
+#endif
+
+ int vOffset = 0;
+ for (int i = 0; i < draws.count(); ++i) {
+ const Draw& draw = draws[i];
+ target->drawIndexed(kTriangles_GrPrimitiveType,
+ vOffset, // start vertex
+ 0, // start index
+ draw.fVertexCnt,
+ draw.fIndexCnt,
+ &devBounds);
+ vOffset += draw.fVertexCnt;
+ }
+
+ return true;
+}
diff --git a/gpu/GrAAConvexPathRenderer.h b/gpu/GrAAConvexPathRenderer.h
new file mode 100644
index 00000000..62394a3a
--- /dev/null
+++ b/gpu/GrAAConvexPathRenderer.h
@@ -0,0 +1,26 @@
+
+/*
+ * 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 "GrPathRenderer.h"
+
+
+class GrAAConvexPathRenderer : public GrPathRenderer {
+public:
+ GrAAConvexPathRenderer();
+
+ virtual bool canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const SK_OVERRIDE;
+
+protected:
+ virtual bool onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) SK_OVERRIDE;
+};
diff --git a/gpu/GrAAHairLinePathRenderer.cpp b/gpu/GrAAHairLinePathRenderer.cpp
new file mode 100644
index 00000000..4a73b53e
--- /dev/null
+++ b/gpu/GrAAHairLinePathRenderer.cpp
@@ -0,0 +1,1288 @@
+/*
+ * 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 "GrAAHairLinePathRenderer.h"
+
+#include "GrContext.h"
+#include "GrDrawState.h"
+#include "GrDrawTargetCaps.h"
+#include "GrEffect.h"
+#include "GrGpu.h"
+#include "GrIndexBuffer.h"
+#include "GrPathUtils.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkGeometry.h"
+#include "SkStroke.h"
+#include "SkTemplates.h"
+
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLSL.h"
+
+namespace {
+// quadratics are rendered as 5-sided polys in order to bound the
+// AA stroke around the center-curve. See comments in push_quad_index_buffer and
+// bloat_quad. Quadratics and conics share an index buffer
+static const int kVertsPerQuad = 5;
+static const int kIdxsPerQuad = 9;
+
+static const int kVertsPerLineSeg = 6;
+static const int kIdxsPerLineSeg = 12;
+
+static const int kNumQuadsInIdxBuffer = 256;
+static const size_t kQuadIdxSBufize = kIdxsPerQuad *
+ sizeof(uint16_t) *
+ kNumQuadsInIdxBuffer;
+
+static const int kNumLineSegsInIdxBuffer = 256;
+static const size_t kLineSegIdxSBufize = kIdxsPerLineSeg *
+ sizeof(uint16_t) *
+ kNumLineSegsInIdxBuffer;
+
+static bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) {
+ uint16_t* data = (uint16_t*) qIdxBuffer->lock();
+ bool tempData = NULL == data;
+ if (tempData) {
+ data = SkNEW_ARRAY(uint16_t, kNumQuadsInIdxBuffer * kIdxsPerQuad);
+ }
+ for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) {
+
+ // Each quadratic is rendered as a five sided polygon. This poly bounds
+ // the quadratic's bounding triangle but has been expanded so that the
+ // 1-pixel wide area around the curve is inside the poly.
+ // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
+ // that is rendered would look like this:
+ // b0
+ // b
+ //
+ // a0 c0
+ // a c
+ // a1 c1
+ // Each is drawn as three triangles specified by these 9 indices:
+ int baseIdx = i * kIdxsPerQuad;
+ uint16_t baseVert = (uint16_t)(i * kVertsPerQuad);
+ data[0 + baseIdx] = baseVert + 0; // a0
+ data[1 + baseIdx] = baseVert + 1; // a1
+ data[2 + baseIdx] = baseVert + 2; // b0
+ data[3 + baseIdx] = baseVert + 2; // b0
+ data[4 + baseIdx] = baseVert + 4; // c1
+ data[5 + baseIdx] = baseVert + 3; // c0
+ data[6 + baseIdx] = baseVert + 1; // a1
+ data[7 + baseIdx] = baseVert + 4; // c1
+ data[8 + baseIdx] = baseVert + 2; // b0
+ }
+ if (tempData) {
+ bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize);
+ delete[] data;
+ return ret;
+ } else {
+ qIdxBuffer->unlock();
+ return true;
+ }
+}
+
+static bool push_line_index_data(GrIndexBuffer* lIdxBuffer) {
+ uint16_t* data = (uint16_t*) lIdxBuffer->lock();
+ bool tempData = NULL == data;
+ if (tempData) {
+ data = SkNEW_ARRAY(uint16_t, kNumLineSegsInIdxBuffer * kIdxsPerLineSeg);
+ }
+ for (int i = 0; i < kNumLineSegsInIdxBuffer; ++i) {
+ // Each line segment is rendered as two quads, with alpha = 1 along the
+ // spine of the segment, and alpha = 0 along the outer edges, represented
+ // horizontally (i.e., the line equation is t*(p1-p0) + p0)
+ //
+ // p4 p5
+ // p0 p1
+ // p2 p3
+ //
+ // Each is drawn as four triangles specified by these 12 indices:
+ int baseIdx = i * kIdxsPerLineSeg;
+ uint16_t baseVert = (uint16_t)(i * kVertsPerLineSeg);
+ data[0 + baseIdx] = baseVert + 0; // p0
+ data[1 + baseIdx] = baseVert + 1; // p1
+ data[2 + baseIdx] = baseVert + 2; // p2
+
+ data[3 + baseIdx] = baseVert + 2; // p2
+ data[4 + baseIdx] = baseVert + 1; // p1
+ data[5 + baseIdx] = baseVert + 3; // p3
+
+ data[6 + baseIdx] = baseVert + 0; // p0
+ data[7 + baseIdx] = baseVert + 5; // p5
+ data[8 + baseIdx] = baseVert + 1; // p1
+
+ data[9 + baseIdx] = baseVert + 0; // p0
+ data[10+ baseIdx] = baseVert + 4; // p4
+ data[11+ baseIdx] = baseVert + 5; // p5
+ }
+ if (tempData) {
+ bool ret = lIdxBuffer->updateData(data, kLineSegIdxSBufize);
+ delete[] data;
+ return ret;
+ } else {
+ lIdxBuffer->unlock();
+ return true;
+ }
+}
+}
+
+GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) {
+ GrGpu* gpu = context->getGpu();
+ GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false);
+ SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf);
+ if (NULL == qIdxBuf || !push_quad_index_data(qIdxBuf)) {
+ return NULL;
+ }
+ GrIndexBuffer* lIdxBuf = gpu->createIndexBuffer(kLineSegIdxSBufize, false);
+ SkAutoTUnref<GrIndexBuffer> lIdxBuffer(lIdxBuf);
+ if (NULL == lIdxBuf || !push_line_index_data(lIdxBuf)) {
+ return NULL;
+ }
+ return SkNEW_ARGS(GrAAHairLinePathRenderer,
+ (context, lIdxBuf, qIdxBuf));
+}
+
+GrAAHairLinePathRenderer::GrAAHairLinePathRenderer(
+ const GrContext* context,
+ const GrIndexBuffer* linesIndexBuffer,
+ const GrIndexBuffer* quadsIndexBuffer) {
+ fLinesIndexBuffer = linesIndexBuffer;
+ linesIndexBuffer->ref();
+ fQuadsIndexBuffer = quadsIndexBuffer;
+ quadsIndexBuffer->ref();
+}
+
+GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() {
+ fLinesIndexBuffer->unref();
+ fQuadsIndexBuffer->unref();
+}
+
+namespace {
+
+#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true>
+
+// Takes 178th time of logf on Z600 / VC2010
+int get_float_exp(float x) {
+ GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
+#if GR_DEBUG
+ static bool tested;
+ if (!tested) {
+ tested = true;
+ GrAssert(get_float_exp(0.25f) == -2);
+ GrAssert(get_float_exp(0.3f) == -2);
+ GrAssert(get_float_exp(0.5f) == -1);
+ GrAssert(get_float_exp(1.f) == 0);
+ GrAssert(get_float_exp(2.f) == 1);
+ GrAssert(get_float_exp(2.5f) == 1);
+ GrAssert(get_float_exp(8.f) == 3);
+ GrAssert(get_float_exp(100.f) == 6);
+ GrAssert(get_float_exp(1000.f) == 9);
+ GrAssert(get_float_exp(1024.f) == 10);
+ GrAssert(get_float_exp(3000000.f) == 21);
+ }
+#endif
+ const int* iptr = (const int*)&x;
+ return (((*iptr) & 0x7f800000) >> 23) - 127;
+}
+
+// Uses the max curvature function for quads to estimate
+// where to chop the conic. If the max curvature is not
+// found along the curve segment it will return 1 and
+// dst[0] is the original conic. If it returns 2 the dst[0]
+// and dst[1] are the two new conics.
+int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
+ SkScalar t = SkFindQuadMaxCurvature(src);
+ if (t == 0) {
+ if (dst) {
+ dst[0].set(src, weight);
+ }
+ return 1;
+ } else {
+ if (dst) {
+ SkConic conic;
+ conic.set(src, weight);
+ conic.chopAt(t, dst);
+ }
+ return 2;
+ }
+}
+
+// Calls split_conic on the entire conic and then once more on each subsection.
+// Most cases will result in either 1 conic (chop point is not within t range)
+// or 3 points (split once and then one subsection is split again).
+int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
+ SkConic dstTemp[2];
+ int conicCnt = split_conic(src, dstTemp, weight);
+ if (2 == conicCnt) {
+ int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
+ conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
+ } else {
+ dst[0] = dstTemp[0];
+ }
+ return conicCnt;
+}
+
+// returns 0 if quad/conic is degen or close to it
+// in this case approx the path with lines
+// otherwise returns 1
+int is_degen_quad_or_conic(const SkPoint p[3]) {
+ static const SkScalar gDegenerateToLineTol = SK_Scalar1;
+ static const SkScalar gDegenerateToLineTolSqd =
+ SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
+
+ if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
+ p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
+ return 1;
+ }
+
+ SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
+ if (dsqd < gDegenerateToLineTolSqd) {
+ return 1;
+ }
+
+ if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
+ return 1;
+ }
+ return 0;
+}
+
+// we subdivide the quads to avoid huge overfill
+// if it returns -1 then should be drawn as lines
+int num_quad_subdivs(const SkPoint p[3]) {
+ static const SkScalar gDegenerateToLineTol = SK_Scalar1;
+ static const SkScalar gDegenerateToLineTolSqd =
+ SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
+
+ if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
+ p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
+ return -1;
+ }
+
+ SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
+ if (dsqd < gDegenerateToLineTolSqd) {
+ return -1;
+ }
+
+ if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
+ return -1;
+ }
+
+ // tolerance of triangle height in pixels
+ // tuned on windows Quadro FX 380 / Z600
+ // trade off of fill vs cpu time on verts
+ // maybe different when do this using gpu (geo or tess shaders)
+ static const SkScalar gSubdivTol = 175 * SK_Scalar1;
+
+ if (dsqd <= SkScalarMul(gSubdivTol, gSubdivTol)) {
+ return 0;
+ } else {
+ static const int kMaxSub = 4;
+ // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
+ // = log4(d*d/tol*tol)/2
+ // = log2(d*d/tol*tol)
+
+#ifdef SK_SCALAR_IS_FLOAT
+ // +1 since we're ignoring the mantissa contribution.
+ int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
+ log = GrMin(GrMax(0, log), kMaxSub);
+ return log;
+#else
+ SkScalar log = SkScalarLog(
+ SkScalarDiv(dsqd,
+ SkScalarMul(gSubdivTol, gSubdivTol)));
+ static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
+ log = SkScalarMul(log, conv);
+ return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
+#endif
+ }
+}
+
+/**
+ * Generates the lines and quads to be rendered. Lines are always recorded in
+ * device space. We will do a device space bloat to account for the 1pixel
+ * thickness.
+ * Quads are recorded in device space unless m contains
+ * perspective, then in they are in src space. We do this because we will
+ * subdivide large quads to reduce over-fill. This subdivision has to be
+ * performed before applying the perspective matrix.
+ */
+int generate_lines_and_quads(const SkPath& path,
+ const SkMatrix& m,
+ const SkIRect& devClipBounds,
+ GrAAHairLinePathRenderer::PtArray* lines,
+ GrAAHairLinePathRenderer::PtArray* quads,
+ GrAAHairLinePathRenderer::PtArray* conics,
+ GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
+ GrAAHairLinePathRenderer::FloatArray* conicWeights) {
+ SkPath::Iter iter(path, false);
+
+ int totalQuadCount = 0;
+ SkRect bounds;
+ SkIRect ibounds;
+
+ bool persp = m.hasPerspective();
+
+ for (;;) {
+ GrPoint pathPts[4];
+ GrPoint devPts[4];
+ SkPath::Verb verb = iter.next(pathPts);
+ switch (verb) {
+ case SkPath::kConic_Verb: {
+ SkConic dst[4];
+ // We chop the conics to create tighter clipping to hide error
+ // that appears near max curvature of very thin conics. Thin
+ // hyperbolas with high weight still show error.
+ int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
+ for (int i = 0; i < conicCnt; ++i) {
+ SkPoint* chopPnts = dst[i].fPts;
+ m.mapPoints(devPts, chopPnts, 3);
+ bounds.setBounds(devPts, 3);
+ bounds.outset(SK_Scalar1, SK_Scalar1);
+ bounds.roundOut(&ibounds);
+ if (SkIRect::Intersects(devClipBounds, ibounds)) {
+ if (is_degen_quad_or_conic(devPts)) {
+ SkPoint* pts = lines->push_back_n(4);
+ pts[0] = devPts[0];
+ pts[1] = devPts[1];
+ pts[2] = devPts[1];
+ pts[3] = devPts[2];
+ } else {
+ // when in perspective keep conics in src space
+ SkPoint* cPts = persp ? chopPnts : devPts;
+ SkPoint* pts = conics->push_back_n(3);
+ pts[0] = cPts[0];
+ pts[1] = cPts[1];
+ pts[2] = cPts[2];
+ conicWeights->push_back() = dst[i].fW;
+ }
+ }
+ }
+ break;
+ }
+ case SkPath::kMove_Verb:
+ break;
+ case SkPath::kLine_Verb:
+ m.mapPoints(devPts, pathPts, 2);
+ bounds.setBounds(devPts, 2);
+ bounds.outset(SK_Scalar1, SK_Scalar1);
+ bounds.roundOut(&ibounds);
+ if (SkIRect::Intersects(devClipBounds, ibounds)) {
+ SkPoint* pts = lines->push_back_n(2);
+ pts[0] = devPts[0];
+ pts[1] = devPts[1];
+ }
+ break;
+ case SkPath::kQuad_Verb: {
+ SkPoint choppedPts[5];
+ // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
+ // When it is degenerate it allows the approximation with lines to work since the
+ // chop point (if there is one) will be at the parabola's vertex. In the nearly
+ // degenerate the QuadUVMatrix computed for the points is almost singular which
+ // can cause rendering artifacts.
+ int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
+ for (int i = 0; i < n; ++i) {
+ SkPoint* quadPts = choppedPts + i * 2;
+ m.mapPoints(devPts, quadPts, 3);
+ bounds.setBounds(devPts, 3);
+ bounds.outset(SK_Scalar1, SK_Scalar1);
+ bounds.roundOut(&ibounds);
+
+ if (SkIRect::Intersects(devClipBounds, ibounds)) {
+ int subdiv = num_quad_subdivs(devPts);
+ GrAssert(subdiv >= -1);
+ if (-1 == subdiv) {
+ SkPoint* pts = lines->push_back_n(4);
+ pts[0] = devPts[0];
+ pts[1] = devPts[1];
+ pts[2] = devPts[1];
+ pts[3] = devPts[2];
+ } else {
+ // when in perspective keep quads in src space
+ SkPoint* qPts = persp ? quadPts : devPts;
+ SkPoint* pts = quads->push_back_n(3);
+ pts[0] = qPts[0];
+ pts[1] = qPts[1];
+ pts[2] = qPts[2];
+ quadSubdivCnts->push_back() = subdiv;
+ totalQuadCount += 1 << subdiv;
+ }
+ }
+ }
+ break;
+ }
+ case SkPath::kCubic_Verb:
+ m.mapPoints(devPts, pathPts, 4);
+ bounds.setBounds(devPts, 4);
+ bounds.outset(SK_Scalar1, SK_Scalar1);
+ bounds.roundOut(&ibounds);
+ if (SkIRect::Intersects(devClipBounds, ibounds)) {
+ PREALLOC_PTARRAY(32) q;
+ // we don't need a direction if we aren't constraining the subdivision
+ static const SkPath::Direction kDummyDir = SkPath::kCCW_Direction;
+ // We convert cubics to quadratics (for now).
+ // In perspective have to do conversion in src space.
+ if (persp) {
+ SkScalar tolScale =
+ GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
+ path.getBounds());
+ GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q);
+ } else {
+ GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q);
+ }
+ for (int i = 0; i < q.count(); i += 3) {
+ SkPoint* qInDevSpace;
+ // bounds has to be calculated in device space, but q is
+ // in src space when there is perspective.
+ if (persp) {
+ m.mapPoints(devPts, &q[i], 3);
+ bounds.setBounds(devPts, 3);
+ qInDevSpace = devPts;
+ } else {
+ bounds.setBounds(&q[i], 3);
+ qInDevSpace = &q[i];
+ }
+ bounds.outset(SK_Scalar1, SK_Scalar1);
+ bounds.roundOut(&ibounds);
+ if (SkIRect::Intersects(devClipBounds, ibounds)) {
+ int subdiv = num_quad_subdivs(qInDevSpace);
+ GrAssert(subdiv >= -1);
+ if (-1 == subdiv) {
+ SkPoint* pts = lines->push_back_n(4);
+ // lines should always be in device coords
+ pts[0] = qInDevSpace[0];
+ pts[1] = qInDevSpace[1];
+ pts[2] = qInDevSpace[1];
+ pts[3] = qInDevSpace[2];
+ } else {
+ SkPoint* pts = quads->push_back_n(3);
+ // q is already in src space when there is no
+ // perspective and dev coords otherwise.
+ pts[0] = q[0 + i];
+ pts[1] = q[1 + i];
+ pts[2] = q[2 + i];
+ quadSubdivCnts->push_back() = subdiv;
+ totalQuadCount += 1 << subdiv;
+ }
+ }
+ }
+ }
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ case SkPath::kDone_Verb:
+ return totalQuadCount;
+ }
+ }
+}
+
+struct LineVertex {
+ GrPoint fPos;
+ GrColor fCoverage;
+};
+
+struct BezierVertex {
+ GrPoint fPos;
+ union {
+ struct {
+ SkScalar fK;
+ SkScalar fL;
+ SkScalar fM;
+ } fConic;
+ GrVec fQuadCoord;
+ struct {
+ SkScalar fBogus[4];
+ };
+ };
+};
+
+GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(GrPoint));
+
+void intersect_lines(const SkPoint& ptA, const SkVector& normA,
+ const SkPoint& ptB, const SkVector& normB,
+ SkPoint* result) {
+
+ SkScalar lineAW = -normA.dot(ptA);
+ SkScalar lineBW = -normB.dot(ptB);
+
+ SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
+ SkScalarMul(normA.fY, normB.fX);
+ wInv = SkScalarInvert(wInv);
+
+ result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
+ result->fX = SkScalarMul(result->fX, wInv);
+
+ result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
+ result->fY = SkScalarMul(result->fY, wInv);
+}
+
+void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kVertsPerQuad]) {
+ // this should be in the src space, not dev coords, when we have perspective
+ GrPathUtils::QuadUVMatrix DevToUV(qpts);
+ DevToUV.apply<kVertsPerQuad, sizeof(BezierVertex), sizeof(GrPoint)>(verts);
+}
+
+void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
+ const SkMatrix* toSrc, BezierVertex verts[kVertsPerQuad],
+ SkRect* devBounds) {
+ GrAssert(!toDevice == !toSrc);
+ // original quad is specified by tri a,b,c
+ SkPoint a = qpts[0];
+ SkPoint b = qpts[1];
+ SkPoint c = qpts[2];
+
+ if (toDevice) {
+ toDevice->mapPoints(&a, 1);
+ toDevice->mapPoints(&b, 1);
+ toDevice->mapPoints(&c, 1);
+ }
+ // make a new poly where we replace a and c by a 1-pixel wide edges orthog
+ // to edges ab and bc:
+ //
+ // before | after
+ // | b0
+ // b |
+ // |
+ // | a0 c0
+ // a c | a1 c1
+ //
+ // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
+ // respectively.
+ BezierVertex& a0 = verts[0];
+ BezierVertex& a1 = verts[1];
+ BezierVertex& b0 = verts[2];
+ BezierVertex& c0 = verts[3];
+ BezierVertex& c1 = verts[4];
+
+ SkVector ab = b;
+ ab -= a;
+ SkVector ac = c;
+ ac -= a;
+ SkVector cb = b;
+ cb -= c;
+
+ // We should have already handled degenerates
+ GrAssert(ab.length() > 0 && cb.length() > 0);
+
+ ab.normalize();
+ SkVector abN;
+ abN.setOrthog(ab, SkVector::kLeft_Side);
+ if (abN.dot(ac) > 0) {
+ abN.negate();
+ }
+
+ cb.normalize();
+ SkVector cbN;
+ cbN.setOrthog(cb, SkVector::kLeft_Side);
+ if (cbN.dot(ac) < 0) {
+ cbN.negate();
+ }
+
+ a0.fPos = a;
+ a0.fPos += abN;
+ a1.fPos = a;
+ a1.fPos -= abN;
+
+ c0.fPos = c;
+ c0.fPos += cbN;
+ c1.fPos = c;
+ c1.fPos -= cbN;
+
+ // This point may not be within 1 pixel of a control point. We update the bounding box to
+ // include it.
+ intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
+ devBounds->growToInclude(b0.fPos.fX, b0.fPos.fY);
+
+ if (toSrc) {
+ toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kVertsPerQuad);
+ }
+}
+
+// Input:
+// Three control points: p[0], p[1], p[2] and weight: w
+// Output:
+// Let:
+// l = (2*w * (y1 - y0), 2*w * (x0 - x1), 2*w * (x1*y0 - x0*y1))
+// m = (2*w * (y2 - y1), 2*w * (x1 - x2), 2*w * (x2*y1 - x1*y2))
+// k = (y2 - y0, x0 - x2, (x2 - x0)*y0 - (y2 - y0)*x0 )
+void calc_conic_klm(const SkPoint p[3], const SkScalar weight,
+ SkScalar k[3], SkScalar l[3], SkScalar m[3]) {
+ const SkScalar w2 = 2 * weight;
+ l[0] = w2 * (p[1].fY - p[0].fY);
+ l[1] = w2 * (p[0].fX - p[1].fX);
+ l[2] = w2 * (p[1].fX * p[0].fY - p[0].fX * p[1].fY);
+
+ m[0] = w2 * (p[2].fY - p[1].fY);
+ m[1] = w2 * (p[1].fX - p[2].fX);
+ m[2] = w2 * (p[2].fX * p[1].fY - p[1].fX * p[2].fY);
+
+ k[0] = p[2].fY - p[0].fY;
+ k[1] = p[0].fX - p[2].fX;
+ k[2] = (p[2].fX - p[0].fX) * p[0].fY - (p[2].fY - p[0].fY) * p[0].fX;
+
+ // scale the max absolute value of coeffs to 10
+ SkScalar scale = 0.0f;
+ for (int i = 0; i < 3; ++i) {
+ scale = SkMaxScalar(scale, SkScalarAbs(k[i]));
+ scale = SkMaxScalar(scale, SkScalarAbs(l[i]));
+ scale = SkMaxScalar(scale, SkScalarAbs(m[i]));
+ }
+ GrAssert(scale > 0);
+ scale /= 10.0f;
+ k[0] /= scale;
+ k[1] /= scale;
+ k[2] /= scale;
+ l[0] /= scale;
+ l[1] /= scale;
+ l[2] /= scale;
+ m[0] /= scale;
+ m[1] /= scale;
+ m[2] /= scale;
+}
+
+// Equations based off of Loop-Blinn Quadratic GPU Rendering
+// Input Parametric:
+// P(t) = (P0*(1-t)^2 + 2*w*P1*t*(1-t) + P2*t^2) / (1-t)^2 + 2*w*t*(1-t) + t^2)
+// Output Implicit:
+// f(x, y, w) = f(P) = K^2 - LM
+// K = dot(k, P), L = dot(l, P), M = dot(m, P)
+// k, l, m are calculated in function calc_conic_klm
+void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kVertsPerQuad], const float weight) {
+ SkScalar k[3];
+ SkScalar l[3];
+ SkScalar m[3];
+
+ calc_conic_klm(p, weight, k, l, m);
+
+ for (int i = 0; i < kVertsPerQuad; ++i) {
+ const SkPoint pnt = verts[i].fPos;
+ verts[i].fConic.fK = pnt.fX * k[0] + pnt.fY * k[1] + k[2];
+ verts[i].fConic.fL = pnt.fX * l[0] + pnt.fY * l[1] + l[2];
+ verts[i].fConic.fM = pnt.fX * m[0] + pnt.fY * m[1] + m[2];
+ }
+}
+
+void add_conics(const SkPoint p[3],
+ float weight,
+ const SkMatrix* toDevice,
+ const SkMatrix* toSrc,
+ BezierVertex** vert,
+ SkRect* devBounds) {
+ bloat_quad(p, toDevice, toSrc, *vert, devBounds);
+ set_conic_coeffs(p, *vert, weight);
+ *vert += kVertsPerQuad;
+}
+
+void add_quads(const SkPoint p[3],
+ int subdiv,
+ const SkMatrix* toDevice,
+ const SkMatrix* toSrc,
+ BezierVertex** vert,
+ SkRect* devBounds) {
+ GrAssert(subdiv >= 0);
+ if (subdiv) {
+ SkPoint newP[5];
+ SkChopQuadAtHalf(p, newP);
+ add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert, devBounds);
+ add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert, devBounds);
+ } else {
+ bloat_quad(p, toDevice, toSrc, *vert, devBounds);
+ set_uv_quad(p, *vert);
+ *vert += kVertsPerQuad;
+ }
+}
+
+void add_line(const SkPoint p[2],
+ int rtHeight,
+ const SkMatrix* toSrc,
+ GrColor coverage,
+ LineVertex** vert) {
+ const SkPoint& a = p[0];
+ const SkPoint& b = p[1];
+
+ SkVector orthVec = b;
+ orthVec -= a;
+
+ if (orthVec.setLength(SK_Scalar1)) {
+ orthVec.setOrthog(orthVec);
+
+ for (int i = 0; i < kVertsPerLineSeg; ++i) {
+ (*vert)[i].fPos = (i & 0x1) ? b : a;
+ if (i & 0x2) {
+ (*vert)[i].fPos += orthVec;
+ (*vert)[i].fCoverage = 0;
+ } else if (i & 0x4) {
+ (*vert)[i].fPos -= orthVec;
+ (*vert)[i].fCoverage = 0;
+ } else {
+ (*vert)[i].fCoverage = coverage;
+ }
+ }
+ if (NULL != toSrc) {
+ toSrc->mapPointsWithStride(&(*vert)->fPos,
+ sizeof(LineVertex),
+ kVertsPerLineSeg);
+ }
+ } else {
+ // just make it degenerate and likely offscreen
+ for (int i = 0; i < kVertsPerLineSeg; ++i) {
+ (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax);
+ }
+ }
+
+ *vert += kVertsPerLineSeg;
+}
+
+}
+
+/**
+ * Shader is based off of Loop-Blinn Quadratic GPU Rendering
+ * The output of this effect is a hairline edge for conics.
+ * Conics specified by implicit equation K^2 - LM.
+ * K, L, and M, are the first three values of the vertex attribute,
+ * the fourth value is not used. Distance is calculated using a
+ * first order approximation from the taylor series.
+ * Coverage is max(0, 1-distance).
+ */
+
+/**
+ * Test were also run using a second order distance approximation.
+ * There were two versions of the second order approx. The first version
+ * is of roughly the form:
+ * f(q) = |f(p)| - ||f'(p)||*||q-p|| - ||f''(p)||*||q-p||^2.
+ * The second is similar:
+ * f(q) = |f(p)| + ||f'(p)||*||q-p|| + ||f''(p)||*||q-p||^2.
+ * The exact version of the equations can be found in the paper
+ * "Distance Approximations for Rasterizing Implicit Curves" by Gabriel Taubin
+ *
+ * In both versions we solve the quadratic for ||q-p||.
+ * Version 1:
+ * gFM is magnitude of first partials and gFM2 is magnitude of 2nd partials (as derived from paper)
+ * builder->fsCodeAppend("\t\tedgeAlpha = (sqrt(gFM*gFM+4.0*func*gF2M) - gFM)/(2.0*gF2M);\n");
+ * Version 2:
+ * builder->fsCodeAppend("\t\tedgeAlpha = (gFM - sqrt(gFM*gFM-4.0*func*gF2M))/(2.0*gF2M);\n");
+ *
+ * Also note that 2nd partials of k,l,m are zero
+ *
+ * When comparing the two second order approximations to the first order approximations,
+ * the following results were found. Version 1 tends to underestimate the distances, thus it
+ * basically increases all the error that we were already seeing in the first order
+ * approx. So this version is not the one to use. Version 2 has the opposite effect
+ * and tends to overestimate the distances. This is much closer to what we are
+ * looking for. It is able to render ellipses (even thin ones) without the need to chop.
+ * However, it can not handle thin hyperbolas well and thus would still rely on
+ * chopping to tighten the clipping. Another side effect of the overestimating is
+ * that the curves become much thinner and "ropey". If all that was ever rendered
+ * were "not too thin" curves and ellipses then 2nd order may have an advantage since
+ * only one geometry would need to be rendered. However no benches were run comparing
+ * chopped first order and non chopped 2nd order.
+ */
+class HairConicEdgeEffect : public GrEffect {
+public:
+ static GrEffectRef* Create() {
+ GR_CREATE_STATIC_EFFECT(gHairConicEdgeEffect, HairConicEdgeEffect, ());
+ gHairConicEdgeEffect->ref();
+ return gHairConicEdgeEffect;
+ }
+
+ virtual ~HairConicEdgeEffect() {}
+
+ static const char* Name() { return "HairConicEdge"; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<HairConicEdgeEffect>::getInstance();
+ }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char *vsName, *fsName;
+
+ SkAssertResult(builder->enableFeature(
+ GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
+ builder->addVarying(kVec4f_GrSLType, "ConicCoeffs",
+ &vsName, &fsName);
+ const SkString* attr0Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsName, attr0Name->c_str());
+
+ builder->fsCodeAppend("\t\tfloat edgeAlpha;\n");
+
+ builder->fsCodeAppendf("\t\tvec3 dklmdx = dFdx(%s.xyz);\n", fsName);
+ builder->fsCodeAppendf("\t\tvec3 dklmdy = dFdy(%s.xyz);\n", fsName);
+ builder->fsCodeAppendf("\t\tfloat dfdx =\n"
+ "\t\t\t2.0*%s.x*dklmdx.x - %s.y*dklmdx.z - %s.z*dklmdx.y;\n",
+ fsName, fsName, fsName);
+ builder->fsCodeAppendf("\t\tfloat dfdy =\n"
+ "\t\t\t2.0*%s.x*dklmdy.x - %s.y*dklmdy.z - %s.z*dklmdy.y;\n",
+ fsName, fsName, fsName);
+ builder->fsCodeAppend("\t\tvec2 gF = vec2(dfdx, dfdy);\n");
+ builder->fsCodeAppend("\t\tfloat gFM = sqrt(dot(gF, gF));\n");
+ builder->fsCodeAppendf("\t\tfloat func = abs(%s.x*%s.x - %s.y*%s.z);\n", fsName, fsName,
+ fsName, fsName);
+ builder->fsCodeAppend("\t\tedgeAlpha = func / gFM;\n");
+ builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
+ // Add line below for smooth cubic ramp
+ // builder->fsCodeAppend("\t\tedgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);\n");
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return 0x0;
+ }
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+private:
+ HairConicEdgeEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ return true;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(HairConicEdgeEffect);
+
+GrEffectRef* HairConicEdgeEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps& caps,
+ GrTexture*[]) {
+ return caps.shaderDerivativeSupport() ? HairConicEdgeEffect::Create() : NULL;
+}
+
+/**
+ * The output of this effect is a hairline edge for quadratics.
+ * Quadratic specified by 0=u^2-v canonical coords. u and v are the first
+ * two components of the vertex attribute. Uses unsigned distance.
+ * Coverage is min(0, 1-distance). 3rd & 4th component unused.
+ * Requires shader derivative instruction support.
+ */
+class HairQuadEdgeEffect : public GrEffect {
+public:
+
+ static GrEffectRef* Create() {
+ GR_CREATE_STATIC_EFFECT(gHairQuadEdgeEffect, HairQuadEdgeEffect, ());
+ gHairQuadEdgeEffect->ref();
+ return gHairQuadEdgeEffect;
+ }
+
+ virtual ~HairQuadEdgeEffect() {}
+
+ static const char* Name() { return "HairQuadEdge"; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<HairQuadEdgeEffect>::getInstance();
+ }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char *vsName, *fsName;
+ const SkString* attrName =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n");
+
+ SkAssertResult(builder->enableFeature(
+ GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
+ builder->addVarying(kVec4f_GrSLType, "HairQuadEdge", &vsName, &fsName);
+
+ builder->fsCodeAppendf("\t\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
+ builder->fsCodeAppendf("\t\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
+ builder->fsCodeAppendf("\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->fsCodeAppendf("\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName,
+ fsName);
+ builder->fsCodeAppend("\t\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
+ builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+
+ builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return 0x0;
+ }
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+private:
+ HairQuadEdgeEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ return true;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(HairQuadEdgeEffect);
+
+GrEffectRef* HairQuadEdgeEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps& caps,
+ GrTexture*[]) {
+ // Doesn't work without derivative instructions.
+ return caps.shaderDerivativeSupport() ? HairQuadEdgeEffect::Create() : NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// position + edge
+extern const GrVertexAttrib gHairlineBezierAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
+};
+
+// position + coverage
+extern const GrVertexAttrib gHairlineLineAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kCoverage_GrVertexAttribBinding},
+};
+
+};
+
+bool GrAAHairLinePathRenderer::createLineGeom(
+ const SkPath& path,
+ GrDrawTarget* target,
+ const PtArray& lines,
+ int lineCnt,
+ GrDrawTarget::AutoReleaseGeometry* arg,
+ SkRect* devBounds) {
+ GrDrawState* drawState = target->drawState();
+ int rtHeight = drawState->getRenderTarget()->height();
+
+ const SkMatrix& viewM = drawState->getViewMatrix();
+
+ *devBounds = path.getBounds();
+ viewM.mapRect(devBounds);
+ devBounds->outset(SK_Scalar1, SK_Scalar1);
+
+ int vertCnt = kVertsPerLineSeg * lineCnt;
+
+ target->drawState()->setVertexAttribs<gHairlineLineAttribs>(SK_ARRAY_COUNT(gHairlineLineAttribs));
+ GrAssert(sizeof(LineVertex) == target->getDrawState().getVertexSize());
+
+ if (!arg->set(target, vertCnt, 0)) {
+ return false;
+ }
+
+ LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices());
+
+ const SkMatrix* toSrc = NULL;
+ SkMatrix ivm;
+
+ if (viewM.hasPerspective()) {
+ if (viewM.invert(&ivm)) {
+ toSrc = &ivm;
+ }
+ }
+
+ for (int i = 0; i < lineCnt; ++i) {
+ add_line(&lines[2*i], rtHeight, toSrc, drawState->getCoverage(), &verts);
+ }
+
+ return true;
+}
+
+bool GrAAHairLinePathRenderer::createBezierGeom(
+ const SkPath& path,
+ GrDrawTarget* target,
+ const PtArray& quads,
+ int quadCnt,
+ const PtArray& conics,
+ int conicCnt,
+ const IntArray& qSubdivs,
+ const FloatArray& cWeights,
+ GrDrawTarget::AutoReleaseGeometry* arg,
+ SkRect* devBounds) {
+ GrDrawState* drawState = target->drawState();
+
+ const SkMatrix& viewM = drawState->getViewMatrix();
+
+ // All the vertices that we compute are within 1 of path control points with the exception of
+ // one of the bounding vertices for each quad. The add_quads() function will update the bounds
+ // for each quad added.
+ *devBounds = path.getBounds();
+ viewM.mapRect(devBounds);
+ devBounds->outset(SK_Scalar1, SK_Scalar1);
+
+ int vertCnt = kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt;
+
+ target->drawState()->setVertexAttribs<gHairlineBezierAttribs>(SK_ARRAY_COUNT(gHairlineBezierAttribs));
+ GrAssert(sizeof(BezierVertex) == target->getDrawState().getVertexSize());
+
+ if (!arg->set(target, vertCnt, 0)) {
+ return false;
+ }
+
+ BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices());
+
+ const SkMatrix* toDevice = NULL;
+ const SkMatrix* toSrc = NULL;
+ SkMatrix ivm;
+
+ if (viewM.hasPerspective()) {
+ if (viewM.invert(&ivm)) {
+ toDevice = &viewM;
+ toSrc = &ivm;
+ }
+ }
+
+ int unsubdivQuadCnt = quads.count() / 3;
+ for (int i = 0; i < unsubdivQuadCnt; ++i) {
+ GrAssert(qSubdivs[i] >= 0);
+ add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds);
+ }
+
+ // Start Conics
+ for (int i = 0; i < conicCnt; ++i) {
+ add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds);
+ }
+ return true;
+}
+
+bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const {
+ if (!stroke.isHairlineStyle() || !antiAlias) {
+ return false;
+ }
+
+ if (SkPath::kLine_SegmentMask == path.getSegmentMasks() ||
+ target->caps()->shaderDerivativeSupport()) {
+ return true;
+ }
+ return false;
+}
+
+template <class VertexType>
+bool check_bounds(GrDrawState* drawState, const SkRect& devBounds, void* vertices, int vCount)
+{
+ SkRect tolDevBounds = devBounds;
+ tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
+ SkRect actualBounds;
+
+ VertexType* verts = reinterpret_cast<VertexType*>(vertices);
+ bool first = true;
+ for (int i = 0; i < vCount; ++i) {
+ SkPoint pos = verts[i].fPos;
+ // This is a hack to workaround the fact that we move some degenerate segments offscreen.
+ if (SK_ScalarMax == pos.fX) {
+ continue;
+ }
+ drawState->getViewMatrix().mapPoints(&pos, 1);
+ if (first) {
+ actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY);
+ first = false;
+ } else {
+ actualBounds.growToInclude(pos.fX, pos.fY);
+ }
+ }
+ if (!first) {
+ return tolDevBounds.contains(actualBounds);
+ }
+
+ return true;
+}
+
+bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path,
+ const SkStrokeRec&,
+ GrDrawTarget* target,
+ bool antiAlias) {
+
+ GrDrawState* drawState = target->drawState();
+
+ SkIRect devClipBounds;
+ target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devClipBounds);
+
+ int lineCnt;
+ int quadCnt;
+ int conicCnt;
+ PREALLOC_PTARRAY(128) lines;
+ PREALLOC_PTARRAY(128) quads;
+ PREALLOC_PTARRAY(128) conics;
+ IntArray qSubdivs;
+ FloatArray cWeights;
+ quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClipBounds,
+ &lines, &quads, &conics, &qSubdivs, &cWeights);
+ lineCnt = lines.count() / 2;
+ conicCnt = conics.count() / 3;
+
+ // do lines first
+ {
+ GrDrawTarget::AutoReleaseGeometry arg;
+ SkRect devBounds;
+
+ if (!this->createLineGeom(path,
+ target,
+ lines,
+ lineCnt,
+ &arg,
+ &devBounds)) {
+ return false;
+ }
+
+ GrDrawTarget::AutoStateRestore asr;
+
+ // createGeom transforms the geometry to device space when the matrix does not have
+ // perspective.
+ if (target->getDrawState().getViewMatrix().hasPerspective()) {
+ asr.set(target, GrDrawTarget::kPreserve_ASRInit);
+ } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
+ return false;
+ }
+ GrDrawState* drawState = target->drawState();
+
+ // Check devBounds
+ SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(),
+ kVertsPerLineSeg * lineCnt));
+
+ {
+ GrDrawState::AutoRestoreEffects are(drawState);
+ target->setIndexSourceToBuffer(fLinesIndexBuffer);
+ int lines = 0;
+ while (lines < lineCnt) {
+ int n = GrMin(lineCnt - lines, kNumLineSegsInIdxBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType,
+ kVertsPerLineSeg*lines, // startV
+ 0, // startI
+ kVertsPerLineSeg*n, // vCount
+ kIdxsPerLineSeg*n,
+ &devBounds); // iCount
+ lines += n;
+ }
+ }
+ }
+
+ // then quadratics/conics
+ {
+ GrDrawTarget::AutoReleaseGeometry arg;
+ SkRect devBounds;
+
+ if (!this->createBezierGeom(path,
+ target,
+ quads,
+ quadCnt,
+ conics,
+ conicCnt,
+ qSubdivs,
+ cWeights,
+ &arg,
+ &devBounds)) {
+ return false;
+ }
+
+ GrDrawTarget::AutoStateRestore asr;
+
+ // createGeom transforms the geometry to device space when the matrix does not have
+ // perspective.
+ if (target->getDrawState().getViewMatrix().hasPerspective()) {
+ asr.set(target, GrDrawTarget::kPreserve_ASRInit);
+ } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
+ return false;
+ }
+ GrDrawState* drawState = target->drawState();
+
+ static const int kEdgeAttrIndex = 1;
+
+ GrEffectRef* hairQuadEffect = HairQuadEdgeEffect::Create();
+ GrEffectRef* hairConicEffect = HairConicEdgeEffect::Create();
+
+ // Check devBounds
+ SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices(),
+ kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt));
+
+ {
+ GrDrawState::AutoRestoreEffects are(drawState);
+ target->setIndexSourceToBuffer(fQuadsIndexBuffer);
+ int quads = 0;
+ drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref();
+ while (quads < quadCnt) {
+ int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType,
+ kVertsPerQuad*quads, // startV
+ 0, // startI
+ kVertsPerQuad*n, // vCount
+ kIdxsPerQuad*n, // iCount
+ &devBounds);
+ quads += n;
+ }
+ }
+
+ {
+ GrDrawState::AutoRestoreEffects are(drawState);
+ int conics = 0;
+ drawState->addCoverageEffect(hairConicEffect, 1, 2)->unref();
+ while (conics < conicCnt) {
+ int n = GrMin(conicCnt - conics, kNumQuadsInIdxBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType,
+ kVertsPerQuad*(quadCnt + conics), // startV
+ 0, // startI
+ kVertsPerQuad*n, // vCount
+ kIdxsPerQuad*n, // iCount
+ &devBounds);
+ conics += n;
+ }
+ }
+ }
+
+ target->resetIndexSource();
+
+ return true;
+}
diff --git a/gpu/GrAAHairLinePathRenderer.h b/gpu/GrAAHairLinePathRenderer.h
new file mode 100644
index 00000000..c8a3eae7
--- /dev/null
+++ b/gpu/GrAAHairLinePathRenderer.h
@@ -0,0 +1,65 @@
+
+/*
+ * 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 GrAAHairLinePathRenderer_DEFINED
+#define GrAAHairLinePathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+
+class GrAAHairLinePathRenderer : public GrPathRenderer {
+public:
+ virtual ~GrAAHairLinePathRenderer();
+
+ static GrPathRenderer* Create(GrContext* context);
+
+ virtual bool canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const SK_OVERRIDE;
+
+ typedef SkTArray<SkPoint, true> PtArray;
+ typedef SkTArray<int, true> IntArray;
+ typedef SkTArray<float, true> FloatArray;
+
+protected:
+ virtual bool onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) SK_OVERRIDE;
+
+private:
+ GrAAHairLinePathRenderer(const GrContext* context,
+ const GrIndexBuffer* fLinesIndexBuffer,
+ const GrIndexBuffer* fQuadsIndexBuffer);
+
+ bool createLineGeom(const SkPath& path,
+ GrDrawTarget* target,
+ const PtArray& lines,
+ int lineCnt,
+ GrDrawTarget::AutoReleaseGeometry* arg,
+ SkRect* devBounds);
+
+ bool createBezierGeom(const SkPath& path,
+ GrDrawTarget* target,
+ const PtArray& quads,
+ int quadCnt,
+ const PtArray& conics,
+ int conicCnt,
+ const IntArray& qSubdivs,
+ const FloatArray& cWeights,
+ GrDrawTarget::AutoReleaseGeometry* arg,
+ SkRect* devBounds);
+
+ const GrIndexBuffer* fLinesIndexBuffer;
+ const GrIndexBuffer* fQuadsIndexBuffer;
+
+ typedef GrPathRenderer INHERITED;
+};
+
+
+#endif
diff --git a/gpu/GrAARectRenderer.cpp b/gpu/GrAARectRenderer.cpp
new file mode 100644
index 00000000..3a6ff303
--- /dev/null
+++ b/gpu/GrAARectRenderer.cpp
@@ -0,0 +1,812 @@
+/*
+ * 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 "GrAARectRenderer.h"
+#include "GrRefCnt.h"
+#include "GrGpu.h"
+#include "gl/GrGLEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkColorPriv.h"
+
+SK_DEFINE_INST_COUNT(GrAARectRenderer)
+
+///////////////////////////////////////////////////////////////////////////////
+class GrGLAlignedRectEffect;
+
+// Axis Aligned special case
+class GrAlignedRectEffect : public GrEffect {
+public:
+ static GrEffectRef* Create() {
+ GR_CREATE_STATIC_EFFECT(gAlignedRectEffect, GrAlignedRectEffect, ());
+ gAlignedRectEffect->ref();
+ return gAlignedRectEffect;
+ }
+
+ virtual ~GrAlignedRectEffect() {}
+
+ static const char* Name() { return "AlignedRectEdge"; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrAlignedRectEffect>::getInstance();
+ }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ // setup the varying for the Axis aligned rect effect
+ // xy -> interpolated offset
+ // zw -> w/2+0.5, h/2+0.5
+ const char *vsRectName, *fsRectName;
+ builder->addVarying(kVec4f_GrSLType, "Rect", &vsRectName, &fsRectName);
+ const SkString* attr0Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsRectName, attr0Name->c_str());
+
+ // TODO: compute all these offsets, spans, and scales in the VS
+ builder->fsCodeAppendf("\tfloat insetW = min(1.0, %s.z) - 0.5;\n", fsRectName);
+ builder->fsCodeAppendf("\tfloat insetH = min(1.0, %s.w) - 0.5;\n", fsRectName);
+ builder->fsCodeAppend("\tfloat outset = 0.5;\n");
+ // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects
+ // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
+ builder->fsCodeAppend("\tfloat spanW = insetW + outset;\n");
+ builder->fsCodeAppend("\tfloat spanH = insetH + outset;\n");
+ // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
+ // value of coverage that is used. In other words it is the coverage that is
+ // used in the interior of the rect after the ramp.
+ builder->fsCodeAppend("\tfloat scaleW = min(1.0, 2.0*insetW/spanW);\n");
+ builder->fsCodeAppend("\tfloat scaleH = min(1.0, 2.0*insetH/spanH);\n");
+
+ // Compute the coverage for the rect's width
+ builder->fsCodeAppendf(
+ "\tfloat coverage = scaleW*clamp((%s.z-abs(%s.x))/spanW, 0.0, 1.0);\n", fsRectName,
+ fsRectName);
+ // Compute the coverage for the rect's height and merge with the width
+ builder->fsCodeAppendf(
+ "\tcoverage = coverage*scaleH*clamp((%s.w-abs(%s.y))/spanH, 0.0, 1.0);\n",
+ fsRectName, fsRectName);
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "coverage");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return 0;
+ }
+
+ virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+
+private:
+ GrAlignedRectEffect() : GrEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ }
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE { return true; }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+
+GR_DEFINE_EFFECT_TEST(GrAlignedRectEffect);
+
+GrEffectRef* GrAlignedRectEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ return GrAlignedRectEffect::Create();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+class GrGLRectEffect;
+
+/**
+ * The output of this effect is a modulation of the input color and coverage
+ * for an arbitrarily oriented rect. The rect is specified as:
+ * Center of the rect
+ * Unit vector point down the height of the rect
+ * Half width + 0.5
+ * Half height + 0.5
+ * The center and vector are stored in a vec4 varying ("RectEdge") with the
+ * center in the xy components and the vector in the zw components.
+ * The munged width and height are stored in a vec2 varying ("WidthHeight")
+ * with the width in x and the height in y.
+ */
+class GrRectEffect : public GrEffect {
+public:
+ static GrEffectRef* Create() {
+ GR_CREATE_STATIC_EFFECT(gRectEffect, GrRectEffect, ());
+ gRectEffect->ref();
+ return gRectEffect;
+ }
+
+ virtual ~GrRectEffect() {}
+
+ static const char* Name() { return "RectEdge"; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRectEffect>::getInstance();
+ }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ // setup the varying for the center point and the unit vector
+ // that points down the height of the rect
+ const char *vsRectEdgeName, *fsRectEdgeName;
+ builder->addVarying(kVec4f_GrSLType, "RectEdge",
+ &vsRectEdgeName, &fsRectEdgeName);
+ const SkString* attr0Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsRectEdgeName, attr0Name->c_str());
+
+ // setup the varying for width/2+.5 and height/2+.5
+ const char *vsWidthHeightName, *fsWidthHeightName;
+ builder->addVarying(kVec2f_GrSLType, "WidthHeight",
+ &vsWidthHeightName, &fsWidthHeightName);
+ const SkString* attr1Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[1]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsWidthHeightName, attr1Name->c_str());
+
+ // TODO: compute all these offsets, spans, and scales in the VS
+ builder->fsCodeAppendf("\tfloat insetW = min(1.0, %s.x) - 0.5;\n", fsWidthHeightName);
+ builder->fsCodeAppendf("\tfloat insetH = min(1.0, %s.y) - 0.5;\n", fsWidthHeightName);
+ builder->fsCodeAppend("\tfloat outset = 0.5;\n");
+ // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects
+ // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
+ builder->fsCodeAppend("\tfloat spanW = insetW + outset;\n");
+ builder->fsCodeAppend("\tfloat spanH = insetH + outset;\n");
+ // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
+ // value of coverage that is used. In other words it is the coverage that is
+ // used in the interior of the rect after the ramp.
+ builder->fsCodeAppend("\tfloat scaleW = min(1.0, 2.0*insetW/spanW);\n");
+ builder->fsCodeAppend("\tfloat scaleH = min(1.0, 2.0*insetH/spanH);\n");
+
+ // Compute the coverage for the rect's width
+ builder->fsCodeAppendf("\tvec2 offset = %s.xy - %s.xy;\n",
+ builder->fragmentPosition(), fsRectEdgeName);
+ builder->fsCodeAppendf("\tfloat perpDot = abs(offset.x * %s.w - offset.y * %s.z);\n",
+ fsRectEdgeName, fsRectEdgeName);
+ builder->fsCodeAppendf(
+ "\tfloat coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0);\n",
+ fsWidthHeightName);
+
+ // Compute the coverage for the rect's height and merge with the width
+ builder->fsCodeAppendf("\tperpDot = abs(dot(offset, %s.zw));\n",
+ fsRectEdgeName);
+ builder->fsCodeAppendf(
+ "\tcoverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0.0, 1.0);\n",
+ fsWidthHeightName);
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "coverage");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return 0;
+ }
+
+ virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+
+private:
+ GrRectEffect() : GrEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ this->addVertexAttrib(kVec2f_GrSLType);
+ this->setWillReadFragmentPosition();
+ }
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE { return true; }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+
+GR_DEFINE_EFFECT_TEST(GrRectEffect);
+
+GrEffectRef* GrRectEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ return GrRectEffect::Create();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+extern const GrVertexAttrib gAARectCoverageAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kCoverage_GrVertexAttribBinding},
+};
+
+extern const GrVertexAttrib gAARectColorAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kColor_GrVertexAttribBinding},
+};
+
+static void set_aa_rect_vertex_attributes(GrDrawState* drawState, bool useCoverage) {
+ if (useCoverage) {
+ drawState->setVertexAttribs<gAARectCoverageAttribs>(SK_ARRAY_COUNT(gAARectCoverageAttribs));
+ } else {
+ drawState->setVertexAttribs<gAARectColorAttribs>(SK_ARRAY_COUNT(gAARectColorAttribs));
+ }
+}
+
+static void set_inset_fan(GrPoint* pts, size_t stride,
+ const SkRect& r, SkScalar dx, SkScalar dy) {
+ pts->setRectFan(r.fLeft + dx, r.fTop + dy,
+ r.fRight - dx, r.fBottom - dy, stride);
+}
+
+};
+
+void GrAARectRenderer::reset() {
+ GrSafeSetNull(fAAFillRectIndexBuffer);
+ GrSafeSetNull(fAAStrokeRectIndexBuffer);
+}
+
+static const uint16_t gFillAARectIdx[] = {
+ 0, 1, 5, 5, 4, 0,
+ 1, 2, 6, 6, 5, 1,
+ 2, 3, 7, 7, 6, 2,
+ 3, 0, 4, 4, 7, 3,
+ 4, 5, 6, 6, 7, 4,
+};
+
+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(kAAFillRectIndexBufferSize, false);
+ if (NULL != fAAFillRectIndexBuffer) {
+ 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;
+}
+
+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,
+ 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0,
+
+ 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4,
+ 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4,
+ 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4,
+ 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4,
+
+ 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8,
+ 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8,
+ 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8,
+ 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8,
+};
+
+int GrAARectRenderer::aaStrokeRectIndexCount() {
+ return GR_ARRAY_COUNT(gStrokeAARectIdx);
+}
+
+GrIndexBuffer* GrAARectRenderer::aaStrokeRectIndexBuffer(GrGpu* gpu) {
+ if (NULL == fAAStrokeRectIndexBuffer) {
+ fAAStrokeRectIndexBuffer =
+ gpu->createIndexBuffer(sizeof(gStrokeAARectIdx), false);
+ if (NULL != fAAStrokeRectIndexBuffer) {
+#if GR_DEBUG
+ bool updated =
+#endif
+ fAAStrokeRectIndexBuffer->updateData(gStrokeAARectIdx,
+ sizeof(gStrokeAARectIdx));
+ GR_DEBUGASSERT(updated);
+ }
+ }
+ return fAAStrokeRectIndexBuffer;
+}
+
+void GrAARectRenderer::geometryFillAARect(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect& rect,
+ const SkMatrix& combinedMatrix,
+ const SkRect& devRect,
+ bool useVertexCoverage) {
+ GrDrawState* drawState = target->drawState();
+
+ set_aa_rect_vertex_attributes(drawState, useVertexCoverage);
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 8, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ GrIndexBuffer* indexBuffer = this->aaFillRectIndexBuffer(gpu);
+ if (NULL == indexBuffer) {
+ GrPrintf("Failed to create index buffer!\n");
+ return;
+ }
+
+ intptr_t verts = reinterpret_cast<intptr_t>(geo.vertices());
+ size_t vsize = drawState->getVertexSize();
+ GrAssert(sizeof(GrPoint) + sizeof(GrColor) == vsize);
+
+ GrPoint* fan0Pos = reinterpret_cast<GrPoint*>(verts);
+ GrPoint* fan1Pos = reinterpret_cast<GrPoint*>(verts + 4 * vsize);
+
+ SkScalar inset = SkMinScalar(devRect.width(), SK_Scalar1);
+ inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height());
+
+ if (combinedMatrix.rectStaysRect()) {
+ // Temporarily #if'ed out. We don't want to pass in the devRect but
+ // right now it is computed in GrContext::apply_aa_to_rect and we don't
+ // want to throw away the work
+#if 0
+ SkRect devRect;
+ combinedMatrix.mapRect(&devRect, rect);
+#endif
+
+ set_inset_fan(fan0Pos, vsize, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
+ set_inset_fan(fan1Pos, vsize, devRect, inset, inset);
+ } else {
+ // compute transformed (1, 0) and (0, 1) vectors
+ SkVector vec[2] = {
+ { combinedMatrix[SkMatrix::kMScaleX], combinedMatrix[SkMatrix::kMSkewY] },
+ { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] }
+ };
+
+ vec[0].normalize();
+ vec[0].scale(SK_ScalarHalf);
+ vec[1].normalize();
+ vec[1].scale(SK_ScalarHalf);
+
+ // create the rotated rect
+ fan0Pos->setRectFan(rect.fLeft, rect.fTop,
+ rect.fRight, rect.fBottom, vsize);
+ combinedMatrix.mapPointsWithStride(fan0Pos, vsize, 4);
+
+ // Now create the inset points and then outset the original
+ // rotated points
+
+ // TL
+ *((SkPoint*)((intptr_t)fan1Pos + 0 * vsize)) =
+ *((SkPoint*)((intptr_t)fan0Pos + 0 * vsize)) + vec[0] + vec[1];
+ *((SkPoint*)((intptr_t)fan0Pos + 0 * vsize)) -= vec[0] + vec[1];
+ // BL
+ *((SkPoint*)((intptr_t)fan1Pos + 1 * vsize)) =
+ *((SkPoint*)((intptr_t)fan0Pos + 1 * vsize)) + vec[0] - vec[1];
+ *((SkPoint*)((intptr_t)fan0Pos + 1 * vsize)) -= vec[0] - vec[1];
+ // BR
+ *((SkPoint*)((intptr_t)fan1Pos + 2 * vsize)) =
+ *((SkPoint*)((intptr_t)fan0Pos + 2 * vsize)) - vec[0] - vec[1];
+ *((SkPoint*)((intptr_t)fan0Pos + 2 * vsize)) += vec[0] + vec[1];
+ // TR
+ *((SkPoint*)((intptr_t)fan1Pos + 3 * vsize)) =
+ *((SkPoint*)((intptr_t)fan0Pos + 3 * vsize)) - vec[0] + vec[1];
+ *((SkPoint*)((intptr_t)fan0Pos + 3 * vsize)) += vec[0] - vec[1];
+ }
+
+ verts += sizeof(GrPoint);
+ for (int i = 0; i < 4; ++i) {
+ *reinterpret_cast<GrColor*>(verts + i * vsize) = 0;
+ }
+
+ int scale;
+ if (inset < SK_ScalarHalf) {
+ scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
+ SkASSERT(scale >= 0 && scale <= 255);
+ } else {
+ scale = 0xff;
+ }
+
+ GrColor innerColor;
+ if (useVertexCoverage) {
+ innerColor = GrColorPackRGBA(scale, scale, scale, scale);
+ } else {
+ if (0xff == scale) {
+ innerColor = target->getDrawState().getColor();
+ } else {
+ innerColor = SkAlphaMulQ(target->getDrawState().getColor(), scale);
+ }
+ }
+
+ verts += 4 * vsize;
+ for (int i = 0; i < 4; ++i) {
+ *reinterpret_cast<GrColor*>(verts + i * vsize) = innerColor;
+ }
+
+ target->setIndexSourceToBuffer(indexBuffer);
+ target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1,
+ kVertsPerAAFillRect,
+ kIndicesPerAAFillRect);
+ target->resetIndexSource();
+}
+
+namespace {
+
+// Rotated
+struct RectVertex {
+ GrPoint fPos;
+ GrPoint fCenter;
+ GrPoint fDir;
+ GrPoint fWidthHeight;
+};
+
+// Rotated
+extern const GrVertexAttrib gAARectVertexAttribs[] = {
+ { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
+ { kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding },
+ { kVec2f_GrVertexAttribType, 3*sizeof(GrPoint), kEffect_GrVertexAttribBinding }
+};
+
+// Axis Aligned
+struct AARectVertex {
+ GrPoint fPos;
+ GrPoint fOffset;
+ GrPoint fWidthHeight;
+};
+
+// Axis Aligned
+extern const GrVertexAttrib gAAAARectVertexAttribs[] = {
+ { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
+ { kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding },
+};
+
+};
+
+void GrAARectRenderer::shaderFillAARect(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect& rect,
+ const SkMatrix& combinedMatrix) {
+ GrDrawState* drawState = target->drawState();
+
+ SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY());
+ combinedMatrix.mapPoints(&center, 1);
+
+ // compute transformed (0, 1) vector
+ SkVector dir = { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] };
+ dir.normalize();
+
+ // compute transformed (width, 0) and (0, height) vectors
+ SkVector vec[2] = {
+ { combinedMatrix[SkMatrix::kMScaleX], combinedMatrix[SkMatrix::kMSkewY] },
+ { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] }
+ };
+
+ SkScalar newWidth = SkScalarHalf(rect.width() * vec[0].length()) + SK_ScalarHalf;
+ SkScalar newHeight = SkScalarHalf(rect.height() * vec[1].length()) + SK_ScalarHalf;
+ drawState->setVertexAttribs<gAARectVertexAttribs>(SK_ARRAY_COUNT(gAARectVertexAttribs));
+ GrAssert(sizeof(RectVertex) == drawState->getVertexSize());
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ RectVertex* verts = reinterpret_cast<RectVertex*>(geo.vertices());
+
+ GrEffectRef* effect = GrRectEffect::Create();
+ static const int kRectAttrIndex = 1;
+ static const int kWidthIndex = 2;
+ drawState->addCoverageEffect(effect, kRectAttrIndex, kWidthIndex)->unref();
+
+ for (int i = 0; i < 4; ++i) {
+ verts[i].fCenter = center;
+ verts[i].fDir = dir;
+ verts[i].fWidthHeight.fX = newWidth;
+ verts[i].fWidthHeight.fY = newHeight;
+ }
+
+ SkRect devRect;
+ combinedMatrix.mapRect(&devRect, rect);
+
+ SkRect devBounds = {
+ devRect.fLeft - SK_ScalarHalf,
+ devRect.fTop - SK_ScalarHalf,
+ devRect.fRight + SK_ScalarHalf,
+ devRect.fBottom + SK_ScalarHalf
+ };
+
+ verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
+ verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
+ verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
+ verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
+
+ target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer());
+ target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
+ target->resetIndexSource();
+}
+
+void GrAARectRenderer::shaderFillAlignedAARect(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect& rect,
+ const SkMatrix& combinedMatrix) {
+ GrDrawState* drawState = target->drawState();
+ SkASSERT(combinedMatrix.rectStaysRect());
+
+ drawState->setVertexAttribs<gAAAARectVertexAttribs>(SK_ARRAY_COUNT(gAAAARectVertexAttribs));
+ GrAssert(sizeof(AARectVertex) == drawState->getVertexSize());
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ AARectVertex* verts = reinterpret_cast<AARectVertex*>(geo.vertices());
+
+ GrEffectRef* effect = GrAlignedRectEffect::Create();
+ static const int kOffsetIndex = 1;
+ drawState->addCoverageEffect(effect, kOffsetIndex)->unref();
+
+ SkRect devRect;
+ combinedMatrix.mapRect(&devRect, rect);
+
+ SkRect devBounds = {
+ devRect.fLeft - SK_ScalarHalf,
+ devRect.fTop - SK_ScalarHalf,
+ devRect.fRight + SK_ScalarHalf,
+ devRect.fBottom + SK_ScalarHalf
+ };
+
+ GrPoint widthHeight = {
+ SkScalarHalf(devRect.width()) + SK_ScalarHalf,
+ SkScalarHalf(devRect.height()) + SK_ScalarHalf
+ };
+
+ verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
+ verts[0].fOffset = SkPoint::Make(-widthHeight.fX, -widthHeight.fY);
+ verts[0].fWidthHeight = widthHeight;
+
+ verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
+ verts[1].fOffset = SkPoint::Make(-widthHeight.fX, widthHeight.fY);
+ verts[1].fWidthHeight = widthHeight;
+
+ verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
+ verts[2].fOffset = widthHeight;
+ verts[2].fWidthHeight = widthHeight;
+
+ verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
+ verts[3].fOffset = SkPoint::Make(widthHeight.fX, -widthHeight.fY);
+ verts[3].fWidthHeight = widthHeight;
+
+ target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer());
+ target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
+ target->resetIndexSource();
+}
+
+void GrAARectRenderer::strokeAARect(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect& rect,
+ const SkMatrix& combinedMatrix,
+ const SkRect& devRect,
+ SkScalar width,
+ bool useVertexCoverage) {
+ GrVec devStrokeSize;
+ if (width > 0) {
+ devStrokeSize.set(width, width);
+ combinedMatrix.mapVectors(&devStrokeSize, 1);
+ devStrokeSize.setAbs(devStrokeSize);
+ } else {
+ devStrokeSize.set(SK_Scalar1, SK_Scalar1);
+ }
+
+ const SkScalar dx = devStrokeSize.fX;
+ const SkScalar dy = devStrokeSize.fY;
+ const SkScalar rx = SkScalarMul(dx, SK_ScalarHalf);
+ const SkScalar ry = SkScalarMul(dy, SK_ScalarHalf);
+
+ // Temporarily #if'ed out. We don't want to pass in the devRect but
+ // right now it is computed in GrContext::apply_aa_to_rect and we don't
+ // want to throw away the work
+#if 0
+ SkRect devRect;
+ combinedMatrix.mapRect(&devRect, rect);
+#endif
+
+ SkScalar spare;
+ {
+ SkScalar w = devRect.width() - dx;
+ SkScalar h = devRect.height() - dy;
+ spare = GrMin(w, h);
+ }
+
+ SkRect devOutside(devRect);
+ devOutside.outset(rx, ry);
+
+ if (spare <= 0) {
+ this->fillAARect(gpu, target, devOutside, SkMatrix::I(),
+ devOutside, useVertexCoverage);
+ return;
+ }
+
+ SkRect devInside(devRect);
+ devInside.inset(rx, ry);
+
+ this->geometryStrokeAARect(gpu, target, devOutside, devInside, useVertexCoverage);
+}
+
+void GrAARectRenderer::geometryStrokeAARect(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect& devOutside,
+ const SkRect& devInside,
+ bool useVertexCoverage) {
+ GrDrawState* drawState = target->drawState();
+
+ set_aa_rect_vertex_attributes(drawState, useVertexCoverage);
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+ GrIndexBuffer* indexBuffer = this->aaStrokeRectIndexBuffer(gpu);
+ if (NULL == indexBuffer) {
+ GrPrintf("Failed to create index buffer!\n");
+ return;
+ }
+
+ intptr_t verts = reinterpret_cast<intptr_t>(geo.vertices());
+ size_t vsize = drawState->getVertexSize();
+ GrAssert(sizeof(GrPoint) + sizeof(GrColor) == vsize);
+
+ // We create vertices for four nested rectangles. There are two ramps from 0 to full
+ // coverage, one on the exterior of the stroke and the other on the interior.
+ // The following pointers refer to the four rects, from outermost to innermost.
+ GrPoint* fan0Pos = reinterpret_cast<GrPoint*>(verts);
+ GrPoint* fan1Pos = reinterpret_cast<GrPoint*>(verts + 4 * vsize);
+ GrPoint* fan2Pos = reinterpret_cast<GrPoint*>(verts + 8 * vsize);
+ GrPoint* fan3Pos = reinterpret_cast<GrPoint*>(verts + 12 * vsize);
+
+#ifndef SK_IGNORE_THIN_STROKED_RECT_FIX
+ // TODO: this only really works if the X & Y margins are the same all around
+ // the rect
+ SkScalar inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight);
+ inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft);
+ inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop);
+ inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom);
+ SkASSERT(inset >= 0);
+#else
+ SkScalar inset = SK_ScalarHalf;
+#endif
+
+ // outermost
+ set_inset_fan(fan0Pos, vsize, devOutside, -SK_ScalarHalf, -SK_ScalarHalf);
+ // inner two
+ set_inset_fan(fan1Pos, vsize, devOutside, inset, inset);
+ set_inset_fan(fan2Pos, vsize, devInside, -inset, -inset);
+ // innermost
+ set_inset_fan(fan3Pos, vsize, devInside, SK_ScalarHalf, SK_ScalarHalf);
+
+ // The outermost rect has 0 coverage
+ verts += sizeof(GrPoint);
+ for (int i = 0; i < 4; ++i) {
+ *reinterpret_cast<GrColor*>(verts + i * vsize) = 0;
+ }
+
+ int scale;
+ if (inset < SK_ScalarHalf) {
+ scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
+ SkASSERT(scale >= 0 && scale <= 255);
+ } else {
+ scale = 0xff;
+ }
+
+ // The inner two rects have full coverage
+ GrColor innerColor;
+ if (useVertexCoverage) {
+ innerColor = GrColorPackRGBA(scale, scale, scale, scale);
+ } else {
+ if (0xff == scale) {
+ innerColor = target->getDrawState().getColor();
+ } else {
+ innerColor = SkAlphaMulQ(target->getDrawState().getColor(), scale);
+ }
+ }
+
+ verts += 4 * vsize;
+ for (int i = 0; i < 8; ++i) {
+ *reinterpret_cast<GrColor*>(verts + i * vsize) = innerColor;
+ }
+
+ // The innermost rect has 0 coverage
+ verts += 8 * vsize;
+ for (int i = 0; i < 4; ++i) {
+ *reinterpret_cast<GrColor*>(verts + i * vsize) = 0;
+ }
+
+ target->setIndexSourceToBuffer(indexBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType,
+ 0, 0, 16, aaStrokeRectIndexCount());
+}
+
+void GrAARectRenderer::fillAANestedRects(GrGpu* gpu,
+ GrDrawTarget* target,
+ const SkRect rects[2],
+ const SkMatrix& combinedMatrix,
+ bool useVertexCoverage) {
+ SkASSERT(combinedMatrix.rectStaysRect());
+ SkASSERT(!rects[1].isEmpty());
+
+ SkRect devOutside, devInside;
+ combinedMatrix.mapRect(&devOutside, rects[0]);
+ // can't call mapRect for devInside since it calls sort
+ combinedMatrix.mapPoints((SkPoint*)&devInside, (const SkPoint*)&rects[1], 2);
+
+ if (devInside.isEmpty()) {
+ this->fillAARect(gpu, target, devOutside, SkMatrix::I(), devOutside, useVertexCoverage);
+ return;
+ }
+
+ this->geometryStrokeAARect(gpu, target, devOutside, devInside, useVertexCoverage);
+}
diff --git a/gpu/GrAddPathRenderers_default.cpp b/gpu/GrAddPathRenderers_default.cpp
new file mode 100644
index 00000000..4f172434
--- /dev/null
+++ b/gpu/GrAddPathRenderers_default.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 "GrStencilAndCoverPathRenderer.h"
+#include "GrAAHairLinePathRenderer.h"
+#include "GrAAConvexPathRenderer.h"
+#if GR_STROKE_PATH_RENDERING
+#include "../../experimental/StrokePathRenderer/GrStrokePathRenderer.h"
+#endif
+#if GR_ANDROID_PATH_RENDERING
+#include "../../experimental/AndroidPathRenderer/GrAndroidPathRenderer.h"
+#endif
+
+void GrPathRenderer::AddPathRenderers(GrContext* ctx, GrPathRendererChain* chain) {
+#if GR_STROKE_PATH_RENDERING
+ chain->addPathRenderer(SkNEW(GrStrokePathRenderer))->unref();
+#endif
+#if GR_ANDROID_PATH_RENDERING
+ chain->addPathRenderer(SkNEW(GrAndroidPathRenderer))->unref();
+#endif
+ if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(ctx)) {
+ chain->addPathRenderer(pr)->unref();
+ }
+ if (GrPathRenderer* pr = GrAAHairLinePathRenderer::Create(ctx)) {
+ chain->addPathRenderer(pr)->unref();
+ }
+ chain->addPathRenderer(SkNEW(GrAAConvexPathRenderer))->unref();
+}
diff --git a/gpu/GrAddPathRenderers_none.cpp b/gpu/GrAddPathRenderers_none.cpp
new file mode 100644
index 00000000..02da7107
--- /dev/null
+++ b/gpu/GrAddPathRenderers_none.cpp
@@ -0,0 +1,15 @@
+
+/*
+ * 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 "GrPathRenderer.h"
+
+
+void GrPathRenderer::AddPathRenderers(GrContext*,
+ GrPathRendererChain::UsageFlags,
+ GrPathRendererChain*) {}
diff --git a/gpu/GrAllocPool.cpp b/gpu/GrAllocPool.cpp
new file mode 100644
index 00000000..971f8ee1
--- /dev/null
+++ b/gpu/GrAllocPool.cpp
@@ -0,0 +1,118 @@
+
+/*
+ * 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 "GrAllocPool.h"
+
+#define GrAllocPool_MIN_BLOCK_SIZE ((size_t)128)
+
+struct GrAllocPool::Block {
+ Block* fNext;
+ char* fPtr;
+ size_t fBytesFree;
+ size_t fBytesTotal;
+
+ static Block* Create(size_t size, Block* next) {
+ GrAssert(size >= GrAllocPool_MIN_BLOCK_SIZE);
+
+ Block* block = (Block*)GrMalloc(sizeof(Block) + size);
+ block->fNext = next;
+ block->fPtr = (char*)block + sizeof(Block);
+ block->fBytesFree = size;
+ block->fBytesTotal = size;
+ return block;
+ }
+
+ bool canAlloc(size_t bytes) const {
+ return bytes <= fBytesFree;
+ }
+
+ void* alloc(size_t bytes) {
+ GrAssert(bytes <= fBytesFree);
+ fBytesFree -= bytes;
+ void* ptr = fPtr;
+ fPtr += bytes;
+ return ptr;
+ }
+
+ size_t release(size_t bytes) {
+ GrAssert(bytes > 0);
+ size_t free = GrMin(bytes, fBytesTotal - fBytesFree);
+ fBytesFree += free;
+ fPtr -= free;
+ return bytes - free;
+ }
+
+ bool empty() const { return fBytesTotal == fBytesFree; }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrAllocPool::GrAllocPool(size_t blockSize) {
+ fBlock = NULL;
+ fMinBlockSize = GrMax(blockSize, GrAllocPool_MIN_BLOCK_SIZE);
+ GR_DEBUGCODE(fBlocksAllocated = 0;)
+}
+
+GrAllocPool::~GrAllocPool() {
+ this->reset();
+}
+
+void GrAllocPool::reset() {
+ this->validate();
+
+ Block* block = fBlock;
+ while (block) {
+ Block* next = block->fNext;
+ GrFree(block);
+ block = next;
+ }
+ fBlock = NULL;
+ GR_DEBUGCODE(fBlocksAllocated = 0;)
+}
+
+void* GrAllocPool::alloc(size_t size) {
+ this->validate();
+
+ if (!fBlock || !fBlock->canAlloc(size)) {
+ size_t blockSize = GrMax(fMinBlockSize, size);
+ fBlock = Block::Create(blockSize, fBlock);
+ GR_DEBUGCODE(fBlocksAllocated += 1;)
+ }
+ return fBlock->alloc(size);
+}
+
+void GrAllocPool::release(size_t bytes) {
+ this->validate();
+
+ while (bytes && NULL != fBlock) {
+ bytes = fBlock->release(bytes);
+ if (fBlock->empty()) {
+ Block* next = fBlock->fNext;
+ GrFree(fBlock);
+ fBlock = next;
+ GR_DEBUGCODE(fBlocksAllocated -= 1;)
+ }
+ }
+}
+
+
+#if GR_DEBUG
+
+void GrAllocPool::validate() const {
+ Block* block = fBlock;
+ int count = 0;
+ while (block) {
+ count += 1;
+ block = block->fNext;
+ }
+ GrAssert(fBlocksAllocated == count);
+}
+
+#endif
diff --git a/gpu/GrAllocPool.h b/gpu/GrAllocPool.h
new file mode 100644
index 00000000..4f58f006
--- /dev/null
+++ b/gpu/GrAllocPool.h
@@ -0,0 +1,63 @@
+
+/*
+ * 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 GrAllocPool_DEFINED
+#define GrAllocPool_DEFINED
+
+#include "GrNoncopyable.h"
+
+class GrAllocPool : GrNoncopyable {
+public:
+ GrAllocPool(size_t blockSize = 0);
+ ~GrAllocPool();
+
+ /**
+ * Frees all blocks that have been allocated with alloc().
+ */
+ void reset();
+
+ /**
+ * Returns a block of memory bytes size big. This address must not be
+ * passed to realloc/free/delete or any other function that assumes the
+ * address was allocated by malloc or new (because it hasn't).
+ */
+ void* alloc(size_t bytes);
+
+ /**
+ * Releases the most recently allocated bytes back to allocpool.
+ */
+ void release(size_t bytes);
+
+private:
+ struct Block;
+
+ Block* fBlock;
+ size_t fMinBlockSize;
+
+#if GR_DEBUG
+ int fBlocksAllocated;
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+};
+
+template <typename T> class GrTAllocPool {
+public:
+ GrTAllocPool(int count) : fPool(count * sizeof(T)) {}
+
+ void reset() { fPool.reset(); }
+ T* alloc() { return (T*)fPool.alloc(sizeof(T)); }
+
+private:
+ GrAllocPool fPool;
+};
+
+#endif
diff --git a/gpu/GrAllocator.h b/gpu/GrAllocator.h
new file mode 100755
index 00000000..dc4c3de2
--- /dev/null
+++ b/gpu/GrAllocator.h
@@ -0,0 +1,250 @@
+
+/*
+ * 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 GrAllocator_DEFINED
+#define GrAllocator_DEFINED
+
+#include "GrNoncopyable.h"
+#include "GrConfig.h"
+#include "SkTArray.h"
+
+class GrAllocator : GrNoncopyable {
+public:
+ ~GrAllocator() {
+ reset();
+ }
+
+ /**
+ * Create an allocator
+ *
+ * @param itemSize the size of each item to allocate
+ * @param itemsPerBlock the number of items to allocate at once
+ * @param initialBlock optional memory to use for the first block.
+ * Must be at least itemSize*itemsPerBlock sized.
+ * Caller is responsible for freeing this memory.
+ */
+ GrAllocator(size_t itemSize, int itemsPerBlock, void* initialBlock) :
+ fItemSize(itemSize),
+ fItemsPerBlock(itemsPerBlock),
+ fOwnFirstBlock(NULL == initialBlock),
+ fCount(0) {
+ GrAssert(itemsPerBlock > 0);
+ fBlockSize = fItemSize * fItemsPerBlock;
+ fBlocks.push_back() = initialBlock;
+ GR_DEBUGCODE(if (!fOwnFirstBlock) {*((char*)initialBlock+fBlockSize-1)='a';} );
+ }
+
+ /**
+ * Adds an item and returns pointer to it.
+ *
+ * @return pointer to the added item.
+ */
+ void* push_back() {
+ int indexInBlock = fCount % fItemsPerBlock;
+ // we always have at least one block
+ if (0 == indexInBlock) {
+ if (0 != fCount) {
+ fBlocks.push_back() = GrMalloc(fBlockSize);
+ } else if (fOwnFirstBlock) {
+ fBlocks[0] = GrMalloc(fBlockSize);
+ }
+ }
+ void* ret = (char*)fBlocks[fCount/fItemsPerBlock] +
+ fItemSize * indexInBlock;
+ ++fCount;
+ return ret;
+ }
+
+ /**
+ * removes all added items
+ */
+ void reset() {
+ int blockCount = GrMax((unsigned)1,
+ GrUIDivRoundUp(fCount, fItemsPerBlock));
+ for (int i = 1; i < blockCount; ++i) {
+ GrFree(fBlocks[i]);
+ }
+ if (fOwnFirstBlock) {
+ GrFree(fBlocks[0]);
+ fBlocks[0] = NULL;
+ }
+ fBlocks.pop_back_n(blockCount-1);
+ fCount = 0;
+ }
+
+ /**
+ * count of items
+ */
+ int count() const {
+ return fCount;
+ }
+
+ /**
+ * is the count 0
+ */
+ bool empty() const { return fCount == 0; }
+
+ /**
+ * access last item, only call if count() != 0
+ */
+ void* back() {
+ GrAssert(fCount);
+ return (*this)[fCount-1];
+ }
+
+ /**
+ * access last item, only call if count() != 0
+ */
+ const void* back() const {
+ GrAssert(fCount);
+ return (*this)[fCount-1];
+ }
+
+ /**
+ * access item by index.
+ */
+ void* operator[] (int i) {
+ GrAssert(i >= 0 && i < fCount);
+ return (char*)fBlocks[i / fItemsPerBlock] +
+ fItemSize * (i % fItemsPerBlock);
+ }
+
+ /**
+ * access item by index.
+ */
+ const void* operator[] (int i) const {
+ GrAssert(i >= 0 && i < fCount);
+ return (const char*)fBlocks[i / fItemsPerBlock] +
+ fItemSize * (i % fItemsPerBlock);
+ }
+
+private:
+ static const int NUM_INIT_BLOCK_PTRS = 8;
+
+ SkSTArray<NUM_INIT_BLOCK_PTRS, void*> fBlocks;
+ size_t fBlockSize;
+ size_t fItemSize;
+ int fItemsPerBlock;
+ bool fOwnFirstBlock;
+ int fCount;
+
+ typedef GrNoncopyable INHERITED;
+};
+
+template <typename T>
+class GrTAllocator : GrNoncopyable {
+
+public:
+ virtual ~GrTAllocator() { this->reset(); };
+
+ /**
+ * Create an allocator
+ *
+ * @param itemsPerBlock the number of items to allocate at once
+ * @param initialBlock optional memory to use for the first block.
+ * Must be at least size(T)*itemsPerBlock sized.
+ * Caller is responsible for freeing this memory.
+ */
+ explicit GrTAllocator(int itemsPerBlock)
+ : fAllocator(sizeof(T), itemsPerBlock, NULL) {}
+
+ /**
+ * Adds an item and returns it.
+ *
+ * @return the added item.
+ */
+ T& push_back() {
+ void* item = fAllocator.push_back();
+ GrAssert(NULL != item);
+ SkNEW_PLACEMENT(item, T);
+ return *(T*)item;
+ }
+
+ T& push_back(const T& t) {
+ void* item = fAllocator.push_back();
+ GrAssert(NULL != item);
+ SkNEW_PLACEMENT_ARGS(item, T, (t));
+ return *(T*)item;
+ }
+
+ /**
+ * removes all added items
+ */
+ void reset() {
+ int c = fAllocator.count();
+ for (int i = 0; i < c; ++i) {
+ ((T*)fAllocator[i])->~T();
+ }
+ fAllocator.reset();
+ }
+
+ /**
+ * count of items
+ */
+ int count() const {
+ return fAllocator.count();
+ }
+
+ /**
+ * is the count 0
+ */
+ bool empty() const { return fAllocator.empty(); }
+
+ /**
+ * access last item, only call if count() != 0
+ */
+ T& back() {
+ return *(T*)fAllocator.back();
+ }
+
+ /**
+ * access last item, only call if count() != 0
+ */
+ const T& back() const {
+ return *(const T*)fAllocator.back();
+ }
+
+ /**
+ * access item by index.
+ */
+ T& operator[] (int i) {
+ return *(T*)(fAllocator[i]);
+ }
+
+ /**
+ * access item by index.
+ */
+ const T& operator[] (int i) const {
+ return *(const T*)(fAllocator[i]);
+ }
+
+protected:
+ GrTAllocator(int itemsPerBlock, void* initialBlock)
+ : fAllocator(sizeof(T), itemsPerBlock, initialBlock) {
+ }
+
+private:
+ GrAllocator fAllocator;
+ typedef GrNoncopyable INHERITED;
+};
+
+template <int N, typename T> class GrSTAllocator : public GrTAllocator<T> {
+private:
+ typedef GrTAllocator<T> INHERITED;
+
+public:
+ GrSTAllocator() : INHERITED(N, fStorage.get()) {
+ }
+
+private:
+ SkAlignedSTStorage<N, T> fStorage;
+};
+
+#endif
diff --git a/gpu/GrAtlas.cpp b/gpu/GrAtlas.cpp
new file mode 100644
index 00000000..f2daca11
--- /dev/null
+++ b/gpu/GrAtlas.cpp
@@ -0,0 +1,252 @@
+
+/*
+ * 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 "GrAtlas.h"
+#include "GrContext.h"
+#include "GrGpu.h"
+#include "GrRectanizer.h"
+#include "GrPlotMgr.h"
+
+#if 0
+#define GR_PLOT_WIDTH 8
+#define GR_PLOT_HEIGHT 4
+#define GR_ATLAS_WIDTH 256
+#define GR_ATLAS_HEIGHT 256
+
+#define GR_ATLAS_TEXTURE_WIDTH (GR_PLOT_WIDTH * GR_ATLAS_WIDTH)
+#define GR_ATLAS_TEXTURE_HEIGHT (GR_PLOT_HEIGHT * GR_ATLAS_HEIGHT)
+
+#else
+
+#define GR_ATLAS_TEXTURE_WIDTH 1024
+#define GR_ATLAS_TEXTURE_HEIGHT 2048
+
+#define GR_ATLAS_WIDTH 341
+#define GR_ATLAS_HEIGHT 341
+
+#define GR_PLOT_WIDTH (GR_ATLAS_TEXTURE_WIDTH / GR_ATLAS_WIDTH)
+#define GR_PLOT_HEIGHT (GR_ATLAS_TEXTURE_HEIGHT / GR_ATLAS_HEIGHT)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define BORDER 1
+
+#if GR_DEBUG
+ static int gCounter;
+#endif
+
+// for testing
+#define FONT_CACHE_STATS 0
+#if FONT_CACHE_STATS
+static int g_UploadCount = 0;
+#endif
+
+GrAtlas::GrAtlas(GrAtlasMgr* mgr, int plotX, int plotY, GrMaskFormat format) {
+ fAtlasMgr = mgr; // just a pointer, not an owner
+ fNext = NULL;
+ fUsed = false;
+
+ fTexture = mgr->getTexture(format); // we're not an owner, just a pointer
+ fPlot.set(plotX, plotY);
+
+ fRects = GrRectanizer::Factory(GR_ATLAS_WIDTH - BORDER,
+ GR_ATLAS_HEIGHT - BORDER);
+
+ fMaskFormat = format;
+
+#if GR_DEBUG
+// GrPrintf(" GrAtlas %p [%d %d] %d\n", this, plotX, plotY, gCounter);
+ gCounter += 1;
+#endif
+}
+
+GrAtlas::~GrAtlas() {
+ fAtlasMgr->freePlot(fMaskFormat, fPlot.fX, fPlot.fY);
+
+ delete fRects;
+
+#if GR_DEBUG
+ --gCounter;
+// GrPrintf("~GrAtlas %p [%d %d] %d\n", this, fPlot.fX, fPlot.fY, gCounter);
+#endif
+}
+
+bool GrAtlas::RemoveUnusedAtlases(GrAtlasMgr* atlasMgr, GrAtlas** startAtlas) {
+ // GrAtlas** is used so that a pointer to the head element can be passed in and
+ // modified when the first element is deleted
+ GrAtlas** atlasRef = startAtlas;
+ GrAtlas* atlas = *startAtlas;
+ bool removed = false;
+ while (NULL != atlas) {
+ if (!atlas->used()) {
+ *atlasRef = atlas->fNext;
+ atlasMgr->deleteAtlas(atlas);
+ atlas = *atlasRef;
+ removed = true;
+ } else {
+ atlasRef = &atlas->fNext;
+ atlas = atlas->fNext;
+ }
+ }
+
+ return removed;
+}
+
+static void adjustForPlot(GrIPoint16* loc, const GrIPoint16& plot) {
+ loc->fX += plot.fX * GR_ATLAS_WIDTH;
+ loc->fY += plot.fY * GR_ATLAS_HEIGHT;
+}
+
+static uint8_t* zerofill(uint8_t* ptr, int count) {
+ while (--count >= 0) {
+ *ptr++ = 0;
+ }
+ return ptr;
+}
+
+bool GrAtlas::addSubImage(int width, int height, const void* image,
+ GrIPoint16* loc) {
+ if (!fRects->addRect(width + BORDER, height + BORDER, loc)) {
+ return false;
+ }
+
+ SkAutoSMalloc<1024> storage;
+ int dstW = width + 2*BORDER;
+ int dstH = height + 2*BORDER;
+ if (BORDER) {
+ const int bpp = GrMaskFormatBytesPerPixel(fMaskFormat);
+ const size_t dstRB = dstW * bpp;
+ uint8_t* dst = (uint8_t*)storage.reset(dstH * dstRB);
+ Gr_bzero(dst, dstRB); // zero top row
+ dst += dstRB;
+ for (int y = 0; y < height; y++) {
+ dst = zerofill(dst, bpp); // zero left edge
+ memcpy(dst, image, width * bpp);
+ dst += width * bpp;
+ dst = zerofill(dst, bpp); // zero right edge
+ image = (const void*)((const char*)image + width * bpp);
+ }
+ Gr_bzero(dst, dstRB); // zero bottom row
+ image = storage.get();
+ }
+ adjustForPlot(loc, fPlot);
+ GrContext* context = fTexture->getContext();
+ // We pass the flag that does not force a flush. We assume our caller is
+ // smart and hasn't referenced the part of the texture we're about to update
+ // since the last flush.
+ context->writeTexturePixels(fTexture,
+ loc->fX, loc->fY, dstW, dstH,
+ fTexture->config(), image, 0,
+ GrContext::kDontFlush_PixelOpsFlag);
+
+ // now tell the caller to skip the top/left BORDER
+ loc->fX += BORDER;
+ loc->fY += BORDER;
+
+#if FONT_CACHE_STATS
+ ++g_UploadCount;
+#endif
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrAtlasMgr::GrAtlasMgr(GrGpu* gpu) {
+ fGpu = gpu;
+ gpu->ref();
+ Gr_bzero(fTexture, sizeof(fTexture));
+ fPlotMgr = SkNEW_ARGS(GrPlotMgr, (GR_PLOT_WIDTH, GR_PLOT_HEIGHT));
+}
+
+GrAtlasMgr::~GrAtlasMgr() {
+ for (size_t i = 0; i < GR_ARRAY_COUNT(fTexture); i++) {
+ GrSafeUnref(fTexture[i]);
+ }
+ delete fPlotMgr;
+
+ fGpu->unref();
+#if FONT_CACHE_STATS
+ GrPrintf("Num uploads: %d\n", g_UploadCount);
+#endif
+}
+
+static GrPixelConfig maskformat2pixelconfig(GrMaskFormat format) {
+ switch (format) {
+ case kA8_GrMaskFormat:
+ return kAlpha_8_GrPixelConfig;
+ case kA565_GrMaskFormat:
+ return kRGB_565_GrPixelConfig;
+ case kA888_GrMaskFormat:
+ return kSkia8888_GrPixelConfig;
+ default:
+ GrAssert(!"unknown maskformat");
+ }
+ return kUnknown_GrPixelConfig;
+}
+
+GrAtlas* GrAtlasMgr::addToAtlas(GrAtlas** atlas,
+ int width, int height, const void* image,
+ GrMaskFormat format,
+ GrIPoint16* loc) {
+ GrAssert(NULL == *atlas || (*atlas)->getMaskFormat() == format);
+
+ // iterate through entire atlas list, see if we can find a hole
+ GrAtlas* atlasIter = *atlas;
+ while (atlasIter) {
+ if (atlasIter->addSubImage(width, height, image, loc)) {
+ return atlasIter;
+ }
+ atlasIter = atlasIter->fNext;
+ }
+
+ // If the above fails, then either we have no starting atlas, or the current
+ // atlas list is full. Either way we need to allocate a new atlas
+
+ GrIPoint16 plot;
+ if (!fPlotMgr->newPlot(&plot)) {
+ return NULL;
+ }
+
+ GrAssert(0 == kA8_GrMaskFormat);
+ GrAssert(1 == kA565_GrMaskFormat);
+ if (NULL == fTexture[format]) {
+ // TODO: Update this to use the cache rather than directly creating a texture.
+ GrTextureDesc desc;
+ desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
+ desc.fWidth = GR_ATLAS_TEXTURE_WIDTH;
+ desc.fHeight = GR_ATLAS_TEXTURE_HEIGHT;
+ desc.fConfig = maskformat2pixelconfig(format);
+
+ fTexture[format] = fGpu->createTexture(desc, NULL, 0);
+ if (NULL == fTexture[format]) {
+ return NULL;
+ }
+ }
+
+ GrAtlas* newAtlas = SkNEW_ARGS(GrAtlas, (this, plot.fX, plot.fY, format));
+ if (!newAtlas->addSubImage(width, height, image, loc)) {
+ delete newAtlas;
+ return NULL;
+ }
+
+ // new atlas, put at head
+ newAtlas->fNext = *atlas;
+ *atlas = newAtlas;
+
+ return newAtlas;
+}
+
+void GrAtlasMgr::freePlot(GrMaskFormat format, int x, int y) {
+ GrAssert(fPlotMgr->isBusy(x, y));
+ fPlotMgr->freePlot(x, y);
+}
diff --git a/gpu/GrAtlas.h b/gpu/GrAtlas.h
new file mode 100644
index 00000000..b6f25c21
--- /dev/null
+++ b/gpu/GrAtlas.h
@@ -0,0 +1,94 @@
+
+/*
+ * 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 GrAtlas_DEFINED
+#define GrAtlas_DEFINED
+
+#include "GrPoint.h"
+#include "GrTexture.h"
+
+class GrGpu;
+class GrRectanizer;
+class GrAtlasMgr;
+
+class GrAtlas {
+public:
+ int getPlotX() const { return fPlot.fX; }
+ int getPlotY() const { return fPlot.fY; }
+ GrMaskFormat getMaskFormat() const { return fMaskFormat; }
+
+ GrTexture* texture() const { return fTexture; }
+
+ bool addSubImage(int width, int height, const void*, GrIPoint16*);
+
+ static void FreeLList(GrAtlas* atlas) {
+ while (NULL != atlas) {
+ GrAtlas* next = atlas->fNext;
+ delete atlas;
+ atlas = next;
+ }
+ }
+
+ static void MarkAllUnused(GrAtlas* atlas) {
+ while (NULL != atlas) {
+ atlas->fUsed = false;
+ atlas = atlas->fNext;
+ }
+ }
+
+ static bool RemoveUnusedAtlases(GrAtlasMgr* atlasMgr, GrAtlas** startAtlas);
+
+ bool used() const { return fUsed; }
+ void setUsed(bool used) { fUsed = used; }
+
+private:
+ GrAtlas(GrAtlasMgr*, int plotX, int plotY, GrMaskFormat format);
+ ~GrAtlas(); // does not try to delete the fNext field
+
+ GrAtlas* fNext;
+
+ // for recycling
+ bool fUsed;
+
+ GrTexture* fTexture;
+ GrRectanizer* fRects;
+ GrAtlasMgr* fAtlasMgr;
+ GrIPoint16 fPlot;
+ GrMaskFormat fMaskFormat;
+
+ friend class GrAtlasMgr;
+};
+
+class GrPlotMgr;
+
+class GrAtlasMgr {
+public:
+ GrAtlasMgr(GrGpu*);
+ ~GrAtlasMgr();
+
+ GrAtlas* addToAtlas(GrAtlas**, int width, int height, const void*,
+ GrMaskFormat, GrIPoint16*);
+ void deleteAtlas(GrAtlas* atlas) { delete atlas; }
+
+ GrTexture* getTexture(GrMaskFormat format) const {
+ GrAssert((unsigned)format < kCount_GrMaskFormats);
+ return fTexture[format];
+ }
+
+ // to be called by ~GrAtlas()
+ void freePlot(GrMaskFormat format, int x, int y);
+
+private:
+ GrGpu* fGpu;
+ GrTexture* fTexture[kCount_GrMaskFormats];
+ GrPlotMgr* fPlotMgr;
+};
+
+#endif
diff --git a/gpu/GrBinHashKey.h b/gpu/GrBinHashKey.h
new file mode 100644
index 00000000..8fa53ef6
--- /dev/null
+++ b/gpu/GrBinHashKey.h
@@ -0,0 +1,110 @@
+
+/*
+ * 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 GrBinHashKey_DEFINED
+#define GrBinHashKey_DEFINED
+
+#include "GrTypes.h"
+
+/**
+ * Hash function class that can take a data chunk of any predetermined length. The hash function
+ * used is the One-at-a-Time Hash (http://burtleburtle.net/bob/hash/doobs.html).
+ *
+ * Keys are computed from ENTRY objects. ENTRY must be fully ordered by a member:
+ * int compare(const GrTBinHashKey<ENTRY, ..>& k);
+ * which returns negative if the ENTRY < k, 0 if it equals k, and positive if k < the ENTRY.
+ * Additionally, ENTRY must be flattenable into the key using setKeyData.
+ *
+ * This class satisfies the requirements to be a key for a GrTHashTable.
+ */
+template<typename ENTRY, size_t KEY_SIZE>
+class GrTBinHashKey {
+public:
+ enum { kKeySize = KEY_SIZE };
+
+ GrTBinHashKey() {
+ this->reset();
+ }
+
+ GrTBinHashKey(const GrTBinHashKey<ENTRY, KEY_SIZE>& other) {
+ *this = other;
+ }
+
+ GrTBinHashKey<ENTRY, KEY_SIZE>& operator=(const GrTBinHashKey<ENTRY, KEY_SIZE>& other) {
+ memcpy(this, &other, sizeof(*this));
+ return *this;
+ }
+
+ ~GrTBinHashKey() {
+ }
+
+ void reset() {
+ fHash = 0;
+#if GR_DEBUG
+ fIsValid = false;
+#endif
+ }
+
+ void setKeyData(const uint32_t* SK_RESTRICT data) {
+ GrAssert(GrIsALIGN4(KEY_SIZE));
+ memcpy(&fData, data, KEY_SIZE);
+
+ uint32_t hash = 0;
+ size_t len = KEY_SIZE;
+ while (len >= 4) {
+ hash += *data++;
+ hash += (fHash << 10);
+ hash ^= (hash >> 6);
+ len -= 4;
+ }
+ hash += (fHash << 3);
+ hash ^= (fHash >> 11);
+ hash += (fHash << 15);
+#if GR_DEBUG
+ fIsValid = true;
+#endif
+ fHash = hash;
+ }
+
+ int compare(const GrTBinHashKey<ENTRY, KEY_SIZE>& key) const {
+ GrAssert(fIsValid && key.fIsValid);
+ return memcmp(fData, key.fData, KEY_SIZE);
+ }
+
+ static bool EQ(const ENTRY& entry, const GrTBinHashKey<ENTRY, KEY_SIZE>& key) {
+ GrAssert(key.fIsValid);
+ return 0 == entry.compare(key);
+ }
+
+ static bool LT(const ENTRY& entry, const GrTBinHashKey<ENTRY, KEY_SIZE>& key) {
+ GrAssert(key.fIsValid);
+ return entry.compare(key) < 0;
+ }
+
+ uint32_t getHash() const {
+ GrAssert(fIsValid);
+ return fHash;
+ }
+
+ const uint8_t* getData() const {
+ GrAssert(fIsValid);
+ return fData;
+ }
+
+private:
+ uint32_t fHash;
+ uint8_t fData[KEY_SIZE]; // Buffer for key storage
+
+#if GR_DEBUG
+public:
+ bool fIsValid;
+#endif
+};
+
+#endif
diff --git a/gpu/GrBufferAllocPool.cpp b/gpu/GrBufferAllocPool.cpp
new file mode 100644
index 00000000..d8dd8bd0
--- /dev/null
+++ b/gpu/GrBufferAllocPool.cpp
@@ -0,0 +1,488 @@
+
+/*
+ * 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 "GrBufferAllocPool.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+#include "GrIndexBuffer.h"
+#include "GrTypes.h"
+#include "GrVertexBuffer.h"
+
+#if GR_DEBUG
+ #define VALIDATE validate
+#else
+ static void VALIDATE(bool = false) {}
+#endif
+
+// page size
+#define GrBufferAllocPool_MIN_BLOCK_SIZE ((size_t)1 << 12)
+
+GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu,
+ BufferType bufferType,
+ bool frequentResetHint,
+ size_t blockSize,
+ int preallocBufferCnt) :
+ fBlocks(GrMax(8, 2*preallocBufferCnt)) {
+
+ GrAssert(NULL != gpu);
+ fGpu = gpu;
+ fGpu->ref();
+ fGpuIsReffed = true;
+
+ fBufferType = bufferType;
+ fFrequentResetHint = frequentResetHint;
+ fBufferPtr = NULL;
+ fMinBlockSize = GrMax(GrBufferAllocPool_MIN_BLOCK_SIZE, blockSize);
+
+ fBytesInUse = 0;
+
+ fPreallocBuffersInUse = 0;
+ fPreallocBufferStartIdx = 0;
+ for (int i = 0; i < preallocBufferCnt; ++i) {
+ GrGeometryBuffer* buffer = this->createBuffer(fMinBlockSize);
+ if (NULL != buffer) {
+ *fPreallocBuffers.append() = buffer;
+ }
+ }
+}
+
+GrBufferAllocPool::~GrBufferAllocPool() {
+ VALIDATE();
+ if (fBlocks.count()) {
+ GrGeometryBuffer* buffer = fBlocks.back().fBuffer;
+ if (buffer->isLocked()) {
+ buffer->unlock();
+ }
+ }
+ while (!fBlocks.empty()) {
+ destroyBlock();
+ }
+ fPreallocBuffers.unrefAll();
+ releaseGpuRef();
+}
+
+void GrBufferAllocPool::releaseGpuRef() {
+ if (fGpuIsReffed) {
+ fGpu->unref();
+ fGpuIsReffed = false;
+ }
+}
+
+void GrBufferAllocPool::reset() {
+ VALIDATE();
+ fBytesInUse = 0;
+ if (fBlocks.count()) {
+ GrGeometryBuffer* buffer = fBlocks.back().fBuffer;
+ if (buffer->isLocked()) {
+ buffer->unlock();
+ }
+ }
+ // fPreallocBuffersInUse will be decremented down to zero in the while loop
+ int preallocBuffersInUse = fPreallocBuffersInUse;
+ while (!fBlocks.empty()) {
+ this->destroyBlock();
+ }
+ if (fPreallocBuffers.count()) {
+ // must set this after above loop.
+ fPreallocBufferStartIdx = (fPreallocBufferStartIdx +
+ preallocBuffersInUse) %
+ fPreallocBuffers.count();
+ }
+ // we may have created a large cpu mirror of a large VB. Reset the size
+ // to match our pre-allocated VBs.
+ fCpuData.reset(fMinBlockSize);
+ GrAssert(0 == fPreallocBuffersInUse);
+ VALIDATE();
+}
+
+void GrBufferAllocPool::unlock() {
+ VALIDATE();
+
+ if (NULL != fBufferPtr) {
+ BufferBlock& block = fBlocks.back();
+ if (block.fBuffer->isLocked()) {
+ block.fBuffer->unlock();
+ } else {
+ size_t flushSize = block.fBuffer->sizeInBytes() - block.fBytesFree;
+ flushCpuData(fBlocks.back().fBuffer, flushSize);
+ }
+ fBufferPtr = NULL;
+ }
+ VALIDATE();
+}
+
+#if GR_DEBUG
+void GrBufferAllocPool::validate(bool unusedBlockAllowed) const {
+ if (NULL != fBufferPtr) {
+ GrAssert(!fBlocks.empty());
+ if (fBlocks.back().fBuffer->isLocked()) {
+ GrGeometryBuffer* buf = fBlocks.back().fBuffer;
+ GrAssert(buf->lockPtr() == fBufferPtr);
+ } else {
+ GrAssert(fCpuData.get() == fBufferPtr);
+ }
+ } else {
+ GrAssert(fBlocks.empty() || !fBlocks.back().fBuffer->isLocked());
+ }
+ size_t bytesInUse = 0;
+ for (int i = 0; i < fBlocks.count() - 1; ++i) {
+ GrAssert(!fBlocks[i].fBuffer->isLocked());
+ }
+ for (int i = 0; i < fBlocks.count(); ++i) {
+ size_t bytes = fBlocks[i].fBuffer->sizeInBytes() - fBlocks[i].fBytesFree;
+ bytesInUse += bytes;
+ GrAssert(bytes || unusedBlockAllowed);
+ }
+
+ GrAssert(bytesInUse == fBytesInUse);
+ if (unusedBlockAllowed) {
+ GrAssert((fBytesInUse && !fBlocks.empty()) ||
+ (!fBytesInUse && (fBlocks.count() < 2)));
+ } else {
+ GrAssert((0 == fBytesInUse) == fBlocks.empty());
+ }
+}
+#endif
+
+void* GrBufferAllocPool::makeSpace(size_t size,
+ size_t alignment,
+ const GrGeometryBuffer** buffer,
+ size_t* offset) {
+ VALIDATE();
+
+ GrAssert(NULL != buffer);
+ GrAssert(NULL != offset);
+
+ if (NULL != fBufferPtr) {
+ BufferBlock& back = fBlocks.back();
+ size_t usedBytes = back.fBuffer->sizeInBytes() - back.fBytesFree;
+ size_t pad = GrSizeAlignUpPad(usedBytes,
+ alignment);
+ if ((size + pad) <= back.fBytesFree) {
+ usedBytes += pad;
+ *offset = usedBytes;
+ *buffer = back.fBuffer;
+ back.fBytesFree -= size + pad;
+ fBytesInUse += size + pad;
+ VALIDATE();
+ return (void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes);
+ }
+ }
+
+ // We could honor the space request using by a partial update of the current
+ // VB (if there is room). But we don't currently use draw calls to GL that
+ // allow the driver to know that previously issued draws won't read from
+ // the part of the buffer we update. Also, the GL buffer implementation
+ // may be cheating on the actual buffer size by shrinking the buffer on
+ // updateData() if the amount of data passed is less than the full buffer
+ // size.
+
+ if (!createBlock(size)) {
+ return NULL;
+ }
+ GrAssert(NULL != fBufferPtr);
+
+ *offset = 0;
+ BufferBlock& back = fBlocks.back();
+ *buffer = back.fBuffer;
+ back.fBytesFree -= size;
+ fBytesInUse += size;
+ VALIDATE();
+ return fBufferPtr;
+}
+
+int GrBufferAllocPool::currentBufferItems(size_t itemSize) const {
+ VALIDATE();
+ if (NULL != fBufferPtr) {
+ const BufferBlock& back = fBlocks.back();
+ size_t usedBytes = back.fBuffer->sizeInBytes() - back.fBytesFree;
+ size_t pad = GrSizeAlignUpPad(usedBytes, itemSize);
+ return (back.fBytesFree - pad) / itemSize;
+ } else if (fPreallocBuffersInUse < fPreallocBuffers.count()) {
+ return fMinBlockSize / itemSize;
+ }
+ return 0;
+}
+
+int GrBufferAllocPool::preallocatedBuffersRemaining() const {
+ return fPreallocBuffers.count() - fPreallocBuffersInUse;
+}
+
+int GrBufferAllocPool::preallocatedBufferCount() const {
+ return fPreallocBuffers.count();
+}
+
+void GrBufferAllocPool::putBack(size_t bytes) {
+ VALIDATE();
+
+ // if the putBack unwinds all the preallocated buffers then we will
+ // advance the starting index. As blocks are destroyed fPreallocBuffersInUse
+ // will be decremented. I will reach zero if all blocks using preallocated
+ // buffers are released.
+ int preallocBuffersInUse = fPreallocBuffersInUse;
+
+ while (bytes) {
+ // caller shouldnt try to put back more than they've taken
+ GrAssert(!fBlocks.empty());
+ BufferBlock& block = fBlocks.back();
+ size_t bytesUsed = block.fBuffer->sizeInBytes() - block.fBytesFree;
+ if (bytes >= bytesUsed) {
+ bytes -= bytesUsed;
+ fBytesInUse -= bytesUsed;
+ // if we locked a vb to satisfy the make space and we're releasing
+ // beyond it, then unlock it.
+ if (block.fBuffer->isLocked()) {
+ block.fBuffer->unlock();
+ }
+ this->destroyBlock();
+ } else {
+ block.fBytesFree += bytes;
+ fBytesInUse -= bytes;
+ bytes = 0;
+ break;
+ }
+ }
+ if (!fPreallocBuffersInUse && fPreallocBuffers.count()) {
+ fPreallocBufferStartIdx = (fPreallocBufferStartIdx +
+ preallocBuffersInUse) %
+ fPreallocBuffers.count();
+ }
+ VALIDATE();
+}
+
+bool GrBufferAllocPool::createBlock(size_t requestSize) {
+
+ size_t size = GrMax(requestSize, fMinBlockSize);
+ GrAssert(size >= GrBufferAllocPool_MIN_BLOCK_SIZE);
+
+ VALIDATE();
+
+ BufferBlock& block = fBlocks.push_back();
+
+ if (size == fMinBlockSize &&
+ fPreallocBuffersInUse < fPreallocBuffers.count()) {
+
+ uint32_t nextBuffer = (fPreallocBuffersInUse +
+ fPreallocBufferStartIdx) %
+ fPreallocBuffers.count();
+ block.fBuffer = fPreallocBuffers[nextBuffer];
+ block.fBuffer->ref();
+ ++fPreallocBuffersInUse;
+ } else {
+ block.fBuffer = this->createBuffer(size);
+ if (NULL == block.fBuffer) {
+ fBlocks.pop_back();
+ return false;
+ }
+ }
+
+ block.fBytesFree = size;
+ if (NULL != fBufferPtr) {
+ GrAssert(fBlocks.count() > 1);
+ BufferBlock& prev = fBlocks.fromBack(1);
+ if (prev.fBuffer->isLocked()) {
+ prev.fBuffer->unlock();
+ } else {
+ flushCpuData(prev.fBuffer,
+ prev.fBuffer->sizeInBytes() - prev.fBytesFree);
+ }
+ fBufferPtr = NULL;
+ }
+
+ GrAssert(NULL == fBufferPtr);
+
+ // If the buffer is CPU-backed we lock it because it is free to do so and saves a copy.
+ // Otherwise when buffer locking is supported:
+ // a) If the frequently reset hint is set we only lock when the requested size meets a
+ // threshold (since we don't expect it is likely that we will see more vertex data)
+ // b) If the hint is not set we lock if the buffer size is greater than the threshold.
+ bool attemptLock = block.fBuffer->isCPUBacked();
+ if (!attemptLock && fGpu->caps()->bufferLockSupport()) {
+ if (fFrequentResetHint) {
+ attemptLock = requestSize > GR_GEOM_BUFFER_LOCK_THRESHOLD;
+ } else {
+ attemptLock = size > GR_GEOM_BUFFER_LOCK_THRESHOLD;
+ }
+ }
+
+ if (attemptLock) {
+ fBufferPtr = block.fBuffer->lock();
+ }
+
+ if (NULL == fBufferPtr) {
+ fBufferPtr = fCpuData.reset(size);
+ }
+
+ VALIDATE(true);
+
+ return true;
+}
+
+void GrBufferAllocPool::destroyBlock() {
+ GrAssert(!fBlocks.empty());
+
+ BufferBlock& block = fBlocks.back();
+ if (fPreallocBuffersInUse > 0) {
+ uint32_t prevPreallocBuffer = (fPreallocBuffersInUse +
+ fPreallocBufferStartIdx +
+ (fPreallocBuffers.count() - 1)) %
+ fPreallocBuffers.count();
+ if (block.fBuffer == fPreallocBuffers[prevPreallocBuffer]) {
+ --fPreallocBuffersInUse;
+ }
+ }
+ GrAssert(!block.fBuffer->isLocked());
+ block.fBuffer->unref();
+ fBlocks.pop_back();
+ fBufferPtr = NULL;
+}
+
+void GrBufferAllocPool::flushCpuData(GrGeometryBuffer* buffer,
+ size_t flushSize) {
+ GrAssert(NULL != buffer);
+ GrAssert(!buffer->isLocked());
+ GrAssert(fCpuData.get() == fBufferPtr);
+ GrAssert(flushSize <= buffer->sizeInBytes());
+ VALIDATE(true);
+
+ if (fGpu->caps()->bufferLockSupport() &&
+ flushSize > GR_GEOM_BUFFER_LOCK_THRESHOLD) {
+ void* data = buffer->lock();
+ if (NULL != data) {
+ memcpy(data, fBufferPtr, flushSize);
+ buffer->unlock();
+ return;
+ }
+ }
+ buffer->updateData(fBufferPtr, flushSize);
+ VALIDATE(true);
+}
+
+GrGeometryBuffer* GrBufferAllocPool::createBuffer(size_t size) {
+ if (kIndex_BufferType == fBufferType) {
+ return fGpu->createIndexBuffer(size, true);
+ } else {
+ GrAssert(kVertex_BufferType == fBufferType);
+ return fGpu->createVertexBuffer(size, true);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu,
+ bool frequentResetHint,
+ size_t bufferSize,
+ int preallocBufferCnt)
+: GrBufferAllocPool(gpu,
+ kVertex_BufferType,
+ frequentResetHint,
+ bufferSize,
+ preallocBufferCnt) {
+}
+
+void* GrVertexBufferAllocPool::makeSpace(size_t vertexSize,
+ int vertexCount,
+ const GrVertexBuffer** buffer,
+ int* startVertex) {
+
+ GrAssert(vertexCount >= 0);
+ GrAssert(NULL != buffer);
+ GrAssert(NULL != startVertex);
+
+ size_t offset = 0; // assign to suppress warning
+ const GrGeometryBuffer* geomBuffer = NULL; // assign to suppress warning
+ void* ptr = INHERITED::makeSpace(vertexSize * vertexCount,
+ vertexSize,
+ &geomBuffer,
+ &offset);
+
+ *buffer = (const GrVertexBuffer*) geomBuffer;
+ GrAssert(0 == offset % vertexSize);
+ *startVertex = offset / vertexSize;
+ return ptr;
+}
+
+bool GrVertexBufferAllocPool::appendVertices(size_t vertexSize,
+ int vertexCount,
+ const void* vertices,
+ const GrVertexBuffer** buffer,
+ int* startVertex) {
+ void* space = makeSpace(vertexSize, vertexCount, buffer, startVertex);
+ if (NULL != space) {
+ memcpy(space,
+ vertices,
+ vertexSize * vertexCount);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int GrVertexBufferAllocPool::preallocatedBufferVertices(size_t vertexSize) const {
+ return INHERITED::preallocatedBufferSize() / vertexSize;
+}
+
+int GrVertexBufferAllocPool::currentBufferVertices(size_t vertexSize) const {
+ return currentBufferItems(vertexSize);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu,
+ bool frequentResetHint,
+ size_t bufferSize,
+ int preallocBufferCnt)
+: GrBufferAllocPool(gpu,
+ kIndex_BufferType,
+ frequentResetHint,
+ bufferSize,
+ preallocBufferCnt) {
+}
+
+void* GrIndexBufferAllocPool::makeSpace(int indexCount,
+ const GrIndexBuffer** buffer,
+ int* startIndex) {
+
+ GrAssert(indexCount >= 0);
+ GrAssert(NULL != buffer);
+ GrAssert(NULL != startIndex);
+
+ size_t offset = 0; // assign to suppress warning
+ const GrGeometryBuffer* geomBuffer = NULL; // assign to suppress warning
+ void* ptr = INHERITED::makeSpace(indexCount * sizeof(uint16_t),
+ sizeof(uint16_t),
+ &geomBuffer,
+ &offset);
+
+ *buffer = (const GrIndexBuffer*) geomBuffer;
+ GrAssert(0 == offset % sizeof(uint16_t));
+ *startIndex = offset / sizeof(uint16_t);
+ return ptr;
+}
+
+bool GrIndexBufferAllocPool::appendIndices(int indexCount,
+ const void* indices,
+ const GrIndexBuffer** buffer,
+ int* startIndex) {
+ void* space = makeSpace(indexCount, buffer, startIndex);
+ if (NULL != space) {
+ memcpy(space, indices, sizeof(uint16_t) * indexCount);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int GrIndexBufferAllocPool::preallocatedBufferIndices() const {
+ return INHERITED::preallocatedBufferSize() / sizeof(uint16_t);
+}
+
+int GrIndexBufferAllocPool::currentBufferIndices() const {
+ return currentBufferItems(sizeof(uint16_t));
+}
diff --git a/gpu/GrBufferAllocPool.h b/gpu/GrBufferAllocPool.h
new file mode 100644
index 00000000..ffd8c340
--- /dev/null
+++ b/gpu/GrBufferAllocPool.h
@@ -0,0 +1,352 @@
+
+/*
+ * 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 GrBufferAllocPool_DEFINED
+#define GrBufferAllocPool_DEFINED
+
+#include "GrNoncopyable.h"
+
+#include "SkTArray.h"
+#include "SkTDArray.h"
+
+class GrGeometryBuffer;
+class GrGpu;
+
+/**
+ * A pool of geometry buffers tied to a GrGpu.
+ *
+ * The pool allows a client to make space for geometry and then put back excess
+ * space if it over allocated. When a client is ready to draw from the pool
+ * it calls unlock on the pool ensure buffers are ready for drawing. The pool
+ * can be reset after drawing is completed to recycle space.
+ *
+ * At creation time a minimum per-buffer size can be specified. Additionally,
+ * a number of buffers to preallocate can be specified. These will
+ * be allocated at the min size and kept around until the pool is destroyed.
+ */
+class GrBufferAllocPool : GrNoncopyable {
+
+public:
+ /**
+ * Ensures all buffers are unlocked and have all data written to them.
+ * Call before drawing using buffers from the pool.
+ */
+ void unlock();
+
+ /**
+ * Invalidates all the data in the pool, unrefs non-preallocated buffers.
+ */
+ void reset();
+
+ /**
+ * Gets the number of preallocated buffers that are yet to be used.
+ */
+ int preallocatedBuffersRemaining() const;
+
+ /**
+ * gets the number of preallocated buffers
+ */
+ int preallocatedBufferCount() const;
+
+ /**
+ * Frees data from makeSpaces in LIFO order.
+ */
+ void putBack(size_t bytes);
+
+ /**
+ * Gets the GrGpu that this pool is associated with.
+ */
+ GrGpu* getGpu() { return fGpu; }
+
+protected:
+ /**
+ * Used to determine what type of buffers to create. We could make the
+ * createBuffer a virtual except that we want to use it in the cons for
+ * pre-allocated buffers.
+ */
+ enum BufferType {
+ kVertex_BufferType,
+ kIndex_BufferType,
+ };
+
+ /**
+ * Constructor
+ *
+ * @param gpu The GrGpu used to create the buffers.
+ * @param bufferType The type of buffers to create.
+ * @param frequentResetHint A hint that indicates that the pool
+ * should expect frequent unlock() calls
+ * (as opposed to many makeSpace / acquires
+ * between resets).
+ * @param bufferSize The minimum size of created buffers.
+ * This value will be clamped to some
+ * reasonable minimum.
+ * @param preallocBufferCnt The pool will allocate this number of
+ * buffers at bufferSize and keep them until it
+ * is destroyed.
+ */
+ GrBufferAllocPool(GrGpu* gpu,
+ BufferType bufferType,
+ bool frequentResetHint,
+ size_t bufferSize = 0,
+ int preallocBufferCnt = 0);
+
+ virtual ~GrBufferAllocPool();
+
+ /**
+ * Gets the size of the preallocated buffers.
+ *
+ * @return the size of preallocated buffers.
+ */
+ size_t preallocatedBufferSize() const {
+ return fPreallocBuffers.count() ? fMinBlockSize : 0;
+ }
+
+ /**
+ * Returns a block of memory to hold data. A buffer designated to hold the
+ * data is given to the caller. The buffer may or may not be locked. The
+ * returned ptr remains valid until any of the following:
+ * *makeSpace is called again.
+ * *unlock is called.
+ * *reset is called.
+ * *this object is destroyed.
+ *
+ * Once unlock on the pool is called the data is guaranteed to be in the
+ * buffer at the offset indicated by offset. Until that time it may be
+ * in temporary storage and/or the buffer may be locked.
+ *
+ * @param size the amount of data to make space for
+ * @param alignment alignment constraint from start of buffer
+ * @param buffer returns the buffer that will hold the data.
+ * @param offset returns the offset into buffer of the data.
+ * @return pointer to where the client should write the data.
+ */
+ void* makeSpace(size_t size,
+ size_t alignment,
+ const GrGeometryBuffer** buffer,
+ size_t* offset);
+
+ /**
+ * Gets the number of items of a size that can be added to the current
+ * buffer without spilling to another buffer. If the pool has been reset, or
+ * the previous makeSpace completely exhausted a buffer then the returned
+ * size will be the size of the next available preallocated buffer, or zero
+ * if no preallocated buffer remains available. It is assumed that items
+ * should be itemSize-aligned from the start of a buffer.
+ *
+ * @return the number of items that would fit in the current buffer.
+ */
+ int currentBufferItems(size_t itemSize) const;
+
+ GrGeometryBuffer* createBuffer(size_t size);
+
+private:
+
+ // The GrGpu must be able to clear the ref of pools it creates as members
+ friend class GrGpu;
+ void releaseGpuRef();
+
+ struct BufferBlock {
+ size_t fBytesFree;
+ GrGeometryBuffer* fBuffer;
+ };
+
+ bool createBlock(size_t requestSize);
+ void destroyBlock();
+ void flushCpuData(GrGeometryBuffer* buffer, size_t flushSize);
+#if GR_DEBUG
+ void validate(bool unusedBlockAllowed = false) const;
+#endif
+
+ size_t fBytesInUse;
+
+ GrGpu* fGpu;
+ bool fGpuIsReffed;
+ bool fFrequentResetHint;
+ SkTDArray<GrGeometryBuffer*> fPreallocBuffers;
+ size_t fMinBlockSize;
+ BufferType fBufferType;
+
+ SkTArray<BufferBlock> fBlocks;
+ int fPreallocBuffersInUse;
+ // We attempt to cycle through the preallocated buffers rather than
+ // always starting from the first.
+ int fPreallocBufferStartIdx;
+ SkAutoMalloc fCpuData;
+ void* fBufferPtr;
+};
+
+class GrVertexBuffer;
+
+/**
+ * A GrBufferAllocPool of vertex buffers
+ */
+class GrVertexBufferAllocPool : public GrBufferAllocPool {
+public:
+ /**
+ * Constructor
+ *
+ * @param gpu The GrGpu used to create the vertex buffers.
+ * @param frequentResetHint A hint that indicates that the pool
+ * should expect frequent unlock() calls
+ * (as opposed to many makeSpace / acquires
+ * between resets).
+ * @param bufferSize The minimum size of created VBs This value
+ * will be clamped to some reasonable minimum.
+ * @param preallocBufferCnt The pool will allocate this number of VBs at
+ * bufferSize and keep them until it is
+ * destroyed.
+ */
+ GrVertexBufferAllocPool(GrGpu* gpu,
+ bool frequentResetHint,
+ size_t bufferSize = 0,
+ int preallocBufferCnt = 0);
+
+ /**
+ * Returns a block of memory to hold vertices. A buffer designated to hold
+ * the vertices given to the caller. The buffer may or may not be locked.
+ * The returned ptr remains valid until any of the following:
+ * *makeSpace is called again.
+ * *unlock is called.
+ * *reset is called.
+ * *this object is destroyed.
+ *
+ * Once unlock on the pool is called the vertices are guaranteed to be in
+ * the buffer at the offset indicated by startVertex. Until that time they
+ * may be in temporary storage and/or the buffer may be locked.
+ *
+ * @param vertexSize specifies size of a vertex to allocate space for
+ * @param vertexCount number of vertices to allocate space for
+ * @param buffer returns the vertex buffer that will hold the
+ * vertices.
+ * @param startVertex returns the offset into buffer of the first vertex.
+ * In units of the size of a vertex from layout param.
+ * @return pointer to first vertex.
+ */
+ void* makeSpace(size_t vertexSize,
+ int vertexCount,
+ const GrVertexBuffer** buffer,
+ int* startVertex);
+
+ /**
+ * Shortcut to make space and then write verts into the made space.
+ */
+ bool appendVertices(size_t vertexSize,
+ int vertexCount,
+ const void* vertices,
+ const GrVertexBuffer** buffer,
+ int* startVertex);
+
+ /**
+ * Gets the number of vertices that can be added to the current VB without
+ * spilling to another VB. If the pool has been reset, or the previous
+ * makeSpace completely exhausted a VB then the returned number of vertices
+ * would fit in the next available preallocated buffer. If any makeSpace
+ * would force a new VB to be created the return value will be zero.
+ *
+ * @param the size of a vertex to compute space for.
+ * @return the number of vertices that would fit in the current buffer.
+ */
+ int currentBufferVertices(size_t vertexSize) const;
+
+ /**
+ * Gets the number of vertices that can fit in a preallocated vertex buffer.
+ * Zero if no preallocated buffers.
+ *
+ * @param the size of a vertex to compute space for.
+ *
+ * @return number of vertices that fit in one of the preallocated vertex
+ * buffers.
+ */
+ int preallocatedBufferVertices(size_t vertexSize) const;
+
+private:
+ typedef GrBufferAllocPool INHERITED;
+};
+
+class GrIndexBuffer;
+
+/**
+ * A GrBufferAllocPool of index buffers
+ */
+class GrIndexBufferAllocPool : public GrBufferAllocPool {
+public:
+ /**
+ * Constructor
+ *
+ * @param gpu The GrGpu used to create the index buffers.
+ * @param frequentResetHint A hint that indicates that the pool
+ * should expect frequent unlock() calls
+ * (as opposed to many makeSpace / acquires
+ * between resets).
+ * @param bufferSize The minimum size of created IBs This value
+ * will be clamped to some reasonable minimum.
+ * @param preallocBufferCnt The pool will allocate this number of VBs at
+ * bufferSize and keep them until it is
+ * destroyed.
+ */
+ GrIndexBufferAllocPool(GrGpu* gpu,
+ bool frequentResetHint,
+ size_t bufferSize = 0,
+ int preallocBufferCnt = 0);
+
+ /**
+ * Returns a block of memory to hold indices. A buffer designated to hold
+ * the indices is given to the caller. The buffer may or may not be locked.
+ * The returned ptr remains valid until any of the following:
+ * *makeSpace is called again.
+ * *unlock is called.
+ * *reset is called.
+ * *this object is destroyed.
+ *
+ * Once unlock on the pool is called the indices are guaranteed to be in the
+ * buffer at the offset indicated by startIndex. Until that time they may be
+ * in temporary storage and/or the buffer may be locked.
+ *
+ * @param indexCount number of indices to allocate space for
+ * @param buffer returns the index buffer that will hold the indices.
+ * @param startIndex returns the offset into buffer of the first index.
+ * @return pointer to first index.
+ */
+ void* makeSpace(int indexCount,
+ const GrIndexBuffer** buffer,
+ int* startIndex);
+
+ /**
+ * Shortcut to make space and then write indices into the made space.
+ */
+ bool appendIndices(int indexCount,
+ const void* indices,
+ const GrIndexBuffer** buffer,
+ int* startIndex);
+
+ /**
+ * Gets the number of indices that can be added to the current IB without
+ * spilling to another IB. If the pool has been reset, or the previous
+ * makeSpace completely exhausted a IB then the returned number of indices
+ * would fit in the next available preallocated buffer. If any makeSpace
+ * would force a new IB to be created the return value will be zero.
+ */
+ int currentBufferIndices() const;
+
+ /**
+ * Gets the number of indices that can fit in a preallocated index buffer.
+ * Zero if no preallocated buffers.
+ *
+ * @return number of indices that fit in one of the preallocated index
+ * buffers.
+ */
+ int preallocatedBufferIndices() const;
+
+private:
+ typedef GrBufferAllocPool INHERITED;
+};
+
+#endif
diff --git a/gpu/GrCacheID.cpp b/gpu/GrCacheID.cpp
new file mode 100644
index 00000000..87917ac9
--- /dev/null
+++ b/gpu/GrCacheID.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 "GrTypes.h"
+#include "SkThread.h" // for sk_atomic_inc
+
+// Well, the dummy_ "fix" caused a warning on windows, so hiding all of it
+// until we can find a universal fix.
+#if 0
+// This used to be a global scope, but we got a warning about unused variable
+// so we moved it into here. We just want it to compile, so we can test the
+// static asserts.
+static inline void dummy_function_to_avoid_unused_var_warning() {
+ GrCacheID::Key kAssertKey;
+ GR_STATIC_ASSERT(sizeof(kAssertKey.fData8) == sizeof(kAssertKey.fData32));
+ GR_STATIC_ASSERT(sizeof(kAssertKey.fData8) == sizeof(kAssertKey.fData64));
+ GR_STATIC_ASSERT(sizeof(kAssertKey.fData8) == sizeof(kAssertKey));
+}
+#endif
+
+GrCacheID::Domain GrCacheID::GenerateDomain() {
+ static int32_t gNextDomain = kInvalid_Domain + 1;
+
+ int32_t domain = sk_atomic_inc(&gNextDomain);
+ if (domain >= 1 << (8 * sizeof(Domain))) {
+ GrCrash("Too many Cache Domains");
+ }
+
+ return static_cast<Domain>(domain);
+}
diff --git a/gpu/GrClipData.cpp b/gpu/GrClipData.cpp
new file mode 100644
index 00000000..22b43710
--- /dev/null
+++ b/gpu/GrClipData.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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 "GrClipData.h"
+
+#include "GrSurface.h"
+#include "SkRect.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * getConservativeBounds returns the conservative bounding box of the clip
+ * in device (as opposed to canvas) coordinates. If the bounding box is
+ * the result of purely intersections of rects (with an initial replace)
+ * isIntersectionOfRects will be set to true.
+ */
+void GrClipData::getConservativeBounds(const GrSurface* surface,
+ SkIRect* devResult,
+ bool* isIntersectionOfRects) const {
+ SkRect devBounds;
+
+ fClipStack->getConservativeBounds(-fOrigin.fX,
+ -fOrigin.fY,
+ surface->width(),
+ surface->height(),
+ &devBounds,
+ isIntersectionOfRects);
+
+ devBounds.roundOut(devResult);
+}
diff --git a/gpu/GrClipMaskCache.cpp b/gpu/GrClipMaskCache.cpp
new file mode 100644
index 00000000..470cc4a2
--- /dev/null
+++ b/gpu/GrClipMaskCache.cpp
@@ -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.
+ */
+
+#include "GrClipMaskCache.h"
+
+GrClipMaskCache::GrClipMaskCache()
+ : fContext(NULL)
+ , fStack(sizeof(GrClipStackFrame)) {
+ // We need an initial frame to capture the clip state prior to
+ // any pushes
+ SkNEW_PLACEMENT(fStack.push_back(), GrClipStackFrame);
+}
+
+void GrClipMaskCache::push() {
+ SkNEW_PLACEMENT(fStack.push_back(), GrClipStackFrame);
+}
diff --git a/gpu/GrClipMaskCache.h b/gpu/GrClipMaskCache.h
new file mode 100644
index 00000000..330a1f21
--- /dev/null
+++ b/gpu/GrClipMaskCache.h
@@ -0,0 +1,239 @@
+/*
+ * 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 GrClipMaskCache_DEFINED
+#define GrClipMaskCache_DEFINED
+
+#include "GrContext.h"
+#include "GrNoncopyable.h"
+#include "SkClipStack.h"
+
+class GrTexture;
+
+/**
+ * The stencil buffer stores the last clip path - providing a single entry
+ * "cache". This class provides similar functionality for AA clip paths
+ */
+class GrClipMaskCache : public GrNoncopyable {
+public:
+ GrClipMaskCache();
+
+ ~GrClipMaskCache() {
+
+ while (!fStack.empty()) {
+ GrClipStackFrame* temp = (GrClipStackFrame*) fStack.back();
+ temp->~GrClipStackFrame();
+ fStack.pop_back();
+ }
+ }
+
+ bool canReuse(int32_t clipGenID, const SkIRect& bounds) {
+
+ 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 == bounds &&
+ back->fLastClipGenID == clipGenID) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void reset() {
+ if (fStack.empty()) {
+// GrAssert(false);
+ return;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ back->reset();
+ }
+
+ /**
+ * After a "push" the clip state is entirely open. Currently, the
+ * entire clip stack will be re-rendered into a new clip mask.
+ * TODO: can we take advantage of the nested nature of the clips to
+ * reduce the mask creation cost?
+ */
+ void push();
+
+ void pop() {
+ //GrAssert(!fStack.empty());
+
+ if (!fStack.empty()) {
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ back->~GrClipStackFrame();
+ fStack.pop_back();
+ }
+ }
+
+ int32_t getLastClipGenID() const {
+
+ if (fStack.empty()) {
+ return SkClipStack::kInvalidGenID;
+ }
+
+ return ((GrClipStackFrame*) fStack.back())->fLastClipGenID;
+ }
+
+ GrTexture* getLastMask() {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ return NULL;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ return back->fLastMask.texture();
+ }
+
+ const GrTexture* getLastMask() const {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ return NULL;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ return back->fLastMask.texture();
+ }
+
+ void acquireMask(int32_t clipGenID,
+ const GrTextureDesc& desc,
+ const SkIRect& bound) {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ return;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ back->acquireMask(fContext, clipGenID, desc, bound);
+ }
+
+ int getLastMaskWidth() const {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ return -1;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ if (NULL == back->fLastMask.texture()) {
+ return -1;
+ }
+
+ return back->fLastMask.texture()->width();
+ }
+
+ int getLastMaskHeight() const {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ return -1;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ if (NULL == back->fLastMask.texture()) {
+ return -1;
+ }
+
+ return back->fLastMask.texture()->height();
+ }
+
+ void getLastBound(SkIRect* bound) const {
+
+ if (fStack.empty()) {
+ GrAssert(false);
+ bound->setEmpty();
+ return;
+ }
+
+ GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+ *bound = back->fLastBound;
+ }
+
+ void setContext(GrContext* context) {
+ fContext = context;
+ }
+
+ GrContext* getContext() {
+ return fContext;
+ }
+
+ void releaseResources() {
+
+ SkDeque::F2BIter iter(fStack);
+ for (GrClipStackFrame* frame = (GrClipStackFrame*) iter.next();
+ frame != NULL;
+ frame = (GrClipStackFrame*) iter.next()) {
+ frame->reset();
+ }
+ }
+
+private:
+ struct GrClipStackFrame {
+
+ GrClipStackFrame() {
+ this->reset();
+ }
+
+ void acquireMask(GrContext* context,
+ int32_t clipGenID,
+ const GrTextureDesc& desc,
+ const SkIRect& bound) {
+
+ fLastClipGenID = clipGenID;
+
+ fLastMask.set(context, desc);
+
+ fLastBound = bound;
+ }
+
+ void reset () {
+ fLastClipGenID = SkClipStack::kInvalidGenID;
+
+ GrTextureDesc desc;
+
+ fLastMask.set(NULL, desc);
+ fLastBound.setEmpty();
+ }
+
+ 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 clip-stack space. This rect is
+ // used by GrClipMaskManager to position a rect and compute texture coords for the mask.
+ SkIRect fLastBound;
+ };
+
+ GrContext* fContext;
+ SkDeque fStack;
+
+ typedef GrNoncopyable INHERITED;
+};
+
+#endif // GrClipMaskCache_DEFINED
diff --git a/gpu/GrClipMaskManager.cpp b/gpu/GrClipMaskManager.cpp
new file mode 100644
index 00000000..806928ca
--- /dev/null
+++ b/gpu/GrClipMaskManager.cpp
@@ -0,0 +1,1013 @@
+
+/*
+ * 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 "GrClipMaskManager.h"
+#include "GrAAConvexPathRenderer.h"
+#include "GrAAHairLinePathRenderer.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+#include "GrPaint.h"
+#include "GrPathRenderer.h"
+#include "GrRenderTarget.h"
+#include "GrStencilBuffer.h"
+#include "GrSWMaskHelper.h"
+#include "effects/GrTextureDomainEffect.h"
+#include "SkRasterClip.h"
+#include "SkStrokeRec.h"
+#include "SkTLazy.h"
+
+#define GR_AA_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
+// stage matrix this also alters the vertex layout
+void setup_drawstate_aaclip(GrGpu* gpu,
+ GrTexture* result,
+ const SkIRect &devBound) {
+ GrDrawState* drawState = gpu->drawState();
+ GrAssert(drawState);
+
+ SkMatrix mat;
+ // We want to use device coords to compute the texture coordinates. We set our matrix to be
+ // equal to the view matrix followed by an offset to the devBound, and then a scaling matrix to
+ // normalized coords. We apply this matrix to the vertex positions rather than local coords.
+ mat.setIDiv(result->width(), result->height());
+ mat.preTranslate(SkIntToScalar(-devBound.fLeft),
+ SkIntToScalar(-devBound.fTop));
+ mat.preConcat(drawState->getViewMatrix());
+
+ SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
+ // This could be a long-lived effect that is cached with the alpha-mask.
+ drawState->addCoverageEffect(
+ GrTextureDomainEffect::Create(result,
+ mat,
+ GrTextureDomainEffect::MakeTexelDomain(result, domainTexels),
+ GrTextureDomainEffect::kDecal_WrapMode,
+ GrTextureParams::kNone_FilterMode,
+ GrEffect::kPosition_CoordsType))->unref();
+}
+
+bool path_needs_SW_renderer(GrContext* context,
+ GrGpu* gpu,
+ const SkPath& origPath,
+ const SkStrokeRec& stroke,
+ bool doAA) {
+ // 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 NULL == context->getPathRenderer(*path, stroke, gpu, false, type);
+}
+
+}
+
+/*
+ * This method traverses the clip stack to see if the GrSoftwarePathRenderer
+ * 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 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.
+ 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 (Element::kPath_Type == element->getType() &&
+ path_needs_SW_renderer(this->getContext(), fGpu,
+ element->getPath(),
+ stroke,
+ element->isAA())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sort out what kind of clip mask needs to be created: alpha, stencil,
+// scissor, or entirely software
+bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn,
+ GrDrawState::AutoRestoreEffects* are) {
+ fCurrClipMaskType = kNone_ClipMaskType;
+
+ ElementList elements(16);
+ InitialState initialState;
+ SkIRect clipSpaceIBounds;
+ bool requiresAA;
+ bool isRect = false;
+
+ GrDrawState* drawState = fGpu->drawState();
+
+ const GrRenderTarget* rt = drawState->getRenderTarget();
+ // GrDrawTarget should have filtered this for us
+ GrAssert(NULL != rt);
+
+ 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;
+ }
+ }
+ }
+
+ if (ignoreClip) {
+ fGpu->disableScissor();
+ this->setGpuStencil();
+ return true;
+ }
+
+#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.
+ if (0 == rt->numSamples() && requiresAA) {
+ int32_t genID = clipDataIn->fClipStack->getTopmostGenID();
+ GrTexture* result = NULL;
+
+ 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 (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);
+ are->set(fGpu->drawState());
+ setup_drawstate_aaclip(fGpu, result, rtSpaceMaskBounds);
+ fGpu->disableScissor();
+ this->setGpuStencil();
+ return true;
+ }
+ // 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 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 (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.
+ 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;
+}
+
+#define VISUALIZE_COMPLEX_CLIP 0
+
+#if VISUALIZE_COMPLEX_CLIP
+ #include "SkRandom.h"
+ SkRandom gRandom;
+ #define SET_RANDOM_COLOR drawState->setColor(0xff000000 | gRandom.nextU());
+#else
+ #define SET_RANDOM_COLOR
+#endif
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+// set up the OpenGL blend function to perform the specified
+// boolean operation for alpha clip mask creation
+void setup_boolean_blendcoeffs(GrDrawState* drawState, SkRegion::Op op) {
+
+ switch (op) {
+ case SkRegion::kReplace_Op:
+ drawState->setBlendFunc(kOne_GrBlendCoeff, kZero_GrBlendCoeff);
+ break;
+ case SkRegion::kIntersect_Op:
+ drawState->setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
+ break;
+ case SkRegion::kUnion_Op:
+ drawState->setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
+ break;
+ case SkRegion::kXOR_Op:
+ drawState->setBlendFunc(kIDC_GrBlendCoeff, kISC_GrBlendCoeff);
+ break;
+ case SkRegion::kDifference_Op:
+ drawState->setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
+ break;
+ case SkRegion::kReverseDifference_Op:
+ drawState->setBlendFunc(kIDC_GrBlendCoeff, kZero_GrBlendCoeff);
+ break;
+ default:
+ GrAssert(false);
+ break;
+ }
+}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool GrClipMaskManager::drawElement(GrTexture* target,
+ const SkClipStack::Element* element,
+ GrPathRenderer* pr) {
+ GrDrawState* drawState = fGpu->drawState();
+
+ drawState->setRenderTarget(target->asRenderTarget());
+
+ 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(),
+ SkMatrix::I(),
+ 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;
+ }
+ default:
+ // something is wrong if we're trying to draw an empty element.
+ GrCrash("Unexpected element type");
+ return false;
+ }
+ return true;
+}
+
+bool GrClipMaskManager::canStencilAndDrawElement(GrTexture* target,
+ const SkClipStack::Element* element,
+ GrPathRenderer** pr) {
+ GrDrawState* drawState = fGpu->drawState();
+ drawState->setRenderTarget(target->asRenderTarget());
+
+ 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 SkIRect& dstBound,
+ const SkIRect& srcBound) {
+ GrDrawState::AutoViewMatrixRestore avmr;
+ GrDrawState* drawState = fGpu->drawState();
+ SkAssertResult(avmr.setIdentity(drawState));
+ GrDrawState::AutoRestoreEffects are(drawState);
+
+ drawState->setRenderTarget(dstMask->asRenderTarget());
+
+ setup_boolean_blendcoeffs(drawState, op);
+
+ SkMatrix sampleM;
+ sampleM.setIDiv(srcMask->width(), srcMask->height());
+
+ drawState->addColorEffect(
+ GrTextureDomainEffect::Create(srcMask,
+ sampleM,
+ GrTextureDomainEffect::MakeTexelDomain(srcMask, srcBound),
+ GrTextureDomainEffect::kDecal_WrapMode,
+ GrTextureParams::kNone_FilterMode))->unref();
+ fGpu->drawSimpleRect(SkRect::MakeFromIRect(dstBound), NULL);
+}
+
+// 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(int width, int height, GrAutoScratchTexture* temp) {
+ if (NULL != temp->texture()) {
+ // we've already allocated the temp texture
+ return;
+ }
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit|kNoStencil_GrTextureFlagBit;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+
+ temp->set(this->getContext(), desc);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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();
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = clipSpaceIBounds.width();
+ desc.fHeight = clipSpaceIBounds.height();
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+ if (this->getContext()->isConfigRenderable(kAlpha_8_GrPixelConfig)) {
+ // We would always like A8 but it isn't supported on all platforms
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+ }
+
+ fAACache.acquireMask(clipStackGenID, desc, clipSpaceIBounds);
+ }
+
+ *result = fAACache.getLastMask();
+ return cached;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Create a 8-bit clip mask in alpha
+GrTexture* GrClipMaskManager::createAlphaClipMask(int32_t clipStackGenID,
+ InitialState initialState,
+ const ElementList& elements,
+ const SkIRect& clipSpaceIBounds) {
+ GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
+
+ GrTexture* result;
+ if (this->getMaskTexture(clipStackGenID, clipSpaceIBounds, &result)) {
+ fCurrClipMaskType = kAlpha_ClipMaskType;
+ return result;
+ }
+
+ if (NULL == result) {
+ fAACache.reset();
+ return NULL;
+ }
+
+ // 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());
+
+ // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
+ SkMatrix translate;
+ translate.setTranslate(clipToMaskOffset);
+ GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit, &translate);
+
+ GrDrawState* drawState = fGpu->drawState();
+
+ // We're drawing a coverage mask and want coverage to be run through the blend function.
+ drawState->enableState(GrDrawState::kCoverageDrawing_StateBit);
+
+ // 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());
+
+ // 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;
+ // walk through each clip element and perform its set op
+ for (ElementList::Iter iter = elements.headIter(); iter.get(); iter.next()) {
+ const Element* element = iter.get();
+ SkRegion::Op op = element->getOp();
+ bool invert = element->isInverseFilled();
+
+ 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.
+ SkIRect maskSpaceElementIBounds;
+
+ if (useTemp) {
+ if (invert) {
+ maskSpaceElementIBounds = maskSpaceIBounds;
+ } else {
+ SkRect elementBounds = element->getBounds();
+ elementBounds.offset(clipToMaskOffset);
+ elementBounds.roundOut(&maskSpaceElementIBounds);
+ }
+
+ 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);
+
+ } 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);
+ }
+
+ drawState->setAlpha(invert ? 0x00 : 0xff);
+
+ if (!this->drawElement(dst, element, pr)) {
+ fAACache.reset();
+ return NULL;
+ }
+
+ 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
+ drawState->setAlpha(0xff);
+ setup_boolean_blendcoeffs(drawState, op);
+ this->drawElement(result, element);
+ }
+ }
+
+ fCurrClipMaskType = kAlpha_ClipMaskType;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device
+// (as opposed to canvas) coordinates
+bool GrClipMaskManager::createStencilClipMask(InitialState initialState,
+ const ElementList& elements,
+ const SkIRect& clipSpaceIBounds,
+ const SkIPoint& clipSpaceToStencilOffset) {
+
+ GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
+
+ GrDrawState* drawState = fGpu->drawState();
+ GrAssert(drawState->isClipState());
+
+ GrRenderTarget* rt = drawState->getRenderTarget();
+ GrAssert(NULL != rt);
+
+ // TODO: dynamically attach a SB when needed.
+ GrStencilBuffer* stencilBuffer = rt->getStencilBuffer();
+ if (NULL == stencilBuffer) {
+ return false;
+ }
+ int32_t genID = elements.tail()->getGenID();
+
+ if (stencilBuffer->mustRenderClip(genID, clipSpaceIBounds, clipSpaceToStencilOffset)) {
+
+ stencilBuffer->setLastClip(genID, clipSpaceIBounds, clipSpaceToStencilOffset);
+
+ // Set the matrix so that rendered clip elements are transformed from clip to stencil space.
+ SkVector translate = {
+ SkIntToScalar(clipSpaceToStencilOffset.fX),
+ SkIntToScalar(clipSpaceToStencilOffset.fY)
+ };
+ SkMatrix matrix;
+ matrix.setTranslate(translate);
+ GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit, &matrix);
+ drawState = fGpu->drawState();
+
+ drawState->setRenderTarget(rt);
+
+ // 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);
+
+#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");
+ clipBit = (1 << (clipBit-1));
+
+ fGpu->clearStencilClip(stencilSpaceIBounds, kAllIn_InitialState == initialState);
+
+ // walk through each clip element and perform its set op
+ // with the existing clip.
+ for (ElementList::Iter iter(elements.headIter()); NULL != iter.get(); iter.next()) {
+ const Element* element = iter.get();
+ 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, element->isAA());
+ }
+
+ // This will be used to determine whether the clip shape can be rendered into the
+ // stencil with arbitrary stencil settings.
+ GrPathRenderer::StencilSupport stencilSupport;
+
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+
+ SkRegion::Op op = element->getOp();
+
+ GrPathRenderer* pr = NULL;
+ SkTCopyOnFirstWrite<SkPath> clipPath;
+ if (Element::kRect_Type == element->getType()) {
+ stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport;
+ fillInverted = false;
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ clipPath.init(element->getPath());
+ fillInverted = clipPath->isInverseFillType();
+ if (fillInverted) {
+ clipPath.writable()->toggleInverseFillType();
+ }
+ pr = this->getContext()->getPathRenderer(*clipPath,
+ stroke,
+ fGpu,
+ false,
+ GrPathRendererChain::kStencilOnly_DrawType,
+ &stencilSupport);
+ if (NULL == pr) {
+ return false;
+ }
+ }
+
+ 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);
+
+ // 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);
+ SET_RANDOM_COLOR
+ if (Element::kRect_Type == element->getType()) {
+ *drawState->stencil() = gDrawToStencil;
+ fGpu->drawSimpleRect(element->getRect(), NULL);
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ if (!clipPath->isEmpty()) {
+ if (canRenderDirectToStencil) {
+ *drawState->stencil() = gDrawToStencil;
+ pr->drawPath(*clipPath, stroke, fGpu, false);
+ } else {
+ pr->stencilPath(*clipPath, stroke, fGpu);
+ }
+ }
+ }
+ }
+
+ // now we modify the clip bit by rendering either the clip
+ // element directly or a bounding rect of the entire clip.
+ drawState->enableState(GrGpu::kModifyStencilClip_StateBit);
+ for (int p = 0; p < passes; ++p) {
+ *drawState->stencil() = stencilSettings[p];
+ if (canDrawDirectToClip) {
+ if (Element::kRect_Type == element->getType()) {
+ SET_RANDOM_COLOR
+ fGpu->drawSimpleRect(element->getRect(), NULL);
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ SET_RANDOM_COLOR
+ pr->drawPath(*clipPath, stroke, fGpu, false);
+ }
+ } else {
+ SET_RANDOM_COLOR
+ // The view matrix is setup to do clip space -> stencil space translation, so
+ // draw rect in clip space.
+ fGpu->drawSimpleRect(SkRect::MakeFromIRect(clipSpaceIBounds), NULL);
+ }
+ }
+ }
+ }
+ // set this last because recursive draws may overwrite it back to kNone.
+ GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
+ fCurrClipMaskType = kStencil_ClipMaskType;
+ return true;
+}
+
+
+// mapping of clip-respecting stencil funcs to normal stencil funcs
+// mapping depends on whether stencil-clipping is in effect.
+static const GrStencilFunc
+ gSpecialToBasicStencilFunc[2][kClipStencilFuncCount] = {
+ {// Stencil-Clipping is DISABLED, we are effectively always inside the clip
+ // In the Clip Funcs
+ kAlways_StencilFunc, // kAlwaysIfInClip_StencilFunc
+ kEqual_StencilFunc, // kEqualIfInClip_StencilFunc
+ kLess_StencilFunc, // kLessIfInClip_StencilFunc
+ kLEqual_StencilFunc, // kLEqualIfInClip_StencilFunc
+ // Special in the clip func that forces user's ref to be 0.
+ kNotEqual_StencilFunc, // kNonZeroIfInClip_StencilFunc
+ // make ref 0 and do normal nequal.
+ },
+ {// Stencil-Clipping is ENABLED
+ // In the Clip Funcs
+ kEqual_StencilFunc, // kAlwaysIfInClip_StencilFunc
+ // eq stencil clip bit, mask
+ // out user bits.
+
+ kEqual_StencilFunc, // kEqualIfInClip_StencilFunc
+ // add stencil bit to mask and ref
+
+ kLess_StencilFunc, // kLessIfInClip_StencilFunc
+ kLEqual_StencilFunc, // kLEqualIfInClip_StencilFunc
+ // for both of these we can add
+ // the clip bit to the mask and
+ // ref and compare as normal
+ // Special in the clip func that forces user's ref to be 0.
+ kLess_StencilFunc, // kNonZeroIfInClip_StencilFunc
+ // make ref have only the clip bit set
+ // and make comparison be less
+ // 10..0 < 1..user_bits..
+ }
+};
+
+namespace {
+// Sets the settings to clip against the stencil buffer clip while ignoring the
+// client bits.
+const GrStencilSettings& basic_apply_stencil_clip_settings() {
+ // stencil settings to use when clip is in stencil
+ GR_STATIC_CONST_SAME_STENCIL_STRUCT(gSettings,
+ kKeep_StencilOp,
+ kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ 0x0000,
+ 0x0000,
+ 0x0000);
+ return *GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(&gSettings);
+}
+}
+
+void GrClipMaskManager::setGpuStencil() {
+ // We make two copies of the StencilSettings here (except in the early
+ // exit scenario. One copy from draw state to the stack var. Then another
+ // from the stack var to the gpu. We could make this class hold a ptr to
+ // GrGpu's fStencilSettings and eliminate the stack copy here.
+
+ const GrDrawState& drawState = fGpu->getDrawState();
+
+ // use stencil for clipping if clipping is enabled and the clip
+ // has been written into the stencil.
+ GrClipMaskManager::StencilClipMode clipMode;
+ if (this->isClipInStencil() && drawState.isClipState()) {
+ clipMode = GrClipMaskManager::kRespectClip_StencilClipMode;
+ // We can't be modifying the clip and respecting it at the same time.
+ GrAssert(!drawState.isStateFlagEnabled(
+ GrGpu::kModifyStencilClip_StateBit));
+ } else if (drawState.isStateFlagEnabled(
+ GrGpu::kModifyStencilClip_StateBit)) {
+ clipMode = GrClipMaskManager::kModifyClip_StencilClipMode;
+ } else {
+ clipMode = GrClipMaskManager::kIgnoreClip_StencilClipMode;
+ }
+
+ GrStencilSettings settings;
+ // The GrGpu client may not be using the stencil buffer but we may need to
+ // enable it in order to respect a stencil clip.
+ if (drawState.getStencil().isDisabled()) {
+ if (GrClipMaskManager::kRespectClip_StencilClipMode == clipMode) {
+ settings = basic_apply_stencil_clip_settings();
+ } else {
+ fGpu->disableStencil();
+ return;
+ }
+ } else {
+ settings = drawState.getStencil();
+ }
+
+ // TODO: dynamically attach a stencil buffer
+ int stencilBits = 0;
+ GrStencilBuffer* stencilBuffer =
+ drawState.getRenderTarget()->getStencilBuffer();
+ if (NULL != stencilBuffer) {
+ stencilBits = stencilBuffer->bits();
+ }
+
+ GrAssert(fGpu->caps()->stencilWrapOpsSupport() || !settings.usesWrapOp());
+ GrAssert(fGpu->caps()->twoSidedStencilSupport() || !settings.isTwoSided());
+ this->adjustStencilParams(&settings, clipMode, stencilBits);
+ fGpu->setStencilSettings(settings);
+}
+
+void GrClipMaskManager::adjustStencilParams(GrStencilSettings* settings,
+ StencilClipMode mode,
+ int stencilBitCnt) {
+ GrAssert(stencilBitCnt > 0);
+
+ if (kModifyClip_StencilClipMode == mode) {
+ // We assume that this clip manager itself is drawing to the GrGpu and
+ // has already setup the correct values.
+ return;
+ }
+
+ unsigned int clipBit = (1 << (stencilBitCnt - 1));
+ unsigned int userBits = clipBit - 1;
+
+ GrStencilSettings::Face face = GrStencilSettings::kFront_Face;
+ bool twoSided = fGpu->caps()->twoSidedStencilSupport();
+
+ bool finished = false;
+ while (!finished) {
+ GrStencilFunc func = settings->func(face);
+ uint16_t writeMask = settings->writeMask(face);
+ uint16_t funcMask = settings->funcMask(face);
+ uint16_t funcRef = settings->funcRef(face);
+
+ GrAssert((unsigned) func < kStencilFuncCount);
+
+ writeMask &= userBits;
+
+ if (func >= kBasicStencilFuncCount) {
+ int respectClip = kRespectClip_StencilClipMode == mode;
+ if (respectClip) {
+ // The GrGpu class should have checked this
+ GrAssert(this->isClipInStencil());
+ switch (func) {
+ case kAlwaysIfInClip_StencilFunc:
+ funcMask = clipBit;
+ funcRef = clipBit;
+ break;
+ case kEqualIfInClip_StencilFunc:
+ case kLessIfInClip_StencilFunc:
+ case kLEqualIfInClip_StencilFunc:
+ funcMask = (funcMask & userBits) | clipBit;
+ funcRef = (funcRef & userBits) | clipBit;
+ break;
+ case kNonZeroIfInClip_StencilFunc:
+ funcMask = (funcMask & userBits) | clipBit;
+ funcRef = clipBit;
+ break;
+ default:
+ GrCrash("Unknown stencil func");
+ }
+ } else {
+ funcMask &= userBits;
+ funcRef &= userBits;
+ }
+ const GrStencilFunc* table =
+ gSpecialToBasicStencilFunc[respectClip];
+ func = table[func - kBasicStencilFuncCount];
+ GrAssert(func >= 0 && func < kBasicStencilFuncCount);
+ } else {
+ funcMask &= userBits;
+ funcRef &= userBits;
+ }
+
+ settings->setFunc(face, func);
+ settings->setWriteMask(face, writeMask);
+ settings->setFuncMask(face, funcMask);
+ settings->setFuncRef(face, funcRef);
+
+ if (GrStencilSettings::kFront_Face == face) {
+ face = GrStencilSettings::kBack_Face;
+ finished = !twoSided;
+ } else {
+ finished = true;
+ }
+ }
+ if (!twoSided) {
+ settings->copyFrontSettingsToBack();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+GrTexture* GrClipMaskManager::createSoftwareClipMask(int32_t clipStackGenID,
+ GrReducedClip::InitialState initialState,
+ const GrReducedClip::ElementList& elements,
+ const SkIRect& clipSpaceIBounds) {
+ GrAssert(kNone_ClipMaskType == fCurrClipMaskType);
+
+ GrTexture* result;
+ if (this->getMaskTexture(clipStackGenID, clipSpaceIBounds, &result)) {
+ return result;
+ }
+
+ if (NULL == result) {
+ fAACache.reset();
+ return NULL;
+ }
+
+ // 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());
+
+ GrSWMaskHelper helper(this->getContext());
+
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(-clipSpaceIBounds.fLeft),
+ SkIntToScalar(-clipSpaceIBounds.fTop));
+ helper.init(maskSpaceIBounds, &matrix);
+
+ helper.clear(kAllIn_InitialState == initialState ? 0xFF : 0x00);
+
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+
+ for (ElementList::Iter iter(elements.headIter()) ; NULL != iter.get(); iter.next()) {
+
+ 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::kReverseDifference_Op == op) {
+ SkRect temp = SkRect::MakeFromIRect(clipSpaceIBounds);
+ // invert the entire scene
+ helper.draw(temp, SkRegion::kXOR_Op, false, 0xFF);
+ }
+
+ if (Element::kRect_Type == element->getType()) {
+ // convert the rect to a path so we can invert the fill
+ SkPath temp;
+ temp.addRect(element->getRect());
+ temp.setFillType(SkPath::kInverseEvenOdd_FillType);
+
+ helper.draw(temp, stroke, SkRegion::kReplace_Op,
+ element->isAA(),
+ 0x00);
+ } else {
+ GrAssert(Element::kPath_Type == element->getType());
+ SkPath clipPath = element->getPath();
+ clipPath.toggleInverseFillType();
+ helper.draw(clipPath, stroke,
+ SkRegion::kReplace_Op,
+ element->isAA(),
+ 0x00);
+ }
+
+ continue;
+ }
+
+ // The other ops (union, xor, diff) only affect pixels inside
+ // the geometry so they can just be drawn normally
+ 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);
+ }
+ }
+
+ helper.toTexture(result);
+
+ fCurrClipMaskType = kAlpha_ClipMaskType;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void GrClipMaskManager::releaseResources() {
+ fAACache.releaseResources();
+}
+
+void GrClipMaskManager::setGpu(GrGpu* gpu) {
+ fGpu = gpu;
+ fAACache.setContext(gpu->getContext());
+}
diff --git a/gpu/GrClipMaskManager.h b/gpu/GrClipMaskManager.h
new file mode 100644
index 00000000..2cba4776
--- /dev/null
+++ b/gpu/GrClipMaskManager.h
@@ -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.
+ */
+
+#ifndef GrClipMaskManager_DEFINED
+#define GrClipMaskManager_DEFINED
+
+#include "GrContext.h"
+#include "GrDrawState.h"
+#include "GrNoncopyable.h"
+#include "GrReducedClip.h"
+#include "GrStencil.h"
+#include "GrTexture.h"
+
+#include "SkClipStack.h"
+#include "SkDeque.h"
+#include "SkPath.h"
+#include "SkRefCnt.h"
+#include "SkTLList.h"
+
+#include "GrClipMaskCache.h"
+
+class GrGpu;
+class GrPathRenderer;
+class GrPathRendererChain;
+class SkPath;
+class GrTexture;
+
+/**
+ * The clip mask creator handles the generation of the clip mask. If anti
+ * aliasing is requested it will (in the future) generate a single channel
+ * (8bit) mask. If no anti aliasing is requested it will generate a 1-bit
+ * mask in the stencil buffer. In the non anti-aliasing case, if the clip
+ * mask can be represented as a rectangle then scissoring is used. In all
+ * cases scissoring is used to bound the range of the clip mask.
+ */
+class GrClipMaskManager : public GrNoncopyable {
+public:
+ GrClipMaskManager()
+ : fGpu(NULL)
+ , fCurrClipMaskType(kNone_ClipMaskType) {
+ }
+
+ /**
+ * Creates a clip mask if necessary as a stencil buffer or alpha texture
+ * and sets the GrGpu's scissor and stencil state. If the return is false
+ * then the draw can be skipped.
+ */
+ bool setupClipping(const GrClipData* clipDataIn, GrDrawState::AutoRestoreEffects*);
+
+ void releaseResources();
+
+ bool isClipInStencil() const {
+ return kStencil_ClipMaskType == fCurrClipMaskType;
+ }
+ bool isClipInAlpha() const {
+ return kAlpha_ClipMaskType == fCurrClipMaskType;
+ }
+
+ void invalidateStencilMask() {
+ if (kStencil_ClipMaskType == fCurrClipMaskType) {
+ fCurrClipMaskType = kNone_ClipMaskType;
+ }
+ }
+
+ GrContext* getContext() {
+ return fAACache.getContext();
+ }
+
+ void setGpu(GrGpu* gpu);
+private:
+ /**
+ * Informs the helper function adjustStencilParams() about how the stencil
+ * buffer clip is being used.
+ */
+ enum StencilClipMode {
+ // Draw to the clip bit of the stencil buffer
+ kModifyClip_StencilClipMode,
+ // Clip against the existing representation of the clip in the high bit
+ // of the stencil buffer.
+ kRespectClip_StencilClipMode,
+ // Neither writing to nor clipping against the clip bit.
+ kIgnoreClip_StencilClipMode,
+ };
+
+ GrGpu* fGpu;
+
+ /**
+ * We may represent the clip as a mask in the stencil buffer or as an alpha
+ * texture. It may be neither because the scissor rect suffices or we
+ * haven't yet examined the clip.
+ */
+ enum ClipMaskType {
+ kNone_ClipMaskType,
+ kStencil_ClipMaskType,
+ kAlpha_ClipMaskType,
+ } fCurrClipMaskType;
+
+ GrClipMaskCache fAACache; // cache for the AA path
+
+ // 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 SkIRect& dstBound,
+ const SkIRect& srcBound);
+
+ void getTemp(int width, int height, GrAutoScratchTexture* temp);
+
+ void setupCache(const SkClipStack& clip,
+ const SkIRect& bounds);
+
+ /**
+ * Called prior to return control back the GrGpu in setupClipping. It
+ * updates the GrGpu with stencil settings that account stencil-based
+ * clipping.
+ */
+ void setGpuStencil();
+
+ /**
+ * Adjusts the stencil settings to account for interaction with stencil
+ * clipping.
+ */
+ void adjustStencilParams(GrStencilSettings* settings,
+ StencilClipMode mode,
+ int stencilBitCnt);
+
+ typedef GrNoncopyable INHERITED;
+};
+
+#endif // GrClipMaskManager_DEFINED
diff --git a/gpu/GrContext.cpp b/gpu/GrContext.cpp
new file mode 100644
index 00000000..7631c8ac
--- /dev/null
+++ b/gpu/GrContext.cpp
@@ -0,0 +1,1735 @@
+
+/*
+ * 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 "GrContext.h"
+
+#include "effects/GrSingleTextureEffect.h"
+#include "effects/GrConfigConversionEffect.h"
+
+#include "GrBufferAllocPool.h"
+#include "GrGpu.h"
+#include "GrDrawTargetCaps.h"
+#include "GrIndexBuffer.h"
+#include "GrInOrderDrawBuffer.h"
+#include "GrOvalRenderer.h"
+#include "GrPathRenderer.h"
+#include "GrPathUtils.h"
+#include "GrResourceCache.h"
+#include "GrSoftwarePathRenderer.h"
+#include "GrStencilBuffer.h"
+#include "GrTextStrike.h"
+#include "SkRTConf.h"
+#include "SkRRect.h"
+#include "SkStrokeRec.h"
+#include "SkTLazy.h"
+#include "SkTLS.h"
+#include "SkTrace.h"
+
+SK_DEFINE_INST_COUNT(GrContext)
+SK_DEFINE_INST_COUNT(GrDrawState)
+
+// It can be useful to set this to false to test whether a bug is caused by using the
+// InOrderDrawBuffer, to compare performance of using/not using InOrderDrawBuffer, or to make
+// debugging simpler.
+SK_CONF_DECLARE(bool, c_Defer, "gpu.deferContext", true,
+ "Defers rendering in GrContext via GrInOrderDrawBuffer.");
+
+#define BUFFERED_DRAW (c_Defer ? kYes_BufferedDraw : kNo_BufferedDraw)
+
+// When we're using coverage AA but the blend is incompatible (given gpu
+// limitations) should we disable AA or draw wrong?
+#define DISABLE_COVERAGE_AA_FOR_BLEND 1
+
+#if GR_DEBUG
+ // change this to a 1 to see notifications when partial coverage fails
+ #define GR_DEBUG_PARTIAL_COVERAGE_CHECK 0
+#else
+ #define GR_DEBUG_PARTIAL_COVERAGE_CHECK 0
+#endif
+
+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;
+
+static const size_t DRAW_BUFFER_IBPOOL_BUFFER_SIZE = 1 << 11;
+static const int DRAW_BUFFER_IBPOOL_PREALLOC_BUFFERS = 4;
+
+#define ASSERT_OWNED_RESOURCE(R) GrAssert(!(R) || (R)->getContext() == this)
+
+// Glorified typedef to avoid including GrDrawState.h in GrContext.h
+class GrContext::AutoRestoreEffects : public GrDrawState::AutoRestoreEffects {};
+
+GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext) {
+ GrContext* context = SkNEW(GrContext);
+ if (context->init(backend, backendContext)) {
+ return context;
+ } else {
+ context->unref();
+ return NULL;
+ }
+}
+
+namespace {
+void* CreateThreadInstanceCount() {
+ return SkNEW_ARGS(int, (0));
+}
+void DeleteThreadInstanceCount(void* v) {
+ delete reinterpret_cast<int*>(v);
+}
+#define THREAD_INSTANCE_COUNT \
+ (*reinterpret_cast<int*>(SkTLS::Get(CreateThreadInstanceCount, DeleteThreadInstanceCount)))
+}
+
+GrContext::GrContext() {
+ ++THREAD_INSTANCE_COUNT;
+ fDrawState = NULL;
+ fGpu = NULL;
+ fClip = NULL;
+ fPathRendererChain = NULL;
+ fSoftwarePathRenderer = NULL;
+ fTextureCache = NULL;
+ fFontCache = NULL;
+ fDrawBuffer = NULL;
+ fDrawBufferVBAllocPool = NULL;
+ fDrawBufferIBAllocPool = NULL;
+ fAARectRenderer = NULL;
+ fOvalRenderer = NULL;
+ fViewMatrix.reset();
+ fMaxTextureSizeOverride = 1 << 20;
+}
+
+bool GrContext::init(GrBackend backend, GrBackendContext backendContext) {
+ GrAssert(NULL == fGpu);
+
+ fGpu = GrGpu::Create(backend, backendContext, this);
+ if (NULL == fGpu) {
+ return false;
+ }
+
+ fDrawState = SkNEW(GrDrawState);
+ fGpu->setDrawState(fDrawState);
+
+ fTextureCache = SkNEW_ARGS(GrResourceCache,
+ (MAX_TEXTURE_CACHE_COUNT,
+ MAX_TEXTURE_CACHE_BYTES));
+ fTextureCache->setOverbudgetCallback(OverbudgetCB, this);
+
+ fFontCache = SkNEW_ARGS(GrFontCache, (fGpu));
+
+ fLastDrawWasBuffered = kNo_BufferedDraw;
+
+ fAARectRenderer = SkNEW(GrAARectRenderer);
+ fOvalRenderer = SkNEW(GrOvalRenderer);
+
+ fDidTestPMConversions = false;
+
+ this->setupDrawBuffer();
+
+ return true;
+}
+
+int GrContext::GetThreadInstanceCount() {
+ return THREAD_INSTANCE_COUNT;
+}
+
+GrContext::~GrContext() {
+ for (int i = 0; i < fCleanUpData.count(); ++i) {
+ (*fCleanUpData[i].fFunc)(this, fCleanUpData[i].fInfo);
+ }
+
+ if (NULL == fGpu) {
+ return;
+ }
+
+ this->flush();
+
+ // Since the gpu can hold scratch textures, give it a chance to let go
+ // of them before freeing the texture cache
+ fGpu->purgeResources();
+
+ delete fTextureCache;
+ fTextureCache = NULL;
+ delete fFontCache;
+ delete fDrawBuffer;
+ delete fDrawBufferVBAllocPool;
+ delete fDrawBufferIBAllocPool;
+
+ fAARectRenderer->unref();
+ fOvalRenderer->unref();
+
+ fGpu->unref();
+ GrSafeUnref(fPathRendererChain);
+ GrSafeUnref(fSoftwarePathRenderer);
+ fDrawState->unref();
+
+ --THREAD_INSTANCE_COUNT;
+}
+
+void GrContext::contextLost() {
+ this->contextDestroyed();
+ this->setupDrawBuffer();
+}
+
+void GrContext::contextDestroyed() {
+ // abandon first to so destructors
+ // don't try to free the resources in the API.
+ fGpu->abandonResources();
+
+ // a path renderer may be holding onto resources that
+ // are now unusable
+ GrSafeSetNull(fPathRendererChain);
+ GrSafeSetNull(fSoftwarePathRenderer);
+
+ delete fDrawBuffer;
+ fDrawBuffer = NULL;
+
+ delete fDrawBufferVBAllocPool;
+ fDrawBufferVBAllocPool = NULL;
+
+ delete fDrawBufferIBAllocPool;
+ fDrawBufferIBAllocPool = NULL;
+
+ fAARectRenderer->reset();
+ fOvalRenderer->reset();
+
+ fTextureCache->purgeAllUnlocked();
+ fFontCache->freeAll();
+ fGpu->markContextDirty();
+}
+
+void GrContext::resetContext(uint32_t state) {
+ fGpu->markContextDirty(state);
+}
+
+void GrContext::freeGpuResources() {
+ this->flush();
+
+ fGpu->purgeResources();
+
+ fAARectRenderer->reset();
+ fOvalRenderer->reset();
+
+ fTextureCache->purgeAllUnlocked();
+ fFontCache->freeAll();
+ // a path renderer may be holding onto resources
+ GrSafeSetNull(fPathRendererChain);
+ GrSafeSetNull(fSoftwarePathRenderer);
+}
+
+size_t GrContext::getGpuTextureCacheBytes() const {
+ return fTextureCache->getCachedResourceBytes();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrTexture* GrContext::findAndRefTexture(const GrTextureDesc& desc,
+ const GrCacheID& cacheID,
+ const GrTextureParams* params) {
+ GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID);
+ GrResource* resource = fTextureCache->find(resourceKey);
+ SkSafeRef(resource);
+ return static_cast<GrTexture*>(resource);
+}
+
+bool GrContext::isTextureInCache(const GrTextureDesc& desc,
+ const GrCacheID& cacheID,
+ const GrTextureParams* params) const {
+ GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID);
+ return fTextureCache->hasKey(resourceKey);
+}
+
+void GrContext::addStencilBuffer(GrStencilBuffer* sb) {
+ ASSERT_OWNED_RESOURCE(sb);
+
+ GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(sb->width(),
+ sb->height(),
+ sb->numSamples());
+ fTextureCache->addResource(resourceKey, sb);
+}
+
+GrStencilBuffer* GrContext::findStencilBuffer(int width, int height,
+ int sampleCnt) {
+ GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(width,
+ height,
+ sampleCnt);
+ GrResource* resource = fTextureCache->find(resourceKey);
+ return static_cast<GrStencilBuffer*>(resource);
+}
+
+static void stretchImage(void* dst,
+ int dstW,
+ int dstH,
+ void* src,
+ int srcW,
+ int srcH,
+ int bpp) {
+ GrFixed dx = (srcW << 16) / dstW;
+ GrFixed dy = (srcH << 16) / dstH;
+
+ GrFixed y = dy >> 1;
+
+ int dstXLimit = dstW*bpp;
+ for (int j = 0; j < dstH; ++j) {
+ GrFixed x = dx >> 1;
+ void* srcRow = (uint8_t*)src + (y>>16)*srcW*bpp;
+ void* dstRow = (uint8_t*)dst + j*dstW*bpp;
+ for (int i = 0; i < dstXLimit; i += bpp) {
+ memcpy((uint8_t*) dstRow + i,
+ (uint8_t*) srcRow + (x>>16)*bpp,
+ bpp);
+ x += dx;
+ }
+ y += dy;
+ }
+}
+
+namespace {
+
+// position + local coordinate
+extern const GrVertexAttrib gVertexAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding}
+};
+
+};
+
+// The desired texture is NPOT and tiled but that isn't supported by
+// the current hardware. Resize the texture to be a POT
+GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc,
+ const GrCacheID& cacheID,
+ void* srcData,
+ size_t rowBytes,
+ bool filter) {
+ SkAutoTUnref<GrTexture> clampedTexture(this->findAndRefTexture(desc, cacheID, NULL));
+ if (NULL == clampedTexture) {
+ clampedTexture.reset(this->createTexture(NULL, desc, cacheID, srcData, rowBytes));
+
+ if (NULL == clampedTexture) {
+ return NULL;
+ }
+ }
+
+ GrTextureDesc rtDesc = desc;
+ rtDesc.fFlags = rtDesc.fFlags |
+ kRenderTarget_GrTextureFlagBit |
+ kNoStencil_GrTextureFlagBit;
+ rtDesc.fWidth = GrNextPow2(GrMax(desc.fWidth, 64));
+ rtDesc.fHeight = GrNextPow2(GrMax(desc.fHeight, 64));
+
+ GrTexture* texture = fGpu->createTexture(rtDesc, NULL, 0);
+
+ if (NULL != texture) {
+ GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
+ GrDrawState* drawState = fGpu->drawState();
+ drawState->setRenderTarget(texture->asRenderTarget());
+
+ // if filtering is not desired then we want to ensure all
+ // texels in the resampled image are copies of texels from
+ // the original.
+ GrTextureParams params(SkShader::kClamp_TileMode, filter ? GrTextureParams::kBilerp_FilterMode :
+ GrTextureParams::kNone_FilterMode);
+ drawState->addColorTextureEffect(clampedTexture, SkMatrix::I(), params);
+
+ drawState->setVertexAttribs<gVertexAttribs>(SK_ARRAY_COUNT(gVertexAttribs));
+
+ GrDrawTarget::AutoReleaseGeometry arg(fGpu, 4, 0);
+
+ if (arg.succeeded()) {
+ GrPoint* verts = (GrPoint*) arg.vertices();
+ verts[0].setIRectFan(0, 0, texture->width(), texture->height(), 2 * sizeof(GrPoint));
+ verts[1].setIRectFan(0, 0, 1, 1, 2 * sizeof(GrPoint));
+ fGpu->drawNonIndexed(kTriangleFan_GrPrimitiveType, 0, 4);
+ }
+ } else {
+ // TODO: Our CPU stretch doesn't filter. But we create separate
+ // 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.
+
+ rtDesc.fFlags = kNone_GrTextureFlags;
+ // no longer need to clamp at min RT size.
+ 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);
+ stretchImage(stretchedPixels.get(), rtDesc.fWidth, rtDesc.fHeight,
+ srcData, desc.fWidth, desc.fHeight, bpp);
+
+ size_t stretchedRowBytes = rtDesc.fWidth * bpp;
+
+ SkDEBUGCODE(GrTexture* texture = )fGpu->createTexture(rtDesc, stretchedPixels.get(),
+ stretchedRowBytes);
+ GrAssert(NULL != texture);
+ }
+
+ return texture;
+}
+
+GrTexture* GrContext::createTexture(const GrTextureParams* params,
+ const GrTextureDesc& desc,
+ const GrCacheID& cacheID,
+ void* srcData,
+ size_t rowBytes) {
+ SK_TRACE_EVENT0("GrContext::createTexture");
+
+ GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID);
+
+ GrTexture* texture;
+ if (GrTexture::NeedsResizing(resourceKey)) {
+ texture = this->createResizedTexture(desc, cacheID,
+ srcData, rowBytes,
+ GrTexture::NeedsBilerp(resourceKey));
+ } else {
+ texture= fGpu->createTexture(desc, srcData, rowBytes);
+ }
+
+ if (NULL != texture) {
+ // Adding a resource could put us overbudget. Try to free up the
+ // necessary space before adding it.
+ fTextureCache->purgeAsNeeded(1, texture->sizeInBytes());
+ fTextureCache->addResource(resourceKey, texture);
+ }
+
+ return texture;
+}
+
+static GrTexture* create_scratch_texture(GrGpu* gpu,
+ GrResourceCache* textureCache,
+ const GrTextureDesc& desc) {
+ GrTexture* texture = gpu->createTexture(desc, NULL, 0);
+ if (NULL != texture) {
+ GrResourceKey key = GrTexture::ComputeScratchKey(texture->desc());
+ // Adding a resource could put us overbudget. Try to free up the
+ // necessary space before adding it.
+ textureCache->purgeAsNeeded(1, texture->sizeInBytes());
+ // Make the resource exclusive so future 'find' calls don't return it
+ textureCache->addResource(key, texture, GrResourceCache::kHide_OwnershipFlag);
+ }
+ return texture;
+}
+
+GrTexture* GrContext::lockAndRefScratchTexture(const GrTextureDesc& inDesc, ScratchTexMatch match) {
+
+ GrAssert((inDesc.fFlags & kRenderTarget_GrTextureFlagBit) ||
+ !(inDesc.fFlags & kNoStencil_GrTextureFlagBit));
+
+ // Renderable A8 targets are not universally supported (e.g., not on ANGLE)
+ GrAssert(this->isConfigRenderable(kAlpha_8_GrPixelConfig) ||
+ !(inDesc.fFlags & kRenderTarget_GrTextureFlagBit) ||
+ (inDesc.fConfig != kAlpha_8_GrPixelConfig));
+
+ if (!fGpu->caps()->reuseScratchTextures()) {
+ // If we're never recycling scratch textures we can
+ // always make them the right size
+ return create_scratch_texture(fGpu, fTextureCache, inDesc);
+ }
+
+ GrTextureDesc desc = inDesc;
+
+ if (kApprox_ScratchTexMatch == match) {
+ // bin by pow2 with a reasonable min
+ static const int MIN_SIZE = 16;
+ desc.fWidth = GrMax(MIN_SIZE, GrNextPow2(desc.fWidth));
+ desc.fHeight = GrMax(MIN_SIZE, GrNextPow2(desc.fHeight));
+ }
+
+ GrResource* resource = NULL;
+ int origWidth = desc.fWidth;
+ int origHeight = desc.fHeight;
+
+ do {
+ GrResourceKey key = GrTexture::ComputeScratchKey(desc);
+ // Ensure we have exclusive access to the texture so future 'find' calls don't return it
+ resource = fTextureCache->find(key, GrResourceCache::kHide_OwnershipFlag);
+ if (NULL != resource) {
+ resource->ref();
+ break;
+ }
+ if (kExact_ScratchTexMatch == match) {
+ break;
+ }
+ // We had a cache miss and we are in approx mode, relax the fit of the flags.
+
+ // We no longer try to reuse textures that were previously used as render targets in
+ // situations where no RT is needed; doing otherwise can confuse the video driver and
+ // cause significant performance problems in some cases.
+ if (desc.fFlags & kNoStencil_GrTextureFlagBit) {
+ desc.fFlags = desc.fFlags & ~kNoStencil_GrTextureFlagBit;
+ } else {
+ break;
+ }
+
+ } while (true);
+
+ if (NULL == resource) {
+ desc.fFlags = inDesc.fFlags;
+ desc.fWidth = origWidth;
+ desc.fHeight = origHeight;
+ resource = create_scratch_texture(fGpu, fTextureCache, desc);
+ }
+
+ return static_cast<GrTexture*>(resource);
+}
+
+void GrContext::addExistingTextureToCache(GrTexture* texture) {
+
+ if (NULL == texture) {
+ return;
+ }
+
+ // This texture should already have a cache entry since it was once
+ // attached
+ GrAssert(NULL != texture->getCacheEntry());
+
+ // Conceptually, the cache entry is going to assume responsibility
+ // for the creation ref.
+ GrAssert(texture->unique());
+
+ // Since this texture came from an AutoScratchTexture it should
+ // still be in the exclusive pile
+ fTextureCache->makeNonExclusive(texture->getCacheEntry());
+
+ if (fGpu->caps()->reuseScratchTextures()) {
+ this->purgeCache();
+ } else {
+ // When we aren't reusing textures we know this scratch texture
+ // will never be reused and would be just wasting time in the cache
+ fTextureCache->deleteResource(texture->getCacheEntry());
+ }
+}
+
+
+void GrContext::unlockScratchTexture(GrTexture* texture) {
+ ASSERT_OWNED_RESOURCE(texture);
+ GrAssert(NULL != texture->getCacheEntry());
+
+ // If this is a scratch texture we detached it from the cache
+ // while it was locked (to avoid two callers simultaneously getting
+ // the same texture).
+ if (texture->getCacheEntry()->key().isScratch()) {
+ fTextureCache->makeNonExclusive(texture->getCacheEntry());
+ this->purgeCache();
+ }
+}
+
+void GrContext::purgeCache() {
+ if (NULL != fTextureCache) {
+ fTextureCache->purgeAsNeeded();
+ }
+}
+
+bool GrContext::OverbudgetCB(void* data) {
+ GrAssert(NULL != data);
+
+ GrContext* context = reinterpret_cast<GrContext*>(data);
+
+ // Flush the InOrderDrawBuffer to possibly free up some textures
+ context->flush();
+
+ // TODO: actually track flush's behavior rather than always just
+ // returning true.
+ return true;
+}
+
+
+GrTexture* GrContext::createUncachedTexture(const GrTextureDesc& descIn,
+ void* srcData,
+ size_t rowBytes) {
+ GrTextureDesc descCopy = descIn;
+ return fGpu->createTexture(descCopy, srcData, rowBytes);
+}
+
+void GrContext::getTextureCacheLimits(int* maxTextures,
+ size_t* maxTextureBytes) const {
+ fTextureCache->getLimits(maxTextures, maxTextureBytes);
+}
+
+void GrContext::setTextureCacheLimits(int maxTextures, size_t maxTextureBytes) {
+ fTextureCache->setLimits(maxTextures, maxTextureBytes);
+}
+
+int GrContext::getMaxTextureSize() const {
+ return GrMin(fGpu->caps()->maxTextureSize(), fMaxTextureSizeOverride);
+}
+
+int GrContext::getMaxRenderTargetSize() const {
+ return fGpu->caps()->maxRenderTargetSize();
+}
+
+int GrContext::getMaxSampleCount() const {
+ return fGpu->caps()->maxSampleCount();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrTexture* GrContext::wrapBackendTexture(const GrBackendTextureDesc& desc) {
+ return fGpu->wrapBackendTexture(desc);
+}
+
+GrRenderTarget* GrContext::wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
+ return fGpu->wrapBackendRenderTarget(desc);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GrContext::supportsIndex8PixelConfig(const GrTextureParams* params,
+ int width, int height) const {
+ const GrDrawTargetCaps* caps = fGpu->caps();
+ if (!caps->eightBitPaletteSupport()) {
+ return false;
+ }
+
+ bool isPow2 = GrIsPow2(width) && GrIsPow2(height);
+
+ if (!isPow2) {
+ bool tiled = NULL != params && params->isTiled();
+ if (tiled && !caps->npotTextureTileSupport()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrContext::clear(const SkIRect* rect,
+ const GrColor color,
+ GrRenderTarget* target) {
+ AutoRestoreEffects are;
+ this->prepareToDraw(NULL, BUFFERED_DRAW, &are)->clear(rect, color, target);
+}
+
+void GrContext::drawPaint(const GrPaint& origPaint) {
+ // set rect to be big enough to fill the space, but not super-huge, so we
+ // don't overflow fixed-point implementations
+ SkRect r;
+ r.setLTRB(0, 0,
+ SkIntToScalar(getRenderTarget()->width()),
+ SkIntToScalar(getRenderTarget()->height()));
+ SkMatrix inverse;
+ SkTCopyOnFirstWrite<GrPaint> paint(origPaint);
+ AutoMatrix am;
+
+ // We attempt to map r by the inverse matrix and draw that. mapRect will
+ // map the four corners and bound them with a new rect. This will not
+ // produce a correct result for some perspective matrices.
+ if (!this->getMatrix().hasPerspective()) {
+ if (!fViewMatrix.invert(&inverse)) {
+ GrPrintf("Could not invert matrix\n");
+ return;
+ }
+ inverse.mapRect(&r);
+ } else {
+ if (!am.setIdentity(this, paint.writable())) {
+ GrPrintf("Could not invert matrix\n");
+ return;
+ }
+ }
+ // by definition this fills the entire clip, no need for AA
+ if (paint->isAntiAlias()) {
+ paint.writable()->setAntiAlias(false);
+ }
+ this->drawRect(*paint, r);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+inline bool disable_coverage_aa_for_blend(GrDrawTarget* target) {
+ return DISABLE_COVERAGE_AA_FOR_BLEND && !target->canApplyCoverage();
+}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/* create a triangle strip that strokes the specified triangle. There are 8
+ unique vertices, but we repreat the last 2 to close up. Alternatively we
+ could use an indices array, and then only send 8 verts, but not sure that
+ would be faster.
+ */
+static void setStrokeRectStrip(GrPoint verts[10], SkRect rect,
+ SkScalar width) {
+ const SkScalar rad = SkScalarHalf(width);
+ rect.sort();
+
+ verts[0].set(rect.fLeft + rad, rect.fTop + rad);
+ verts[1].set(rect.fLeft - rad, rect.fTop - rad);
+ verts[2].set(rect.fRight - rad, rect.fTop + rad);
+ verts[3].set(rect.fRight + rad, rect.fTop - rad);
+ verts[4].set(rect.fRight - rad, rect.fBottom - rad);
+ verts[5].set(rect.fRight + rad, rect.fBottom + rad);
+ verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
+ verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
+ verts[8] = verts[0];
+ verts[9] = verts[1];
+}
+
+static bool isIRect(const SkRect& r) {
+ return SkScalarIsInt(r.fLeft) && SkScalarIsInt(r.fTop) &&
+ SkScalarIsInt(r.fRight) && SkScalarIsInt(r.fBottom);
+}
+
+static bool apply_aa_to_rect(GrDrawTarget* target,
+ const SkRect& rect,
+ SkScalar strokeWidth,
+ const SkMatrix* matrix,
+ SkMatrix* combinedMatrix,
+ SkRect* devRect,
+ bool* useVertexCoverage) {
+ // we use a simple coverage ramp to do aa on axis-aligned rects
+ // we check if the rect will be axis-aligned, and the rect won't land on
+ // integer coords.
+
+ // we are keeping around the "tweak the alpha" trick because
+ // it is our only hope for the fixed-pipe implementation.
+ // In a shader implementation we can give a separate coverage input
+ // TODO: remove this ugliness when we drop the fixed-pipe impl
+ *useVertexCoverage = false;
+ if (!target->getDrawState().canTweakAlphaForCoverage()) {
+ if (disable_coverage_aa_for_blend(target)) {
+#if GR_DEBUG
+ //GrPrintf("Turning off AA to correctly apply blend.\n");
+#endif
+ return false;
+ } else {
+ *useVertexCoverage = true;
+ }
+ }
+ const GrDrawState& drawState = target->getDrawState();
+ if (drawState.getRenderTarget()->isMultisampled()) {
+ return false;
+ }
+
+ if (0 == strokeWidth && target->willUseHWAALines()) {
+ return false;
+ }
+
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ if (strokeWidth >= 0) {
+#endif
+ if (!drawState.getViewMatrix().preservesAxisAlignment()) {
+ return false;
+ }
+
+ if (NULL != matrix && !matrix->preservesAxisAlignment()) {
+ return false;
+ }
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ } else {
+ if (!drawState.getViewMatrix().preservesAxisAlignment() &&
+ !drawState.getViewMatrix().preservesRightAngles()) {
+ return false;
+ }
+
+ if (NULL != matrix && !matrix->preservesRightAngles()) {
+ return false;
+ }
+ }
+#endif
+
+ *combinedMatrix = drawState.getViewMatrix();
+ if (NULL != matrix) {
+ combinedMatrix->preConcat(*matrix);
+
+#if GR_DEBUG
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ if (strokeWidth >= 0) {
+#endif
+ GrAssert(combinedMatrix->preservesAxisAlignment());
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ } else {
+ GrAssert(combinedMatrix->preservesRightAngles());
+ }
+#endif
+#endif
+ }
+
+ combinedMatrix->mapRect(devRect, rect);
+
+ if (strokeWidth < 0) {
+ return !isIRect(*devRect);
+ } else {
+ return true;
+ }
+}
+
+void GrContext::drawRect(const GrPaint& paint,
+ const SkRect& rect,
+ SkScalar width,
+ const SkMatrix* matrix) {
+ SK_TRACE_EVENT0("GrContext::drawRect");
+
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ SkRect devRect;
+ SkMatrix combinedMatrix;
+ bool useVertexCoverage;
+ bool needAA = paint.isAntiAlias() &&
+ !target->getDrawState().getRenderTarget()->isMultisampled();
+ bool doAA = needAA && apply_aa_to_rect(target, rect, width, matrix,
+ &combinedMatrix, &devRect,
+ &useVertexCoverage);
+ if (doAA) {
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(target->drawState())) {
+ return;
+ }
+ if (width >= 0) {
+ fAARectRenderer->strokeAARect(this->getGpu(), target,
+ rect, combinedMatrix, devRect,
+ width, useVertexCoverage);
+ } else {
+ // filled AA rect
+ fAARectRenderer->fillAARect(this->getGpu(), target,
+ rect, combinedMatrix, devRect,
+ useVertexCoverage);
+ }
+ return;
+ }
+
+ if (width >= 0) {
+ // TODO: consider making static vertex buffers for these cases.
+ // Hairline could be done by just adding closing vertex to
+ // unitSquareVertexBuffer()
+
+ static const int worstCaseVertCount = 10;
+ target->drawState()->setDefaultVertexAttribs();
+ GrDrawTarget::AutoReleaseGeometry geo(target, worstCaseVertCount, 0);
+
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ GrPrimitiveType primType;
+ int vertCount;
+ GrPoint* vertex = geo.positions();
+
+ if (width > 0) {
+ vertCount = 10;
+ primType = kTriangleStrip_GrPrimitiveType;
+ setStrokeRectStrip(vertex, rect, width);
+ } else {
+ // hairline
+ vertCount = 5;
+ primType = kLineStrip_GrPrimitiveType;
+ vertex[0].set(rect.fLeft, rect.fTop);
+ vertex[1].set(rect.fRight, rect.fTop);
+ vertex[2].set(rect.fRight, rect.fBottom);
+ vertex[3].set(rect.fLeft, rect.fBottom);
+ vertex[4].set(rect.fLeft, rect.fTop);
+ }
+
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (NULL != matrix) {
+ GrDrawState* drawState = target->drawState();
+ avmr.set(drawState, *matrix);
+ }
+
+ target->drawNonIndexed(primType, 0, vertCount);
+ } else {
+ // filled BW rect
+ target->drawSimpleRect(rect, matrix);
+ }
+}
+
+void GrContext::drawRectToRect(const GrPaint& paint,
+ const SkRect& dstRect,
+ const SkRect& localRect,
+ const SkMatrix* dstMatrix,
+ const SkMatrix* localMatrix) {
+ SK_TRACE_EVENT0("GrContext::drawRectToRect");
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ target->drawRect(dstRect, dstMatrix, &localRect, localMatrix);
+}
+
+namespace {
+
+extern const GrVertexAttrib gPosUVColorAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding },
+ {kVec4ub_GrVertexAttribType, 2*sizeof(GrPoint), kColor_GrVertexAttribBinding}
+};
+
+extern const GrVertexAttrib gPosColorAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kColor_GrVertexAttribBinding},
+};
+
+static void set_vertex_attributes(GrDrawState* drawState,
+ const GrPoint* texCoords,
+ const GrColor* colors,
+ int* colorOffset,
+ int* texOffset) {
+ *texOffset = -1;
+ *colorOffset = -1;
+
+ if (NULL != texCoords && NULL != colors) {
+ *texOffset = sizeof(GrPoint);
+ *colorOffset = 2*sizeof(GrPoint);
+ drawState->setVertexAttribs<gPosUVColorAttribs>(3);
+ } else if (NULL != texCoords) {
+ *texOffset = sizeof(GrPoint);
+ drawState->setVertexAttribs<gPosUVColorAttribs>(2);
+ } else if (NULL != colors) {
+ *colorOffset = sizeof(GrPoint);
+ drawState->setVertexAttribs<gPosColorAttribs>(2);
+ } else {
+ drawState->setVertexAttribs<gPosColorAttribs>(1);
+ }
+}
+
+};
+
+void GrContext::drawVertices(const GrPaint& paint,
+ GrPrimitiveType primitiveType,
+ int vertexCount,
+ const GrPoint positions[],
+ const GrPoint texCoords[],
+ const GrColor colors[],
+ const uint16_t indices[],
+ int indexCount) {
+ SK_TRACE_EVENT0("GrContext::drawVertices");
+
+ GrDrawTarget::AutoReleaseGeometry geo;
+
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ GrDrawState* drawState = target->drawState();
+
+ int colorOffset = -1, texOffset = -1;
+ set_vertex_attributes(drawState, texCoords, colors, &colorOffset, &texOffset);
+
+ size_t vertexSize = drawState->getVertexSize();
+ if (sizeof(GrPoint) != vertexSize) {
+ if (!geo.set(target, vertexCount, 0)) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+ void* curVertex = geo.vertices();
+
+ for (int i = 0; i < vertexCount; ++i) {
+ *((GrPoint*)curVertex) = positions[i];
+
+ if (texOffset >= 0) {
+ *(GrPoint*)((intptr_t)curVertex + texOffset) = texCoords[i];
+ }
+ if (colorOffset >= 0) {
+ *(GrColor*)((intptr_t)curVertex + colorOffset) = colors[i];
+ }
+ curVertex = (void*)((intptr_t)curVertex + vertexSize);
+ }
+ } else {
+ target->setVertexSourceToArray(positions, vertexCount);
+ }
+
+ // we don't currently apply offscreen AA to this path. Need improved
+ // management of GrDrawTarget's geometry to avoid copying points per-tile.
+
+ if (NULL != indices) {
+ target->setIndexSourceToArray(indices, indexCount);
+ target->drawIndexed(primitiveType, 0, 0, vertexCount, indexCount);
+ target->resetIndexSource();
+ } else {
+ target->drawNonIndexed(primitiveType, 0, vertexCount);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrContext::drawRRect(const GrPaint& paint,
+ const SkRRect& rect,
+ const SkStrokeRec& stroke) {
+ if (rect.isEmpty()) {
+ return;
+ }
+
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ bool useAA = paint.isAntiAlias() &&
+ !target->getDrawState().getRenderTarget()->isMultisampled() &&
+ !disable_coverage_aa_for_blend(target);
+
+ if (!fOvalRenderer->drawSimpleRRect(target, this, useAA, rect, stroke)) {
+ SkPath path;
+ path.addRRect(rect);
+ this->internalDrawPath(target, useAA, path, stroke);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrContext::drawOval(const GrPaint& paint,
+ const SkRect& oval,
+ const SkStrokeRec& stroke) {
+ if (oval.isEmpty()) {
+ return;
+ }
+
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ bool useAA = paint.isAntiAlias() &&
+ !target->getDrawState().getRenderTarget()->isMultisampled() &&
+ !disable_coverage_aa_for_blend(target);
+
+ if (!fOvalRenderer->drawOval(target, this, useAA, oval, stroke)) {
+ SkPath path;
+ path.addOval(oval);
+ this->internalDrawPath(target, useAA, path, stroke);
+ }
+}
+
+// Can 'path' be drawn as a pair of filled nested rectangles?
+static bool is_nested_rects(GrDrawTarget* target,
+ const SkPath& path,
+ const SkStrokeRec& stroke,
+ SkRect rects[2],
+ bool* useVertexCoverage) {
+ SkASSERT(stroke.isFillStyle());
+
+ if (path.isInverseFillType()) {
+ return false;
+ }
+
+ const GrDrawState& drawState = target->getDrawState();
+
+ // TODO: this restriction could be lifted if we were willing to apply
+ // the matrix to all the points individually rather than just to the rect
+ if (!drawState.getViewMatrix().preservesAxisAlignment()) {
+ return false;
+ }
+
+ *useVertexCoverage = false;
+ if (!target->getDrawState().canTweakAlphaForCoverage()) {
+ if (disable_coverage_aa_for_blend(target)) {
+ return false;
+ } else {
+ *useVertexCoverage = true;
+ }
+ }
+
+ SkPath::Direction dirs[2];
+ if (!path.isNestedRects(rects, dirs)) {
+ return false;
+ }
+
+ if (SkPath::kWinding_FillType == path.getFillType() && dirs[0] == dirs[1]) {
+ // The two rects need to be wound opposite to each other
+ return false;
+ }
+
+ // Right now, nested rects where the margin is not the same width
+ // all around do not render correctly
+ const SkScalar* outer = rects[0].asScalars();
+ const SkScalar* inner = rects[1].asScalars();
+
+ SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
+ for (int i = 1; i < 4; ++i) {
+ SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
+ if (!SkScalarNearlyEqual(margin, temp)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void GrContext::drawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) {
+
+ if (path.isEmpty()) {
+ if (path.isInverseFillType()) {
+ this->drawPaint(paint);
+ }
+ return;
+ }
+
+ // Note that internalDrawPath may sw-rasterize the path into a scratch texture.
+ // Scratch textures can be recycled after they are returned to the texture
+ // cache. This presents a potential hazard for buffered drawing. However,
+ // the writePixels that uploads to the scratch will perform a flush so we're
+ // OK.
+ AutoRestoreEffects are;
+ GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
+
+ bool useAA = paint.isAntiAlias() && !target->getDrawState().getRenderTarget()->isMultisampled();
+ if (useAA && stroke.getWidth() < 0 && !path.isConvex()) {
+ // Concave AA paths are expensive - try to avoid them for special cases
+ bool useVertexCoverage;
+ SkRect rects[2];
+
+ if (is_nested_rects(target, path, stroke, rects, &useVertexCoverage)) {
+ SkMatrix origViewMatrix = target->getDrawState().getViewMatrix();
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(target->drawState())) {
+ return;
+ }
+
+ fAARectRenderer->fillAANestedRects(this->getGpu(), target,
+ rects,
+ origViewMatrix,
+ useVertexCoverage);
+ return;
+ }
+ }
+
+ SkRect ovalRect;
+ bool isOval = path.isOval(&ovalRect);
+
+ if (!isOval || path.isInverseFillType()
+ || !fOvalRenderer->drawOval(target, this, useAA, ovalRect, stroke)) {
+ this->internalDrawPath(target, useAA, path, stroke);
+ }
+}
+
+void GrContext::internalDrawPath(GrDrawTarget* target, bool useAA, const SkPath& path,
+ const SkStrokeRec& stroke) {
+ SkASSERT(!path.isEmpty());
+
+ // An Assumption here is that path renderer would use some form of tweaking
+ // the src color (either the input alpha or in the frag shader) to implement
+ // aa. If we have some future driver-mojo path AA that can do the right
+ // thing WRT to the blend then we'll need some query on the PR.
+ if (disable_coverage_aa_for_blend(target)) {
+#if GR_DEBUG
+ //GrPrintf("Turning off AA to correctly apply blend.\n");
+#endif
+ useAA = false;
+ }
+
+ GrPathRendererChain::DrawType type = useAA ? GrPathRendererChain::kColorAntiAlias_DrawType :
+ GrPathRendererChain::kColor_DrawType;
+
+ const SkPath* pathPtr = &path;
+ SkPath tmpPath;
+ SkStrokeRec strokeRec(stroke);
+
+ // Try a 1st time without stroking the path and without allowing the SW renderer
+ GrPathRenderer* pr = this->getPathRenderer(*pathPtr, strokeRec, target, false, type);
+
+ if (NULL == pr) {
+ if (!strokeRec.isHairlineStyle()) {
+ // It didn't work the 1st time, so try again with the stroked path
+ if (strokeRec.applyToPath(&tmpPath, *pathPtr)) {
+ pathPtr = &tmpPath;
+ strokeRec.setFillStyle();
+ }
+ }
+ if (pathPtr->isEmpty()) {
+ return;
+ }
+
+ // This time, allow SW renderer
+ pr = this->getPathRenderer(*pathPtr, strokeRec, target, true, type);
+ }
+
+ if (NULL == pr) {
+#if GR_DEBUG
+ GrPrintf("Unable to find path renderer compatible with path.\n");
+#endif
+ return;
+ }
+
+ pr->drawPath(*pathPtr, strokeRec, target, useAA);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrContext::flush(int flagsBitfield) {
+ if (NULL == fDrawBuffer) {
+ return;
+ }
+
+ if (kDiscard_FlushBit & flagsBitfield) {
+ fDrawBuffer->reset();
+ } else {
+ fDrawBuffer->flush();
+ }
+}
+
+bool GrContext::writeTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer, size_t rowBytes,
+ uint32_t flags) {
+ SK_TRACE_EVENT0("GrContext::writeTexturePixels");
+ ASSERT_OWNED_RESOURCE(texture);
+
+ if ((kUnpremul_PixelOpsFlag & flags) || !fGpu->canWriteTexturePixels(texture, config)) {
+ if (NULL != texture->asRenderTarget()) {
+ return this->writeRenderTargetPixels(texture->asRenderTarget(),
+ left, top, width, height,
+ config, buffer, rowBytes, flags);
+ } else {
+ return false;
+ }
+ }
+
+ if (!(kDontFlush_PixelOpsFlag & flags)) {
+ this->flush();
+ }
+
+ return fGpu->writeTexturePixels(texture, left, top, width, height,
+ config, buffer, rowBytes);
+}
+
+bool GrContext::readTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, void* buffer, size_t rowBytes,
+ uint32_t flags) {
+ SK_TRACE_EVENT0("GrContext::readTexturePixels");
+ ASSERT_OWNED_RESOURCE(texture);
+
+ // TODO: code read pixels for textures that aren't also rendertargets
+ GrRenderTarget* target = texture->asRenderTarget();
+ if (NULL != target) {
+ return this->readRenderTargetPixels(target,
+ left, top, width, height,
+ config, buffer, rowBytes,
+ flags);
+ } else {
+ return false;
+ }
+}
+
+#include "SkConfig8888.h"
+
+namespace {
+/**
+ * Converts a GrPixelConfig to a SkCanvas::Config8888. Only byte-per-channel
+ * formats are representable as Config8888 and so the function returns false
+ * if the GrPixelConfig has no equivalent Config8888.
+ */
+bool grconfig_to_config8888(GrPixelConfig config,
+ bool unpremul,
+ SkCanvas::Config8888* config8888) {
+ switch (config) {
+ case kRGBA_8888_GrPixelConfig:
+ if (unpremul) {
+ *config8888 = SkCanvas::kRGBA_Unpremul_Config8888;
+ } else {
+ *config8888 = SkCanvas::kRGBA_Premul_Config8888;
+ }
+ return true;
+ case kBGRA_8888_GrPixelConfig:
+ if (unpremul) {
+ *config8888 = SkCanvas::kBGRA_Unpremul_Config8888;
+ } else {
+ *config8888 = SkCanvas::kBGRA_Premul_Config8888;
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+
+// It returns a configuration with where the byte position of the R & B components are swapped in
+// relation to the input config. This should only be called with the result of
+// grconfig_to_config8888 as it will fail for other configs.
+SkCanvas::Config8888 swap_config8888_red_and_blue(SkCanvas::Config8888 config8888) {
+ switch (config8888) {
+ case SkCanvas::kBGRA_Premul_Config8888:
+ return SkCanvas::kRGBA_Premul_Config8888;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ return SkCanvas::kRGBA_Unpremul_Config8888;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ return SkCanvas::kBGRA_Premul_Config8888;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ return SkCanvas::kBGRA_Unpremul_Config8888;
+ default:
+ GrCrash("Unexpected input");
+ return SkCanvas::kBGRA_Unpremul_Config8888;;
+ }
+}
+}
+
+bool GrContext::readRenderTargetPixels(GrRenderTarget* target,
+ int left, int top, int width, int height,
+ GrPixelConfig dstConfig, void* buffer, size_t rowBytes,
+ uint32_t flags) {
+ SK_TRACE_EVENT0("GrContext::readRenderTargetPixels");
+ ASSERT_OWNED_RESOURCE(target);
+
+ if (NULL == target) {
+ target = fRenderTarget.get();
+ if (NULL == target) {
+ return false;
+ }
+ }
+
+ if (!(kDontFlush_PixelOpsFlag & flags)) {
+ this->flush();
+ }
+
+ // Determine which conversions have to be applied: flipY, swapRAnd, and/or unpremul.
+
+ // If fGpu->readPixels would incur a y-flip cost then we will read the pixels upside down. We'll
+ // either do the flipY by drawing into a scratch with a matrix or on the cpu after the read.
+ bool flipY = fGpu->readPixelsWillPayForYFlip(target, left, top,
+ width, height, dstConfig,
+ rowBytes);
+ // We ignore the preferred config if it is different than our config unless it is an R/B swap.
+ // In that case we'll perform an R and B swap while drawing to a scratch texture of the swapped
+ // config. Then we will call readPixels on the scratch with the swapped config. The swaps during
+ // the draw cancels out the fact that we call readPixels with a config that is R/B swapped from
+ // dstConfig.
+ GrPixelConfig readConfig = dstConfig;
+ bool swapRAndB = false;
+ if (GrPixelConfigSwapRAndB(dstConfig) ==
+ fGpu->preferredReadPixelsConfig(dstConfig, target->config())) {
+ readConfig = GrPixelConfigSwapRAndB(readConfig);
+ swapRAndB = true;
+ }
+
+ bool unpremul = SkToBool(kUnpremul_PixelOpsFlag & flags);
+
+ if (unpremul && !GrPixelConfigIs8888(dstConfig)) {
+ // The unpremul flag is only allowed for these two configs.
+ return false;
+ }
+
+ // If the src is a texture and we would have to do conversions after read pixels, we instead
+ // do the conversions by drawing the src to a scratch texture. If we handle any of the
+ // conversions in the draw we set the corresponding bool to false so that we don't reapply it
+ // on the read back pixels.
+ GrTexture* src = target->asTexture();
+ GrAutoScratchTexture ast;
+ if (NULL != src && (swapRAndB || unpremul || flipY)) {
+ // Make the scratch a render target because we don't have a robust readTexturePixels as of
+ // yet. It calls this function.
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fConfig = readConfig;
+ desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+
+ // When a full read back is faster than a partial we could always make the scratch exactly
+ // match the passed rect. However, if we see many different size rectangles we will trash
+ // our texture cache and pay the cost of creating and destroying many textures. So, we only
+ // request an exact match when the caller is reading an entire RT.
+ ScratchTexMatch match = kApprox_ScratchTexMatch;
+ if (0 == left &&
+ 0 == top &&
+ target->width() == width &&
+ target->height() == height &&
+ fGpu->fullReadPixelsIsFasterThanPartial()) {
+ match = kExact_ScratchTexMatch;
+ }
+ ast.set(this, desc, match);
+ GrTexture* texture = ast.texture();
+ if (texture) {
+ // compute a matrix to perform the draw
+ SkMatrix textureMatrix;
+ textureMatrix.setTranslate(SK_Scalar1 *left, SK_Scalar1 *top);
+ textureMatrix.postIDiv(src->width(), src->height());
+
+ SkAutoTUnref<const GrEffectRef> effect;
+ if (unpremul) {
+ effect.reset(this->createPMToUPMEffect(src, swapRAndB, textureMatrix));
+ if (NULL != effect) {
+ unpremul = false; // we no longer need to do this on CPU after the read back.
+ }
+ }
+ // 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 != effect || flipY || swapRAndB) {
+ if (!effect) {
+ effect.reset(GrConfigConversionEffect::Create(
+ src,
+ swapRAndB,
+ GrConfigConversionEffect::kNone_PMConversion,
+ textureMatrix));
+ }
+ swapRAndB = false; // we will handle the swap in the draw.
+
+ // We protect the existing geometry here since it may not be
+ // clear to the caller that a draw operation (i.e., drawSimpleRect)
+ // can be invoked in this method
+ GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit);
+ GrDrawState* drawState = fGpu->drawState();
+ GrAssert(effect);
+ drawState->addColorEffect(effect);
+
+ drawState->setRenderTarget(texture->asRenderTarget());
+ SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
+ fGpu->drawSimpleRect(rect, NULL);
+ // we want to read back from the scratch's origin
+ left = 0;
+ top = 0;
+ target = texture->asRenderTarget();
+ }
+ }
+ }
+ if (!fGpu->readPixels(target,
+ left, top, width, height,
+ readConfig, buffer, rowBytes)) {
+ return false;
+ }
+ // Perform any conversions we weren't able to perform using a scratch texture.
+ if (unpremul || swapRAndB) {
+ // These are initialized to suppress a warning
+ SkCanvas::Config8888 srcC8888 = SkCanvas::kNative_Premul_Config8888;
+ SkCanvas::Config8888 dstC8888 = SkCanvas::kNative_Premul_Config8888;
+
+ SkDEBUGCODE(bool c8888IsValid =) grconfig_to_config8888(dstConfig, false, &srcC8888);
+ grconfig_to_config8888(dstConfig, unpremul, &dstC8888);
+
+ if (swapRAndB) {
+ GrAssert(c8888IsValid); // we should only do r/b swap on 8888 configs
+ srcC8888 = swap_config8888_red_and_blue(srcC8888);
+ }
+ GrAssert(c8888IsValid);
+ uint32_t* b32 = reinterpret_cast<uint32_t*>(buffer);
+ SkConvertConfig8888Pixels(b32, rowBytes, dstC8888,
+ b32, rowBytes, srcC8888,
+ width, height);
+ }
+ return true;
+}
+
+void GrContext::resolveRenderTarget(GrRenderTarget* target) {
+ GrAssert(target);
+ ASSERT_OWNED_RESOURCE(target);
+ // In the future we may track whether there are any pending draws to this
+ // target. We don't today so we always perform a flush. We don't promise
+ // this to our clients, though.
+ this->flush();
+ fGpu->resolveRenderTarget(target);
+}
+
+void GrContext::copyTexture(GrTexture* src, GrRenderTarget* dst, const SkIPoint* topLeft) {
+ if (NULL == src || NULL == dst) {
+ return;
+ }
+ ASSERT_OWNED_RESOURCE(src);
+
+ // Writes pending to the source texture are not tracked, so a flush
+ // is required to ensure that the copy captures the most recent contents
+ // of the source texture. See similar behavior in
+ // GrContext::resolveRenderTarget.
+ this->flush();
+
+ GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit);
+ GrDrawState* drawState = fGpu->drawState();
+ drawState->setRenderTarget(dst);
+ 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->addColorTextureEffect(src, sampleM);
+ SkRect dstR = SkRect::MakeWH(SkIntToScalar(srcRect.width()), SkIntToScalar(srcRect.height()));
+ fGpu->drawSimpleRect(dstR, NULL);
+}
+
+bool GrContext::writeRenderTargetPixels(GrRenderTarget* target,
+ int left, int top, int width, int height,
+ GrPixelConfig srcConfig,
+ const void* buffer,
+ size_t rowBytes,
+ uint32_t flags) {
+ SK_TRACE_EVENT0("GrContext::writeRenderTargetPixels");
+ ASSERT_OWNED_RESOURCE(target);
+
+ if (NULL == target) {
+ target = fRenderTarget.get();
+ if (NULL == target) {
+ return false;
+ }
+ }
+
+ // TODO: when underlying api has a direct way to do this we should use it (e.g. glDrawPixels on
+ // desktop GL).
+
+ // We will always call some form of writeTexturePixels and we will pass our flags on to it.
+ // Thus, we don't perform a flush here since that call will do it (if the kNoFlush flag isn't
+ // set.)
+
+ // If the RT is also a texture and we don't have to premultiply then take the texture path.
+ // We expect to be at least as fast or faster since it doesn't use an intermediate texture as
+ // we do below.
+
+#if !GR_MAC_BUILD
+ // At least some drivers on the Mac get confused when glTexImage2D is called on a texture
+ // attached to an FBO. The FBO still sees the old image. TODO: determine what OS versions and/or
+ // HW is affected.
+ if (NULL != target->asTexture() && !(kUnpremul_PixelOpsFlag & flags) &&
+ fGpu->canWriteTexturePixels(target->asTexture(), srcConfig)) {
+ return this->writeTexturePixels(target->asTexture(),
+ left, top, width, height,
+ srcConfig, buffer, rowBytes, flags);
+ }
+#endif
+
+ // We ignore the preferred config unless it is a R/B swap of the src config. In that case
+ // we will upload the original src data to a scratch texture but we will spoof it as the swapped
+ // config. This scratch will then have R and B swapped. We correct for this by swapping again
+ // when drawing the scratch to the dst using a conversion effect.
+ bool swapRAndB = false;
+ GrPixelConfig writeConfig = srcConfig;
+ if (GrPixelConfigSwapRAndB(srcConfig) ==
+ fGpu->preferredWritePixelsConfig(srcConfig, target->config())) {
+ writeConfig = GrPixelConfigSwapRAndB(srcConfig);
+ swapRAndB = true;
+ }
+
+ GrTextureDesc desc;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fConfig = writeConfig;
+ GrAutoScratchTexture ast(this, desc);
+ GrTexture* texture = ast.texture();
+ if (NULL == texture) {
+ return false;
+ }
+
+ SkAutoTUnref<const GrEffectRef> effect;
+ 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);
+
+ if (kUnpremul_PixelOpsFlag & flags) {
+ if (!GrPixelConfigIs8888(srcConfig)) {
+ return false;
+ }
+ effect.reset(this->createUPMToPMEffect(texture, swapRAndB, textureMatrix));
+ // handle the unpremul step on the CPU if we couldn't create an effect to do it.
+ if (NULL == effect) {
+ SkCanvas::Config8888 srcConfig8888, dstConfig8888;
+ GR_DEBUGCODE(bool success = )
+ grconfig_to_config8888(srcConfig, true, &srcConfig8888);
+ GrAssert(success);
+ GR_DEBUGCODE(success = )
+ grconfig_to_config8888(srcConfig, false, &dstConfig8888);
+ GrAssert(success);
+ const uint32_t* src = reinterpret_cast<const uint32_t*>(buffer);
+ tmpPixels.reset(width * height);
+ SkConvertConfig8888Pixels(tmpPixels.get(), 4 * width, dstConfig8888,
+ src, rowBytes, srcConfig8888,
+ width, height);
+ buffer = tmpPixels.get();
+ rowBytes = 4 * width;
+ }
+ }
+ if (NULL == effect) {
+ effect.reset(GrConfigConversionEffect::Create(texture,
+ swapRAndB,
+ GrConfigConversionEffect::kNone_PMConversion,
+ textureMatrix));
+ }
+
+ if (!this->writeTexturePixels(texture,
+ 0, 0, width, height,
+ writeConfig, buffer, rowBytes,
+ flags & ~kUnpremul_PixelOpsFlag)) {
+ return false;
+ }
+
+ // writeRenderTargetPixels can be called in the midst of drawing another
+ // object (e.g., when uploading a SW path rendering to the gpu while
+ // drawing a rect) so preserve the current geometry.
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top));
+ GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit, &matrix);
+ GrDrawState* drawState = fGpu->drawState();
+ GrAssert(effect);
+ drawState->addColorEffect(effect);
+
+ drawState->setRenderTarget(target);
+
+ fGpu->drawSimpleRect(SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)), NULL);
+ return true;
+}
+////////////////////////////////////////////////////////////////////////////////
+
+GrDrawTarget* GrContext::prepareToDraw(const GrPaint* paint,
+ BufferedDraw buffered,
+ AutoRestoreEffects* are) {
+ // All users of this draw state should be freeing up all effects when they're done.
+ // Otherwise effects that own resources may keep those resources alive indefinitely.
+ GrAssert(0 == fDrawState->numColorStages() && 0 == fDrawState->numCoverageStages());
+
+ if (kNo_BufferedDraw == buffered && kYes_BufferedDraw == fLastDrawWasBuffered) {
+ fDrawBuffer->flush();
+ fLastDrawWasBuffered = kNo_BufferedDraw;
+ }
+ ASSERT_OWNED_RESOURCE(fRenderTarget.get());
+ if (NULL != paint) {
+ GrAssert(NULL != are);
+ are->set(fDrawState);
+ fDrawState->setFromPaint(*paint, fViewMatrix, fRenderTarget.get());
+#if GR_DEBUG_PARTIAL_COVERAGE_CHECK
+ if ((paint->hasMask() || 0xff != paint->fCoverage) &&
+ !fGpu->canApplyCoverage()) {
+ GrPrintf("Partial pixel coverage will be incorrectly blended.\n");
+ }
+#endif
+ } else {
+ fDrawState->reset(fViewMatrix);
+ fDrawState->setRenderTarget(fRenderTarget.get());
+ }
+ GrDrawTarget* target;
+ if (kYes_BufferedDraw == buffered) {
+ fLastDrawWasBuffered = kYes_BufferedDraw;
+ target = fDrawBuffer;
+ } else {
+ GrAssert(kNo_BufferedDraw == buffered);
+ fLastDrawWasBuffered = kNo_BufferedDraw;
+ target = fGpu;
+ }
+ fDrawState->setState(GrDrawState::kClip_StateBit, NULL != fClip &&
+ !fClip->fClipStack->isWideOpen());
+ target->setClip(fClip);
+ GrAssert(fDrawState == target->drawState());
+ return target;
+}
+
+/*
+ * This method finds a path renderer that can draw the specified path on
+ * the provided target.
+ * Due to its expense, the software path renderer has split out so it can
+ * can be individually allowed/disallowed via the "allowSW" boolean.
+ */
+GrPathRenderer* GrContext::getPathRenderer(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool allowSW,
+ GrPathRendererChain::DrawType drawType,
+ GrPathRendererChain::StencilSupport* stencilSupport) {
+
+ if (NULL == fPathRendererChain) {
+ fPathRendererChain = SkNEW_ARGS(GrPathRendererChain, (this));
+ }
+
+ GrPathRenderer* pr = fPathRendererChain->getPathRenderer(path,
+ stroke,
+ target,
+ drawType,
+ stencilSupport);
+
+ if (NULL == pr && allowSW) {
+ if (NULL == fSoftwarePathRenderer) {
+ fSoftwarePathRenderer = SkNEW_ARGS(GrSoftwarePathRenderer, (this));
+ }
+ pr = fSoftwarePathRenderer;
+ }
+
+ return pr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrContext::isConfigRenderable(GrPixelConfig config) const {
+ return fGpu->isConfigRenderable(config);
+}
+
+static inline intptr_t setOrClear(intptr_t bits, int shift, intptr_t pred) {
+ intptr_t mask = 1 << shift;
+ if (pred) {
+ bits |= mask;
+ } else {
+ bits &= ~mask;
+ }
+ return bits;
+}
+
+void GrContext::setupDrawBuffer() {
+
+ GrAssert(NULL == fDrawBuffer);
+ GrAssert(NULL == fDrawBufferVBAllocPool);
+ GrAssert(NULL == fDrawBufferIBAllocPool);
+
+ fDrawBufferVBAllocPool =
+ SkNEW_ARGS(GrVertexBufferAllocPool, (fGpu, false,
+ DRAW_BUFFER_VBPOOL_BUFFER_SIZE,
+ DRAW_BUFFER_VBPOOL_PREALLOC_BUFFERS));
+ fDrawBufferIBAllocPool =
+ SkNEW_ARGS(GrIndexBufferAllocPool, (fGpu, false,
+ DRAW_BUFFER_IBPOOL_BUFFER_SIZE,
+ DRAW_BUFFER_IBPOOL_PREALLOC_BUFFERS));
+
+ fDrawBuffer = SkNEW_ARGS(GrInOrderDrawBuffer, (fGpu,
+ fDrawBufferVBAllocPool,
+ fDrawBufferIBAllocPool));
+
+ fDrawBuffer->setDrawState(fDrawState);
+}
+
+GrDrawTarget* GrContext::getTextTarget() {
+ return this->prepareToDraw(NULL, BUFFERED_DRAW, NULL);
+}
+
+const GrIndexBuffer* GrContext::getQuadIndexBuffer() const {
+ return fGpu->getQuadIndexBuffer();
+}
+
+namespace {
+void test_pm_conversions(GrContext* ctx, int* pmToUPMValue, int* upmToPMValue) {
+ GrConfigConversionEffect::PMConversion pmToUPM;
+ GrConfigConversionEffect::PMConversion upmToPM;
+ GrConfigConversionEffect::TestForPreservingPMConversions(ctx, &pmToUPM, &upmToPM);
+ *pmToUPMValue = pmToUPM;
+ *upmToPMValue = upmToPM;
+}
+}
+
+const GrEffectRef* GrContext::createPMToUPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix) {
+ if (!fDidTestPMConversions) {
+ test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
+ fDidTestPMConversions = true;
+ }
+ GrConfigConversionEffect::PMConversion pmToUPM =
+ static_cast<GrConfigConversionEffect::PMConversion>(fPMToUPMConversion);
+ if (GrConfigConversionEffect::kNone_PMConversion != pmToUPM) {
+ return GrConfigConversionEffect::Create(texture, swapRAndB, pmToUPM, matrix);
+ } else {
+ return NULL;
+ }
+}
+
+const GrEffectRef* GrContext::createUPMToPMEffect(GrTexture* texture,
+ bool swapRAndB,
+ const SkMatrix& matrix) {
+ if (!fDidTestPMConversions) {
+ test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion);
+ fDidTestPMConversions = true;
+ }
+ GrConfigConversionEffect::PMConversion upmToPM =
+ static_cast<GrConfigConversionEffect::PMConversion>(fUPMToPMConversion);
+ if (GrConfigConversionEffect::kNone_PMConversion != upmToPM) {
+ return GrConfigConversionEffect::Create(texture, swapRAndB, upmToPM, matrix);
+ } else {
+ return NULL;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#if GR_CACHE_STATS
+void GrContext::printCacheStats() const {
+ fTextureCache->printStats();
+}
+#endif
diff --git a/gpu/GrDefaultPathRenderer.cpp b/gpu/GrDefaultPathRenderer.cpp
new file mode 100644
index 00000000..6d16fb63
--- /dev/null
+++ b/gpu/GrDefaultPathRenderer.cpp
@@ -0,0 +1,521 @@
+/*
+ * 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 "GrDefaultPathRenderer.h"
+
+#include "GrContext.h"
+#include "GrDrawState.h"
+#include "GrPathUtils.h"
+#include "SkString.h"
+#include "SkStrokeRec.h"
+#include "SkTrace.h"
+
+
+GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
+ bool stencilWrapOpsSupport)
+ : fSeparateStencil(separateStencilSupport)
+ , fStencilWrapOps(stencilWrapOpsSupport) {
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Stencil rules for paths
+
+////// Even/Odd
+
+GR_STATIC_CONST_SAME_STENCIL(gEOStencilPass,
+ kInvert_StencilOp,
+ kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+
+// ok not to check clip b/c stencil pass only wrote inside clip
+GR_STATIC_CONST_SAME_STENCIL(gEOColorPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kNotEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+// have to check clip b/c outside clip will always be zero.
+GR_STATIC_CONST_SAME_STENCIL(gInvEOColorPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kEqualIfInClip_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+////// Winding
+
+// when we have separate stencil we increment front faces / decrement back faces
+// when we don't have wrap incr and decr we use the stencil test to simulate
+// them.
+
+GR_STATIC_CONST_STENCIL(gWindStencilSeparateWithWrap,
+ kIncWrap_StencilOp, kDecWrap_StencilOp,
+ kKeep_StencilOp, kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffff, 0xffff,
+ 0xffff, 0xffff,
+ 0xffff, 0xffff);
+
+// if inc'ing the max value, invert to make 0
+// if dec'ing zero invert to make all ones.
+// we can't avoid touching the stencil on both passing and
+// failing, so we can't resctrict ourselves to the clip.
+GR_STATIC_CONST_STENCIL(gWindStencilSeparateNoWrap,
+ kInvert_StencilOp, kInvert_StencilOp,
+ kIncClamp_StencilOp, kDecClamp_StencilOp,
+ kEqual_StencilFunc, kEqual_StencilFunc,
+ 0xffff, 0xffff,
+ 0xffff, 0x0000,
+ 0xffff, 0xffff);
+
+// When there are no separate faces we do two passes to setup the winding rule
+// stencil. First we draw the front faces and inc, then we draw the back faces
+// and dec. These are same as the above two split into the incrementing and
+// decrementing passes.
+GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapInc,
+ kIncWrap_StencilOp,
+ kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapDec,
+ kDecWrap_StencilOp,
+ kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapInc,
+ kInvert_StencilOp,
+ kIncClamp_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0xffff,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapDec,
+ kInvert_StencilOp,
+ kDecClamp_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+// Color passes are the same whether we use the two-sided stencil or two passes
+
+GR_STATIC_CONST_SAME_STENCIL(gWindColorPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kNonZeroIfInClip_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvWindColorPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kEqualIfInClip_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+////// Normal render to stencil
+
+// Sometimes the default path renderer can draw a path directly to the stencil
+// buffer without having to first resolve the interior / exterior.
+GR_STATIC_CONST_SAME_STENCIL(gDirectToStencil,
+ kZero_StencilOp,
+ kIncClamp_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+
+////////////////////////////////////////////////////////////////////////////////
+// Helpers for drawPath
+
+#define STENCIL_OFF 0 // Always disable stencil (even when needed)
+
+static inline bool single_pass_path(const SkPath& path, const SkStrokeRec& stroke) {
+#if STENCIL_OFF
+ return true;
+#else
+ if (!stroke.isHairlineStyle() && !path.isInverseFillType()) {
+ return path.isConvex();
+ }
+ return false;
+#endif
+}
+
+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(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 (!hairLine) {
+ *((*indices)++) = fanCenterIdx;
+ }
+ *((*indices)++) = edgeV0Idx;
+ *((*indices)++) = edgeV0Idx + 1;
+}
+
+bool GrDefaultPathRenderer::createGeom(const SkPath& path,
+ const SkStrokeRec& stroke,
+ SkScalar srcSpaceTol,
+ GrDrawTarget* target,
+ GrPrimitiveType* primType,
+ int* vertexCnt,
+ int* indexCnt,
+ GrDrawTarget::AutoReleaseGeometry* arg) {
+ {
+ SK_TRACE_EVENT0("GrDefaultPathRenderer::createGeom");
+
+ SkScalar srcSpaceTolSqd = SkScalarMul(srcSpaceTol, srcSpaceTol);
+ int contourCnt;
+ int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt,
+ srcSpaceTol);
+
+ if (maxPts <= 0) {
+ return false;
+ }
+ if (maxPts > ((int)SK_MaxU16 + 1)) {
+ GrPrintf("Path not rendered, too many verts (%d)\n", maxPts);
+ return false;
+ }
+
+ bool indexed = contourCnt > 1;
+
+ const bool isHairline = stroke.isHairlineStyle();
+
+ int maxIdxs = 0;
+ if (isHairline) {
+ if (indexed) {
+ maxIdxs = 2 * maxPts;
+ *primType = kLines_GrPrimitiveType;
+ } else {
+ *primType = kLineStrip_GrPrimitiveType;
+ }
+ } else {
+ if (indexed) {
+ maxIdxs = 3 * maxPts;
+ *primType = kTriangles_GrPrimitiveType;
+ } else {
+ *primType = kTriangleFan_GrPrimitiveType;
+ }
+ }
+
+ target->drawState()->setDefaultVertexAttribs();
+ if (!arg->set(target, maxPts, maxIdxs)) {
+ return false;
+ }
+
+ uint16_t* idxBase = reinterpret_cast<uint16_t*>(arg->indices());
+ uint16_t* idx = idxBase;
+ uint16_t subpathIdxStart = 0;
+
+ GrPoint* base = reinterpret_cast<GrPoint*>(arg->vertices());
+ GrAssert(NULL != base);
+ GrPoint* vert = base;
+
+ GrPoint pts[4];
+
+ bool first = true;
+ int subpath = 0;
+
+ SkPath::Iter iter(path, false);
+
+ for (;;) {
+ SkPath::Verb verb = iter.next(pts);
+ switch (verb) {
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kMove_Verb:
+ if (!first) {
+ uint16_t currIdx = (uint16_t) (vert - base);
+ subpathIdxStart = currIdx;
+ ++subpath;
+ }
+ *vert = pts[0];
+ vert++;
+ break;
+ case SkPath::kLine_Verb:
+ if (indexed) {
+ uint16_t prevIdx = (uint16_t)(vert - base) - 1;
+ append_countour_edge_indices(isHairline, subpathIdxStart,
+ prevIdx, &idx);
+ }
+ *(vert++) = pts[1];
+ break;
+ case SkPath::kQuad_Verb: {
+ // first pt of quad is the pt we ended on in previous step
+ uint16_t firstQPtIdx = (uint16_t)(vert - base) - 1;
+ uint16_t numPts = (uint16_t)
+ GrPathUtils::generateQuadraticPoints(
+ pts[0], pts[1], pts[2],
+ srcSpaceTolSqd, &vert,
+ GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
+ if (indexed) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(isHairline, subpathIdxStart,
+ firstQPtIdx + i, &idx);
+ }
+ }
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ // first pt of cubic is the pt we ended on in previous step
+ uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1;
+ uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
+ pts[0], pts[1], pts[2], pts[3],
+ srcSpaceTolSqd, &vert,
+ GrPathUtils::cubicPointCount(pts, srcSpaceTol));
+ if (indexed) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(isHairline, subpathIdxStart,
+ firstCPtIdx + i, &idx);
+ }
+ }
+ break;
+ }
+ case SkPath::kClose_Verb:
+ break;
+ case SkPath::kDone_Verb:
+ // uint16_t currIdx = (uint16_t) (vert - base);
+ goto FINISHED;
+ }
+ first = false;
+ }
+FINISHED:
+ GrAssert((vert - base) <= maxPts);
+ GrAssert((idx - idxBase) <= maxIdxs);
+
+ *vertexCnt = vert - base;
+ *indexCnt = idx - idxBase;
+
+ }
+ return true;
+}
+
+bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool stencilOnly) {
+
+ SkMatrix viewM = target->getDrawState().getViewMatrix();
+ SkScalar tol = SK_Scalar1;
+ tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, path.getBounds());
+
+ int vertexCnt;
+ int indexCnt;
+ GrPrimitiveType primType;
+ GrDrawTarget::AutoReleaseGeometry arg;
+ if (!this->createGeom(path,
+ stroke,
+ tol,
+ target,
+ &primType,
+ &vertexCnt,
+ &indexCnt,
+ &arg)) {
+ return false;
+ }
+
+ GrAssert(NULL != target);
+ GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kPreserve_ASRInit);
+ GrDrawState* drawState = target->drawState();
+ bool colorWritesWereDisabled = drawState->isColorWriteDisabled();
+ // face culling doesn't make sense here
+ GrAssert(GrDrawState::kBoth_DrawFace == drawState->getDrawFace());
+
+ int passCount = 0;
+ const GrStencilSettings* passes[3];
+ GrDrawState::DrawFace drawFace[3];
+ bool reverse = false;
+ bool lastPassIsBounds;
+
+ if (stroke.isHairlineStyle()) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = NULL;
+ }
+ lastPassIsBounds = false;
+ drawFace[0] = GrDrawState::kBoth_DrawFace;
+ } else {
+ if (single_pass_path(path, stroke)) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = NULL;
+ }
+ drawFace[0] = GrDrawState::kBoth_DrawFace;
+ lastPassIsBounds = false;
+ } else {
+ switch (path.getFillType()) {
+ case SkPath::kInverseEvenOdd_FillType:
+ reverse = true;
+ // fallthrough
+ case SkPath::kEvenOdd_FillType:
+ passes[0] = &gEOStencilPass;
+ if (stencilOnly) {
+ passCount = 1;
+ lastPassIsBounds = false;
+ } else {
+ passCount = 2;
+ lastPassIsBounds = true;
+ if (reverse) {
+ passes[1] = &gInvEOColorPass;
+ } else {
+ passes[1] = &gEOColorPass;
+ }
+ }
+ drawFace[0] = drawFace[1] = GrDrawState::kBoth_DrawFace;
+ break;
+
+ case SkPath::kInverseWinding_FillType:
+ reverse = true;
+ // fallthrough
+ case SkPath::kWinding_FillType:
+ if (fSeparateStencil) {
+ if (fStencilWrapOps) {
+ passes[0] = &gWindStencilSeparateWithWrap;
+ } else {
+ passes[0] = &gWindStencilSeparateNoWrap;
+ }
+ passCount = 2;
+ drawFace[0] = GrDrawState::kBoth_DrawFace;
+ } else {
+ if (fStencilWrapOps) {
+ passes[0] = &gWindSingleStencilWithWrapInc;
+ passes[1] = &gWindSingleStencilWithWrapDec;
+ } else {
+ passes[0] = &gWindSingleStencilNoWrapInc;
+ passes[1] = &gWindSingleStencilNoWrapDec;
+ }
+ // which is cw and which is ccw is arbitrary.
+ drawFace[0] = GrDrawState::kCW_DrawFace;
+ drawFace[1] = GrDrawState::kCCW_DrawFace;
+ passCount = 3;
+ }
+ if (stencilOnly) {
+ lastPassIsBounds = false;
+ --passCount;
+ } else {
+ lastPassIsBounds = true;
+ drawFace[passCount-1] = GrDrawState::kBoth_DrawFace;
+ if (reverse) {
+ passes[passCount-1] = &gInvWindColorPass;
+ } else {
+ passes[passCount-1] = &gWindColorPass;
+ }
+ }
+ break;
+ default:
+ GrAssert(!"Unknown path fFill!");
+ return false;
+ }
+ }
+ }
+
+ SkRect devBounds;
+ GetPathDevBounds(path, drawState->getRenderTarget(), viewM, &devBounds);
+
+ for (int p = 0; p < passCount; ++p) {
+ drawState->setDrawFace(drawFace[p]);
+ if (NULL != passes[p]) {
+ *drawState->stencil() = *passes[p];
+ }
+
+ if (lastPassIsBounds && (p == passCount-1)) {
+ if (!colorWritesWereDisabled) {
+ drawState->disableState(GrDrawState::kNoColorWrites_StateBit);
+ }
+ SkRect bounds;
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (reverse) {
+ GrAssert(NULL != drawState->getRenderTarget());
+ // draw over the dev bounds (which will be the whole dst surface for inv fill).
+ bounds = devBounds;
+ SkMatrix vmi;
+ // mapRect through persp matrix may not be correct
+ if (!drawState->getViewMatrix().hasPerspective() &&
+ drawState->getViewInverse(&vmi)) {
+ vmi.mapRect(&bounds);
+ } else {
+ avmr.setIdentity(drawState);
+ }
+ } else {
+ bounds = path.getBounds();
+ }
+ GrDrawTarget::AutoGeometryAndStatePush agasp(target, GrDrawTarget::kPreserve_ASRInit);
+ target->drawSimpleRect(bounds, NULL);
+ } else {
+ if (passCount > 1) {
+ drawState->enableState(GrDrawState::kNoColorWrites_StateBit);
+ }
+ if (indexCnt) {
+ target->drawIndexed(primType, 0, 0,
+ vertexCnt, indexCnt, &devBounds);
+ } else {
+ target->drawNonIndexed(primType, 0, vertexCnt, &devBounds);
+ }
+ }
+ }
+ return true;
+}
+
+bool GrDefaultPathRenderer::canDrawPath(const SkPath& path,
+ 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 (stroke.isFillStyle() || stroke.isHairlineStyle()) && !antiAlias;
+}
+
+bool GrDefaultPathRenderer::onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) {
+ return this->internalDrawPath(path,
+ stroke,
+ target,
+ false);
+}
+
+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/gpu/GrDefaultPathRenderer.h b/gpu/GrDefaultPathRenderer.h
new file mode 100644
index 00000000..e602fae2
--- /dev/null
+++ b/gpu/GrDefaultPathRenderer.h
@@ -0,0 +1,62 @@
+/*
+ * 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 GrDefaultPathRenderer_DEFINED
+#define GrDefaultPathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+#include "SkTemplates.h"
+
+/**
+ * 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);
+
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
+ bool antiAlias) const SK_OVERRIDE;
+
+private:
+
+ 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;
+
+ bool internalDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ GrDrawTarget*,
+ bool stencilOnly);
+
+ bool createGeom(const SkPath&,
+ const SkStrokeRec&,
+ SkScalar srcSpaceTol,
+ GrDrawTarget*,
+ GrPrimitiveType*,
+ int* vertexCnt,
+ int* indexCnt,
+ GrDrawTarget::AutoReleaseGeometry*);
+
+ bool fSeparateStencil;
+ bool fStencilWrapOps;
+
+ typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/gpu/GrDrawState.cpp b/gpu/GrDrawState.cpp
new file mode 100644
index 00000000..0a567573
--- /dev/null
+++ b/gpu/GrDrawState.cpp
@@ -0,0 +1,484 @@
+/*
+ * 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 "GrDrawState.h"
+#include "GrPaint.h"
+
+bool GrDrawState::setIdentityViewMatrix() {
+ if (fColorStages.count() || fCoverageStages.count()) {
+ SkMatrix invVM;
+ if (!fCommon.fViewMatrix.invert(&invVM)) {
+ // sad trombone sound
+ return false;
+ }
+ for (int s = 0; s < fColorStages.count(); ++s) {
+ fColorStages[s].localCoordChange(invVM);
+ }
+ for (int s = 0; s < fCoverageStages.count(); ++s) {
+ fCoverageStages[s].localCoordChange(invVM);
+ }
+ }
+ fCommon.fViewMatrix.reset();
+ return true;
+}
+
+void GrDrawState::setFromPaint(const GrPaint& paint, const SkMatrix& vm, GrRenderTarget* rt) {
+ GrAssert(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
+
+ fColorStages.reset();
+ fCoverageStages.reset();
+
+ for (int i = 0; i < paint.numColorStages(); ++i) {
+ fColorStages.push_back(paint.getColorStage(i));
+ }
+
+ for (int i = 0; i < paint.numCoverageStages(); ++i) {
+ fCoverageStages.push_back(paint.getCoverageStage(i));
+ }
+
+ this->setRenderTarget(rt);
+
+ fCommon.fViewMatrix = vm;
+
+ // These have no equivalent in GrPaint, set them to defaults
+ fCommon.fBlendConstant = 0x0;
+ fCommon.fDrawFace = kBoth_DrawFace;
+ fCommon.fStencilSettings.setDisabled();
+ this->resetStateFlags();
+
+ // Enable the clip bit
+ this->enableState(GrDrawState::kClip_StateBit);
+
+ this->setColor(paint.getColor());
+ this->setCoverage4(paint.getCoverage());
+ this->setState(GrDrawState::kDither_StateBit, paint.isDither());
+ this->setState(GrDrawState::kHWAntialias_StateBit, paint.isAntiAlias());
+
+ this->setBlendFunc(paint.getSrcBlendCoeff(), paint.getDstBlendCoeff());
+ this->setColorFilter(paint.getColorFilterColor(), paint.getColorFilterMode());
+ this->setCoverage(paint.getCoverage());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static size_t vertex_size(const GrVertexAttrib* attribs, int count) {
+ // this works as long as we're 4 byte-aligned
+#if GR_DEBUG
+ uint32_t overlapCheck = 0;
+#endif
+ GrAssert(count <= GrDrawState::kMaxVertexAttribCnt);
+ size_t size = 0;
+ for (int index = 0; index < count; ++index) {
+ size_t attribSize = GrVertexAttribTypeSize(attribs[index].fType);
+ size += attribSize;
+#if GR_DEBUG
+ size_t dwordCount = attribSize >> 2;
+ uint32_t mask = (1 << dwordCount)-1;
+ size_t offsetShift = attribs[index].fOffset >> 2;
+ GrAssert(!(overlapCheck & (mask << offsetShift)));
+ overlapCheck |= (mask << offsetShift);
+#endif
+ }
+ return size;
+}
+
+size_t GrDrawState::getVertexSize() const {
+ return vertex_size(fCommon.fVAPtr, fCommon.fVACount);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrDrawState::setVertexAttribs(const GrVertexAttrib* attribs, int count) {
+ GrAssert(count <= kMaxVertexAttribCnt);
+
+ fCommon.fVAPtr = attribs;
+ fCommon.fVACount = count;
+
+ // Set all the indices to -1
+ memset(fCommon.fFixedFunctionVertexAttribIndices,
+ 0xff,
+ sizeof(fCommon.fFixedFunctionVertexAttribIndices));
+#if GR_DEBUG
+ uint32_t overlapCheck = 0;
+#endif
+ for (int i = 0; i < count; ++i) {
+ if (attribs[i].fBinding < kGrFixedFunctionVertexAttribBindingCnt) {
+ // The fixed function attribs can only be specified once
+ GrAssert(-1 == fCommon.fFixedFunctionVertexAttribIndices[attribs[i].fBinding]);
+ GrAssert(GrFixedFunctionVertexAttribVectorCount(attribs[i].fBinding) ==
+ GrVertexAttribTypeVectorCount(attribs[i].fType));
+ fCommon.fFixedFunctionVertexAttribIndices[attribs[i].fBinding] = i;
+ }
+#if GR_DEBUG
+ size_t dwordCount = GrVertexAttribTypeSize(attribs[i].fType) >> 2;
+ uint32_t mask = (1 << dwordCount)-1;
+ size_t offsetShift = attribs[i].fOffset >> 2;
+ GrAssert(!(overlapCheck & (mask << offsetShift)));
+ overlapCheck |= (mask << offsetShift);
+#endif
+ }
+ // Positions must be specified.
+ GrAssert(-1 != fCommon.fFixedFunctionVertexAttribIndices[kPosition_GrVertexAttribBinding]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrDrawState::setDefaultVertexAttribs() {
+ static const GrVertexAttrib kPositionAttrib =
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding};
+
+ fCommon.fVAPtr = &kPositionAttrib;
+ fCommon.fVACount = 1;
+
+ // set all the fixed function indices to -1 except position.
+ memset(fCommon.fFixedFunctionVertexAttribIndices,
+ 0xff,
+ sizeof(fCommon.fFixedFunctionVertexAttribIndices));
+ fCommon.fFixedFunctionVertexAttribIndices[kPosition_GrVertexAttribBinding] = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrDrawState::validateVertexAttribs() const {
+ // check consistency of effects and attributes
+ GrSLType slTypes[kMaxVertexAttribCnt];
+ for (int i = 0; i < kMaxVertexAttribCnt; ++i) {
+ slTypes[i] = static_cast<GrSLType>(-1);
+ }
+ int totalStages = fColorStages.count() + fCoverageStages.count();
+ for (int s = 0; s < totalStages; ++s) {
+ int covIdx = s - fColorStages.count();
+ const GrEffectStage& stage = covIdx < 0 ? fColorStages[s] : fCoverageStages[covIdx];
+ const GrEffectRef* effect = stage.getEffect();
+ GrAssert(NULL != effect);
+ // make sure that any attribute indices have the correct binding type, that the attrib
+ // type and effect's shader lang type are compatible, and that attributes shared by
+ // multiple effects use the same shader lang type.
+ const int* attributeIndices = stage.getVertexAttribIndices();
+ int numAttributes = stage.getVertexAttribIndexCount();
+ for (int i = 0; i < numAttributes; ++i) {
+ int attribIndex = attributeIndices[i];
+ if (attribIndex >= fCommon.fVACount ||
+ kEffect_GrVertexAttribBinding != fCommon.fVAPtr[attribIndex].fBinding) {
+ return false;
+ }
+
+ GrSLType effectSLType = (*effect)->vertexAttribType(i);
+ GrVertexAttribType attribType = fCommon.fVAPtr[attribIndex].fType;
+ int slVecCount = GrSLTypeVectorCount(effectSLType);
+ int attribVecCount = GrVertexAttribTypeVectorCount(attribType);
+ if (slVecCount != attribVecCount ||
+ (static_cast<GrSLType>(-1) != slTypes[attribIndex] &&
+ slTypes[attribIndex] != effectSLType)) {
+ return false;
+ }
+ slTypes[attribIndex] = effectSLType;
+ }
+ }
+
+ return true;
+}
+
+bool GrDrawState::willEffectReadDstColor() const {
+ if (!this->isColorWriteDisabled()) {
+ for (int s = 0; s < fColorStages.count(); ++s) {
+ if ((*fColorStages[s].getEffect())->willReadDstColor()) {
+ return true;
+ }
+ }
+ }
+ for (int s = 0; s < fCoverageStages.count(); ++s) {
+ if ((*fCoverageStages[s].getEffect())->willReadDstColor()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrDrawState::srcAlphaWillBeOne() const {
+ uint32_t validComponentFlags;
+ GrColor color;
+ // Check if per-vertex or constant color may have partial alpha
+ if (this->hasColorVertexAttribute()) {
+ validComponentFlags = 0;
+ color = 0; // not strictly necessary but we get false alarms from tools about uninit.
+ } else {
+ validComponentFlags = kRGBA_GrColorComponentFlags;
+ color = this->getColor();
+ }
+
+ // Run through the color stages
+ for (int s = 0; s < fColorStages.count(); ++s) {
+ const GrEffectRef* effect = fColorStages[s].getEffect();
+ (*effect)->getConstantColorComponents(&color, &validComponentFlags);
+ }
+
+ // Check if the color filter could introduce an alpha.
+ // We could skip the above work when this is true, but it is rare and the right fix is to make
+ // the color filter a GrEffect and implement getConstantColorComponents() for it.
+ if (SkXfermode::kDst_Mode != this->getColorFilterMode()) {
+ validComponentFlags = 0;
+ }
+
+ // Check whether coverage is treated as color. If so we run through the coverage computation.
+ if (this->isCoverageDrawing()) {
+ GrColor coverageColor = this->getCoverage();
+ GrColor oldColor = color;
+ color = 0;
+ for (int c = 0; c < 4; ++c) {
+ if (validComponentFlags & (1 << c)) {
+ U8CPU a = (oldColor >> (c * 8)) & 0xff;
+ U8CPU b = (coverageColor >> (c * 8)) & 0xff;
+ color |= (SkMulDiv255Round(a, b) << (c * 8));
+ }
+ }
+ for (int s = 0; s < fCoverageStages.count(); ++s) {
+ const GrEffectRef* effect = fCoverageStages[s].getEffect();
+ (*effect)->getConstantColorComponents(&color, &validComponentFlags);
+ }
+ }
+ return (kA_GrColorComponentFlag & validComponentFlags) && 0xff == GrColorUnpackA(color);
+}
+
+bool GrDrawState::hasSolidCoverage() const {
+ // If we're drawing coverage directly then coverage is effectively treated as color.
+ if (this->isCoverageDrawing()) {
+ return true;
+ }
+
+ GrColor coverage;
+ uint32_t validComponentFlags;
+ // Initialize to an unknown starting coverage if per-vertex coverage is specified.
+ if (this->hasCoverageVertexAttribute()) {
+ validComponentFlags = 0;
+ } else {
+ coverage = fCommon.fCoverage;
+ validComponentFlags = kRGBA_GrColorComponentFlags;
+ }
+
+ // Run through the coverage stages and see if the coverage will be all ones at the end.
+ for (int s = 0; s < fCoverageStages.count(); ++s) {
+ const GrEffectRef* effect = fCoverageStages[s].getEffect();
+ (*effect)->getConstantColorComponents(&coverage, &validComponentFlags);
+ }
+ return (kRGBA_GrColorComponentFlags == validComponentFlags) && (0xffffffff == coverage);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Some blend modes allow folding a fractional coverage value into the color's alpha channel, while
+// others will blend incorrectly.
+bool GrDrawState::canTweakAlphaForCoverage() const {
+ /*
+ The fractional coverage is f.
+ The src and dst coeffs are Cs and Cd.
+ The dst and src colors are S and D.
+ 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 substituting in the various possibilities for Cd we
+ find that only 1, ISA, and ISC produce the correct destination when applied to S' and D.
+ Also, if we're directly rendering coverage (isCoverageDrawing) then coverage is treated as
+ color by definition.
+ */
+ return kOne_GrBlendCoeff == fCommon.fDstBlend ||
+ kISA_GrBlendCoeff == fCommon.fDstBlend ||
+ kISC_GrBlendCoeff == fCommon.fDstBlend ||
+ this->isCoverageDrawing();
+}
+
+GrDrawState::BlendOptFlags GrDrawState::getBlendOpts(bool forceCoverage,
+ GrBlendCoeff* srcCoeff,
+ GrBlendCoeff* dstCoeff) const {
+
+ GrBlendCoeff bogusSrcCoeff, bogusDstCoeff;
+ if (NULL == srcCoeff) {
+ srcCoeff = &bogusSrcCoeff;
+ }
+ *srcCoeff = this->getSrcBlendCoeff();
+
+ if (NULL == dstCoeff) {
+ dstCoeff = &bogusDstCoeff;
+ }
+ *dstCoeff = this->getDstBlendCoeff();
+
+ if (this->isColorWriteDisabled()) {
+ *srcCoeff = kZero_GrBlendCoeff;
+ *dstCoeff = kOne_GrBlendCoeff;
+ }
+
+ bool srcAIsOne = this->srcAlphaWillBeOne();
+ bool dstCoeffIsOne = kOne_GrBlendCoeff == *dstCoeff ||
+ (kSA_GrBlendCoeff == *dstCoeff && srcAIsOne);
+ bool dstCoeffIsZero = kZero_GrBlendCoeff == *dstCoeff ||
+ (kISA_GrBlendCoeff == *dstCoeff && srcAIsOne);
+
+ bool covIsZero = !this->isCoverageDrawing() &&
+ !this->hasCoverageVertexAttribute() &&
+ 0 == this->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) || covIsZero) {
+ if (this->getStencil().doesWrite()) {
+ return kDisableBlend_BlendOptFlag |
+ kEmitTransBlack_BlendOptFlag;
+ } else {
+ return kSkipDraw_BlendOptFlag;
+ }
+ }
+
+ // check for coverage due to constant coverage, per-vertex coverage, or coverage stage
+ bool hasCoverage = forceCoverage ||
+ 0xffffffff != this->getCoverage() ||
+ this->hasCoverageVertexAttribute() ||
+ fCoverageStages.count() > 0;
+
+ // if we don't have coverage we can check whether the dst
+ // has to read at all. If not, we'll disable blending.
+ if (!hasCoverage) {
+ if (dstCoeffIsZero) {
+ if (kOne_GrBlendCoeff == *srcCoeff) {
+ // if there is no coverage and coeffs are (1,0) then we
+ // won't need to read the dst at all, it gets replaced by src
+ return kDisableBlend_BlendOptFlag;
+ } else if (kZero_GrBlendCoeff == *srcCoeff) {
+ // if the op is "clear" then we don't need to emit a color
+ // or blend, just write transparent black into the dst.
+ *srcCoeff = kOne_GrBlendCoeff;
+ *dstCoeff = kZero_GrBlendCoeff;
+ return kDisableBlend_BlendOptFlag | kEmitTransBlack_BlendOptFlag;
+ }
+ }
+ } else if (this->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
+ if (this->canTweakAlphaForCoverage()) {
+ return kCoverageAsAlpha_BlendOptFlag;
+ }
+ if (dstCoeffIsZero) {
+ if (kZero_GrBlendCoeff == *srcCoeff) {
+ // the source color is not included in the blend
+ // the dst coeff is effectively zero so blend works out to:
+ // (c)(0)D + (1-c)D = (1-c)D.
+ *dstCoeff = kISA_GrBlendCoeff;
+ return kEmitCoverage_BlendOptFlag;
+ } else if (srcAIsOne) {
+ // the dst coeff is effectively zero so blend works out to:
+ // cS + (c)(0)D + (1-c)D = cS + (1-c)D.
+ // If Sa is 1 then we can replace Sa with c
+ // and set dst coeff to 1-Sa.
+ *dstCoeff = kISA_GrBlendCoeff;
+ return kCoverageAsAlpha_BlendOptFlag;
+ }
+ } else if (dstCoeffIsOne) {
+ // the dst coeff is effectively one so blend works out to:
+ // cS + (c)(1)D + (1-c)D = cS + D.
+ *dstCoeff = kOne_GrBlendCoeff;
+ return kCoverageAsAlpha_BlendOptFlag;
+ }
+ }
+ if (kOne_GrBlendCoeff == *srcCoeff &&
+ kZero_GrBlendCoeff == *dstCoeff &&
+ this->willEffectReadDstColor()) {
+ // In this case the shader will fully resolve the color, coverage, and dst and we don't
+ // need blending.
+ return kDisableBlend_BlendOptFlag;
+ }
+ return kNone_BlendOpt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrDrawState::AutoViewMatrixRestore::restore() {
+ if (NULL != fDrawState) {
+ GR_DEBUGCODE(--fDrawState->fBlockEffectRemovalCnt;)
+ fDrawState->fCommon.fViewMatrix = fViewMatrix;
+ GrAssert(fDrawState->numColorStages() >= fNumColorStages);
+ int numCoverageStages = fSavedCoordChanges.count() - fNumColorStages;
+ GrAssert(fDrawState->numCoverageStages() >= numCoverageStages);
+
+ int i = 0;
+ for (int s = 0; s < fNumColorStages; ++s, ++i) {
+ fDrawState->fColorStages[s].restoreCoordChange(fSavedCoordChanges[i]);
+ }
+ for (int s = 0; s < numCoverageStages; ++s, ++i) {
+ fDrawState->fCoverageStages[s].restoreCoordChange(fSavedCoordChanges[i]);
+ }
+ fDrawState = NULL;
+ }
+}
+
+void GrDrawState::AutoViewMatrixRestore::set(GrDrawState* drawState,
+ const SkMatrix& preconcatMatrix) {
+ this->restore();
+
+ GrAssert(NULL == fDrawState);
+ if (NULL == drawState || preconcatMatrix.isIdentity()) {
+ return;
+ }
+ fDrawState = drawState;
+
+ fViewMatrix = drawState->getViewMatrix();
+ drawState->fCommon.fViewMatrix.preConcat(preconcatMatrix);
+
+ this->doEffectCoordChanges(preconcatMatrix);
+ GR_DEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
+}
+
+bool GrDrawState::AutoViewMatrixRestore::setIdentity(GrDrawState* drawState) {
+ this->restore();
+
+ if (NULL == drawState) {
+ return false;
+ }
+
+ if (drawState->getViewMatrix().isIdentity()) {
+ return true;
+ }
+
+ fViewMatrix = drawState->getViewMatrix();
+ if (0 == drawState->numTotalStages()) {
+ drawState->fCommon.fViewMatrix.reset();
+ fDrawState = drawState;
+ fNumColorStages = 0;
+ fSavedCoordChanges.reset(0);
+ GR_DEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
+ return true;
+ } else {
+ SkMatrix inv;
+ if (!fViewMatrix.invert(&inv)) {
+ return false;
+ }
+ drawState->fCommon.fViewMatrix.reset();
+ fDrawState = drawState;
+ this->doEffectCoordChanges(inv);
+ GR_DEBUGCODE(++fDrawState->fBlockEffectRemovalCnt;)
+ return true;
+ }
+}
+
+void GrDrawState::AutoViewMatrixRestore::doEffectCoordChanges(const SkMatrix& coordChangeMatrix) {
+ fSavedCoordChanges.reset(fDrawState->numTotalStages());
+ int i = 0;
+
+ fNumColorStages = fDrawState->numColorStages();
+ for (int s = 0; s < fNumColorStages; ++s, ++i) {
+ fDrawState->fColorStages[s].saveCoordChange(&fSavedCoordChanges[i]);
+ fDrawState->fColorStages[s].localCoordChange(coordChangeMatrix);
+ }
+
+ int numCoverageStages = fDrawState->numCoverageStages();
+ for (int s = 0; s < numCoverageStages; ++s, ++i) {
+ fDrawState->fCoverageStages[s].saveCoordChange(&fSavedCoordChanges[i]);
+ fDrawState->fCoverageStages[s].localCoordChange(coordChangeMatrix);
+ }
+}
diff --git a/gpu/GrDrawState.h b/gpu/GrDrawState.h
new file mode 100644
index 00000000..c006e6c5
--- /dev/null
+++ b/gpu/GrDrawState.h
@@ -0,0 +1,1100 @@
+/*
+ * 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 GrDrawState_DEFINED
+#define GrDrawState_DEFINED
+
+#include "GrBackendEffectFactory.h"
+#include "GrColor.h"
+#include "GrEffectStage.h"
+#include "GrPaint.h"
+#include "GrPoint.h"
+#include "GrRefCnt.h"
+#include "GrRenderTarget.h"
+#include "GrStencil.h"
+#include "GrTemplates.h"
+#include "GrTexture.h"
+#include "GrTypesPriv.h"
+#include "effects/GrSimpleTextureEffect.h"
+
+#include "SkMatrix.h"
+#include "SkXfermode.h"
+
+class GrDrawState : public GrRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(GrDrawState)
+
+ GrDrawState() {
+ GR_DEBUGCODE(fBlockEffectRemovalCnt = 0;)
+ this->reset();
+ }
+
+ GrDrawState(const SkMatrix& initialViewMatrix) {
+ GR_DEBUGCODE(fBlockEffectRemovalCnt = 0;)
+ this->reset(initialViewMatrix);
+ }
+
+ /**
+ * Copies another draw state.
+ **/
+ GrDrawState(const GrDrawState& state) : INHERITED() {
+ GR_DEBUGCODE(fBlockEffectRemovalCnt = 0;)
+ *this = state;
+ }
+
+ /**
+ * Copies another draw state with a preconcat to the view matrix.
+ **/
+ GrDrawState(const GrDrawState& state, const SkMatrix& preConcatMatrix) {
+ GR_DEBUGCODE(fBlockEffectRemovalCnt = 0;)
+ *this = state;
+ if (!preConcatMatrix.isIdentity()) {
+ for (int i = 0; i < fColorStages.count(); ++i) {
+ fColorStages[i].localCoordChange(preConcatMatrix);
+ }
+ for (int i = 0; i < fCoverageStages.count(); ++i) {
+ fCoverageStages[i].localCoordChange(preConcatMatrix);
+ }
+ }
+ }
+
+ virtual ~GrDrawState() { GrAssert(0 == fBlockEffectRemovalCnt); }
+
+ /**
+ * Resets to the default state. GrEffects will be removed from all stages.
+ */
+ void reset() { this->onReset(NULL); }
+
+ void reset(const SkMatrix& initialViewMatrix) { this->onReset(&initialViewMatrix); }
+
+ /**
+ * Initializes the GrDrawState based on a GrPaint, view matrix and render target. Note that
+ * GrDrawState encompasses more than GrPaint. Aspects of GrDrawState that have no GrPaint
+ * equivalents are set to default values. Clipping will be enabled.
+ */
+ void setFromPaint(const GrPaint& , const SkMatrix& viewMatrix, GrRenderTarget*);
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Vertex Attributes
+ ////
+
+ enum {
+ kMaxVertexAttribCnt = kLast_GrVertexAttribBinding + 4,
+ };
+
+ /**
+ * The format of vertices is represented as an array of GrVertexAttribs, with each representing
+ * the type of the attribute, its offset, and semantic binding (see GrVertexAttrib in
+ * GrTypesPriv.h).
+ *
+ * The mapping of attributes with kEffect bindings to GrEffect inputs is specified when
+ * setEffect is called.
+ */
+
+ /**
+ * Sets vertex attributes for next draw. The object driving the templatization
+ * should be a global GrVertexAttrib array that is never changed.
+ */
+ template <const GrVertexAttrib A[]> void setVertexAttribs(int count) {
+ this->setVertexAttribs(A, count);
+ }
+
+ const GrVertexAttrib* getVertexAttribs() const { return fCommon.fVAPtr; }
+ int getVertexAttribCount() const { return fCommon.fVACount; }
+
+ size_t getVertexSize() const;
+
+ /**
+ * Sets default vertex attributes for next draw. The default is a single attribute:
+ * {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribType}
+ */
+ void setDefaultVertexAttribs();
+
+ /**
+ * Getters for index into getVertexAttribs() for particular bindings. -1 is returned if the
+ * binding does not appear in the current attribs. These bindings should appear only once in
+ * the attrib array.
+ */
+
+ int positionAttributeIndex() const {
+ return fCommon.fFixedFunctionVertexAttribIndices[kPosition_GrVertexAttribBinding];
+ }
+ int localCoordAttributeIndex() const {
+ return fCommon.fFixedFunctionVertexAttribIndices[kLocalCoord_GrVertexAttribBinding];
+ }
+ int colorVertexAttributeIndex() const {
+ return fCommon.fFixedFunctionVertexAttribIndices[kColor_GrVertexAttribBinding];
+ }
+ int coverageVertexAttributeIndex() const {
+ return fCommon.fFixedFunctionVertexAttribIndices[kCoverage_GrVertexAttribBinding];
+ }
+
+ bool hasLocalCoordAttribute() const {
+ return -1 != fCommon.fFixedFunctionVertexAttribIndices[kLocalCoord_GrVertexAttribBinding];
+ }
+ bool hasColorVertexAttribute() const {
+ return -1 != fCommon.fFixedFunctionVertexAttribIndices[kColor_GrVertexAttribBinding];
+ }
+ bool hasCoverageVertexAttribute() const {
+ return -1 != fCommon.fFixedFunctionVertexAttribIndices[kCoverage_GrVertexAttribBinding];
+ }
+
+ bool validateVertexAttribs() const;
+
+ /**
+ * Helper to save/restore vertex attribs
+ */
+ class AutoVertexAttribRestore {
+ public:
+ AutoVertexAttribRestore(GrDrawState* drawState) {
+ GrAssert(NULL != drawState);
+ fDrawState = drawState;
+ fVAPtr = drawState->fCommon.fVAPtr;
+ fVACount = drawState->fCommon.fVACount;
+ fDrawState->setDefaultVertexAttribs();
+ }
+
+ ~AutoVertexAttribRestore(){
+ fDrawState->fCommon.fVAPtr = fVAPtr;
+ fDrawState->fCommon.fVACount = fVACount;
+ }
+
+ private:
+ GrDrawState* fDrawState;
+ const GrVertexAttrib* fVAPtr;
+ int fVACount;
+ };
+
+ /**
+ * Accessing positions, local coords, or colors, of a vertex within an array is a hassle
+ * involving casts and simple math. These helpers exist to keep GrDrawTarget clients' code a bit
+ * nicer looking.
+ */
+
+ /**
+ * Gets a pointer to a GrPoint of a vertex's position or texture
+ * coordinate.
+ * @param vertices the vertex array
+ * @param vertexIndex the index of the vertex in the array
+ * @param vertexSize the size of each vertex in the array
+ * @param offset the offset in bytes of the vertex component.
+ * Defaults to zero (corresponding to vertex position)
+ * @return pointer to the vertex component as a GrPoint
+ */
+ static GrPoint* GetVertexPoint(void* vertices,
+ int vertexIndex,
+ int vertexSize,
+ int offset = 0) {
+ intptr_t start = GrTCast<intptr_t>(vertices);
+ return GrTCast<GrPoint*>(start + offset +
+ vertexIndex * vertexSize);
+ }
+ static const GrPoint* GetVertexPoint(const void* vertices,
+ int vertexIndex,
+ int vertexSize,
+ int offset = 0) {
+ intptr_t start = GrTCast<intptr_t>(vertices);
+ return GrTCast<const GrPoint*>(start + offset +
+ vertexIndex * vertexSize);
+ }
+
+ /**
+ * Gets a pointer to a GrColor inside a vertex within a vertex array.
+ * @param vertices the vetex array
+ * @param vertexIndex the index of the vertex in the array
+ * @param vertexSize the size of each vertex in the array
+ * @param offset the offset in bytes of the vertex color
+ * @return pointer to the vertex component as a GrColor
+ */
+ static GrColor* GetVertexColor(void* vertices,
+ int vertexIndex,
+ int vertexSize,
+ int offset) {
+ intptr_t start = GrTCast<intptr_t>(vertices);
+ return GrTCast<GrColor*>(start + offset +
+ vertexIndex * vertexSize);
+ }
+ static const GrColor* GetVertexColor(const void* vertices,
+ int vertexIndex,
+ int vertexSize,
+ int offset) {
+ const intptr_t start = GrTCast<intptr_t>(vertices);
+ return GrTCast<const GrColor*>(start + offset +
+ vertexIndex * vertexSize);
+ }
+
+ /// @}
+
+ /**
+ * Determines whether src alpha is guaranteed to be one for all src pixels
+ */
+ bool srcAlphaWillBeOne() const;
+
+ /**
+ * Determines whether the output coverage is guaranteed to be one for all pixels hit by a draw.
+ */
+ bool hasSolidCoverage() const;
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Color
+ ////
+
+ /**
+ * Sets color for next draw to a premultiplied-alpha color.
+ *
+ * @param color the color to set.
+ */
+ void setColor(GrColor color) { fCommon.fColor = color; }
+
+ GrColor getColor() const { return fCommon.fColor; }
+
+ /**
+ * Sets the color to be used for the next draw to be
+ * (r,g,b,a) = (alpha, alpha, alpha, alpha).
+ *
+ * @param alpha The alpha value to set as the color.
+ */
+ void setAlpha(uint8_t a) {
+ this->setColor((a << 24) | (a << 16) | (a << 8) | a);
+ }
+
+ /**
+ * Add a color filter that can be represented by a color and a mode. Applied
+ * after color-computing effect stages.
+ */
+ void setColorFilter(GrColor c, SkXfermode::Mode mode) {
+ fCommon.fColorFilterColor = c;
+ fCommon.fColorFilterMode = mode;
+ }
+
+ GrColor getColorFilterColor() const { return fCommon.fColorFilterColor; }
+ SkXfermode::Mode getColorFilterMode() const { return fCommon.fColorFilterMode; }
+
+ /**
+ * Constructor sets the color to be 'color' which is undone by the destructor.
+ */
+ class AutoColorRestore : public ::GrNoncopyable {
+ public:
+ AutoColorRestore() : fDrawState(NULL), fOldColor(0) {}
+
+ AutoColorRestore(GrDrawState* drawState, GrColor color) {
+ fDrawState = NULL;
+ this->set(drawState, color);
+ }
+
+ void reset() {
+ if (NULL != fDrawState) {
+ fDrawState->setColor(fOldColor);
+ fDrawState = NULL;
+ }
+ }
+
+ void set(GrDrawState* drawState, GrColor color) {
+ this->reset();
+ fDrawState = drawState;
+ fOldColor = fDrawState->getColor();
+ fDrawState->setColor(color);
+ }
+
+ ~AutoColorRestore() { this->reset(); }
+ private:
+ GrDrawState* fDrawState;
+ GrColor fOldColor;
+ };
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Coverage
+ ////
+
+ /**
+ * Sets a constant fractional coverage to be applied to the draw. The
+ * initial value (after construction or reset()) is 0xff. The constant
+ * coverage is ignored when per-vertex coverage is provided.
+ */
+ void setCoverage(uint8_t coverage) {
+ fCommon.fCoverage = GrColorPackRGBA(coverage, coverage, coverage, coverage);
+ }
+
+ /**
+ * Version of above that specifies 4 channel per-vertex color. The value
+ * should be premultiplied.
+ */
+ void setCoverage4(GrColor coverage) {
+ fCommon.fCoverage = coverage;
+ }
+
+ GrColor getCoverage() const {
+ return fCommon.fCoverage;
+ }
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Effect Stages
+ /// Each stage hosts a GrEffect. The effect produces an output color or coverage in the fragment
+ /// shader. Its inputs are the output from the previous stage as well as some variables
+ /// available to it in the fragment and vertex shader (e.g. the vertex position, the dst color,
+ /// the fragment position, local coordinates).
+ ///
+ /// 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 color-stage is either the constant color or interpolated
+ /// per-vertex colors. The input to the first coverage stage is either a constant coverage
+ /// (usually full-coverage) or interpolated per-vertex coverage.
+ ///
+ /// See the documentation of kCoverageDrawing_StateBit for information about disabling the
+ /// the color / coverage distinction.
+ ////
+
+ const GrEffectRef* addColorEffect(const GrEffectRef* effect, int attr0 = -1, int attr1 = -1) {
+ GrAssert(NULL != effect);
+ SkNEW_APPEND_TO_TARRAY(&fColorStages, GrEffectStage, (effect, attr0, attr1));
+ return effect;
+ }
+
+ const GrEffectRef* addCoverageEffect(const GrEffectRef* effect, int attr0 = -1, int attr1 = -1) {
+ GrAssert(NULL != effect);
+ SkNEW_APPEND_TO_TARRAY(&fCoverageStages, GrEffectStage, (effect, attr0, attr1));
+ return effect;
+ }
+
+ /**
+ * Creates a GrSimpleTextureEffect that uses local coords as texture coordinates.
+ */
+ void addColorTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+ this->addColorEffect(effect)->unref();
+ }
+
+ void addCoverageTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+ this->addCoverageEffect(effect)->unref();
+ }
+
+ void addColorTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrTextureParams& params) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+ this->addColorEffect(effect)->unref();
+ }
+
+ void addCoverageTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrTextureParams& params) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+ this->addCoverageEffect(effect)->unref();
+ }
+
+ /**
+ * When this object is destroyed it will remove any effects from the draw state that were added
+ * after its constructor.
+ */
+ class AutoRestoreEffects : public ::GrNoncopyable {
+ public:
+ AutoRestoreEffects() : fDrawState(NULL), fColorEffectCnt(0), fCoverageEffectCnt(0) {}
+
+ AutoRestoreEffects(GrDrawState* ds) : fDrawState(NULL), fColorEffectCnt(0), fCoverageEffectCnt(0) {
+ this->set(ds);
+ }
+
+ ~AutoRestoreEffects() { this->set(NULL); }
+
+ void set(GrDrawState* ds) {
+ if (NULL != fDrawState) {
+ int n = fDrawState->fColorStages.count() - fColorEffectCnt;
+ GrAssert(n >= 0);
+ fDrawState->fColorStages.pop_back_n(n);
+ n = fDrawState->fCoverageStages.count() - fCoverageEffectCnt;
+ GrAssert(n >= 0);
+ fDrawState->fCoverageStages.pop_back_n(n);
+ GR_DEBUGCODE(--fDrawState->fBlockEffectRemovalCnt;)
+ }
+ fDrawState = ds;
+ if (NULL != ds) {
+ fColorEffectCnt = ds->fColorStages.count();
+ fCoverageEffectCnt = ds->fCoverageStages.count();
+ GR_DEBUGCODE(++ds->fBlockEffectRemovalCnt;)
+ }
+ }
+
+ private:
+ GrDrawState* fDrawState;
+ int fColorEffectCnt;
+ int fCoverageEffectCnt;
+ };
+
+ int numColorStages() const { return fColorStages.count(); }
+ int numCoverageStages() const { return fCoverageStages.count(); }
+ int numTotalStages() const { return this->numColorStages() + this->numCoverageStages(); }
+
+ const GrEffectStage& getColorStage(int stageIdx) const { return fColorStages[stageIdx]; }
+ const GrEffectStage& getCoverageStage(int stageIdx) const { return fCoverageStages[stageIdx]; }
+
+ /**
+ * Checks whether any of the effects will read the dst pixel color.
+ */
+ bool willEffectReadDstColor() const;
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Blending
+ ////
+
+ /**
+ * Sets the blending function coefficients.
+ *
+ * The blend function will be:
+ * D' = sat(S*srcCoef + D*dstCoef)
+ *
+ * where D is the existing destination color, S is the incoming source
+ * color, and D' is the new destination color that will be written. sat()
+ * is the saturation function.
+ *
+ * @param srcCoef coefficient applied to the src color.
+ * @param dstCoef coefficient applied to the dst color.
+ */
+ void setBlendFunc(GrBlendCoeff srcCoeff, GrBlendCoeff dstCoeff) {
+ fCommon.fSrcBlend = srcCoeff;
+ fCommon.fDstBlend = dstCoeff;
+ #if GR_DEBUG
+ switch (dstCoeff) {
+ case kDC_GrBlendCoeff:
+ case kIDC_GrBlendCoeff:
+ case kDA_GrBlendCoeff:
+ case kIDA_GrBlendCoeff:
+ GrPrintf("Unexpected dst blend coeff. Won't work correctly with"
+ "coverage stages.\n");
+ break;
+ default:
+ break;
+ }
+ switch (srcCoeff) {
+ case kSC_GrBlendCoeff:
+ case kISC_GrBlendCoeff:
+ case kSA_GrBlendCoeff:
+ case kISA_GrBlendCoeff:
+ GrPrintf("Unexpected src blend coeff. Won't work correctly with"
+ "coverage stages.\n");
+ break;
+ default:
+ break;
+ }
+ #endif
+ }
+
+ GrBlendCoeff getSrcBlendCoeff() const { return fCommon.fSrcBlend; }
+ GrBlendCoeff getDstBlendCoeff() const { return fCommon.fDstBlend; }
+
+ void getDstBlendCoeff(GrBlendCoeff* srcBlendCoeff,
+ GrBlendCoeff* dstBlendCoeff) const {
+ *srcBlendCoeff = fCommon.fSrcBlend;
+ *dstBlendCoeff = fCommon.fDstBlend;
+ }
+
+ /**
+ * Sets the blending function constant referenced by the following blending
+ * coefficients:
+ * kConstC_GrBlendCoeff
+ * kIConstC_GrBlendCoeff
+ * kConstA_GrBlendCoeff
+ * kIConstA_GrBlendCoeff
+ *
+ * @param constant the constant to set
+ */
+ void setBlendConstant(GrColor constant) { fCommon.fBlendConstant = constant; }
+
+ /**
+ * Retrieves the last value set by setBlendConstant()
+ * @return the blending constant value
+ */
+ GrColor getBlendConstant() const { return fCommon.fBlendConstant; }
+
+ /**
+ * Determines whether multiplying the computed per-pixel color by the pixel's fractional
+ * coverage before the blend will give the correct final destination color. In general it
+ * will not as coverage is applied after blending.
+ */
+ bool canTweakAlphaForCoverage() const;
+
+ /**
+ * Optimizations for blending / coverage to that can be applied based on the current state.
+ */
+ enum BlendOptFlags {
+ /**
+ * No optimization
+ */
+ kNone_BlendOpt = 0,
+ /**
+ * Don't draw at all
+ */
+ kSkipDraw_BlendOptFlag = 0x1,
+ /**
+ * Emit the src color, disable HW blending (replace dst with src)
+ */
+ kDisableBlend_BlendOptFlag = 0x2,
+ /**
+ * The coverage value does not have to be computed separately from alpha, the the output
+ * color can be the modulation of the two.
+ */
+ kCoverageAsAlpha_BlendOptFlag = 0x4,
+ /**
+ * Instead of emitting a src color, emit coverage in the alpha channel and r,g,b are
+ * "don't cares".
+ */
+ kEmitCoverage_BlendOptFlag = 0x8,
+ /**
+ * Emit transparent black instead of the src color, no need to compute coverage.
+ */
+ kEmitTransBlack_BlendOptFlag = 0x10,
+ };
+ GR_DECL_BITFIELD_OPS_FRIENDS(BlendOptFlags);
+
+ /**
+ * 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.
+ *
+ * Subclasses of GrDrawTarget that actually draw (as opposed to those that just buffer for
+ * playback) must call this function and respect the flags that replace the output color.
+ */
+ BlendOptFlags getBlendOpts(bool forceCoverage = false,
+ GrBlendCoeff* srcCoeff = NULL,
+ GrBlendCoeff* dstCoeff = NULL) const;
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name View Matrix
+ ////
+
+ /**
+ * Sets the view matrix to identity and updates any installed effects to compensate for the
+ * coord system change.
+ */
+ bool setIdentityViewMatrix();
+
+ /**
+ * Retrieves the current view matrix
+ * @return the current view matrix.
+ */
+ const SkMatrix& getViewMatrix() const { return fCommon.fViewMatrix; }
+
+ /**
+ * Retrieves the inverse of the current view matrix.
+ *
+ * If the current view matrix is invertible, return true, and if matrix
+ * is non-null, copy the inverse into it. If the current view matrix is
+ * non-invertible, return false and ignore the matrix parameter.
+ *
+ * @param matrix if not null, will receive a copy of the current inverse.
+ */
+ bool getViewInverse(SkMatrix* matrix) const {
+ // TODO: determine whether we really need to leave matrix unmodified
+ // at call sites when inversion fails.
+ SkMatrix inverse;
+ if (fCommon.fViewMatrix.invert(&inverse)) {
+ if (matrix) {
+ *matrix = inverse;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Preconcats the current view matrix and restores the previous view matrix in the destructor.
+ * Effect matrices are automatically adjusted to compensate and adjusted back in the destructor.
+ */
+ class AutoViewMatrixRestore : public ::GrNoncopyable {
+ public:
+ AutoViewMatrixRestore() : fDrawState(NULL) {}
+
+ AutoViewMatrixRestore(GrDrawState* ds, const SkMatrix& preconcatMatrix) {
+ fDrawState = NULL;
+ this->set(ds, preconcatMatrix);
+ }
+
+ ~AutoViewMatrixRestore() { this->restore(); }
+
+ /**
+ * Can be called prior to destructor to restore the original matrix.
+ */
+ void restore();
+
+ void set(GrDrawState* drawState, const SkMatrix& preconcatMatrix);
+
+ /** Sets the draw state's matrix to identity. This can fail because the current view matrix
+ is not invertible. */
+ bool setIdentity(GrDrawState* drawState);
+
+ private:
+ void doEffectCoordChanges(const SkMatrix& coordChangeMatrix);
+
+ GrDrawState* fDrawState;
+ SkMatrix fViewMatrix;
+ int fNumColorStages;
+ SkAutoSTArray<8, GrEffectStage::SavedCoordChange> fSavedCoordChanges;
+ };
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Render Target
+ ////
+
+ /**
+ * Sets the render-target used at the next drawing call
+ *
+ * @param target The render target to set.
+ */
+ void setRenderTarget(GrRenderTarget* target) {
+ fRenderTarget.reset(SkSafeRef(target));
+ }
+
+ /**
+ * Retrieves the currently set render-target.
+ *
+ * @return The currently set render target.
+ */
+ const GrRenderTarget* getRenderTarget() const { return fRenderTarget.get(); }
+ GrRenderTarget* getRenderTarget() { return fRenderTarget.get(); }
+
+ class AutoRenderTargetRestore : public ::GrNoncopyable {
+ public:
+ AutoRenderTargetRestore() : fDrawState(NULL), fSavedTarget(NULL) {}
+ AutoRenderTargetRestore(GrDrawState* ds, GrRenderTarget* newTarget) {
+ fDrawState = NULL;
+ fSavedTarget = NULL;
+ this->set(ds, newTarget);
+ }
+ ~AutoRenderTargetRestore() { this->restore(); }
+
+ void restore() {
+ if (NULL != fDrawState) {
+ fDrawState->setRenderTarget(fSavedTarget);
+ fDrawState = NULL;
+ }
+ GrSafeSetNull(fSavedTarget);
+ }
+
+ void set(GrDrawState* ds, GrRenderTarget* newTarget) {
+ this->restore();
+
+ if (NULL != ds) {
+ GrAssert(NULL == fSavedTarget);
+ fSavedTarget = ds->getRenderTarget();
+ SkSafeRef(fSavedTarget);
+ ds->setRenderTarget(newTarget);
+ fDrawState = ds;
+ }
+ }
+ private:
+ GrDrawState* fDrawState;
+ GrRenderTarget* fSavedTarget;
+ };
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Stencil
+ ////
+
+ /**
+ * Sets the stencil settings to use for the next draw.
+ * Changing the clip has the side-effect of possibly zeroing
+ * out the client settable stencil bits. So multipass algorithms
+ * using stencil should not change the clip between passes.
+ * @param settings the stencil settings to use.
+ */
+ void setStencil(const GrStencilSettings& settings) {
+ fCommon.fStencilSettings = settings;
+ }
+
+ /**
+ * Shortcut to disable stencil testing and ops.
+ */
+ void disableStencil() {
+ fCommon.fStencilSettings.setDisabled();
+ }
+
+ const GrStencilSettings& getStencil() const { return fCommon.fStencilSettings; }
+
+ GrStencilSettings* stencil() { return &fCommon.fStencilSettings; }
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name State Flags
+ ////
+
+ /**
+ * Flags that affect rendering. Controlled using enable/disableState(). All
+ * default to disabled.
+ */
+ enum StateBits {
+ /**
+ * Perform dithering. TODO: Re-evaluate whether we need this bit
+ */
+ 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.
+ */
+ kHWAntialias_StateBit = 0x02,
+ /**
+ * Draws will respect the clip, otherwise the clip is ignored.
+ */
+ kClip_StateBit = 0x04,
+ /**
+ * Disables writing to the color buffer. Useful when performing stencil
+ * operations.
+ */
+ kNoColorWrites_StateBit = 0x08,
+
+ /**
+ * 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.
+ */
+ kCoverageDrawing_StateBit = 0x10,
+
+ // Users of the class may add additional bits to the vector
+ kDummyStateBit,
+ kLastPublicStateBit = kDummyStateBit-1,
+ };
+
+ void resetStateFlags() {
+ fCommon.fFlagBits = 0;
+ }
+
+ /**
+ * Enable render state settings.
+ *
+ * @param stateBits bitfield of StateBits specifying the states to enable
+ */
+ void enableState(uint32_t stateBits) {
+ fCommon.fFlagBits |= stateBits;
+ }
+
+ /**
+ * Disable render state settings.
+ *
+ * @param stateBits bitfield of StateBits specifying the states to disable
+ */
+ void disableState(uint32_t stateBits) {
+ fCommon.fFlagBits &= ~(stateBits);
+ }
+
+ /**
+ * Enable or disable stateBits based on a boolean.
+ *
+ * @param stateBits bitfield of StateBits to enable or disable
+ * @param enable if true enable stateBits, otherwise disable
+ */
+ void setState(uint32_t stateBits, bool enable) {
+ if (enable) {
+ this->enableState(stateBits);
+ } else {
+ this->disableState(stateBits);
+ }
+ }
+
+ bool isDitherState() const {
+ return 0 != (fCommon.fFlagBits & kDither_StateBit);
+ }
+
+ bool isHWAntialiasState() const {
+ return 0 != (fCommon.fFlagBits & kHWAntialias_StateBit);
+ }
+
+ bool isClipState() const {
+ return 0 != (fCommon.fFlagBits & kClip_StateBit);
+ }
+
+ bool isColorWriteDisabled() const {
+ return 0 != (fCommon.fFlagBits & kNoColorWrites_StateBit);
+ }
+
+ bool isCoverageDrawing() const {
+ return 0 != (fCommon.fFlagBits & kCoverageDrawing_StateBit);
+ }
+
+ bool isStateFlagEnabled(uint32_t stateBit) const {
+ return 0 != (stateBit & fCommon.fFlagBits);
+ }
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// @name Face Culling
+ ////
+
+ enum DrawFace {
+ kInvalid_DrawFace = -1,
+
+ kBoth_DrawFace,
+ kCCW_DrawFace,
+ kCW_DrawFace,
+ };
+
+ /**
+ * Controls whether clockwise, counterclockwise, or both faces are drawn.
+ * @param face the face(s) to draw.
+ */
+ void setDrawFace(DrawFace face) {
+ GrAssert(kInvalid_DrawFace != face);
+ fCommon.fDrawFace = face;
+ }
+
+ /**
+ * Gets whether the target is drawing clockwise, counterclockwise,
+ * or both faces.
+ * @return the current draw face(s).
+ */
+ DrawFace getDrawFace() const { return fCommon.fDrawFace; }
+
+ /// @}
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ bool operator ==(const GrDrawState& s) const {
+ if (fRenderTarget.get() != s.fRenderTarget.get() ||
+ fColorStages.count() != s.fColorStages.count() ||
+ fCoverageStages.count() != s.fCoverageStages.count() ||
+ fCommon != s.fCommon) {
+ return false;
+ }
+ for (int i = 0; i < fColorStages.count(); i++) {
+ if (fColorStages[i] != s.fColorStages[i]) {
+ return false;
+ }
+ }
+ for (int i = 0; i < fCoverageStages.count(); i++) {
+ if (fCoverageStages[i] != s.fCoverageStages[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator !=(const GrDrawState& s) const { return !(*this == s); }
+
+ GrDrawState& operator= (const GrDrawState& s) {
+ GrAssert(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
+ this->setRenderTarget(s.fRenderTarget.get());
+ fCommon = s.fCommon;
+ fColorStages = s.fColorStages;
+ fCoverageStages = s.fCoverageStages;
+ return *this;
+ }
+
+private:
+
+ void onReset(const SkMatrix* initialViewMatrix) {
+ GrAssert(0 == fBlockEffectRemovalCnt || 0 == this->numTotalStages());
+ fColorStages.reset();
+ fCoverageStages.reset();
+
+ fRenderTarget.reset(NULL);
+
+ this->setDefaultVertexAttribs();
+
+ fCommon.fColor = 0xffffffff;
+ if (NULL == initialViewMatrix) {
+ fCommon.fViewMatrix.reset();
+ } else {
+ fCommon.fViewMatrix = *initialViewMatrix;
+ }
+ fCommon.fSrcBlend = kOne_GrBlendCoeff;
+ fCommon.fDstBlend = kZero_GrBlendCoeff;
+ fCommon.fBlendConstant = 0x0;
+ fCommon.fFlagBits = 0x0;
+ fCommon.fStencilSettings.setDisabled();
+ fCommon.fCoverage = 0xffffffff;
+ fCommon.fColorFilterMode = SkXfermode::kDst_Mode;
+ fCommon.fColorFilterColor = 0x0;
+ fCommon.fDrawFace = kBoth_DrawFace;
+ }
+
+ /** Fields that are identical in GrDrawState and GrDrawState::DeferredState. */
+ struct CommonState {
+ // These fields are roughly sorted by decreasing likelihood of being different in op==
+ GrColor fColor;
+ SkMatrix fViewMatrix;
+ GrBlendCoeff fSrcBlend;
+ GrBlendCoeff fDstBlend;
+ GrColor fBlendConstant;
+ uint32_t fFlagBits;
+ const GrVertexAttrib* fVAPtr;
+ int fVACount;
+ GrStencilSettings fStencilSettings;
+ GrColor fCoverage;
+ SkXfermode::Mode fColorFilterMode;
+ GrColor fColorFilterColor;
+ DrawFace fDrawFace;
+
+ // This is simply a different representation of info in fVertexAttribs and thus does
+ // not need to be compared in op==.
+ int fFixedFunctionVertexAttribIndices[kGrFixedFunctionVertexAttribBindingCnt];
+
+ bool operator== (const CommonState& other) const {
+ bool result = fColor == other.fColor &&
+ fViewMatrix.cheapEqualTo(other.fViewMatrix) &&
+ fSrcBlend == other.fSrcBlend &&
+ fDstBlend == other.fDstBlend &&
+ fBlendConstant == other.fBlendConstant &&
+ fFlagBits == other.fFlagBits &&
+ fVACount == other.fVACount &&
+ !memcmp(fVAPtr, other.fVAPtr, fVACount * sizeof(GrVertexAttrib)) &&
+ fStencilSettings == other.fStencilSettings &&
+ fCoverage == other.fCoverage &&
+ fColorFilterMode == other.fColorFilterMode &&
+ fColorFilterColor == other.fColorFilterColor &&
+ fDrawFace == other.fDrawFace;
+ GrAssert(!result || 0 == memcmp(fFixedFunctionVertexAttribIndices,
+ other.fFixedFunctionVertexAttribIndices,
+ sizeof(fFixedFunctionVertexAttribIndices)));
+ return result;
+ }
+ bool operator!= (const CommonState& other) const { return !(*this == other); }
+ };
+
+ /** GrDrawState uses GrEffectStages to hold stage state which holds a ref on GrEffectRef.
+ DeferredState must directly reference GrEffects, however. */
+ struct SavedEffectStage {
+ SavedEffectStage() : fEffect(NULL) {}
+ const GrEffect* fEffect;
+ GrEffectStage::SavedCoordChange fCoordChange;
+ };
+
+public:
+ /**
+ * DeferredState contains all of the data of a GrDrawState but does not hold refs on GrResource
+ * objects. Resources are allowed to hit zero ref count while in DeferredStates. Their internal
+ * dispose mechanism returns them to the cache. This allows recycling resources through the
+ * the cache while they are in a deferred draw queue.
+ */
+ class DeferredState {
+ public:
+ DeferredState() : fRenderTarget(NULL) {
+ GR_DEBUGCODE(fInitialized = false;)
+ }
+ // TODO: Remove this when DeferredState no longer holds a ref to the RT
+ ~DeferredState() { SkSafeUnref(fRenderTarget); }
+
+ void saveFrom(const GrDrawState& drawState) {
+ fCommon = drawState.fCommon;
+ // TODO: Here we will copy the GrRenderTarget pointer without taking a ref.
+ fRenderTarget = drawState.fRenderTarget.get();
+ SkSafeRef(fRenderTarget);
+ // Here we ref the effects directly rather than the effect-refs. TODO: When the effect-
+ // ref gets fully unref'ed it will cause the underlying effect to unref its resources
+ // and recycle them to the cache (if no one else is holding a ref to the resources).
+ fStages.reset(drawState.fColorStages.count() + drawState.fCoverageStages.count());
+ fColorStageCnt = drawState.fColorStages.count();
+ for (int i = 0; i < fColorStageCnt; ++i) {
+ fStages[i].saveFrom(drawState.fColorStages[i]);
+ }
+ for (int i = 0; i < drawState.fCoverageStages.count(); ++i) {
+ fStages[i + fColorStageCnt].saveFrom(drawState.fCoverageStages[i]);
+ }
+ GR_DEBUGCODE(fInitialized = true;)
+ }
+
+ void restoreTo(GrDrawState* drawState) {
+ GrAssert(fInitialized);
+ drawState->fCommon = fCommon;
+ drawState->setRenderTarget(fRenderTarget);
+ // reinflate color/cov stage arrays.
+ drawState->fColorStages.reset();
+ for (int i = 0; i < fColorStageCnt; ++i) {
+ SkNEW_APPEND_TO_TARRAY(&drawState->fColorStages, GrEffectStage, (fStages[i]));
+ }
+ int coverageStageCnt = fStages.count() - fColorStageCnt;
+ drawState->fCoverageStages.reset();
+ for (int i = 0; i < coverageStageCnt; ++i) {
+ SkNEW_APPEND_TO_TARRAY(&drawState->fCoverageStages,
+ GrEffectStage, (fStages[i + fColorStageCnt]));
+ }
+ }
+
+ bool isEqual(const GrDrawState& state) const {
+ int numCoverageStages = fStages.count() - fColorStageCnt;
+ if (fRenderTarget != state.fRenderTarget.get() ||
+ fColorStageCnt != state.fColorStages.count() ||
+ numCoverageStages != state.fCoverageStages.count() ||
+ fCommon != state.fCommon) {
+ return false;
+ }
+ bool explicitLocalCoords = state.hasLocalCoordAttribute();
+ for (int i = 0; i < fColorStageCnt; ++i) {
+ if (!fStages[i].isEqual(state.fColorStages[i], explicitLocalCoords)) {
+ return false;
+ }
+ }
+ for (int i = 0; i < numCoverageStages; ++i) {
+ int s = fColorStageCnt + i;
+ if (!fStages[s].isEqual(state.fCoverageStages[i], explicitLocalCoords)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ typedef SkAutoSTArray<8, GrEffectStage::DeferredStage> DeferredStageArray;
+
+ GrRenderTarget* fRenderTarget;
+ CommonState fCommon;
+ int fColorStageCnt;
+ DeferredStageArray fStages;
+
+ GR_DEBUGCODE(bool fInitialized;)
+ };
+
+private:
+
+ SkAutoTUnref<GrRenderTarget> fRenderTarget;
+ CommonState fCommon;
+
+ typedef SkSTArray<4, GrEffectStage> EffectStageArray;
+ EffectStageArray fColorStages;
+ EffectStageArray fCoverageStages;
+
+ // Some of the auto restore objects assume that no effects are removed during their lifetime.
+ // This is used to assert that this condition holds.
+ GR_DEBUGCODE(int fBlockEffectRemovalCnt;)
+
+ /**
+ * Sets vertex attributes for next draw.
+ *
+ * @param attribs the array of vertex attributes to set.
+ * @param count the number of attributes being set, limited to kMaxVertexAttribCnt.
+ */
+ void setVertexAttribs(const GrVertexAttrib attribs[], int count);
+
+ typedef GrRefCnt INHERITED;
+};
+
+GR_MAKE_BITFIELD_OPS(GrDrawState::BlendOptFlags);
+
+#endif
diff --git a/gpu/GrDrawTarget.cpp b/gpu/GrDrawTarget.cpp
new file mode 100644
index 00000000..e38f2a35
--- /dev/null
+++ b/gpu/GrDrawTarget.cpp
@@ -0,0 +1,999 @@
+
+/*
+ * 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 "GrDrawTarget.h"
+#include "GrContext.h"
+#include "GrDrawTargetCaps.h"
+#include "GrRenderTarget.h"
+#include "GrTexture.h"
+#include "GrVertexBuffer.h"
+
+#include "SkStrokeRec.h"
+
+SK_DEFINE_INST_COUNT(GrDrawTarget)
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrDrawTarget::DrawInfo& GrDrawTarget::DrawInfo::operator =(const DrawInfo& di) {
+ fPrimitiveType = di.fPrimitiveType;
+ fStartVertex = di.fStartVertex;
+ fStartIndex = di.fStartIndex;
+ fVertexCount = di.fVertexCount;
+ fIndexCount = di.fIndexCount;
+
+ fInstanceCount = di.fInstanceCount;
+ fVerticesPerInstance = di.fVerticesPerInstance;
+ fIndicesPerInstance = di.fIndicesPerInstance;
+
+ if (NULL != di.fDevBounds) {
+ GrAssert(di.fDevBounds == &di.fDevBoundsStorage);
+ fDevBoundsStorage = di.fDevBoundsStorage;
+ fDevBounds = &fDevBoundsStorage;
+ } else {
+ fDevBounds = NULL;
+ }
+
+ fDstCopy = di.fDstCopy;
+
+ return *this;
+}
+
+#if GR_DEBUG
+bool GrDrawTarget::DrawInfo::isInstanced() const {
+ if (fInstanceCount > 0) {
+ GrAssert(0 == fIndexCount % fIndicesPerInstance);
+ GrAssert(0 == fVertexCount % fVerticesPerInstance);
+ GrAssert(fIndexCount / fIndicesPerInstance == fInstanceCount);
+ GrAssert(fVertexCount / fVerticesPerInstance == fInstanceCount);
+ // there is no way to specify a non-zero start index to drawIndexedInstances().
+ GrAssert(0 == fStartIndex);
+ return true;
+ } else {
+ GrAssert(!fVerticesPerInstance);
+ GrAssert(!fIndicesPerInstance);
+ return false;
+ }
+}
+#endif
+
+void GrDrawTarget::DrawInfo::adjustInstanceCount(int instanceOffset) {
+ GrAssert(this->isInstanced());
+ GrAssert(instanceOffset + fInstanceCount >= 0);
+ fInstanceCount += instanceOffset;
+ fVertexCount = fVerticesPerInstance * fInstanceCount;
+ fIndexCount = fIndicesPerInstance * fInstanceCount;
+}
+
+void GrDrawTarget::DrawInfo::adjustStartVertex(int vertexOffset) {
+ fStartVertex += vertexOffset;
+ GrAssert(fStartVertex >= 0);
+}
+
+void GrDrawTarget::DrawInfo::adjustStartIndex(int indexOffset) {
+ GrAssert(this->isIndexed());
+ fStartIndex += indexOffset;
+ GrAssert(fStartIndex >= 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define DEBUG_INVAL_BUFFER 0xdeadcafe
+#define DEBUG_INVAL_START_IDX -1
+
+GrDrawTarget::GrDrawTarget(GrContext* context)
+ : fClip(NULL)
+ , fContext(context) {
+ GrAssert(NULL != context);
+
+ fDrawState = &fDefaultDrawState;
+ // We assume that fDrawState always owns a ref to the object it points at.
+ fDefaultDrawState.ref();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.push_back();
+#if GR_DEBUG
+ geoSrc.fVertexCount = DEBUG_INVAL_START_IDX;
+ geoSrc.fVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER;
+ geoSrc.fIndexCount = DEBUG_INVAL_START_IDX;
+ geoSrc.fIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER;
+#endif
+ geoSrc.fVertexSrc = kNone_GeometrySrcType;
+ geoSrc.fIndexSrc = kNone_GeometrySrcType;
+}
+
+GrDrawTarget::~GrDrawTarget() {
+ GrAssert(1 == fGeoSrcStateStack.count());
+ SkDEBUGCODE(GeometrySrcState& geoSrc = fGeoSrcStateStack.back());
+ GrAssert(kNone_GeometrySrcType == geoSrc.fIndexSrc);
+ GrAssert(kNone_GeometrySrcType == geoSrc.fVertexSrc);
+ fDrawState->unref();
+}
+
+void GrDrawTarget::releaseGeometry() {
+ int popCnt = fGeoSrcStateStack.count() - 1;
+ while (popCnt) {
+ this->popGeometrySource();
+ --popCnt;
+ }
+ this->resetVertexSource();
+ this->resetIndexSource();
+}
+
+void GrDrawTarget::setClip(const GrClipData* clip) {
+ clipWillBeSet(clip);
+ fClip = clip;
+}
+
+const GrClipData* GrDrawTarget::getClip() const {
+ return fClip;
+}
+
+void GrDrawTarget::setDrawState(GrDrawState* drawState) {
+ GrAssert(NULL != fDrawState);
+ if (NULL == drawState) {
+ drawState = &fDefaultDrawState;
+ }
+ if (fDrawState != drawState) {
+ fDrawState->unref();
+ drawState->ref();
+ fDrawState = drawState;
+ }
+}
+
+bool GrDrawTarget::reserveVertexSpace(size_t vertexSize,
+ int vertexCount,
+ void** vertices) {
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ bool acquired = false;
+ if (vertexCount > 0) {
+ GrAssert(NULL != vertices);
+ this->releasePreviousVertexSource();
+ geoSrc.fVertexSrc = kNone_GeometrySrcType;
+
+ acquired = this->onReserveVertexSpace(vertexSize,
+ vertexCount,
+ vertices);
+ }
+ if (acquired) {
+ geoSrc.fVertexSrc = kReserved_GeometrySrcType;
+ geoSrc.fVertexCount = vertexCount;
+ geoSrc.fVertexSize = vertexSize;
+ } else if (NULL != vertices) {
+ *vertices = NULL;
+ }
+ return acquired;
+}
+
+bool GrDrawTarget::reserveIndexSpace(int indexCount,
+ void** indices) {
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ bool acquired = false;
+ if (indexCount > 0) {
+ GrAssert(NULL != indices);
+ this->releasePreviousIndexSource();
+ geoSrc.fIndexSrc = kNone_GeometrySrcType;
+
+ acquired = this->onReserveIndexSpace(indexCount, indices);
+ }
+ if (acquired) {
+ geoSrc.fIndexSrc = kReserved_GeometrySrcType;
+ geoSrc.fIndexCount = indexCount;
+ } else if (NULL != indices) {
+ *indices = NULL;
+ }
+ return acquired;
+
+}
+
+bool GrDrawTarget::reserveVertexAndIndexSpace(int vertexCount,
+ int indexCount,
+ void** vertices,
+ void** indices) {
+ size_t vertexSize = this->drawState()->getVertexSize();
+ this->willReserveVertexAndIndexSpace(vertexCount, indexCount);
+ if (vertexCount) {
+ if (!this->reserveVertexSpace(vertexSize, vertexCount, vertices)) {
+ if (indexCount) {
+ this->resetIndexSource();
+ }
+ return false;
+ }
+ }
+ if (indexCount) {
+ if (!this->reserveIndexSpace(indexCount, indices)) {
+ if (vertexCount) {
+ this->resetVertexSource();
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GrDrawTarget::geometryHints(int32_t* vertexCount,
+ int32_t* indexCount) const {
+ if (NULL != vertexCount) {
+ *vertexCount = -1;
+ }
+ if (NULL != indexCount) {
+ *indexCount = -1;
+ }
+ return false;
+}
+
+void GrDrawTarget::releasePreviousVertexSource() {
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ switch (geoSrc.fVertexSrc) {
+ case kNone_GeometrySrcType:
+ break;
+ case kArray_GeometrySrcType:
+ this->releaseVertexArray();
+ break;
+ case kReserved_GeometrySrcType:
+ this->releaseReservedVertexSpace();
+ break;
+ case kBuffer_GeometrySrcType:
+ geoSrc.fVertexBuffer->unref();
+#if GR_DEBUG
+ geoSrc.fVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER;
+#endif
+ break;
+ default:
+ GrCrash("Unknown Vertex Source Type.");
+ break;
+ }
+}
+
+void GrDrawTarget::releasePreviousIndexSource() {
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ switch (geoSrc.fIndexSrc) {
+ case kNone_GeometrySrcType: // these two don't require
+ break;
+ case kArray_GeometrySrcType:
+ this->releaseIndexArray();
+ break;
+ case kReserved_GeometrySrcType:
+ this->releaseReservedIndexSpace();
+ break;
+ case kBuffer_GeometrySrcType:
+ geoSrc.fIndexBuffer->unref();
+#if GR_DEBUG
+ geoSrc.fIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER;
+#endif
+ break;
+ default:
+ GrCrash("Unknown Index Source Type.");
+ break;
+ }
+}
+
+void GrDrawTarget::setVertexSourceToArray(const void* vertexArray,
+ int vertexCount) {
+ this->releasePreviousVertexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fVertexSrc = kArray_GeometrySrcType;
+ geoSrc.fVertexSize = this->drawState()->getVertexSize();
+ geoSrc.fVertexCount = vertexCount;
+ this->onSetVertexSourceToArray(vertexArray, vertexCount);
+}
+
+void GrDrawTarget::setIndexSourceToArray(const void* indexArray,
+ int indexCount) {
+ this->releasePreviousIndexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fIndexSrc = kArray_GeometrySrcType;
+ geoSrc.fIndexCount = indexCount;
+ this->onSetIndexSourceToArray(indexArray, indexCount);
+}
+
+void GrDrawTarget::setVertexSourceToBuffer(const GrVertexBuffer* buffer) {
+ this->releasePreviousVertexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fVertexSrc = kBuffer_GeometrySrcType;
+ geoSrc.fVertexBuffer = buffer;
+ buffer->ref();
+ geoSrc.fVertexSize = this->drawState()->getVertexSize();
+}
+
+void GrDrawTarget::setIndexSourceToBuffer(const GrIndexBuffer* buffer) {
+ this->releasePreviousIndexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fIndexSrc = kBuffer_GeometrySrcType;
+ geoSrc.fIndexBuffer = buffer;
+ buffer->ref();
+}
+
+void GrDrawTarget::resetVertexSource() {
+ this->releasePreviousVertexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fVertexSrc = kNone_GeometrySrcType;
+}
+
+void GrDrawTarget::resetIndexSource() {
+ this->releasePreviousIndexSource();
+ GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ geoSrc.fIndexSrc = kNone_GeometrySrcType;
+}
+
+void GrDrawTarget::pushGeometrySource() {
+ this->geometrySourceWillPush();
+ GeometrySrcState& newState = fGeoSrcStateStack.push_back();
+ newState.fIndexSrc = kNone_GeometrySrcType;
+ newState.fVertexSrc = kNone_GeometrySrcType;
+#if GR_DEBUG
+ newState.fVertexCount = ~0;
+ newState.fVertexBuffer = (GrVertexBuffer*)~0;
+ newState.fIndexCount = ~0;
+ newState.fIndexBuffer = (GrIndexBuffer*)~0;
+#endif
+}
+
+void GrDrawTarget::popGeometrySource() {
+ // if popping last element then pops are unbalanced with pushes
+ GrAssert(fGeoSrcStateStack.count() > 1);
+
+ this->geometrySourceWillPop(fGeoSrcStateStack.fromBack(1));
+ this->releasePreviousVertexSource();
+ this->releasePreviousIndexSource();
+ fGeoSrcStateStack.pop_back();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrDrawTarget::checkDraw(GrPrimitiveType type, int startVertex,
+ int startIndex, int vertexCount,
+ int indexCount) const {
+ const GrDrawState& drawState = this->getDrawState();
+#if GR_DEBUG
+ const GeometrySrcState& geoSrc = fGeoSrcStateStack.back();
+ int maxVertex = startVertex + vertexCount;
+ int maxValidVertex;
+ switch (geoSrc.fVertexSrc) {
+ case kNone_GeometrySrcType:
+ GrCrash("Attempting to draw without vertex src.");
+ case kReserved_GeometrySrcType: // fallthrough
+ case kArray_GeometrySrcType:
+ maxValidVertex = geoSrc.fVertexCount;
+ break;
+ case kBuffer_GeometrySrcType:
+ maxValidVertex = geoSrc.fVertexBuffer->sizeInBytes() / geoSrc.fVertexSize;
+ break;
+ }
+ if (maxVertex > maxValidVertex) {
+ GrCrash("Drawing outside valid vertex range.");
+ }
+ if (indexCount > 0) {
+ int maxIndex = startIndex + indexCount;
+ int maxValidIndex;
+ switch (geoSrc.fIndexSrc) {
+ case kNone_GeometrySrcType:
+ GrCrash("Attempting to draw indexed geom without index src.");
+ case kReserved_GeometrySrcType: // fallthrough
+ case kArray_GeometrySrcType:
+ maxValidIndex = geoSrc.fIndexCount;
+ break;
+ case kBuffer_GeometrySrcType:
+ maxValidIndex = geoSrc.fIndexBuffer->sizeInBytes() / sizeof(uint16_t);
+ break;
+ }
+ if (maxIndex > maxValidIndex) {
+ GrCrash("Index reads outside valid index range.");
+ }
+ }
+
+ GrAssert(NULL != drawState.getRenderTarget());
+
+ for (int s = 0; s < drawState.numColorStages(); ++s) {
+ const GrEffectRef& effect = *drawState.getColorStage(s).getEffect();
+ int numTextures = effect->numTextures();
+ for (int t = 0; t < numTextures; ++t) {
+ GrTexture* texture = effect->texture(t);
+ GrAssert(texture->asRenderTarget() != drawState.getRenderTarget());
+ }
+ }
+ for (int s = 0; s < drawState.numCoverageStages(); ++s) {
+ const GrEffectRef& effect = *drawState.getCoverageStage(s).getEffect();
+ int numTextures = effect->numTextures();
+ for (int t = 0; t < numTextures; ++t) {
+ GrTexture* texture = effect->texture(t);
+ GrAssert(texture->asRenderTarget() != drawState.getRenderTarget());
+ }
+ }
+
+ GrAssert(drawState.validateVertexAttribs());
+#endif
+ if (NULL == drawState.getRenderTarget()) {
+ return false;
+ }
+ return true;
+}
+
+bool GrDrawTarget::setupDstReadIfNecessary(DrawInfo* info) {
+ if (this->caps()->dstReadInShaderSupport() || !this->getDrawState().willEffectReadDstColor()) {
+ return true;
+ }
+ GrRenderTarget* rt = this->drawState()->getRenderTarget();
+
+ const GrClipData* clip = this->getClip();
+ SkIRect copyRect;
+ clip->getConservativeBounds(this->getDrawState().getRenderTarget(), &copyRect);
+ SkIRect drawIBounds;
+ if (info->getDevIBounds(&drawIBounds)) {
+ if (!copyRect.intersect(drawIBounds)) {
+#if GR_DEBUG
+ GrPrintf("Missed an early reject. Bailing on draw from setupDstReadIfNecessary.\n");
+#endif
+ return false;
+ }
+ } else {
+#if GR_DEBUG
+ //GrPrintf("No dev bounds when dst copy is made.\n");
+#endif
+ }
+
+ // MSAA consideration: When there is support for reading MSAA samples in the shader we could
+ // have per-sample dst values by making the copy multisampled.
+ GrTextureDesc desc;
+ this->initCopySurfaceDstDesc(rt, &desc);
+ desc.fWidth = copyRect.width();
+ desc.fHeight = copyRect.height();
+
+ GrAutoScratchTexture ast(fContext, desc, GrContext::kApprox_ScratchTexMatch);
+
+ if (NULL == ast.texture()) {
+ GrPrintf("Failed to create temporary copy of destination texture.\n");
+ return false;
+ }
+ SkIPoint dstPoint = {0, 0};
+ if (this->copySurface(ast.texture(), rt, copyRect, dstPoint)) {
+ info->fDstCopy.setTexture(ast.texture());
+ info->fDstCopy.setOffset(copyRect.fLeft, copyRect.fTop);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void GrDrawTarget::drawIndexed(GrPrimitiveType type,
+ int startVertex,
+ int startIndex,
+ int vertexCount,
+ int indexCount,
+ const SkRect* devBounds) {
+ if (indexCount > 0 && this->checkDraw(type, startVertex, startIndex, vertexCount, indexCount)) {
+ DrawInfo info;
+ info.fPrimitiveType = type;
+ info.fStartVertex = startVertex;
+ info.fStartIndex = startIndex;
+ info.fVertexCount = vertexCount;
+ info.fIndexCount = indexCount;
+
+ info.fInstanceCount = 0;
+ info.fVerticesPerInstance = 0;
+ info.fIndicesPerInstance = 0;
+
+ if (NULL != devBounds) {
+ info.setDevBounds(*devBounds);
+ }
+ // TODO: We should continue with incorrect blending.
+ if (!this->setupDstReadIfNecessary(&info)) {
+ return;
+ }
+ this->onDraw(info);
+ }
+}
+
+void GrDrawTarget::drawNonIndexed(GrPrimitiveType type,
+ int startVertex,
+ int vertexCount,
+ const SkRect* devBounds) {
+ if (vertexCount > 0 && this->checkDraw(type, startVertex, -1, vertexCount, -1)) {
+ DrawInfo info;
+ info.fPrimitiveType = type;
+ info.fStartVertex = startVertex;
+ info.fStartIndex = 0;
+ info.fVertexCount = vertexCount;
+ info.fIndexCount = 0;
+
+ info.fInstanceCount = 0;
+ info.fVerticesPerInstance = 0;
+ info.fIndicesPerInstance = 0;
+
+ if (NULL != devBounds) {
+ info.setDevBounds(*devBounds);
+ }
+ // TODO: We should continue with incorrect blending.
+ if (!this->setupDstReadIfNecessary(&info)) {
+ return;
+ }
+ this->onDraw(info);
+ }
+}
+
+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(this->caps()->pathStencilingSupport());
+ GrAssert(!stroke.isHairlineStyle());
+ GrAssert(!SkPath::IsInverseFillType(fill));
+ this->onStencilPath(path, stroke, fill);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrDrawTarget::willUseHWAALines() const {
+ // There is a conflict between using smooth lines and our use of premultiplied alpha. Smooth
+ // lines tweak the incoming alpha value but not in a premul-alpha way. So we only use them when
+ // our alpha is 0xff and tweaking the color for partial coverage is OK
+ if (!this->caps()->hwAALineSupport() ||
+ !this->getDrawState().isHWAntialiasState()) {
+ return false;
+ }
+ GrDrawState::BlendOptFlags opts = this->getDrawState().getBlendOpts();
+ return (GrDrawState::kDisableBlend_BlendOptFlag & opts) &&
+ (GrDrawState::kCoverageAsAlpha_BlendOptFlag & opts);
+}
+
+bool GrDrawTarget::canApplyCoverage() const {
+ // we can correctly apply coverage if a) we have dual source blending
+ // or b) one of our blend optimizations applies.
+ return this->caps()->dualSourceBlendingSupport() ||
+ GrDrawState::kNone_BlendOpt != this->getDrawState().getBlendOpts(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrDrawTarget::drawIndexedInstances(GrPrimitiveType type,
+ int instanceCount,
+ int verticesPerInstance,
+ int indicesPerInstance,
+ const SkRect* devBounds) {
+ if (!verticesPerInstance || !indicesPerInstance) {
+ return;
+ }
+
+ int maxInstancesPerDraw = this->indexCountInCurrentSource() / indicesPerInstance;
+ if (!maxInstancesPerDraw) {
+ return;
+ }
+
+ DrawInfo info;
+ info.fPrimitiveType = type;
+ info.fStartIndex = 0;
+ info.fStartVertex = 0;
+ info.fIndicesPerInstance = indicesPerInstance;
+ info.fVerticesPerInstance = verticesPerInstance;
+
+ // Set the same bounds for all the draws.
+ if (NULL != devBounds) {
+ info.setDevBounds(*devBounds);
+ }
+ // TODO: We should continue with incorrect blending.
+ if (!this->setupDstReadIfNecessary(&info)) {
+ return;
+ }
+
+ while (instanceCount) {
+ info.fInstanceCount = GrMin(instanceCount, maxInstancesPerDraw);
+ info.fVertexCount = info.fInstanceCount * verticesPerInstance;
+ info.fIndexCount = info.fInstanceCount * indicesPerInstance;
+
+ if (this->checkDraw(type,
+ info.fStartVertex,
+ info.fStartIndex,
+ info.fVertexCount,
+ info.fIndexCount)) {
+ this->onDraw(info);
+ }
+ info.fStartVertex += info.fVertexCount;
+ instanceCount -= info.fInstanceCount;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// position + (optional) texture coord
+extern const GrVertexAttrib gBWRectPosUVAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding}
+};
+
+void set_vertex_attributes(GrDrawState* drawState, bool hasUVs) {
+ if (hasUVs) {
+ drawState->setVertexAttribs<gBWRectPosUVAttribs>(2);
+ } else {
+ drawState->setVertexAttribs<gBWRectPosUVAttribs>(1);
+ }
+}
+
+};
+
+void GrDrawTarget::onDrawRect(const SkRect& rect,
+ const SkMatrix* matrix,
+ const SkRect* localRect,
+ const SkMatrix* localMatrix) {
+
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (NULL != matrix) {
+ avmr.set(this->drawState(), *matrix);
+ }
+
+ set_vertex_attributes(this->drawState(), NULL != localRect);
+
+ AutoReleaseGeometry geo(this, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ size_t vsize = this->drawState()->getVertexSize();
+ geo.positions()->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, vsize);
+ if (NULL != localRect) {
+ GrPoint* coords = GrTCast<GrPoint*>(GrTCast<intptr_t>(geo.vertices()) +
+ sizeof(GrPoint));
+ coords->setRectFan(localRect->fLeft, localRect->fTop,
+ localRect->fRight, localRect->fBottom,
+ vsize);
+ if (NULL != localMatrix) {
+ localMatrix->mapPointsWithStride(coords, vsize, 4);
+ }
+ }
+ SkTLazy<SkRect> bounds;
+ if (this->getDrawState().willEffectReadDstColor()) {
+ bounds.init();
+ this->getDrawState().getViewMatrix().mapRect(bounds.get(), rect);
+ }
+
+ this->drawNonIndexed(kTriangleFan_GrPrimitiveType, 0, 4, bounds.getMaybeNull());
+}
+
+void GrDrawTarget::clipWillBeSet(const GrClipData* clipData) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrDrawTarget::AutoStateRestore::AutoStateRestore() {
+ fDrawTarget = NULL;
+}
+
+GrDrawTarget::AutoStateRestore::AutoStateRestore(GrDrawTarget* target,
+ ASRInit init,
+ const SkMatrix* vm) {
+ fDrawTarget = NULL;
+ this->set(target, init, vm);
+}
+
+GrDrawTarget::AutoStateRestore::~AutoStateRestore() {
+ if (NULL != fDrawTarget) {
+ fDrawTarget->setDrawState(fSavedState);
+ fSavedState->unref();
+ }
+}
+
+void GrDrawTarget::AutoStateRestore::set(GrDrawTarget* target, ASRInit init, const SkMatrix* vm) {
+ GrAssert(NULL == fDrawTarget);
+ fDrawTarget = target;
+ fSavedState = target->drawState();
+ GrAssert(fSavedState);
+ fSavedState->ref();
+ if (kReset_ASRInit == init) {
+ if (NULL == vm) {
+ // calls the default cons
+ fTempState.init();
+ } else {
+ SkNEW_IN_TLAZY(&fTempState, GrDrawState, (*vm));
+ }
+ } else {
+ GrAssert(kPreserve_ASRInit == init);
+ if (NULL == vm) {
+ fTempState.set(*fSavedState);
+ } else {
+ SkNEW_IN_TLAZY(&fTempState, GrDrawState, (*fSavedState, *vm));
+ }
+ }
+ target->setDrawState(fTempState.get());
+}
+
+bool GrDrawTarget::AutoStateRestore::setIdentity(GrDrawTarget* target, ASRInit init) {
+ GrAssert(NULL == fDrawTarget);
+ fDrawTarget = target;
+ fSavedState = target->drawState();
+ GrAssert(fSavedState);
+ fSavedState->ref();
+ if (kReset_ASRInit == init) {
+ // calls the default cons
+ fTempState.init();
+ } else {
+ GrAssert(kPreserve_ASRInit == init);
+ // calls the copy cons
+ fTempState.set(*fSavedState);
+ if (!fTempState.get()->setIdentityViewMatrix()) {
+ // let go of any resources held by the temp
+ fTempState.get()->reset();
+ fDrawTarget = NULL;
+ fSavedState->unref();
+ fSavedState = NULL;
+ return false;
+ }
+ }
+ target->setDrawState(fTempState.get());
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrDrawTarget::AutoReleaseGeometry::AutoReleaseGeometry(
+ GrDrawTarget* target,
+ int vertexCount,
+ int indexCount) {
+ fTarget = NULL;
+ this->set(target, vertexCount, indexCount);
+}
+
+GrDrawTarget::AutoReleaseGeometry::AutoReleaseGeometry() {
+ fTarget = NULL;
+}
+
+GrDrawTarget::AutoReleaseGeometry::~AutoReleaseGeometry() {
+ this->reset();
+}
+
+bool GrDrawTarget::AutoReleaseGeometry::set(GrDrawTarget* target,
+ int vertexCount,
+ int indexCount) {
+ this->reset();
+ fTarget = target;
+ bool success = true;
+ if (NULL != fTarget) {
+ fTarget = target;
+ success = target->reserveVertexAndIndexSpace(vertexCount,
+ indexCount,
+ &fVertices,
+ &fIndices);
+ if (!success) {
+ fTarget = NULL;
+ this->reset();
+ }
+ }
+ GrAssert(success == (NULL != fTarget));
+ return success;
+}
+
+void GrDrawTarget::AutoReleaseGeometry::reset() {
+ if (NULL != fTarget) {
+ if (NULL != fVertices) {
+ fTarget->resetVertexSource();
+ }
+ if (NULL != fIndices) {
+ fTarget->resetIndexSource();
+ }
+ fTarget = NULL;
+ }
+ fVertices = NULL;
+ 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);
+}
+
+namespace {
+// returns true if the read/written rect intersects the src/dst and false if not.
+bool clip_srcrect_and_dstpoint(const GrSurface* dst,
+ const GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint,
+ SkIRect* clippedSrcRect,
+ SkIPoint* clippedDstPoint) {
+ *clippedSrcRect = srcRect;
+ *clippedDstPoint = dstPoint;
+
+ // clip the left edge to src and dst bounds, adjusting dstPoint if necessary
+ if (clippedSrcRect->fLeft < 0) {
+ clippedDstPoint->fX -= clippedSrcRect->fLeft;
+ clippedSrcRect->fLeft = 0;
+ }
+ if (clippedDstPoint->fX < 0) {
+ clippedSrcRect->fLeft -= clippedDstPoint->fX;
+ clippedDstPoint->fX = 0;
+ }
+
+ // clip the top edge to src and dst bounds, adjusting dstPoint if necessary
+ if (clippedSrcRect->fTop < 0) {
+ clippedDstPoint->fY -= clippedSrcRect->fTop;
+ clippedSrcRect->fTop = 0;
+ }
+ if (clippedDstPoint->fY < 0) {
+ clippedSrcRect->fTop -= clippedDstPoint->fY;
+ clippedDstPoint->fY = 0;
+ }
+
+ // clip the right edge to the src and dst bounds.
+ if (clippedSrcRect->fRight > src->width()) {
+ clippedSrcRect->fRight = src->width();
+ }
+ if (clippedDstPoint->fX + clippedSrcRect->width() > dst->width()) {
+ clippedSrcRect->fRight = clippedSrcRect->fLeft + dst->width() - clippedDstPoint->fX;
+ }
+
+ // clip the bottom edge to the src and dst bounds.
+ if (clippedSrcRect->fBottom > src->height()) {
+ clippedSrcRect->fBottom = src->height();
+ }
+ if (clippedDstPoint->fY + clippedSrcRect->height() > dst->height()) {
+ clippedSrcRect->fBottom = clippedSrcRect->fTop + dst->height() - clippedDstPoint->fY;
+ }
+
+ // The above clipping steps may have inverted the rect if it didn't intersect either the src or
+ // dst bounds.
+ return !clippedSrcRect->isEmpty();
+}
+}
+
+bool GrDrawTarget::copySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ GrAssert(NULL != dst);
+ GrAssert(NULL != src);
+
+ SkIRect clippedSrcRect;
+ SkIPoint clippedDstPoint;
+ // If the rect is outside the src or dst then we've already succeeded.
+ if (!clip_srcrect_and_dstpoint(dst,
+ src,
+ srcRect,
+ dstPoint,
+ &clippedSrcRect,
+ &clippedDstPoint)) {
+ GrAssert(this->canCopySurface(dst, src, srcRect, dstPoint));
+ return true;
+ }
+
+ bool result = this->onCopySurface(dst, src, clippedSrcRect, clippedDstPoint);
+ GrAssert(result == this->canCopySurface(dst, src, clippedSrcRect, clippedDstPoint));
+ return result;
+}
+
+bool GrDrawTarget::canCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ GrAssert(NULL != dst);
+ GrAssert(NULL != src);
+
+ SkIRect clippedSrcRect;
+ SkIPoint clippedDstPoint;
+ // If the rect is outside the src or dst then we're guaranteed success
+ if (!clip_srcrect_and_dstpoint(dst,
+ src,
+ srcRect,
+ dstPoint,
+ &clippedSrcRect,
+ &clippedDstPoint)) {
+ return true;
+ }
+ return this->onCanCopySurface(dst, src, clippedSrcRect, clippedDstPoint);
+}
+
+bool GrDrawTarget::onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ // Check that the read/write rects are contained within the src/dst bounds.
+ GrAssert(!srcRect.isEmpty());
+ GrAssert(SkIRect::MakeWH(src->width(), src->height()).contains(srcRect));
+ GrAssert(dstPoint.fX >= 0 && dstPoint.fY >= 0);
+ GrAssert(dstPoint.fX + srcRect.width() <= dst->width() &&
+ dstPoint.fY + srcRect.height() <= dst->height());
+
+ return !dst->isSameAs(src) && NULL != dst->asRenderTarget() && NULL != src->asTexture();
+}
+
+bool GrDrawTarget::onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ if (!GrDrawTarget::onCanCopySurface(dst, src, srcRect, dstPoint)) {
+ return false;
+ }
+
+ GrRenderTarget* rt = dst->asRenderTarget();
+ GrTexture* tex = src->asTexture();
+
+ GrDrawTarget::AutoStateRestore asr(this, kReset_ASRInit);
+ this->drawState()->setRenderTarget(rt);
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(srcRect.fLeft - dstPoint.fX),
+ SkIntToScalar(srcRect.fTop - dstPoint.fY));
+ matrix.postIDiv(tex->width(), tex->height());
+ this->drawState()->addColorTextureEffect(tex, matrix);
+ SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX,
+ dstPoint.fY,
+ srcRect.width(),
+ srcRect.height());
+ this->drawSimpleRect(dstRect);
+ return true;
+}
+
+void GrDrawTarget::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+ // Make the dst of the copy be a render target because the default copySurface draws to the dst.
+ desc->fOrigin = kDefault_GrSurfaceOrigin;
+ desc->fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc->fConfig = src->config();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_INST_COUNT(GrDrawTargetCaps)
+
+void GrDrawTargetCaps::reset() {
+ f8BitPaletteSupport = false;
+ fNPOTTextureTileSupport = false;
+ fTwoSidedStencilSupport = false;
+ fStencilWrapOpsSupport = false;
+ fHWAALineSupport = false;
+ fShaderDerivativeSupport = false;
+ fGeometryShaderSupport = false;
+ fDualSourceBlendingSupport = false;
+ fBufferLockSupport = false;
+ fPathStencilingSupport = false;
+ fDstReadInShaderSupport = false;
+ fReuseScratchTextures = true;
+
+ fMaxRenderTargetSize = 0;
+ fMaxTextureSize = 0;
+ fMaxSampleCount = 0;
+}
+
+GrDrawTargetCaps& GrDrawTargetCaps::operator=(const GrDrawTargetCaps& other) {
+ f8BitPaletteSupport = other.f8BitPaletteSupport;
+ fNPOTTextureTileSupport = other.fNPOTTextureTileSupport;
+ fTwoSidedStencilSupport = other.fTwoSidedStencilSupport;
+ fStencilWrapOpsSupport = other.fStencilWrapOpsSupport;
+ fHWAALineSupport = other.fHWAALineSupport;
+ fShaderDerivativeSupport = other.fShaderDerivativeSupport;
+ fGeometryShaderSupport = other.fGeometryShaderSupport;
+ fDualSourceBlendingSupport = other.fDualSourceBlendingSupport;
+ fBufferLockSupport = other.fBufferLockSupport;
+ fPathStencilingSupport = other.fPathStencilingSupport;
+ fDstReadInShaderSupport = other.fDstReadInShaderSupport;
+ fReuseScratchTextures = other.fReuseScratchTextures;
+
+ fMaxRenderTargetSize = other.fMaxRenderTargetSize;
+ fMaxTextureSize = other.fMaxTextureSize;
+ fMaxSampleCount = other.fMaxSampleCount;
+
+ return *this;
+}
+
+void GrDrawTargetCaps::print() const {
+ static const char* gNY[] = {"NO", "YES"};
+ GrPrintf("8 Bit Palette Support : %s\n", gNY[f8BitPaletteSupport]);
+ GrPrintf("NPOT Texture Tile Support : %s\n", gNY[fNPOTTextureTileSupport]);
+ GrPrintf("Two Sided Stencil Support : %s\n", gNY[fTwoSidedStencilSupport]);
+ GrPrintf("Stencil Wrap Ops Support : %s\n", gNY[fStencilWrapOpsSupport]);
+ GrPrintf("HW AA Lines Support : %s\n", gNY[fHWAALineSupport]);
+ GrPrintf("Shader Derivative Support : %s\n", gNY[fShaderDerivativeSupport]);
+ GrPrintf("Geometry Shader Support : %s\n", gNY[fGeometryShaderSupport]);
+ GrPrintf("Dual Source Blending Support: %s\n", gNY[fDualSourceBlendingSupport]);
+ GrPrintf("Buffer Lock Support : %s\n", gNY[fBufferLockSupport]);
+ GrPrintf("Path Stenciling Support : %s\n", gNY[fPathStencilingSupport]);
+ GrPrintf("Dst Read In Shader Support : %s\n", gNY[fDstReadInShaderSupport]);
+ GrPrintf("Reuse Scratch Textures : %s\n", gNY[fReuseScratchTextures]);
+ GrPrintf("Max Texture Size : %d\n", fMaxTextureSize);
+ GrPrintf("Max Render Target Size : %d\n", fMaxRenderTargetSize);
+ GrPrintf("Max Sample Count : %d\n", fMaxSampleCount);
+}
diff --git a/gpu/GrDrawTarget.h b/gpu/GrDrawTarget.h
new file mode 100644
index 00000000..0c2b568d
--- /dev/null
+++ b/gpu/GrDrawTarget.h
@@ -0,0 +1,855 @@
+
+/*
+ * 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 GrDrawTarget_DEFINED
+#define GrDrawTarget_DEFINED
+
+#include "GrClipData.h"
+#include "GrDrawState.h"
+#include "GrIndexBuffer.h"
+#include "SkMatrix.h"
+#include "GrRefCnt.h"
+
+#include "SkClipStack.h"
+#include "SkPath.h"
+#include "SkTLazy.h"
+#include "SkTArray.h"
+#include "SkXfermode.h"
+
+class GrClipData;
+class GrDrawTargetCaps;
+class GrPath;
+class GrVertexBuffer;
+class SkStrokeRec;
+
+class GrDrawTarget : public GrRefCnt {
+protected:
+ class DrawInfo;
+
+public:
+ SK_DECLARE_INST_COUNT(GrDrawTarget)
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ // The context may not be fully constructed and should not be used during GrDrawTarget
+ // construction.
+ GrDrawTarget(GrContext* context);
+ virtual ~GrDrawTarget();
+
+ /**
+ * Gets the capabilities of the draw target.
+ */
+ const GrDrawTargetCaps* caps() const { return fCaps.get(); }
+
+ /**
+ * Sets the current clip to the region specified by clip. All draws will be
+ * clipped against this clip if kClip_StateBit is enabled.
+ *
+ * Setting the clip may (or may not) zero out the client's stencil bits.
+ *
+ * @param description of the clipping region
+ */
+ void setClip(const GrClipData* clip);
+
+ /**
+ * Gets the current clip.
+ *
+ * @return the clip.
+ */
+ const GrClipData* getClip() const;
+
+ /**
+ * Sets the draw state object for the draw target. Note that this does not
+ * make a copy. The GrDrawTarget will take a reference to passed object.
+ * Passing NULL will cause the GrDrawTarget to use its own internal draw
+ * state object rather than an externally provided one.
+ */
+ void setDrawState(GrDrawState* drawState);
+
+ /**
+ * Read-only access to the GrDrawTarget's current draw state.
+ */
+ const GrDrawState& getDrawState() const { return *fDrawState; }
+
+ /**
+ * Read-write access to the GrDrawTarget's current draw state. Note that
+ * this doesn't ref.
+ */
+ GrDrawState* drawState() { return fDrawState; }
+
+ /**
+ * Color alpha and coverage are two inputs to the drawing pipeline. For some
+ * blend modes it is safe to fold the coverage into constant or per-vertex
+ * color alpha value. For other blend modes they must be handled separately.
+ * Depending on features available in the underlying 3D API this may or may
+ * not be possible.
+ *
+ * This function considers the current draw state and the draw target's
+ * capabilities to determine whether coverage can be handled correctly. The
+ * following assumptions are made:
+ * 1. The caller intends to somehow specify coverage. This can be
+ * specified either by enabling a coverage stage on the GrDrawState or
+ * via the vertex layout.
+ * 2. Other than enabling coverage stages or enabling coverage in the
+ * layout, the current configuration of the target's GrDrawState is as
+ * it will be at draw time.
+ */
+ bool canApplyCoverage() const;
+
+ /**
+ * Given the current draw state and hw support, will HW AA lines be used (if
+ * a line primitive type is drawn)?
+ */
+ bool willUseHWAALines() const;
+
+ /**
+ * There are three types of "sources" of geometry (vertices and indices) for
+ * draw calls made on the target. When performing an indexed draw, the
+ * indices and vertices can use different source types. Once a source is
+ * specified it can be used for multiple draws. However, the time at which
+ * the geometry data is no longer editable depends on the source type.
+ *
+ * Sometimes it is necessary to perform a draw while upstack code has
+ * already specified geometry that it isn't finished with. So there are push
+ * and pop methods. This allows the client to push the sources, draw
+ * something using alternate sources, and then pop to restore the original
+ * sources.
+ *
+ * Aside from pushes and pops, a source remains valid until another source
+ * is set or resetVertexSource / resetIndexSource is called. Drawing from
+ * a reset source is an error.
+ *
+ * The three types of sources are:
+ *
+ * 1. A cpu array (set*SourceToArray). This is useful when the caller
+ * already provided vertex data in a format compatible with a
+ * GrVertexLayout. The data in the array is consumed at the time that
+ * set*SourceToArray is called and subsequent edits to the array will not
+ * be reflected in draws.
+ *
+ * 2. Reserve. This is most useful when the caller has data it must
+ * transform before drawing and is not long-lived. The caller requests
+ * that the draw target make room for some amount of vertex and/or index
+ * data. The target provides ptrs to hold the vertex and/or index data.
+ *
+ * The data is writable up until the next drawIndexed, drawNonIndexed,
+ * drawIndexedInstances, drawRect, copySurface, or pushGeometrySource. At
+ * this point the data is frozen and the ptrs are no longer valid.
+ *
+ * Where the space is allocated and how it is uploaded to the GPU is
+ * subclass-dependent.
+ *
+ * 3. Vertex and Index Buffers. This is most useful for geometry that will
+ * is long-lived. When the data in the buffer is consumed depends on the
+ * GrDrawTarget subclass. For deferred subclasses the caller has to
+ * guarantee that the data is still available in the buffers at playback.
+ * (TODO: Make this more automatic as we have done for read/write pixels)
+ *
+ * The size of each vertex is determined by querying the current GrDrawState.
+ */
+
+ /**
+ * Reserves space for vertices and/or indices. Zero can be specifed as
+ * either the vertex or index count if the caller desires to only reserve
+ * space for only indices or only vertices. If zero is specifed for
+ * vertexCount then the vertex source will be unmodified and likewise for
+ * indexCount.
+ *
+ * If the function returns true then the reserve suceeded and the vertices
+ * and indices pointers will point to the space created.
+ *
+ * If the target cannot make space for the request then this function will
+ * return false. If vertexCount was non-zero then upon failure the vertex
+ * source is reset and likewise for indexCount.
+ *
+ * The pointers to the space allocated for vertices and indices remain valid
+ * until a drawIndexed, drawNonIndexed, drawIndexedInstances, drawRect,
+ * copySurface, or push/popGeomtrySource is called. At that point logically a
+ * snapshot of the data is made and the pointers are invalid.
+ *
+ * @param vertexCount the number of vertices to reserve space for. Can be
+ * 0. Vertex size is queried from the current GrDrawState.
+ * @param indexCount the number of indices to reserve space for. Can be 0.
+ * @param vertices will point to reserved vertex space if vertexCount is
+ * non-zero. Illegal to pass NULL if vertexCount > 0.
+ * @param indices will point to reserved index space if indexCount is
+ * non-zero. Illegal to pass NULL if indexCount > 0.
+ */
+ bool reserveVertexAndIndexSpace(int vertexCount,
+ int indexCount,
+ void** vertices,
+ void** indices);
+
+ /**
+ * Provides hints to caller about the number of vertices and indices
+ * that can be allocated cheaply. This can be useful if caller is reserving
+ * space but doesn't know exactly how much geometry is needed.
+ *
+ * Also may hint whether the draw target should be flushed first. This is
+ * useful for deferred targets.
+ *
+ * @param vertexCount in: hint about how many vertices the caller would
+ * like to allocate. Vertex size is queried from the
+ * current GrDrawState.
+ * out: a hint about the number of vertices that can be
+ * allocated cheaply. Negative means no hint.
+ * Ignored if NULL.
+ * @param indexCount in: hint about how many indices the caller would
+ * like to allocate.
+ * out: a hint about the number of indices that can be
+ * allocated cheaply. Negative means no hint.
+ * Ignored if NULL.
+ *
+ * @return true if target should be flushed based on the input values.
+ */
+ virtual bool geometryHints(int* vertexCount,
+ int* indexCount) const;
+
+ /**
+ * Sets source of vertex data for the next draw. Array must contain
+ * the vertex data when this is called.
+ *
+ * @param vertexArray cpu array containing vertex data.
+ * @param vertexCount the number of vertices in the array. Vertex size is
+ * queried from the current GrDrawState.
+ */
+ void setVertexSourceToArray(const void* vertexArray, int vertexCount);
+
+ /**
+ * Sets source of index data for the next indexed draw. Array must contain
+ * the indices when this is called.
+ *
+ * @param indexArray cpu array containing index data.
+ * @param indexCount the number of indices in the array.
+ */
+ void setIndexSourceToArray(const void* indexArray, int indexCount);
+
+ /**
+ * Sets source of vertex data for the next draw. Data does not have to be
+ * in the buffer until drawIndexed, drawNonIndexed, or drawIndexedInstances.
+ *
+ * @param buffer vertex buffer containing vertex data. Must be
+ * unlocked before draw call. Vertex size is queried
+ * from current GrDrawState.
+ */
+ void setVertexSourceToBuffer(const GrVertexBuffer* buffer);
+
+ /**
+ * Sets source of index data for the next indexed draw. Data does not have
+ * to be in the buffer until drawIndexed.
+ *
+ * @param buffer index buffer containing indices. Must be unlocked
+ * before indexed draw call.
+ */
+ void setIndexSourceToBuffer(const GrIndexBuffer* buffer);
+
+ /**
+ * Resets vertex source. Drawing from reset vertices is illegal. Set vertex
+ * source to reserved, array, or buffer before next draw. May be able to free
+ * up temporary storage allocated by setVertexSourceToArray or
+ * reserveVertexSpace.
+ */
+ void resetVertexSource();
+
+ /**
+ * Resets index source. Indexed Drawing from reset indices is illegal. Set
+ * index source to reserved, array, or buffer before next indexed draw. May
+ * be able to free up temporary storage allocated by setIndexSourceToArray
+ * or reserveIndexSpace.
+ */
+ void resetIndexSource();
+
+ /**
+ * Query to find out if the vertex or index source is reserved.
+ */
+ bool hasReservedVerticesOrIndices() const {
+ return kReserved_GeometrySrcType == this->getGeomSrc().fVertexSrc ||
+ kReserved_GeometrySrcType == this->getGeomSrc().fIndexSrc;
+ }
+
+ /**
+ * Pushes and resets the vertex/index sources. Any reserved vertex / index
+ * data is finalized (i.e. cannot be updated after the matching pop but can
+ * be drawn from). Must be balanced by a pop.
+ */
+ void pushGeometrySource();
+
+ /**
+ * Pops the vertex / index sources from the matching push.
+ */
+ void popGeometrySource();
+
+ /**
+ * Draws indexed geometry using the current state and current vertex / index
+ * sources.
+ *
+ * @param type The type of primitives to draw.
+ * @param startVertex the vertex in the vertex array/buffer corresponding
+ * to index 0
+ * @param startIndex first index to read from index src.
+ * @param vertexCount one greater than the max index.
+ * @param indexCount the number of index elements to read. The index count
+ * is effectively trimmed to the last completely
+ * specified primitive.
+ * @param devBounds optional bounds hint. This is a promise from the caller,
+ * not a request for clipping.
+ */
+ void drawIndexed(GrPrimitiveType type,
+ int startVertex,
+ int startIndex,
+ int vertexCount,
+ int indexCount,
+ const SkRect* devBounds = NULL);
+
+ /**
+ * Draws non-indexed geometry using the current state and current vertex
+ * sources.
+ *
+ * @param type The type of primitives to draw.
+ * @param startVertex the vertex in the vertex array/buffer corresponding
+ * to index 0
+ * @param vertexCount one greater than the max index.
+ * @param devBounds optional bounds hint. This is a promise from the caller,
+ * not a request for clipping.
+ */
+ void drawNonIndexed(GrPrimitiveType type,
+ int startVertex,
+ int vertexCount,
+ const SkRect* devBounds = NULL);
+
+ /**
+ * Draws path into the stencil buffer. The fill must be either even/odd or
+ * 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*, const SkStrokeRec& stroke, SkPath::FillType fill);
+
+ /**
+ * Helper function for drawing rects. It performs a geometry src push and pop
+ * and thus will finalize any reserved geometry.
+ *
+ * @param rect the rect to draw
+ * @param matrix optional matrix applied to rect (before viewMatrix)
+ * @param localRect optional rect that specifies local coords to map onto
+ * rect. If NULL then rect serves as the local coords.
+ * @param localMatrix optional matrix applied to localRect. If
+ * srcRect is non-NULL and srcMatrix is non-NULL
+ * then srcRect will be transformed by srcMatrix.
+ * srcMatrix can be NULL when no srcMatrix is desired.
+ */
+ void drawRect(const SkRect& rect,
+ const SkMatrix* matrix,
+ const SkRect* localRect,
+ const SkMatrix* localMatrix) {
+ AutoGeometryPush agp(this);
+ this->onDrawRect(rect, matrix, localRect, localMatrix);
+ }
+
+ /**
+ * Helper for drawRect when the caller doesn't need separate local rects or matrices.
+ */
+ void drawSimpleRect(const SkRect& rect, const SkMatrix* matrix = NULL) {
+ this->drawRect(rect, matrix, NULL, NULL);
+ }
+ void drawSimpleRect(const SkIRect& 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
+ * given number of vertices (V) and indices (I) per-instance. The indices in
+ * the index source must have the form i[k+I] == i[k] + V. Also, all indices
+ * i[kI] ... i[(k+1)I-1] must be elements of the range kV ... (k+1)V-1. As a
+ * concrete example, the following index buffer for drawing a series of
+ * quads each as two triangles each satisfies these conditions with V=4 and
+ * I=6:
+ * (0,1,2,0,2,3, 4,5,6,4,6,7, 8,9,10,8,10,11, ...)
+ *
+ * The call assumes that the pattern of indices fills the entire index
+ * source. The size of the index buffer limits the number of instances that
+ * can be drawn by the GPU in a single draw. However, the caller may specify
+ * any (positive) number for instanceCount and if necessary multiple GPU
+ * draws will be issued. Moreover, when drawIndexedInstances is called
+ * multiple times it may be possible for GrDrawTarget to group them into a
+ * single GPU draw.
+ *
+ * @param type the type of primitives to draw
+ * @param instanceCount the number of instances to draw. Each instance
+ * consists of verticesPerInstance vertices indexed by
+ * indicesPerInstance indices drawn as the primitive
+ * type specified by type.
+ * @param verticesPerInstance The number of vertices in each instance (V
+ * in the above description).
+ * @param indicesPerInstance The number of indices in each instance (I
+ * in the above description).
+ * @param devBounds optional bounds hint. This is a promise from the caller,
+ * not a request for clipping.
+ */
+ void drawIndexedInstances(GrPrimitiveType type,
+ int instanceCount,
+ int verticesPerInstance,
+ int indicesPerInstance,
+ const SkRect* devBounds = 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.
+ */
+ virtual void clear(const SkIRect* rect,
+ GrColor color,
+ GrRenderTarget* renderTarget = NULL) = 0;
+
+ /**
+ * Copies a pixel rectangle from one surface to another. This call may finalize
+ * reserved vertex/index data (as though a draw call was made). The src pixels
+ * copied are specified by srcRect. They are copied to a rect of the same
+ * size in dst with top left at dstPoint. If the src rect is clipped by the
+ * src bounds then pixel values in the dst rect corresponding to area clipped
+ * by the src rect are not overwritten. This method can fail and return false
+ * depending on the type of surface, configs, etc, and the backend-specific
+ * limitations. If rect is clipped out entirely by the src or dst bounds then
+ * true is returned since there is no actual copy necessary to succeed.
+ */
+ bool copySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint);
+ /**
+ * Function that determines whether a copySurface call would succeed without
+ * performing the copy.
+ */
+ bool canCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint);
+
+ /**
+ * This is can be called before allocating a texture to be a dst for copySurface. It will
+ * populate the origin, config, and flags fields of the desc such that copySurface is more
+ * likely to succeed and be efficient.
+ */
+ virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc);
+
+
+ /**
+ * Release any resources that are cached but not currently in use. This
+ * is intended to give an application some recourse when resources are low.
+ */
+ virtual void purgeResources() {};
+
+ /**
+ * For subclass internal use to invoke a call to onDraw(). See DrawInfo below.
+ */
+ void executeDraw(const DrawInfo& info) { this->onDraw(info); }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * See AutoStateRestore below.
+ */
+ enum ASRInit {
+ kPreserve_ASRInit,
+ kReset_ASRInit
+ };
+
+ /**
+ * Saves off the current state and restores it in the destructor. It will
+ * install a new GrDrawState object on the target (setDrawState) and restore
+ * the previous one in the destructor. The caller should call drawState() to
+ * get the new draw state after the ASR is installed.
+ *
+ * GrDrawState* state = target->drawState();
+ * AutoStateRestore asr(target, GrDrawTarget::kReset_ASRInit).
+ * state->setRenderTarget(rt); // state refers to the GrDrawState set on
+ * // target before asr was initialized.
+ * // Therefore, rt is set on the GrDrawState
+ * // that will be restored after asr's
+ * // destructor rather than target's current
+ * // GrDrawState.
+ */
+ class AutoStateRestore : ::GrNoncopyable {
+ public:
+ /**
+ * Default ASR will have no effect unless set() is subsequently called.
+ */
+ AutoStateRestore();
+
+ /**
+ * Saves the state on target. The state will be restored when the ASR
+ * is destroyed. If this constructor is used do not call set().
+ *
+ * @param init Should the newly installed GrDrawState be a copy of the
+ * previous state or a default-initialized GrDrawState.
+ * @param viewMatrix Optional view matrix. If init = kPreserve then the draw state's
+ * matrix will be preconcat'ed with the param. All stages will be
+ updated to compensate for the matrix change. If init == kReset
+ then the draw state's matrix will be this matrix.
+ */
+ AutoStateRestore(GrDrawTarget* target, ASRInit init, const SkMatrix* viewMatrix = NULL);
+
+ ~AutoStateRestore();
+
+ /**
+ * Saves the state on target. The state will be restored when the ASR
+ * is destroyed. This should only be called once per ASR object and only
+ * when the default constructor was used. For nested saves use multiple
+ * ASR objects.
+ *
+ * @param init Should the newly installed GrDrawState be a copy of the
+ * previous state or a default-initialized GrDrawState.
+ * @param viewMatrix Optional view matrix. If init = kPreserve then the draw state's
+ * matrix will be preconcat'ed with the param. All stages will be
+ updated to compensate for the matrix change. If init == kReset
+ then the draw state's matrix will be this matrix.
+ */
+ void set(GrDrawTarget* target, ASRInit init, const SkMatrix* viewMatrix = NULL);
+
+ /**
+ * Like set() but makes the view matrix identity. When init is kReset it is as though
+ * NULL was passed to set's viewMatrix param. When init is kPreserve it is as though
+ * the inverse view matrix was passed. If kPreserve is passed and the draw state's matrix
+ * is not invertible then this may fail.
+ */
+ bool setIdentity(GrDrawTarget* target, ASRInit init);
+
+ private:
+ GrDrawTarget* fDrawTarget;
+ SkTLazy<GrDrawState> fTempState;
+ GrDrawState* fSavedState;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ class AutoReleaseGeometry : ::GrNoncopyable {
+ public:
+ AutoReleaseGeometry(GrDrawTarget* target,
+ int vertexCount,
+ int indexCount);
+ AutoReleaseGeometry();
+ ~AutoReleaseGeometry();
+ bool set(GrDrawTarget* target,
+ int vertexCount,
+ int indexCount);
+ bool succeeded() const { return NULL != fTarget; }
+ void* vertices() const { GrAssert(this->succeeded()); return fVertices; }
+ void* indices() const { GrAssert(this->succeeded()); return fIndices; }
+ GrPoint* positions() const {
+ return static_cast<GrPoint*>(this->vertices());
+ }
+
+ private:
+ void reset();
+
+ GrDrawTarget* fTarget;
+ void* fVertices;
+ void* fIndices;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ class AutoClipRestore : ::GrNoncopyable {
+ public:
+ AutoClipRestore(GrDrawTarget* target) {
+ fTarget = target;
+ fClip = fTarget->getClip();
+ }
+
+ AutoClipRestore(GrDrawTarget* target, const SkIRect& newClip);
+
+ ~AutoClipRestore() {
+ fTarget->setClip(fClip);
+ }
+ private:
+ GrDrawTarget* fTarget;
+ const GrClipData* fClip;
+ SkTLazy<SkClipStack> fStack;
+ GrClipData fReplacementClip;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Saves the geometry src state at construction and restores in the destructor. It also saves
+ * and then restores the vertex attrib state.
+ */
+ class AutoGeometryPush : ::GrNoncopyable {
+ public:
+ AutoGeometryPush(GrDrawTarget* target)
+ : fAttribRestore(target->drawState()) {
+ GrAssert(NULL != target);
+ fTarget = target;
+ target->pushGeometrySource();
+ }
+
+ ~AutoGeometryPush() { fTarget->popGeometrySource(); }
+
+ private:
+ GrDrawTarget* fTarget;
+ GrDrawState::AutoVertexAttribRestore fAttribRestore;
+ };
+
+ /**
+ * Combination of AutoGeometryPush and AutoStateRestore. The vertex attribs will be in default
+ * state regardless of ASRInit value.
+ */
+ class AutoGeometryAndStatePush : ::GrNoncopyable {
+ public:
+ AutoGeometryAndStatePush(GrDrawTarget* target,
+ ASRInit init,
+ const SkMatrix* viewMatrix = NULL)
+ : fState(target, init, viewMatrix) {
+ GrAssert(NULL != target);
+ fTarget = target;
+ target->pushGeometrySource();
+ if (kPreserve_ASRInit == init) {
+ target->drawState()->setDefaultVertexAttribs();
+ }
+ }
+
+ ~AutoGeometryAndStatePush() { fTarget->popGeometrySource(); }
+
+ private:
+ AutoStateRestore fState;
+ GrDrawTarget* fTarget;
+ };
+
+protected:
+
+ enum GeometrySrcType {
+ kNone_GeometrySrcType, //<! src has not been specified
+ kReserved_GeometrySrcType, //<! src was set using reserve*Space
+ kArray_GeometrySrcType, //<! src was set using set*SourceToArray
+ kBuffer_GeometrySrcType //<! src was set using set*SourceToBuffer
+ };
+
+ struct GeometrySrcState {
+ GeometrySrcType fVertexSrc;
+ union {
+ // valid if src type is buffer
+ const GrVertexBuffer* fVertexBuffer;
+ // valid if src type is reserved or array
+ int fVertexCount;
+ };
+
+ GeometrySrcType fIndexSrc;
+ union {
+ // valid if src type is buffer
+ const GrIndexBuffer* fIndexBuffer;
+ // valid if src type is reserved or array
+ int fIndexCount;
+ };
+
+ size_t fVertexSize;
+ };
+
+ int indexCountInCurrentSource() const {
+ const GeometrySrcState& src = this->getGeomSrc();
+ switch (src.fIndexSrc) {
+ case kNone_GeometrySrcType:
+ return 0;
+ case kReserved_GeometrySrcType:
+ case kArray_GeometrySrcType:
+ return src.fIndexCount;
+ case kBuffer_GeometrySrcType:
+ return src.fIndexBuffer->sizeInBytes() / sizeof(uint16_t);
+ default:
+ GrCrash("Unexpected Index Source.");
+ return 0;
+ }
+ }
+
+ // This method is called by copySurface The srcRect is guaranteed to be entirely within the
+ // src bounds. Likewise, the dst rect implied by dstPoint and srcRect's width and height falls
+ // entirely within the dst. The default implementation will draw a rect from the src to the
+ // dst if the src is a texture and the dst is a render target and fail otherwise.
+ virtual bool onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint);
+
+ // Called to determine whether an onCopySurface call would succeed or not. This is useful for
+ // proxy subclasses to test whether the copy would succeed without executing it yet. Derived
+ // classes must keep this consistent with their implementation of onCopySurface(). The inputs
+ // are the same as onCopySurface(), i.e. srcRect and dstPoint are clipped to be inside the src
+ // and dst bounds.
+ virtual bool onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint);
+
+ GrContext* getContext() { return fContext; }
+ const GrContext* getContext() const { return fContext; }
+
+ // A subclass may override this function if it wishes to be notified when the clip is changed.
+ // The override should call INHERITED::clipWillBeSet().
+ virtual void clipWillBeSet(const GrClipData* clipData);
+
+ // subclasses must call this in their destructors to ensure all vertex
+ // and index sources have been released (including those held by
+ // pushGeometrySource())
+ void releaseGeometry();
+
+ // accessors for derived classes
+ const GeometrySrcState& getGeomSrc() const { return fGeoSrcStateStack.back(); }
+ // it is preferable to call this rather than getGeomSrc()->fVertexSize because of the assert.
+ size_t getVertexSize() const {
+ // the vertex layout is only valid if a vertex source has been specified.
+ GrAssert(this->getGeomSrc().fVertexSrc != kNone_GeometrySrcType);
+ return this->getGeomSrc().fVertexSize;
+ }
+
+ // Subclass must initialize this in its constructor.
+ SkAutoTUnref<const GrDrawTargetCaps> fCaps;
+
+ /**
+ * Used to communicate draws to subclass's onDraw function.
+ */
+ class DrawInfo {
+ public:
+ DrawInfo(const DrawInfo& di) { (*this) = di; }
+ DrawInfo& operator =(const DrawInfo& di);
+
+ GrPrimitiveType primitiveType() const { return fPrimitiveType; }
+ int startVertex() const { return fStartVertex; }
+ int startIndex() const { return fStartIndex; }
+ int vertexCount() const { return fVertexCount; }
+ int indexCount() const { return fIndexCount; }
+ int verticesPerInstance() const { return fVerticesPerInstance; }
+ int indicesPerInstance() const { return fIndicesPerInstance; }
+ int instanceCount() const { return fInstanceCount; }
+
+ bool isIndexed() const { return fIndexCount > 0; }
+#if GR_DEBUG
+ bool isInstanced() const; // this version is longer because of asserts
+#else
+ bool isInstanced() const { return fInstanceCount > 0; }
+#endif
+
+ // adds or remove instances
+ void adjustInstanceCount(int instanceOffset);
+ // shifts the start vertex
+ void adjustStartVertex(int vertexOffset);
+ // shifts the start index
+ void adjustStartIndex(int indexOffset);
+
+ void setDevBounds(const SkRect& bounds) {
+ fDevBoundsStorage = bounds;
+ fDevBounds = &fDevBoundsStorage;
+ }
+ const SkRect* getDevBounds() const { return fDevBounds; }
+
+ bool getDevIBounds(SkIRect* bounds) const {
+ if (NULL != fDevBounds) {
+ fDevBounds->roundOut(bounds);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // NULL if no copy of the dst is needed for the draw.
+ const GrDeviceCoordTexture* getDstCopy() const {
+ if (NULL != fDstCopy.texture()) {
+ return &fDstCopy;
+ } else {
+ return NULL;
+ }
+ }
+
+ private:
+ DrawInfo() { fDevBounds = NULL; }
+
+ friend class GrDrawTarget;
+
+ GrPrimitiveType fPrimitiveType;
+
+ int fStartVertex;
+ int fStartIndex;
+ int fVertexCount;
+ int fIndexCount;
+
+ int fInstanceCount;
+ int fVerticesPerInstance;
+ int fIndicesPerInstance;
+
+ SkRect fDevBoundsStorage;
+ SkRect* fDevBounds;
+
+ GrDeviceCoordTexture fDstCopy;
+ };
+
+private:
+ // A subclass can optionally overload this function to be notified before
+ // vertex and index space is reserved.
+ virtual void willReserveVertexAndIndexSpace(int vertexCount, int indexCount) {}
+
+ // implemented by subclass to allocate space for reserved geom
+ virtual bool onReserveVertexSpace(size_t vertexSize, int vertexCount, void** vertices) = 0;
+ virtual bool onReserveIndexSpace(int indexCount, void** indices) = 0;
+ // implemented by subclass to handle release of reserved geom space
+ virtual void releaseReservedVertexSpace() = 0;
+ virtual void releaseReservedIndexSpace() = 0;
+ // subclass must consume array contents when set
+ virtual void onSetVertexSourceToArray(const void* vertexArray, int vertexCount) = 0;
+ virtual void onSetIndexSourceToArray(const void* indexArray, int indexCount) = 0;
+ // subclass is notified that geom source will be set away from an array
+ virtual void releaseVertexArray() = 0;
+ virtual void releaseIndexArray() = 0;
+ // subclass overrides to be notified just before geo src state is pushed/popped.
+ virtual void geometrySourceWillPush() = 0;
+ virtual void geometrySourceWillPop(const GeometrySrcState& restoredState) = 0;
+ // subclass called to perform drawing
+ virtual void onDraw(const DrawInfo&) = 0;
+ // Implementation of drawRect. The geometry src and vertex attribs will already
+ // be saved before this is called and restored afterwards. A subclass may override
+ // this to perform more optimal rect rendering. Its draws should be funneled through
+ // one of the public GrDrawTarget draw methods (e.g. drawNonIndexed,
+ // drawIndexedInstances, ...). The base class draws a two triangle fan using
+ // drawNonIndexed from reserved vertex space.
+ virtual void onDrawRect(const SkRect& rect,
+ const SkMatrix* matrix,
+ const SkRect* localRect,
+ const SkMatrix* localMatrix);
+ virtual void onStencilPath(const GrPath*, const SkStrokeRec& stroke, SkPath::FillType fill) = 0;
+
+ // helpers for reserving vertex and index space.
+ bool reserveVertexSpace(size_t vertexSize,
+ int vertexCount,
+ void** vertices);
+ bool reserveIndexSpace(int indexCount, void** indices);
+
+ // called by drawIndexed and drawNonIndexed. Use a negative indexCount to
+ // indicate non-indexed drawing.
+ bool checkDraw(GrPrimitiveType type, int startVertex,
+ int startIndex, int vertexCount,
+ int indexCount) const;
+ // called when setting a new vert/idx source to unref prev vb/ib
+ void releasePreviousVertexSource();
+ void releasePreviousIndexSource();
+
+ // Makes a copy of the dst if it is necessary for the draw. Returns false if a copy is required
+ // but couldn't be made. Otherwise, returns true.
+ bool setupDstReadIfNecessary(DrawInfo* info);
+
+ enum {
+ kPreallocGeoSrcStateStackCnt = 4,
+ };
+ SkSTArray<kPreallocGeoSrcStateStackCnt, GeometrySrcState, true> fGeoSrcStateStack;
+ const GrClipData* fClip;
+ GrDrawState* fDrawState;
+ GrDrawState fDefaultDrawState;
+ // The context owns us, not vice-versa, so this ptr is not ref'ed by DrawTarget.
+ GrContext* fContext;
+
+ typedef GrRefCnt INHERITED;
+};
+
+#endif
diff --git a/gpu/GrDrawTargetCaps.h b/gpu/GrDrawTargetCaps.h
new file mode 100644
index 00000000..61c9fede
--- /dev/null
+++ b/gpu/GrDrawTargetCaps.h
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRefCnt.h"
+
+#ifndef GrDrawTargetCaps_DEFINED
+#define GrDrawTargetCaps_DEFINED
+
+/**
+ * Represents the draw target capabilities.
+ */
+class GrDrawTargetCaps : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(Caps)
+
+ GrDrawTargetCaps() { this->reset(); }
+ GrDrawTargetCaps(const GrDrawTargetCaps& other) : INHERITED() { *this = other; }
+ GrDrawTargetCaps& operator= (const GrDrawTargetCaps&);
+
+ virtual void reset();
+ virtual void print() const;
+
+ bool eightBitPaletteSupport() const { return f8BitPaletteSupport; }
+ bool npotTextureTileSupport() const { return fNPOTTextureTileSupport; }
+ bool twoSidedStencilSupport() const { return fTwoSidedStencilSupport; }
+ bool stencilWrapOpsSupport() const { return fStencilWrapOpsSupport; }
+ bool hwAALineSupport() const { return fHWAALineSupport; }
+ bool shaderDerivativeSupport() const { return fShaderDerivativeSupport; }
+ bool geometryShaderSupport() const { return fGeometryShaderSupport; }
+ bool dualSourceBlendingSupport() const { return fDualSourceBlendingSupport; }
+ bool bufferLockSupport() const { return fBufferLockSupport; }
+ bool pathStencilingSupport() const { return fPathStencilingSupport; }
+ bool dstReadInShaderSupport() const { return fDstReadInShaderSupport; }
+ bool reuseScratchTextures() const { return fReuseScratchTextures; }
+
+ int maxRenderTargetSize() const { return fMaxRenderTargetSize; }
+ int maxTextureSize() const { return fMaxTextureSize; }
+ // Will be 0 if MSAA is not supported
+ int maxSampleCount() const { return fMaxSampleCount; }
+
+protected:
+ bool f8BitPaletteSupport : 1;
+ bool fNPOTTextureTileSupport : 1;
+ bool fTwoSidedStencilSupport : 1;
+ bool fStencilWrapOpsSupport : 1;
+ bool fHWAALineSupport : 1;
+ bool fShaderDerivativeSupport : 1;
+ bool fGeometryShaderSupport : 1;
+ bool fDualSourceBlendingSupport : 1;
+ bool fBufferLockSupport : 1;
+ bool fPathStencilingSupport : 1;
+ bool fDstReadInShaderSupport : 1;
+ bool fReuseScratchTextures : 1;
+
+ int fMaxRenderTargetSize;
+ int fMaxTextureSize;
+ int fMaxSampleCount;
+
+ typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/gpu/GrEffect.cpp b/gpu/GrEffect.cpp
new file mode 100644
index 00000000..22b38a37
--- /dev/null
+++ b/gpu/GrEffect.cpp
@@ -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.
+ */
+
+#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(SkMWCRandom* 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;
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_INST_COUNT(GrEffectRef)
+
+GrEffectRef::~GrEffectRef() {
+ GrAssert(this->unique());
+ fEffect->EffectRefDestroyed();
+ fEffect->unref();
+}
+
+void* GrEffectRef::operator new(size_t size) {
+ return GrEffect_Globals::GetTLS()->allocate(size);
+}
+
+void GrEffectRef::operator delete(void* target) {
+ GrEffect_Globals::GetTLS()->release(target);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrEffect::~GrEffect() {
+ GrAssert(NULL == fEffectRef);
+}
+
+const char* GrEffect::name() const {
+ return this->getFactory().name();
+}
+
+void GrEffect::addTextureAccess(const GrTextureAccess* access) {
+ fTextureAccesses.push_back(access);
+}
+
+void GrEffect::addVertexAttrib(GrSLType type) {
+ GrAssert(fVertexAttribTypes.count() < kMaxVertexAttribs);
+ fVertexAttribTypes.push_back(type);
+}
+
+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/gpu/GrGeometryBuffer.cpp b/gpu/GrGeometryBuffer.cpp
new file mode 100644
index 00000000..202d0c33
--- /dev/null
+++ b/gpu/GrGeometryBuffer.cpp
@@ -0,0 +1,10 @@
+/*
+ * 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 "GrGeometryBuffer.h"
+
+SK_DEFINE_INST_COUNT(GrGeometryBuffer)
diff --git a/gpu/GrGeometryBuffer.h b/gpu/GrGeometryBuffer.h
new file mode 100644
index 00000000..3bb7118f
--- /dev/null
+++ b/gpu/GrGeometryBuffer.h
@@ -0,0 +1,103 @@
+
+/*
+ * 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 GrGeometryBuffer_DEFINED
+#define GrGeometryBuffer_DEFINED
+
+#include "GrResource.h"
+
+class GrGpu;
+
+/**
+ * Parent class for vertex and index buffers
+ */
+class GrGeometryBuffer : public GrResource {
+public:
+ SK_DECLARE_INST_COUNT(GrGeometryBuffer);
+
+ /**
+ *Retrieves whether the buffer was created with the dynamic flag
+ *
+ * @return true if the buffer was created with the dynamic flag
+ */
+ bool dynamic() const { return fDynamic; }
+
+ /**
+ * Returns true if the buffer is a wrapper around a CPU array. If true it
+ * indicates that lock will always succeed and will be free.
+ */
+ bool isCPUBacked() const { return fCPUBacked; }
+
+ /**
+ * Locks the buffer to be written by the CPU.
+ *
+ * The previous content of the buffer is invalidated. It is an error
+ * to draw from the buffer while it is locked. It is an error to call lock
+ * on an already locked buffer. It may fail if the backend doesn't support
+ * locking the buffer. If the buffer is CPU backed then it will always
+ * succeed and is a free operation. Must be matched by an unlock() call.
+ * Currently only one lock at a time is supported (no nesting of
+ * lock/unlock).
+ *
+ * @return a pointer to the data or NULL if the lock fails.
+ */
+ virtual void* lock() = 0;
+
+ /**
+ * Returns the same ptr that lock() returned at time of lock or NULL if the
+ * is not locked.
+ *
+ * @return ptr to locked buffer data or undefined if buffer is not locked.
+ */
+ virtual void* lockPtr() const = 0;
+
+ /**
+ * Unlocks the buffer.
+ *
+ * The pointer returned by the previous lock call will no longer be valid.
+ */
+ virtual void unlock() = 0;
+
+ /**
+ Queries whether the buffer has been locked.
+
+ @return true if the buffer is locked, false otherwise.
+ */
+ virtual bool isLocked() const = 0;
+
+ /**
+ * Updates the buffer data.
+ *
+ * The size of the buffer will be preserved. The src data will be
+ * placed at the beginning of the buffer and any remaining contents will
+ * be undefined.
+ *
+ * @return returns true if the update succeeds, false otherwise.
+ */
+ virtual bool updateData(const void* src, size_t srcSizeInBytes) = 0;
+
+ // GrResource overrides
+ virtual size_t sizeInBytes() const { return fSizeInBytes; }
+
+protected:
+ GrGeometryBuffer(GrGpu* gpu, bool isWrapped, size_t sizeInBytes, bool dynamic, bool cpuBacked)
+ : INHERITED(gpu, isWrapped)
+ , fSizeInBytes(sizeInBytes)
+ , fDynamic(dynamic)
+ , fCPUBacked(cpuBacked) {}
+
+private:
+ size_t fSizeInBytes;
+ bool fDynamic;
+ bool fCPUBacked;
+
+ typedef GrResource INHERITED;
+};
+
+#endif
diff --git a/gpu/GrGpu.cpp b/gpu/GrGpu.cpp
new file mode 100644
index 00000000..9564a50f
--- /dev/null
+++ b/gpu/GrGpu.cpp
@@ -0,0 +1,500 @@
+
+/*
+ * 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 "GrGpu.h"
+
+#include "GrBufferAllocPool.h"
+#include "GrContext.h"
+#include "GrDrawTargetCaps.h"
+#include "GrIndexBuffer.h"
+#include "GrStencilBuffer.h"
+#include "GrVertexBuffer.h"
+
+// probably makes no sense for this to be less than a page
+static const size_t VERTEX_POOL_VB_SIZE = 1 << 18;
+static const int VERTEX_POOL_VB_COUNT = 4;
+static const size_t INDEX_POOL_IB_SIZE = 1 << 16;
+static const int INDEX_POOL_IB_COUNT = 4;
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define DEBUG_INVAL_BUFFER 0xdeadcafe
+#define DEBUG_INVAL_START_IDX -1
+
+GrGpu::GrGpu(GrContext* context)
+ : GrDrawTarget(context)
+ , fResetTimestamp(kExpiredTimestamp+1)
+ , fResetBits(kAll_GrBackendState)
+ , fVertexPool(NULL)
+ , fIndexPool(NULL)
+ , fVertexPoolUseCnt(0)
+ , fIndexPoolUseCnt(0)
+ , fQuadIndexBuffer(NULL) {
+
+ fClipMaskManager.setGpu(this);
+
+ fGeomPoolStateStack.push_back();
+#if GR_DEBUG
+ GeometryPoolState& poolState = fGeomPoolStateStack.back();
+ poolState.fPoolVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER;
+ poolState.fPoolStartVertex = DEBUG_INVAL_START_IDX;
+ poolState.fPoolIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER;
+ poolState.fPoolStartIndex = DEBUG_INVAL_START_IDX;
+#endif
+
+ for (int i = 0; i < kGrPixelConfigCnt; ++i) {
+ fConfigRenderSupport[i] = false;
+ };
+}
+
+GrGpu::~GrGpu() {
+ this->releaseResources();
+}
+
+void GrGpu::abandonResources() {
+
+ fClipMaskManager.releaseResources();
+
+ while (NULL != fResourceList.head()) {
+ fResourceList.head()->abandon();
+ }
+
+ GrAssert(NULL == fQuadIndexBuffer || !fQuadIndexBuffer->isValid());
+ GrSafeSetNull(fQuadIndexBuffer);
+ delete fVertexPool;
+ fVertexPool = NULL;
+ delete fIndexPool;
+ fIndexPool = NULL;
+}
+
+void GrGpu::releaseResources() {
+
+ fClipMaskManager.releaseResources();
+
+ while (NULL != fResourceList.head()) {
+ fResourceList.head()->release();
+ }
+
+ GrAssert(NULL == fQuadIndexBuffer || !fQuadIndexBuffer->isValid());
+ GrSafeSetNull(fQuadIndexBuffer);
+ delete fVertexPool;
+ fVertexPool = NULL;
+ delete fIndexPool;
+ fIndexPool = NULL;
+}
+
+void GrGpu::insertResource(GrResource* resource) {
+ GrAssert(NULL != resource);
+ GrAssert(this == resource->getGpu());
+
+ fResourceList.addToHead(resource);
+}
+
+void GrGpu::removeResource(GrResource* resource) {
+ GrAssert(NULL != resource);
+ GrAssert(this == resource->getGpu());
+
+ fResourceList.remove(resource);
+}
+
+
+void GrGpu::unimpl(const char msg[]) {
+#if GR_DEBUG
+ GrPrintf("--- GrGpu unimplemented(\"%s\")\n", msg);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+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 &&
+ (kRenderTarget_GrTextureFlagBit & desc.fFlags) &&
+ !(kNoStencil_GrTextureFlagBit & desc.fFlags)) {
+ GrAssert(NULL != tex->asRenderTarget());
+ // TODO: defer this and attach dynamically
+ if (!this->attachStencilBufferToRenderTarget(tex->asRenderTarget())) {
+ tex->unref();
+ return NULL;
+ }
+ }
+ return tex;
+}
+
+bool GrGpu::attachStencilBufferToRenderTarget(GrRenderTarget* rt) {
+ GrAssert(NULL == rt->getStencilBuffer());
+ GrStencilBuffer* sb =
+ this->getContext()->findStencilBuffer(rt->width(),
+ rt->height(),
+ rt->numSamples());
+ if (NULL != sb) {
+ rt->setStencilBuffer(sb);
+ bool attached = this->attachStencilBufferToRenderTarget(sb, rt);
+ if (!attached) {
+ rt->setStencilBuffer(NULL);
+ }
+ return attached;
+ }
+ if (this->createStencilBufferForRenderTarget(rt,
+ rt->width(), rt->height())) {
+ // Right now we're clearing the stencil buffer here after it is
+ // attached to an RT for the first time. When we start matching
+ // stencil buffers with smaller color targets this will no longer
+ // be correct because it won't be guaranteed to clear the entire
+ // sb.
+ // We used to clear down in the GL subclass using a special purpose
+ // FBO. But iOS doesn't allow a stencil-only FBO. It reports unsupported
+ // FBO status.
+ GrDrawState::AutoRenderTargetRestore artr(this->drawState(), rt);
+ this->clearStencil();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+GrTexture* GrGpu::wrapBackendTexture(const GrBackendTextureDesc& desc) {
+ this->handleDirtyContext();
+ GrTexture* tex = this->onWrapBackendTexture(desc);
+ if (NULL == tex) {
+ return NULL;
+ }
+ // TODO: defer this and attach dynamically
+ GrRenderTarget* tgt = tex->asRenderTarget();
+ if (NULL != tgt &&
+ !this->attachStencilBufferToRenderTarget(tgt)) {
+ tex->unref();
+ return NULL;
+ } else {
+ return tex;
+ }
+}
+
+GrRenderTarget* GrGpu::wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
+ this->handleDirtyContext();
+ return this->onWrapBackendRenderTarget(desc);
+}
+
+GrVertexBuffer* GrGpu::createVertexBuffer(uint32_t size, bool dynamic) {
+ this->handleDirtyContext();
+ return this->onCreateVertexBuffer(size, dynamic);
+}
+
+GrIndexBuffer* GrGpu::createIndexBuffer(uint32_t size, bool dynamic) {
+ this->handleDirtyContext();
+ return this->onCreateIndexBuffer(size, dynamic);
+}
+
+GrPath* GrGpu::createPath(const SkPath& path) {
+ GrAssert(this->caps()->pathStencilingSupport());
+ this->handleDirtyContext();
+ return this->onCreatePath(path);
+}
+
+void GrGpu::clear(const SkIRect* rect,
+ GrColor color,
+ GrRenderTarget* renderTarget) {
+ GrDrawState::AutoRenderTargetRestore art;
+ if (NULL != renderTarget) {
+ art.set(this->drawState(), renderTarget);
+ }
+ if (NULL == this->getDrawState().getRenderTarget()) {
+ GrAssert(0);
+ return;
+ }
+ this->handleDirtyContext();
+ this->onClear(rect, color);
+}
+
+void GrGpu::forceRenderTargetFlush() {
+ this->handleDirtyContext();
+ this->onForceRenderTargetFlush();
+}
+
+bool GrGpu::readPixels(GrRenderTarget* target,
+ int left, int top, int width, int height,
+ GrPixelConfig config, void* buffer,
+ size_t rowBytes) {
+ this->handleDirtyContext();
+ return this->onReadPixels(target, left, top, width, height,
+ config, buffer, rowBytes);
+}
+
+bool GrGpu::writeTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes) {
+ this->handleDirtyContext();
+ return this->onWriteTexturePixels(texture, left, top, width, height,
+ config, buffer, rowBytes);
+}
+
+void GrGpu::resolveRenderTarget(GrRenderTarget* target) {
+ GrAssert(target);
+ this->handleDirtyContext();
+ this->onResolveRenderTarget(target);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const int MAX_QUADS = 1 << 12; // max possible: (1 << 14) - 1;
+
+GR_STATIC_ASSERT(4 * MAX_QUADS <= 65535);
+
+static inline void fill_indices(uint16_t* indices, int quadCount) {
+ for (int i = 0; i < quadCount; ++i) {
+ indices[6 * i + 0] = 4 * i + 0;
+ indices[6 * i + 1] = 4 * i + 1;
+ indices[6 * i + 2] = 4 * i + 2;
+ indices[6 * i + 3] = 4 * i + 0;
+ indices[6 * i + 4] = 4 * i + 2;
+ indices[6 * i + 5] = 4 * i + 3;
+ }
+}
+
+const GrIndexBuffer* GrGpu::getQuadIndexBuffer() const {
+ if (NULL == fQuadIndexBuffer) {
+ static const int SIZE = sizeof(uint16_t) * 6 * MAX_QUADS;
+ GrGpu* me = const_cast<GrGpu*>(this);
+ fQuadIndexBuffer = me->createIndexBuffer(SIZE, false);
+ if (NULL != fQuadIndexBuffer) {
+ uint16_t* indices = (uint16_t*)fQuadIndexBuffer->lock();
+ if (NULL != indices) {
+ fill_indices(indices, MAX_QUADS);
+ fQuadIndexBuffer->unlock();
+ } else {
+ indices = (uint16_t*)GrMalloc(SIZE);
+ fill_indices(indices, MAX_QUADS);
+ if (!fQuadIndexBuffer->updateData(indices, SIZE)) {
+ fQuadIndexBuffer->unref();
+ fQuadIndexBuffer = NULL;
+ GrCrash("Can't get indices into buffer!");
+ }
+ GrFree(indices);
+ }
+ }
+ }
+
+ return fQuadIndexBuffer;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrGpu::setupClipAndFlushState(DrawType type, const GrDeviceCoordTexture* dstCopy,
+ GrDrawState::AutoRestoreEffects* are) {
+ if (!fClipMaskManager.setupClipping(this->getClip(), are)) {
+ return false;
+ }
+
+ if (!this->flushGraphicsState(type, dstCopy)) {
+ return false;
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrGpu::geometrySourceWillPush() {
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+ if (kArray_GeometrySrcType == geoSrc.fVertexSrc ||
+ kReserved_GeometrySrcType == geoSrc.fVertexSrc) {
+ this->finalizeReservedVertices();
+ }
+ if (kArray_GeometrySrcType == geoSrc.fIndexSrc ||
+ kReserved_GeometrySrcType == geoSrc.fIndexSrc) {
+ this->finalizeReservedIndices();
+ }
+ GeometryPoolState& newState = fGeomPoolStateStack.push_back();
+#if GR_DEBUG
+ newState.fPoolVertexBuffer = (GrVertexBuffer*)DEBUG_INVAL_BUFFER;
+ newState.fPoolStartVertex = DEBUG_INVAL_START_IDX;
+ newState.fPoolIndexBuffer = (GrIndexBuffer*)DEBUG_INVAL_BUFFER;
+ newState.fPoolStartIndex = DEBUG_INVAL_START_IDX;
+#else
+ (void) newState; // silence compiler warning
+#endif
+}
+
+void GrGpu::geometrySourceWillPop(const GeometrySrcState& restoredState) {
+ // if popping last entry then pops are unbalanced with pushes
+ GrAssert(fGeomPoolStateStack.count() > 1);
+ fGeomPoolStateStack.pop_back();
+}
+
+void GrGpu::onDraw(const DrawInfo& info) {
+ this->handleDirtyContext();
+ GrDrawState::AutoRestoreEffects are;
+ if (!this->setupClipAndFlushState(PrimTypeToDrawType(info.primitiveType()),
+ info.getDstCopy(),
+ &are)) {
+ return;
+ }
+ this->onGpuDraw(info);
+}
+
+void GrGpu::onStencilPath(const GrPath* path, const SkStrokeRec&, SkPath::FillType fill) {
+ this->handleDirtyContext();
+
+ // TODO: make this more efficient (don't copy and copy back)
+ GrAutoTRestore<GrStencilSettings> asr(this->drawState()->stencil());
+
+ this->setStencilPathSettings(*path, fill, this->drawState()->stencil());
+ GrDrawState::AutoRestoreEffects are;
+ if (!this->setupClipAndFlushState(kStencilPath_DrawType, NULL, &are)) {
+ return;
+ }
+
+ this->onGpuStencilPath(path, fill);
+}
+
+void GrGpu::finalizeReservedVertices() {
+ GrAssert(NULL != fVertexPool);
+ fVertexPool->unlock();
+}
+
+void GrGpu::finalizeReservedIndices() {
+ GrAssert(NULL != fIndexPool);
+ fIndexPool->unlock();
+}
+
+void GrGpu::prepareVertexPool() {
+ if (NULL == fVertexPool) {
+ GrAssert(0 == fVertexPoolUseCnt);
+ fVertexPool = SkNEW_ARGS(GrVertexBufferAllocPool, (this, true,
+ VERTEX_POOL_VB_SIZE,
+ VERTEX_POOL_VB_COUNT));
+ fVertexPool->releaseGpuRef();
+ } else if (!fVertexPoolUseCnt) {
+ // the client doesn't have valid data in the pool
+ fVertexPool->reset();
+ }
+}
+
+void GrGpu::prepareIndexPool() {
+ if (NULL == fIndexPool) {
+ GrAssert(0 == fIndexPoolUseCnt);
+ fIndexPool = SkNEW_ARGS(GrIndexBufferAllocPool, (this, true,
+ INDEX_POOL_IB_SIZE,
+ INDEX_POOL_IB_COUNT));
+ fIndexPool->releaseGpuRef();
+ } else if (!fIndexPoolUseCnt) {
+ // the client doesn't have valid data in the pool
+ fIndexPool->reset();
+ }
+}
+
+bool GrGpu::onReserveVertexSpace(size_t vertexSize,
+ int vertexCount,
+ void** vertices) {
+ GeometryPoolState& geomPoolState = fGeomPoolStateStack.back();
+
+ GrAssert(vertexCount > 0);
+ GrAssert(NULL != vertices);
+
+ this->prepareVertexPool();
+
+ *vertices = fVertexPool->makeSpace(vertexSize,
+ vertexCount,
+ &geomPoolState.fPoolVertexBuffer,
+ &geomPoolState.fPoolStartVertex);
+ if (NULL == *vertices) {
+ return false;
+ }
+ ++fVertexPoolUseCnt;
+ return true;
+}
+
+bool GrGpu::onReserveIndexSpace(int indexCount, void** indices) {
+ GeometryPoolState& geomPoolState = fGeomPoolStateStack.back();
+
+ GrAssert(indexCount > 0);
+ GrAssert(NULL != indices);
+
+ this->prepareIndexPool();
+
+ *indices = fIndexPool->makeSpace(indexCount,
+ &geomPoolState.fPoolIndexBuffer,
+ &geomPoolState.fPoolStartIndex);
+ if (NULL == *indices) {
+ return false;
+ }
+ ++fIndexPoolUseCnt;
+ return true;
+}
+
+void GrGpu::releaseReservedVertexSpace() {
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+ GrAssert(kReserved_GeometrySrcType == geoSrc.fVertexSrc);
+ size_t bytes = geoSrc.fVertexCount * geoSrc.fVertexSize;
+ fVertexPool->putBack(bytes);
+ --fVertexPoolUseCnt;
+}
+
+void GrGpu::releaseReservedIndexSpace() {
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+ GrAssert(kReserved_GeometrySrcType == geoSrc.fIndexSrc);
+ size_t bytes = geoSrc.fIndexCount * sizeof(uint16_t);
+ fIndexPool->putBack(bytes);
+ --fIndexPoolUseCnt;
+}
+
+void GrGpu::onSetVertexSourceToArray(const void* vertexArray, int vertexCount) {
+ this->prepareVertexPool();
+ GeometryPoolState& geomPoolState = fGeomPoolStateStack.back();
+#if GR_DEBUG
+ bool success =
+#endif
+ fVertexPool->appendVertices(this->getVertexSize(),
+ vertexCount,
+ vertexArray,
+ &geomPoolState.fPoolVertexBuffer,
+ &geomPoolState.fPoolStartVertex);
+ ++fVertexPoolUseCnt;
+ GR_DEBUGASSERT(success);
+}
+
+void GrGpu::onSetIndexSourceToArray(const void* indexArray, int indexCount) {
+ this->prepareIndexPool();
+ GeometryPoolState& geomPoolState = fGeomPoolStateStack.back();
+#if GR_DEBUG
+ bool success =
+#endif
+ fIndexPool->appendIndices(indexCount,
+ indexArray,
+ &geomPoolState.fPoolIndexBuffer,
+ &geomPoolState.fPoolStartIndex);
+ ++fIndexPoolUseCnt;
+ GR_DEBUGASSERT(success);
+}
+
+void GrGpu::releaseVertexArray() {
+ // if vertex source was array, we stowed data in the pool
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+ GrAssert(kArray_GeometrySrcType == geoSrc.fVertexSrc);
+ size_t bytes = geoSrc.fVertexCount * geoSrc.fVertexSize;
+ fVertexPool->putBack(bytes);
+ --fVertexPoolUseCnt;
+}
+
+void GrGpu::releaseIndexArray() {
+ // if index source was array, we stowed data in the pool
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+ GrAssert(kArray_GeometrySrcType == geoSrc.fIndexSrc);
+ size_t bytes = geoSrc.fIndexCount * sizeof(uint16_t);
+ fIndexPool->putBack(bytes);
+ --fIndexPoolUseCnt;
+}
diff --git a/gpu/GrGpu.h b/gpu/GrGpu.h
new file mode 100644
index 00000000..86c25ee7
--- /dev/null
+++ b/gpu/GrGpu.h
@@ -0,0 +1,539 @@
+/*
+ * 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 GrGpu_DEFINED
+#define GrGpu_DEFINED
+
+#include "GrDrawTarget.h"
+#include "GrRefCnt.h"
+#include "GrClipMaskManager.h"
+#include "SkPath.h"
+
+class GrContext;
+class GrIndexBufferAllocPool;
+class GrPath;
+class GrPathRenderer;
+class GrPathRendererChain;
+class GrResource;
+class GrStencilBuffer;
+class GrVertexBufferAllocPool;
+
+class GrGpu : public GrDrawTarget {
+public:
+
+ /**
+ * Additional blend coefficients for dual source blending, not exposed
+ * through GrPaint/GrContext.
+ */
+ enum ExtendedBlendCoeffs {
+ // source 2 refers to second output color when
+ // using dual source blending.
+ kS2C_GrBlendCoeff = kPublicGrBlendCoeffCount,
+ kIS2C_GrBlendCoeff,
+ kS2A_GrBlendCoeff,
+ kIS2A_GrBlendCoeff,
+
+ kTotalGrBlendCoeffCount
+ };
+
+ /**
+ * 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. The context will not be
+ * fully constructed and should not be used by GrGpu until after this function returns.
+ */
+ static GrGpu* Create(GrBackend, GrBackendContext, GrContext* context);
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ GrGpu(GrContext* context);
+ virtual ~GrGpu();
+
+ GrContext* getContext() { return this->INHERITED::getContext(); }
+ const GrContext* getContext() const { return this->INHERITED::getContext(); }
+
+ /**
+ * The GrGpu object normally assumes that no outsider is setting state
+ * within the underlying 3D API's context/device/whatever. This call informs
+ * the GrGpu that the state was modified and it shouldn't make assumptions
+ * about the state.
+ */
+ void markContextDirty(uint32_t state = kAll_GrBackendState) {
+ fResetBits |= state;
+ }
+
+ void unimpl(const char[]);
+
+ /**
+ * Creates a texture object. If desc width or height is not a power of
+ * two but underlying API requires a power of two texture then srcData
+ * will be embedded in a power of two texture. The extra width and height
+ * is filled as though srcData were rendered clamped into the texture.
+ *
+ * If kRenderTarget_TextureFlag is specified the GrRenderTarget is
+ * accessible via GrTexture::asRenderTarget(). The texture will hold a ref
+ * on the render target until the texture is destroyed.
+ *
+ * @param desc describes the texture to be created.
+ * @param srcData texel data to load texture. Begins with full-size
+ * palette data for paletted textures. Contains width*
+ * height texels. If NULL texture data is uninitialized.
+ *
+ * @return The texture object if successful, otherwise NULL.
+ */
+ GrTexture* createTexture(const GrTextureDesc& desc,
+ const void* srcData, size_t rowBytes);
+
+ /**
+ * Implements GrContext::wrapBackendTexture
+ */
+ GrTexture* wrapBackendTexture(const GrBackendTextureDesc&);
+
+ /**
+ * Implements GrContext::wrapBackendTexture
+ */
+ GrRenderTarget* wrapBackendRenderTarget(const GrBackendRenderTargetDesc&);
+
+ /**
+ * Creates a vertex buffer.
+ *
+ * @param size size in bytes of the vertex buffer
+ * @param dynamic hints whether the data will be frequently changed
+ * by either GrVertexBuffer::lock or
+ * GrVertexBuffer::updateData.
+ *
+ * @return The vertex buffer if successful, otherwise NULL.
+ */
+ GrVertexBuffer* createVertexBuffer(uint32_t size, bool dynamic);
+
+ /**
+ * Creates an index buffer.
+ *
+ * @param size size in bytes of the index buffer
+ * @param dynamic hints whether the data will be frequently changed
+ * by either GrIndexBuffer::lock or
+ * GrIndexBuffer::updateData.
+ *
+ * @return The index buffer if successful, otherwise NULL.
+ */
+ GrIndexBuffer* createIndexBuffer(uint32_t size, bool dynamic);
+
+ /**
+ * Creates a path object that can be stenciled using stencilPath(). It is
+ * only legal to call this if the caps report support for path stenciling.
+ */
+ GrPath* createPath(const SkPath& path);
+
+ /**
+ * Returns an index buffer that can be used to render quads.
+ * Six indices per quad: 0, 1, 2, 0, 2, 3, etc.
+ * The max number of quads can be queried using GrIndexBuffer::maxQuads().
+ * Draw with kTriangles_GrPrimitiveType
+ * @ return the quad index buffer
+ */
+ const GrIndexBuffer* getQuadIndexBuffer() const;
+
+ /**
+ * Resolves MSAA.
+ */
+ void resolveRenderTarget(GrRenderTarget* target);
+
+ /**
+ * Ensures that the current render target is actually set in the
+ * underlying 3D API. Used when client wants to use 3D API to directly
+ * render to the RT.
+ */
+ void forceRenderTargetFlush();
+
+ /**
+ * Gets a preferred 8888 config to use for writing/reading pixel data to/from a surface with
+ * config surfaceConfig. The returned config must have at least as many bits per channel as the
+ * readConfig or writeConfig param.
+ */
+ virtual GrPixelConfig preferredReadPixelsConfig(GrPixelConfig readConfig,
+ GrPixelConfig surfaceConfig) const {
+ return readConfig;
+ }
+ virtual GrPixelConfig preferredWritePixelsConfig(GrPixelConfig writeConfig,
+ GrPixelConfig surfaceConfig) const {
+ return writeConfig;
+ }
+
+ /**
+ * Called before uploading writing pixels to a GrTexture when the src pixel config doesn't
+ * match the texture's config.
+ */
+ virtual bool canWriteTexturePixels(const GrTexture*, GrPixelConfig srcConfig) const = 0;
+
+ /**
+ * OpenGL's readPixels returns the result bottom-to-top while the skia
+ * API is top-to-bottom. Thus we have to do a y-axis flip. The obvious
+ * solution is to have the subclass do the flip using either the CPU or GPU.
+ * However, the caller (GrContext) may have transformations to apply and can
+ * simply fold in the y-flip for free. On the other hand, the subclass may
+ * be able to do it for free itself. For example, the subclass may have to
+ * do memcpys to handle rowBytes that aren't tight. It could do the y-flip
+ * concurrently.
+ *
+ * This function returns true if a y-flip is required to put the pixels in
+ * top-to-bottom order and the subclass cannot do it for free.
+ *
+ * See read pixels for the params
+ * @return true if calling readPixels with the same set of params will
+ * produce bottom-to-top data
+ */
+ virtual bool readPixelsWillPayForYFlip(GrRenderTarget* renderTarget,
+ int left, int top,
+ int width, int height,
+ GrPixelConfig config,
+ size_t rowBytes) const = 0;
+ /**
+ * This should return true if reading a NxM rectangle of pixels from a
+ * render target is faster if the target has dimensons N and M and the read
+ * rectangle has its top-left at 0,0.
+ */
+ virtual bool fullReadPixelsIsFasterThanPartial() const { return false; };
+
+ /**
+ * Reads a rectangle of pixels from a render target.
+ *
+ * @param renderTarget the render target to read from. NULL means the
+ * current render target.
+ * @param left left edge of the rectangle to read (inclusive)
+ * @param top top edge of the rectangle to read (inclusive)
+ * @param width width of rectangle to read in pixels.
+ * @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 the number of bytes between consecutive rows. Zero
+ * means rows are tightly packed.
+ * @param invertY buffer should be populated bottom-to-top as opposed
+ * to top-to-bottom (skia's usual order)
+ *
+ * @return true if the read succeeded, false if not. The read can fail
+ * because of a unsupported pixel config or because no render
+ * target is currently set.
+ */
+ bool readPixels(GrRenderTarget* renderTarget,
+ int left, int top, int width, int height,
+ GrPixelConfig config, void* buffer, size_t rowBytes);
+
+ /**
+ * Updates the pixels in a rectangle of a texture.
+ *
+ * @param left left edge of the rectangle to write (inclusive)
+ * @param top top edge of the rectangle to write (inclusive)
+ * @param width width of rectangle to write in pixels.
+ * @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 between consecutive rows. Zero
+ * means rows are tightly packed.
+ */
+ bool writeTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes);
+
+ /**
+ * Called to tell Gpu object that all GrResources have been lost and should
+ * be abandoned. Overrides must call INHERITED::abandonResources().
+ */
+ virtual void abandonResources();
+
+ /**
+ * Called to tell Gpu object to release all GrResources. Overrides must call
+ * INHERITED::releaseResources().
+ */
+ void releaseResources();
+
+ /**
+ * Add resource to list of resources. Should only be called by GrResource.
+ * @param resource the resource to add.
+ */
+ void insertResource(GrResource* resource);
+
+ /**
+ * Remove resource from list of resources. Should only be called by
+ * GrResource.
+ * @param resource the resource to remove.
+ */
+ void removeResource(GrResource* resource);
+
+ // GrDrawTarget overrides
+ virtual void clear(const SkIRect* rect,
+ GrColor color,
+ GrRenderTarget* renderTarget = NULL) SK_OVERRIDE;
+
+ virtual void purgeResources() SK_OVERRIDE {
+ // The clip mask manager can rebuild all its clip masks so just
+ // get rid of them all.
+ fClipMaskManager.releaseResources();
+ }
+
+ // After the client interacts directly with the 3D context state the GrGpu
+ // must resync its internal state and assumptions about 3D context state.
+ // Each time this occurs the GrGpu bumps a timestamp.
+ // state of the 3D context
+ // At 10 resets / frame and 60fps a 64bit timestamp will overflow in about
+ // a billion years.
+ typedef uint64_t ResetTimestamp;
+
+ // This timestamp is always older than the current timestamp
+ static const ResetTimestamp kExpiredTimestamp = 0;
+ // Returns a timestamp based on the number of times the context was reset.
+ // This timestamp can be used to lazily detect when cached 3D context state
+ // is dirty.
+ ResetTimestamp getResetTimestamp() const {
+ return fResetTimestamp;
+ }
+
+ /**
+ * Can the provided configuration act as a color render target?
+ */
+ bool isConfigRenderable(GrPixelConfig config) const {
+ GrAssert(kGrPixelConfigCnt > config);
+ return fConfigRenderSupport[config];
+ }
+
+ /**
+ * These methods are called by the clip manager's setupClipping function
+ * which (called as part of GrGpu's implementation of onDraw and
+ * onStencilPath member functions.) The GrGpu subclass should flush the
+ * stencil state to the 3D API in its implementation of flushGraphicsState.
+ */
+ void enableScissor(const SkIRect& rect) {
+ fScissorState.fEnabled = true;
+ fScissorState.fRect = rect;
+ }
+ void disableScissor() { fScissorState.fEnabled = false; }
+
+ /**
+ * Like the scissor methods above this is called by setupClipping and
+ * should be flushed by the GrGpu subclass in flushGraphicsState. These
+ * stencil settings should be used in place of those on the GrDrawState.
+ * They have been adjusted to account for any interactions between the
+ * GrDrawState's stencil settings and stencil clipping.
+ */
+ void setStencilSettings(const GrStencilSettings& settings) {
+ fStencilSettings = settings;
+ }
+ void disableStencil() { fStencilSettings.setDisabled(); }
+
+ // GrGpu subclass sets clip bit in the stencil buffer. The subclass is
+ // free to clear the remaining bits to zero if masked clears are more
+ // expensive than clearing all bits.
+ virtual void clearStencilClip(const SkIRect& rect, bool insideClip) = 0;
+
+ enum PrivateDrawStateStateBits {
+ kFirstBit = (GrDrawState::kLastPublicStateBit << 1),
+
+ kModifyStencilClip_StateBit = kFirstBit, // allows draws to modify
+ // stencil bits used for
+ // clipping.
+ };
+
+protected:
+ enum DrawType {
+ kDrawPoints_DrawType,
+ kDrawLines_DrawType,
+ kDrawTriangles_DrawType,
+ kStencilPath_DrawType,
+ };
+
+ DrawType PrimTypeToDrawType(GrPrimitiveType type) {
+ switch (type) {
+ case kTriangles_GrPrimitiveType:
+ case kTriangleStrip_GrPrimitiveType:
+ case kTriangleFan_GrPrimitiveType:
+ return kDrawTriangles_DrawType;
+ case kPoints_GrPrimitiveType:
+ return kDrawPoints_DrawType;
+ case kLines_GrPrimitiveType:
+ case kLineStrip_GrPrimitiveType:
+ return kDrawLines_DrawType;
+ default:
+ GrCrash("Unexpected primitive type");
+ return kDrawTriangles_DrawType;
+ }
+ }
+
+ // prepares clip flushes gpu state before a draw
+ bool setupClipAndFlushState(DrawType,
+ const GrDeviceCoordTexture* dstCopy,
+ GrDrawState::AutoRestoreEffects* are);
+
+ // Functions used to map clip-respecting stencil tests into normal
+ // stencil funcs supported by GPUs.
+ static GrStencilFunc ConvertStencilFunc(bool stencilInClip,
+ GrStencilFunc func);
+ static void ConvertStencilFuncAndMask(GrStencilFunc func,
+ bool clipInStencil,
+ unsigned int clipBit,
+ unsigned int userBits,
+ unsigned int* ref,
+ unsigned int* mask);
+
+ GrClipMaskManager fClipMaskManager;
+
+ struct GeometryPoolState {
+ const GrVertexBuffer* fPoolVertexBuffer;
+ int fPoolStartVertex;
+
+ const GrIndexBuffer* fPoolIndexBuffer;
+ int fPoolStartIndex;
+ };
+ const GeometryPoolState& getGeomPoolState() {
+ return fGeomPoolStateStack.back();
+ }
+
+ // The state of the scissor is controlled by the clip manager
+ struct ScissorState {
+ bool fEnabled;
+ SkIRect fRect;
+ } fScissorState;
+
+ // The final stencil settings to use as determined by the clip manager.
+ GrStencilSettings fStencilSettings;
+
+ // Derived classes need access to this so they can fill it out in their
+ // constructors
+ bool fConfigRenderSupport[kGrPixelConfigCnt];
+
+ // Helpers for setting up geometry state
+ void finalizeReservedVertices();
+ void finalizeReservedIndices();
+
+private:
+ // GrDrawTarget overrides
+ virtual bool onReserveVertexSpace(size_t vertexSize, int vertexCount, void** vertices) SK_OVERRIDE;
+ virtual bool onReserveIndexSpace(int indexCount, void** indices) SK_OVERRIDE;
+ virtual void releaseReservedVertexSpace() SK_OVERRIDE;
+ virtual void releaseReservedIndexSpace() SK_OVERRIDE;
+ virtual void onSetVertexSourceToArray(const void* vertexArray, int vertexCount) SK_OVERRIDE;
+ virtual void onSetIndexSourceToArray(const void* indexArray, int indexCount) SK_OVERRIDE;
+ virtual void releaseVertexArray() SK_OVERRIDE;
+ virtual void releaseIndexArray() SK_OVERRIDE;
+ virtual void geometrySourceWillPush() SK_OVERRIDE;
+ virtual void geometrySourceWillPop(const GeometrySrcState& restoredState) SK_OVERRIDE;
+
+
+ // called when the 3D context state is unknown. Subclass should emit any
+ // assumed 3D context state and dirty any state cache.
+ virtual void onResetContext(uint32_t resetBits) = 0;
+
+ // overridden by backend-specific derived class to create objects.
+ virtual GrTexture* onCreateTexture(const GrTextureDesc& desc,
+ const void* srcData,
+ size_t rowBytes) = 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 backend-specific derived class to perform the clear and
+ // clearRect. NULL rect means clear whole target.
+ virtual void onClear(const SkIRect* rect, GrColor color) = 0;
+
+ // overridden by backend-specific derived class to perform the draw call.
+ virtual void onGpuDraw(const DrawInfo&) = 0;
+ // when GrDrawTarget::stencilPath is called the draw state's current stencil
+ // settings are ignored. Instead the GrGpu decides the stencil rules
+ // necessary to stencil the path. These are still subject to filtering by
+ // the clip mask manager.
+ virtual void setStencilPathSettings(const GrPath&,
+ SkPath::FillType,
+ GrStencilSettings* settings) = 0;
+ // overridden by backend-specific derived class to perform the path stenciling.
+ virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) = 0;
+
+ // overridden by backend-specific derived class to perform flush
+ virtual void onForceRenderTargetFlush() = 0;
+
+ // 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,
+ void* buffer,
+ size_t rowBytes) = 0;
+
+ // overridden by backend-specific derived class to perform the texture update
+ virtual bool onWriteTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes) = 0;
+
+ // overridden by backend-specific derived class to perform the resolve
+ virtual void onResolveRenderTarget(GrRenderTarget* target) = 0;
+
+ // width and height may be larger than rt (if underlying API allows it).
+ // Should attach the SB to the RT. Returns false if compatible sb could
+ // not be created.
+ virtual bool createStencilBufferForRenderTarget(GrRenderTarget*, int width, int height) = 0;
+
+ // attaches an existing SB to an existing RT.
+ virtual bool attachStencilBufferToRenderTarget(GrStencilBuffer*, GrRenderTarget*) = 0;
+
+ // The GrGpu typically records the clients requested state and then flushes
+ // deltas from previous state at draw time. This function does the
+ // backend-specific flush of the state.
+ // returns false if current state is unsupported.
+ virtual bool flushGraphicsState(DrawType, const GrDeviceCoordTexture* dstCopy) = 0;
+
+ // clears the entire stencil buffer to 0
+ virtual void clearStencil() = 0;
+
+ // Given a rt, find or create a stencil buffer and attach it
+ bool attachStencilBufferToRenderTarget(GrRenderTarget* target);
+
+ // GrDrawTarget overrides
+ virtual void onDraw(const DrawInfo&) 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();
+ void prepareIndexPool();
+
+ void resetContext() {
+ // We call this because the client may have messed with the
+ // stencil buffer. Perhaps we should detect whether it is a
+ // internally created stencil buffer and if so skip the invalidate.
+ fClipMaskManager.invalidateStencilMask();
+ this->onResetContext(fResetBits);
+ fResetBits = 0;
+ ++fResetTimestamp;
+ }
+
+ void handleDirtyContext() {
+ if (fResetBits) {
+ this->resetContext();
+ }
+ }
+
+ enum {
+ kPreallocGeomPoolStateStackCnt = 4,
+ };
+ typedef SkTInternalLList<GrResource> ResourceList;
+ SkSTArray<kPreallocGeomPoolStateStackCnt, GeometryPoolState, true> fGeomPoolStateStack;
+ ResetTimestamp fResetTimestamp;
+ uint32_t fResetBits;
+ GrVertexBufferAllocPool* fVertexPool;
+ GrIndexBufferAllocPool* fIndexPool;
+ // counts number of uses of vertex/index pool in the geometry stack
+ int fVertexPoolUseCnt;
+ int fIndexPoolUseCnt;
+ // these are mutable so they can be created on-demand
+ mutable GrIndexBuffer* fQuadIndexBuffer;
+ // Used to abandon/release all resources created by this GrGpu. TODO: Move this
+ // functionality to GrResourceCache.
+ ResourceList fResourceList;
+
+ typedef GrDrawTarget INHERITED;
+};
+
+#endif
diff --git a/gpu/GrGpuFactory.cpp b/gpu/GrGpuFactory.cpp
new file mode 100644
index 00000000..a3c8eba4
--- /dev/null
+++ b/gpu/GrGpuFactory.cpp
@@ -0,0 +1,43 @@
+
+/*
+ * 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 "GrTypes.h"
+
+#include "gl/GrGLConfig.h"
+
+#include "GrGpu.h"
+#include "gl/GrGpuGL.h"
+
+GrGpu* GrGpu::Create(GrBackend backend, GrBackendContext backendContext, GrContext* context) {
+
+ const GrGLInterface* glInterface = NULL;
+ SkAutoTUnref<const GrGLInterface> glInterfaceUnref;
+
+ if (kOpenGL_GrBackend == backend) {
+ glInterface = reinterpret_cast<const GrGLInterface*>(backendContext);
+ if (NULL == glInterface) {
+ glInterface = GrGLDefaultInterface();
+ // By calling GrGLDefaultInterface we've taken a ref on the
+ // returned object. We only want to hold that ref until after
+ // the GrGpu is constructed and has taken ownership.
+ glInterfaceUnref.reset(glInterface);
+ }
+ if (NULL == glInterface) {
+#if GR_DEBUG
+ GrPrintf("No GL interface provided!\n");
+#endif
+ return NULL;
+ }
+ GrGLContext ctx(glInterface);
+ if (ctx.isInitialized()) {
+ return SkNEW_ARGS(GrGpuGL, (ctx, context));
+ }
+ }
+ return NULL;
+}
diff --git a/gpu/GrInOrderDrawBuffer.cpp b/gpu/GrInOrderDrawBuffer.cpp
new file mode 100644
index 00000000..3ec95966
--- /dev/null
+++ b/gpu/GrInOrderDrawBuffer.cpp
@@ -0,0 +1,827 @@
+/*
+ * 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 "GrInOrderDrawBuffer.h"
+
+#include "GrBufferAllocPool.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+#include "GrIndexBuffer.h"
+#include "GrPath.h"
+#include "GrPoint.h"
+#include "GrRenderTarget.h"
+#include "GrTemplates.h"
+#include "GrTexture.h"
+#include "GrVertexBuffer.h"
+
+GrInOrderDrawBuffer::GrInOrderDrawBuffer(GrGpu* gpu,
+ GrVertexBufferAllocPool* vertexPool,
+ GrIndexBufferAllocPool* indexPool)
+ : GrDrawTarget(gpu->getContext())
+ , fDstGpu(gpu)
+ , fClipSet(true)
+ , fClipProxyState(kUnknown_ClipProxyState)
+ , fVertexPool(*vertexPool)
+ , fIndexPool(*indexPool)
+ , fFlushing(false) {
+
+ fDstGpu->ref();
+ fCaps.reset(SkRef(fDstGpu->caps()));
+
+ GrAssert(NULL != vertexPool);
+ GrAssert(NULL != indexPool);
+
+ GeometryPoolState& poolState = fGeoPoolStateStack.push_back();
+ poolState.fUsedPoolVertexBytes = 0;
+ poolState.fUsedPoolIndexBytes = 0;
+#if GR_DEBUG
+ poolState.fPoolVertexBuffer = (GrVertexBuffer*)~0;
+ poolState.fPoolStartVertex = ~0;
+ poolState.fPoolIndexBuffer = (GrIndexBuffer*)~0;
+ poolState.fPoolStartIndex = ~0;
+#endif
+ this->reset();
+}
+
+GrInOrderDrawBuffer::~GrInOrderDrawBuffer() {
+ this->reset();
+ // This must be called by before the GrDrawTarget destructor
+ this->releaseGeometry();
+ fDstGpu->unref();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+void get_vertex_bounds(const void* vertices,
+ size_t vertexSize,
+ int vertexCount,
+ SkRect* bounds) {
+ GrAssert(vertexSize >= sizeof(GrPoint));
+ GrAssert(vertexCount > 0);
+ const GrPoint* point = static_cast<const GrPoint*>(vertices);
+ bounds->fLeft = bounds->fRight = point->fX;
+ bounds->fTop = bounds->fBottom = point->fY;
+ for (int i = 1; i < vertexCount; ++i) {
+ point = reinterpret_cast<GrPoint*>(reinterpret_cast<intptr_t>(point) + vertexSize);
+ bounds->growToInclude(point->fX, point->fY);
+ }
+}
+}
+
+
+namespace {
+
+extern const GrVertexAttrib kRectPosColorUVAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kColor_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint)+sizeof(GrColor),
+ kLocalCoord_GrVertexAttribBinding},
+};
+
+extern const GrVertexAttrib kRectPosUVAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding},
+};
+
+static void set_vertex_attributes(GrDrawState* drawState,
+ bool hasColor, bool hasUVs,
+ int* colorOffset, int* localOffset) {
+ *colorOffset = -1;
+ *localOffset = -1;
+
+ // Using per-vertex colors allows batching across colors. (A lot of rects in a row differing
+ // only in color is a common occurrence in tables). However, having per-vertex colors disables
+ // blending optimizations because we don't know if the color will be solid or not. These
+ // optimizations help determine whether coverage and color can be blended correctly when
+ // dual-source blending isn't available. This comes into play when there is coverage. If colors
+ // were a stage it could take a hint that every vertex's color will be opaque.
+ if (hasColor && hasUVs) {
+ *colorOffset = sizeof(GrPoint);
+ *localOffset = sizeof(GrPoint) + sizeof(GrColor);
+ drawState->setVertexAttribs<kRectPosColorUVAttribs>(3);
+ } else if (hasColor) {
+ *colorOffset = sizeof(GrPoint);
+ drawState->setVertexAttribs<kRectPosColorUVAttribs>(2);
+ } else if (hasUVs) {
+ *localOffset = sizeof(GrPoint);
+ drawState->setVertexAttribs<kRectPosUVAttribs>(2);
+ } else {
+ drawState->setVertexAttribs<kRectPosUVAttribs>(1);
+ }
+}
+
+};
+
+void GrInOrderDrawBuffer::onDrawRect(const SkRect& rect,
+ const SkMatrix* matrix,
+ const SkRect* localRect,
+ const SkMatrix* localMatrix) {
+ GrDrawState::AutoColorRestore acr;
+
+ GrDrawState* drawState = this->drawState();
+
+ GrColor color = drawState->getColor();
+
+ int colorOffset, localOffset;
+ set_vertex_attributes(drawState,
+ this->caps()->dualSourceBlendingSupport() || drawState->hasSolidCoverage(),
+ NULL != localRect,
+ &colorOffset, &localOffset);
+ if (colorOffset >= 0) {
+ // We set the draw state's color to white here. This is done so that any batching performed
+ // in our subclass's onDraw() won't get a false from GrDrawState::op== due to a color
+ // mismatch. TODO: Once vertex layout is owned by GrDrawState it should skip comparing the
+ // constant color in its op== when the kColor layout bit is set and then we can remove
+ // this.
+ acr.set(drawState, 0xFFFFFFFF);
+ }
+
+ AutoReleaseGeometry geo(this, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ // Go to device coords to allow batching across matrix changes
+ SkMatrix combinedMatrix;
+ if (NULL != matrix) {
+ combinedMatrix = *matrix;
+ } else {
+ combinedMatrix.reset();
+ }
+ combinedMatrix.postConcat(drawState->getViewMatrix());
+ // When the caller has provided an explicit source rect for a stage then we don't want to
+ // modify that stage's matrix. Otherwise if the effect is generating its source rect from
+ // the vertex positions then we have to account for the view matrix change.
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(drawState)) {
+ return;
+ }
+
+ size_t vsize = drawState->getVertexSize();
+
+ geo.positions()->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, vsize);
+ combinedMatrix.mapPointsWithStride(geo.positions(), vsize, 4);
+
+ SkRect devBounds;
+ // since we already computed the dev verts, set the bounds hint. This will help us avoid
+ // unnecessary clipping in our onDraw().
+ get_vertex_bounds(geo.vertices(), vsize, 4, &devBounds);
+
+ if (localOffset >= 0) {
+ GrPoint* coords = GrTCast<GrPoint*>(GrTCast<intptr_t>(geo.vertices()) + localOffset);
+ coords->setRectFan(localRect->fLeft, localRect->fTop,
+ localRect->fRight, localRect->fBottom,
+ vsize);
+ if (NULL != localMatrix) {
+ localMatrix->mapPointsWithStride(coords, vsize, 4);
+ }
+ }
+
+ if (colorOffset >= 0) {
+ GrColor* vertColor = GrTCast<GrColor*>(GrTCast<intptr_t>(geo.vertices()) + colorOffset);
+ for (int i = 0; i < 4; ++i) {
+ *vertColor = color;
+ vertColor = (GrColor*) ((intptr_t) vertColor + vsize);
+ }
+ }
+
+ this->setIndexSourceToBuffer(this->getContext()->getQuadIndexBuffer());
+ this->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6, &devBounds);
+
+ // to ensure that stashing the drawState ptr is valid
+ GrAssert(this->drawState() == drawState);
+}
+
+bool GrInOrderDrawBuffer::quickInsideClip(const SkRect& devBounds) {
+ if (!this->getDrawState().isClipState()) {
+ return true;
+ }
+ if (kUnknown_ClipProxyState == fClipProxyState) {
+ SkIRect rect;
+ bool iior;
+ this->getClip()->getConservativeBounds(this->getDrawState().getRenderTarget(), &rect, &iior);
+ if (iior) {
+ // The clip is a rect. We will remember that in fProxyClip. It is common for an edge (or
+ // all edges) of the clip to be at the edge of the RT. However, we get that clipping for
+ // free via the viewport. We don't want to think that clipping must be enabled in this
+ // case. So we extend the clip outward from the edge to avoid these false negatives.
+ fClipProxyState = kValid_ClipProxyState;
+ fClipProxy = SkRect::MakeFromIRect(rect);
+
+ if (fClipProxy.fLeft <= 0) {
+ fClipProxy.fLeft = SK_ScalarMin;
+ }
+ if (fClipProxy.fTop <= 0) {
+ fClipProxy.fTop = SK_ScalarMin;
+ }
+ if (fClipProxy.fRight >= this->getDrawState().getRenderTarget()->width()) {
+ fClipProxy.fRight = SK_ScalarMax;
+ }
+ if (fClipProxy.fBottom >= this->getDrawState().getRenderTarget()->height()) {
+ fClipProxy.fBottom = SK_ScalarMax;
+ }
+ } else {
+ fClipProxyState = kInvalid_ClipProxyState;
+ }
+ }
+ if (kValid_ClipProxyState == fClipProxyState) {
+ return fClipProxy.contains(devBounds);
+ }
+ SkPoint originOffset = {SkIntToScalar(this->getClip()->fOrigin.fX),
+ SkIntToScalar(this->getClip()->fOrigin.fY)};
+ SkRect clipSpaceBounds = devBounds;
+ clipSpaceBounds.offset(originOffset);
+ return this->getClip()->fClipStack->quickContains(clipSpaceBounds);
+}
+
+int GrInOrderDrawBuffer::concatInstancedDraw(const DrawInfo& info) {
+ GrAssert(info.isInstanced());
+
+ const GeometrySrcState& geomSrc = this->getGeomSrc();
+ const GrDrawState& drawState = this->getDrawState();
+
+ // we only attempt to concat the case when reserved verts are used with a client-specified index
+ // buffer. To make this work with client-specified VBs we'd need to know if the VB was updated
+ // between draws.
+ if (kReserved_GeometrySrcType != geomSrc.fVertexSrc ||
+ kBuffer_GeometrySrcType != geomSrc.fIndexSrc) {
+ return 0;
+ }
+ // Check if there is a draw info that is compatible that uses the same VB from the pool and
+ // the same IB
+ if (kDraw_Cmd != fCmds.back()) {
+ return 0;
+ }
+
+ DrawRecord* draw = &fDraws.back();
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ const GrVertexBuffer* vertexBuffer = poolState.fPoolVertexBuffer;
+
+ if (!draw->isInstanced() ||
+ draw->verticesPerInstance() != info.verticesPerInstance() ||
+ draw->indicesPerInstance() != info.indicesPerInstance() ||
+ draw->fVertexBuffer != vertexBuffer ||
+ draw->fIndexBuffer != geomSrc.fIndexBuffer) {
+ return 0;
+ }
+ // info does not yet account for the offset from the start of the pool's VB while the previous
+ // draw record does.
+ int adjustedStartVertex = poolState.fPoolStartVertex + info.startVertex();
+ if (draw->startVertex() + draw->vertexCount() != adjustedStartVertex) {
+ return 0;
+ }
+
+ GrAssert(poolState.fPoolStartVertex == draw->startVertex() + draw->vertexCount());
+
+ // how many instances can be concat'ed onto draw given the size of the index buffer
+ int instancesToConcat = this->indexCountInCurrentSource() / info.indicesPerInstance();
+ instancesToConcat -= draw->instanceCount();
+ instancesToConcat = GrMin(instancesToConcat, info.instanceCount());
+
+ // update the amount of reserved vertex data actually referenced in draws
+ size_t vertexBytes = instancesToConcat * info.verticesPerInstance() *
+ drawState.getVertexSize();
+ poolState.fUsedPoolVertexBytes = GrMax(poolState.fUsedPoolVertexBytes, vertexBytes);
+
+ draw->adjustInstanceCount(instancesToConcat);
+ return instancesToConcat;
+}
+
+class AutoClipReenable {
+public:
+ AutoClipReenable() : fDrawState(NULL) {}
+ ~AutoClipReenable() {
+ if (NULL != fDrawState) {
+ fDrawState->enableState(GrDrawState::kClip_StateBit);
+ }
+ }
+ void set(GrDrawState* drawState) {
+ if (drawState->isClipState()) {
+ fDrawState = drawState;
+ drawState->disableState(GrDrawState::kClip_StateBit);
+ }
+ }
+private:
+ GrDrawState* fDrawState;
+};
+
+void GrInOrderDrawBuffer::onDraw(const DrawInfo& info) {
+
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ const GrDrawState& drawState = this->getDrawState();
+ AutoClipReenable acr;
+
+ if (drawState.isClipState() &&
+ NULL != info.getDevBounds() &&
+ this->quickInsideClip(*info.getDevBounds())) {
+ acr.set(this->drawState());
+ }
+
+ if (this->needsNewClip()) {
+ this->recordClip();
+ }
+ if (this->needsNewState()) {
+ this->recordState();
+ }
+
+ DrawRecord* draw;
+ if (info.isInstanced()) {
+ int instancesConcated = this->concatInstancedDraw(info);
+ if (info.instanceCount() > instancesConcated) {
+ draw = this->recordDraw(info);
+ draw->adjustInstanceCount(-instancesConcated);
+ } else {
+ return;
+ }
+ } else {
+ draw = this->recordDraw(info);
+ }
+
+ switch (this->getGeomSrc().fVertexSrc) {
+ case kBuffer_GeometrySrcType:
+ draw->fVertexBuffer = this->getGeomSrc().fVertexBuffer;
+ break;
+ case kReserved_GeometrySrcType: // fallthrough
+ case kArray_GeometrySrcType: {
+ size_t vertexBytes = (info.vertexCount() + info.startVertex()) *
+ drawState.getVertexSize();
+ poolState.fUsedPoolVertexBytes = GrMax(poolState.fUsedPoolVertexBytes, vertexBytes);
+ draw->fVertexBuffer = poolState.fPoolVertexBuffer;
+ draw->adjustStartVertex(poolState.fPoolStartVertex);
+ break;
+ }
+ default:
+ GrCrash("unknown geom src type");
+ }
+ draw->fVertexBuffer->ref();
+
+ if (info.isIndexed()) {
+ switch (this->getGeomSrc().fIndexSrc) {
+ case kBuffer_GeometrySrcType:
+ draw->fIndexBuffer = this->getGeomSrc().fIndexBuffer;
+ break;
+ case kReserved_GeometrySrcType: // fallthrough
+ case kArray_GeometrySrcType: {
+ size_t indexBytes = (info.indexCount() + info.startIndex()) * sizeof(uint16_t);
+ poolState.fUsedPoolIndexBytes = GrMax(poolState.fUsedPoolIndexBytes, indexBytes);
+ draw->fIndexBuffer = poolState.fPoolIndexBuffer;
+ draw->adjustStartIndex(poolState.fPoolStartIndex);
+ break;
+ }
+ default:
+ GrCrash("unknown geom src type");
+ }
+ draw->fIndexBuffer->ref();
+ } else {
+ draw->fIndexBuffer = NULL;
+ }
+}
+
+GrInOrderDrawBuffer::StencilPath::StencilPath() : fStroke(SkStrokeRec::kFill_InitStyle) {}
+
+void GrInOrderDrawBuffer::onStencilPath(const GrPath* path, const SkStrokeRec& stroke,
+ SkPath::FillType fill) {
+ if (this->needsNewClip()) {
+ this->recordClip();
+ }
+ // Only compare the subset of GrDrawState relevant to path stenciling?
+ if (this->needsNewState()) {
+ this->recordState();
+ }
+ StencilPath* sp = this->recordStencilPath();
+ sp->fPath.reset(path);
+ path->ref();
+ sp->fFill = fill;
+ sp->fStroke = stroke;
+}
+
+void GrInOrderDrawBuffer::clear(const SkIRect* rect, GrColor color, GrRenderTarget* renderTarget) {
+ SkIRect r;
+ if (NULL == renderTarget) {
+ renderTarget = this->drawState()->getRenderTarget();
+ GrAssert(NULL != renderTarget);
+ }
+ if (NULL == rect) {
+ // We could do something smart and remove previous draws and clears to
+ // the current render target. If we get that smart we have to make sure
+ // those draws aren't read before this clear (render-to-texture).
+ r.setLTRB(0, 0, renderTarget->width(), renderTarget->height());
+ rect = &r;
+ }
+ Clear* clr = this->recordClear();
+ clr->fColor = color;
+ clr->fRect = *rect;
+ clr->fRenderTarget = renderTarget;
+ renderTarget->ref();
+}
+
+void GrInOrderDrawBuffer::reset() {
+ GrAssert(1 == fGeoPoolStateStack.count());
+ this->resetVertexSource();
+ this->resetIndexSource();
+ int numDraws = fDraws.count();
+ for (int d = 0; d < numDraws; ++d) {
+ // we always have a VB, but not always an IB
+ GrAssert(NULL != fDraws[d].fVertexBuffer);
+ fDraws[d].fVertexBuffer->unref();
+ GrSafeUnref(fDraws[d].fIndexBuffer);
+ }
+ fCmds.reset();
+ fDraws.reset();
+ fStencilPaths.reset();
+ fStates.reset();
+ fClears.reset();
+ fVertexPool.reset();
+ fIndexPool.reset();
+ fClips.reset();
+ fClipOrigins.reset();
+ fCopySurfaces.reset();
+ fClipSet = true;
+}
+
+void GrInOrderDrawBuffer::flush() {
+ if (fFlushing) {
+ return;
+ }
+
+ GrAssert(kReserved_GeometrySrcType != this->getGeomSrc().fVertexSrc);
+ GrAssert(kReserved_GeometrySrcType != this->getGeomSrc().fIndexSrc);
+
+ int numCmds = fCmds.count();
+ if (0 == numCmds) {
+ return;
+ }
+
+ GrAutoTRestore<bool> flushRestore(&fFlushing);
+ fFlushing = true;
+
+ fVertexPool.unlock();
+ fIndexPool.unlock();
+
+ GrDrawTarget::AutoClipRestore acr(fDstGpu);
+ AutoGeometryAndStatePush agasp(fDstGpu, kPreserve_ASRInit);
+
+ GrDrawState playbackState;
+ GrDrawState* prevDrawState = fDstGpu->drawState();
+ prevDrawState->ref();
+ fDstGpu->setDrawState(&playbackState);
+
+ GrClipData clipData;
+
+ int currState = 0;
+ int currClip = 0;
+ int currClear = 0;
+ int currDraw = 0;
+ int currStencilPath = 0;
+ int currCopySurface = 0;
+
+ for (int c = 0; c < numCmds; ++c) {
+ switch (fCmds[c]) {
+ case kDraw_Cmd: {
+ const DrawRecord& draw = fDraws[currDraw];
+ fDstGpu->setVertexSourceToBuffer(draw.fVertexBuffer);
+ if (draw.isIndexed()) {
+ fDstGpu->setIndexSourceToBuffer(draw.fIndexBuffer);
+ }
+ fDstGpu->executeDraw(draw);
+
+ ++currDraw;
+ break;
+ }
+ case kStencilPath_Cmd: {
+ const StencilPath& sp = fStencilPaths[currStencilPath];
+ fDstGpu->stencilPath(sp.fPath.get(), sp.fStroke, sp.fFill);
+ ++currStencilPath;
+ break;
+ }
+ case kSetState_Cmd:
+ fStates[currState].restoreTo(&playbackState);
+ ++currState;
+ break;
+ case kSetClip_Cmd:
+ clipData.fClipStack = &fClips[currClip];
+ clipData.fOrigin = fClipOrigins[currClip];
+ fDstGpu->setClip(&clipData);
+ ++currClip;
+ break;
+ case kClear_Cmd:
+ fDstGpu->clear(&fClears[currClear].fRect,
+ fClears[currClear].fColor,
+ fClears[currClear].fRenderTarget);
+ ++currClear;
+ break;
+ case kCopySurface_Cmd:
+ fDstGpu->copySurface(fCopySurfaces[currCopySurface].fDst.get(),
+ fCopySurfaces[currCopySurface].fSrc.get(),
+ fCopySurfaces[currCopySurface].fSrcRect,
+ fCopySurfaces[currCopySurface].fDstPoint);
+ ++currCopySurface;
+ break;
+ }
+ }
+ // we should have consumed all the states, clips, etc.
+ GrAssert(fStates.count() == currState);
+ GrAssert(fClips.count() == currClip);
+ GrAssert(fClipOrigins.count() == currClip);
+ GrAssert(fClears.count() == currClear);
+ GrAssert(fDraws.count() == currDraw);
+ GrAssert(fCopySurfaces.count() == currCopySurface);
+
+ fDstGpu->setDrawState(prevDrawState);
+ prevDrawState->unref();
+ this->reset();
+}
+
+bool GrInOrderDrawBuffer::onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ if (fDstGpu->canCopySurface(dst, src, srcRect, dstPoint)) {
+ CopySurface* cs = this->recordCopySurface();
+ cs->fDst.reset(SkRef(dst));
+ cs->fSrc.reset(SkRef(src));
+ cs->fSrcRect = srcRect;
+ cs->fDstPoint = dstPoint;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool GrInOrderDrawBuffer::onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ return fDstGpu->canCopySurface(dst, src, srcRect, dstPoint);
+}
+
+void GrInOrderDrawBuffer::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+ fDstGpu->initCopySurfaceDstDesc(src, desc);
+}
+
+void GrInOrderDrawBuffer::willReserveVertexAndIndexSpace(
+ int vertexCount,
+ int indexCount) {
+ // We use geometryHints() to know whether to flush the draw buffer. We
+ // can't flush if we are inside an unbalanced pushGeometrySource.
+ // Moreover, flushing blows away vertex and index data that was
+ // previously reserved. So if the vertex or index data is pulled from
+ // reserved space and won't be released by this request then we can't
+ // flush.
+ bool insideGeoPush = fGeoPoolStateStack.count() > 1;
+
+ bool unreleasedVertexSpace =
+ !vertexCount &&
+ kReserved_GeometrySrcType == this->getGeomSrc().fVertexSrc;
+
+ bool unreleasedIndexSpace =
+ !indexCount &&
+ kReserved_GeometrySrcType == this->getGeomSrc().fIndexSrc;
+
+ // we don't want to finalize any reserved geom on the target since
+ // we don't know that the client has finished writing to it.
+ bool targetHasReservedGeom = fDstGpu->hasReservedVerticesOrIndices();
+
+ int vcount = vertexCount;
+ int icount = indexCount;
+
+ if (!insideGeoPush &&
+ !unreleasedVertexSpace &&
+ !unreleasedIndexSpace &&
+ !targetHasReservedGeom &&
+ this->geometryHints(&vcount, &icount)) {
+
+ this->flush();
+ }
+}
+
+bool GrInOrderDrawBuffer::geometryHints(int* vertexCount,
+ int* indexCount) const {
+ // we will recommend a flush if the data could fit in a single
+ // preallocated buffer but none are left and it can't fit
+ // in the current buffer (which may not be prealloced).
+ bool flush = false;
+ if (NULL != indexCount) {
+ int32_t currIndices = fIndexPool.currentBufferIndices();
+ if (*indexCount > currIndices &&
+ (!fIndexPool.preallocatedBuffersRemaining() &&
+ *indexCount <= fIndexPool.preallocatedBufferIndices())) {
+
+ flush = true;
+ }
+ *indexCount = currIndices;
+ }
+ if (NULL != vertexCount) {
+ size_t vertexSize = this->getDrawState().getVertexSize();
+ int32_t currVertices = fVertexPool.currentBufferVertices(vertexSize);
+ if (*vertexCount > currVertices &&
+ (!fVertexPool.preallocatedBuffersRemaining() &&
+ *vertexCount <= fVertexPool.preallocatedBufferVertices(vertexSize))) {
+
+ flush = true;
+ }
+ *vertexCount = currVertices;
+ }
+ return flush;
+}
+
+bool GrInOrderDrawBuffer::onReserveVertexSpace(size_t vertexSize,
+ int vertexCount,
+ void** vertices) {
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ GrAssert(vertexCount > 0);
+ GrAssert(NULL != vertices);
+ GrAssert(0 == poolState.fUsedPoolVertexBytes);
+
+ *vertices = fVertexPool.makeSpace(vertexSize,
+ vertexCount,
+ &poolState.fPoolVertexBuffer,
+ &poolState.fPoolStartVertex);
+ return NULL != *vertices;
+}
+
+bool GrInOrderDrawBuffer::onReserveIndexSpace(int indexCount, void** indices) {
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ GrAssert(indexCount > 0);
+ GrAssert(NULL != indices);
+ GrAssert(0 == poolState.fUsedPoolIndexBytes);
+
+ *indices = fIndexPool.makeSpace(indexCount,
+ &poolState.fPoolIndexBuffer,
+ &poolState.fPoolStartIndex);
+ return NULL != *indices;
+}
+
+void GrInOrderDrawBuffer::releaseReservedVertexSpace() {
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+
+ // If we get a release vertex space call then our current source should either be reserved
+ // or array (which we copied into reserved space).
+ GrAssert(kReserved_GeometrySrcType == geoSrc.fVertexSrc ||
+ kArray_GeometrySrcType == geoSrc.fVertexSrc);
+
+ // When the caller reserved vertex buffer space we gave it back a pointer
+ // provided by the vertex buffer pool. At each draw we tracked the largest
+ // offset into the pool's pointer that was referenced. Now we return to the
+ // pool any portion at the tail of the allocation that no draw referenced.
+ size_t reservedVertexBytes = geoSrc.fVertexSize * geoSrc.fVertexCount;
+ fVertexPool.putBack(reservedVertexBytes -
+ poolState.fUsedPoolVertexBytes);
+ poolState.fUsedPoolVertexBytes = 0;
+ poolState.fPoolVertexBuffer = NULL;
+ poolState.fPoolStartVertex = 0;
+}
+
+void GrInOrderDrawBuffer::releaseReservedIndexSpace() {
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ const GeometrySrcState& geoSrc = this->getGeomSrc();
+
+ // If we get a release index space call then our current source should either be reserved
+ // or array (which we copied into reserved space).
+ GrAssert(kReserved_GeometrySrcType == geoSrc.fIndexSrc ||
+ kArray_GeometrySrcType == geoSrc.fIndexSrc);
+
+ // Similar to releaseReservedVertexSpace we return any unused portion at
+ // the tail
+ size_t reservedIndexBytes = sizeof(uint16_t) * geoSrc.fIndexCount;
+ fIndexPool.putBack(reservedIndexBytes - poolState.fUsedPoolIndexBytes);
+ poolState.fUsedPoolIndexBytes = 0;
+ poolState.fPoolIndexBuffer = NULL;
+ poolState.fPoolStartIndex = 0;
+}
+
+void GrInOrderDrawBuffer::onSetVertexSourceToArray(const void* vertexArray,
+ int vertexCount) {
+
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ GrAssert(0 == poolState.fUsedPoolVertexBytes);
+#if GR_DEBUG
+ bool success =
+#endif
+ fVertexPool.appendVertices(this->getVertexSize(),
+ vertexCount,
+ vertexArray,
+ &poolState.fPoolVertexBuffer,
+ &poolState.fPoolStartVertex);
+ GR_DEBUGASSERT(success);
+}
+
+void GrInOrderDrawBuffer::onSetIndexSourceToArray(const void* indexArray,
+ int indexCount) {
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ GrAssert(0 == poolState.fUsedPoolIndexBytes);
+#if GR_DEBUG
+ bool success =
+#endif
+ fIndexPool.appendIndices(indexCount,
+ indexArray,
+ &poolState.fPoolIndexBuffer,
+ &poolState.fPoolStartIndex);
+ GR_DEBUGASSERT(success);
+}
+
+void GrInOrderDrawBuffer::releaseVertexArray() {
+ // When the client provides an array as the vertex source we handled it
+ // by copying their array into reserved space.
+ this->GrInOrderDrawBuffer::releaseReservedVertexSpace();
+}
+
+void GrInOrderDrawBuffer::releaseIndexArray() {
+ // When the client provides an array as the index source we handled it
+ // by copying their array into reserved space.
+ this->GrInOrderDrawBuffer::releaseReservedIndexSpace();
+}
+
+void GrInOrderDrawBuffer::geometrySourceWillPush() {
+ GeometryPoolState& poolState = fGeoPoolStateStack.push_back();
+ poolState.fUsedPoolVertexBytes = 0;
+ poolState.fUsedPoolIndexBytes = 0;
+#if GR_DEBUG
+ poolState.fPoolVertexBuffer = (GrVertexBuffer*)~0;
+ poolState.fPoolStartVertex = ~0;
+ poolState.fPoolIndexBuffer = (GrIndexBuffer*)~0;
+ poolState.fPoolStartIndex = ~0;
+#endif
+}
+
+void GrInOrderDrawBuffer::geometrySourceWillPop(
+ const GeometrySrcState& restoredState) {
+ GrAssert(fGeoPoolStateStack.count() > 1);
+ fGeoPoolStateStack.pop_back();
+ GeometryPoolState& poolState = fGeoPoolStateStack.back();
+ // we have to assume that any slack we had in our vertex/index data
+ // is now unreleasable because data may have been appended later in the
+ // pool.
+ if (kReserved_GeometrySrcType == restoredState.fVertexSrc ||
+ kArray_GeometrySrcType == restoredState.fVertexSrc) {
+ poolState.fUsedPoolVertexBytes = restoredState.fVertexSize * restoredState.fVertexCount;
+ }
+ if (kReserved_GeometrySrcType == restoredState.fIndexSrc ||
+ kArray_GeometrySrcType == restoredState.fIndexSrc) {
+ poolState.fUsedPoolIndexBytes = sizeof(uint16_t) *
+ restoredState.fIndexCount;
+ }
+}
+
+bool GrInOrderDrawBuffer::needsNewState() const {
+ return fStates.empty() || !fStates.back().isEqual(this->getDrawState());
+}
+
+bool GrInOrderDrawBuffer::needsNewClip() const {
+ GrAssert(fClips.count() == fClipOrigins.count());
+ if (this->getDrawState().isClipState()) {
+ if (fClipSet &&
+ (fClips.empty() ||
+ fClips.back() != *this->getClip()->fClipStack ||
+ fClipOrigins.back() != this->getClip()->fOrigin)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void GrInOrderDrawBuffer::recordClip() {
+ fClips.push_back() = *this->getClip()->fClipStack;
+ fClipOrigins.push_back() = this->getClip()->fOrigin;
+ fClipSet = false;
+ fCmds.push_back(kSetClip_Cmd);
+}
+
+void GrInOrderDrawBuffer::recordState() {
+ fStates.push_back().saveFrom(this->getDrawState());
+ fCmds.push_back(kSetState_Cmd);
+}
+
+GrInOrderDrawBuffer::DrawRecord* GrInOrderDrawBuffer::recordDraw(const DrawInfo& info) {
+ fCmds.push_back(kDraw_Cmd);
+ return &fDraws.push_back(info);
+}
+
+GrInOrderDrawBuffer::StencilPath* GrInOrderDrawBuffer::recordStencilPath() {
+ fCmds.push_back(kStencilPath_Cmd);
+ return &fStencilPaths.push_back();
+}
+
+GrInOrderDrawBuffer::Clear* GrInOrderDrawBuffer::recordClear() {
+ fCmds.push_back(kClear_Cmd);
+ return &fClears.push_back();
+}
+
+GrInOrderDrawBuffer::CopySurface* GrInOrderDrawBuffer::recordCopySurface() {
+ fCmds.push_back(kCopySurface_Cmd);
+ return &fCopySurfaces.push_back();
+}
+
+
+void GrInOrderDrawBuffer::clipWillBeSet(const GrClipData* newClipData) {
+ INHERITED::clipWillBeSet(newClipData);
+ fClipSet = true;
+ fClipProxyState = kUnknown_ClipProxyState;
+}
diff --git a/gpu/GrInOrderDrawBuffer.h b/gpu/GrInOrderDrawBuffer.h
new file mode 100644
index 00000000..6512dccf
--- /dev/null
+++ b/gpu/GrInOrderDrawBuffer.h
@@ -0,0 +1,230 @@
+
+/*
+ * 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 GrInOrderDrawBuffer_DEFINED
+#define GrInOrderDrawBuffer_DEFINED
+
+#include "GrDrawTarget.h"
+#include "GrAllocPool.h"
+#include "GrAllocator.h"
+#include "GrPath.h"
+
+#include "SkClipStack.h"
+#include "SkStrokeRec.h"
+#include "SkTemplates.h"
+
+class GrGpu;
+class GrIndexBufferAllocPool;
+class GrVertexBufferAllocPool;
+
+/**
+ * GrInOrderDrawBuffer is an implementation of GrDrawTarget that queues up draws for eventual
+ * playback into a GrGpu. In theory one draw buffer could playback into another. When index or
+ * vertex buffers are used as geometry sources it is the callers the draw buffer only holds
+ * references to the buffers. It is the callers responsibility to ensure that the data is still
+ * valid when the draw buffer is played back into a GrGpu. Similarly, it is the caller's
+ * responsibility to ensure that all referenced textures, buffers, and render-targets are associated
+ * in the GrGpu object that the buffer is played back into. The buffer requires VB and IB pools to
+ * store geometry.
+ */
+class GrInOrderDrawBuffer : public GrDrawTarget {
+public:
+
+ /**
+ * Creates a GrInOrderDrawBuffer
+ *
+ * @param gpu the gpu object that this draw buffer flushes to.
+ * @param vertexPool pool where vertices for queued draws will be saved when
+ * the vertex source is either reserved or array.
+ * @param indexPool pool where indices for queued draws will be saved when
+ * the index source is either reserved or array.
+ */
+ GrInOrderDrawBuffer(GrGpu* gpu,
+ GrVertexBufferAllocPool* vertexPool,
+ GrIndexBufferAllocPool* indexPool);
+
+ virtual ~GrInOrderDrawBuffer();
+
+ /**
+ * Empties the draw buffer of any queued up draws. This must not be called while inside an
+ * unbalanced pushGeometrySource(). The current draw state and clip are preserved.
+ */
+ void reset();
+
+ /**
+ * This plays the queued up draws to its GrGpu target. It also resets this object (i.e. flushing
+ * is destructive). This buffer must not have an active reserved vertex or index source. Any
+ * reserved geometry on the target will be finalized because it's geometry source will be pushed
+ * before flushing and popped afterwards.
+ */
+ void flush();
+
+ // overrides from GrDrawTarget
+ virtual bool geometryHints(int* vertexCount,
+ int* indexCount) const SK_OVERRIDE;
+ virtual void clear(const SkIRect* rect,
+ GrColor color,
+ GrRenderTarget* renderTarget = NULL) SK_OVERRIDE;
+
+ virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) SK_OVERRIDE;
+
+
+protected:
+ virtual void clipWillBeSet(const GrClipData* newClip) SK_OVERRIDE;
+
+private:
+ enum Cmd {
+ kDraw_Cmd = 1,
+ kStencilPath_Cmd = 2,
+ kSetState_Cmd = 3,
+ kSetClip_Cmd = 4,
+ kClear_Cmd = 5,
+ kCopySurface_Cmd = 6,
+ };
+
+ class DrawRecord : public DrawInfo {
+ public:
+ DrawRecord(const DrawInfo& info) : DrawInfo(info) {}
+ const GrVertexBuffer* fVertexBuffer;
+ const GrIndexBuffer* fIndexBuffer;
+ };
+
+ struct StencilPath : GrNoncopyable {
+ StencilPath();
+
+ SkAutoTUnref<const GrPath> fPath;
+ SkStrokeRec fStroke;
+ SkPath::FillType fFill;
+ };
+
+ struct Clear : GrNoncopyable {
+ Clear() : fRenderTarget(NULL) {}
+ ~Clear() { GrSafeUnref(fRenderTarget); }
+
+ SkIRect fRect;
+ GrColor fColor;
+ GrRenderTarget* fRenderTarget;
+ };
+
+ struct CopySurface : GrNoncopyable {
+ SkAutoTUnref<GrSurface> fDst;
+ SkAutoTUnref<GrSurface> fSrc;
+ SkIRect fSrcRect;
+ SkIPoint fDstPoint;
+ };
+
+ // overrides from GrDrawTarget
+ virtual void onDraw(const DrawInfo&) SK_OVERRIDE;
+ virtual void onDrawRect(const SkRect& rect,
+ const SkMatrix* matrix,
+ const SkRect* localRect,
+ const SkMatrix* localMatrix) SK_OVERRIDE;
+ virtual void onStencilPath(const GrPath*, const SkStrokeRec& stroke, SkPath::FillType) SK_OVERRIDE;
+ virtual bool onReserveVertexSpace(size_t vertexSize,
+ int vertexCount,
+ void** vertices) SK_OVERRIDE;
+ virtual bool onReserveIndexSpace(int indexCount,
+ void** indices) SK_OVERRIDE;
+ virtual void releaseReservedVertexSpace() SK_OVERRIDE;
+ virtual void releaseReservedIndexSpace() SK_OVERRIDE;
+ virtual void onSetVertexSourceToArray(const void* vertexArray,
+ int vertexCount) SK_OVERRIDE;
+ virtual void onSetIndexSourceToArray(const void* indexArray,
+ int indexCount) SK_OVERRIDE;
+ virtual void releaseVertexArray() SK_OVERRIDE;
+ virtual void releaseIndexArray() SK_OVERRIDE;
+ virtual void geometrySourceWillPush() SK_OVERRIDE;
+ virtual void geometrySourceWillPop(const GeometrySrcState& restoredState) SK_OVERRIDE;
+ virtual void willReserveVertexAndIndexSpace(int vertexCount,
+ int indexCount) SK_OVERRIDE;
+ virtual bool onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) SK_OVERRIDE;
+ virtual bool onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) SK_OVERRIDE;
+
+ bool quickInsideClip(const SkRect& devBounds);
+
+ // Attempts to concat instances from info onto the previous draw. info must represent an
+ // instanced draw. The caller must have already recorded a new draw state and clip if necessary.
+ int concatInstancedDraw(const DrawInfo& info);
+
+ // we lazily record state and clip changes in order to skip clips and states that have no
+ // effect.
+ bool needsNewState() const;
+ bool needsNewClip() const;
+
+ // these functions record a command
+ void recordState();
+ void recordClip();
+ DrawRecord* recordDraw(const DrawInfo&);
+ StencilPath* recordStencilPath();
+ Clear* recordClear();
+ CopySurface* recordCopySurface();
+
+ // TODO: Use a single allocator for commands and records
+ enum {
+ kCmdPreallocCnt = 32,
+ kDrawPreallocCnt = 8,
+ kStencilPathPreallocCnt = 8,
+ kStatePreallocCnt = 8,
+ kClipPreallocCnt = 8,
+ kClearPreallocCnt = 4,
+ kGeoPoolStatePreAllocCnt = 4,
+ kCopySurfacePreallocCnt = 4,
+ };
+
+ SkSTArray<kCmdPreallocCnt, uint8_t, true> fCmds;
+ GrSTAllocator<kDrawPreallocCnt, DrawRecord> fDraws;
+ GrSTAllocator<kStatePreallocCnt, StencilPath> fStencilPaths;
+ GrSTAllocator<kStatePreallocCnt, GrDrawState::DeferredState> fStates;
+ GrSTAllocator<kClearPreallocCnt, Clear> fClears;
+ GrSTAllocator<kCopySurfacePreallocCnt, CopySurface> fCopySurfaces;
+ GrSTAllocator<kClipPreallocCnt, SkClipStack> fClips;
+ GrSTAllocator<kClipPreallocCnt, SkIPoint> fClipOrigins;
+
+ GrDrawTarget* fDstGpu;
+
+ bool fClipSet;
+
+ enum ClipProxyState {
+ kUnknown_ClipProxyState,
+ kValid_ClipProxyState,
+ kInvalid_ClipProxyState
+ };
+ ClipProxyState fClipProxyState;
+ SkRect fClipProxy;
+
+ GrVertexBufferAllocPool& fVertexPool;
+
+ GrIndexBufferAllocPool& fIndexPool;
+
+ struct GeometryPoolState {
+ const GrVertexBuffer* fPoolVertexBuffer;
+ int fPoolStartVertex;
+ const GrIndexBuffer* fPoolIndexBuffer;
+ int fPoolStartIndex;
+ // caller may conservatively over reserve vertices / indices.
+ // we release unused space back to allocator if possible
+ // can only do this if there isn't an intervening pushGeometrySource()
+ size_t fUsedPoolVertexBytes;
+ size_t fUsedPoolIndexBytes;
+ };
+ SkSTArray<kGeoPoolStatePreAllocCnt, GeometryPoolState> fGeoPoolStateStack;
+
+ bool fFlushing;
+
+ typedef GrDrawTarget INHERITED;
+};
+
+#endif
diff --git a/gpu/GrIndexBuffer.h b/gpu/GrIndexBuffer.h
new file mode 100644
index 00000000..69ee86f7
--- /dev/null
+++ b/gpu/GrIndexBuffer.h
@@ -0,0 +1,33 @@
+
+/*
+ * 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 GrIndexBuffer_DEFINED
+#define GrIndexBuffer_DEFINED
+
+#include "GrGeometryBuffer.h"
+
+class GrIndexBuffer : public GrGeometryBuffer {
+public:
+ /**
+ * Retrieves the maximum number of quads that could be rendered
+ * from the index buffer (using kTriangles_GrPrimitiveType).
+ * @return the maximum number of quads using full size of index buffer.
+ */
+ int maxQuads() const {
+ return this->sizeInBytes() / (sizeof(uint16_t) * 6);
+ }
+protected:
+ GrIndexBuffer(GrGpu* gpu, bool isWrapped, size_t sizeInBytes, bool dynamic, bool cpuBacked)
+ : INHERITED(gpu, isWrapped, sizeInBytes, dynamic, cpuBacked) {}
+private:
+ typedef GrGeometryBuffer INHERITED;
+};
+
+#endif
diff --git a/gpu/GrMemory.cpp b/gpu/GrMemory.cpp
new file mode 100644
index 00000000..bf96b0bc
--- /dev/null
+++ b/gpu/GrMemory.cpp
@@ -0,0 +1,26 @@
+
+/*
+ * 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 <stdlib.h>
+#include "GrTypes.h"
+
+void* GrMalloc(size_t bytes) {
+ void* ptr = ::malloc(bytes);
+ if (NULL == ptr) {
+ ::exit(-1);
+ }
+ return ptr;
+}
+
+void GrFree(void* ptr) {
+ if (ptr) {
+ ::free(ptr);
+ }
+}
diff --git a/gpu/GrMemoryPool.cpp b/gpu/GrMemoryPool.cpp
new file mode 100644
index 00000000..fc777e0d
--- /dev/null
+++ b/gpu/GrMemoryPool.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 "GrMemoryPool.h"
+
+#if GR_DEBUG
+ #define VALIDATE this->validate()
+#else
+ #define VALIDATE
+#endif
+
+GrMemoryPool::GrMemoryPool(size_t preallocSize, size_t minAllocSize) {
+ GR_DEBUGCODE(fAllocationCnt = 0);
+
+ minAllocSize = GrMax<size_t>(minAllocSize, 1 << 10);
+ fMinAllocSize = GrSizeAlignUp(minAllocSize + kPerAllocPad, kAlignment),
+ fPreallocSize = GrSizeAlignUp(preallocSize + kPerAllocPad, kAlignment);
+ fPreallocSize = GrMax(fPreallocSize, fMinAllocSize);
+
+ fHead = CreateBlock(fPreallocSize);
+ fTail = fHead;
+ fHead->fNext = NULL;
+ fHead->fPrev = NULL;
+ VALIDATE;
+};
+
+GrMemoryPool::~GrMemoryPool() {
+ VALIDATE;
+ GrAssert(0 == fAllocationCnt);
+ GrAssert(fHead == fTail);
+ GrAssert(0 == fHead->fLiveCount);
+ DeleteBlock(fHead);
+};
+
+void* GrMemoryPool::allocate(size_t size) {
+ VALIDATE;
+ size = GrSizeAlignUp(size, kAlignment);
+ size += kPerAllocPad;
+ if (fTail->fFreeSize < size) {
+ int blockSize = size;
+ blockSize = GrMax<size_t>(blockSize, fMinAllocSize);
+ BlockHeader* block = CreateBlock(blockSize);
+
+ block->fPrev = fTail;
+ block->fNext = NULL;
+ GrAssert(NULL == fTail->fNext);
+ fTail->fNext = block;
+ fTail = block;
+ }
+ GrAssert(fTail->fFreeSize >= size);
+ intptr_t ptr = fTail->fCurrPtr;
+ // We stash a pointer to the block header, just before the allocated space,
+ // so that we can decrement the live count on delete in constant time.
+ *reinterpret_cast<BlockHeader**>(ptr) = fTail;
+ ptr += kPerAllocPad;
+ fTail->fPrevPtr = fTail->fCurrPtr;
+ fTail->fCurrPtr += size;
+ fTail->fFreeSize -= size;
+ fTail->fLiveCount += 1;
+ GR_DEBUGCODE(++fAllocationCnt);
+ VALIDATE;
+ return reinterpret_cast<void*>(ptr);
+}
+
+void GrMemoryPool::release(void* p) {
+ VALIDATE;
+ intptr_t ptr = reinterpret_cast<intptr_t>(p) - kPerAllocPad;
+ BlockHeader* block = *reinterpret_cast<BlockHeader**>(ptr);
+ if (1 == block->fLiveCount) {
+ // the head block is special, it is reset rather than deleted
+ if (fHead == block) {
+ fHead->fCurrPtr = reinterpret_cast<intptr_t>(fHead) +
+ kHeaderSize;
+ fHead->fLiveCount = 0;
+ fHead->fFreeSize = fPreallocSize;
+ } else {
+ BlockHeader* prev = block->fPrev;
+ BlockHeader* next = block->fNext;
+ GrAssert(prev);
+ prev->fNext = next;
+ if (next) {
+ next->fPrev = prev;
+ } else {
+ GrAssert(fTail == block);
+ fTail = prev;
+ }
+ DeleteBlock(block);
+ }
+ } else {
+ --block->fLiveCount;
+ // Trivial reclaim: if we're releasing the most recent allocation, reuse it
+ if (block->fPrevPtr == ptr) {
+ block->fFreeSize += (block->fCurrPtr - block->fPrevPtr);
+ block->fCurrPtr = block->fPrevPtr;
+ }
+ }
+ GR_DEBUGCODE(--fAllocationCnt);
+ VALIDATE;
+}
+
+GrMemoryPool::BlockHeader* GrMemoryPool::CreateBlock(size_t size) {
+ BlockHeader* block =
+ reinterpret_cast<BlockHeader*>(GrMalloc(size + kHeaderSize));
+ // we assume malloc gives us aligned memory
+ GrAssert(!(reinterpret_cast<intptr_t>(block) % kAlignment));
+ block->fLiveCount = 0;
+ block->fFreeSize = size;
+ block->fCurrPtr = reinterpret_cast<intptr_t>(block) + kHeaderSize;
+ block->fPrevPtr = 0; // gcc warns on assigning NULL to an intptr_t.
+ return block;
+}
+
+void GrMemoryPool::DeleteBlock(BlockHeader* block) {
+ GrFree(block);
+}
+
+void GrMemoryPool::validate() {
+#ifdef SK_DEBUG
+ BlockHeader* block = fHead;
+ BlockHeader* prev = NULL;
+ GrAssert(block);
+ int allocCount = 0;
+ do {
+ allocCount += block->fLiveCount;
+ GrAssert(prev == block->fPrev);
+ if (NULL != prev) {
+ GrAssert(prev->fNext == block);
+ }
+
+ intptr_t b = reinterpret_cast<intptr_t>(block);
+ size_t ptrOffset = block->fCurrPtr - b;
+ size_t totalSize = ptrOffset + block->fFreeSize;
+ size_t userSize = totalSize - kHeaderSize;
+ intptr_t userStart = b + kHeaderSize;
+
+ GrAssert(!(b % kAlignment));
+ GrAssert(!(totalSize % kAlignment));
+ GrAssert(!(userSize % kAlignment));
+ GrAssert(!(block->fCurrPtr % kAlignment));
+ if (fHead != block) {
+ GrAssert(block->fLiveCount);
+ GrAssert(userSize >= fMinAllocSize);
+ } else {
+ GrAssert(userSize == fPreallocSize);
+ }
+ if (!block->fLiveCount) {
+ GrAssert(ptrOffset == kHeaderSize);
+ GrAssert(userStart == block->fCurrPtr);
+ } else {
+ GrAssert(block == *reinterpret_cast<BlockHeader**>(userStart));
+ }
+ prev = block;
+ } while ((block = block->fNext));
+ GrAssert(allocCount == fAllocationCnt);
+ GrAssert(prev == fTail);
+#endif
+}
diff --git a/gpu/GrMemoryPool.h b/gpu/GrMemoryPool.h
new file mode 100644
index 00000000..b33ee824
--- /dev/null
+++ b/gpu/GrMemoryPool.h
@@ -0,0 +1,80 @@
+/*
+ * 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 GrMemoryPool_DEFINED
+#define GrMemoryPool_DEFINED
+
+#include "GrTypes.h"
+
+/**
+ * Allocates memory in blocks and parcels out space in the blocks for allocation
+ * requests. It is optimized for allocate / release speed over memory
+ * effeciency. The interface is designed to be used to implement operator new
+ * and delete overrides. All allocations are expected to be released before the
+ * pool's destructor is called. Allocations will be 8-byte aligned.
+ */
+class GrMemoryPool {
+public:
+ /**
+ * Prealloc size is the amount of space to make available at pool creation
+ * time and keep around until pool destruction. The min alloc size is the
+ * smallest allowed size of additional allocations.
+ */
+ GrMemoryPool(size_t preallocSize, size_t minAllocSize);
+
+ ~GrMemoryPool();
+
+ /**
+ * Allocates memory. The memory must be freed with release().
+ */
+ void* allocate(size_t size);
+
+ /**
+ * p must have been returned by allocate()
+ */
+ void release(void* p);
+
+ /**
+ * Returns true if there are no unreleased allocations.
+ */
+ bool isEmpty() const { return fTail == fHead && !fHead->fLiveCount; }
+
+private:
+ struct BlockHeader;
+
+ static BlockHeader* CreateBlock(size_t size);
+
+ static void DeleteBlock(BlockHeader* block);
+
+ void validate();
+
+ struct BlockHeader {
+ BlockHeader* fNext; ///< doubly-linked list of blocks.
+ BlockHeader* fPrev;
+ int fLiveCount; ///< number of outstanding allocations in the
+ ///< block.
+ intptr_t fCurrPtr; ///< ptr to the start of blocks free space.
+ intptr_t fPrevPtr; ///< ptr to the last allocation made
+ size_t fFreeSize; ///< amount of free space left in the block.
+ };
+
+ enum {
+ // We assume this alignment is good enough for everybody.
+ kAlignment = 8,
+ kHeaderSize = GR_CT_ALIGN_UP(sizeof(BlockHeader), kAlignment),
+ kPerAllocPad = GR_CT_ALIGN_UP(sizeof(BlockHeader*), kAlignment),
+ };
+ size_t fPreallocSize;
+ size_t fMinAllocSize;
+ BlockHeader* fHead;
+ BlockHeader* fTail;
+#if GR_DEBUG
+ int fAllocationCnt;
+#endif
+};
+
+#endif
diff --git a/gpu/GrOvalRenderer.cpp b/gpu/GrOvalRenderer.cpp
new file mode 100644
index 00000000..6ea922c2
--- /dev/null
+++ b/gpu/GrOvalRenderer.cpp
@@ -0,0 +1,859 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrOvalRenderer.h"
+
+#include "GrEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLSL.h"
+#include "GrTBackendEffectFactory.h"
+
+#include "GrDrawState.h"
+#include "GrDrawTarget.h"
+#include "GrGpu.h"
+
+#include "SkRRect.h"
+#include "SkStrokeRec.h"
+
+SK_DEFINE_INST_COUNT(GrOvalRenderer)
+
+namespace {
+
+struct CircleVertex {
+ GrPoint fPos;
+ GrPoint fOffset;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
+};
+
+struct EllipseVertex {
+ GrPoint fPos;
+ GrPoint fOffset;
+ GrPoint fOuterRadii;
+ GrPoint fInnerRadii;
+};
+
+inline bool circle_stays_circle(const SkMatrix& m) {
+ return m.isSimilarity();
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The output of this effect is a modulation of the input color and coverage for a circle,
+ * specified as offset_x, offset_y (both from center point), outer radius and inner radius.
+ */
+
+class CircleEdgeEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(bool stroke) {
+ GR_CREATE_STATIC_EFFECT(gCircleStrokeEdge, CircleEdgeEffect, (true));
+ GR_CREATE_STATIC_EFFECT(gCircleFillEdge, CircleEdgeEffect, (false));
+
+ if (stroke) {
+ gCircleStrokeEdge->ref();
+ return gCircleStrokeEdge;
+ } else {
+ gCircleFillEdge->ref();
+ return gCircleFillEdge;
+ }
+ }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<CircleEdgeEffect>::getInstance();
+ }
+
+ virtual ~CircleEdgeEffect() {}
+
+ static const char* Name() { return "CircleEdge"; }
+
+ inline bool isStroked() const { return fStroke; }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const CircleEdgeEffect& circleEffect = drawEffect.castEffect<CircleEdgeEffect>();
+ const char *vsName, *fsName;
+ builder->addVarying(kVec4f_GrSLType, "CircleEdge", &vsName, &fsName);
+
+ const SkString* attrName =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
+
+ builder->fsCodeAppendf("\tfloat d = length(%s.xy);\n", fsName);
+ builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.z - d, 0.0, 1.0);\n", fsName);
+ if (circleEffect.isStroked()) {
+ builder->fsCodeAppendf("\tfloat innerAlpha = clamp(d - %s.w, 0.0, 1.0);\n", fsName);
+ builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n");
+ }
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const CircleEdgeEffect& circleEffect = drawEffect.castEffect<CircleEdgeEffect>();
+
+ return circleEffect.isStroked() ? 0x1 : 0x0;
+ }
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+
+private:
+ CircleEdgeEffect(bool stroke) : GrEffect() {
+ this->addVertexAttrib(kVec4f_GrSLType);
+ fStroke = stroke;
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ const CircleEdgeEffect& cee = CastEffect<CircleEdgeEffect>(other);
+ return cee.fStroke == fStroke;
+ }
+
+ bool fStroke;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(CircleEdgeEffect);
+
+GrEffectRef* CircleEdgeEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ return CircleEdgeEffect::Create(random->nextBool());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The output of this effect is a modulation of the input color and coverage for an axis-aligned
+ * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii,
+ * in both x and y directions.
+ *
+ * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0.
+ */
+
+class EllipseEdgeEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(bool stroke) {
+ GR_CREATE_STATIC_EFFECT(gEllipseStrokeEdge, EllipseEdgeEffect, (true));
+ GR_CREATE_STATIC_EFFECT(gEllipseFillEdge, EllipseEdgeEffect, (false));
+
+ if (stroke) {
+ gEllipseStrokeEdge->ref();
+ return gEllipseStrokeEdge;
+ } else {
+ gEllipseFillEdge->ref();
+ return gEllipseFillEdge;
+ }
+ }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0;
+ }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<EllipseEdgeEffect>::getInstance();
+ }
+
+ virtual ~EllipseEdgeEffect() {}
+
+ static const char* Name() { return "EllipseEdge"; }
+
+ inline bool isStroked() const { return fStroke; }
+
+ class GLEffect : public GrGLEffect {
+ public:
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<EllipseEdgeEffect>();
+
+ const char *vsOffsetName, *fsOffsetName;
+ const char *vsRadiiName, *fsRadiiName;
+
+ builder->addVarying(kVec2f_GrSLType, "EllipseOffsets", &vsOffsetName, &fsOffsetName);
+ const SkString* attr0Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsOffsetName, attr0Name->c_str());
+
+ builder->addVarying(kVec4f_GrSLType, "EllipseRadii", &vsRadiiName, &fsRadiiName);
+ const SkString* attr1Name =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[1]);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsRadiiName, attr1Name->c_str());
+
+ // for outer curve
+ builder->fsCodeAppendf("\tvec2 scaledOffset = %s*%s.xy;\n", fsOffsetName, fsRadiiName);
+ builder->fsCodeAppend("\tfloat test = dot(scaledOffset, scaledOffset) - 1.0;\n");
+ builder->fsCodeAppendf("\tvec2 grad = 2.0*scaledOffset*%s.xy;\n", fsRadiiName);
+ builder->fsCodeAppend("\tfloat grad_dot = dot(grad, grad);\n");
+ // we need to clamp the length^2 of the gradiant vector to a non-zero value, because
+ // on the Nexus 4 the undefined result of inversesqrt(0) drops out an entire tile
+ // TODO: restrict this to Adreno-only
+ builder->fsCodeAppend("\tgrad_dot = max(grad_dot, 1.0e-4);\n");
+ builder->fsCodeAppend("\tfloat invlen = inversesqrt(grad_dot);\n");
+ builder->fsCodeAppend("\tfloat edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);\n");
+
+ // for inner curve
+ if (ellipseEffect.isStroked()) {
+ builder->fsCodeAppendf("\tscaledOffset = %s*%s.zw;\n", fsOffsetName, fsRadiiName);
+ builder->fsCodeAppend("\ttest = dot(scaledOffset, scaledOffset) - 1.0;\n");
+ builder->fsCodeAppendf("\tgrad = 2.0*scaledOffset*%s.zw;\n", fsRadiiName);
+ builder->fsCodeAppend("\tinvlen = inversesqrt(dot(grad, grad));\n");
+ builder->fsCodeAppend("\tedgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);\n");
+ }
+
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<EllipseEdgeEffect>();
+
+ return ellipseEffect.isStroked() ? 0x1 : 0x0;
+ }
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {
+ }
+
+ private:
+ typedef GrGLEffect INHERITED;
+ };
+
+private:
+ EllipseEdgeEffect(bool stroke) : GrEffect() {
+ this->addVertexAttrib(kVec2f_GrSLType);
+ this->addVertexAttrib(kVec4f_GrSLType);
+ fStroke = stroke;
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ const EllipseEdgeEffect& eee = CastEffect<EllipseEdgeEffect>(other);
+ return eee.fStroke == fStroke;
+ }
+
+ bool fStroke;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(EllipseEdgeEffect);
+
+GrEffectRef* EllipseEdgeEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ return EllipseEdgeEffect::Create(random->nextBool());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrOvalRenderer::reset() {
+ GrSafeSetNull(fRRectIndexBuffer);
+}
+
+bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, bool useAA,
+ const SkRect& oval, const SkStrokeRec& stroke)
+{
+ if (!useAA) {
+ return false;
+ }
+
+ const SkMatrix& vm = context->getMatrix();
+
+ // we can draw circles
+ if (SkScalarNearlyEqual(oval.width(), oval.height())
+ && circle_stays_circle(vm)) {
+ this->drawCircle(target, useAA, oval, stroke);
+
+ // and axis-aligned ellipses only
+ } else if (vm.rectStaysRect()) {
+ return this->drawEllipse(target, useAA, oval, stroke);
+
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+
+// position + edge
+extern const GrVertexAttrib gCircleVertexAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
+};
+
+};
+
+void GrOvalRenderer::drawCircle(GrDrawTarget* target,
+ bool useAA,
+ const SkRect& circle,
+ const SkStrokeRec& stroke)
+{
+ GrDrawState* drawState = target->drawState();
+
+ const SkMatrix& vm = drawState->getViewMatrix();
+ GrPoint center = GrPoint::Make(circle.centerX(), circle.centerY());
+ vm.mapPoints(&center, 1);
+ SkScalar radius = vm.mapRadius(SkScalarHalf(circle.width()));
+ SkScalar strokeWidth = vm.mapRadius(stroke.getWidth());
+
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(drawState)) {
+ return;
+ }
+
+ drawState->setVertexAttribs<gCircleVertexAttribs>(SK_ARRAY_COUNT(gCircleVertexAttribs));
+ GrAssert(sizeof(CircleVertex) == drawState->getVertexSize());
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return;
+ }
+
+ CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
+
+ SkStrokeRec::Style style = stroke.getStyle();
+ bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style);
+
+ GrEffectRef* effect = CircleEdgeEffect::Create(isStroked);
+ static const int kCircleEdgeAttrIndex = 1;
+ drawState->addCoverageEffect(effect, kCircleEdgeAttrIndex)->unref();
+
+ SkScalar innerRadius = 0.0f;
+ SkScalar outerRadius = radius;
+ SkScalar halfWidth = 0;
+ if (style != SkStrokeRec::kFill_Style) {
+ if (SkScalarNearlyZero(strokeWidth)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(strokeWidth);
+ }
+
+ outerRadius += halfWidth;
+ if (isStroked) {
+ innerRadius = radius - halfWidth;
+ isStroked = (innerRadius > 0);
+ }
+ }
+
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // clamp(distance-to-center - radius, 0, 1). Second, the outer radius is used to compute the
+ // verts of the bounding box that is rendered and the outset ensures the box will cover all
+ // pixels partially covered by the circle.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+
+ SkRect bounds = SkRect::MakeLTRB(
+ center.fX - outerRadius,
+ center.fY - outerRadius,
+ center.fX + outerRadius,
+ center.fY + outerRadius
+ );
+
+ verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop);
+ verts[0].fOffset = SkPoint::Make(-outerRadius, -outerRadius);
+ verts[0].fOuterRadius = outerRadius;
+ verts[0].fInnerRadius = innerRadius;
+
+ verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+ verts[1].fOffset = SkPoint::Make(outerRadius, -outerRadius);
+ verts[1].fOuterRadius = outerRadius;
+ verts[1].fInnerRadius = innerRadius;
+
+ verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom);
+ verts[2].fOffset = SkPoint::Make(-outerRadius, outerRadius);
+ verts[2].fOuterRadius = outerRadius;
+ verts[2].fInnerRadius = innerRadius;
+
+ verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+ verts[3].fOffset = SkPoint::Make(outerRadius, outerRadius);
+ verts[3].fOuterRadius = outerRadius;
+ verts[3].fInnerRadius = innerRadius;
+
+ target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// position + edge
+extern const GrVertexAttrib gEllipseVertexAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding},
+ {kVec4f_GrVertexAttribType, 2*sizeof(GrPoint), kEffect_GrVertexAttribBinding}
+};
+
+};
+
+bool GrOvalRenderer::drawEllipse(GrDrawTarget* target,
+ bool useAA,
+ const SkRect& ellipse,
+ const SkStrokeRec& stroke)
+{
+ GrDrawState* drawState = target->drawState();
+#ifdef SK_DEBUG
+ {
+ // we should have checked for this previously
+ bool isAxisAlignedEllipse = drawState->getViewMatrix().rectStaysRect();
+ SkASSERT(useAA && isAxisAlignedEllipse);
+ }
+#endif
+
+ // do any matrix crunching before we reset the draw state for device coords
+ const SkMatrix& vm = drawState->getViewMatrix();
+ GrPoint center = GrPoint::Make(ellipse.centerX(), ellipse.centerY());
+ vm.mapPoints(&center, 1);
+ SkScalar ellipseXRadius = SkScalarHalf(ellipse.width());
+ SkScalar ellipseYRadius = SkScalarHalf(ellipse.height());
+ SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*ellipseXRadius +
+ vm[SkMatrix::kMSkewY]*ellipseYRadius);
+ SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*ellipseXRadius +
+ vm[SkMatrix::kMScaleY]*ellipseYRadius);
+
+ // do (potentially) anisotropic mapping of stroke
+ SkVector scaledStroke;
+ SkScalar strokeWidth = stroke.getWidth();
+ scaledStroke.fX = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMScaleX] + vm[SkMatrix::kMSkewY]));
+ scaledStroke.fY = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMSkewX] + vm[SkMatrix::kMScaleY]));
+
+ SkStrokeRec::Style style = stroke.getStyle();
+ bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style);
+
+ SkScalar innerXRadius = 0.0f;
+ SkScalar innerYRadius = 0.0f;
+ if (SkStrokeRec::kFill_Style != style) {
+ if (SkScalarNearlyZero(scaledStroke.length())) {
+ scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
+ } else {
+ scaledStroke.scale(SK_ScalarHalf);
+ }
+
+ // we only handle thick strokes for near-circular ellipses
+ if (scaledStroke.length() > SK_ScalarHalf &&
+ (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRadius)) {
+ return false;
+ }
+
+ // we don't handle it if curvature of the stroke is less than curvature of the ellipse
+ if (scaledStroke.fX*(yRadius*yRadius) < (scaledStroke.fY*scaledStroke.fY)*xRadius ||
+ scaledStroke.fY*(xRadius*xRadius) < (scaledStroke.fX*scaledStroke.fX)*yRadius) {
+ return false;
+ }
+
+ // this is legit only if scale & translation (which should be the case at the moment)
+ if (isStroked) {
+ innerXRadius = xRadius - scaledStroke.fX;
+ innerYRadius = yRadius - scaledStroke.fY;
+ isStroked = (innerXRadius > 0 && innerYRadius > 0);
+ }
+
+ xRadius += scaledStroke.fX;
+ yRadius += scaledStroke.fY;
+ }
+
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(drawState)) {
+ return false;
+ }
+
+ drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllipseVertexAttribs));
+ GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize());
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return false;
+ }
+
+ EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices());
+
+ GrEffectRef* effect = EllipseEdgeEffect::Create(isStroked);
+
+ static const int kEllipseCenterAttrIndex = 1;
+ static const int kEllipseEdgeAttrIndex = 2;
+ drawState->addCoverageEffect(effect, kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref();
+
+ // Compute the reciprocals of the radii here to save time in the shader
+ SkScalar xRadRecip = SkScalarInvert(xRadius);
+ SkScalar yRadRecip = SkScalarInvert(yRadius);
+ SkScalar xInnerRadRecip = SkScalarInvert(innerXRadius);
+ SkScalar yInnerRadRecip = SkScalarInvert(innerYRadius);
+
+ // We've extended the outer x radius out half a pixel to antialias.
+ // This will also expand the rect so all the pixels will be captured.
+ // TODO: Consider if we should use sqrt(2)/2 instead
+ xRadius += SK_ScalarHalf;
+ yRadius += SK_ScalarHalf;
+
+ SkRect bounds = SkRect::MakeLTRB(
+ center.fX - xRadius,
+ center.fY - yRadius,
+ center.fX + xRadius,
+ center.fY + yRadius
+ );
+
+ verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop);
+ verts[0].fOffset = SkPoint::Make(-xRadius, -yRadius);
+ verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+ verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+ verts[1].fOffset = SkPoint::Make(xRadius, -yRadius);
+ verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+ verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom);
+ verts[2].fOffset = SkPoint::Make(-xRadius, yRadius);
+ verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+ verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+ verts[3].fOffset = SkPoint::Make(xRadius, yRadius);
+ verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+
+ target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const uint16_t gRRectIndices[] = {
+ // corners
+ 0, 1, 5, 0, 5, 4,
+ 2, 3, 7, 2, 7, 6,
+ 8, 9, 13, 8, 13, 12,
+ 10, 11, 15, 10, 15, 14,
+
+ // edges
+ 1, 2, 6, 1, 6, 5,
+ 4, 5, 9, 4, 9, 8,
+ 6, 7, 11, 6, 11, 10,
+ 9, 10, 14, 9, 14, 13,
+
+ // center
+ // we place this at the end so that we can ignore these indices when rendering stroke-only
+ 5, 6, 10, 5, 10, 9
+};
+
+
+GrIndexBuffer* GrOvalRenderer::rRectIndexBuffer(GrGpu* gpu) {
+ if (NULL == fRRectIndexBuffer) {
+ fRRectIndexBuffer =
+ gpu->createIndexBuffer(sizeof(gRRectIndices), false);
+ if (NULL != fRRectIndexBuffer) {
+#if GR_DEBUG
+ bool updated =
+#endif
+ fRRectIndexBuffer->updateData(gRRectIndices,
+ sizeof(gRRectIndices));
+ GR_DEBUGASSERT(updated);
+ }
+ }
+ return fRRectIndexBuffer;
+}
+
+bool GrOvalRenderer::drawSimpleRRect(GrDrawTarget* target, GrContext* context, bool useAA,
+ const SkRRect& rrect, const SkStrokeRec& stroke)
+{
+ // only anti-aliased rrects for now
+ if (!useAA) {
+ return false;
+ }
+
+ const SkMatrix& vm = context->getMatrix();
+#ifdef SK_DEBUG
+ {
+ // we should have checked for this previously
+ SkASSERT(useAA && vm.rectStaysRect() && rrect.isSimple());
+ }
+#endif
+
+ // do any matrix crunching before we reset the draw state for device coords
+ const SkRect& rrectBounds = rrect.getBounds();
+ SkRect bounds;
+ vm.mapRect(&bounds, rrectBounds);
+
+ SkVector radii = rrect.getSimpleRadii();
+ SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*radii.fX +
+ vm[SkMatrix::kMSkewY]*radii.fY);
+ SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*radii.fX +
+ vm[SkMatrix::kMScaleY]*radii.fY);
+
+ // if hairline stroke is greater than radius, we don't handle that right now
+ SkStrokeRec::Style style = stroke.getStyle();
+ if (SkStrokeRec::kHairline_Style == style &&
+ (SK_ScalarHalf >= xRadius || SK_ScalarHalf >= yRadius)) {
+ return false;
+ }
+
+ // do (potentially) anisotropic mapping of stroke
+ SkVector scaledStroke;
+ SkScalar strokeWidth = stroke.getWidth();
+ scaledStroke.fX = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMScaleX] + vm[SkMatrix::kMSkewY]));
+ scaledStroke.fY = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMSkewX] + vm[SkMatrix::kMScaleY]));
+
+ // if half of strokewidth is greater than radius, we don't handle that right now
+ if (SK_ScalarHalf*scaledStroke.fX >= xRadius || SK_ScalarHalf*scaledStroke.fY >= yRadius) {
+ return false;
+ }
+
+ // reset to device coordinates
+ GrDrawState* drawState = target->drawState();
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(drawState)) {
+ return false;
+ }
+
+ bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style);
+
+ GrIndexBuffer* indexBuffer = this->rRectIndexBuffer(context->getGpu());
+ if (NULL == indexBuffer) {
+ GrPrintf("Failed to create index buffer!\n");
+ return false;
+ }
+
+ // if the corners are circles, use the circle renderer
+ if ((!isStroked || scaledStroke.fX == scaledStroke.fY) && xRadius == yRadius) {
+ drawState->setVertexAttribs<gCircleVertexAttribs>(SK_ARRAY_COUNT(gCircleVertexAttribs));
+ GrAssert(sizeof(CircleVertex) == drawState->getVertexSize());
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return false;
+ }
+ CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
+
+ SkScalar innerRadius = 0.0f;
+ SkScalar outerRadius = xRadius;
+ SkScalar halfWidth = 0;
+ if (style != SkStrokeRec::kFill_Style) {
+ if (SkScalarNearlyZero(scaledStroke.fX)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(scaledStroke.fX);
+ }
+
+ if (isStroked) {
+ innerRadius = xRadius - halfWidth;
+ isStroked = (innerRadius > 0);
+ }
+ outerRadius += halfWidth;
+ bounds.outset(halfWidth, halfWidth);
+ }
+
+ GrEffectRef* effect = CircleEdgeEffect::Create(isStroked);
+ static const int kCircleEdgeAttrIndex = 1;
+ drawState->addCoverageEffect(effect, kCircleEdgeAttrIndex)->unref();
+
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // clamp(distance-to-center - radius, 0, 1). Second, the outer radius is used to compute the
+ // verts of the bounding box that is rendered and the outset ensures the box will cover all
+ // pixels partially covered by the circle.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+
+ // Expand the rect so all the pixels will be captured.
+ bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+
+ SkScalar yCoords[4] = {
+ bounds.fTop,
+ bounds.fTop + outerRadius,
+ bounds.fBottom - outerRadius,
+ bounds.fBottom
+ };
+ SkScalar yOuterRadii[4] = {
+ -outerRadius,
+ 0,
+ 0,
+ outerRadius
+ };
+ for (int i = 0; i < 4; ++i) {
+ verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+ verts->fOffset = SkPoint::Make(-outerRadius, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fInnerRadius = innerRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fInnerRadius = innerRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fInnerRadius = innerRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+ verts->fOffset = SkPoint::Make(outerRadius, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fInnerRadius = innerRadius;
+ verts++;
+ }
+
+ // drop out the middle quad if we're stroked
+ int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_COUNT(gRRectIndices);
+ target->setIndexSourceToBuffer(indexBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bounds);
+
+ // otherwise we use the ellipse renderer
+ } else {
+ drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllipseVertexAttribs));
+ GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize());
+
+ SkScalar innerXRadius = 0.0f;
+ SkScalar innerYRadius = 0.0f;
+ if (SkStrokeRec::kFill_Style != style) {
+ if (SkScalarNearlyZero(scaledStroke.length())) {
+ scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf);
+ } else {
+ scaledStroke.scale(SK_ScalarHalf);
+ }
+
+ // we only handle thick strokes for near-circular ellipses
+ if (scaledStroke.length() > SK_ScalarHalf &&
+ (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRadius)) {
+ return false;
+ }
+
+ // we don't handle it if curvature of the stroke is less than curvature of the ellipse
+ if (scaledStroke.fX*(yRadius*yRadius) < (scaledStroke.fY*scaledStroke.fY)*xRadius ||
+ scaledStroke.fY*(xRadius*xRadius) < (scaledStroke.fX*scaledStroke.fX)*yRadius) {
+ return false;
+ }
+
+ // this is legit only if scale & translation (which should be the case at the moment)
+ if (isStroked) {
+ innerXRadius = xRadius - scaledStroke.fX;
+ innerYRadius = yRadius - scaledStroke.fY;
+ isStroked = (innerXRadius > 0 && innerYRadius > 0);
+ }
+
+ xRadius += scaledStroke.fX;
+ yRadius += scaledStroke.fY;
+ bounds.outset(scaledStroke.fX, scaledStroke.fY);
+ }
+
+ GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0);
+ if (!geo.succeeded()) {
+ GrPrintf("Failed to get space for vertices!\n");
+ return false;
+ }
+ EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices());
+
+ GrEffectRef* effect = EllipseEdgeEffect::Create(isStroked);
+ static const int kEllipseOffsetAttrIndex = 1;
+ static const int kEllipseRadiiAttrIndex = 2;
+ drawState->addCoverageEffect(effect,
+ kEllipseOffsetAttrIndex, kEllipseRadiiAttrIndex)->unref();
+
+ // Compute the reciprocals of the radii here to save time in the shader
+ SkScalar xRadRecip = SkScalarInvert(xRadius);
+ SkScalar yRadRecip = SkScalarInvert(yRadius);
+ SkScalar xInnerRadRecip = SkScalarInvert(innerXRadius);
+ SkScalar yInnerRadRecip = SkScalarInvert(innerYRadius);
+
+ // Extend the radii out half a pixel to antialias.
+ SkScalar xOuterRadius = xRadius + SK_ScalarHalf;
+ SkScalar yOuterRadius = yRadius + SK_ScalarHalf;
+
+ // Expand the rect so all the pixels will be captured.
+ bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+
+ SkScalar yCoords[4] = {
+ bounds.fTop,
+ bounds.fTop + yOuterRadius,
+ bounds.fBottom - yOuterRadius,
+ bounds.fBottom
+ };
+ SkScalar yOuterOffsets[4] = {
+ yOuterRadius,
+ SK_ScalarNearlyZero, // we're using inversesqrt() in the shader, so can't be exactly 0
+ SK_ScalarNearlyZero,
+ yOuterRadius
+ };
+
+ for (int i = 0; i < 4; ++i) {
+ verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+ verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
+ verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]);
+ verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
+ verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]);
+ verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
+ verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+ verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]);
+ verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
+ verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
+ verts++;
+ }
+
+ // drop out the middle quad if we're stroked
+ int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_COUNT(gRRectIndices);
+ target->setIndexSourceToBuffer(indexBuffer);
+ target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bounds);
+ }
+
+ return true;
+}
diff --git a/gpu/GrPaint.cpp b/gpu/GrPaint.cpp
new file mode 100644
index 00000000..d67f2e87
--- /dev/null
+++ b/gpu/GrPaint.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrPaint.h"
+
+#include "effects/GrSimpleTextureEffect.h"
+
+void GrPaint::addColorTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+ this->addColorEffect(effect)->unref();
+}
+
+void GrPaint::addCoverageTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+ this->addCoverageEffect(effect)->unref();
+}
+
+void GrPaint::addColorTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrTextureParams& params) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+ this->addColorEffect(effect)->unref();
+}
+
+void GrPaint::addCoverageTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrTextureParams& params) {
+ GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+ this->addCoverageEffect(effect)->unref();
+}
diff --git a/gpu/GrPath.cpp b/gpu/GrPath.cpp
new file mode 100644
index 00000000..afd22390
--- /dev/null
+++ b/gpu/GrPath.cpp
@@ -0,0 +1,10 @@
+/*
+ * 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 "GrPath.h"
+
+SK_DEFINE_INST_COUNT(GrPath)
diff --git a/gpu/GrPath.h b/gpu/GrPath.h
new file mode 100644
index 00000000..c7e7f920
--- /dev/null
+++ b/gpu/GrPath.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 GrPath_DEFINED
+#define GrPath_DEFINED
+
+#include "GrResource.h"
+#include "SkRect.h"
+
+class GrPath : public GrResource {
+public:
+ SK_DECLARE_INST_COUNT(GrPath);
+
+ GrPath(GrGpu* gpu, bool isWrapped) : INHERITED(gpu, isWrapped) {}
+
+ const SkRect& getBounds() const { return fBounds; }
+
+protected:
+ SkRect fBounds;
+
+private:
+ typedef GrResource INHERITED;
+};
+
+#endif
diff --git a/gpu/GrPathRenderer.cpp b/gpu/GrPathRenderer.cpp
new file mode 100644
index 00000000..e88db22b
--- /dev/null
+++ b/gpu/GrPathRenderer.cpp
@@ -0,0 +1,26 @@
+
+/*
+ * 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 "GrPathRenderer.h"
+
+SK_DEFINE_INST_COUNT(GrPathRenderer)
+
+GrPathRenderer::GrPathRenderer() {
+}
+
+void GrPathRenderer::GetPathDevBounds(const SkPath& path,
+ int devW, int devH,
+ const SkMatrix& matrix,
+ SkRect* bounds) {
+ if (path.isInverseFillType()) {
+ *bounds = SkRect::MakeWH(SkIntToScalar(devW), SkIntToScalar(devH));
+ return;
+ }
+ *bounds = path.getBounds();
+ matrix.mapRect(bounds);
+}
diff --git a/gpu/GrPathRenderer.h b/gpu/GrPathRenderer.h
new file mode 100644
index 00000000..a64b9e34
--- /dev/null
+++ b/gpu/GrPathRenderer.h
@@ -0,0 +1,199 @@
+
+/*
+ * 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 GrPathRenderer_DEFINED
+#define GrPathRenderer_DEFINED
+
+#include "GrDrawTarget.h"
+#include "GrPathRendererChain.h"
+#include "GrStencil.h"
+
+#include "SkStrokeRec.h"
+#include "SkTArray.h"
+
+class SkPath;
+
+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).
+ */
+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.
+ *
+ * @param context the context that will use the path renderer
+ * @param prChain the chain to add path renderers to.
+ */
+ static void AddPathRenderers(GrContext* context, GrPathRendererChain* prChain);
+
+
+ GrPathRenderer();
+
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * @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).
+ */
+ StencilSupport getStencilSupport(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target) const {
+ 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.
+ *
+ * @param path The path to draw
+ * @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,
+ const SkStrokeRec& rec,
+ const GrDrawTarget* target,
+ bool antiAlias) const = 0;
+ /**
+ * 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 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.
+ */
+ bool drawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) {
+ GrAssert(!path.isEmpty());
+ 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. 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
+ */
+ void stencilPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target) {
+ GrAssert(!path.isEmpty());
+ GrAssert(kNoSupport_StencilSupport != this->getStencilSupport(path, stroke, target));
+ this->onStencilPath(path, stroke, target);
+ }
+
+protected:
+ /**
+ * 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,
+ 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);
+ }
+
+ // Helper for getting the device bounds of a path. Inverse filled paths will have bounds set
+ // by devSize. Non-inverse path bounds will not necessarily be clipped to devSize.
+ static void GetPathDevBounds(const SkPath& path,
+ int devW,
+ int devH,
+ const SkMatrix& matrix,
+ SkRect* bounds);
+
+ // Helper version that gets the dev width and height from a GrSurface.
+ static void GetPathDevBounds(const SkPath& path,
+ const GrSurface* device,
+ const SkMatrix& matrix,
+ SkRect* bounds) {
+ GetPathDevBounds(path, device->width(), device->height(), matrix, bounds);
+ }
+
+private:
+
+ typedef GrRefCnt INHERITED;
+};
+
+#endif
diff --git a/gpu/GrPathRendererChain.cpp b/gpu/GrPathRendererChain.cpp
new file mode 100644
index 00000000..b9157c72
--- /dev/null
+++ b/gpu/GrPathRendererChain.cpp
@@ -0,0 +1,89 @@
+
+/*
+ * 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 "GrPathRendererChain.h"
+
+#include "GrContext.h"
+#include "GrDefaultPathRenderer.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+
+SK_DEFINE_INST_COUNT(GrPathRendererChain)
+
+GrPathRendererChain::GrPathRendererChain(GrContext* context)
+ : fInit(false)
+ , fOwner(context) {
+}
+
+GrPathRendererChain::~GrPathRendererChain() {
+ for (int i = 0; i < fChain.count(); ++i) {
+ fChain[i]->unref();
+ }
+}
+
+GrPathRenderer* GrPathRendererChain::addPathRenderer(GrPathRenderer* pr) {
+ fChain.push_back() = pr;
+ pr->ref();
+ return pr;
+}
+
+GrPathRenderer* GrPathRendererChain::getPathRenderer(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ 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, 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];
+ }
+ }
+ return NULL;
+}
+
+void GrPathRendererChain::init() {
+ GrAssert(!fInit);
+ GrGpu* gpu = fOwner->getGpu();
+ bool twoSided = gpu->caps()->twoSidedStencilSupport();
+ bool wrapOp = gpu->caps()->stencilWrapOpsSupport();
+ GrPathRenderer::AddPathRenderers(fOwner, this);
+ this->addPathRenderer(SkNEW_ARGS(GrDefaultPathRenderer,
+ (twoSided, wrapOp)))->unref();
+ fInit = true;
+}
diff --git a/gpu/GrPathUtils.cpp b/gpu/GrPathUtils.cpp
new file mode 100644
index 00000000..f169455b
--- /dev/null
+++ b/gpu/GrPathUtils.cpp
@@ -0,0 +1,478 @@
+/*
+ * 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 "GrPathUtils.h"
+
+#include "GrPoint.h"
+#include "SkGeometry.h"
+
+SkScalar GrPathUtils::scaleToleranceToSrc(SkScalar devTol,
+ const SkMatrix& viewM,
+ const SkRect& pathBounds) {
+ // In order to tesselate the path we get a bound on how much the matrix can
+ // stretch when mapping to screen coordinates.
+ 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) {
+ 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 = SkScalarDiv(srcTol, stretch);
+ return srcTol;
+}
+
+static const int MAX_POINTS_PER_CURVE = 1 << 10;
+static const SkScalar gMinCurveTol = SkFloatToScalar(0.0001f);
+
+uint32_t GrPathUtils::quadraticPointCount(const GrPoint points[],
+ SkScalar tol) {
+ if (tol < gMinCurveTol) {
+ tol = gMinCurveTol;
+ }
+ GrAssert(tol > 0);
+
+ SkScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]);
+ if (d <= tol) {
+ return 1;
+ } else {
+ // Each time we subdivide, d should be cut in 4. So we need to
+ // subdivide x = log4(d/tol) times. x subdivisions creates 2^(x)
+ // points.
+ // 2^(log4(x)) = sqrt(x);
+ int temp = SkScalarCeil(SkScalarSqrt(SkScalarDiv(d, tol)));
+ int pow2 = GrNextPow2(temp);
+ // Because of NaNs & INFs we can wind up with a degenerate temp
+ // such that pow2 comes out negative. Also, our point generator
+ // will always output at least one pt.
+ if (pow2 < 1) {
+ pow2 = 1;
+ }
+ return GrMin(pow2, MAX_POINTS_PER_CURVE);
+ }
+}
+
+uint32_t GrPathUtils::generateQuadraticPoints(const GrPoint& p0,
+ const GrPoint& p1,
+ const GrPoint& p2,
+ SkScalar tolSqd,
+ GrPoint** points,
+ uint32_t pointsLeft) {
+ if (pointsLeft < 2 ||
+ (p1.distanceToLineSegmentBetweenSqd(p0, p2)) < tolSqd) {
+ (*points)[0] = p2;
+ *points += 1;
+ return 1;
+ }
+
+ GrPoint q[] = {
+ { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+ { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.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);
+ uint32_t b = generateQuadraticPoints(r, q[1], p2, tolSqd, points, pointsLeft);
+ return a + b;
+}
+
+uint32_t GrPathUtils::cubicPointCount(const GrPoint points[],
+ SkScalar tol) {
+ if (tol < gMinCurveTol) {
+ tol = gMinCurveTol;
+ }
+ GrAssert(tol > 0);
+
+ SkScalar d = GrMax(
+ points[1].distanceToLineSegmentBetweenSqd(points[0], points[3]),
+ points[2].distanceToLineSegmentBetweenSqd(points[0], points[3]));
+ d = SkScalarSqrt(d);
+ if (d <= tol) {
+ return 1;
+ } else {
+ int temp = SkScalarCeil(SkScalarSqrt(SkScalarDiv(d, tol)));
+ int pow2 = GrNextPow2(temp);
+ // Because of NaNs & INFs we can wind up with a degenerate temp
+ // such that pow2 comes out negative. Also, our point generator
+ // will always output at least one pt.
+ if (pow2 < 1) {
+ pow2 = 1;
+ }
+ return GrMin(pow2, MAX_POINTS_PER_CURVE);
+ }
+}
+
+uint32_t GrPathUtils::generateCubicPoints(const GrPoint& p0,
+ const GrPoint& p1,
+ const GrPoint& p2,
+ const GrPoint& p3,
+ SkScalar tolSqd,
+ GrPoint** points,
+ uint32_t pointsLeft) {
+ if (pointsLeft < 2 ||
+ (p1.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd &&
+ p2.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd)) {
+ (*points)[0] = p3;
+ *points += 1;
+ return 1;
+ }
+ GrPoint q[] = {
+ { 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[] = {
+ { 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 = { 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);
+ return a + b;
+}
+
+int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths,
+ SkScalar tol) {
+ if (tol < gMinCurveTol) {
+ tol = gMinCurveTol;
+ }
+ GrAssert(tol > 0);
+
+ int pointCount = 0;
+ *subpaths = 1;
+
+ bool first = true;
+
+ SkPath::Iter iter(path, false);
+ SkPath::Verb verb;
+
+ GrPoint pts[4];
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ pointCount += 1;
+ break;
+ case SkPath::kQuad_Verb:
+ pointCount += quadraticPointCount(pts, tol);
+ break;
+ case SkPath::kCubic_Verb:
+ pointCount += cubicPointCount(pts, tol);
+ break;
+ case SkPath::kMove_Verb:
+ pointCount += 1;
+ if (!first) {
+ ++(*subpaths);
+ }
+ break;
+ default:
+ break;
+ }
+ first = false;
+ }
+ return pointCount;
+}
+
+void GrPathUtils::QuadUVMatrix::set(const GrPoint qPts[3]) {
+ // can't make this static, no cons :(
+ SkMatrix UVpts;
+#ifndef SK_SCALAR_IS_FLOAT
+ GrCrash("Expected scalar is float.");
+#endif
+ SkMatrix m;
+ // We want M such that M * xy_pt = uv_pt
+ // We know M * control_pts = [0 1/2 1]
+ // [0 0 1]
+ // [1 1 1]
+ // We invert the control pt matrix and post concat to both sides to get M.
+ 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(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).
+ SkScalar maxD = qPts[0].distanceToSqd(qPts[1]);
+ int maxEdge = 0;
+ SkScalar d = qPts[1].distanceToSqd(qPts[2]);
+ if (d > maxD) {
+ maxD = d;
+ maxEdge = 1;
+ }
+ d = qPts[2].distanceToSqd(qPts[0]);
+ if (d > maxD) {
+ maxD = d;
+ maxEdge = 2;
+ }
+ // We could have a tolerance here, not sure if it would improve anything
+ if (maxD > 0) {
+ // Set the matrix to give (u = 0, v = distance_to_line)
+ GrVec lineVec = qPts[(maxEdge + 1)%3] - qPts[maxEdge];
+ // when looking from the point 0 down the line we want positive
+ // distances to be to the left. This matches the non-degenerate
+ // case.
+ lineVec.setOrthog(lineVec, GrPoint::kLeft_Side);
+ lineVec.dot(qPts[0]);
+ // first row
+ fM[0] = 0;
+ fM[1] = 0;
+ fM[2] = 0;
+ // second row
+ fM[3] = lineVec.fX;
+ fM[4] = lineVec.fY;
+ fM[5] = -lineVec.dot(qPts[maxEdge]);
+ } else {
+ // It's a point. It should cover zero area. Just set the matrix such
+ // that (u, v) will always be far away from the quad.
+ fM[0] = 0; fM[1] = 0; fM[2] = 100.f;
+ fM[3] = 0; fM[4] = 0; fM[5] = 100.f;
+ }
+ } else {
+ m.postConcat(UVpts);
+
+ // The matrix should not have perspective.
+ SkDEBUGCODE(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);
+ if (1.f != m33) {
+ m33 = 1.f / m33;
+ fM[0] = m33 * m.get(SkMatrix::kMScaleX);
+ fM[1] = m33 * m.get(SkMatrix::kMSkewX);
+ fM[2] = m33 * m.get(SkMatrix::kMTransX);
+ fM[3] = m33 * m.get(SkMatrix::kMSkewY);
+ fM[4] = m33 * m.get(SkMatrix::kMScaleY);
+ fM[5] = m33 * m.get(SkMatrix::kMTransY);
+ } else {
+ fM[0] = m.get(SkMatrix::kMScaleX);
+ fM[1] = m.get(SkMatrix::kMSkewX);
+ fM[2] = m.get(SkMatrix::kMTransX);
+ fM[3] = m.get(SkMatrix::kMSkewY);
+ fM[4] = m.get(SkMatrix::kMScaleY);
+ fM[5] = m.get(SkMatrix::kMTransY);
+ }
+ }
+}
+
+namespace {
+
+// a is the first control point of the cubic.
+// ab is the vector from a to the second control point.
+// dc is the vector from the fourth to the third control point.
+// d is the fourth control point.
+// p is the candidate quadratic control point.
+// this assumes that the cubic doesn't inflect and is simple
+bool is_point_within_cubic_tangents(const SkPoint& a,
+ const SkVector& ab,
+ const SkVector& dc,
+ const SkPoint& d,
+ SkPath::Direction dir,
+ const SkPoint p) {
+ SkVector ap = p - a;
+ SkScalar apXab = ap.cross(ab);
+ if (SkPath::kCW_Direction == dir) {
+ if (apXab > 0) {
+ return false;
+ }
+ } else {
+ GrAssert(SkPath::kCCW_Direction == dir);
+ if (apXab < 0) {
+ return false;
+ }
+ }
+
+ SkVector dp = p - d;
+ SkScalar dpXdc = dp.cross(dc);
+ if (SkPath::kCW_Direction == dir) {
+ if (dpXdc < 0) {
+ return false;
+ }
+ } else {
+ GrAssert(SkPath::kCCW_Direction == dir);
+ if (dpXdc > 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void convert_noninflect_cubic_to_quads(const SkPoint p[4],
+ SkScalar toleranceSqd,
+ bool constrainWithinTangents,
+ SkPath::Direction dir,
+ SkTArray<SkPoint, true>* quads,
+ int sublevel = 0) {
+
+ // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
+ // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
+
+ SkVector ab = p[1] - p[0];
+ SkVector dc = p[2] - p[3];
+
+ if (ab.isZero()) {
+ if (dc.isZero()) {
+ SkPoint* degQuad = quads->push_back_n(3);
+ degQuad[0] = p[0];
+ degQuad[1] = p[0];
+ degQuad[2] = p[3];
+ return;
+ }
+ ab = p[2] - p[0];
+ }
+ if (dc.isZero()) {
+ dc = p[1] - p[3];
+ }
+
+ // When the ab and cd tangents are nearly parallel with vector from d to a the constraint that
+ // the quad point falls between the tangents becomes hard to enforce and we are likely to hit
+ // the max subdivision count. However, in this case the cubic is approaching a line and the
+ // accuracy of the quad point isn't so important. We check if the two middle cubic control
+ // points are very close to the baseline vector. If so then we just pick quadratic points on the
+ // control polygon.
+
+ if (constrainWithinTangents) {
+ SkVector da = p[0] - p[3];
+ SkScalar invDALengthSqd = da.lengthSqd();
+ if (invDALengthSqd > SK_ScalarNearlyZero) {
+ invDALengthSqd = SkScalarInvert(invDALengthSqd);
+ // cross(ab, da)^2/length(da)^2 == sqd distance from b to line from d to a.
+ // same goed for point c using vector cd.
+ SkScalar detABSqd = ab.cross(da);
+ detABSqd = SkScalarSquare(detABSqd);
+ SkScalar detDCSqd = dc.cross(da);
+ detDCSqd = SkScalarSquare(detDCSqd);
+ if (SkScalarMul(detABSqd, invDALengthSqd) < toleranceSqd &&
+ SkScalarMul(detDCSqd, invDALengthSqd) < toleranceSqd) {
+ SkPoint b = p[0] + ab;
+ SkPoint c = p[3] + dc;
+ SkPoint mid = b + c;
+ mid.scale(SK_ScalarHalf);
+ // Insert two quadratics to cover the case when ab points away from d and/or dc
+ // points away from a.
+ if (SkVector::DotProduct(da, dc) < 0 || SkVector::DotProduct(ab,da) > 0) {
+ SkPoint* qpts = quads->push_back_n(6);
+ qpts[0] = p[0];
+ qpts[1] = b;
+ qpts[2] = mid;
+ qpts[3] = mid;
+ qpts[4] = c;
+ qpts[5] = p[3];
+ } else {
+ SkPoint* qpts = quads->push_back_n(3);
+ qpts[0] = p[0];
+ qpts[1] = mid;
+ qpts[2] = p[3];
+ }
+ return;
+ }
+ }
+ }
+
+ static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
+ static const int kMaxSubdivs = 10;
+
+ ab.scale(kLengthScale);
+ dc.scale(kLengthScale);
+
+ // e0 and e1 are extrapolations along vectors ab and dc.
+ SkVector c0 = p[0];
+ c0 += ab;
+ SkVector c1 = p[3];
+ c1 += dc;
+
+ SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : c0.distanceToSqd(c1);
+ if (dSqd < toleranceSqd) {
+ SkPoint cAvg = c0;
+ cAvg += c1;
+ cAvg.scale(SK_ScalarHalf);
+
+ bool subdivide = false;
+
+ if (constrainWithinTangents &&
+ !is_point_within_cubic_tangents(p[0], ab, dc, p[3], dir, cAvg)) {
+ // choose a new cAvg that is the intersection of the two tangent lines.
+ ab.setOrthog(ab);
+ SkScalar z0 = -ab.dot(p[0]);
+ dc.setOrthog(dc);
+ SkScalar z1 = -dc.dot(p[3]);
+ cAvg.fX = SkScalarMul(ab.fY, z1) - SkScalarMul(z0, dc.fY);
+ cAvg.fY = SkScalarMul(z0, dc.fX) - SkScalarMul(ab.fX, z1);
+ SkScalar z = SkScalarMul(ab.fX, dc.fY) - SkScalarMul(ab.fY, dc.fX);
+ z = SkScalarInvert(z);
+ cAvg.fX *= z;
+ cAvg.fY *= z;
+ if (sublevel <= kMaxSubdivs) {
+ SkScalar d0Sqd = c0.distanceToSqd(cAvg);
+ SkScalar d1Sqd = c1.distanceToSqd(cAvg);
+ // We need to subdivide if d0 + d1 > tolerance but we have the sqd values. We know
+ // the distances and tolerance can't be negative.
+ // (d0 + d1)^2 > toleranceSqd
+ // d0Sqd + 2*d0*d1 + d1Sqd > toleranceSqd
+ SkScalar d0d1 = SkScalarSqrt(SkScalarMul(d0Sqd, d1Sqd));
+ subdivide = 2 * d0d1 + d0Sqd + d1Sqd > toleranceSqd;
+ }
+ }
+ if (!subdivide) {
+ SkPoint* pts = quads->push_back_n(3);
+ pts[0] = p[0];
+ pts[1] = cAvg;
+ pts[2] = p[3];
+ return;
+ }
+ }
+ SkPoint choppedPts[7];
+ SkChopCubicAtHalf(p, choppedPts);
+ convert_noninflect_cubic_to_quads(choppedPts + 0,
+ toleranceSqd,
+ constrainWithinTangents,
+ dir,
+ quads,
+ sublevel + 1);
+ convert_noninflect_cubic_to_quads(choppedPts + 3,
+ toleranceSqd,
+ constrainWithinTangents,
+ dir,
+ quads,
+ sublevel + 1);
+}
+}
+
+void GrPathUtils::convertCubicToQuads(const GrPoint p[4],
+ SkScalar tolScale,
+ bool constrainWithinTangents,
+ SkPath::Direction dir,
+ SkTArray<SkPoint, true>* quads) {
+ SkPoint chopped[10];
+ int count = SkChopCubicAtInflections(p, chopped);
+
+ // base tolerance is 1 pixel.
+ static const SkScalar kTolerance = SK_Scalar1;
+ const SkScalar tolSqd = SkScalarSquare(SkScalarMul(tolScale, kTolerance));
+
+ for (int i = 0; i < count; ++i) {
+ SkPoint* cubic = chopped + 3*i;
+ convert_noninflect_cubic_to_quads(cubic, tolSqd, constrainWithinTangents, dir, quads);
+ }
+
+}
diff --git a/gpu/GrPathUtils.h b/gpu/GrPathUtils.h
new file mode 100644
index 00000000..fc319ec5
--- /dev/null
+++ b/gpu/GrPathUtils.h
@@ -0,0 +1,119 @@
+/*
+ * 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 GrPathUtils_DEFINED
+#define GrPathUtils_DEFINED
+
+#include "GrPoint.h"
+#include "SkRect.h"
+#include "SkPath.h"
+#include "SkTArray.h"
+
+class SkMatrix;
+
+/**
+ * Utilities for evaluating paths.
+ */
+namespace GrPathUtils {
+ SkScalar scaleToleranceToSrc(SkScalar devTol,
+ const SkMatrix& viewM,
+ const SkRect& 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,
+ 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[], SkScalar tol);
+
+ uint32_t generateQuadraticPoints(const GrPoint& p0,
+ const GrPoint& p1,
+ const GrPoint& p2,
+ 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[], SkScalar tol);
+
+ uint32_t generateCubicPoints(const GrPoint& p0,
+ const GrPoint& p1,
+ const GrPoint& p2,
+ const GrPoint& p3,
+ SkScalar tolSqd,
+ GrPoint** points,
+ uint32_t pointsLeft);
+
+ // A 2x3 matrix that goes from the 2d space coordinates to UV space where
+ // u^2-v = 0 specifies the quad. The matrix is determined by the control
+ // points of the quadratic.
+ class QuadUVMatrix {
+ public:
+ QuadUVMatrix() {};
+ // Initialize the matrix from the control pts
+ QuadUVMatrix(const GrPoint controlPts[3]) { this->set(controlPts); }
+ void set(const GrPoint controlPts[3]);
+
+ /**
+ * Applies the matrix to vertex positions to compute UV coords. This
+ * has been templated so that the compiler can easliy unroll the loop
+ * and reorder to avoid stalling for loads. The assumption is that a
+ * path renderer will have a small fixed number of vertices that it
+ * uploads for each quad.
+ *
+ * N is the number of vertices.
+ * STRIDE is the size of each vertex.
+ * UV_OFFSET is the offset of the UV values within each vertex.
+ * vertices is a pointer to the first vertex.
+ */
+ template <int N, size_t STRIDE, size_t UV_OFFSET>
+ void apply(const void* vertices) {
+ intptr_t xyPtr = reinterpret_cast<intptr_t>(vertices);
+ intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + UV_OFFSET;
+ float sx = fM[0];
+ float kx = fM[1];
+ float tx = fM[2];
+ float ky = fM[3];
+ float sy = fM[4];
+ float ty = fM[5];
+ for (int i = 0; i < N; ++i) {
+ const GrPoint* xy = reinterpret_cast<const GrPoint*>(xyPtr);
+ GrPoint* uv = reinterpret_cast<GrPoint*>(uvPtr);
+ uv->fX = sx * xy->fX + kx * xy->fY + tx;
+ uv->fY = ky * xy->fX + sy * xy->fY + ty;
+ xyPtr += STRIDE;
+ uvPtr += STRIDE;
+ }
+ }
+ private:
+ float fM[6];
+ };
+
+
+ // Converts a cubic into a sequence of quads. If working in device space
+ // use tolScale = 1, otherwise set based on stretchiness of the matrix. The
+ // result is sets of 3 points in quads (TODO: share endpoints in returned
+ // array)
+ // When we approximate a cubic {a,b,c,d} with a quadratic we may have to
+ // ensure that the new control point lies between the lines ab and cd. The
+ // convex path renderer requires this. It starts with a path where all the
+ // control points taken together form a convex polygon. It relies on this
+ // property and the quadratic approximation of cubics step cannot alter it.
+ // Setting constrainWithinTangents to true enforces this property. When this
+ // is true the cubic must be simple and dir must specify the orientation of
+ // the cubic. Otherwise, dir is ignored.
+ void convertCubicToQuads(const GrPoint p[4],
+ SkScalar tolScale,
+ bool constrainWithinTangents,
+ SkPath::Direction dir,
+ SkTArray<SkPoint, true>* quads);
+};
+#endif
diff --git a/gpu/GrPlotMgr.h b/gpu/GrPlotMgr.h
new file mode 100644
index 00000000..4f79a213
--- /dev/null
+++ b/gpu/GrPlotMgr.h
@@ -0,0 +1,76 @@
+
+/*
+ * 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 GrPlotMgr_DEFINED
+#define GrPlotMgr_DEFINED
+
+#include "GrTypes.h"
+#include "GrPoint.h"
+
+class GrPlotMgr : GrNoncopyable {
+public:
+ GrPlotMgr(int width, int height) {
+ fDim.set(width, height);
+ size_t needed = width * height;
+ if (needed <= sizeof(fStorage)) {
+ fBusy = fStorage;
+ } else {
+ fBusy = SkNEW_ARRAY(char, needed);
+ }
+ this->reset();
+ }
+
+ ~GrPlotMgr() {
+ if (fBusy != fStorage) {
+ delete[] fBusy;
+ }
+ }
+
+ void reset() {
+ Gr_bzero(fBusy, fDim.fX * fDim.fY);
+ }
+
+ bool newPlot(GrIPoint16* loc) {
+ char* busy = fBusy;
+ for (int y = 0; y < fDim.fY; y++) {
+ for (int x = 0; x < fDim.fX; x++) {
+ if (!*busy) {
+ *busy = true;
+ loc->set(x, y);
+ return true;
+ }
+ busy++;
+ }
+ }
+ return false;
+ }
+
+ bool isBusy(int x, int y) const {
+ GrAssert((unsigned)x < (unsigned)fDim.fX);
+ GrAssert((unsigned)y < (unsigned)fDim.fY);
+ return fBusy[y * fDim.fX + x] != 0;
+ }
+
+ void freePlot(int x, int y) {
+ GrAssert((unsigned)x < (unsigned)fDim.fX);
+ GrAssert((unsigned)y < (unsigned)fDim.fY);
+ fBusy[y * fDim.fX + x] = false;
+ }
+
+private:
+ enum {
+ STORAGE = 64
+ };
+ char fStorage[STORAGE];
+ char* fBusy;
+ GrIPoint16 fDim;
+};
+
+#endif
diff --git a/gpu/GrRectanizer.cpp b/gpu/GrRectanizer.cpp
new file mode 100644
index 00000000..75ff92c7
--- /dev/null
+++ b/gpu/GrRectanizer.cpp
@@ -0,0 +1,121 @@
+
+/*
+ * 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 "GrRectanizer.h"
+#include "GrTBSearch.h"
+
+#define MIN_HEIGHT_POW2 2
+
+class GrRectanizerPow2 : public GrRectanizer {
+public:
+ GrRectanizerPow2(int w, int h) : GrRectanizer(w, h) {
+ fNextStripY = 0;
+ fAreaSoFar = 0;
+ Gr_bzero(fRows, sizeof(fRows));
+ }
+
+ virtual ~GrRectanizerPow2() {
+ }
+
+ virtual bool addRect(int w, int h, GrIPoint16* loc);
+
+ virtual float percentFull() const {
+ return fAreaSoFar / ((float)this->width() * this->height());
+ }
+
+ virtual int stripToPurge(int height) const { return -1; }
+ virtual void purgeStripAtY(int yCoord) { }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ struct Row {
+ GrIPoint16 fLoc;
+ int fRowHeight;
+
+ bool canAddWidth(int width, int containerWidth) const {
+ return fLoc.fX + width <= containerWidth;
+ }
+ };
+
+ Row fRows[16];
+
+ static int HeightToRowIndex(int height) {
+ GrAssert(height >= MIN_HEIGHT_POW2);
+ return 32 - SkCLZ(height - 1);
+ }
+
+ int fNextStripY;
+ int32_t fAreaSoFar;
+
+ bool canAddStrip(int height) const {
+ return fNextStripY + height <= this->height();
+ }
+
+ void initRow(Row* row, int rowHeight) {
+ row->fLoc.set(0, fNextStripY);
+ row->fRowHeight = rowHeight;
+ fNextStripY += rowHeight;
+ }
+};
+
+bool GrRectanizerPow2::addRect(int width, int height, GrIPoint16* loc) {
+ if ((unsigned)width > (unsigned)this->width() ||
+ (unsigned)height > (unsigned)this->height()) {
+ return false;
+ }
+
+ int32_t area = width * height;
+
+ /*
+ We use bsearch, but there may be more than one row with the same height,
+ so we actually search for height-1, which can only be a pow2 itself if
+ height == 2. Thus we set a minimum height.
+ */
+ height = GrNextPow2(height);
+ if (height < MIN_HEIGHT_POW2) {
+ height = MIN_HEIGHT_POW2;
+ }
+
+ Row* row = &fRows[HeightToRowIndex(height)];
+ GrAssert(row->fRowHeight == 0 || row->fRowHeight == height);
+
+ if (0 == row->fRowHeight) {
+ if (!this->canAddStrip(height)) {
+ return false;
+ }
+ this->initRow(row, height);
+ } else {
+ if (!row->canAddWidth(width, this->width())) {
+ if (!this->canAddStrip(height)) {
+ return false;
+ }
+ // that row is now "full", so retarget our Row record for
+ // another one
+ this->initRow(row, height);
+ }
+ }
+
+ GrAssert(row->fRowHeight == height);
+ GrAssert(row->canAddWidth(width, this->width()));
+ *loc = row->fLoc;
+ row->fLoc.fX += width;
+
+ GrAssert(row->fLoc.fX <= this->width());
+ GrAssert(row->fLoc.fY <= this->height());
+ GrAssert(fNextStripY <= this->height());
+ fAreaSoFar += area;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrRectanizer* GrRectanizer::Factory(int width, int height) {
+ return SkNEW_ARGS(GrRectanizerPow2, (width, height));
+}
diff --git a/gpu/GrRectanizer.h b/gpu/GrRectanizer.h
new file mode 100644
index 00000000..bacf4fdd
--- /dev/null
+++ b/gpu/GrRectanizer.h
@@ -0,0 +1,51 @@
+/*
+ * 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 GrRectanizer_DEFINED
+#define GrRectanizer_DEFINED
+
+#include "GrPoint.h"
+
+class GrRectanizerPurgeListener {
+public:
+ virtual ~GrRectanizerPurgeListener() {}
+
+ virtual void notifyPurgeStrip(void*, int yCoord) = 0;
+};
+
+class GrRectanizer {
+public:
+ GrRectanizer(int width, int height) : fWidth(width), fHeight(height) {
+ GrAssert(width >= 0);
+ GrAssert(height >= 0);
+ }
+
+ virtual ~GrRectanizer() {}
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+
+ virtual bool addRect(int width, int height, GrIPoint16* loc) = 0;
+ virtual float percentFull() const = 0;
+
+ // return the Y-coordinate of a strip that should be purged, given height
+ // i.e. return the oldest such strip, or some other criteria. Return -1
+ // if there is no candidate
+ virtual int stripToPurge(int height) const = 0;
+ virtual void purgeStripAtY(int yCoord) = 0;
+
+ /**
+ * Our factory, which returns the subclass du jour
+ */
+ static GrRectanizer* Factory(int width, int height);
+
+private:
+ int fWidth;
+ int fHeight;
+};
+
+#endif
diff --git a/gpu/GrRectanizer_fifo.cpp b/gpu/GrRectanizer_fifo.cpp
new file mode 100644
index 00000000..b6ef1d9a
--- /dev/null
+++ b/gpu/GrRectanizer_fifo.cpp
@@ -0,0 +1,121 @@
+
+/*
+ * 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 "GrRectanizer.h"
+#include "GrTBSearch.h"
+
+#define MIN_HEIGHT_POW2 2
+
+class GrRectanizerFIFO : public GrRectanizer {
+public:
+ GrRectanizerFIFO(int w, int h) : GrRectanizer(w, h) {
+ fNextStripY = 0;
+ fAreaSoFar = 0;
+ Gr_bzero(fRows, sizeof(fRows));
+ }
+
+ virtual ~GrRectanizerFIFO() {
+ }
+
+ virtual bool addRect(int w, int h, GrIPoint16* loc);
+
+ virtual float percentFull() const {
+ return fAreaSoFar / ((float)this->width() * this->height());
+ }
+
+ virtual int stripToPurge(int height) const { return -1; }
+ virtual void purgeStripAtY(int yCoord) { }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ struct Row {
+ GrIPoint16 fLoc;
+ int fRowHeight;
+
+ bool canAddWidth(int width, int containerWidth) const {
+ return fLoc.fX + width <= containerWidth;
+ }
+ };
+
+ Row fRows[16];
+
+ static int HeightToRowIndex(int height) {
+ GrAssert(height >= MIN_HEIGHT_POW2);
+ return 32 - Gr_clz(height - 1);
+ }
+
+ int fNextStripY;
+ int32_t fAreaSoFar;
+
+ bool canAddStrip(int height) const {
+ return fNextStripY + height <= this->height();
+ }
+
+ void initRow(Row* row, int rowHeight) {
+ row->fLoc.set(0, fNextStripY);
+ row->fRowHeight = rowHeight;
+ fNextStripY += rowHeight;
+ }
+};
+
+bool GrRectanizerFIFO::addRect(int width, int height, GrIPoint16* loc) {
+ if ((unsigned)width > (unsigned)this->width() ||
+ (unsigned)height > (unsigned)this->height()) {
+ return false;
+ }
+
+ int32_t area = width * height;
+
+ /*
+ We use bsearch, but there may be more than one row with the same height,
+ so we actually search for height-1, which can only be a pow2 itself if
+ height == 2. Thus we set a minimum height.
+ */
+ height = GrNextPow2(height);
+ if (height < MIN_HEIGHT_POW2) {
+ height = MIN_HEIGHT_POW2;
+ }
+
+ Row* row = &fRows[HeightToRowIndex(height)];
+ GrAssert(row->fRowHeight == 0 || row->fRowHeight == height);
+
+ if (0 == row->fRowHeight) {
+ if (!this->canAddStrip(height)) {
+ return false;
+ }
+ this->initRow(row, height);
+ } else {
+ if (!row->canAddWidth(width, this->width())) {
+ if (!this->canAddStrip(height)) {
+ return false;
+ }
+ // that row is now "full", so retarget our Row record for
+ // another one
+ this->initRow(row, height);
+ }
+ }
+
+ GrAssert(row->fRowHeight == height);
+ GrAssert(row->canAddWidth(width, this->width()));
+ *loc = row->fLoc;
+ row->fLoc.fX += width;
+
+ GrAssert(row->fLoc.fX <= this->width());
+ GrAssert(row->fLoc.fY <= this->height());
+ GrAssert(fNextStripY <= this->height());
+ fAreaSoFar += area;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrRectanizer* GrRectanizer::Factory(int width, int height) {
+ return SkNEW_ARGS(GrRectanizerFIFO, (width, height));
+}
diff --git a/gpu/GrRedBlackTree.h b/gpu/GrRedBlackTree.h
new file mode 100644
index 00000000..ba03be29
--- /dev/null
+++ b/gpu/GrRedBlackTree.h
@@ -0,0 +1,1118 @@
+
+/*
+ * 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 GrRedBlackTree_DEFINED
+#define GrRedBlackTree_DEFINED
+
+#include "GrNoncopyable.h"
+
+template <typename T>
+class GrLess {
+public:
+ bool operator()(const T& a, const T& b) const { return a < b; }
+};
+
+template <typename T>
+class GrLess<T*> {
+public:
+ bool operator()(const T* a, const T* b) const { return *a < *b; }
+};
+
+/**
+ * In debug build this will cause full traversals of the tree when the validate
+ * is called on insert and remove. Useful for debugging but very slow.
+ */
+#define DEEP_VALIDATE 0
+
+/**
+ * A sorted tree that uses the red-black tree algorithm. Allows duplicate
+ * entries. Data is of type T and is compared using functor C. A single C object
+ * will be created and used for all comparisons.
+ */
+template <typename T, typename C = GrLess<T> >
+class GrRedBlackTree : public GrNoncopyable {
+public:
+ /**
+ * Creates an empty tree.
+ */
+ GrRedBlackTree();
+ virtual ~GrRedBlackTree();
+
+ /**
+ * Class used to iterater through the tree. The valid range of the tree
+ * is given by [begin(), end()). It is legal to dereference begin() but not
+ * end(). The iterator has preincrement and predecrement operators, it is
+ * legal to decerement end() if the tree is not empty to get the last
+ * element. However, a last() helper is provided.
+ */
+ class Iter;
+
+ /**
+ * Add an element to the tree. Duplicates are allowed.
+ * @param t the item to add.
+ * @return an iterator to the item.
+ */
+ Iter insert(const T& t);
+
+ /**
+ * Removes all items in the tree.
+ */
+ void reset();
+
+ /**
+ * @return true if there are no items in the tree, false otherwise.
+ */
+ bool empty() const {return 0 == fCount;}
+
+ /**
+ * @return the number of items in the tree.
+ */
+ int count() const {return fCount;}
+
+ /**
+ * @return an iterator to the first item in sorted order, or end() if empty
+ */
+ Iter begin();
+ /**
+ * Gets the last valid iterator. This is always valid, even on an empty.
+ * However, it can never be dereferenced. Useful as a loop terminator.
+ * @return an iterator that is just beyond the last item in sorted order.
+ */
+ Iter end();
+ /**
+ * @return an iterator that to the last item in sorted order, or end() if
+ * empty.
+ */
+ Iter last();
+
+ /**
+ * Finds an occurrence of an item.
+ * @param t the item to find.
+ * @return an iterator to a tree element equal to t or end() if none exists.
+ */
+ Iter find(const T& t);
+ /**
+ * Finds the first of an item in iterator order.
+ * @param t the item to find.
+ * @return an iterator to the first element equal to t or end() if
+ * none exists.
+ */
+ Iter findFirst(const T& t);
+ /**
+ * Finds the last of an item in iterator order.
+ * @param t the item to find.
+ * @return an iterator to the last element equal to t or end() if
+ * none exists.
+ */
+ Iter findLast(const T& t);
+ /**
+ * Gets the number of items in the tree equal to t.
+ * @param t the item to count.
+ * @return number of items equal to t in the tree
+ */
+ int countOf(const T& t) const;
+
+ /**
+ * Removes the item indicated by an iterator. The iterator will not be valid
+ * afterwards.
+ *
+ * @param iter iterator of item to remove. Must be valid (not end()).
+ */
+ void remove(const Iter& iter) { deleteAtNode(iter.fN); }
+
+ static void UnitTest();
+
+private:
+ enum Color {
+ kRed_Color,
+ kBlack_Color
+ };
+
+ enum Child {
+ kLeft_Child = 0,
+ kRight_Child = 1
+ };
+
+ struct Node {
+ T fItem;
+ Color fColor;
+
+ Node* fParent;
+ Node* fChildren[2];
+ };
+
+ void rotateRight(Node* n);
+ void rotateLeft(Node* n);
+
+ static Node* SuccessorNode(Node* x);
+ static Node* PredecessorNode(Node* x);
+
+ void deleteAtNode(Node* x);
+ static void RecursiveDelete(Node* x);
+
+ int onCountOf(const Node* n, const T& t) const;
+
+#if GR_DEBUG
+ void validate() const;
+ int checkNode(Node* n, int* blackHeight) const;
+ // checks relationship between a node and its children. allowRedRed means
+ // node may be in an intermediate state where a red parent has a red child.
+ bool validateChildRelations(const Node* n, bool allowRedRed) const;
+ // place to stick break point if validateChildRelations is failing.
+ bool validateChildRelationsFailed() const { return false; }
+#else
+ void validate() const {}
+#endif
+
+ int fCount;
+ Node* fRoot;
+ Node* fFirst;
+ Node* fLast;
+
+ const C fComp;
+};
+
+template <typename T, typename C>
+class GrRedBlackTree<T,C>::Iter {
+public:
+ Iter() {};
+ Iter(const Iter& i) {fN = i.fN; fTree = i.fTree;}
+ Iter& operator =(const Iter& i) {
+ fN = i.fN;
+ fTree = i.fTree;
+ return *this;
+ }
+ // altering the sort value of the item using this method will cause
+ // errors.
+ T& operator *() const { return fN->fItem; }
+ bool operator ==(const Iter& i) const {
+ return fN == i.fN && fTree == i.fTree;
+ }
+ bool operator !=(const Iter& i) const { return !(*this == i); }
+ Iter& operator ++() {
+ GrAssert(*this != fTree->end());
+ fN = SuccessorNode(fN);
+ return *this;
+ }
+ Iter& operator --() {
+ GrAssert(*this != fTree->begin());
+ if (NULL != fN) {
+ fN = PredecessorNode(fN);
+ } else {
+ *this = fTree->last();
+ }
+ return *this;
+ }
+
+private:
+ friend class GrRedBlackTree;
+ explicit Iter(Node* n, GrRedBlackTree* tree) {
+ fN = n;
+ fTree = tree;
+ }
+ Node* fN;
+ GrRedBlackTree* fTree;
+};
+
+template <typename T, typename C>
+GrRedBlackTree<T,C>::GrRedBlackTree() : fComp() {
+ fRoot = NULL;
+ fFirst = NULL;
+ fLast = NULL;
+ fCount = 0;
+ validate();
+}
+
+template <typename T, typename C>
+GrRedBlackTree<T,C>::~GrRedBlackTree() {
+ RecursiveDelete(fRoot);
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::begin() {
+ return Iter(fFirst, this);
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::end() {
+ return Iter(NULL, this);
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::last() {
+ return Iter(fLast, this);
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::find(const T& t) {
+ Node* n = fRoot;
+ while (NULL != n) {
+ if (fComp(t, n->fItem)) {
+ n = n->fChildren[kLeft_Child];
+ } else {
+ if (!fComp(n->fItem, t)) {
+ return Iter(n, this);
+ }
+ n = n->fChildren[kRight_Child];
+ }
+ }
+ return end();
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::findFirst(const T& t) {
+ Node* n = fRoot;
+ Node* leftMost = NULL;
+ while (NULL != n) {
+ if (fComp(t, n->fItem)) {
+ n = n->fChildren[kLeft_Child];
+ } else {
+ if (!fComp(n->fItem, t)) {
+ // found one. check if another in left subtree.
+ leftMost = n;
+ n = n->fChildren[kLeft_Child];
+ } else {
+ n = n->fChildren[kRight_Child];
+ }
+ }
+ }
+ return Iter(leftMost, this);
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::findLast(const T& t) {
+ Node* n = fRoot;
+ Node* rightMost = NULL;
+ while (NULL != n) {
+ if (fComp(t, n->fItem)) {
+ n = n->fChildren[kLeft_Child];
+ } else {
+ if (!fComp(n->fItem, t)) {
+ // found one. check if another in right subtree.
+ rightMost = n;
+ }
+ n = n->fChildren[kRight_Child];
+ }
+ }
+ return Iter(rightMost, this);
+}
+
+template <typename T, typename C>
+int GrRedBlackTree<T,C>::countOf(const T& t) const {
+ return onCountOf(fRoot, t);
+}
+
+template <typename T, typename C>
+int GrRedBlackTree<T,C>::onCountOf(const Node* n, const T& t) const {
+ // this is count*log(n) :(
+ while (NULL != n) {
+ if (fComp(t, n->fItem)) {
+ n = n->fChildren[kLeft_Child];
+ } else {
+ if (!fComp(n->fItem, t)) {
+ int count = 1;
+ count += onCountOf(n->fChildren[kLeft_Child], t);
+ count += onCountOf(n->fChildren[kRight_Child], t);
+ return count;
+ }
+ n = n->fChildren[kRight_Child];
+ }
+ }
+ return 0;
+
+}
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::reset() {
+ RecursiveDelete(fRoot);
+ fRoot = NULL;
+ fFirst = NULL;
+ fLast = NULL;
+ fCount = 0;
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Iter GrRedBlackTree<T,C>::insert(const T& t) {
+ validate();
+
+ ++fCount;
+
+ Node* x = SkNEW(Node);
+ x->fChildren[kLeft_Child] = NULL;
+ x->fChildren[kRight_Child] = NULL;
+ x->fItem = t;
+
+ Node* returnNode = x;
+
+ Node* gp = NULL;
+ Node* p = NULL;
+ Node* n = fRoot;
+ Child pc = kLeft_Child; // suppress uninit warning
+ Child gpc = kLeft_Child;
+
+ bool first = true;
+ bool last = true;
+ while (NULL != n) {
+ gpc = pc;
+ pc = fComp(x->fItem, n->fItem) ? kLeft_Child : kRight_Child;
+ first = first && kLeft_Child == pc;
+ last = last && kRight_Child == pc;
+ gp = p;
+ p = n;
+ n = p->fChildren[pc];
+ }
+ if (last) {
+ fLast = x;
+ }
+ if (first) {
+ fFirst = x;
+ }
+
+ if (NULL == p) {
+ fRoot = x;
+ x->fColor = kBlack_Color;
+ x->fParent = NULL;
+ GrAssert(1 == fCount);
+ return Iter(returnNode, this);
+ }
+ p->fChildren[pc] = x;
+ x->fColor = kRed_Color;
+ x->fParent = p;
+
+ do {
+ // assumptions at loop start.
+ GrAssert(NULL != x);
+ GrAssert(kRed_Color == x->fColor);
+ // can't have a grandparent but no parent.
+ GrAssert(!(NULL != gp && NULL == p));
+ // make sure pc and gpc are correct
+ GrAssert(NULL == p || p->fChildren[pc] == x);
+ GrAssert(NULL == gp || gp->fChildren[gpc] == p);
+
+ // if x's parent is black then we didn't violate any of the
+ // red/black properties when we added x as red.
+ if (kBlack_Color == p->fColor) {
+ return Iter(returnNode, this);
+ }
+ // gp must be valid because if p was the root then it is black
+ GrAssert(NULL != gp);
+ // gp must be black since it's child, p, is red.
+ GrAssert(kBlack_Color == gp->fColor);
+
+
+ // x and its parent are red, violating red-black property.
+ Node* u = gp->fChildren[1-gpc];
+ // if x's uncle (p's sibling) is also red then we can flip
+ // p and u to black and make gp red. But then we have to recurse
+ // up to gp since it's parent may also be red.
+ if (NULL != u && kRed_Color == u->fColor) {
+ p->fColor = kBlack_Color;
+ u->fColor = kBlack_Color;
+ gp->fColor = kRed_Color;
+ x = gp;
+ p = x->fParent;
+ if (NULL == p) {
+ // x (prev gp) is the root, color it black and be done.
+ GrAssert(fRoot == x);
+ x->fColor = kBlack_Color;
+ validate();
+ return Iter(returnNode, this);
+ }
+ gp = p->fParent;
+ pc = (p->fChildren[kLeft_Child] == x) ? kLeft_Child :
+ kRight_Child;
+ if (NULL != gp) {
+ gpc = (gp->fChildren[kLeft_Child] == p) ? kLeft_Child :
+ kRight_Child;
+ }
+ continue;
+ } break;
+ } while (true);
+ // Here p is red but u is black and we still have to resolve the fact
+ // that x and p are both red.
+ GrAssert(NULL == gp->fChildren[1-gpc] || kBlack_Color == gp->fChildren[1-gpc]->fColor);
+ GrAssert(kRed_Color == x->fColor);
+ GrAssert(kRed_Color == p->fColor);
+ GrAssert(kBlack_Color == gp->fColor);
+
+ // make x be on the same side of p as p is of gp. If it isn't already
+ // the case then rotate x up to p and swap their labels.
+ if (pc != gpc) {
+ if (kRight_Child == pc) {
+ rotateLeft(p);
+ Node* temp = p;
+ p = x;
+ x = temp;
+ pc = kLeft_Child;
+ } else {
+ rotateRight(p);
+ Node* temp = p;
+ p = x;
+ x = temp;
+ pc = kRight_Child;
+ }
+ }
+ // we now rotate gp down, pulling up p to be it's new parent.
+ // gp's child, u, that is not affected we know to be black. gp's new
+ // child is p's previous child (x's pre-rotation sibling) which must be
+ // black since p is red.
+ GrAssert(NULL == p->fChildren[1-pc] ||
+ kBlack_Color == p->fChildren[1-pc]->fColor);
+ // Since gp's two children are black it can become red if p is made
+ // black. This leaves the black-height of both of p's new subtrees
+ // preserved and removes the red/red parent child relationship.
+ p->fColor = kBlack_Color;
+ gp->fColor = kRed_Color;
+ if (kLeft_Child == pc) {
+ rotateRight(gp);
+ } else {
+ rotateLeft(gp);
+ }
+ validate();
+ return Iter(returnNode, this);
+}
+
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::rotateRight(Node* n) {
+ /* d? d?
+ * / /
+ * n s
+ * / \ ---> / \
+ * s a? c? n
+ * / \ / \
+ * c? b? b? a?
+ */
+ Node* d = n->fParent;
+ Node* s = n->fChildren[kLeft_Child];
+ GrAssert(NULL != s);
+ Node* b = s->fChildren[kRight_Child];
+
+ if (NULL != d) {
+ Child c = d->fChildren[kLeft_Child] == n ? kLeft_Child :
+ kRight_Child;
+ d->fChildren[c] = s;
+ } else {
+ GrAssert(fRoot == n);
+ fRoot = s;
+ }
+ s->fParent = d;
+ s->fChildren[kRight_Child] = n;
+ n->fParent = s;
+ n->fChildren[kLeft_Child] = b;
+ if (NULL != b) {
+ b->fParent = n;
+ }
+
+ GR_DEBUGASSERT(validateChildRelations(d, true));
+ GR_DEBUGASSERT(validateChildRelations(s, true));
+ GR_DEBUGASSERT(validateChildRelations(n, false));
+ GR_DEBUGASSERT(validateChildRelations(n->fChildren[kRight_Child], true));
+ GR_DEBUGASSERT(validateChildRelations(b, true));
+ GR_DEBUGASSERT(validateChildRelations(s->fChildren[kLeft_Child], true));
+}
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::rotateLeft(Node* n) {
+
+ Node* d = n->fParent;
+ Node* s = n->fChildren[kRight_Child];
+ GrAssert(NULL != s);
+ Node* b = s->fChildren[kLeft_Child];
+
+ if (NULL != d) {
+ Child c = d->fChildren[kRight_Child] == n ? kRight_Child :
+ kLeft_Child;
+ d->fChildren[c] = s;
+ } else {
+ GrAssert(fRoot == n);
+ fRoot = s;
+ }
+ s->fParent = d;
+ s->fChildren[kLeft_Child] = n;
+ n->fParent = s;
+ n->fChildren[kRight_Child] = b;
+ if (NULL != b) {
+ b->fParent = n;
+ }
+
+ GR_DEBUGASSERT(validateChildRelations(d, true));
+ GR_DEBUGASSERT(validateChildRelations(s, true));
+ GR_DEBUGASSERT(validateChildRelations(n, true));
+ GR_DEBUGASSERT(validateChildRelations(n->fChildren[kLeft_Child], true));
+ GR_DEBUGASSERT(validateChildRelations(b, true));
+ GR_DEBUGASSERT(validateChildRelations(s->fChildren[kRight_Child], true));
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Node* GrRedBlackTree<T,C>::SuccessorNode(Node* x) {
+ GrAssert(NULL != x);
+ if (NULL != x->fChildren[kRight_Child]) {
+ x = x->fChildren[kRight_Child];
+ while (NULL != x->fChildren[kLeft_Child]) {
+ x = x->fChildren[kLeft_Child];
+ }
+ return x;
+ }
+ while (NULL != x->fParent && x == x->fParent->fChildren[kRight_Child]) {
+ x = x->fParent;
+ }
+ return x->fParent;
+}
+
+template <typename T, typename C>
+typename GrRedBlackTree<T,C>::Node* GrRedBlackTree<T,C>::PredecessorNode(Node* x) {
+ GrAssert(NULL != x);
+ if (NULL != x->fChildren[kLeft_Child]) {
+ x = x->fChildren[kLeft_Child];
+ while (NULL != x->fChildren[kRight_Child]) {
+ x = x->fChildren[kRight_Child];
+ }
+ return x;
+ }
+ while (NULL != x->fParent && x == x->fParent->fChildren[kLeft_Child]) {
+ x = x->fParent;
+ }
+ return x->fParent;
+}
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::deleteAtNode(Node* x) {
+ GrAssert(NULL != x);
+ validate();
+ --fCount;
+
+ bool hasLeft = NULL != x->fChildren[kLeft_Child];
+ bool hasRight = NULL != x->fChildren[kRight_Child];
+ Child c = hasLeft ? kLeft_Child : kRight_Child;
+
+ if (hasLeft && hasRight) {
+ // first and last can't have two children.
+ GrAssert(fFirst != x);
+ GrAssert(fLast != x);
+ // if x is an interior node then we find it's successor
+ // and swap them.
+ Node* s = x->fChildren[kRight_Child];
+ while (NULL != s->fChildren[kLeft_Child]) {
+ s = s->fChildren[kLeft_Child];
+ }
+ GrAssert(NULL != s);
+ // this might be expensive relative to swapping node ptrs around.
+ // depends on T.
+ x->fItem = s->fItem;
+ x = s;
+ c = kRight_Child;
+ } else if (NULL == x->fParent) {
+ // if x was the root we just replace it with its child and make
+ // the new root (if the tree is not empty) black.
+ GrAssert(fRoot == x);
+ fRoot = x->fChildren[c];
+ if (NULL != fRoot) {
+ fRoot->fParent = NULL;
+ fRoot->fColor = kBlack_Color;
+ if (x == fLast) {
+ GrAssert(c == kLeft_Child);
+ fLast = fRoot;
+ } else if (x == fFirst) {
+ GrAssert(c == kRight_Child);
+ fFirst = fRoot;
+ }
+ } else {
+ GrAssert(fFirst == fLast && x == fFirst);
+ fFirst = NULL;
+ fLast = NULL;
+ GrAssert(0 == fCount);
+ }
+ delete x;
+ validate();
+ return;
+ }
+
+ Child pc;
+ Node* p = x->fParent;
+ pc = p->fChildren[kLeft_Child] == x ? kLeft_Child : kRight_Child;
+
+ if (NULL == x->fChildren[c]) {
+ if (fLast == x) {
+ fLast = p;
+ GrAssert(p == PredecessorNode(x));
+ } else if (fFirst == x) {
+ fFirst = p;
+ GrAssert(p == SuccessorNode(x));
+ }
+ // x has two implicit black children.
+ Color xcolor = x->fColor;
+ p->fChildren[pc] = NULL;
+ delete x;
+ x = NULL;
+ // when x is red it can be with an implicit black leaf without
+ // violating any of the red-black tree properties.
+ if (kRed_Color == xcolor) {
+ validate();
+ return;
+ }
+ // s is p's other child (x's sibling)
+ Node* s = p->fChildren[1-pc];
+
+ //s cannot be an implicit black node because the original
+ // black-height at x was >= 2 and s's black-height must equal the
+ // initial black height of x.
+ GrAssert(NULL != s);
+ GrAssert(p == s->fParent);
+
+ // assigned in loop
+ Node* sl;
+ Node* sr;
+ bool slRed;
+ bool srRed;
+
+ do {
+ // When we start this loop x may already be deleted it is/was
+ // p's child on its pc side. x's children are/were black. The
+ // first time through the loop they are implict children.
+ // On later passes we will be walking up the tree and they will
+ // be real nodes.
+ // The x side of p has a black-height that is one less than the
+ // s side. It must be rebalanced.
+ GrAssert(NULL != s);
+ GrAssert(p == s->fParent);
+ GrAssert(NULL == x || x->fParent == p);
+
+ //sl and sr are s's children, which may be implicit.
+ sl = s->fChildren[kLeft_Child];
+ sr = s->fChildren[kRight_Child];
+
+ // if the s is red we will rotate s and p, swap their colors so
+ // that x's new sibling is black
+ if (kRed_Color == s->fColor) {
+ // if s is red then it's parent must be black.
+ GrAssert(kBlack_Color == p->fColor);
+ // s's children must also be black since s is red. They can't
+ // be implicit since s is red and it's black-height is >= 2.
+ GrAssert(NULL != sl && kBlack_Color == sl->fColor);
+ GrAssert(NULL != sr && kBlack_Color == sr->fColor);
+ p->fColor = kRed_Color;
+ s->fColor = kBlack_Color;
+ if (kLeft_Child == pc) {
+ rotateLeft(p);
+ s = sl;
+ } else {
+ rotateRight(p);
+ s = sr;
+ }
+ sl = s->fChildren[kLeft_Child];
+ sr = s->fChildren[kRight_Child];
+ }
+ // x and s are now both black.
+ GrAssert(kBlack_Color == s->fColor);
+ GrAssert(NULL == x || kBlack_Color == x->fColor);
+ GrAssert(p == s->fParent);
+ GrAssert(NULL == x || p == x->fParent);
+
+ // when x is deleted its subtree will have reduced black-height.
+ slRed = (NULL != sl && kRed_Color == sl->fColor);
+ srRed = (NULL != sr && kRed_Color == sr->fColor);
+ if (!slRed && !srRed) {
+ // if s can be made red that will balance out x's removal
+ // to make both subtrees of p have the same black-height.
+ if (kBlack_Color == p->fColor) {
+ s->fColor = kRed_Color;
+ // now subtree at p has black-height of one less than
+ // p's parent's other child's subtree. We move x up to
+ // p and go through the loop again. At the top of loop
+ // we assumed x and x's children are black, which holds
+ // by above ifs.
+ // if p is the root there is no other subtree to balance
+ // against.
+ x = p;
+ p = x->fParent;
+ if (NULL == p) {
+ GrAssert(fRoot == x);
+ validate();
+ return;
+ } else {
+ pc = p->fChildren[kLeft_Child] == x ? kLeft_Child :
+ kRight_Child;
+
+ }
+ s = p->fChildren[1-pc];
+ GrAssert(NULL != s);
+ GrAssert(p == s->fParent);
+ continue;
+ } else if (kRed_Color == p->fColor) {
+ // we can make p black and s red. This balance out p's
+ // two subtrees and keep the same black-height as it was
+ // before the delete.
+ s->fColor = kRed_Color;
+ p->fColor = kBlack_Color;
+ validate();
+ return;
+ }
+ }
+ break;
+ } while (true);
+ // if we made it here one or both of sl and sr is red.
+ // s and x are black. We make sure that a red child is on
+ // the same side of s as s is of p.
+ GrAssert(slRed || srRed);
+ if (kLeft_Child == pc && !srRed) {
+ s->fColor = kRed_Color;
+ sl->fColor = kBlack_Color;
+ rotateRight(s);
+ sr = s;
+ s = sl;
+ //sl = s->fChildren[kLeft_Child]; don't need this
+ } else if (kRight_Child == pc && !slRed) {
+ s->fColor = kRed_Color;
+ sr->fColor = kBlack_Color;
+ rotateLeft(s);
+ sl = s;
+ s = sr;
+ //sr = s->fChildren[kRight_Child]; don't need this
+ }
+ // now p is either red or black, x and s are red and s's 1-pc
+ // child is red.
+ // We rotate p towards x, pulling s up to replace p. We make
+ // p be black and s takes p's old color.
+ // Whether p was red or black, we've increased its pc subtree
+ // rooted at x by 1 (balancing the imbalance at the start) and
+ // we've also its subtree rooted at s's black-height by 1. This
+ // can be balanced by making s's red child be black.
+ s->fColor = p->fColor;
+ p->fColor = kBlack_Color;
+ if (kLeft_Child == pc) {
+ GrAssert(NULL != sr && kRed_Color == sr->fColor);
+ sr->fColor = kBlack_Color;
+ rotateLeft(p);
+ } else {
+ GrAssert(NULL != sl && kRed_Color == sl->fColor);
+ sl->fColor = kBlack_Color;
+ rotateRight(p);
+ }
+ }
+ else {
+ // x has exactly one implicit black child. x cannot be red.
+ // Proof by contradiction: Assume X is red. Let c0 be x's implicit
+ // child and c1 be its non-implicit child. c1 must be black because
+ // red nodes always have two black children. Then the two subtrees
+ // of x rooted at c0 and c1 will have different black-heights.
+ GrAssert(kBlack_Color == x->fColor);
+ // So we know x is black and has one implicit black child, c0. c1
+ // must be red, otherwise the subtree at c1 will have a different
+ // black-height than the subtree rooted at c0.
+ GrAssert(kRed_Color == x->fChildren[c]->fColor);
+ // replace x with c1, making c1 black, preserves all red-black tree
+ // props.
+ Node* c1 = x->fChildren[c];
+ if (x == fFirst) {
+ GrAssert(c == kRight_Child);
+ fFirst = c1;
+ while (NULL != fFirst->fChildren[kLeft_Child]) {
+ fFirst = fFirst->fChildren[kLeft_Child];
+ }
+ GrAssert(fFirst == SuccessorNode(x));
+ } else if (x == fLast) {
+ GrAssert(c == kLeft_Child);
+ fLast = c1;
+ while (NULL != fLast->fChildren[kRight_Child]) {
+ fLast = fLast->fChildren[kRight_Child];
+ }
+ GrAssert(fLast == PredecessorNode(x));
+ }
+ c1->fParent = p;
+ p->fChildren[pc] = c1;
+ c1->fColor = kBlack_Color;
+ delete x;
+ validate();
+ }
+ validate();
+}
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::RecursiveDelete(Node* x) {
+ if (NULL != x) {
+ RecursiveDelete(x->fChildren[kLeft_Child]);
+ RecursiveDelete(x->fChildren[kRight_Child]);
+ delete x;
+ }
+}
+
+#if GR_DEBUG
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::validate() const {
+ if (fCount) {
+ GrAssert(NULL == fRoot->fParent);
+ GrAssert(NULL != fFirst);
+ GrAssert(NULL != fLast);
+
+ GrAssert(kBlack_Color == fRoot->fColor);
+ if (1 == fCount) {
+ GrAssert(fFirst == fRoot);
+ GrAssert(fLast == fRoot);
+ GrAssert(0 == fRoot->fChildren[kLeft_Child]);
+ GrAssert(0 == fRoot->fChildren[kRight_Child]);
+ }
+ } else {
+ GrAssert(NULL == fRoot);
+ GrAssert(NULL == fFirst);
+ GrAssert(NULL == fLast);
+ }
+#if DEEP_VALIDATE
+ int bh;
+ int count = checkNode(fRoot, &bh);
+ GrAssert(count == fCount);
+#endif
+}
+
+template <typename T, typename C>
+int GrRedBlackTree<T,C>::checkNode(Node* n, int* bh) const {
+ if (NULL != n) {
+ GrAssert(validateChildRelations(n, false));
+ if (kBlack_Color == n->fColor) {
+ *bh += 1;
+ }
+ GrAssert(!fComp(n->fItem, fFirst->fItem));
+ GrAssert(!fComp(fLast->fItem, n->fItem));
+ int leftBh = *bh;
+ int rightBh = *bh;
+ int cl = checkNode(n->fChildren[kLeft_Child], &leftBh);
+ int cr = checkNode(n->fChildren[kRight_Child], &rightBh);
+ GrAssert(leftBh == rightBh);
+ *bh = leftBh;
+ return 1 + cl + cr;
+ }
+ return 0;
+}
+
+template <typename T, typename C>
+bool GrRedBlackTree<T,C>::validateChildRelations(const Node* n,
+ bool allowRedRed) const {
+ if (NULL != n) {
+ if (NULL != n->fChildren[kLeft_Child] ||
+ NULL != n->fChildren[kRight_Child]) {
+ if (n->fChildren[kLeft_Child] == n->fChildren[kRight_Child]) {
+ return validateChildRelationsFailed();
+ }
+ if (n->fChildren[kLeft_Child] == n->fParent &&
+ NULL != n->fParent) {
+ return validateChildRelationsFailed();
+ }
+ if (n->fChildren[kRight_Child] == n->fParent &&
+ NULL != n->fParent) {
+ return validateChildRelationsFailed();
+ }
+ if (NULL != n->fChildren[kLeft_Child]) {
+ if (!allowRedRed &&
+ kRed_Color == n->fChildren[kLeft_Child]->fColor &&
+ kRed_Color == n->fColor) {
+ return validateChildRelationsFailed();
+ }
+ if (n->fChildren[kLeft_Child]->fParent != n) {
+ return validateChildRelationsFailed();
+ }
+ if (!(fComp(n->fChildren[kLeft_Child]->fItem, n->fItem) ||
+ (!fComp(n->fChildren[kLeft_Child]->fItem, n->fItem) &&
+ !fComp(n->fItem, n->fChildren[kLeft_Child]->fItem)))) {
+ return validateChildRelationsFailed();
+ }
+ }
+ if (NULL != n->fChildren[kRight_Child]) {
+ if (!allowRedRed &&
+ kRed_Color == n->fChildren[kRight_Child]->fColor &&
+ kRed_Color == n->fColor) {
+ return validateChildRelationsFailed();
+ }
+ if (n->fChildren[kRight_Child]->fParent != n) {
+ return validateChildRelationsFailed();
+ }
+ if (!(fComp(n->fItem, n->fChildren[kRight_Child]->fItem) ||
+ (!fComp(n->fChildren[kRight_Child]->fItem, n->fItem) &&
+ !fComp(n->fItem, n->fChildren[kRight_Child]->fItem)))) {
+ return validateChildRelationsFailed();
+ }
+ }
+ }
+ }
+ return true;
+}
+#endif
+
+#include "SkRandom.h"
+
+template <typename T, typename C>
+void GrRedBlackTree<T,C>::UnitTest() {
+ GrRedBlackTree<int> tree;
+ typedef GrRedBlackTree<int>::Iter iter;
+
+ SkMWCRandom r;
+
+ int count[100] = {0};
+ // add 10K ints
+ for (int i = 0; i < 10000; ++i) {
+ int x = r.nextU()%100;
+ SkDEBUGCODE(Iter xi = ) tree.insert(x);
+ GrAssert(*xi == x);
+ ++count[x];
+ }
+
+ tree.insert(0);
+ ++count[0];
+ tree.insert(99);
+ ++count[99];
+ GrAssert(*tree.begin() == 0);
+ GrAssert(*tree.last() == 99);
+ GrAssert(--(++tree.begin()) == tree.begin());
+ GrAssert(--tree.end() == tree.last());
+ GrAssert(tree.count() == 10002);
+
+ int c = 0;
+ // check that we iterate through the correct number of
+ // elements and they are properly sorted.
+ for (Iter a = tree.begin(); tree.end() != a; ++a) {
+ Iter b = a;
+ ++b;
+ ++c;
+ GrAssert(b == tree.end() || *a <= *b);
+ }
+ GrAssert(c == tree.count());
+
+ // check that the tree reports the correct number of each int
+ // and that we can iterate through them correctly both forward
+ // and backward.
+ for (int i = 0; i < 100; ++i) {
+ int c;
+ c = tree.countOf(i);
+ GrAssert(c == count[i]);
+ c = 0;
+ Iter iter = tree.findFirst(i);
+ while (iter != tree.end() && *iter == i) {
+ ++c;
+ ++iter;
+ }
+ GrAssert(count[i] == c);
+ c = 0;
+ iter = tree.findLast(i);
+ if (iter != tree.end()) {
+ do {
+ if (*iter == i) {
+ ++c;
+ } else {
+ break;
+ }
+ if (iter != tree.begin()) {
+ --iter;
+ } else {
+ break;
+ }
+ } while (true);
+ }
+ GrAssert(c == count[i]);
+ }
+ // remove all the ints between 25 and 74. Randomly chose to remove
+ // the first, last, or any entry for each.
+ for (int i = 25; i < 75; ++i) {
+ while (0 != tree.countOf(i)) {
+ --count[i];
+ int x = r.nextU() % 3;
+ Iter iter;
+ switch (x) {
+ case 0:
+ iter = tree.findFirst(i);
+ break;
+ case 1:
+ iter = tree.findLast(i);
+ break;
+ case 2:
+ default:
+ iter = tree.find(i);
+ break;
+ }
+ tree.remove(iter);
+ }
+ GrAssert(0 == count[i]);
+ GrAssert(tree.findFirst(i) == tree.end());
+ GrAssert(tree.findLast(i) == tree.end());
+ GrAssert(tree.find(i) == tree.end());
+ }
+ // remove all of the 0 entries. (tests removing begin())
+ GrAssert(*tree.begin() == 0);
+ GrAssert(*(--tree.end()) == 99);
+ while (0 != tree.countOf(0)) {
+ --count[0];
+ tree.remove(tree.find(0));
+ }
+ GrAssert(0 == count[0]);
+ GrAssert(tree.findFirst(0) == tree.end());
+ GrAssert(tree.findLast(0) == tree.end());
+ GrAssert(tree.find(0) == tree.end());
+ GrAssert(0 < *tree.begin());
+
+ // remove all the 99 entries (tests removing last()).
+ while (0 != tree.countOf(99)) {
+ --count[99];
+ tree.remove(tree.find(99));
+ }
+ GrAssert(0 == count[99]);
+ GrAssert(tree.findFirst(99) == tree.end());
+ GrAssert(tree.findLast(99) == tree.end());
+ GrAssert(tree.find(99) == tree.end());
+ GrAssert(99 > *(--tree.end()));
+ GrAssert(tree.last() == --tree.end());
+
+ // Make sure iteration still goes through correct number of entries
+ // and is still sorted correctly.
+ c = 0;
+ for (Iter a = tree.begin(); tree.end() != a; ++a) {
+ Iter b = a;
+ ++b;
+ ++c;
+ GrAssert(b == tree.end() || *a <= *b);
+ }
+ GrAssert(c == tree.count());
+
+ // repeat check that correct number of each entry is in the tree
+ // and iterates correctly both forward and backward.
+ for (int i = 0; i < 100; ++i) {
+ GrAssert(tree.countOf(i) == count[i]);
+ int c = 0;
+ Iter iter = tree.findFirst(i);
+ while (iter != tree.end() && *iter == i) {
+ ++c;
+ ++iter;
+ }
+ GrAssert(count[i] == c);
+ c = 0;
+ iter = tree.findLast(i);
+ if (iter != tree.end()) {
+ do {
+ if (*iter == i) {
+ ++c;
+ } else {
+ break;
+ }
+ if (iter != tree.begin()) {
+ --iter;
+ } else {
+ break;
+ }
+ } while (true);
+ }
+ GrAssert(count[i] == c);
+ }
+
+ // remove all entries
+ while (!tree.empty()) {
+ tree.remove(tree.begin());
+ }
+
+ // test reset on empty tree.
+ tree.reset();
+}
+
+#endif
diff --git a/gpu/GrReducedClip.cpp b/gpu/GrReducedClip.cpp
new file mode 100644
index 00000000..da42e8cf
--- /dev/null
+++ b/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/gpu/GrReducedClip.h b/gpu/GrReducedClip.h
new file mode 100644
index 00000000..abfc244f
--- /dev/null
+++ b/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/gpu/GrRenderTarget.cpp b/gpu/GrRenderTarget.cpp
new file mode 100644
index 00000000..f5585963
--- /dev/null
+++ b/gpu/GrRenderTarget.cpp
@@ -0,0 +1,111 @@
+
+/*
+ * 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 "GrRenderTarget.h"
+
+#include "GrContext.h"
+#include "GrGpu.h"
+#include "GrStencilBuffer.h"
+
+SK_DEFINE_INST_COUNT(GrRenderTarget)
+
+bool GrRenderTarget::readPixels(int left, int top, int width, int height,
+ GrPixelConfig config,
+ void* buffer,
+ size_t rowBytes,
+ uint32_t pixelOpsFlags) {
+ // go through context so that all necessary flushing occurs
+ GrContext* context = this->getContext();
+ if (NULL == context) {
+ return false;
+ }
+ return context->readRenderTargetPixels(this,
+ left, top, width, height,
+ config, buffer, rowBytes,
+ pixelOpsFlags);
+}
+
+void GrRenderTarget::writePixels(int left, int top, int width, int height,
+ GrPixelConfig config,
+ const void* buffer,
+ size_t rowBytes,
+ uint32_t pixelOpsFlags) {
+ // go through context so that all necessary flushing occurs
+ GrContext* context = this->getContext();
+ if (NULL == context) {
+ return;
+ }
+ context->writeRenderTargetPixels(this,
+ left, top, width, height,
+ config, buffer, rowBytes,
+ pixelOpsFlags);
+}
+
+void GrRenderTarget::resolve() {
+ // go through context so that all necessary flushing occurs
+ GrContext* context = this->getContext();
+ if (NULL == context) {
+ return;
+ }
+ context->resolveRenderTarget(this);
+}
+
+size_t GrRenderTarget::sizeInBytes() const {
+ int colorBits;
+ if (kUnknown_GrPixelConfig == fDesc.fConfig) {
+ colorBits = 32; // don't know, make a guess
+ } else {
+ colorBits = GrBytesPerPixel(fDesc.fConfig);
+ }
+ uint64_t size = fDesc.fWidth;
+ size *= fDesc.fHeight;
+ size *= colorBits;
+ size *= GrMax(1, fDesc.fSampleCnt);
+ return (size_t)(size / 8);
+}
+
+void GrRenderTarget::flagAsNeedingResolve(const SkIRect* rect) {
+ if (kCanResolve_ResolveType == getResolveType()) {
+ if (NULL != rect) {
+ fResolveRect.join(*rect);
+ if (!fResolveRect.intersect(0, 0, this->width(), this->height())) {
+ fResolveRect.setEmpty();
+ }
+ } else {
+ fResolveRect.setLTRB(0, 0, this->width(), this->height());
+ }
+ }
+}
+
+void GrRenderTarget::overrideResolveRect(const SkIRect rect) {
+ fResolveRect = rect;
+ if (fResolveRect.isEmpty()) {
+ fResolveRect.setLargestInverted();
+ } else {
+ if (!fResolveRect.intersect(0, 0, this->width(), this->height())) {
+ fResolveRect.setLargestInverted();
+ }
+ }
+}
+
+void GrRenderTarget::setStencilBuffer(GrStencilBuffer* stencilBuffer) {
+ SkRefCnt_SafeAssign(fStencilBuffer, stencilBuffer);
+}
+
+void GrRenderTarget::onRelease() {
+ this->setStencilBuffer(NULL);
+
+ INHERITED::onRelease();
+}
+
+void GrRenderTarget::onAbandon() {
+ this->setStencilBuffer(NULL);
+
+ INHERITED::onAbandon();
+}
diff --git a/gpu/GrResource.cpp b/gpu/GrResource.cpp
new file mode 100644
index 00000000..8fb21e88
--- /dev/null
+++ b/gpu/GrResource.cpp
@@ -0,0 +1,63 @@
+
+/*
+ * 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 "GrResource.h"
+#include "GrGpu.h"
+
+SK_DEFINE_INST_COUNT(GrResource)
+
+GrResource::GrResource(GrGpu* gpu, bool isWrapped) {
+ fGpu = gpu;
+ fCacheEntry = NULL;
+ fDeferredRefCount = 0;
+ if (isWrapped) {
+ fFlags = kWrapped_Flag;
+ } else {
+ fFlags = 0;
+ }
+ fGpu->insertResource(this);
+}
+
+GrResource::~GrResource() {
+ // subclass should have released this.
+ GrAssert(0 == fDeferredRefCount);
+ GrAssert(!this->isValid());
+}
+
+void GrResource::release() {
+ if (NULL != fGpu) {
+ this->onRelease();
+ fGpu->removeResource(this);
+ fGpu = NULL;
+ }
+}
+
+void GrResource::abandon() {
+ if (NULL != fGpu) {
+ this->onAbandon();
+ fGpu->removeResource(this);
+ fGpu = NULL;
+ }
+}
+
+const GrContext* GrResource::getContext() const {
+ if (NULL != fGpu) {
+ return fGpu->getContext();
+ } else {
+ return NULL;
+ }
+}
+
+GrContext* GrResource::getContext() {
+ if (NULL != fGpu) {
+ return fGpu->getContext();
+ } else {
+ return NULL;
+ }
+}
diff --git a/gpu/GrResourceCache.cpp b/gpu/GrResourceCache.cpp
new file mode 100644
index 00000000..96b11aaa
--- /dev/null
+++ b/gpu/GrResourceCache.cpp
@@ -0,0 +1,474 @@
+
+/*
+ * 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 "GrResourceCache.h"
+#include "GrResource.h"
+
+
+GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() {
+ static int32_t gNextType = 0;
+
+ int32_t type = sk_atomic_inc(&gNextType);
+ if (type >= (1 << 8 * sizeof(ResourceType))) {
+ GrCrash("Too many Resource Types");
+ }
+
+ return static_cast<ResourceType>(type);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource)
+ : fKey(key), fResource(resource) {
+ // we assume ownership of the resource, and will unref it when we die
+ GrAssert(resource);
+ resource->ref();
+}
+
+GrResourceEntry::~GrResourceEntry() {
+ fResource->setCacheEntry(NULL);
+ fResource->unref();
+}
+
+#if GR_DEBUG
+void GrResourceEntry::validate() const {
+ GrAssert(fResource);
+ GrAssert(fResource->getCacheEntry() == this);
+ fResource->validate();
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
+ fMaxCount(maxCount),
+ fMaxBytes(maxBytes) {
+#if GR_CACHE_STATS
+ fHighWaterEntryCount = 0;
+ fHighWaterEntryBytes = 0;
+ fHighWaterClientDetachedCount = 0;
+ fHighWaterClientDetachedBytes = 0;
+#endif
+
+ fEntryCount = 0;
+ fEntryBytes = 0;
+ fClientDetachedCount = 0;
+ fClientDetachedBytes = 0;
+
+ fPurging = false;
+
+ fOverbudgetCB = NULL;
+ fOverbudgetData = NULL;
+}
+
+GrResourceCache::~GrResourceCache() {
+ GrAutoResourceCacheValidate atcv(this);
+
+ EntryList::Iter iter;
+
+ // Unlike the removeAll, here we really remove everything, including locked resources.
+ while (GrResourceEntry* entry = fList.head()) {
+ GrAutoResourceCacheValidate atcv(this);
+
+ // remove from our cache
+ fCache.remove(entry->fKey, entry);
+
+ // remove from our llist
+ this->internalDetach(entry);
+
+ delete entry;
+ }
+}
+
+void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
+ if (NULL != maxResources) {
+ *maxResources = fMaxCount;
+ }
+ if (NULL != maxResourceBytes) {
+ *maxResourceBytes = fMaxBytes;
+ }
+}
+
+void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
+ bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
+
+ fMaxCount = maxResources;
+ fMaxBytes = maxResourceBytes;
+
+ if (smaller) {
+ this->purgeAsNeeded();
+ }
+}
+
+void GrResourceCache::internalDetach(GrResourceEntry* entry,
+ BudgetBehaviors behavior) {
+ fList.remove(entry);
+
+ // update our stats
+ if (kIgnore_BudgetBehavior == behavior) {
+ fClientDetachedCount += 1;
+ fClientDetachedBytes += entry->resource()->sizeInBytes();
+
+#if GR_CACHE_STATS
+ if (fHighWaterClientDetachedCount < fClientDetachedCount) {
+ fHighWaterClientDetachedCount = fClientDetachedCount;
+ }
+ if (fHighWaterClientDetachedBytes < fClientDetachedBytes) {
+ fHighWaterClientDetachedBytes = fClientDetachedBytes;
+ }
+#endif
+
+ } else {
+ GrAssert(kAccountFor_BudgetBehavior == behavior);
+
+ fEntryCount -= 1;
+ fEntryBytes -= entry->resource()->sizeInBytes();
+ }
+}
+
+void GrResourceCache::attachToHead(GrResourceEntry* entry,
+ BudgetBehaviors behavior) {
+ fList.addToHead(entry);
+
+ // update our stats
+ if (kIgnore_BudgetBehavior == behavior) {
+ fClientDetachedCount -= 1;
+ fClientDetachedBytes -= entry->resource()->sizeInBytes();
+ } else {
+ GrAssert(kAccountFor_BudgetBehavior == behavior);
+
+ fEntryCount += 1;
+ fEntryBytes += entry->resource()->sizeInBytes();
+
+#if GR_CACHE_STATS
+ if (fHighWaterEntryCount < fEntryCount) {
+ fHighWaterEntryCount = fEntryCount;
+ }
+ if (fHighWaterEntryBytes < fEntryBytes) {
+ fHighWaterEntryBytes = fEntryBytes;
+ }
+#endif
+ }
+}
+
+// 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 entry->resource()->unique();
+ }
+};
+
+GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) {
+ GrAutoResourceCacheValidate atcv(this);
+
+ 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;
+ }
+
+ if (ownershipFlags & kHide_OwnershipFlag) {
+ this->makeExclusive(entry);
+ } else {
+ // Make this resource MRU
+ this->internalDetach(entry);
+ this->attachToHead(entry);
+ }
+
+ return entry->fResource;
+}
+
+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.
+ // each resource destroyed creates and locks 2 resources and
+ // unlocks 1 thereby causing a new purge).
+ GrAssert(!fPurging);
+ GrAutoResourceCacheValidate atcv(this);
+
+ GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource));
+ resource->setCacheEntry(entry);
+
+ this->attachToHead(entry);
+ fCache.insert(key, entry);
+
+ if (ownershipFlags & kHide_OwnershipFlag) {
+ this->makeExclusive(entry);
+ }
+
+}
+
+void GrResourceCache::makeExclusive(GrResourceEntry* entry) {
+ GrAutoResourceCacheValidate atcv(this);
+
+ // 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
+ fExclusiveList.addToHead(entry);
+#endif
+}
+
+void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) {
+ // If the resource went invalid while it was detached then purge it
+ // This can happen when a 3D context was lost,
+ // the client called GrContext::contextDestroyed() to notify Gr,
+ // and then later an SkGpuDevice's destructor releases its backing
+ // texture (which was invalidated at contextDestroyed time).
+ fClientDetachedCount -= 1;
+ fEntryCount -= 1;
+ size_t size = entry->resource()->sizeInBytes();
+ fClientDetachedBytes -= size;
+ fEntryBytes -= size;
+}
+
+void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) {
+ GrAutoResourceCacheValidate atcv(this);
+
+#if GR_DEBUG
+ fExclusiveList.remove(entry);
+#endif
+
+ if (entry->resource()->isValid()) {
+ // 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);
+ }
+}
+
+/**
+ * Destroying a resource may potentially trigger the unlock of additional
+ * resources which in turn will trigger a nested purge. We block the nested
+ * purge using the fPurging variable. However, the initial purge will keep
+ * looping until either all resources in the cache are unlocked or we've met
+ * the budget. There is an assertion in createAndLock to check against a
+ * resource's destructor inserting new resources into the cache. If these
+ * new resources were unlocked before purgeAsNeeded completed it could
+ * potentially make purgeAsNeeded loop infinitely.
+ *
+ * extraCount and extraBytes are added to the current resource totals to account
+ * for incoming resources (e.g., GrContext is about to add 10MB split between
+ * 10 textures).
+ */
+void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) {
+ if (fPurging) {
+ return;
+ }
+
+ fPurging = true;
+
+ this->internalPurge(extraCount, extraBytes);
+ if (((fEntryCount+extraCount) > fMaxCount ||
+ (fEntryBytes+extraBytes) > fMaxBytes) &&
+ NULL != fOverbudgetCB) {
+ // Despite the purge we're still over budget. See if Ganesh can
+ // release some resources and purge again.
+ if ((*fOverbudgetCB)(fOverbudgetData)) {
+ this->internalPurge(extraCount, extraBytes);
+ }
+ }
+
+ fPurging = false;
+}
+
+void GrResourceCache::deleteResource(GrResourceEntry* entry) {
+ GrAssert(1 == entry->fResource->getRefCnt());
+
+ // remove from our cache
+ fCache.remove(entry->key(), entry);
+
+ // remove from our llist
+ this->internalDetach(entry);
+ delete entry;
+}
+
+void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) {
+ SkASSERT(fPurging);
+
+ bool withinBudget = false;
+ bool changed = false;
+
+ // The purging process is repeated several times since one pass
+ // may free up other resources
+ do {
+ EntryList::Iter iter;
+
+ changed = false;
+
+ // Note: the following code relies on the fact that the
+ // doubly linked list doesn't invalidate its data/pointers
+ // outside of the specific area where a deletion occurs (e.g.,
+ // in internalDetach)
+ GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
+
+ while (NULL != entry) {
+ GrAutoResourceCacheValidate atcv(this);
+
+ if ((fEntryCount+extraCount) <= fMaxCount &&
+ (fEntryBytes+extraBytes) <= fMaxBytes) {
+ withinBudget = true;
+ break;
+ }
+
+ GrResourceEntry* prev = iter.prev();
+ if (entry->fResource->unique()) {
+ changed = true;
+ this->deleteResource(entry);
+ }
+ entry = prev;
+ }
+ } while (!withinBudget && changed);
+}
+
+void GrResourceCache::purgeAllUnlocked() {
+ GrAutoResourceCacheValidate atcv(this);
+
+ // we can have one GrResource holding a lock on another
+ // so we don't want to just do a simple loop kicking each
+ // entry out. Instead change the budget and purge.
+
+ int savedMaxBytes = fMaxBytes;
+ int savedMaxCount = fMaxCount;
+ fMaxBytes = (size_t) -1;
+ fMaxCount = 0;
+ this->purgeAsNeeded();
+
+#if GR_DEBUG
+ GrAssert(fExclusiveList.countEntries() == fClientDetachedCount);
+ GrAssert(countBytes(fExclusiveList) == fClientDetachedBytes);
+ if (!fCache.count()) {
+ // Items may have been detached from the cache (such as the backing
+ // texture for an SkGpuDevice). The above purge would not have removed
+ // them.
+ GrAssert(fEntryCount == fClientDetachedCount);
+ GrAssert(fEntryBytes == fClientDetachedBytes);
+ GrAssert(fList.isEmpty());
+ }
+#endif
+
+ fMaxBytes = savedMaxBytes;
+ fMaxCount = savedMaxCount;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_DEBUG
+size_t GrResourceCache::countBytes(const EntryList& list) {
+ size_t bytes = 0;
+
+ EntryList::Iter iter;
+
+ const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list),
+ EntryList::Iter::kTail_IterStart);
+
+ for ( ; NULL != entry; entry = iter.prev()) {
+ bytes += entry->resource()->sizeInBytes();
+ }
+ return bytes;
+}
+
+static bool both_zero_or_nonzero(int count, size_t bytes) {
+ return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
+}
+
+void GrResourceCache::validate() const {
+ fList.validate();
+ fExclusiveList.validate();
+ GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
+ GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
+ GrAssert(fClientDetachedBytes <= fEntryBytes);
+ GrAssert(fClientDetachedCount <= fEntryCount);
+ GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());
+
+ fCache.validate();
+
+
+ EntryList::Iter iter;
+
+ // check that the exclusively held entries are okay
+ const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList),
+ EntryList::Iter::kHead_IterStart);
+
+ for ( ; NULL != entry; entry = iter.next()) {
+ entry->validate();
+ }
+
+ // check that the shareable entries are okay
+ entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart);
+
+ int count = 0;
+ for ( ; NULL != entry; entry = iter.next()) {
+ entry->validate();
+ GrAssert(fCache.find(entry->key()));
+ count += 1;
+ }
+ GrAssert(count == fEntryCount - fClientDetachedCount);
+
+ size_t bytes = countBytes(fList);
+ GrAssert(bytes == fEntryBytes - fClientDetachedBytes);
+
+ bytes = countBytes(fExclusiveList);
+ GrAssert(bytes == fClientDetachedBytes);
+
+ GrAssert(fList.countEntries() == fEntryCount - fClientDetachedCount);
+
+ GrAssert(fExclusiveList.countEntries() == fClientDetachedCount);
+}
+#endif // GR_DEBUG
+
+#if GR_CACHE_STATS
+
+void GrResourceCache::printStats() {
+ int locked = 0;
+
+ EntryList::Iter iter;
+
+ GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
+
+ for ( ; NULL != entry; entry = iter.prev()) {
+ if (entry->fResource->getRefCnt() > 1) {
+ ++locked;
+ }
+ }
+
+ SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes);
+ SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n",
+ fEntryCount, locked, fHighWaterEntryCount);
+ SkDebugf("\t\tEntry Bytes: current %d high %d\n",
+ fEntryBytes, fHighWaterEntryBytes);
+ SkDebugf("\t\tDetached Entry Count: current %d high %d\n",
+ fClientDetachedCount, fHighWaterClientDetachedCount);
+ SkDebugf("\t\tDetached Bytes: current %d high %d\n",
+ fClientDetachedBytes, fHighWaterClientDetachedBytes);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/gpu/GrResourceCache.h b/gpu/GrResourceCache.h
new file mode 100644
index 00000000..4338d4ad
--- /dev/null
+++ b/gpu/GrResourceCache.h
@@ -0,0 +1,426 @@
+
+/*
+ * 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 GrResourceCache_DEFINED
+#define GrResourceCache_DEFINED
+
+#include "GrConfig.h"
+#include "GrTypes.h"
+#include "GrTHashCache.h"
+#include "GrBinHashKey.h"
+#include "SkTInternalLList.h"
+
+class GrResource;
+class GrResourceEntry;
+
+class GrResourceKey {
+public:
+ enum {
+ kHashBits = 7,
+ kHashCount = 1 << kHashBits,
+ kHashMask = kHashCount - 1
+ };
+
+ static GrCacheID::Domain ScratchDomain() {
+ static const GrCacheID::Domain gDomain = GrCacheID::GenerateDomain();
+ return gDomain;
+ }
+
+ /** Uniquely identifies the GrResource subclass in the key to avoid collisions
+ across resource types. */
+ typedef uint8_t ResourceType;
+
+ /** Flags set by the GrResource subclass. */
+ typedef uint8_t ResourceFlags;
+
+ /** Generate a unique ResourceType */
+ static ResourceType GenerateResourceType();
+
+ /** Creates a key for resource */
+ GrResourceKey(const GrCacheID& id, ResourceType type, ResourceFlags flags) {
+ this->init(id.getDomain(), id.getKey(), type, flags);
+ };
+
+ GrResourceKey(const GrResourceKey& src) {
+ fKey = src.fKey;
+ }
+
+ GrResourceKey() {
+ fKey.fHashedKey.reset();
+ }
+
+ void reset(const GrCacheID& id, ResourceType type, ResourceFlags flags) {
+ this->init(id.getDomain(), id.getKey(), type, flags);
+ }
+
+ //!< returns hash value [0..kHashMask] for the key
+ int getHash() const {
+ return fKey.fHashedKey.getHash() & kHashMask;
+ }
+
+ bool isScratch() const {
+ return ScratchDomain() ==
+ *reinterpret_cast<const GrCacheID::Domain*>(fKey.fHashedKey.getData() +
+ kCacheIDDomainOffset);
+ }
+
+ ResourceType getResourceType() const {
+ return *reinterpret_cast<const ResourceType*>(fKey.fHashedKey.getData() +
+ kResourceTypeOffset);
+ }
+
+ ResourceFlags getResourceFlags() const {
+ return *reinterpret_cast<const ResourceFlags*>(fKey.fHashedKey.getData() +
+ kResourceFlagsOffset);
+ }
+
+ int compare(const GrResourceKey& other) const {
+ return fKey.fHashedKey.compare(other.fKey.fHashedKey);
+ }
+
+ static bool LT(const GrResourceKey& a, const GrResourceKey& b) {
+ return a.compare(b) < 0;
+ }
+
+ static bool EQ(const GrResourceKey& a, const GrResourceKey& b) {
+ return 0 == a.compare(b);
+ }
+
+ inline static bool LT(const GrResourceEntry& entry, const GrResourceKey& key);
+ inline static bool EQ(const GrResourceEntry& entry, const GrResourceKey& key);
+ inline static bool LT(const GrResourceEntry& a, const GrResourceEntry& b);
+ inline static bool EQ(const GrResourceEntry& a, const GrResourceEntry& b);
+
+private:
+ enum {
+ kCacheIDKeyOffset = 0,
+ kCacheIDDomainOffset = kCacheIDKeyOffset + sizeof(GrCacheID::Key),
+ kResourceTypeOffset = kCacheIDDomainOffset + sizeof(GrCacheID::Domain),
+ kResourceFlagsOffset = kResourceTypeOffset + sizeof(ResourceType),
+ kPadOffset = kResourceFlagsOffset + sizeof(ResourceFlags),
+ kKeySize = SkAlign4(kPadOffset),
+ kPadSize = kKeySize - kPadOffset
+ };
+
+ void init(const GrCacheID::Domain domain,
+ const GrCacheID::Key& key,
+ ResourceType type,
+ ResourceFlags flags) {
+ union {
+ uint8_t fKey8[kKeySize];
+ uint32_t fKey32[kKeySize / 4];
+ } keyData;
+
+ uint8_t* k = keyData.fKey8;
+ memcpy(k + kCacheIDKeyOffset, key.fData8, sizeof(GrCacheID::Key));
+ memcpy(k + kCacheIDDomainOffset, &domain, sizeof(GrCacheID::Domain));
+ memcpy(k + kResourceTypeOffset, &type, sizeof(ResourceType));
+ memcpy(k + kResourceFlagsOffset, &flags, sizeof(ResourceFlags));
+ memset(k + kPadOffset, 0, kPadSize);
+ fKey.fHashedKey.setKeyData(keyData.fKey32);
+ }
+
+ struct Key;
+ typedef GrTBinHashKey<Key, kKeySize> HashedKey;
+
+ struct Key {
+ int compare(const HashedKey& hashedKey) const {
+ return fHashedKey.compare(hashedKey);
+ }
+
+ HashedKey fHashedKey;
+ };
+
+ Key fKey;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrResourceEntry {
+public:
+ GrResource* resource() const { return fResource; }
+ const GrResourceKey& key() const { return fKey; }
+
+#if GR_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+private:
+ GrResourceEntry(const GrResourceKey& key, GrResource* resource);
+ ~GrResourceEntry();
+
+ GrResourceKey fKey;
+ GrResource* fResource;
+
+ // we're a linked list
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrResourceEntry);
+
+ friend class GrResourceCache;
+ friend class GrDLinkedList;
+};
+
+bool GrResourceKey::LT(const GrResourceEntry& entry, const GrResourceKey& key) {
+ return LT(entry.key(), key);
+}
+
+bool GrResourceKey::EQ(const GrResourceEntry& entry, const GrResourceKey& key) {
+ return EQ(entry.key(), key);
+}
+
+bool GrResourceKey::LT(const GrResourceEntry& a, const GrResourceEntry& b) {
+ return LT(a.key(), b.key());
+}
+
+bool GrResourceKey::EQ(const GrResourceEntry& a, const GrResourceEntry& b) {
+ return EQ(a.key(), b.key());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "GrTHashCache.h"
+
+/**
+ * Cache of GrResource objects.
+ *
+ * These have a corresponding GrResourceKey, built from 128bits identifying the
+ * resource.
+ *
+ * The cache stores the entries in a double-linked list, which is its LRU.
+ * When an entry is "locked" (i.e. given to the caller), it is moved to the
+ * head of the list. If/when we must purge some of the entries, we walk the
+ * list backwards from the tail, since those are the least recently used.
+ *
+ * For fast searches, we maintain a sorted array (based on the GrResourceKey)
+ * which we can bsearch. When a new entry is added, it is inserted into this
+ * array.
+ *
+ * For even faster searches, a hash is computed from the Key. If there is
+ * a collision between two keys with the same hash, we fall back on the
+ * bsearch, and update the hash to reflect the most recent Key requested.
+ *
+ * It is a goal to make the GrResourceCache the central repository and bookkeeper
+ * of all resources. It should replace the linked list of GrResources that
+ * GrGpu uses to call abandon/release.
+ */
+class GrResourceCache {
+public:
+ GrResourceCache(int maxCount, size_t maxBytes);
+ ~GrResourceCache();
+
+ /**
+ * Return the current resource cache limits.
+ *
+ * @param maxResource If non-null, returns maximum number of resources
+ * that can be held in the cache.
+ * @param maxBytes If non-null, returns maximum number of bytes of
+ * gpu memory that can be held in the cache.
+ */
+ void getLimits(int* maxResources, size_t* maxBytes) const;
+
+ /**
+ * Specify the resource cache limits. If the current cache exceeds either
+ * of these, it will be purged (LRU) to keep the cache within these limits.
+ *
+ * @param maxResources The maximum number of resources that can be held in
+ * the cache.
+ * @param maxBytes The maximum number of bytes of resource memory that
+ * can be held in the cache.
+ */
+ void setLimits(int maxResources, size_t maxResourceBytes);
+
+ /**
+ * The callback function used by the cache when it is still over budget
+ * after a purge. The passed in 'data' is the same 'data' handed to
+ * setOverbudgetCallback. The callback returns true if some resources
+ * have been freed.
+ */
+ typedef bool (*PFOverbudgetCB)(void* data);
+
+ /**
+ * Set the callback the cache should use when it is still over budget
+ * after a purge. The 'data' provided here will be passed back to the
+ * callback. Note that the cache will attempt to purge any resources newly
+ * freed by the callback.
+ */
+ void setOverbudgetCallback(PFOverbudgetCB overbudgetCB, void* data) {
+ fOverbudgetCB = overbudgetCB;
+ fOverbudgetData = data;
+ }
+
+ /**
+ * Returns the number of bytes consumed by cached resources.
+ */
+ 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,
+ uint32_t ownershipFlags = 0);
+
+ /**
+ * 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 addResource(const GrResourceKey& key,
+ GrResource* resource,
+ uint32_t ownershipFlags = 0);
+
+ /**
+ * Determines if the cache contains an entry matching a key. If a matching
+ * entry exists but was detached then it will not be found.
+ */
+ bool hasKey(const GrResourceKey& key) const { return NULL != fCache.find(key); }
+
+ /**
+ * Hide 'entry' so that future searches will not find it. Such
+ * hidden entries will not be purged. The entry still counts against
+ * the cache's budget and should be made non-exclusive when exclusive access
+ * is no longer needed.
+ */
+ void makeExclusive(GrResourceEntry* entry);
+
+ /**
+ * Restore 'entry' so that it can be found by future searches. 'entry'
+ * will also be purgeable (provided its lock count is now 0.)
+ */
+ void makeNonExclusive(GrResourceEntry* entry);
+
+ /**
+ * Remove a resource from the cache and delete it!
+ */
+ void deleteResource(GrResourceEntry* entry);
+
+ /**
+ * Removes every resource in the cache that isn't locked.
+ */
+ void purgeAllUnlocked();
+
+ /**
+ * Allow cache to purge unused resources to obey resource limitations
+ * Note: this entry point will be hidden (again) once totally ref-driven
+ * cache maintenance is implemented. Note that the overbudget callback
+ * will be called if the initial purge doesn't get the cache under
+ * its budget.
+ *
+ * extraCount and extraBytes are added to the current resource allocation
+ * to make sure enough room is available for future additions (e.g,
+ * 10MB across 10 textures is about to be added).
+ */
+ void purgeAsNeeded(int extraCount = 0, size_t extraBytes = 0);
+
+#if GR_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+#if GR_CACHE_STATS
+ void printStats();
+#endif
+
+private:
+ enum BudgetBehaviors {
+ kAccountFor_BudgetBehavior,
+ kIgnore_BudgetBehavior
+ };
+
+ void internalDetach(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior);
+ void attachToHead(GrResourceEntry*, BudgetBehaviors behavior = kAccountFor_BudgetBehavior);
+
+ void removeInvalidResource(GrResourceEntry* entry);
+
+ GrTHashTable<GrResourceEntry, GrResourceKey, 8> fCache;
+
+ // We're an internal doubly linked list
+ typedef SkTInternalLList<GrResourceEntry> EntryList;
+ EntryList fList;
+
+#if GR_DEBUG
+ // These objects cannot be returned by a search
+ EntryList fExclusiveList;
+#endif
+
+ // our budget, used in purgeAsNeeded()
+ int fMaxCount;
+ size_t fMaxBytes;
+
+ // our current stats, related to our budget
+#if GR_CACHE_STATS
+ int fHighWaterEntryCount;
+ size_t fHighWaterEntryBytes;
+ int fHighWaterClientDetachedCount;
+ size_t fHighWaterClientDetachedBytes;
+#endif
+
+ int fEntryCount;
+ size_t fEntryBytes;
+ int fClientDetachedCount;
+ size_t fClientDetachedBytes;
+
+ // prevents recursive purging
+ bool fPurging;
+
+ PFOverbudgetCB fOverbudgetCB;
+ void* fOverbudgetData;
+
+ void internalPurge(int extraCount, size_t extraBytes);
+
+#if GR_DEBUG
+ static size_t countBytes(const SkTInternalLList<GrResourceEntry>& list);
+#endif
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_DEBUG
+ class GrAutoResourceCacheValidate {
+ public:
+ GrAutoResourceCacheValidate(GrResourceCache* cache) : fCache(cache) {
+ cache->validate();
+ }
+ ~GrAutoResourceCacheValidate() {
+ fCache->validate();
+ }
+ private:
+ GrResourceCache* fCache;
+ };
+#else
+ class GrAutoResourceCacheValidate {
+ public:
+ GrAutoResourceCacheValidate(GrResourceCache*) {}
+ };
+#endif
+
+#endif
diff --git a/gpu/GrSWMaskHelper.cpp b/gpu/GrSWMaskHelper.cpp
new file mode 100644
index 00000000..f6e6c312
--- /dev/null
+++ b/gpu/GrSWMaskHelper.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "GrSWMaskHelper.h"
+#include "GrDrawState.h"
+#include "GrGpu.h"
+
+#include "SkStrokeRec.h"
+
+// TODO: try to remove this #include
+#include "GrContext.h"
+
+namespace {
+/*
+ * Convert a boolean operation into a transfer mode code
+ */
+SkXfermode::Mode op_to_mode(SkRegion::Op op) {
+
+ static const SkXfermode::Mode modeMap[] = {
+ SkXfermode::kDstOut_Mode, // kDifference_Op
+ SkXfermode::kModulate_Mode, // kIntersect_Op
+ SkXfermode::kSrcOver_Mode, // kUnion_Op
+ SkXfermode::kXor_Mode, // kXOR_Op
+ SkXfermode::kClear_Mode, // kReverseDifference_Op
+ SkXfermode::kSrc_Mode, // kReplace_Op
+ };
+
+ return modeMap[op];
+}
+
+}
+
+/**
+ * Draw a single rect element of the clip stack into the accumulation bitmap
+ */
+void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op,
+ bool antiAlias, uint8_t alpha) {
+ SkPaint paint;
+
+ SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
+
+ paint.setXfermode(mode);
+ paint.setAntiAlias(antiAlias);
+ paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
+
+ fDraw.drawRect(rect, paint);
+
+ SkSafeUnref(mode);
+}
+
+/**
+ * Draw a single path element of the clip stack into the accumulation bitmap
+ */
+void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
+ bool antiAlias, uint8_t alpha) {
+
+ SkPaint paint;
+ if (stroke.isHairlineStyle()) {
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SK_Scalar1);
+ } else {
+ 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(path, paint);
+
+ SkSafeUnref(mode);
+}
+
+bool GrSWMaskHelper::init(const SkIRect& resultBounds,
+ const SkMatrix* matrix) {
+ if (NULL != matrix) {
+ fMatrix = *matrix;
+ } else {
+ fMatrix.setIdentity();
+ }
+
+ // Now translate so the bound's UL corner is at the origin
+ fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1,
+ -resultBounds.fTop * SK_Scalar1);
+ SkIRect bounds = SkIRect::MakeWH(resultBounds.width(),
+ resultBounds.height());
+
+ fBM.setConfig(SkBitmap::kA8_Config, bounds.fRight, bounds.fBottom);
+ if (!fBM.allocPixels()) {
+ return false;
+ }
+ sk_bzero(fBM.getPixels(), fBM.getSafeSize());
+
+ sk_bzero(&fDraw, sizeof(fDraw));
+ fRasterClip.setRect(bounds);
+ fDraw.fRC = &fRasterClip;
+ fDraw.fClip = &fRasterClip.bwRgn();
+ fDraw.fMatrix = &fMatrix;
+ fDraw.fBitmap = &fBM;
+ return true;
+}
+
+/**
+ * Get a texture (from the texture cache) of the correct size & format.
+ * Return true on success; false on failure.
+ */
+bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* texture) {
+ GrTextureDesc desc;
+ desc.fWidth = fBM.width();
+ desc.fHeight = fBM.height();
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+
+ texture->set(fContext, desc);
+ return NULL != texture->texture();
+}
+
+/**
+ * Move the result of the software mask generation back to the gpu
+ */
+void GrSWMaskHelper::toTexture(GrTexture *texture) {
+ SkAutoLockPixels alp(fBM);
+
+ texture->writePixels(0, 0, fBM.width(), fBM.height(),
+ kAlpha_8_GrPixelConfig,
+ fBM.getPixels(), fBM.rowBytes());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Software rasterizes path to A8 mask (possibly using the context's matrix)
+ * and uploads the result to a scratch texture. Returns the resulting
+ * texture on success; NULL on failure.
+ */
+GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
+ const SkPath& path,
+ const SkStrokeRec& stroke,
+ const SkIRect& resultBounds,
+ bool antiAlias,
+ SkMatrix* matrix) {
+ GrAutoScratchTexture ast;
+
+ GrSWMaskHelper helper(context);
+
+ if (!helper.init(resultBounds, matrix)) {
+ return NULL;
+ }
+
+ helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
+
+ if (!helper.getTexture(&ast)) {
+ return NULL;
+ }
+
+ helper.toTexture(ast.texture());
+
+ return ast.detach();
+}
+
+void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
+ GrDrawTarget* target,
+ const SkIRect& rect) {
+ GrDrawState* drawState = target->drawState();
+
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(drawState)) {
+ return;
+ }
+ GrDrawState::AutoRestoreEffects are(drawState);
+
+ SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft,
+ SK_Scalar1 * rect.fTop,
+ SK_Scalar1 * rect.fRight,
+ SK_Scalar1 * rect.fBottom);
+
+ // We want to use device coords to compute the texture coordinates. We set our matrix to be
+ // equal to the view matrix followed by a translation so that the top-left of the device bounds
+ // maps to 0,0, and then a scaling matrix to normalized coords. We apply this matrix to the
+ // vertex positions rather than local coords.
+ SkMatrix maskMatrix;
+ maskMatrix.setIDiv(texture->width(), texture->height());
+ maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
+ maskMatrix.preConcat(drawState->getViewMatrix());
+
+ drawState->addCoverageEffect(
+ GrSimpleTextureEffect::Create(texture,
+ maskMatrix,
+ GrTextureParams::kNone_FilterMode,
+ GrEffect::kPosition_CoordsType))->unref();
+
+ target->drawSimpleRect(dstRect);
+}
diff --git a/gpu/GrSWMaskHelper.h b/gpu/GrSWMaskHelper.h
new file mode 100644
index 00000000..fab5eeb4
--- /dev/null
+++ b/gpu/GrSWMaskHelper.h
@@ -0,0 +1,107 @@
+/*
+ * 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 GrSWMaskHelper_DEFINED
+#define GrSWMaskHelper_DEFINED
+
+#include "GrColor.h"
+#include "SkMatrix.h"
+#include "GrNoncopyable.h"
+#include "SkBitmap.h"
+#include "SkDraw.h"
+#include "SkRasterClip.h"
+#include "SkRegion.h"
+#include "GrDrawState.h"
+
+class GrAutoScratchTexture;
+class GrContext;
+class GrTexture;
+class SkPath;
+class SkStrokeRec;
+class GrDrawTarget;
+
+/**
+ * The GrSWMaskHelper helps generate clip masks using the software rendering
+ * path. It is intended to be used as:
+ *
+ * GrSWMaskHelper helper(context);
+ * helper.init(...);
+ *
+ * draw one or more paths/rects specifying the required boolean ops
+ *
+ * toTexture(); // to get it from the internal bitmap to the GPU
+ *
+ * The result of this process will be the final mask (on the GPU) in the
+ * upper left hand corner of the texture.
+ */
+class GrSWMaskHelper : public GrNoncopyable {
+public:
+ GrSWMaskHelper(GrContext* context)
+ : fContext(context) {
+ }
+
+ // set up the internal state in preparation for draws. Since many masks
+ // 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 SkIRect& resultBounds, const SkMatrix* matrix);
+
+ // Draw a single rect into the accumulation bitmap using the specified op
+ void draw(const SkRect& 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, 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)
+ bool getTexture(GrAutoScratchTexture* texture);
+
+ // Move the mask generation results from the internal bitmap to the gpu.
+ void toTexture(GrTexture* texture);
+
+ // Reset the internal bitmap
+ void clear(uint8_t alpha) {
+ fBM.eraseColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
+ }
+
+ // Canonical usage utility that draws a single path and uploads it
+ // to the GPU. The result is returned in "result".
+ static GrTexture* DrawPathMaskToTexture(GrContext* context,
+ const SkPath& path,
+ const SkStrokeRec& stroke,
+ const SkIRect& resultBounds,
+ bool antiAlias,
+ 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
+ // GrSoftwarePathRenderer uses it to fulfill a drawPath call.
+ // It draws with "texture" as a path mask into "target" using "rect" as
+ // geometry and the current drawState. The current drawState is altered to
+ // accommodate the mask.
+ // Note that this method assumes that the GrPaint::kTotalStages slot in
+ // the draw state can be used to hold the mask texture stage.
+ // This method is really only intended to be used with the
+ // output of DrawPathMaskToTexture.
+ static void DrawToTargetWithPathMask(GrTexture* texture,
+ GrDrawTarget* target,
+ const SkIRect& rect);
+
+protected:
+private:
+ GrContext* fContext;
+ SkMatrix fMatrix;
+ SkBitmap fBM;
+ SkDraw fDraw;
+ SkRasterClip fRasterClip;
+
+ typedef GrNoncopyable INHERITED;
+};
+
+#endif // GrSWMaskHelper_DEFINED
diff --git a/gpu/GrSoftwarePathRenderer.cpp b/gpu/GrSoftwarePathRenderer.cpp
new file mode 100644
index 00000000..d0936d6b
--- /dev/null
+++ b/gpu/GrSoftwarePathRenderer.cpp
@@ -0,0 +1,153 @@
+
+/*
+ * 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 "GrSoftwarePathRenderer.h"
+#include "GrContext.h"
+#include "GrSWMaskHelper.h"
+
+////////////////////////////////////////////////////////////////////////////////
+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
+ // this would mean that GrDefaultPathRenderer would never be called
+ // (since it appears after the SW renderer in the path renderer
+ // chain). Some testing would need to be done r.e. performance
+ // and consistency of the resulting images before removing
+ // the "!antiAlias" clause from the above test
+ return false;
+ }
+
+ return true;
+}
+
+GrPathRenderer::StencilSupport GrSoftwarePathRenderer::onGetStencilSupport(
+ const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const {
+ return GrPathRenderer::kNoSupport_StencilSupport;
+}
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+// gets device coord bounds of path (not considering the fill) and clip. The
+// path bounds will be a subset of the clip bounds. returns false if
+// path bounds would be empty.
+bool get_path_and_clip_bounds(const GrDrawTarget* target,
+ const SkPath& path,
+ const SkMatrix& matrix,
+ SkIRect* devPathBounds,
+ SkIRect* devClipBounds) {
+ // compute bounds as intersection of rt size, clip, and path
+ const GrRenderTarget* rt = target->getDrawState().getRenderTarget();
+ if (NULL == rt) {
+ return false;
+ }
+ *devPathBounds = SkIRect::MakeWH(rt->width(), rt->height());
+
+ target->getClip()->getConservativeBounds(rt, devClipBounds);
+
+ // TODO: getConservativeBounds already intersects with the
+ // render target's bounding box. Remove this next line
+ if (!devPathBounds->intersect(*devClipBounds)) {
+ return false;
+ }
+
+ if (!path.getBounds().isEmpty()) {
+ SkRect pathSBounds;
+ matrix.mapRect(&pathSBounds, path.getBounds());
+ SkIRect pathIBounds;
+ pathSBounds.roundOut(&pathIBounds);
+ if (!devPathBounds->intersect(pathIBounds)) {
+ // set the correct path bounds, as this would be used later.
+ *devPathBounds = pathIBounds;
+ return false;
+ }
+ } else {
+ *devPathBounds = SkIRect::EmptyIRect();
+ return false;
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void draw_around_inv_path(GrDrawTarget* target,
+ const SkIRect& devClipBounds,
+ const SkIRect& devPathBounds) {
+ GrDrawState::AutoViewMatrixRestore avmr;
+ if (!avmr.setIdentity(target->drawState())) {
+ return;
+ }
+ SkRect rect;
+ if (devClipBounds.fTop < devPathBounds.fTop) {
+ rect.iset(devClipBounds.fLeft, devClipBounds.fTop,
+ devClipBounds.fRight, devPathBounds.fTop);
+ target->drawSimpleRect(rect, NULL);
+ }
+ if (devClipBounds.fLeft < devPathBounds.fLeft) {
+ rect.iset(devClipBounds.fLeft, devPathBounds.fTop,
+ devPathBounds.fLeft, devPathBounds.fBottom);
+ target->drawSimpleRect(rect, NULL);
+ }
+ if (devClipBounds.fRight > devPathBounds.fRight) {
+ rect.iset(devPathBounds.fRight, devPathBounds.fTop,
+ devClipBounds.fRight, devPathBounds.fBottom);
+ target->drawSimpleRect(rect, NULL);
+ }
+ if (devClipBounds.fBottom > devPathBounds.fBottom) {
+ rect.iset(devClipBounds.fLeft, devPathBounds.fBottom,
+ devClipBounds.fRight, devClipBounds.fBottom);
+ target->drawSimpleRect(rect, NULL);
+ }
+}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// return true on success; false on failure
+bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) {
+
+ if (NULL == fContext) {
+ return false;
+ }
+
+ GrDrawState* drawState = target->drawState();
+
+ SkMatrix vm = drawState->getViewMatrix();
+
+ SkIRect devPathBounds, devClipBounds;
+ if (!get_path_and_clip_bounds(target, path, vm,
+ &devPathBounds, &devClipBounds)) {
+ if (path.isInverseFillType()) {
+ draw_around_inv_path(target, devClipBounds, devPathBounds);
+ }
+ return true;
+ }
+
+ SkAutoTUnref<GrTexture> texture(
+ GrSWMaskHelper::DrawPathMaskToTexture(fContext, path, stroke,
+ devPathBounds,
+ antiAlias, &vm));
+ if (NULL == texture) {
+ return false;
+ }
+
+ GrSWMaskHelper::DrawToTargetWithPathMask(texture, target, devPathBounds);
+
+ if (path.isInverseFillType()) {
+ draw_around_inv_path(target, devClipBounds, devPathBounds);
+ }
+
+ return true;
+}
diff --git a/gpu/GrSoftwarePathRenderer.h b/gpu/GrSoftwarePathRenderer.h
new file mode 100644
index 00000000..f8c5620f
--- /dev/null
+++ b/gpu/GrSoftwarePathRenderer.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 GrSoftwarePathRenderer_DEFINED
+#define GrSoftwarePathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+
+class GrContext;
+class GrAutoScratchTexture;
+
+/**
+ * This class uses the software side to render a path to an SkBitmap and
+ * then uploads the result to the gpu
+ */
+class GrSoftwarePathRenderer : public GrPathRenderer {
+public:
+ GrSoftwarePathRenderer(GrContext* context)
+ : fContext(context) {
+ }
+
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
+ bool antiAlias) const SK_OVERRIDE;
+protected:
+ 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:
+ GrContext* fContext;
+
+ typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/gpu/GrStencil.cpp b/gpu/GrStencil.cpp
new file mode 100644
index 00000000..76772608
--- /dev/null
+++ b/gpu/GrStencil.cpp
@@ -0,0 +1,395 @@
+
+/*
+ * 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 "GrStencil.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Stencil Rules for Merging user stencil space into clip
+
+// We can't include the clip bit in the ref or mask values because the division
+// between user and clip bits in the stencil depends on the number of stencil
+// bits in the runtime. Comments below indicate what the code should do to
+// incorporate the clip bit into these settings.
+
+///////
+// Replace
+
+// set the ref to be the clip bit, but mask it out for the test
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipReplace,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff, // unset clip bit
+ 0x0000, // set clip bit
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipReplace,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff, // unset clip bit
+ 0x0000, // set clip bit
+ 0xffff);
+
+///////
+// Intersect
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipIsect,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipIsect,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+///////
+// Difference
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipDiff,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipDiff,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+///////
+// Union
+
+// first pass makes all the passing cases >= just clip bit set.
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipUnionPass0,
+ kReplace_StencilOp,
+ kKeep_StencilOp,
+ kLEqual_StencilFunc,
+ 0xffff,
+ 0x0001, // set clip bit
+ 0xffff);
+
+// second pass allows anything greater than just clip bit set to pass
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipUnionPass1,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kLEqual_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+// first pass finds zeros in the user bits and if found sets
+// the clip bit to 1
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipUnionPass0,
+ kReplace_StencilOp,
+ kKeep_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0x0000 // set clip bit
+);
+
+// second pass zeros the user bits
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipUnionPass1,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff // unset clip bit
+);
+
+///////
+// Xor
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipXorPass0,
+ kInvert_StencilOp,
+ kKeep_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff, // unset clip bit
+ 0x0000,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipXorPass1,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kGreater_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipXorPass0,
+ kInvert_StencilOp,
+ kKeep_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff, // unset clip bit
+ 0x0000,
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipXorPass1,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0xffff);
+
+///////
+// Reverse Diff
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipRDiffPass0,
+ kInvert_StencilOp,
+ kZero_StencilOp,
+ kLess_StencilFunc,
+ 0xffff, // unset clip bit
+ 0x0000, // set clip bit
+ 0xffff);
+
+GR_STATIC_CONST_SAME_STENCIL(gUserToClipRDiffPass1,
+ kReplace_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0x0000, // set clip bit
+ 0x0000, // set clip bit
+ 0xffff);
+
+// We are looking for stencil values that are all zero. The first pass sets the
+// clip bit if the stencil is all zeros. The second pass clears the user bits.
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipRDiffPass0,
+ kInvert_StencilOp,
+ kZero_StencilOp,
+ kEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0x0000 // set clip bit
+);
+
+GR_STATIC_CONST_SAME_STENCIL(gInvUserToClipRDiffPass1,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff // unset clip bit
+);
+
+///////
+// Direct to Stencil
+
+// We can render a clip element directly without first writing to the client
+// portion of the clip when the fill is not inverse and the set operation will
+// only modify the in/out status of samples covered by the clip element.
+
+// this one only works if used right after stencil clip was cleared.
+// Our clip mask creation code doesn't allow midstream replace ops.
+GR_STATIC_CONST_SAME_STENCIL(gReplaceClip,
+ kReplace_StencilOp,
+ kReplace_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0x0000 // set clipBit
+);
+
+GR_STATIC_CONST_SAME_STENCIL(gUnionClip,
+ kReplace_StencilOp,
+ kReplace_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000, // set clip bit
+ 0x0000 // set clip bit
+);
+
+GR_STATIC_CONST_SAME_STENCIL(gXorClip,
+ kInvert_StencilOp,
+ kInvert_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0x0000 // set clip bit
+);
+
+GR_STATIC_CONST_SAME_STENCIL(gDiffClip,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kAlways_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0x0000 // set clip bit
+);
+
+bool GrStencilSettings::GetClipPasses(
+ SkRegion::Op op,
+ bool canBeDirect,
+ unsigned int stencilClipMask,
+ bool invertedFill,
+ int* numPasses,
+ GrStencilSettings settings[kMaxStencilClipPasses]) {
+ if (canBeDirect && !invertedFill) {
+ *numPasses = 0;
+ switch (op) {
+ case SkRegion::kReplace_Op:
+ *numPasses = 1;
+ settings[0] = gReplaceClip;
+ break;
+ case SkRegion::kUnion_Op:
+ *numPasses = 1;
+ settings[0] = gUnionClip;
+ break;
+ case SkRegion::kXOR_Op:
+ *numPasses = 1;
+ settings[0] = gXorClip;
+ break;
+ case SkRegion::kDifference_Op:
+ *numPasses = 1;
+ settings[0] = gDiffClip;
+ break;
+ default: // suppress warning
+ break;
+ }
+ if (1 == *numPasses) {
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fWriteMasks[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+ settings[0].fWriteMasks[kBack_Face] =
+ settings[0].fWriteMasks[kFront_Face];
+ return true;
+ }
+ }
+ switch (op) {
+ // if we make the path renderer go to stencil we always give it a
+ // non-inverted fill and we use the stencil rules on the client->clipbit
+ // pass to select either the zeros or nonzeros.
+ case SkRegion::kReplace_Op:
+ *numPasses= 1;
+ settings[0] = invertedFill ? gInvUserToClipReplace :
+ gUserToClipReplace;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+ break;
+ case SkRegion::kIntersect_Op:
+ *numPasses = 1;
+ settings[0] = invertedFill ? gInvUserToClipIsect : gUserToClipIsect;
+ settings[0].fFuncRefs[kFront_Face] = stencilClipMask;
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+ break;
+ case SkRegion::kUnion_Op:
+ *numPasses = 2;
+ if (invertedFill) {
+ settings[0] = gInvUserToClipUnionPass0;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+ settings[0].fWriteMasks[kFront_Face] |= stencilClipMask;
+ settings[0].fWriteMasks[kBack_Face] =
+ settings[0].fWriteMasks[kFront_Face];
+
+ settings[1] = gInvUserToClipUnionPass1;
+ settings[1].fWriteMasks[kFront_Face] &= ~stencilClipMask;
+ settings[1].fWriteMasks[kBack_Face] &=
+ settings[1].fWriteMasks[kFront_Face];
+
+ } else {
+ settings[0] = gUserToClipUnionPass0;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+
+ settings[1] = gUserToClipUnionPass1;
+ settings[1].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[1].fFuncRefs[kBack_Face] =
+ settings[1].fFuncRefs[kFront_Face];
+ }
+ break;
+ case SkRegion::kXOR_Op:
+ *numPasses = 2;
+ if (invertedFill) {
+ settings[0] = gInvUserToClipXorPass0;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+
+ settings[1] = gInvUserToClipXorPass1;
+ settings[1].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[1].fFuncRefs[kBack_Face] =
+ settings[1].fFuncRefs[kFront_Face];
+ } else {
+ settings[0] = gUserToClipXorPass0;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+
+ settings[1] = gUserToClipXorPass1;
+ settings[1].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[1].fFuncRefs[kBack_Face] =
+ settings[1].fFuncRefs[kFront_Face];
+ }
+ break;
+ case SkRegion::kDifference_Op:
+ *numPasses = 1;
+ settings[0] = invertedFill ? gInvUserToClipDiff : gUserToClipDiff;
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+ break;
+ case SkRegion::kReverseDifference_Op:
+ if (invertedFill) {
+ *numPasses = 2;
+ settings[0] = gInvUserToClipRDiffPass0;
+ settings[0].fWriteMasks[kFront_Face] |= stencilClipMask;
+ settings[0].fWriteMasks[kBack_Face] =
+ settings[0].fWriteMasks[kFront_Face];
+ settings[1] = gInvUserToClipRDiffPass1;
+ settings[1].fWriteMasks[kFront_Face] &= ~stencilClipMask;
+ settings[1].fWriteMasks[kBack_Face] =
+ settings[1].fWriteMasks[kFront_Face];
+ } else {
+ *numPasses = 2;
+ settings[0] = gUserToClipRDiffPass0;
+ settings[0].fFuncMasks[kFront_Face] &= ~stencilClipMask;
+ settings[0].fFuncMasks[kBack_Face] =
+ settings[0].fFuncMasks[kFront_Face];
+ settings[0].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[0].fFuncRefs[kBack_Face] =
+ settings[0].fFuncRefs[kFront_Face];
+
+ settings[1] = gUserToClipRDiffPass1;
+ settings[1].fFuncMasks[kFront_Face] |= stencilClipMask;
+ settings[1].fFuncRefs[kFront_Face] |= stencilClipMask;
+ settings[1].fFuncMasks[kBack_Face] =
+ settings[1].fFuncMasks[kFront_Face];
+ settings[1].fFuncRefs[kBack_Face] =
+ settings[1].fFuncRefs[kFront_Face];
+ }
+ break;
+ default:
+ GrCrash("Unknown set op");
+ }
+ return false;
+}
diff --git a/gpu/GrStencil.h b/gpu/GrStencil.h
new file mode 100644
index 00000000..1af98e6a
--- /dev/null
+++ b/gpu/GrStencil.h
@@ -0,0 +1,395 @@
+
+/*
+ * 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 GrStencil_DEFINED
+#define GrStencil_DEFINED
+
+#include "GrTypes.h"
+#include "SkRegion.h"
+
+/**
+ * Gr uses the stencil buffer to implement complex clipping inside the
+ * GrDrawTarget class. The GrDrawTarget makes a subset of the stencil buffer
+ * bits available for other uses by external code (clients). Client code can
+ * modify these bits. GrDrawTarget will ignore ref, mask, and writemask bits
+ * provided by clients that overlap the bits used to implement clipping.
+ *
+ * When code outside the GrDrawTarget class uses the stencil buffer the contract
+ * is as follows:
+ *
+ * > Normal stencil funcs allow the client to pass / fail regardless of the
+ * reserved clip bits.
+ * > Additional functions allow a test against the clip along with a limited
+ * set of tests against the client bits.
+ * > Client can assume all client bits are zero initially.
+ * > Client must ensure that after all its passes are finished it has only
+ * written to the color buffer in the region inside the clip. Furthermore, it
+ * must zero all client bits that were modifed (both inside and outside the
+ * clip).
+ */
+
+/**
+ * Determines which pixels pass / fail the stencil test.
+ * Stencil test passes if (ref & mask) FUNC (stencil & mask) is true
+ */
+enum GrStencilFunc {
+ kAlways_StencilFunc = 0,
+ kNever_StencilFunc,
+ kGreater_StencilFunc,
+ kGEqual_StencilFunc,
+ kLess_StencilFunc,
+ kLEqual_StencilFunc,
+ kEqual_StencilFunc,
+ kNotEqual_StencilFunc,
+
+ // Gr stores the current clip in the
+ // stencil buffer in the high bits that
+ // are not directly accessible modifiable
+ // via the GrDrawTarget interface. The below
+ // stencil funcs test against the current
+ // clip in addition to the GrDrawTarget
+ // client's stencil bits.
+
+ // pass if inside the clip
+ kAlwaysIfInClip_StencilFunc,
+ kEqualIfInClip_StencilFunc,
+ kLessIfInClip_StencilFunc,
+ kLEqualIfInClip_StencilFunc,
+ kNonZeroIfInClip_StencilFunc, // this one forces the ref to be 0
+
+ // counts
+ kStencilFuncCount,
+ kClipStencilFuncCount = kNonZeroIfInClip_StencilFunc -
+ kAlwaysIfInClip_StencilFunc + 1,
+ kBasicStencilFuncCount = kStencilFuncCount - kClipStencilFuncCount
+};
+
+/**
+ * Operations to perform based on whether stencil test passed failed.
+ */
+enum GrStencilOp {
+ kKeep_StencilOp = 0, // preserve existing stencil value
+ kReplace_StencilOp, // replace with reference value from stencl test
+ kIncWrap_StencilOp, // increment and wrap at max
+ kIncClamp_StencilOp, // increment and clamp at max
+ kDecWrap_StencilOp, // decrement and wrap at 0
+ kDecClamp_StencilOp, // decrement and clamp at 0
+ kZero_StencilOp, // zero stencil bits
+ kInvert_StencilOp, // invert stencil bits
+
+ kStencilOpCount
+};
+
+enum GrStencilFlags {
+ kIsDisabled_StencilFlag = 0x1,
+ kNotDisabled_StencilFlag = 0x2,
+ kDoesWrite_StencilFlag = 0x4,
+ kDoesNotWrite_StencilFlag = 0x8,
+};
+
+/**
+ * GrStencilState needs to be a class with accessors and setters so that it
+ * can maintain flags related to its current state. However, we also want to
+ * be able to declare pre-made stencil settings at compile time (without
+ * inserting static initializer code). So all the data members are in this
+ * struct. A macro defined after the class can be used to jam an instance of
+ * this struct that is created from an initializer list into a
+ * GrStencilSettings. (We hang our heads in shame.)
+ */
+struct GrStencilSettingsStruct {
+ uint8_t fPassOps[2]; // op to perform when faces pass (GrStencilOp)
+ uint8_t fFailOps[2]; // op to perform when faces fail (GrStencilOp)
+ uint8_t fFuncs[2]; // test function for faces (GrStencilFunc)
+ uint8_t fPad0;
+ uint8_t fPad1;
+ uint16_t fFuncMasks[2]; // mask for face tests
+ uint16_t fFuncRefs[2]; // reference values for face tests
+ uint16_t fWriteMasks[2]; // stencil write masks
+ mutable uint32_t fFlags;
+};
+// We rely on this being packed and aligned (memcmp'ed and memcpy'ed)
+GR_STATIC_ASSERT(sizeof(GrStencilSettingsStruct) % 4 == 0);
+GR_STATIC_ASSERT(sizeof(GrStencilSettingsStruct) ==
+ 4*sizeof(uint8_t) + // ops
+ 2*sizeof(uint8_t) + // funcs
+ 2*sizeof(uint8_t) + // pads
+ 2*sizeof(uint16_t) + // func masks
+ 2*sizeof(uint16_t) + // ref values
+ 2*sizeof(uint16_t) + // write masks
+ sizeof(uint32_t)); // flags
+
+// This macro is used to compute the GrStencilSettingsStructs flags
+// associated to disabling. It is used both to define constant structure
+// initializers and inside GrStencilSettings::isDisabled()
+//
+#define GR_STENCIL_SETTINGS_IS_DISABLED( \
+ FRONT_PASS_OP, BACK_PASS_OP, \
+ FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC) \
+ ((FRONT_PASS_OP) == kKeep_StencilOp && \
+ (BACK_PASS_OP) == kKeep_StencilOp && \
+ (FRONT_FAIL_OP) == kKeep_StencilOp && \
+ (BACK_FAIL_OP) == kKeep_StencilOp && \
+ (FRONT_FUNC) == kAlways_StencilFunc && \
+ (BACK_FUNC) == kAlways_StencilFunc)
+
+#define GR_STENCIL_SETTINGS_DOES_WRITE( \
+ FRONT_PASS_OP, BACK_PASS_OP, \
+ FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC) \
+ (!(((FRONT_FUNC) == kNever_StencilFunc || \
+ (FRONT_PASS_OP) == kKeep_StencilOp) && \
+ ((BACK_FUNC) == kNever_StencilFunc || \
+ (BACK_PASS_OP) == kKeep_StencilOp) && \
+ ((FRONT_FUNC) == kAlways_StencilFunc || \
+ (FRONT_FAIL_OP) == kKeep_StencilOp) && \
+ ((BACK_FUNC) == kAlways_StencilFunc || \
+ (BACK_FAIL_OP) == kKeep_StencilOp)))
+
+#define GR_STENCIL_SETTINGS_DEFAULT_FLAGS( \
+ FRONT_PASS_OP, BACK_PASS_OP, \
+ FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC) \
+ ((GR_STENCIL_SETTINGS_IS_DISABLED(FRONT_PASS_OP,BACK_PASS_OP, \
+ FRONT_FAIL_OP,BACK_FAIL_OP,FRONT_FUNC,BACK_FUNC) ? \
+ kIsDisabled_StencilFlag : kNotDisabled_StencilFlag) | \
+ (GR_STENCIL_SETTINGS_DOES_WRITE(FRONT_PASS_OP,BACK_PASS_OP, \
+ FRONT_FAIL_OP,BACK_FAIL_OP,FRONT_FUNC,BACK_FUNC) ? \
+ kDoesWrite_StencilFlag : kDoesNotWrite_StencilFlag))
+
+/**
+ * Class representing stencil state.
+ */
+class GrStencilSettings : private GrStencilSettingsStruct {
+
+public:
+ enum Face {
+ kFront_Face = 0,
+ kBack_Face = 1,
+ };
+
+ GrStencilSettings() {
+ fPad0 = fPad1 = 0;
+ this->setDisabled();
+ }
+
+ GrStencilOp passOp(Face f) const { return static_cast<GrStencilOp>(fPassOps[f]); }
+ GrStencilOp failOp(Face f) const { return static_cast<GrStencilOp>(fFailOps[f]); }
+ GrStencilFunc func(Face f) const { return static_cast<GrStencilFunc>(fFuncs[f]); }
+ uint16_t funcMask(Face f) const { return fFuncMasks[f]; }
+ uint16_t funcRef(Face f) const { return fFuncRefs[f]; }
+ uint16_t writeMask(Face f) const { return fWriteMasks[f]; }
+
+ void setPassOp(Face f, GrStencilOp op) { fPassOps[f] = op; fFlags = 0;}
+ void setFailOp(Face f, GrStencilOp op) { fFailOps[f] = op; fFlags = 0;}
+ void setFunc(Face f, GrStencilFunc func) { fFuncs[f] = func; fFlags = 0;}
+ void setFuncMask(Face f, unsigned short mask) { fFuncMasks[f] = mask; }
+ void setFuncRef(Face f, unsigned short ref) { fFuncRefs[f] = ref; }
+ void setWriteMask(Face f, unsigned short writeMask) { fWriteMasks[f] = writeMask; }
+
+ void copyFrontSettingsToBack() {
+ fPassOps[kBack_Face] = fPassOps[kFront_Face];
+ fFailOps[kBack_Face] = fFailOps[kFront_Face];
+ fFuncs[kBack_Face] = fFuncs[kFront_Face];
+ fFuncMasks[kBack_Face] = fFuncMasks[kFront_Face];
+ fFuncRefs[kBack_Face] = fFuncRefs[kFront_Face];
+ fWriteMasks[kBack_Face] = fWriteMasks[kFront_Face];
+ fFlags = 0;
+ }
+
+ void setSame(GrStencilOp passOp,
+ GrStencilOp failOp,
+ GrStencilFunc func,
+ unsigned short funcMask,
+ unsigned short funcRef,
+ unsigned short writeMask) {
+ fPassOps[kFront_Face] = fPassOps[kBack_Face] = passOp;
+ fFailOps[kFront_Face] = fFailOps[kBack_Face] = failOp;
+ fFuncs[kFront_Face] = fFuncs[kBack_Face] = func;
+ fFuncMasks[kFront_Face] = fFuncMasks[kBack_Face] = funcMask;
+ fFuncRefs[kFront_Face] = fFuncRefs[kBack_Face] = funcRef;
+ fWriteMasks[kFront_Face] = fWriteMasks[kBack_Face] = writeMask;
+ fFlags = 0;
+ }
+
+ void setDisabled() {
+ memset(this, 0, sizeof(*this));
+ GR_STATIC_ASSERT(0 == kKeep_StencilOp);
+ GR_STATIC_ASSERT(0 == kAlways_StencilFunc);
+ fFlags = kIsDisabled_StencilFlag | kDoesNotWrite_StencilFlag;
+ }
+
+ bool isTwoSided() const {
+ return fPassOps[kFront_Face] != fPassOps[kBack_Face] ||
+ fFailOps[kFront_Face] != fFailOps[kBack_Face] ||
+ fFuncs[kFront_Face] != fFuncs[kBack_Face] ||
+ fFuncMasks[kFront_Face] != fFuncMasks[kBack_Face] ||
+ fFuncRefs[kFront_Face] != fFuncRefs[kBack_Face] ||
+ fWriteMasks[kFront_Face] != fWriteMasks[kBack_Face];
+ }
+
+ bool usesWrapOp() const {
+ return kIncWrap_StencilOp == fPassOps[kFront_Face] ||
+ kDecWrap_StencilOp == fPassOps[kFront_Face] ||
+ kIncWrap_StencilOp == fPassOps[kBack_Face] ||
+ kDecWrap_StencilOp == fPassOps[kBack_Face] ||
+ kIncWrap_StencilOp == fFailOps[kFront_Face] ||
+ kDecWrap_StencilOp == fFailOps[kFront_Face] ||
+ kIncWrap_StencilOp == fFailOps[kBack_Face] ||
+ kDecWrap_StencilOp == fFailOps[kBack_Face];
+ }
+
+ bool isDisabled() const {
+ if (fFlags & kIsDisabled_StencilFlag) {
+ return true;
+ }
+ if (fFlags & kNotDisabled_StencilFlag) {
+ return false;
+ }
+ bool disabled = GR_STENCIL_SETTINGS_IS_DISABLED(
+ fPassOps[kFront_Face], fPassOps[kBack_Face],
+ fFailOps[kFront_Face], fFailOps[kBack_Face],
+ fFuncs[kFront_Face], fFuncs[kBack_Face]);
+ fFlags |= disabled ? kIsDisabled_StencilFlag : kNotDisabled_StencilFlag;
+ return disabled;
+ }
+
+ bool doesWrite() const {
+ if (fFlags & kDoesWrite_StencilFlag) {
+ return true;
+ }
+ if (fFlags & kDoesNotWrite_StencilFlag) {
+ return false;
+ }
+ bool writes = GR_STENCIL_SETTINGS_DOES_WRITE(
+ fPassOps[kFront_Face], fPassOps[kBack_Face],
+ fFailOps[kFront_Face], fFailOps[kBack_Face],
+ fFuncs[kFront_Face], fFuncs[kBack_Face]);
+ fFlags |= writes ? kDoesWrite_StencilFlag : kDoesNotWrite_StencilFlag;
+ return writes;
+ }
+
+ void invalidate() {
+ // write an illegal value to the first member
+ fPassOps[0] = (GrStencilOp)(uint8_t)-1;
+ fFlags = 0;
+ }
+
+ bool operator == (const GrStencilSettings& s) const {
+ static const size_t gCompareSize = sizeof(GrStencilSettings) -
+ sizeof(fFlags);
+ GrAssert((const char*)&fFlags + sizeof(fFlags) ==
+ (const char*)this + sizeof(GrStencilSettings));
+ if (this->isDisabled() & s.isDisabled()) { // using & not &&
+ return true;
+ }
+ return 0 == memcmp(this, &s, gCompareSize);
+ }
+
+ bool operator != (const GrStencilSettings& s) const {
+ return !(*this == s);
+ }
+
+ GrStencilSettings& operator =(const GrStencilSettings& s) {
+ memcpy(this, &s, sizeof(GrStencilSettings));
+ return *this;
+ }
+
+private:
+ friend class GrClipMaskManager;
+
+ enum {
+ kMaxStencilClipPasses = 2 // maximum number of passes to add a clip
+ // element to the stencil buffer.
+ };
+
+ /**
+ * Given a thing to draw into the stencil clip, a fill type, and a set op
+ * this function determines:
+ * 1. Whether the thing can be draw directly to the stencil clip or
+ * needs to be drawn to the client portion of the stencil first.
+ * 2. How many passes are needed.
+ * 3. What those passes are.
+ * 4. The fill rule that should actually be used to render (will
+ * always be non-inverted).
+ *
+ * @param op the set op to combine this element with the
+ * existing clip
+ * @param stencilClipMask mask with just the stencil bit used for clipping
+ * enabled.
+ * @param invertedFill is this path inverted
+ * @param numPasses out: the number of passes needed to add the
+ * element to the clip.
+ * @param settings out: the stencil settings to use for each pass
+ *
+ * @return true if the clip element's geometry can be drawn directly to the
+ * stencil clip bit. Will only be true if canBeDirect is true.
+ * numPasses will be 1 if return value is true.
+ */
+ static bool GetClipPasses(SkRegion::Op op,
+ bool canBeDirect,
+ unsigned int stencilClipMask,
+ bool invertedFill,
+ int* numPasses,
+ GrStencilSettings settings[kMaxStencilClipPasses]);
+};
+
+GR_STATIC_ASSERT(sizeof(GrStencilSettingsStruct) == sizeof(GrStencilSettings));
+
+#define GR_STATIC_CONST_STENCIL_STRUCT(STRUCT_NAME, \
+ FRONT_PASS_OP, BACK_PASS_OP, \
+ FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC, \
+ FRONT_MASK, BACK_MASK, \
+ FRONT_REF, BACK_REF, \
+ FRONT_WRITE_MASK, BACK_WRITE_MASK) \
+ static const GrStencilSettingsStruct STRUCT_NAME = { \
+ {(FRONT_PASS_OP), (BACK_PASS_OP) }, \
+ {(FRONT_FAIL_OP), (BACK_FAIL_OP) }, \
+ {(FRONT_FUNC), (BACK_FUNC) }, \
+ (0), (0), \
+ {(FRONT_MASK), (BACK_MASK) }, \
+ {(FRONT_REF), (BACK_REF) }, \
+ {(FRONT_WRITE_MASK), (BACK_WRITE_MASK)}, \
+ GR_STENCIL_SETTINGS_DEFAULT_FLAGS( \
+ FRONT_PASS_OP, BACK_PASS_OP, FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC) \
+ };
+
+#define GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(STRUCT_PTR) \
+ reinterpret_cast<const GrStencilSettings*>(STRUCT_PTR)
+
+#define GR_STATIC_CONST_SAME_STENCIL_STRUCT(STRUCT_NAME, \
+ PASS_OP, FAIL_OP, FUNC, MASK, REF, WRITE_MASK) \
+ GR_STATIC_CONST_STENCIL_STRUCT(STRUCT_NAME, (PASS_OP), (PASS_OP), \
+ (FAIL_OP),(FAIL_OP), (FUNC), (FUNC), (MASK), (MASK), (REF), (REF), \
+ (WRITE_MASK),(WRITE_MASK))
+
+#define GR_STATIC_CONST_STENCIL(NAME, \
+ FRONT_PASS_OP, BACK_PASS_OP, \
+ FRONT_FAIL_OP, BACK_FAIL_OP, \
+ FRONT_FUNC, BACK_FUNC, \
+ FRONT_MASK, BACK_MASK, \
+ FRONT_REF, BACK_REF, \
+ FRONT_WRITE_MASK, BACK_WRITE_MASK) \
+ GR_STATIC_CONST_STENCIL_STRUCT(NAME ## _STRUCT, \
+ (FRONT_PASS_OP),(BACK_PASS_OP),(FRONT_FAIL_OP),(BACK_FAIL_OP), \
+ (FRONT_FUNC),(BACK_FUNC),(FRONT_MASK),(BACK_MASK), \
+ (FRONT_REF),(BACK_REF),(FRONT_WRITE_MASK),(BACK_WRITE_MASK)) \
+ static const GrStencilSettings& NAME = \
+ *GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(&(NAME ## _STRUCT));
+
+
+#define GR_STATIC_CONST_SAME_STENCIL(NAME, \
+ PASS_OP, FAIL_OP, FUNC, MASK, REF, WRITE_MASK) \
+ GR_STATIC_CONST_STENCIL(NAME, (PASS_OP), (PASS_OP), (FAIL_OP), \
+ (FAIL_OP), (FUNC), (FUNC), (MASK), (MASK), (REF), (REF), (WRITE_MASK), \
+ (WRITE_MASK))
+
+#endif
diff --git a/gpu/GrStencilAndCoverPathRenderer.cpp b/gpu/GrStencilAndCoverPathRenderer.cpp
new file mode 100644
index 00000000..e8bc5228
--- /dev/null
+++ b/gpu/GrStencilAndCoverPathRenderer.cpp
@@ -0,0 +1,123 @@
+
+/*
+ * 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 "GrStencilAndCoverPathRenderer.h"
+#include "GrContext.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+#include "GrPath.h"
+#include "SkStrokeRec.h"
+
+GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrContext* context) {
+ GrAssert(NULL != context);
+ GrAssert(NULL != context->getGpu());
+ if (context->getGpu()->caps()->pathStencilingSupport()) {
+ return SkNEW_ARGS(GrStencilAndCoverPathRenderer, (context->getGpu()));
+ } else {
+ return NULL;
+ }
+}
+
+GrStencilAndCoverPathRenderer::GrStencilAndCoverPathRenderer(GrGpu* gpu) {
+ GrAssert(gpu->caps()->pathStencilingSupport());
+ fGpu = gpu;
+ gpu->ref();
+}
+
+GrStencilAndCoverPathRenderer::~GrStencilAndCoverPathRenderer() {
+ fGpu->unref();
+}
+
+bool GrStencilAndCoverPathRenderer::canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const {
+ return stroke.isFillStyle() &&
+ !antiAlias && // doesn't do per-path AA, relies on the target having MSAA
+ target->getDrawState().getStencil().isDisabled();
+}
+
+GrPathRenderer::StencilSupport GrStencilAndCoverPathRenderer::onGetStencilSupport(
+ const SkPath&,
+ const SkStrokeRec& ,
+ const GrDrawTarget*) const {
+ return GrPathRenderer::kStencilOnly_StencilSupport;
+}
+
+void GrStencilAndCoverPathRenderer::onStencilPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target) {
+ GrAssert(!path.isInverseFillType());
+ SkAutoTUnref<GrPath> p(fGpu->createPath(path));
+ target->stencilPath(p, stroke, path.getFillType());
+}
+
+bool GrStencilAndCoverPathRenderer::onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) {
+ GrAssert(!antiAlias);
+ GrAssert(!stroke.isHairlineStyle());
+
+ GrDrawState* drawState = target->drawState();
+ GrAssert(drawState->getStencil().isDisabled());
+
+ SkAutoTUnref<GrPath> p(fGpu->createPath(path));
+
+ 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
+ SkRect bounds = p->getBounds();
+ SkScalar bloat = drawState->getViewMatrix().getMaxStretch() * SK_ScalarHalf;
+ GrDrawState::AutoViewMatrixRestore avmr;
+
+ if (nonInvertedFill == path.getFillType()) {
+ GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ kNotEqual_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+ *drawState->stencil() = kStencilPass;
+ } else {
+ GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass,
+ kZero_StencilOp,
+ kZero_StencilOp,
+ // We know our rect will hit pixels outside the clip and the user bits will be 0
+ // outside the clip. So we can't just fill where the user bits are 0. We also need to
+ // check that the clip bit is set.
+ kEqualIfInClip_StencilFunc,
+ 0xffff,
+ 0x0000,
+ 0xffff);
+ SkMatrix vmi;
+ bounds.setLTRB(0, 0,
+ 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);
+ // theoretically could set bloat = 0, instead leave it because of matrix inversion
+ // precision.
+ } else {
+ avmr.setIdentity(drawState);
+ bloat = 0;
+ }
+ *drawState->stencil() = kInvertedStencilPass;
+ }
+ bounds.outset(bloat, bloat);
+ target->drawSimpleRect(bounds, NULL);
+ target->drawState()->stencil()->setDisabled();
+ return true;
+}
diff --git a/gpu/GrStencilAndCoverPathRenderer.h b/gpu/GrStencilAndCoverPathRenderer.h
new file mode 100644
index 00000000..9ebcec98
--- /dev/null
+++ b/gpu/GrStencilAndCoverPathRenderer.h
@@ -0,0 +1,55 @@
+
+/*
+ * 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 GrBuiltInPathRenderer_DEFINED
+#define GrBuiltInPathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+
+class GrContext;
+class GrGpu;
+
+/**
+ * Uses GrGpu::stencilPath followed by a cover rectangle. This subclass doesn't apply AA; it relies
+ * on the target having MSAA if AA is desired.
+ */
+class GrStencilAndCoverPathRenderer : public GrPathRenderer {
+public:
+
+ static GrPathRenderer* Create(GrContext*);
+
+ virtual ~GrStencilAndCoverPathRenderer();
+
+ virtual bool canDrawPath(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*,
+ bool antiAlias) const SK_OVERRIDE;
+
+protected:
+ 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*);
+
+ GrGpu* fGpu;
+
+ typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/gpu/GrStencilBuffer.cpp b/gpu/GrStencilBuffer.cpp
new file mode 100644
index 00000000..865961ac
--- /dev/null
+++ b/gpu/GrStencilBuffer.cpp
@@ -0,0 +1,49 @@
+
+/*
+ * 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 "GrStencilBuffer.h"
+
+#include "GrContext.h"
+#include "GrGpu.h"
+#include "GrResourceCache.h"
+
+SK_DEFINE_INST_COUNT(GrStencilBuffer)
+
+void GrStencilBuffer::transferToCache() {
+ GrAssert(NULL == this->getCacheEntry());
+
+ this->getGpu()->getContext()->addStencilBuffer(this);
+}
+
+namespace {
+// we should never have more than one stencil buffer with same combo of (width,height,samplecount)
+void gen_cache_id(int width, int height, int sampleCnt, GrCacheID* cacheID) {
+ static const GrCacheID::Domain gStencilBufferDomain = GrCacheID::GenerateDomain();
+ GrCacheID::Key key;
+ uint32_t* keyData = key.fData32;
+ keyData[0] = width;
+ keyData[1] = height;
+ keyData[2] = sampleCnt;
+ memset(keyData + 3, 0, sizeof(key) - 3 * sizeof(uint32_t));
+ GR_STATIC_ASSERT(sizeof(key) >= 3 * sizeof(uint32_t));
+ cacheID->reset(gStencilBufferDomain, key);
+}
+}
+
+GrResourceKey GrStencilBuffer::ComputeKey(int width,
+ int height,
+ int sampleCnt) {
+ // All SBs are created internally to attach to RTs so they all use the same domain.
+ static const GrResourceKey::ResourceType gStencilBufferResourceType =
+ GrResourceKey::GenerateResourceType();
+ GrCacheID id;
+ gen_cache_id(width, height, sampleCnt, &id);
+
+ // we don't use any flags for SBs currently.
+ return GrResourceKey(id, gStencilBufferResourceType, 0);
+}
diff --git a/gpu/GrStencilBuffer.h b/gpu/GrStencilBuffer.h
new file mode 100644
index 00000000..3765a4c6
--- /dev/null
+++ b/gpu/GrStencilBuffer.h
@@ -0,0 +1,82 @@
+
+/*
+ * 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 GrStencilBuffer_DEFINED
+#define GrStencilBuffer_DEFINED
+
+#include "GrClipData.h"
+#include "GrResource.h"
+
+class GrRenderTarget;
+class GrResourceEntry;
+class GrResourceKey;
+
+class GrStencilBuffer : public GrResource {
+public:
+ SK_DECLARE_INST_COUNT(GrStencilBuffer);
+
+ virtual ~GrStencilBuffer() {
+ // TODO: allow SB to be purged and detach itself from rts
+ }
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+ int bits() const { return fBits; }
+ int numSamples() const { return fSampleCnt; }
+
+ // called to note the last clip drawn to this buffer.
+ 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(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.
+ void transferToCache();
+
+ static GrResourceKey ComputeKey(int width, int height, int sampleCnt);
+
+protected:
+ GrStencilBuffer(GrGpu* gpu, bool isWrapped, int width, int height, int bits, int sampleCnt)
+ : GrResource(gpu, isWrapped)
+ , fWidth(width)
+ , fHeight(height)
+ , fBits(bits)
+ , fSampleCnt(sampleCnt)
+ , fLastClipStackGenID(SkClipStack::kInvalidGenID) {
+ fLastClipStackRect.setEmpty();
+ }
+
+private:
+
+ int fWidth;
+ int fHeight;
+ int fBits;
+ int fSampleCnt;
+
+ int32_t fLastClipStackGenID;
+ SkIRect fLastClipStackRect;
+ SkIPoint fLastClipSpaceOffset;
+
+ typedef GrResource INHERITED;
+};
+
+#endif
diff --git a/gpu/GrSurface.cpp b/gpu/GrSurface.cpp
new file mode 100644
index 00000000..47d9959d
--- /dev/null
+++ b/gpu/GrSurface.cpp
@@ -0,0 +1,10 @@
+/*
+ * 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 "GrSurface.h"
+
+SK_DEFINE_INST_COUNT(GrSurface)
diff --git a/gpu/GrTBSearch.h b/gpu/GrTBSearch.h
new file mode 100644
index 00000000..cee4f2a4
--- /dev/null
+++ b/gpu/GrTBSearch.h
@@ -0,0 +1,45 @@
+
+/*
+ * 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 GrTBSearch_DEFINED
+#define GrTBSearch_DEFINED
+
+template <typename ELEM, typename KEY>
+int GrTBSearch(const ELEM array[], int count, KEY target) {
+ GrAssert(count >= 0);
+ if (0 == count) {
+ // we should insert it at 0
+ return ~0;
+ }
+
+ int high = count - 1;
+ int low = 0;
+ while (high > low) {
+ int index = (low + high) >> 1;
+ if (LT(array[index], target)) {
+ low = index + 1;
+ } else {
+ high = index;
+ }
+ }
+
+ // check if we found it
+ if (EQ(array[high], target)) {
+ return high;
+ }
+
+ // now return the ~ of where we should insert it
+ if (LT(array[high], target)) {
+ high += 1;
+ }
+ return ~high;
+}
+
+#endif
diff --git a/gpu/GrTHashCache.h b/gpu/GrTHashCache.h
new file mode 100644
index 00000000..6bd58c2d
--- /dev/null
+++ b/gpu/GrTHashCache.h
@@ -0,0 +1,246 @@
+
+/*
+ * 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 GrTHashCache_DEFINED
+#define GrTHashCache_DEFINED
+
+#include "GrTypes.h"
+#include "SkTDArray.h"
+
+// GrTDefaultFindFunctor implements the default find behavior for
+// GrTHashTable (i.e., return the first resource that matches the
+// provided key)
+template <typename T> class GrTDefaultFindFunctor {
+public:
+ // always accept the first element examined
+ bool operator()(const T*) const { return true; }
+};
+
+/**
+ * Key needs
+ * static bool EQ(const Entry&, const HashKey&);
+ * static bool LT(const Entry&, const HashKey&);
+ * uint32_t getHash() const;
+ *
+ * Allows duplicate key entries but on find you may get
+ * any of the duplicate entries returned.
+ */
+template <typename T, typename Key, size_t kHashBits> class GrTHashTable {
+public:
+ GrTHashTable() { Gr_bzero(fHash, sizeof(fHash)); }
+ ~GrTHashTable() {}
+
+ int count() const { return fSorted.count(); }
+ T* find(const Key&) const;
+ template <typename FindFuncType> T* find(const Key&, const FindFuncType&) const;
+ // return true if key was unique when inserted.
+ bool insert(const Key&, T*);
+ void remove(const Key&, const T*);
+ T* removeAt(int index, uint32_t hash);
+ void removeAll();
+ void deleteAll();
+ void unrefAll();
+
+ /**
+ * Return the index for the element, using a linear search.
+ */
+ int slowFindIndex(T* elem) const { return fSorted.find(elem); }
+
+#if GR_DEBUG
+ void validate() const;
+ bool contains(T*) const;
+#endif
+
+ // testing
+ const SkTDArray<T*>& getArray() const { return fSorted; }
+ SkTDArray<T*>& getArray() { return fSorted; }
+private:
+ enum {
+ kHashCount = 1 << kHashBits,
+ kHashMask = kHashCount - 1
+ };
+ static unsigned hash2Index(uint32_t hash) {
+ hash ^= hash >> 16;
+ if (kHashBits <= 8) {
+ hash ^= hash >> 8;
+ }
+ return hash & kHashMask;
+ }
+
+ mutable T* fHash[kHashCount];
+ SkTDArray<T*> fSorted;
+
+ // search fSorted, and return the found index, or ~index of where it
+ // should be inserted
+ int searchArray(const Key&) const;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename Key, size_t kHashBits>
+int GrTHashTable<T, Key, kHashBits>::searchArray(const Key& key) const {
+ int count = fSorted.count();
+ if (0 == count) {
+ // we should insert it at 0
+ return ~0;
+ }
+
+ const T* const* array = fSorted.begin();
+ int high = count - 1;
+ int low = 0;
+ while (high > low) {
+ int index = (low + high) >> 1;
+ if (Key::LT(*array[index], key)) {
+ low = index + 1;
+ } else {
+ high = index;
+ }
+ }
+
+ // check if we found it
+ if (Key::EQ(*array[high], key)) {
+ // above search should have found the first occurrence if there
+ // are multiple.
+ GrAssert(0 == high || Key::LT(*array[high - 1], key));
+ return high;
+ }
+
+ // now return the ~ of where we should insert it
+ if (Key::LT(*array[high], key)) {
+ high += 1;
+ }
+ return ~high;
+}
+
+template <typename T, typename Key, size_t kHashBits>
+T* GrTHashTable<T, Key, kHashBits>::find(const Key& key) const {
+ GrTDefaultFindFunctor<T> find;
+
+ return this->find(key, find);
+}
+
+template <typename T, typename Key, size_t kHashBits>
+template <typename FindFuncType>
+T* GrTHashTable<T, Key, kHashBits>::find(const Key& key, const FindFuncType& findFunc) const {
+
+ int hashIndex = hash2Index(key.getHash());
+ T* elem = fHash[hashIndex];
+
+ if (NULL != elem && Key::EQ(*elem, key) && findFunc(elem)) {
+ return elem;
+ }
+
+ // bsearch for the key in our sorted array
+ int index = this->searchArray(key);
+ if (index < 0) {
+ return NULL;
+ }
+
+ const T* const* array = fSorted.begin();
+
+ // above search should have found the first occurrence if there
+ // are multiple.
+ GrAssert(0 == index || Key::LT(*array[index - 1], key));
+
+ for ( ; index < count() && Key::EQ(*array[index], key); ++index) {
+ if (findFunc(fSorted[index])) {
+ // update the hash
+ fHash[hashIndex] = fSorted[index];
+ return fSorted[index];
+ }
+ }
+
+ return NULL;
+}
+
+template <typename T, typename Key, size_t kHashBits>
+bool GrTHashTable<T, Key, kHashBits>::insert(const Key& key, T* elem) {
+ int index = this->searchArray(key);
+ bool first = index < 0;
+ if (first) {
+ // turn it into the actual index
+ index = ~index;
+ }
+ // add it to our array
+ *fSorted.insert(index) = elem;
+ // update our hash table (overwrites any dupe's position in the hash)
+ fHash[hash2Index(key.getHash())] = elem;
+ return first;
+}
+
+template <typename T, typename Key, size_t kHashBits>
+void GrTHashTable<T, Key, kHashBits>::remove(const Key& key, const T* elem) {
+ int index = hash2Index(key.getHash());
+ if (fHash[index] == elem) {
+ fHash[index] = NULL;
+ }
+
+ // remove from our sorted array
+ index = this->searchArray(key);
+ GrAssert(index >= 0);
+ // if there are multiple matches searchArray will give us the first match
+ // march forward until we find elem.
+ while (elem != fSorted[index]) {
+ ++index;
+ GrAssert(index < fSorted.count());
+ }
+ GrAssert(elem == fSorted[index]);
+ fSorted.remove(index);
+}
+
+template <typename T, typename Key, size_t kHashBits>
+T* GrTHashTable<T, Key, kHashBits>::removeAt(int elemIndex, uint32_t hash) {
+ int hashIndex = hash2Index(hash);
+ if (fHash[hashIndex] == fSorted[elemIndex]) {
+ fHash[hashIndex] = NULL;
+ }
+ // remove from our sorted array
+ T* elem = fSorted[elemIndex];
+ fSorted.remove(elemIndex);
+ return elem;
+}
+
+template <typename T, typename Key, size_t kHashBits>
+void GrTHashTable<T, Key, kHashBits>::removeAll() {
+ fSorted.reset();
+ Gr_bzero(fHash, sizeof(fHash));
+}
+
+template <typename T, typename Key, size_t kHashBits>
+void GrTHashTable<T, Key, kHashBits>::deleteAll() {
+ fSorted.deleteAll();
+ Gr_bzero(fHash, sizeof(fHash));
+}
+
+template <typename T, typename Key, size_t kHashBits>
+void GrTHashTable<T, Key, kHashBits>::unrefAll() {
+ fSorted.unrefAll();
+ Gr_bzero(fHash, sizeof(fHash));
+}
+
+#if GR_DEBUG
+template <typename T, typename Key, size_t kHashBits>
+void GrTHashTable<T, Key, kHashBits>::validate() const {
+ int count = fSorted.count();
+ for (int i = 1; i < count; i++) {
+ GrAssert(Key::LT(*fSorted[i - 1], *fSorted[i]) ||
+ Key::EQ(*fSorted[i - 1], *fSorted[i]));
+ }
+}
+
+template <typename T, typename Key, size_t kHashBits>
+bool GrTHashTable<T, Key, kHashBits>::contains(T* elem) const {
+ int index = fSorted.find(elem);
+ return index >= 0;
+}
+
+#endif
+
+#endif
diff --git a/gpu/GrTemplates.h b/gpu/GrTemplates.h
new file mode 100644
index 00000000..69720dc1
--- /dev/null
+++ b/gpu/GrTemplates.h
@@ -0,0 +1,70 @@
+
+/*
+ * 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 GrTemplates_DEFINED
+#define GrTemplates_DEFINED
+
+#include "GrNoncopyable.h"
+
+/**
+ * Use to cast a ptr to a different type, and maintain strict-aliasing
+ */
+template <typename Dst, typename Src> Dst GrTCast(Src src) {
+ union {
+ Src src;
+ Dst dst;
+ } data;
+ data.src = src;
+ return data.dst;
+}
+
+/**
+ * takes a T*, saves the value it points to, in and restores the value in the
+ * destructor
+ * e.g.:
+ * {
+ * GrAutoTRestore<int*> autoCountRestore;
+ * if (useExtra) {
+ * autoCountRestore.reset(&fCount);
+ * fCount += fExtraCount;
+ * }
+ * ...
+ * } // fCount is restored
+ */
+template <typename T> class GrAutoTRestore : public GrNoncopyable {
+public:
+ GrAutoTRestore() : fPtr(NULL), fVal() {}
+
+ GrAutoTRestore(T* ptr) {
+ fPtr = ptr;
+ if (NULL != ptr) {
+ fVal = *ptr;
+ }
+ }
+
+ ~GrAutoTRestore() {
+ if (NULL != fPtr) {
+ *fPtr = fVal;
+ }
+ }
+
+ // restores previously saved value (if any) and saves value for passed T*
+ void reset(T* ptr) {
+ if (NULL != fPtr) {
+ *fPtr = fVal;
+ }
+ fPtr = ptr;
+ fVal = *ptr;
+ }
+private:
+ T* fPtr;
+ T fVal;
+};
+
+#endif
diff --git a/gpu/GrTextContext.cpp b/gpu/GrTextContext.cpp
new file mode 100644
index 00000000..5a2c315b
--- /dev/null
+++ b/gpu/GrTextContext.cpp
@@ -0,0 +1,255 @@
+/*
+ * 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 "GrTextContext.h"
+#include "GrAtlas.h"
+#include "GrContext.h"
+#include "GrDrawTarget.h"
+#include "GrFontScaler.h"
+#include "GrIndexBuffer.h"
+#include "GrTextStrike.h"
+#include "GrTextStrike_impl.h"
+#include "SkPath.h"
+#include "SkStrokeRec.h"
+
+static const int kGlyphCoordsAttributeIndex = 1;
+
+void GrTextContext::flushGlyphs() {
+ if (NULL == fDrawTarget) {
+ return;
+ }
+
+ GrDrawState* drawState = fDrawTarget->drawState();
+ GrDrawState::AutoRestoreEffects are(drawState);
+ drawState->setFromPaint(fPaint, SkMatrix::I(), fContext->getRenderTarget());
+
+ if (fCurrVertex > 0) {
+ // setup our sampler state for our text texture/atlas
+ GrAssert(GrIsALIGN4(fCurrVertex));
+ GrAssert(fCurrTexture);
+ GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kNone_FilterMode);
+
+ // This effect could be stored with one of the cache objects (atlas?)
+ drawState->addCoverageEffect(
+ GrSimpleTextureEffect::CreateWithCustomCoords(fCurrTexture, params),
+ kGlyphCoordsAttributeIndex)->unref();
+
+ if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
+ if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
+ kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
+ fPaint.numColorStages()) {
+ GrPrintf("LCD Text will not draw correctly.\n");
+ }
+ // setup blend so that we get mask * paintColor + (1-mask)*dstColor
+ drawState->setBlendConstant(fPaint.getColor());
+ drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
+ // don't modulate by the paint's color in the frag since we're
+ // already doing it via the blend const.
+ drawState->setColor(0xffffffff);
+ } else {
+ // set back to normal in case we took LCD path previously.
+ drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
+ drawState->setColor(fPaint.getColor());
+ }
+
+ int nGlyphs = fCurrVertex / 4;
+ fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
+ fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
+ nGlyphs,
+ 4, 6);
+ fDrawTarget->resetVertexSource();
+ fVertices = NULL;
+ fMaxVertices = 0;
+ fCurrVertex = 0;
+ GrSafeSetNull(fCurrTexture);
+ }
+}
+
+GrTextContext::GrTextContext(GrContext* context, const GrPaint& paint) : fPaint(paint) {
+ fContext = context;
+ fStrike = NULL;
+
+ fCurrTexture = NULL;
+ fCurrVertex = 0;
+
+ const GrClipData* clipData = context->getClip();
+
+ SkRect devConservativeBound;
+ clipData->fClipStack->getConservativeBounds(
+ -clipData->fOrigin.fX,
+ -clipData->fOrigin.fY,
+ context->getRenderTarget()->width(),
+ context->getRenderTarget()->height(),
+ &devConservativeBound);
+
+ devConservativeBound.roundOut(&fClipRect);
+
+ fAutoMatrix.setIdentity(fContext, &fPaint);
+
+ fDrawTarget = fContext->getTextTarget();
+
+ fVertices = NULL;
+ fMaxVertices = 0;
+}
+
+GrTextContext::~GrTextContext() {
+ this->flushGlyphs();
+}
+
+void GrTextContext::flush() {
+ this->flushGlyphs();
+}
+
+namespace {
+
+// position + texture coord
+extern const GrVertexAttrib gTextVertexAttribs[] = {
+ {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
+ {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
+};
+
+};
+
+void GrTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
+ GrFixed vx, GrFixed vy,
+ GrFontScaler* scaler) {
+ if (NULL == fDrawTarget) {
+ return;
+ }
+ if (NULL == fStrike) {
+ fStrike = fContext->getFontCache()->getStrike(scaler);
+ }
+
+ GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
+ if (NULL == glyph || glyph->fBounds.isEmpty()) {
+ return;
+ }
+
+ 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();
+ GrFixed height = glyph->fBounds.height();
+
+ // check if we clipped out
+ if (true || NULL == glyph->fAtlas) {
+ int x = vx >> 16;
+ int y = vy >> 16;
+ if (fClipRect.quickReject(x, y, x + width, y + height)) {
+// SkCLZ(3); // so we can set a break-point in the debugger
+ return;
+ }
+ }
+
+ if (NULL == glyph->fAtlas) {
+ if (fStrike->getGlyphAtlas(glyph, scaler)) {
+ goto HAS_ATLAS;
+ }
+#if 0 // M30 specific revert of font cache improvement to fix https://code.google.com/p/chromium/issues/detail?id=303803
+ // try to clear out an unused atlas before we flush
+ fContext->getFontCache()->freeAtlasExceptFor(fStrike);
+ if (fStrike->getGlyphAtlas(glyph, scaler)) {
+ goto HAS_ATLAS;
+ }
+#endif
+ // before we purge the cache, we must flush any accumulated draws
+ this->flushGlyphs();
+ fContext->flush();
+
+ // try to purge
+ fContext->getFontCache()->purgeExceptFor(fStrike);
+ if (fStrike->getGlyphAtlas(glyph, scaler)) {
+ goto HAS_ATLAS;
+ }
+
+ if (NULL == glyph->fPath) {
+ SkPath* path = SkNEW(SkPath);
+ if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
+ // flag the glyph as being dead?
+ delete path;
+ return;
+ }
+ glyph->fPath = path;
+ }
+
+ GrContext::AutoMatrix am;
+ SkMatrix translate;
+ translate.setTranslate(SkFixedToScalar(vx - SkIntToFixed(glyph->fBounds.fLeft)),
+ SkFixedToScalar(vy - SkIntToFixed(glyph->fBounds.fTop)));
+ GrPaint tmpPaint(fPaint);
+ am.setPreConcat(fContext, translate, &tmpPaint);
+ SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+ fContext->drawPath(tmpPaint, *glyph->fPath, stroke);
+ return;
+ }
+
+HAS_ATLAS:
+ GrAssert(glyph->fAtlas);
+
+ // now promote them to fixed (TODO: Rethink using fixed pt).
+ width = SkIntToFixed(width);
+ height = SkIntToFixed(height);
+
+ GrTexture* texture = glyph->fAtlas->texture();
+ GrAssert(texture);
+
+ if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
+ this->flushGlyphs();
+ fCurrTexture = texture;
+ fCurrTexture->ref();
+ }
+
+ if (NULL == fVertices) {
+ // If we need to reserve vertices allow the draw target to suggest
+ // a number of verts to reserve and whether to perform a flush.
+ fMaxVertices = kMinRequestedVerts;
+ fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
+ SK_ARRAY_COUNT(gTextVertexAttribs));
+ bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
+ if (flush) {
+ this->flushGlyphs();
+ fContext->flush();
+ fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
+ SK_ARRAY_COUNT(gTextVertexAttribs));
+ }
+ fMaxVertices = kDefaultRequestedVerts;
+ // ignore return, no point in flushing again.
+ fDrawTarget->geometryHints(&fMaxVertices, NULL);
+
+ int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
+ if (fMaxVertices < kMinRequestedVerts) {
+ fMaxVertices = kDefaultRequestedVerts;
+ } else if (fMaxVertices > maxQuadVertices) {
+ // don't exceed the limit of the index buffer
+ fMaxVertices = maxQuadVertices;
+ }
+ bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
+ 0,
+ GrTCast<void**>(&fVertices),
+ NULL);
+ GrAlwaysAssert(success);
+ GrAssert(2*sizeof(GrPoint) == fDrawTarget->getDrawState().getVertexSize());
+ }
+
+ GrFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
+ GrFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
+
+ fVertices[2*fCurrVertex].setRectFan(SkFixedToFloat(vx),
+ SkFixedToFloat(vy),
+ SkFixedToFloat(vx + width),
+ SkFixedToFloat(vy + height),
+ 2 * sizeof(SkPoint));
+ fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
+ SkFixedToFloat(texture->normalizeFixedY(ty)),
+ SkFixedToFloat(texture->normalizeFixedX(tx + width)),
+ SkFixedToFloat(texture->normalizeFixedY(ty + height)),
+ 2 * sizeof(SkPoint));
+ fCurrVertex += 4;
+}
diff --git a/gpu/GrTextStrike.cpp b/gpu/GrTextStrike.cpp
new file mode 100644
index 00000000..9373351f
--- /dev/null
+++ b/gpu/GrTextStrike.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 "GrAtlas.h"
+#include "GrGpu.h"
+#include "GrRectanizer.h"
+#include "GrTextStrike.h"
+#include "GrTextStrike_impl.h"
+
+SK_DEFINE_INST_COUNT(GrFontScaler)
+SK_DEFINE_INST_COUNT(GrKey)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define FONT_CACHE_STATS 0
+#if FONT_CACHE_STATS
+static int g_PurgeCount = 0;
+#endif
+
+GrFontCache::GrFontCache(GrGpu* gpu) : fGpu(gpu) {
+ gpu->ref();
+ fAtlasMgr = NULL;
+
+ fHead = fTail = NULL;
+}
+
+GrFontCache::~GrFontCache() {
+ fCache.deleteAll();
+ delete fAtlasMgr;
+ fGpu->unref();
+#if FONT_CACHE_STATS
+ GrPrintf("Num purges: %d\n", g_PurgeCount);
+#endif
+}
+
+GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
+ const Key& key) {
+ if (NULL == fAtlasMgr) {
+ fAtlasMgr = SkNEW_ARGS(GrAtlasMgr, (fGpu));
+ }
+ GrTextStrike* strike = SkNEW_ARGS(GrTextStrike,
+ (this, scaler->getKey(),
+ scaler->getMaskFormat(), fAtlasMgr));
+ fCache.insert(key, strike);
+
+ if (fHead) {
+ fHead->fPrev = strike;
+ } else {
+ GrAssert(NULL == fTail);
+ fTail = strike;
+ }
+ strike->fPrev = NULL;
+ strike->fNext = fHead;
+ fHead = strike;
+
+ return strike;
+}
+
+void GrFontCache::freeAll() {
+ fCache.deleteAll();
+ delete fAtlasMgr;
+ fAtlasMgr = NULL;
+ fHead = NULL;
+ fTail = NULL;
+}
+
+void GrFontCache::purgeExceptFor(GrTextStrike* preserveStrike) {
+ GrTextStrike* strike = fTail;
+ bool purge = true;
+ while (strike) {
+ if (strike == preserveStrike) {
+ strike = strike->fPrev;
+ continue;
+ }
+ GrTextStrike* strikeToPurge = strike;
+ strike = strikeToPurge->fPrev;
+ if (purge) {
+ // keep purging if we won't free up any atlases with this strike.
+ purge = (NULL == strikeToPurge->fAtlas);
+ int index = fCache.slowFindIndex(strikeToPurge);
+ GrAssert(index >= 0);
+ fCache.removeAt(index, strikeToPurge->fFontScalerKey->getHash());
+ this->detachStrikeFromList(strikeToPurge);
+ delete strikeToPurge;
+ } else {
+ // for the remaining strikes, we just mark them unused
+ GrAtlas::MarkAllUnused(strikeToPurge->fAtlas);
+ }
+ }
+#if FONT_CACHE_STATS
+ ++g_PurgeCount;
+#endif
+}
+
+void GrFontCache::freeAtlasExceptFor(GrTextStrike* preserveStrike) {
+ GrTextStrike* strike = fTail;
+ while (strike) {
+ if (strike == preserveStrike) {
+ strike = strike->fPrev;
+ continue;
+ }
+ GrTextStrike* strikeToPurge = strike;
+ strike = strikeToPurge->fPrev;
+ if (strikeToPurge->removeUnusedAtlases()) {
+ if (NULL == strikeToPurge->fAtlas) {
+ int index = fCache.slowFindIndex(strikeToPurge);
+ GrAssert(index >= 0);
+ fCache.removeAt(index, strikeToPurge->fFontScalerKey->getHash());
+ this->detachStrikeFromList(strikeToPurge);
+ delete strikeToPurge;
+ }
+ break;
+ }
+ }
+}
+
+#if GR_DEBUG
+void GrFontCache::validate() const {
+ int count = fCache.count();
+ if (0 == count) {
+ GrAssert(!fHead);
+ GrAssert(!fTail);
+ } else if (1 == count) {
+ GrAssert(fHead == fTail);
+ } else {
+ GrAssert(fHead != fTail);
+ }
+
+ int count2 = 0;
+ const GrTextStrike* strike = fHead;
+ while (strike) {
+ count2 += 1;
+ strike = strike->fNext;
+ }
+ GrAssert(count == count2);
+
+ count2 = 0;
+ strike = fTail;
+ while (strike) {
+ count2 += 1;
+ strike = strike->fPrev;
+ }
+ GrAssert(count == count2);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_DEBUG
+ static int gCounter;
+#endif
+
+/*
+ The text strike is specific to a given font/style/matrix setup, which is
+ represented by the GrHostFontScaler object we are given in getGlyph().
+
+ We map a 32bit glyphID to a GrGlyph record, which in turn points to a
+ atlas and a position within that texture.
+ */
+
+GrTextStrike::GrTextStrike(GrFontCache* cache, const GrKey* key,
+ GrMaskFormat format,
+ GrAtlasMgr* atlasMgr) : fPool(64) {
+ fFontScalerKey = key;
+ fFontScalerKey->ref();
+
+ fFontCache = cache; // no need to ref, it won't go away before we do
+ fAtlasMgr = atlasMgr; // no need to ref, it won't go away before we do
+ fAtlas = NULL;
+
+ fMaskFormat = format;
+
+#if GR_DEBUG
+// GrPrintf(" GrTextStrike %p %d\n", this, gCounter);
+ gCounter += 1;
+#endif
+}
+
+// these signatures are needed because they're used with
+// SkTDArray::visitAll() (see destructor & removeUnusedAtlases())
+static void free_glyph(GrGlyph*& glyph) { glyph->free(); }
+
+static void invalidate_glyph(GrGlyph*& glyph) {
+ if (glyph->fAtlas && !glyph->fAtlas->used()) {
+ glyph->fAtlas = NULL;
+ }
+}
+
+GrTextStrike::~GrTextStrike() {
+ GrAtlas::FreeLList(fAtlas);
+ fFontScalerKey->unref();
+ fCache.getArray().visitAll(free_glyph);
+
+#if GR_DEBUG
+ gCounter -= 1;
+// GrPrintf("~GrTextStrike %p %d\n", this, gCounter);
+#endif
+}
+
+GrGlyph* GrTextStrike::generateGlyph(GrGlyph::PackedID packed,
+ GrFontScaler* scaler) {
+ SkIRect bounds;
+ if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
+ return NULL;
+ }
+
+ GrGlyph* glyph = fPool.alloc();
+ glyph->init(packed, bounds);
+ fCache.insert(packed, glyph);
+ return glyph;
+}
+
+bool GrTextStrike::removeUnusedAtlases() {
+ fCache.getArray().visitAll(invalidate_glyph);
+ return GrAtlas::RemoveUnusedAtlases(fAtlasMgr, &fAtlas);
+
+ return false;
+}
+
+bool GrTextStrike::getGlyphAtlas(GrGlyph* glyph, GrFontScaler* scaler) {
+#if 0 // testing hack to force us to flush our cache often
+ static int gCounter;
+ if ((++gCounter % 10) == 0) return false;
+#endif
+
+ GrAssert(glyph);
+ GrAssert(scaler);
+ GrAssert(fCache.contains(glyph));
+ if (glyph->fAtlas) {
+ glyph->fAtlas->setUsed(true);
+ return true;
+ }
+
+ GrAutoRef ar(scaler);
+
+ int bytesPerPixel = GrMaskFormatBytesPerPixel(fMaskFormat);
+ size_t size = glyph->fBounds.area() * bytesPerPixel;
+ SkAutoSMalloc<1024> storage(size);
+ if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
+ glyph->height(),
+ glyph->width() * bytesPerPixel,
+ storage.get())) {
+ return false;
+ }
+
+ GrAtlas* atlas = fAtlasMgr->addToAtlas(&fAtlas, glyph->width(),
+ glyph->height(), storage.get(),
+ fMaskFormat,
+ &glyph->fAtlasLocation);
+ if (NULL == atlas) {
+ return false;
+ }
+
+ glyph->fAtlas = atlas;
+ atlas->setUsed(true);
+ return true;
+}
diff --git a/gpu/GrTextStrike.h b/gpu/GrTextStrike.h
new file mode 100644
index 00000000..e359e267
--- /dev/null
+++ b/gpu/GrTextStrike.h
@@ -0,0 +1,120 @@
+
+/*
+ * 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 GrTextStrike_DEFINED
+#define GrTextStrike_DEFINED
+
+#include "GrAllocPool.h"
+#include "GrFontScaler.h"
+#include "GrTHashCache.h"
+#include "GrPoint.h"
+#include "GrGlyph.h"
+
+class GrAtlasMgr;
+class GrFontCache;
+class GrGpu;
+class GrFontPurgeListener;
+
+/**
+ * The textcache maps a hostfontscaler instance to a dictionary of
+ * glyphid->strike
+ */
+class GrTextStrike {
+public:
+ GrTextStrike(GrFontCache*, const GrKey* fontScalerKey, GrMaskFormat,
+ GrAtlasMgr*);
+ ~GrTextStrike();
+
+ const GrKey* getFontScalerKey() const { return fFontScalerKey; }
+ GrFontCache* getFontCache() const { return fFontCache; }
+ GrMaskFormat getMaskFormat() const { return fMaskFormat; }
+
+ inline GrGlyph* getGlyph(GrGlyph::PackedID, GrFontScaler*);
+ bool getGlyphAtlas(GrGlyph*, GrFontScaler*);
+
+ // testing
+ int countGlyphs() const { return fCache.getArray().count(); }
+ const GrGlyph* glyphAt(int index) const {
+ return fCache.getArray()[index];
+ }
+ GrAtlas* getAtlas() const { return fAtlas; }
+
+ // returns true if an atlas was removed
+ bool removeUnusedAtlases();
+
+public:
+ // for LRU
+ GrTextStrike* fPrev;
+ GrTextStrike* fNext;
+
+private:
+ class Key;
+ GrTHashTable<GrGlyph, Key, 7> fCache;
+ const GrKey* fFontScalerKey;
+ GrTAllocPool<GrGlyph> fPool;
+
+ GrFontCache* fFontCache;
+ GrAtlasMgr* fAtlasMgr;
+ GrAtlas* fAtlas; // linklist
+
+ GrMaskFormat fMaskFormat;
+
+ GrGlyph* generateGlyph(GrGlyph::PackedID packed, GrFontScaler* scaler);
+ // returns true if after the purge, the strike is empty
+ bool purgeAtlasAtY(GrAtlas* atlas, int yCoord);
+
+ friend class GrFontCache;
+};
+
+class GrFontCache {
+public:
+ GrFontCache(GrGpu*);
+ ~GrFontCache();
+
+ inline GrTextStrike* getStrike(GrFontScaler*);
+
+ void freeAll();
+
+ void purgeExceptFor(GrTextStrike*);
+
+ // remove an unused atlas and its strike (if necessary)
+ void freeAtlasExceptFor(GrTextStrike*);
+
+ // testing
+ int countStrikes() const { return fCache.getArray().count(); }
+ const GrTextStrike* strikeAt(int index) const {
+ return fCache.getArray()[index];
+ }
+ GrTextStrike* getHeadStrike() const { return fHead; }
+
+#if GR_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+private:
+ friend class GrFontPurgeListener;
+
+ class Key;
+ GrTHashTable<GrTextStrike, Key, 8> fCache;
+ // for LRU
+ GrTextStrike* fHead;
+ GrTextStrike* fTail;
+
+ GrGpu* fGpu;
+ GrAtlasMgr* fAtlasMgr;
+
+
+ GrTextStrike* generateStrike(GrFontScaler*, const Key&);
+ inline void detachStrikeFromList(GrTextStrike*);
+};
+
+#endif
diff --git a/gpu/GrTextStrike_impl.h b/gpu/GrTextStrike_impl.h
new file mode 100644
index 00000000..48323bcc
--- /dev/null
+++ b/gpu/GrTextStrike_impl.h
@@ -0,0 +1,105 @@
+
+/*
+ * 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 GrTextStrike_impl_DEFINED
+#define GrTextStrike_impl_DEFINED
+
+class GrFontCache::Key {
+public:
+ Key(GrFontScaler* scaler) {
+ fFontScalerKey = scaler->getKey();
+ }
+
+ uint32_t getHash() const { return fFontScalerKey->getHash(); }
+
+ static bool LT(const GrTextStrike& strike, const Key& key) {
+ return *strike.getFontScalerKey() < *key.fFontScalerKey;
+ }
+ static bool EQ(const GrTextStrike& strike, const Key& key) {
+ return *strike.getFontScalerKey() == *key.fFontScalerKey;
+ }
+
+private:
+ const GrKey* fFontScalerKey;
+};
+
+void GrFontCache::detachStrikeFromList(GrTextStrike* strike) {
+ if (strike->fPrev) {
+ GrAssert(fHead != strike);
+ strike->fPrev->fNext = strike->fNext;
+ } else {
+ GrAssert(fHead == strike);
+ fHead = strike->fNext;
+ }
+
+ if (strike->fNext) {
+ GrAssert(fTail != strike);
+ strike->fNext->fPrev = strike->fPrev;
+ } else {
+ GrAssert(fTail == strike);
+ fTail = strike->fPrev;
+ }
+}
+
+GrTextStrike* GrFontCache::getStrike(GrFontScaler* scaler) {
+ this->validate();
+
+ Key key(scaler);
+ GrTextStrike* strike = fCache.find(key);
+ if (NULL == strike) {
+ strike = this->generateStrike(scaler, key);
+ } else if (strike->fPrev) {
+ // Need to put the strike at the head of its dllist, since that is how
+ // we age the strikes for purging (we purge from the back of the list
+ this->detachStrikeFromList(strike);
+ // attach at the head
+ fHead->fPrev = strike;
+ strike->fNext = fHead;
+ strike->fPrev = NULL;
+ fHead = strike;
+ }
+
+ this->validate();
+ return strike;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This Key just wraps a glyphID, and matches the protocol need for
+ * GrTHashTable
+ */
+class GrTextStrike::Key {
+public:
+ Key(GrGlyph::PackedID id) : fPackedID(id) {}
+
+ uint32_t getHash() const { return fPackedID; }
+
+ static bool LT(const GrGlyph& glyph, const Key& key) {
+ return glyph.fPackedID < key.fPackedID;
+ }
+ static bool EQ(const GrGlyph& glyph, const Key& key) {
+ return glyph.fPackedID == key.fPackedID;
+ }
+
+private:
+ GrGlyph::PackedID fPackedID;
+};
+
+GrGlyph* GrTextStrike::getGlyph(GrGlyph::PackedID packed,
+ GrFontScaler* scaler) {
+ GrGlyph* glyph = fCache.find(packed);
+ if (NULL == glyph) {
+ glyph = this->generateGlyph(packed, scaler);
+ }
+ return glyph;
+}
+
+#endif
diff --git a/gpu/GrTexture.cpp b/gpu/GrTexture.cpp
new file mode 100644
index 00000000..c05f35d9
--- /dev/null
+++ b/gpu/GrTexture.cpp
@@ -0,0 +1,196 @@
+
+/*
+ * 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 "GrTexture.h"
+
+#include "GrContext.h"
+#include "GrDrawTargetCaps.h"
+#include "GrGpu.h"
+#include "GrRenderTarget.h"
+#include "GrResourceCache.h"
+
+SK_DEFINE_INST_COUNT(GrTexture)
+
+GrTexture::~GrTexture() {
+ if (NULL != fRenderTarget.get()) {
+ fRenderTarget.get()->owningTextureDestroyed();
+ }
+}
+
+/**
+ * This method allows us to interrupt the normal deletion process and place
+ * textures back in the texture cache when their ref count goes to zero.
+ */
+void GrTexture::internal_dispose() const {
+
+ if (this->isSetFlag((GrTextureFlags) kReturnToCache_FlagBit) &&
+ NULL != this->INHERITED::getContext()) {
+ GrTexture* nonConstThis = const_cast<GrTexture *>(this);
+ this->fRefCnt = 1; // restore ref count to initial setting
+
+ nonConstThis->resetFlag((GrTextureFlags) kReturnToCache_FlagBit);
+ nonConstThis->INHERITED::getContext()->addExistingTextureToCache(nonConstThis);
+
+ // Note: "this" texture might be freed inside addExistingTextureToCache
+ // if it is purged.
+ return;
+ }
+
+ this->INHERITED::internal_dispose();
+}
+
+bool GrTexture::readPixels(int left, int top, int width, int height,
+ GrPixelConfig config, void* buffer,
+ size_t rowBytes, uint32_t pixelOpsFlags) {
+ // go through context so that all necessary flushing occurs
+ GrContext* context = this->getContext();
+ if (NULL == context) {
+ return false;
+ }
+ return context->readTexturePixels(this,
+ left, top, width, height,
+ config, buffer, rowBytes,
+ pixelOpsFlags);
+}
+
+void GrTexture::writePixels(int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes, uint32_t pixelOpsFlags) {
+ // go through context so that all necessary flushing occurs
+ GrContext* context = this->getContext();
+ if (NULL == context) {
+ return;
+ }
+ context->writeTexturePixels(this,
+ left, top, width, height,
+ config, buffer, rowBytes,
+ pixelOpsFlags);
+}
+
+void GrTexture::onRelease() {
+ GrAssert(!this->isSetFlag((GrTextureFlags) kReturnToCache_FlagBit));
+ INHERITED::onRelease();
+}
+
+void GrTexture::onAbandon() {
+ if (NULL != fRenderTarget.get()) {
+ fRenderTarget->abandon();
+ }
+ INHERITED::onAbandon();
+}
+
+void GrTexture::validateDesc() const {
+ if (NULL != this->asRenderTarget()) {
+ // This texture has a render target
+ GrAssert(0 != (fDesc.fFlags & kRenderTarget_GrTextureFlagBit));
+
+ if (NULL != this->asRenderTarget()->getStencilBuffer()) {
+ GrAssert(0 != (fDesc.fFlags & kNoStencil_GrTextureFlagBit));
+ } else {
+ GrAssert(0 == (fDesc.fFlags & kNoStencil_GrTextureFlagBit));
+ }
+
+ GrAssert(fDesc.fSampleCnt == this->asRenderTarget()->numSamples());
+ } else {
+ GrAssert(0 == (fDesc.fFlags & kRenderTarget_GrTextureFlagBit));
+ GrAssert(0 == (fDesc.fFlags & kNoStencil_GrTextureFlagBit));
+ GrAssert(0 == fDesc.fSampleCnt);
+ }
+}
+
+// These flags need to fit in a GrResourceKey::ResourceFlags so they can be folded into the texture
+// key
+enum TextureFlags {
+ /**
+ * The kStretchToPOT bit is set when the texture is NPOT and is being repeated but the
+ * hardware doesn't support that feature.
+ */
+ kStretchToPOT_TextureFlag = 0x1,
+ /**
+ * The kBilerp bit can only be set when the kStretchToPOT flag is set and indicates whether the
+ * stretched texture should be bilerped.
+ */
+ kBilerp_TextureFlag = 0x2,
+};
+
+namespace {
+GrResourceKey::ResourceFlags get_texture_flags(const GrGpu* gpu,
+ const GrTextureParams* params,
+ const GrTextureDesc& desc) {
+ GrResourceKey::ResourceFlags flags = 0;
+ bool tiled = NULL != params && params->isTiled();
+ if (tiled && !gpu->caps()->npotTextureTileSupport()) {
+ if (!GrIsPow2(desc.fWidth) || !GrIsPow2(desc.fHeight)) {
+ flags |= kStretchToPOT_TextureFlag;
+ switch(params->filterMode()) {
+ case GrTextureParams::kNone_FilterMode:
+ break;
+ case GrTextureParams::kBilerp_FilterMode:
+ case GrTextureParams::kMipMap_FilterMode:
+ flags |= kBilerp_TextureFlag;
+ break;
+ }
+ }
+ }
+ return flags;
+}
+
+GrResourceKey::ResourceType texture_resource_type() {
+ static const GrResourceKey::ResourceType gType = GrResourceKey::GenerateResourceType();
+ return gType;
+}
+
+// FIXME: This should be refactored with the code in gl/GrGpuGL.cpp.
+GrSurfaceOrigin resolve_origin(const GrTextureDesc& desc) {
+ // By default, GrRenderTargets are GL's normal orientation so that they
+ // can be drawn to by the outside world without the client having
+ // to render upside down.
+ bool renderTarget = 0 != (desc.fFlags & kRenderTarget_GrTextureFlagBit);
+ if (kDefault_GrSurfaceOrigin == desc.fOrigin) {
+ return renderTarget ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin;
+ } else {
+ return desc.fOrigin;
+ }
+}
+}
+
+GrResourceKey GrTexture::ComputeKey(const GrGpu* gpu,
+ const GrTextureParams* params,
+ const GrTextureDesc& desc,
+ const GrCacheID& cacheID) {
+ GrResourceKey::ResourceFlags flags = get_texture_flags(gpu, params, desc);
+ return GrResourceKey(cacheID, texture_resource_type(), flags);
+}
+
+GrResourceKey GrTexture::ComputeScratchKey(const GrTextureDesc& desc) {
+ GrCacheID::Key idKey;
+ // Instead of a client-provided key of the texture contents we create a key from the
+ // descriptor.
+ GR_STATIC_ASSERT(sizeof(idKey) >= 16);
+ GrAssert(desc.fHeight < (1 << 16));
+ GrAssert(desc.fWidth < (1 << 16));
+ idKey.fData32[0] = (desc.fWidth) | (desc.fHeight << 16);
+ idKey.fData32[1] = desc.fConfig | desc.fSampleCnt << 16;
+ idKey.fData32[2] = desc.fFlags;
+ idKey.fData32[3] = resolve_origin(desc); // Only needs 2 bits actually
+ static const int kPadSize = sizeof(idKey) - 16;
+ GR_STATIC_ASSERT(kPadSize >= 0);
+ memset(idKey.fData8 + 16, 0, kPadSize);
+
+ GrCacheID cacheID(GrResourceKey::ScratchDomain(), idKey);
+ return GrResourceKey(cacheID, texture_resource_type(), 0);
+}
+
+bool GrTexture::NeedsResizing(const GrResourceKey& key) {
+ return SkToBool(key.getResourceFlags() & kStretchToPOT_TextureFlag);
+}
+
+bool GrTexture::NeedsBilerp(const GrResourceKey& key) {
+ return SkToBool(key.getResourceFlags() & kBilerp_TextureFlag);
+}
diff --git a/gpu/GrTextureAccess.cpp b/gpu/GrTextureAccess.cpp
new file mode 100644
index 00000000..ae6c0422
--- /dev/null
+++ b/gpu/GrTextureAccess.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 "GrTextureAccess.h"
+#include "GrColor.h"
+#include "GrTexture.h"
+
+GrTextureAccess::GrTextureAccess() {
+#if GR_DEBUG
+ memcpy(fSwizzle, "void", 5);
+ fSwizzleMask = 0xbeeffeed;
+#endif
+}
+
+GrTextureAccess::GrTextureAccess(GrTexture* texture, const GrTextureParams& params) {
+ this->reset(texture, params);
+}
+
+GrTextureAccess::GrTextureAccess(GrTexture* texture,
+ GrTextureParams::FilterMode filterMode,
+ SkShader::TileMode tileXAndY) {
+ this->reset(texture, filterMode, tileXAndY);
+}
+
+GrTextureAccess::GrTextureAccess(GrTexture* texture,
+ const char* swizzle,
+ const GrTextureParams& params) {
+ this->reset(texture, swizzle, params);
+}
+
+GrTextureAccess::GrTextureAccess(GrTexture* texture,
+ const char* swizzle,
+ GrTextureParams::FilterMode filterMode,
+ SkShader::TileMode tileXAndY) {
+ this->reset(texture, swizzle, filterMode, tileXAndY);
+}
+
+void GrTextureAccess::reset(GrTexture* texture,
+ const char* swizzle,
+ const GrTextureParams& params) {
+ GrAssert(NULL != texture);
+ GrAssert(strlen(swizzle) >= 1 && strlen(swizzle) <= 4);
+
+ fParams = params;
+ fTexture.reset(SkRef(texture));
+ this->setSwizzle(swizzle);
+}
+
+void GrTextureAccess::reset(GrTexture* texture,
+ const char* swizzle,
+ GrTextureParams::FilterMode filterMode,
+ SkShader::TileMode tileXAndY) {
+ GrAssert(NULL != texture);
+ GrAssert(strlen(swizzle) >= 1 && strlen(swizzle) <= 4);
+
+ fParams.reset(tileXAndY, filterMode);
+ fTexture.reset(SkRef(texture));
+ this->setSwizzle(swizzle);
+}
+
+void GrTextureAccess::reset(GrTexture* texture,
+ const GrTextureParams& params) {
+ GrAssert(NULL != texture);
+ fTexture.reset(SkRef(texture));
+ fParams = params;
+ memcpy(fSwizzle, "rgba", 5);
+ fSwizzleMask = kRGBA_GrColorComponentFlags;
+}
+
+void GrTextureAccess::reset(GrTexture* texture,
+ GrTextureParams::FilterMode filterMode,
+ SkShader::TileMode tileXAndY) {
+ GrAssert(NULL != texture);
+ fTexture.reset(SkRef(texture));
+ fParams.reset(tileXAndY, filterMode);
+ memcpy(fSwizzle, "rgba", 5);
+ fSwizzleMask = kRGBA_GrColorComponentFlags;
+}
+
+void GrTextureAccess::setSwizzle(const char* swizzle) {
+ fSwizzleMask = 0;
+ memset(fSwizzle, '\0', 5);
+ for (int i = 0; i < 4 && '\0' != swizzle[i]; ++i) {
+ fSwizzle[i] = swizzle[i];
+ switch (swizzle[i]) {
+ case 'r':
+ fSwizzleMask |= kR_GrColorComponentFlag;
+ break;
+ case 'g':
+ fSwizzleMask |= kG_GrColorComponentFlag;
+ break;
+ case 'b':
+ fSwizzleMask |= kB_GrColorComponentFlag;
+ break;
+ case 'a':
+ fSwizzleMask |= kA_GrColorComponentFlag;
+ break;
+ default:
+ GrCrash("Unexpected swizzle string character.");
+ break;
+ }
+ }
+}
diff --git a/gpu/GrVertexBuffer.h b/gpu/GrVertexBuffer.h
new file mode 100644
index 00000000..a2bd5a1b
--- /dev/null
+++ b/gpu/GrVertexBuffer.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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 GrVertexBuffer_DEFINED
+#define GrVertexBuffer_DEFINED
+
+#include "GrGeometryBuffer.h"
+
+class GrVertexBuffer : public GrGeometryBuffer {
+protected:
+ GrVertexBuffer(GrGpu* gpu, bool isWrapped, size_t sizeInBytes, bool dynamic, bool cpuBacked)
+ : INHERITED(gpu, isWrapped, sizeInBytes, dynamic, cpuBacked) {}
+private:
+ typedef GrGeometryBuffer INHERITED;
+};
+
+#endif
diff --git a/gpu/SkGpuDevice.cpp b/gpu/SkGpuDevice.cpp
new file mode 100644
index 00000000..be56eb67
--- /dev/null
+++ b/gpu/SkGpuDevice.cpp
@@ -0,0 +1,1797 @@
+/*
+ * 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 "SkGpuDevice.h"
+
+#include "effects/GrTextureDomainEffect.h"
+#include "effects/GrSimpleTextureEffect.h"
+
+#include "GrContext.h"
+#include "GrTextContext.h"
+
+#include "SkGrTexturePixelRef.h"
+
+#include "SkColorFilter.h"
+#include "SkDeviceImageFilterProxy.h"
+#include "SkDrawProcs.h"
+#include "SkGlyphCache.h"
+#include "SkImageFilter.h"
+#include "SkPathEffect.h"
+#include "SkRRect.h"
+#include "SkStroke.h"
+#include "SkUtils.h"
+#include "SkErrorInternals.h"
+
+#define CACHE_COMPATIBLE_DEVICE_TEXTURES 1
+
+#if 0
+ extern bool (*gShouldDrawProc)();
+ #define CHECK_SHOULD_DRAW(draw, forceI) \
+ do { \
+ if (gShouldDrawProc && !gShouldDrawProc()) return; \
+ this->prepareDraw(draw, forceI); \
+ } while (0)
+#else
+ #define CHECK_SHOULD_DRAW(draw, forceI) this->prepareDraw(draw, forceI)
+#endif
+
+// This constant represents the screen alignment criterion in texels for
+// requiring texture domain clamping to prevent color bleeding when drawing
+// a sub region of a larger source image.
+#define COLOR_BLEED_TOLERANCE SkFloatToScalar(0.001f)
+
+#define DO_DEFERRED_CLEAR() \
+ do { \
+ if (fNeedClear) { \
+ this->clear(SK_ColorTRANSPARENT); \
+ } \
+ } while (false) \
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define CHECK_FOR_NODRAW_ANNOTATION(paint) \
+ do { if (paint.isNoDrawAnnotation()) { return; } } while (0)
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+class SkGpuDevice::SkAutoCachedTexture : public ::SkNoncopyable {
+public:
+ SkAutoCachedTexture()
+ : fDevice(NULL)
+ , fTexture(NULL) {
+ }
+
+ SkAutoCachedTexture(SkGpuDevice* device,
+ const SkBitmap& bitmap,
+ const GrTextureParams* params,
+ GrTexture** texture)
+ : fDevice(NULL)
+ , fTexture(NULL) {
+ GrAssert(NULL != texture);
+ *texture = this->set(device, bitmap, params);
+ }
+
+ ~SkAutoCachedTexture() {
+ if (NULL != fTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(fTexture);
+ }
+ }
+
+ GrTexture* set(SkGpuDevice* device,
+ const SkBitmap& bitmap,
+ const GrTextureParams* params) {
+ if (NULL != fTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(fTexture);
+ fTexture = NULL;
+ }
+ fDevice = device;
+ GrTexture* result = (GrTexture*)bitmap.getTexture();
+ if (NULL == result) {
+ // Cannot return the native texture so look it up in our cache
+ fTexture = GrLockAndRefCachedBitmapTexture(device->context(), bitmap, params);
+ result = fTexture;
+ }
+ return result;
+ }
+
+private:
+ SkGpuDevice* fDevice;
+ GrTexture* fTexture;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct GrSkDrawProcs : public SkDrawProcs {
+public:
+ GrContext* fContext;
+ GrTextContext* fTextContext;
+ GrFontScaler* fFontScaler; // cached in the skia glyphcache
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkBitmap::Config grConfig2skConfig(GrPixelConfig config, bool* isOpaque) {
+ switch (config) {
+ case kAlpha_8_GrPixelConfig:
+ *isOpaque = false;
+ return SkBitmap::kA8_Config;
+ case kRGB_565_GrPixelConfig:
+ *isOpaque = true;
+ return SkBitmap::kRGB_565_Config;
+ case kRGBA_4444_GrPixelConfig:
+ *isOpaque = false;
+ return SkBitmap::kARGB_4444_Config;
+ case kSkia8888_GrPixelConfig:
+ // we don't currently have a way of knowing whether
+ // a 8888 is opaque based on the config.
+ *isOpaque = false;
+ return SkBitmap::kARGB_8888_Config;
+ default:
+ *isOpaque = false;
+ return SkBitmap::kNo_Config;
+ }
+}
+
+static SkBitmap make_bitmap(GrContext* context, GrRenderTarget* renderTarget) {
+ GrPixelConfig config = renderTarget->config();
+
+ bool isOpaque;
+ SkBitmap bitmap;
+ bitmap.setConfig(grConfig2skConfig(config, &isOpaque),
+ renderTarget->width(), renderTarget->height());
+ bitmap.setIsOpaque(isOpaque);
+ return bitmap;
+}
+
+SkGpuDevice* SkGpuDevice::Create(GrSurface* surface) {
+ GrAssert(NULL != surface);
+ if (NULL == surface->asRenderTarget() || NULL == surface->getContext()) {
+ return NULL;
+ }
+ if (surface->asTexture()) {
+ return SkNEW_ARGS(SkGpuDevice, (surface->getContext(), surface->asTexture()));
+ } else {
+ return SkNEW_ARGS(SkGpuDevice, (surface->getContext(), surface->asRenderTarget()));
+ }
+}
+
+SkGpuDevice::SkGpuDevice(GrContext* context, GrTexture* texture)
+: SkDevice(make_bitmap(context, texture->asRenderTarget())) {
+ this->initFromRenderTarget(context, texture->asRenderTarget(), false);
+}
+
+SkGpuDevice::SkGpuDevice(GrContext* context, GrRenderTarget* renderTarget)
+: SkDevice(make_bitmap(context, renderTarget)) {
+ this->initFromRenderTarget(context, renderTarget, false);
+}
+
+void SkGpuDevice::initFromRenderTarget(GrContext* context,
+ GrRenderTarget* renderTarget,
+ bool cached) {
+ fDrawProcs = NULL;
+
+ fContext = context;
+ fContext->ref();
+
+ fRenderTarget = NULL;
+ fNeedClear = false;
+
+ GrAssert(NULL != renderTarget);
+ fRenderTarget = renderTarget;
+ fRenderTarget->ref();
+
+ // Hold onto to the texture in the pixel ref (if there is one) because the texture holds a ref
+ // on the RT but not vice-versa.
+ // TODO: Remove this trickery once we figure out how to make SkGrPixelRef do this without
+ // busting chrome (for a currently unknown reason).
+ GrSurface* surface = fRenderTarget->asTexture();
+ if (NULL == surface) {
+ surface = fRenderTarget;
+ }
+ SkPixelRef* pr = SkNEW_ARGS(SkGrPixelRef, (surface, cached));
+
+ this->setPixelRef(pr, 0)->unref();
+}
+
+SkGpuDevice::SkGpuDevice(GrContext* context,
+ SkBitmap::Config config,
+ int width,
+ int height,
+ int sampleCount)
+ : SkDevice(config, width, height, false /*isOpaque*/) {
+
+ fDrawProcs = NULL;
+
+ fContext = context;
+ fContext->ref();
+
+ fRenderTarget = NULL;
+ fNeedClear = false;
+
+ if (config != SkBitmap::kRGB_565_Config) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(config);
+ desc.fSampleCnt = sampleCount;
+
+ SkAutoTUnref<GrTexture> texture(fContext->createUncachedTexture(desc, NULL, 0));
+
+ if (NULL != texture) {
+ fRenderTarget = texture->asRenderTarget();
+ fRenderTarget->ref();
+
+ GrAssert(NULL != fRenderTarget);
+
+ // wrap the bitmap with a pixelref to expose our texture
+ SkGrPixelRef* pr = SkNEW_ARGS(SkGrPixelRef, (texture));
+ this->setPixelRef(pr, 0)->unref();
+ } else {
+ GrPrintf("--- failed to create gpu-offscreen [%d %d]\n",
+ width, height);
+ GrAssert(false);
+ }
+}
+
+SkGpuDevice::~SkGpuDevice() {
+ if (fDrawProcs) {
+ delete fDrawProcs;
+ }
+
+ // The GrContext takes a ref on the target. We don't want to cause the render
+ // target to be unnecessarily kept alive.
+ if (fContext->getRenderTarget() == fRenderTarget) {
+ fContext->setRenderTarget(NULL);
+ }
+
+ if (fContext->getClip() == &fClipData) {
+ fContext->setClip(NULL);
+ }
+
+ SkSafeUnref(fRenderTarget);
+ fContext->unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::makeRenderTargetCurrent() {
+ DO_DEFERRED_CLEAR();
+ fContext->setRenderTarget(fRenderTarget);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+GrPixelConfig config8888_to_grconfig_and_flags(SkCanvas::Config8888 config8888, uint32_t* flags) {
+ switch (config8888) {
+ case SkCanvas::kNative_Premul_Config8888:
+ *flags = 0;
+ return kSkia8888_GrPixelConfig;
+ case SkCanvas::kNative_Unpremul_Config8888:
+ *flags = GrContext::kUnpremul_PixelOpsFlag;
+ return kSkia8888_GrPixelConfig;
+ case SkCanvas::kBGRA_Premul_Config8888:
+ *flags = 0;
+ return kBGRA_8888_GrPixelConfig;
+ case SkCanvas::kBGRA_Unpremul_Config8888:
+ *flags = GrContext::kUnpremul_PixelOpsFlag;
+ return kBGRA_8888_GrPixelConfig;
+ case SkCanvas::kRGBA_Premul_Config8888:
+ *flags = 0;
+ return kRGBA_8888_GrPixelConfig;
+ case SkCanvas::kRGBA_Unpremul_Config8888:
+ *flags = GrContext::kUnpremul_PixelOpsFlag;
+ return kRGBA_8888_GrPixelConfig;
+ default:
+ GrCrash("Unexpected Config8888.");
+ *flags = 0; // suppress warning
+ return kSkia8888_GrPixelConfig;
+ }
+}
+}
+
+bool SkGpuDevice::onReadPixels(const SkBitmap& bitmap,
+ int x, int y,
+ SkCanvas::Config8888 config8888) {
+ DO_DEFERRED_CLEAR();
+ SkASSERT(SkBitmap::kARGB_8888_Config == bitmap.config());
+ SkASSERT(!bitmap.isNull());
+ SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height())));
+
+ SkAutoLockPixels alp(bitmap);
+ GrPixelConfig config;
+ uint32_t flags;
+ config = config8888_to_grconfig_and_flags(config8888, &flags);
+ return fContext->readRenderTargetPixels(fRenderTarget,
+ x, y,
+ bitmap.width(),
+ bitmap.height(),
+ config,
+ bitmap.getPixels(),
+ bitmap.rowBytes(),
+ flags);
+}
+
+void SkGpuDevice::writePixels(const SkBitmap& bitmap, int x, int y,
+ SkCanvas::Config8888 config8888) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ return;
+ }
+
+ GrPixelConfig config;
+ uint32_t flags;
+ if (SkBitmap::kARGB_8888_Config == bitmap.config()) {
+ config = config8888_to_grconfig_and_flags(config8888, &flags);
+ } else {
+ flags = 0;
+ config= SkBitmapConfig2GrPixelConfig(bitmap.config());
+ }
+
+ fRenderTarget->writePixels(x, y, bitmap.width(), bitmap.height(),
+ config, bitmap.getPixels(), bitmap.rowBytes(), flags);
+}
+
+namespace {
+void purgeClipCB(int genID, void* ) {
+
+ if (SkClipStack::kInvalidGenID == genID ||
+ SkClipStack::kEmptyGenID == genID ||
+ SkClipStack::kWideOpenGenID == genID) {
+ // none of these cases will have a cached clip mask
+ return;
+ }
+
+}
+};
+
+void SkGpuDevice::onAttachToCanvas(SkCanvas* canvas) {
+ INHERITED::onAttachToCanvas(canvas);
+
+ // Canvas promises that this ptr is valid until onDetachFromCanvas is called
+ fClipData.fClipStack = canvas->getClipStack();
+
+ fClipData.fClipStack->addPurgeClipCallback(purgeClipCB, fContext);
+}
+
+void SkGpuDevice::onDetachFromCanvas() {
+ INHERITED::onDetachFromCanvas();
+
+ // TODO: iterate through the clip stack and clean up any cached clip masks
+ fClipData.fClipStack->removePurgeClipCallback(purgeClipCB, fContext);
+
+ fClipData.fClipStack = NULL;
+}
+
+#ifdef SK_DEBUG
+static void check_bounds(const GrClipData& clipData,
+ const SkRegion& clipRegion,
+ int renderTargetWidth,
+ int renderTargetHeight) {
+
+ SkIRect devBound;
+
+ devBound.setLTRB(0, 0, renderTargetWidth, renderTargetHeight);
+
+ SkClipStack::BoundsType boundType;
+ SkRect canvTemp;
+
+ clipData.fClipStack->getBounds(&canvTemp, &boundType);
+ if (SkClipStack::kNormal_BoundsType == boundType) {
+ SkIRect devTemp;
+
+ canvTemp.roundOut(&devTemp);
+
+ devTemp.offset(-clipData.fOrigin.fX, -clipData.fOrigin.fY);
+
+ if (!devBound.intersect(devTemp)) {
+ devBound.setEmpty();
+ }
+ }
+
+ GrAssert(devBound.contains(clipRegion.getBounds()));
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// call this every draw call, to ensure that the context reflects our state,
+// and not the state from some other canvas/device
+void SkGpuDevice::prepareDraw(const SkDraw& draw, bool forceIdentity) {
+ GrAssert(NULL != fClipData.fClipStack);
+
+ fContext->setRenderTarget(fRenderTarget);
+
+ SkASSERT(draw.fClipStack && draw.fClipStack == fClipData.fClipStack);
+
+ if (forceIdentity) {
+ fContext->setIdentityMatrix();
+ } else {
+ fContext->setMatrix(*draw.fMatrix);
+ }
+ fClipData.fOrigin = this->getOrigin();
+
+#ifdef SK_DEBUG
+ check_bounds(fClipData, *draw.fClip, fRenderTarget->width(), fRenderTarget->height());
+#endif
+
+ fContext->setClip(&fClipData);
+
+ DO_DEFERRED_CLEAR();
+}
+
+GrRenderTarget* SkGpuDevice::accessRenderTarget() {
+ DO_DEFERRED_CLEAR();
+ return fRenderTarget;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_COMPILE_ASSERT(SkShader::kNone_BitmapType == 0, shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kDefault_BitmapType == 1, shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kRadial_BitmapType == 2, shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kSweep_BitmapType == 3, shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kTwoPointRadial_BitmapType == 4,
+ shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kTwoPointConical_BitmapType == 5,
+ shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kLinear_BitmapType == 6, shader_type_mismatch);
+SK_COMPILE_ASSERT(SkShader::kLast_BitmapType == 6, shader_type_mismatch);
+
+namespace {
+
+// converts a SkPaint to a GrPaint, ignoring the skPaint's shader
+// justAlpha indicates that skPaint's alpha should be used rather than the color
+// Callers may subsequently modify the GrPaint. Setting constantColor indicates
+// that the final paint will draw the same color at every pixel. This allows
+// an optimization where the the color filter can be applied to the skPaint's
+// color once while converting to GrPaint and then ignored.
+inline bool skPaint2GrPaintNoShader(SkGpuDevice* dev,
+ const SkPaint& skPaint,
+ bool justAlpha,
+ bool constantColor,
+ GrPaint* grPaint) {
+
+ grPaint->setDither(skPaint.isDither());
+ grPaint->setAntiAlias(skPaint.isAntiAlias());
+
+ SkXfermode::Coeff sm;
+ SkXfermode::Coeff dm;
+
+ SkXfermode* mode = skPaint.getXfermode();
+ GrEffectRef* xferEffect = NULL;
+ if (SkXfermode::AsNewEffectOrCoeff(mode, dev->context(), &xferEffect, &sm, &dm)) {
+ if (NULL != xferEffect) {
+ grPaint->addColorEffect(xferEffect)->unref();
+ sm = SkXfermode::kOne_Coeff;
+ dm = SkXfermode::kZero_Coeff;
+ }
+ } else {
+ //SkDEBUGCODE(SkDebugf("Unsupported xfer mode.\n");)
+#if 0
+ return false;
+#else
+ // Fall back to src-over
+ sm = SkXfermode::kOne_Coeff;
+ dm = SkXfermode::kISA_Coeff;
+#endif
+ }
+ grPaint->setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
+
+ if (justAlpha) {
+ uint8_t alpha = skPaint.getAlpha();
+ grPaint->setColor(GrColorPackRGBA(alpha, alpha, alpha, alpha));
+ // justAlpha is currently set to true only if there is a texture,
+ // so constantColor should not also be true.
+ GrAssert(!constantColor);
+ } else {
+ grPaint->setColor(SkColor2GrColor(skPaint.getColor()));
+ }
+
+ SkColorFilter* colorFilter = skPaint.getColorFilter();
+ 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<GrEffectRef> effect(colorFilter->asNewEffect(dev->context()));
+ if (NULL != effect.get()) {
+ grPaint->addColorEffect(effect);
+ } else {
+ // TODO: rewrite this using asNewEffect()
+ SkColor color;
+ SkXfermode::Mode filterMode;
+ if (colorFilter->asColorMode(&color, &filterMode)) {
+ grPaint->setXfermodeColorFilter(filterMode, SkColor2GrColor(color));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// This function is similar to skPaint2GrPaintNoShader but also converts
+// 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,
+ const SkPaint& skPaint,
+ bool constantColor,
+ GrPaint* grPaint) {
+ SkShader* shader = skPaint.getShader();
+ if (NULL == shader) {
+ return skPaint2GrPaintNoShader(dev, skPaint, false, constantColor, grPaint);
+ }
+
+ // setup the shader as the first color effect on the paint
+ SkAutoTUnref<GrEffectRef> effect(shader->asNewEffect(dev->context(), skPaint));
+ if (NULL != effect.get()) {
+ grPaint->addColorEffect(effect);
+ // Now setup the rest of the paint.
+ return skPaint2GrPaintNoShader(dev, skPaint, true, false, grPaint);
+ } else {
+ // We still don't have SkColorShader::asNewEffect() implemented.
+ SkShader::GradientInfo info;
+ SkColor color;
+
+ info.fColors = &color;
+ info.fColorOffsets = NULL;
+ info.fColorCount = 1;
+ if (SkShader::kColor_GradientType == shader->asAGradient(&info)) {
+ SkPaint copy(skPaint);
+ copy.setShader(NULL);
+ // modulate the paint alpha by the shader's solid color alpha
+ U8CPU newA = SkMulDiv255Round(SkColorGetA(color), copy.getAlpha());
+ copy.setColor(SkColorSetA(color, newA));
+ return skPaint2GrPaintNoShader(dev, copy, false, constantColor, grPaint);
+ } else {
+ return false;
+ }
+ }
+}
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void SkGpuDevice::clear(SkColor color) {
+ SkIRect rect = SkIRect::MakeWH(this->width(), this->height());
+ fContext->clear(&rect, SkColor2GrColor(color), fRenderTarget);
+ fNeedClear = false;
+}
+
+void SkGpuDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+
+ fContext->drawPaint(grPaint);
+}
+
+// must be in SkCanvas::PointMode order
+static const GrPrimitiveType gPointMode2PrimtiveType[] = {
+ kPoints_GrPrimitiveType,
+ kLines_GrPrimitiveType,
+ kLineStrip_GrPrimitiveType
+};
+
+void SkGpuDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode,
+ size_t count, const SkPoint pts[], const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ CHECK_SHOULD_DRAW(draw, false);
+
+ SkScalar width = paint.getStrokeWidth();
+ if (width < 0) {
+ return;
+ }
+
+ // we only handle hairlines and paints without path effects or mask filters,
+ // else we let the SkDraw call our drawPath()
+ if (width > 0 || paint.getPathEffect() || paint.getMaskFilter()) {
+ draw.drawPoints(mode, count, pts, paint, true);
+ return;
+ }
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+
+ fContext->drawVertices(grPaint,
+ gPointMode2PrimtiveType[mode],
+ count,
+ (GrPoint*)pts,
+ NULL,
+ NULL,
+ NULL,
+ 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::drawRect(const SkDraw& draw, const SkRect& rect,
+ const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ CHECK_SHOULD_DRAW(draw, false);
+
+ bool doStroke = paint.getStyle() != SkPaint::kFill_Style;
+ SkScalar width = paint.getStrokeWidth();
+
+ /*
+ We have special code for hairline strokes, miter-strokes, and fills.
+ Anything else we just call our path code.
+ */
+ bool usePath = doStroke && width > 0 &&
+ paint.getStrokeJoin() != SkPaint::kMiter_Join;
+ // another two reasons we might need to call drawPath...
+ if (paint.getMaskFilter() || paint.getPathEffect()) {
+ usePath = true;
+ }
+ if (!usePath && paint.isAntiAlias() && !fContext->getMatrix().rectStaysRect()) {
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ if (doStroke) {
+#endif
+ usePath = true;
+#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT)
+ } else {
+ usePath = !fContext->getMatrix().preservesRightAngles();
+ }
+#endif
+ }
+ // small miter limit means right angles show bevel...
+ if (SkPaint::kMiter_Join == paint.getStrokeJoin() &&
+ paint.getStrokeMiter() < SK_ScalarSqrt2)
+ {
+ usePath = true;
+ }
+ // until we can both stroke and fill rectangles
+ if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) {
+ usePath = true;
+ }
+
+ if (usePath) {
+ SkPath path;
+ path.addRect(rect);
+ this->drawPath(draw, path, paint, NULL, true);
+ return;
+ }
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+ fContext->drawRect(grPaint, rect, doStroke ? width : -1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::drawRRect(const SkDraw& draw, const SkRRect& rect,
+ const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ CHECK_SHOULD_DRAW(draw, false);
+
+ bool usePath = !rect.isSimple();
+ // another two reasons we might need to call drawPath...
+ if (paint.getMaskFilter() || paint.getPathEffect()) {
+ usePath = true;
+ }
+ // until we can rotate rrects...
+ if (!usePath && !fContext->getMatrix().rectStaysRect()) {
+ usePath = true;
+ }
+
+ if (usePath) {
+ SkPath path;
+ path.addRRect(rect);
+ this->drawPath(draw, path, paint, NULL, true);
+ return;
+ }
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+
+ SkStrokeRec stroke(paint);
+ fContext->drawRRect(grPaint, rect, stroke);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval,
+ const SkPaint& paint) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ CHECK_SHOULD_DRAW(draw, false);
+
+ bool usePath = false;
+ // some basic reasons we might need to call drawPath...
+ if (paint.getMaskFilter() || paint.getPathEffect()) {
+ usePath = true;
+ }
+
+ if (usePath) {
+ SkPath path;
+ path.addOval(oval);
+ this->drawPath(draw, path, paint, NULL, true);
+ return;
+ }
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+ SkStrokeRec stroke(paint);
+
+ fContext->drawOval(grPaint, oval, stroke);
+}
+
+#include "SkMaskFilter.h"
+#include "SkBounder.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+// helpers for applying mask filters
+namespace {
+
+// Draw a mask using the supplied paint. Since the coverage/geometry
+// is already burnt into the mask this boils down to a rect draw.
+// Return true if the mask was successfully drawn.
+bool draw_mask(GrContext* context, const SkRect& maskRect,
+ GrPaint* grp, GrTexture* mask) {
+ GrContext::AutoMatrix am;
+ if (!am.setIdentity(context, grp)) {
+ return false;
+ }
+
+ SkMatrix matrix;
+ matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop);
+ matrix.postIDiv(mask->width(), mask->height());
+
+ grp->addCoverageEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref();
+ context->drawRect(*grp, maskRect);
+ return true;
+}
+
+bool draw_with_mask_filter(GrContext* context, const SkPath& devPath,
+ SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder,
+ GrPaint* grp, SkPaint::Style style) {
+ SkMask srcM, dstM;
+
+ if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), filter, &context->getMatrix(), &srcM,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode, style)) {
+ return false;
+ }
+ SkAutoMaskFreeImage autoSrc(srcM.fImage);
+
+ if (!filter->filterMask(&dstM, srcM, context->getMatrix(), NULL)) {
+ return false;
+ }
+ // this will free-up dstM when we're done (allocated in filterMask())
+ SkAutoMaskFreeImage autoDst(dstM.fImage);
+
+ if (clip.quickReject(dstM.fBounds)) {
+ return false;
+ }
+ if (bounder && !bounder->doIRect(dstM.fBounds)) {
+ return false;
+ }
+
+ // we now have a device-aligned 8bit mask in dstM, ready to be drawn using
+ // the current clip (and identity matrix) and GrPaint settings
+ GrTextureDesc desc;
+ desc.fWidth = dstM.fBounds.width();
+ desc.fHeight = dstM.fBounds.height();
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ GrTexture* texture = ast.texture();
+
+ if (NULL == texture) {
+ return false;
+ }
+ texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
+ dstM.fImage, dstM.fRowBytes);
+
+ SkRect maskRect = SkRect::MakeFromIRect(dstM.fBounds);
+
+ return draw_mask(context, maskRect, grp, texture);
+}
+
+// Create a mask of 'devPath' and place the result in 'mask'. Return true on
+// success; false otherwise.
+bool create_mask_GPU(GrContext* context,
+ const SkRect& maskRect,
+ const SkPath& devPath,
+ const SkStrokeRec& stroke,
+ bool doAA,
+ GrAutoScratchTexture* mask) {
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = SkScalarCeilToInt(maskRect.width());
+ desc.fHeight = SkScalarCeilToInt(maskRect.height());
+ // We actually only need A8, but it often isn't supported as a
+ // render target so default to RGBA_8888
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+ if (context->isConfigRenderable(kAlpha_8_GrPixelConfig)) {
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+ }
+
+ mask->set(context, desc);
+ if (NULL == mask->texture()) {
+ return false;
+ }
+
+ GrTexture* maskTexture = mask->texture();
+ SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
+
+ GrContext::AutoRenderTarget art(context, maskTexture->asRenderTarget());
+ GrContext::AutoClip ac(context, clipRect);
+
+ context->clear(NULL, 0x0);
+
+ GrPaint tempPaint;
+ if (doAA) {
+ tempPaint.setAntiAlias(true);
+ // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst
+ // blend coeff of zero requires dual source blending support in order
+ // to properly blend partially covered pixels. This means the AA
+ // code path may not be taken. So we use a dst blend coeff of ISA. We
+ // could special case AA draws to a dst surface with known alpha=0 to
+ // use a zero dst coeff when dual source blending isn't available.
+ tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
+ }
+
+ GrContext::AutoMatrix am;
+
+ // Draw the mask into maskTexture with the path's top-left at the origin using tempPaint.
+ SkMatrix translate;
+ translate.setTranslate(-maskRect.fLeft, -maskRect.fTop);
+ am.set(context, translate);
+ context->drawPath(tempPaint, devPath, stroke);
+ return true;
+}
+
+SkBitmap wrap_texture(GrTexture* texture) {
+ SkBitmap result;
+ bool dummy;
+ SkBitmap::Config config = grConfig2skConfig(texture->config(), &dummy);
+ result.setConfig(config, texture->width(), texture->height());
+ result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref();
+ return result;
+}
+
+};
+
+void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) {
+ CHECK_FOR_NODRAW_ANNOTATION(paint);
+ CHECK_SHOULD_DRAW(draw, false);
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+
+ // can we cheat, and treat a thin stroke as a hairline w/ coverage
+ // if we can, we draw lots faster (raster device does this same test)
+ SkScalar hairlineCoverage;
+ bool doHairLine = SkDrawTreatAsHairline(paint, fContext->getMatrix(), &hairlineCoverage);
+ if (doHairLine) {
+ grPaint.setCoverage(SkScalarRoundToInt(hairlineCoverage * grPaint.getCoverage()));
+ }
+
+ // If we have a prematrix, apply it to the path, optimizing for the case
+ // 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, effectPath;
+
+ if (prePathMatrix) {
+ SkPath* result = pathPtr;
+
+ if (!pathIsMutable) {
+ result = &tmpPath;
+ pathIsMutable = true;
+ }
+ // should I push prePathMatrix on our MV stack temporarily, instead
+ // of applying it here? See SkDraw.cpp
+ pathPtr->transform(*prePathMatrix, result);
+ pathPtr = result;
+ }
+ // at this point we're done with prePathMatrix
+ SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
+
+ SkStrokeRec stroke(paint);
+ SkPathEffect* pathEffect = paint.getPathEffect();
+ const SkRect* cullRect = NULL; // TODO: what is our bounds?
+ if (pathEffect && pathEffect->filterPath(&effectPath, *pathPtr, &stroke,
+ cullRect)) {
+ pathPtr = &effectPath;
+ }
+
+ if (!pathEffect && doHairLine) {
+ stroke.setHairlineStyle();
+ }
+
+ if (paint.getMaskFilter()) {
+ if (!stroke.isHairlineStyle()) {
+ if (stroke.applyToPath(&tmpPath, *pathPtr)) {
+ pathPtr = &tmpPath;
+ pathIsMutable = true;
+ 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);
+
+ SkRect maskRect;
+ if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(),
+ draw.fClip->getBounds(),
+ fContext->getMatrix(),
+ &maskRect)) {
+ SkIRect finalIRect;
+ maskRect.roundOut(&finalIRect);
+ if (draw.fClip->quickReject(finalIRect)) {
+ // clipped out
+ return;
+ }
+ if (NULL != draw.fBounder && !draw.fBounder->doIRect(finalIRect)) {
+ // nothing to draw
+ return;
+ }
+
+ GrAutoScratchTexture mask;
+
+ if (create_mask_GPU(fContext, maskRect, *devPathPtr, stroke,
+ grPaint.isAntiAlias(), &mask)) {
+ GrTexture* filtered;
+
+ if (paint.getMaskFilter()->filterMaskGPU(mask.texture(), maskRect, &filtered, true)) {
+ SkAutoTUnref<GrTexture> atu(filtered);
+
+ if (draw_mask(fContext, maskRect, &grPaint, filtered)) {
+ // This path is completely drawn
+ return;
+ }
+ }
+ }
+ }
+
+ // draw the mask on the CPU - this is a fallthrough path in case the
+ // GPU path fails
+ SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style :
+ SkPaint::kFill_Style;
+ draw_with_mask_filter(fContext, *devPathPtr, paint.getMaskFilter(),
+ *draw.fClip, draw.fBounder, &grPaint, style);
+ return;
+ }
+
+ fContext->drawPath(grPaint, *pathPtr, stroke);
+}
+
+namespace {
+
+inline int get_tile_count(int l, int t, int r, int b, int tileSize) {
+ int tilesX = (r / tileSize) - (l / tileSize) + 1;
+ int tilesY = (b / tileSize) - (t / tileSize) + 1;
+ return tilesX * tilesY;
+}
+
+inline int determine_tile_size(const SkBitmap& bitmap,
+ const SkRect& src,
+ int maxTextureSize) {
+ static const int kSmallTileSize = 1 << 10;
+ if (maxTextureSize <= kSmallTileSize) {
+ return maxTextureSize;
+ }
+
+ size_t maxTexTotalTileSize;
+ size_t smallTotalTileSize;
+
+ SkIRect iSrc;
+ src.roundOut(&iSrc);
+
+ maxTexTotalTileSize = get_tile_count(iSrc.fLeft,
+ iSrc.fTop,
+ iSrc.fRight,
+ iSrc.fBottom,
+ maxTextureSize);
+ smallTotalTileSize = get_tile_count(iSrc.fLeft,
+ iSrc.fTop,
+ iSrc.fRight,
+ iSrc.fBottom,
+ kSmallTileSize);
+
+ maxTexTotalTileSize *= maxTextureSize * maxTextureSize;
+ smallTotalTileSize *= kSmallTileSize * kSmallTileSize;
+
+ if (maxTexTotalTileSize > 2 * smallTotalTileSize) {
+ return kSmallTileSize;
+ } else {
+ return maxTextureSize;
+ }
+}
+}
+
+bool SkGpuDevice::shouldTileBitmap(const SkBitmap& bitmap,
+ const GrTextureParams& params,
+ const SkRect* srcRectPtr) const {
+ // if bitmap is explictly texture backed then just use the texture
+ if (NULL != bitmap.getTexture()) {
+ return false;
+ }
+ // if it's larger than the max texture size, then we have no choice but
+ // tiling
+ const int maxTextureSize = fContext->getMaxTextureSize();
+ if (bitmap.width() > maxTextureSize ||
+ bitmap.height() > maxTextureSize) {
+ return true;
+ }
+ // if we are going to have to draw the whole thing, then don't tile
+ if (NULL == srcRectPtr) {
+ return false;
+ }
+ // if the entire texture is already in our cache then no reason to tile it
+ if (GrIsBitmapInCache(fContext, bitmap, &params)) {
+ return false;
+ }
+
+ // At this point we know we could do the draw by uploading the entire bitmap
+ // as a texture. However, if the texture would be large compared to the
+ // cache size and we don't require most of it for this draw then tile to
+ // reduce the amount of upload and cache spill.
+
+ // assumption here is that sw bitmap size is a good proxy for its size as
+ // a texture
+ size_t bmpSize = bitmap.getSize();
+ size_t cacheSize;
+ fContext->getTextureCacheLimits(NULL, &cacheSize);
+ if (bmpSize < cacheSize / 2) {
+ return false;
+ }
+
+ SkScalar fracUsed = SkScalarMul(srcRectPtr->width() / bitmap.width(),
+ srcRectPtr->height() / bitmap.height());
+ if (fracUsed <= SK_ScalarHalf) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void SkGpuDevice::drawBitmap(const SkDraw& draw,
+ const SkBitmap& bitmap,
+ const SkMatrix& m,
+ const SkPaint& paint) {
+ // We cannot call drawBitmapRect here since 'm' could be anything
+ this->drawBitmapCommon(draw, bitmap, NULL, m, paint);
+}
+
+void SkGpuDevice::drawBitmapCommon(const SkDraw& draw,
+ const SkBitmap& bitmap,
+ const SkRect* srcRectPtr,
+ const SkMatrix& m,
+ const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ SkRect srcRect;
+ if (NULL == srcRectPtr) {
+ srcRect.set(0, 0, SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
+ } else {
+ srcRect = *srcRectPtr;
+ }
+
+ if (paint.getMaskFilter()){
+ // Convert the bitmap to a shader so that the rect can be drawn
+ // through drawRect, which supports mask filters.
+ SkMatrix newM(m);
+ SkBitmap tmp; // subset of bitmap, if necessary
+ const SkBitmap* bitmapPtr = &bitmap;
+ if (NULL != srcRectPtr) {
+ SkIRect iSrc;
+ srcRect.roundOut(&iSrc);
+ if (!bitmap.extractSubset(&tmp, iSrc)) {
+ return; // extraction failed
+ }
+ bitmapPtr = &tmp;
+ srcRect.offset(SkIntToScalar(-iSrc.fLeft), SkIntToScalar(-iSrc.fTop));
+ // The source rect has changed so update the matrix
+ newM.preTranslate(SkIntToScalar(iSrc.fLeft), SkIntToScalar(iSrc.fTop));
+ }
+
+ SkPaint paintWithTexture(paint);
+ paintWithTexture.setShader(SkShader::CreateBitmapShader(*bitmapPtr,
+ SkShader::kClamp_TileMode, SkShader::kClamp_TileMode))->unref();
+
+ // Transform 'newM' needs to be concatenated to the current matrix,
+ // rather than transforming the primitive directly, so that 'newM' will
+ // also affect the behavior of the mask filter.
+ SkMatrix drawMatrix;
+ drawMatrix.setConcat(fContext->getMatrix(), newM);
+ SkDraw transformedDraw(draw);
+ transformedDraw.fMatrix = &drawMatrix;
+
+ this->drawRect(transformedDraw, srcRect, paintWithTexture);
+
+ return;
+ }
+
+ GrTextureParams params;
+ SkPaint::FilterLevel paintFilterLevel = paint.getFilterLevel();
+ GrTextureParams::FilterMode textureFilterMode;
+ switch(paintFilterLevel) {
+ case SkPaint::kNone_FilterLevel:
+ textureFilterMode = GrTextureParams::kNone_FilterMode;
+ break;
+ case SkPaint::kLow_FilterLevel:
+ textureFilterMode = GrTextureParams::kBilerp_FilterMode;
+ break;
+ case SkPaint::kMedium_FilterLevel:
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+ case SkPaint::kHigh_FilterLevel:
+ SkErrorInternals::SetError( kInvalidPaint_SkError,
+ "Sorry, I don't yet support high quality "
+ "filtering on the GPU. Falling back to "
+ "MIPMaps.");
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+ default:
+ SkErrorInternals::SetError( kInvalidPaint_SkError,
+ "Sorry, I don't understand the filtering "
+ "mode you asked for. Falling back to "
+ "MIPMaps.");
+ textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+ break;
+
+ }
+
+ params.setFilterMode(textureFilterMode);
+
+ if (!this->shouldTileBitmap(bitmap, params, srcRectPtr)) {
+ // take the simple case
+ this->internalDrawBitmap(bitmap, srcRect, m, params, paint);
+ } else {
+ this->drawTiledBitmap(bitmap, srcRect, m, params, paint);
+ }
+}
+
+// Break 'bitmap' into several tiles to draw it since it has already
+// been determined to be too large to fit in VRAM
+void SkGpuDevice::drawTiledBitmap(const SkBitmap& bitmap,
+ const SkRect& srcRect,
+ const SkMatrix& m,
+ const GrTextureParams& params,
+ const SkPaint& paint) {
+ const int maxTextureSize = fContext->getMaxTextureSize();
+
+ int tileSize = determine_tile_size(bitmap, srcRect, maxTextureSize);
+
+ // compute clip bounds in local coordinates
+ SkRect clipRect;
+ {
+ const GrRenderTarget* rt = fContext->getRenderTarget();
+ clipRect.setWH(SkIntToScalar(rt->width()), SkIntToScalar(rt->height()));
+ if (!fContext->getClip()->fClipStack->intersectRectWithClip(&clipRect)) {
+ return;
+ }
+ SkMatrix matrix, inverse;
+ matrix.setConcat(fContext->getMatrix(), m);
+ if (!matrix.invert(&inverse)) {
+ return;
+ }
+ inverse.mapRect(&clipRect);
+ }
+
+ int nx = bitmap.width() / tileSize;
+ int ny = bitmap.height() / tileSize;
+ for (int x = 0; x <= nx; x++) {
+ for (int y = 0; y <= ny; y++) {
+ SkRect tileR;
+ tileR.set(SkIntToScalar(x * tileSize),
+ SkIntToScalar(y * tileSize),
+ SkIntToScalar((x + 1) * tileSize),
+ SkIntToScalar((y + 1) * tileSize));
+
+ if (!SkRect::Intersects(tileR, clipRect)) {
+ continue;
+ }
+
+ if (!tileR.intersect(srcRect)) {
+ continue;
+ }
+
+ SkBitmap tmpB;
+ SkIRect iTileR;
+ tileR.roundOut(&iTileR);
+ if (bitmap.extractSubset(&tmpB, iTileR)) {
+ // now offset it to make it "local" to our tmp bitmap
+ tileR.offset(SkIntToScalar(-iTileR.fLeft), SkIntToScalar(-iTileR.fTop));
+ SkMatrix tmpM(m);
+ tmpM.preTranslate(SkIntToScalar(iTileR.fLeft),
+ SkIntToScalar(iTileR.fTop));
+
+ this->internalDrawBitmap(tmpB, tileR, tmpM, params, paint);
+ }
+ }
+ }
+}
+
+static bool has_aligned_samples(const SkRect& srcRect,
+ const SkRect& transformedRect) {
+ // detect pixel disalignment
+ if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) -
+ transformedRect.left()) < COLOR_BLEED_TOLERANCE &&
+ SkScalarAbs(SkScalarRoundToScalar(transformedRect.top()) -
+ transformedRect.top()) < COLOR_BLEED_TOLERANCE &&
+ SkScalarAbs(transformedRect.width() - srcRect.width()) <
+ COLOR_BLEED_TOLERANCE &&
+ SkScalarAbs(transformedRect.height() - srcRect.height()) <
+ COLOR_BLEED_TOLERANCE) {
+ return true;
+ }
+ return false;
+}
+
+static bool may_color_bleed(const SkRect& srcRect,
+ const SkRect& transformedRect,
+ const SkMatrix& m) {
+ // Only gets called if has_aligned_samples returned false.
+ // So we can assume that sampling is axis aligned but not texel aligned.
+ GrAssert(!has_aligned_samples(srcRect, transformedRect));
+ SkRect innerSrcRect(srcRect), innerTransformedRect,
+ outerTransformedRect(transformedRect);
+ innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf);
+ m.mapRect(&innerTransformedRect, innerSrcRect);
+
+ // The gap between outerTransformedRect and innerTransformedRect
+ // represents the projection of the source border area, which is
+ // problematic for color bleeding. We must check whether any
+ // destination pixels sample the border area.
+ outerTransformedRect.inset(COLOR_BLEED_TOLERANCE, COLOR_BLEED_TOLERANCE);
+ innerTransformedRect.outset(COLOR_BLEED_TOLERANCE, COLOR_BLEED_TOLERANCE);
+ SkIRect outer, inner;
+ outerTransformedRect.round(&outer);
+ innerTransformedRect.round(&inner);
+ // If the inner and outer rects round to the same result, it means the
+ // border does not overlap any pixel centers. Yay!
+ return inner != outer;
+}
+
+
+/*
+ * This is called by drawBitmap(), which has to handle images that may be too
+ * large to be represented by a single texture.
+ *
+ * internalDrawBitmap assumes that the specified bitmap will fit in a texture
+ * and that non-texture portion of the GrPaint has already been setup.
+ */
+void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap,
+ const SkRect& srcRect,
+ const SkMatrix& m,
+ const GrTextureParams& params,
+ const SkPaint& paint) {
+ SkASSERT(bitmap.width() <= fContext->getMaxTextureSize() &&
+ bitmap.height() <= fContext->getMaxTextureSize());
+
+ GrTexture* texture;
+ SkAutoCachedTexture act(this, bitmap, &params, &texture);
+ if (NULL == texture) {
+ return;
+ }
+
+ SkRect dstRect(srcRect);
+ SkRect paintRect;
+ SkScalar wInv = SkScalarInvert(SkIntToScalar(bitmap.width()));
+ SkScalar hInv = SkScalarInvert(SkIntToScalar(bitmap.height()));
+ paintRect.setLTRB(SkScalarMul(srcRect.fLeft, wInv),
+ SkScalarMul(srcRect.fTop, hInv),
+ SkScalarMul(srcRect.fRight, wInv),
+ SkScalarMul(srcRect.fBottom, hInv));
+
+ bool needsTextureDomain = false;
+ if (params.filterMode() != GrTextureParams::kNone_FilterMode) {
+ // Need texture domain if drawing a sub rect.
+ needsTextureDomain = srcRect.width() < bitmap.width() ||
+ srcRect.height() < bitmap.height();
+ if (needsTextureDomain && m.rectStaysRect() && fContext->getMatrix().rectStaysRect()) {
+ // sampling is axis-aligned
+ SkRect transformedRect;
+ SkMatrix srcToDeviceMatrix(m);
+ srcToDeviceMatrix.postConcat(fContext->getMatrix());
+ srcToDeviceMatrix.mapRect(&transformedRect, srcRect);
+
+ if (has_aligned_samples(srcRect, transformedRect)) {
+ // We could also turn off filtering here (but we already did a cache lookup with
+ // params).
+ needsTextureDomain = false;
+ } else {
+ needsTextureDomain = may_color_bleed(srcRect, transformedRect, m);
+ }
+ }
+ }
+
+ SkRect textureDomain = SkRect::MakeEmpty();
+ SkAutoTUnref<GrEffectRef> effect;
+ if (needsTextureDomain) {
+ // Use a constrained texture domain to avoid color bleeding
+ 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 = SkScalarHalf(paintRect.left() + paintRect.right());
+ }
+ if (srcRect.height() > SK_Scalar1) {
+ SkScalar border = SK_ScalarHalf / bitmap.height();
+ top = paintRect.top() + border;
+ bottom = paintRect.bottom() - border;
+ } else {
+ top = bottom = SkScalarHalf(paintRect.top() + paintRect.bottom());
+ }
+ textureDomain.setLTRB(left, top, right, bottom);
+ effect.reset(GrTextureDomainEffect::Create(texture,
+ SkMatrix::I(),
+ textureDomain,
+ GrTextureDomainEffect::kClamp_WrapMode,
+ params.filterMode()));
+ } else {
+ effect.reset(GrSimpleTextureEffect::Create(texture, SkMatrix::I(), params));
+ }
+
+ // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring
+ // the rest from the SkPaint.
+ GrPaint grPaint;
+ grPaint.addColorEffect(effect);
+ bool alphaOnly = !(SkBitmap::kA8_Config == bitmap.config());
+ if (!skPaint2GrPaintNoShader(this, paint, alphaOnly, false, &grPaint)) {
+ return;
+ }
+
+ fContext->drawRectToRect(grPaint, dstRect, paintRect, &m);
+}
+
+static bool filter_texture(SkDevice* device, GrContext* context,
+ GrTexture* texture, SkImageFilter* filter,
+ int w, int h, const SkMatrix& ctm, SkBitmap* result,
+ SkIPoint* offset) {
+ GrAssert(filter);
+ SkDeviceImageFilterProxy proxy(device);
+
+ 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);
+ return filter->filterImageGPU(&proxy, wrap_texture(texture), ctm, result, offset);
+ } else {
+ return false;
+ }
+}
+
+void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
+ int left, int top, const SkPaint& paint) {
+ // drawSprite is defined to be in device coords.
+ CHECK_SHOULD_DRAW(draw, true);
+
+ SkAutoLockPixels alp(bitmap, !bitmap.getTexture());
+ if (!bitmap.getTexture() && !bitmap.readyToDraw()) {
+ return;
+ }
+
+ int w = bitmap.width();
+ int h = bitmap.height();
+
+ GrTexture* texture;
+ // draw sprite uses the default texture params
+ SkAutoCachedTexture act(this, bitmap, NULL, &texture);
+
+ SkImageFilter* filter = paint.getImageFilter();
+ SkIPoint offset = SkIPoint::Make(left, top);
+ // This bitmap will own the filtered result as a texture.
+ SkBitmap filteredBitmap;
+
+ if (NULL != filter) {
+ if (filter_texture(this, fContext, texture, filter, w, h, SkMatrix::I(), &filteredBitmap,
+ &offset)) {
+ texture = (GrTexture*) filteredBitmap.getTexture();
+ w = filteredBitmap.width();
+ h = filteredBitmap.height();
+ } else {
+ return;
+ }
+ }
+
+ GrPaint grPaint;
+ grPaint.addColorTextureEffect(texture, SkMatrix::I());
+
+ if(!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
+ return;
+ }
+
+ fContext->drawRectToRect(grPaint,
+ SkRect::MakeXYWH(SkIntToScalar(offset.fX),
+ SkIntToScalar(offset.fY),
+ SkIntToScalar(w),
+ SkIntToScalar(h)),
+ SkRect::MakeXYWH(0,
+ 0,
+ SK_Scalar1 * w / texture->width(),
+ SK_Scalar1 * h / texture->height()));
+}
+
+void SkGpuDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
+ const SkRect* src, const SkRect& dst,
+ const SkPaint& paint) {
+ SkMatrix matrix;
+ SkRect bitmapBounds, tmpSrc;
+
+ bitmapBounds.set(0, 0,
+ SkIntToScalar(bitmap.width()),
+ SkIntToScalar(bitmap.height()));
+
+ // Compute matrix from the two rectangles
+ if (NULL != src) {
+ tmpSrc = *src;
+ } else {
+ tmpSrc = bitmapBounds;
+ }
+ matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
+
+ // clip the tmpSrc to the bounds of the bitmap. No check needed if src==null.
+ if (NULL != src) {
+ if (!bitmapBounds.contains(tmpSrc)) {
+ if (!tmpSrc.intersect(bitmapBounds)) {
+ return; // nothing to draw
+ }
+ }
+ }
+
+ this->drawBitmapCommon(draw, bitmap, &tmpSrc, matrix, paint);
+}
+
+void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* device,
+ int x, int y, const SkPaint& paint) {
+ // clear of the source device must occur before CHECK_SHOULD_DRAW
+ SkGpuDevice* dev = static_cast<SkGpuDevice*>(device);
+ if (dev->fNeedClear) {
+ // TODO: could check here whether we really need to draw at all
+ dev->clear(0x0);
+ }
+
+ // drawDevice is defined to be in device coords.
+ CHECK_SHOULD_DRAW(draw, true);
+
+ GrRenderTarget* devRT = device->accessRenderTarget();
+ GrTexture* devTex;
+ if (NULL == (devTex = devRT->asTexture())) {
+ return;
+ }
+
+ const SkBitmap& bm = dev->accessBitmap(false);
+ int w = bm.width();
+ int h = bm.height();
+
+ SkImageFilter* filter = paint.getImageFilter();
+ // This bitmap will own the filtered result as a texture.
+ SkBitmap filteredBitmap;
+
+ if (NULL != filter) {
+ SkIPoint offset = SkIPoint::Make(0, 0);
+ if (filter_texture(this, fContext, devTex, filter, w, h, SkMatrix::I(), &filteredBitmap,
+ &offset)) {
+ devTex = filteredBitmap.getTexture();
+ w = filteredBitmap.width();
+ h = filteredBitmap.height();
+ x += offset.fX;
+ y += offset.fY;
+ } else {
+ return;
+ }
+ }
+
+ GrPaint grPaint;
+ grPaint.addColorTextureEffect(devTex, SkMatrix::I());
+
+ if (!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
+ return;
+ }
+
+ SkRect dstRect = SkRect::MakeXYWH(SkIntToScalar(x),
+ SkIntToScalar(y),
+ SkIntToScalar(w),
+ SkIntToScalar(h));
+
+ // The device being drawn may not fill up its texture (e.g. saveLayer uses approximate
+ // scratch texture).
+ SkRect srcRect = SkRect::MakeWH(SK_Scalar1 * w / devTex->width(),
+ SK_Scalar1 * h / devTex->height());
+
+ fContext->drawRectToRect(grPaint, dstRect, srcRect);
+}
+
+bool SkGpuDevice::canHandleImageFilter(SkImageFilter* filter) {
+ return filter->canFilterImageGPU();
+}
+
+bool SkGpuDevice::filterImage(SkImageFilter* filter, const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ // want explicitly our impl, so guard against a subclass of us overriding it
+ if (!this->SkGpuDevice::canHandleImageFilter(filter)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src, !src.getTexture());
+ if (!src.getTexture() && !src.readyToDraw()) {
+ return false;
+ }
+
+ GrTexture* texture;
+ // We assume here that the filter will not attempt to tile the src. Otherwise, this cache lookup
+ // must be pushed upstack.
+ SkAutoCachedTexture act(this, src, NULL, &texture);
+
+ return filter_texture(this, fContext, texture, filter, src.width(), src.height(), ctm, result,
+ offset);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// must be in SkCanvas::VertexMode order
+static const GrPrimitiveType gVertexMode2PrimitiveType[] = {
+ kTriangles_GrPrimitiveType,
+ kTriangleStrip_GrPrimitiveType,
+ kTriangleFan_GrPrimitiveType,
+};
+
+void SkGpuDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode,
+ int vertexCount, const SkPoint vertices[],
+ const SkPoint texs[], const SkColor colors[],
+ SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ GrPaint grPaint;
+ // we ignore the shader if texs is null.
+ if (NULL == texs) {
+ if (!skPaint2GrPaintNoShader(this, paint, false, NULL == colors, &grPaint)) {
+ return;
+ }
+ } else {
+ if (!skPaint2GrPaintShader(this, paint, NULL == colors, &grPaint)) {
+ return;
+ }
+ }
+
+ if (NULL != xmode && NULL != texs && NULL != colors) {
+ if (!SkXfermode::IsMode(xmode, SkXfermode::kModulate_Mode)) {
+ SkDebugf("Unsupported vertex-color/texture xfer mode.\n");
+#if 0
+ return
+#endif
+ }
+ }
+
+ SkAutoSTMalloc<128, GrColor> convertedColors(0);
+ if (NULL != colors) {
+ // need to convert byte order and from non-PM to PM
+ convertedColors.reset(vertexCount);
+ for (int i = 0; i < vertexCount; ++i) {
+ convertedColors[i] = SkColor2GrColor(colors[i]);
+ }
+ colors = convertedColors.get();
+ }
+ fContext->drawVertices(grPaint,
+ gVertexMode2PrimitiveType[vmode],
+ vertexCount,
+ (GrPoint*) vertices,
+ (GrPoint*) texs,
+ colors,
+ indices,
+ indexCount);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void GlyphCacheAuxProc(void* data) {
+ GrFontScaler* scaler = (GrFontScaler*)data;
+ SkSafeUnref(scaler);
+}
+
+static GrFontScaler* get_gr_font_scaler(SkGlyphCache* cache) {
+ void* auxData;
+ GrFontScaler* scaler = NULL;
+ if (cache->getAuxProcData(GlyphCacheAuxProc, &auxData)) {
+ scaler = (GrFontScaler*)auxData;
+ }
+ if (NULL == scaler) {
+ scaler = SkNEW_ARGS(SkGrFontScaler, (cache));
+ cache->setAuxProc(GlyphCacheAuxProc, scaler);
+ }
+ return scaler;
+}
+
+static void SkGPU_Draw1Glyph(const SkDraw1Glyph& state,
+ SkFixed fx, SkFixed fy,
+ const SkGlyph& glyph) {
+ SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0);
+
+ GrSkDrawProcs* procs = static_cast<GrSkDrawProcs*>(state.fDraw->fProcs);
+
+ if (NULL == procs->fFontScaler) {
+ procs->fFontScaler = get_gr_font_scaler(state.fCache);
+ }
+
+ procs->fTextContext->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
+ glyph.getSubXFixed(),
+ glyph.getSubYFixed()),
+ SkFixedFloorToFixed(fx),
+ SkFixedFloorToFixed(fy),
+ procs->fFontScaler);
+}
+
+SkDrawProcs* SkGpuDevice::initDrawForText(GrTextContext* context) {
+
+ // deferred allocation
+ if (NULL == fDrawProcs) {
+ fDrawProcs = SkNEW(GrSkDrawProcs);
+ fDrawProcs->fD1GProc = SkGPU_Draw1Glyph;
+ fDrawProcs->fContext = fContext;
+ }
+
+ // init our (and GL's) state
+ fDrawProcs->fTextContext = context;
+ fDrawProcs->fFontScaler = NULL;
+ return fDrawProcs;
+}
+
+void SkGpuDevice::drawText(const SkDraw& draw, const void* text,
+ size_t byteLength, SkScalar x, SkScalar y,
+ const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ if (fContext->getMatrix().hasPerspective()) {
+ // this guy will just call our drawPath()
+ draw.drawText((const char*)text, byteLength, x, y, paint);
+ } else {
+ SkDraw myDraw(draw);
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+ GrTextContext context(fContext, grPaint);
+ myDraw.fProcs = this->initDrawForText(&context);
+ this->INHERITED::drawText(myDraw, text, byteLength, x, y, paint);
+ }
+}
+
+void SkGpuDevice::drawPosText(const SkDraw& draw, const void* text,
+ size_t byteLength, const SkScalar pos[],
+ SkScalar constY, int scalarsPerPos,
+ const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ if (fContext->getMatrix().hasPerspective()) {
+ // this guy will just call our drawPath()
+ draw.drawPosText((const char*)text, byteLength, pos, constY,
+ scalarsPerPos, paint);
+ } else {
+ SkDraw myDraw(draw);
+
+ GrPaint grPaint;
+ if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) {
+ return;
+ }
+ GrTextContext context(fContext, grPaint);
+ myDraw.fProcs = this->initDrawForText(&context);
+ this->INHERITED::drawPosText(myDraw, text, byteLength, pos, constY,
+ scalarsPerPos, paint);
+ }
+}
+
+void SkGpuDevice::drawTextOnPath(const SkDraw& draw, const void* text,
+ size_t len, const SkPath& path,
+ const SkMatrix* m, const SkPaint& paint) {
+ CHECK_SHOULD_DRAW(draw, false);
+
+ SkASSERT(draw.fDevice == this);
+ draw.drawTextOnPath((const char*)text, len, path, m, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkGpuDevice::filterTextFlags(const SkPaint& paint, TextFlags* flags) {
+ if (!paint.isLCDRenderText()) {
+ // we're cool with the paint as is
+ return false;
+ }
+
+ if (paint.getShader() ||
+ paint.getXfermode() || // unless its srcover
+ paint.getMaskFilter() ||
+ paint.getRasterizer() ||
+ paint.getColorFilter() ||
+ paint.getPathEffect() ||
+ paint.isFakeBoldText() ||
+ paint.getStyle() != SkPaint::kFill_Style) {
+ // turn off lcd
+ flags->fFlags = paint.getFlags() & ~SkPaint::kLCDRenderText_Flag;
+ flags->fHinting = paint.getHinting();
+ return true;
+ }
+ // we're cool with the paint as is
+ return false;
+}
+
+void SkGpuDevice::flush() {
+ DO_DEFERRED_CLEAR();
+ fContext->resolveRenderTarget(fRenderTarget);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDevice* SkGpuDevice::onCreateCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque,
+ Usage usage) {
+ GrTextureDesc desc;
+ desc.fConfig = fRenderTarget->config();
+ desc.fFlags = kRenderTarget_GrTextureFlagBit;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fSampleCnt = fRenderTarget->numSamples();
+
+ SkAutoTUnref<GrTexture> texture;
+ // Skia's convention is to only clear a device if it is non-opaque.
+ bool needClear = !isOpaque;
+
+#if CACHE_COMPATIBLE_DEVICE_TEXTURES
+ // layers are never draw in repeat modes, so we can request an approx
+ // match and ignore any padding.
+ const GrContext::ScratchTexMatch match = (kSaveLayer_Usage == usage) ?
+ GrContext::kApprox_ScratchTexMatch :
+ GrContext::kExact_ScratchTexMatch;
+ texture.reset(fContext->lockAndRefScratchTexture(desc, match));
+#else
+ texture.reset(fContext->createUncachedTexture(desc, NULL, 0));
+#endif
+ if (NULL != texture.get()) {
+ return SkNEW_ARGS(SkGpuDevice,(fContext, texture, needClear));
+ } else {
+ GrPrintf("---- failed to create compatible device texture [%d %d]\n", width, height);
+ return NULL;
+ }
+}
+
+SkGpuDevice::SkGpuDevice(GrContext* context,
+ GrTexture* texture,
+ bool needClear)
+ : SkDevice(make_bitmap(context, texture->asRenderTarget())) {
+
+ GrAssert(texture && texture->asRenderTarget());
+ // This constructor is called from onCreateCompatibleDevice. It has locked the RT in the texture
+ // cache. We pass true for the third argument so that it will get unlocked.
+ this->initFromRenderTarget(context, texture->asRenderTarget(), true);
+ fNeedClear = needClear;
+}
diff --git a/gpu/SkGr.cpp b/gpu/SkGr.cpp
new file mode 100644
index 00000000..4935d8ff
--- /dev/null
+++ b/gpu/SkGr.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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 "SkGr.h"
+
+/* Fill out buffer with the compressed format Ganesh expects from a colortable
+ based bitmap. [palette (colortable) + indices].
+
+ At the moment Ganesh only supports 8bit version. If Ganesh allowed we others
+ we could detect that the colortable.count is <= 16, and then repack the
+ indices as nibbles to save RAM, but it would take more time (i.e. a lot
+ slower than memcpy), so skipping that for now.
+
+ Ganesh wants a full 256 palette entry, even though Skia's ctable is only as big
+ as the colortable.count says it is.
+ */
+static void build_compressed_data(void* buffer, const SkBitmap& bitmap) {
+ SkASSERT(SkBitmap::kIndex8_Config == bitmap.config());
+
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ SkDEBUGFAIL("bitmap not ready to draw!");
+ return;
+ }
+
+ SkColorTable* ctable = bitmap.getColorTable();
+ char* dst = (char*)buffer;
+
+ memcpy(dst, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
+ ctable->unlockColors(false);
+
+ // always skip a full 256 number of entries, even if we memcpy'd fewer
+ dst += kGrColorTableSize;
+
+ if ((unsigned)bitmap.width() == bitmap.rowBytes()) {
+ memcpy(dst, bitmap.getPixels(), bitmap.getSize());
+ } else {
+ // need to trim off the extra bytes per row
+ size_t width = bitmap.width();
+ size_t rowBytes = bitmap.rowBytes();
+ const char* src = (const char*)bitmap.getPixels();
+ for (int y = 0; y < bitmap.height(); y++) {
+ memcpy(dst, src, width);
+ src += rowBytes;
+ dst += width;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void generate_bitmap_cache_id(const SkBitmap& bitmap, GrCacheID* id) {
+ // Our id includes the offset, width, and height so that bitmaps created by extractSubset()
+ // are unique.
+ uint32_t genID = bitmap.getGenerationID();
+ size_t offset = bitmap.pixelRefOffset();
+ int16_t width = static_cast<int16_t>(bitmap.width());
+ int16_t height = static_cast<int16_t>(bitmap.height());
+
+ GrCacheID::Key key;
+ memcpy(key.fData8, &genID, 4);
+ memcpy(key.fData8 + 4, &width, 2);
+ memcpy(key.fData8 + 6, &height, 2);
+ memcpy(key.fData8 + 8, &offset, sizeof(size_t));
+ static const size_t kKeyDataSize = 8 + sizeof(size_t);
+ memset(key.fData8 + kKeyDataSize, 0, sizeof(key) - kKeyDataSize);
+ GR_STATIC_ASSERT(sizeof(key) >= 8 + sizeof(size_t));
+ static const GrCacheID::Domain gBitmapTextureDomain = GrCacheID::GenerateDomain();
+ id->reset(gBitmapTextureDomain, key);
+}
+
+static void generate_bitmap_texture_desc(const SkBitmap& bitmap, GrTextureDesc* desc) {
+ desc->fFlags = kNone_GrTextureFlags;
+ desc->fWidth = bitmap.width();
+ desc->fHeight = bitmap.height();
+ desc->fConfig = SkBitmapConfig2GrPixelConfig(bitmap.config());
+ desc->fSampleCnt = 0;
+}
+
+static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx,
+ bool cache,
+ const GrTextureParams* params,
+ const SkBitmap& origBitmap) {
+ SkBitmap tmpBitmap;
+
+ const SkBitmap* bitmap = &origBitmap;
+
+ GrTextureDesc desc;
+ generate_bitmap_texture_desc(*bitmap, &desc);
+
+ if (SkBitmap::kIndex8_Config == bitmap->config()) {
+ // build_compressed_data doesn't do npot->pot expansion
+ // and paletted textures can't be sub-updated
+ if (ctx->supportsIndex8PixelConfig(params, bitmap->width(), bitmap->height())) {
+ size_t imagesize = bitmap->width() * bitmap->height() + kGrColorTableSize;
+ SkAutoMalloc storage(imagesize);
+
+ build_compressed_data(storage.get(), origBitmap);
+
+ // our compressed data will be trimmed, so pass width() for its
+ // "rowBytes", since they are the same now.
+
+ if (cache) {
+ GrCacheID cacheID;
+ generate_bitmap_cache_id(origBitmap, &cacheID);
+ return ctx->createTexture(params, desc, cacheID, storage.get(), bitmap->width());
+ } else {
+ GrTexture* result = ctx->lockAndRefScratchTexture(desc,
+ GrContext::kExact_ScratchTexMatch);
+ result->writePixels(0, 0, bitmap->width(),
+ bitmap->height(), desc.fConfig,
+ storage.get());
+ return result;
+ }
+ } else {
+ origBitmap.copyTo(&tmpBitmap, SkBitmap::kARGB_8888_Config);
+ // now bitmap points to our temp, which has been promoted to 32bits
+ bitmap = &tmpBitmap;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(bitmap->config());
+ }
+ }
+
+ SkAutoLockPixels alp(*bitmap);
+ if (!bitmap->readyToDraw()) {
+ return NULL;
+ }
+ if (cache) {
+ // This texture is likely to be used again so leave it in the cache
+ GrCacheID cacheID;
+ generate_bitmap_cache_id(origBitmap, &cacheID);
+ return ctx->createTexture(params, desc, cacheID, bitmap->getPixels(), bitmap->rowBytes());
+ } else {
+ // This texture is unlikely to be used again (in its present form) so
+ // just use a scratch texture. This will remove the texture from the
+ // cache so no one else can find it. Additionally, once unlocked, the
+ // scratch texture will go to the end of the list for purging so will
+ // likely be available for this volatile bitmap the next time around.
+ GrTexture* result = ctx->lockAndRefScratchTexture(desc, GrContext::kExact_ScratchTexMatch);
+ result->writePixels(0, 0,
+ bitmap->width(), bitmap->height(),
+ desc.fConfig,
+ bitmap->getPixels(),
+ bitmap->rowBytes());
+ return result;
+ }
+}
+
+bool GrIsBitmapInCache(const GrContext* ctx,
+ const SkBitmap& bitmap,
+ const GrTextureParams* params) {
+ GrCacheID cacheID;
+ generate_bitmap_cache_id(bitmap, &cacheID);
+
+ GrTextureDesc desc;
+ generate_bitmap_texture_desc(bitmap, &desc);
+ return ctx->isTextureInCache(desc, cacheID, params);
+}
+
+GrTexture* GrLockAndRefCachedBitmapTexture(GrContext* ctx,
+ const SkBitmap& bitmap,
+ const GrTextureParams* params) {
+ GrTexture* result = NULL;
+
+ bool cache = !bitmap.isVolatile();
+
+ if (cache) {
+ // If the bitmap isn't changing try to find a cached copy first.
+
+ GrCacheID cacheID;
+ generate_bitmap_cache_id(bitmap, &cacheID);
+
+ GrTextureDesc desc;
+ generate_bitmap_texture_desc(bitmap, &desc);
+
+ result = ctx->findAndRefTexture(desc, cacheID, params);
+ }
+ if (NULL == result) {
+ result = sk_gr_create_bitmap_texture(ctx, cache, params, bitmap);
+ }
+ if (NULL == result) {
+ GrPrintf("---- failed to create texture for cache [%d %d]\n",
+ bitmap.width(), bitmap.height());
+ }
+ return result;
+}
+
+void GrUnlockAndUnrefCachedBitmapTexture(GrTexture* texture) {
+ GrAssert(NULL != texture->getContext());
+
+ texture->getContext()->unlockScratchTexture(texture);
+ texture->unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrPixelConfig SkBitmapConfig2GrPixelConfig(SkBitmap::Config config) {
+ switch (config) {
+ case SkBitmap::kA8_Config:
+ return kAlpha_8_GrPixelConfig;
+ case SkBitmap::kIndex8_Config:
+ return kIndex_8_GrPixelConfig;
+ case SkBitmap::kRGB_565_Config:
+ return kRGB_565_GrPixelConfig;
+ case SkBitmap::kARGB_4444_Config:
+ return kRGBA_4444_GrPixelConfig;
+ case SkBitmap::kARGB_8888_Config:
+ return kSkia8888_GrPixelConfig;
+ default:
+ // kNo_Config, kA1_Config missing
+ return kUnknown_GrPixelConfig;
+ }
+}
diff --git a/gpu/SkGrFontScaler.cpp b/gpu/SkGrFontScaler.cpp
new file mode 100644
index 00000000..35be3d04
--- /dev/null
+++ b/gpu/SkGrFontScaler.cpp
@@ -0,0 +1,201 @@
+
+/*
+ * 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 "GrTemplates.h"
+#include "SkGr.h"
+#include "SkDescriptor.h"
+#include "SkGlyphCache.h"
+
+class SkGrDescKey : public GrKey {
+public:
+ explicit SkGrDescKey(const SkDescriptor& desc);
+ virtual ~SkGrDescKey();
+
+protected:
+ // overrides
+ virtual bool lt(const GrKey& rh) const;
+ virtual bool eq(const GrKey& rh) const;
+
+private:
+ SkDescriptor* fDesc;
+ enum {
+ kMaxStorageInts = 16
+ };
+ uint32_t fStorage[kMaxStorageInts];
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGrDescKey::SkGrDescKey(const SkDescriptor& desc) : GrKey(desc.getChecksum()) {
+ size_t size = desc.getLength();
+ if (size <= sizeof(fStorage)) {
+ fDesc = GrTCast<SkDescriptor*>(fStorage);
+ } else {
+ fDesc = SkDescriptor::Alloc(size);
+ }
+ memcpy(fDesc, &desc, size);
+}
+
+SkGrDescKey::~SkGrDescKey() {
+ if (fDesc != GrTCast<SkDescriptor*>(fStorage)) {
+ SkDescriptor::Free(fDesc);
+ }
+}
+
+bool SkGrDescKey::lt(const GrKey& rh) const {
+ const SkDescriptor* srcDesc = ((const SkGrDescKey*)&rh)->fDesc;
+ size_t lenLH = fDesc->getLength();
+ size_t lenRH = srcDesc->getLength();
+ int cmp = memcmp(fDesc, srcDesc, SkMin32(lenLH, lenRH));
+ if (0 == cmp) {
+ return lenLH < lenRH;
+ } else {
+ return cmp < 0;
+ }
+}
+
+bool SkGrDescKey::eq(const GrKey& rh) const {
+ const SkDescriptor* srcDesc = ((const SkGrDescKey*)&rh)->fDesc;
+ return fDesc->equals(*srcDesc);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGrFontScaler::SkGrFontScaler(SkGlyphCache* strike) {
+ fStrike = strike;
+ fKey = NULL;
+}
+
+SkGrFontScaler::~SkGrFontScaler() {
+ GrSafeUnref(fKey);
+}
+
+GrMaskFormat SkGrFontScaler::getMaskFormat() {
+ SkMask::Format format = fStrike->getMaskFormat();
+ switch (format) {
+ case SkMask::kBW_Format:
+ // fall through to kA8 -- we store BW glyphs in our 8-bit cache
+ case SkMask::kA8_Format:
+ return kA8_GrMaskFormat;
+ case SkMask::kLCD16_Format:
+ return kA565_GrMaskFormat;
+ case SkMask::kLCD32_Format:
+ return kA888_GrMaskFormat;
+ default:
+ GrAssert(!"unsupported SkMask::Format");
+ return kA8_GrMaskFormat;
+ }
+}
+
+const GrKey* SkGrFontScaler::getKey() {
+ if (NULL == fKey) {
+ fKey = SkNEW_ARGS(SkGrDescKey, (fStrike->getDescriptor()));
+ }
+ return fKey;
+}
+
+bool SkGrFontScaler::getPackedGlyphBounds(GrGlyph::PackedID packed,
+ SkIRect* bounds) {
+ const SkGlyph& glyph = fStrike->getGlyphIDMetrics(GrGlyph::UnpackID(packed),
+ GrGlyph::UnpackFixedX(packed),
+ GrGlyph::UnpackFixedY(packed));
+ bounds->setXYWH(glyph.fLeft, glyph.fTop, glyph.fWidth, glyph.fHeight);
+ return true;
+
+}
+
+namespace {
+// expands each bit in a bitmask to 0 or ~0 of type INT_TYPE. Used to expand a BW glyph mask to
+// A8, RGB565, or RGBA8888.
+template <typename INT_TYPE>
+void expand_bits(INT_TYPE* dst,
+ const uint8_t* src,
+ int width,
+ int height,
+ int dstRowBytes,
+ int srcRowBytes) {
+ for (int i = 0; i < height; ++i) {
+ int rowWritesLeft = width;
+ const uint8_t* s = src;
+ INT_TYPE* d = dst;
+ while (rowWritesLeft > 0) {
+ unsigned mask = *s++;
+ for (int i = 7; i >= 0 && rowWritesLeft; --i, --rowWritesLeft) {
+ *d++ = (mask & (1 << i)) ? (INT_TYPE)(~0UL) : 0;
+ }
+ }
+ dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes);
+ src += srcRowBytes;
+ }
+}
+}
+
+bool SkGrFontScaler::getPackedGlyphImage(GrGlyph::PackedID packed,
+ int width, int height,
+ int dstRB, void* dst) {
+ const SkGlyph& glyph = fStrike->getGlyphIDMetrics(GrGlyph::UnpackID(packed),
+ GrGlyph::UnpackFixedX(packed),
+ GrGlyph::UnpackFixedY(packed));
+ GrAssert(glyph.fWidth == width);
+ GrAssert(glyph.fHeight == height);
+ const void* src = fStrike->findImage(glyph);
+ if (NULL == src) {
+ return false;
+ }
+
+ int srcRB = glyph.rowBytes();
+ // The windows font host sometimes has BW glyphs in a non-BW strike. So it is important here to
+ // check the glyph's format, not the strike's format, and to be able to convert to any of the
+ // GrMaskFormats.
+ if (SkMask::kBW_Format == glyph.fMaskFormat) {
+ // expand bits to our mask type
+ const uint8_t* bits = reinterpret_cast<const uint8_t*>(src);
+ switch (this->getMaskFormat()) {
+ case kA8_GrMaskFormat:{
+ uint8_t* bytes = reinterpret_cast<uint8_t*>(dst);
+ expand_bits(bytes, bits, width, height, dstRB, srcRB);
+ break;
+ }
+ case kA565_GrMaskFormat: {
+ uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst);
+ expand_bits(rgb565, bits, width, height, dstRB, srcRB);
+ break;
+ }
+ case kA888_GrMaskFormat: {
+ uint32_t* rgba8888 = reinterpret_cast<uint32_t*>(dst);
+ expand_bits(rgba8888, bits, width, height, dstRB, srcRB);
+ break;
+ }
+ default:
+ GrCrash("Unknown GrMaskFormat");
+ }
+ } else if (srcRB == dstRB) {
+ memcpy(dst, src, dstRB * height);
+ } else {
+ const int bbp = GrMaskFormatBytesPerPixel(this->getMaskFormat());
+ for (int y = 0; y < height; y++) {
+ memcpy(dst, src, width * bbp);
+ src = (const char*)src + srcRB;
+ dst = (char*)dst + dstRB;
+ }
+ }
+ return true;
+}
+
+// we should just return const SkPath* (NULL means false)
+bool SkGrFontScaler::getGlyphPath(uint16_t glyphID, SkPath* path) {
+
+ const SkGlyph& glyph = fStrike->getGlyphIDMetrics(glyphID);
+ const SkPath* skPath = fStrike->findPath(glyph);
+ if (skPath) {
+ *path = *skPath;
+ return true;
+ }
+ return false;
+}
diff --git a/gpu/SkGrPixelRef.cpp b/gpu/SkGrPixelRef.cpp
new file mode 100644
index 00000000..98819bfd
--- /dev/null
+++ b/gpu/SkGrPixelRef.cpp
@@ -0,0 +1,179 @@
+
+/*
+ * 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 "SkGrPixelRef.h"
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkGr.h"
+#include "SkRect.h"
+
+// since we call lockPixels recursively on fBitmap, we need a distinct mutex,
+// to avoid deadlock with the default one provided by SkPixelRef.
+SK_DECLARE_STATIC_MUTEX(gROLockPixelsPixelRefMutex);
+
+SkROLockPixelsPixelRef::SkROLockPixelsPixelRef() : INHERITED(&gROLockPixelsPixelRefMutex) {
+}
+
+SkROLockPixelsPixelRef::~SkROLockPixelsPixelRef() {
+}
+
+void* SkROLockPixelsPixelRef::onLockPixels(SkColorTable** ctable) {
+ if (ctable) {
+ *ctable = NULL;
+ }
+ fBitmap.reset();
+// SkDebugf("---------- calling readpixels in support of lockpixels\n");
+ if (!this->onReadPixels(&fBitmap, NULL)) {
+ SkDebugf("SkROLockPixelsPixelRef::onLockPixels failed!\n");
+ return NULL;
+ }
+ fBitmap.lockPixels();
+ return fBitmap.getPixels();
+}
+
+void SkROLockPixelsPixelRef::onUnlockPixels() {
+ fBitmap.unlockPixels();
+}
+
+bool SkROLockPixelsPixelRef::onLockPixelsAreWritable() const {
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkGrPixelRef* copyToTexturePixelRef(GrTexture* texture, SkBitmap::Config dstConfig,
+ const SkIRect* subset) {
+ if (NULL == texture) {
+ return NULL;
+ }
+ GrContext* context = texture->getContext();
+ if (NULL == context) {
+ return NULL;
+ }
+ GrTextureDesc desc;
+
+ 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);
+
+ GrTexture* dst = context->createUncachedTexture(desc, NULL, 0);
+ if (NULL == dst) {
+ return NULL;
+ }
+
+ context->copyTexture(texture, dst->asRenderTarget(), topLeft);
+
+ // TODO: figure out if this is responsible for Chrome canvas errors
+#if 0
+ // The render texture we have created (to perform the copy) isn't fully
+ // functional (since it doesn't have a stencil buffer). Release it here
+ // so the caller doesn't try to render to it.
+ // TODO: we can undo this release when dynamic stencil buffer attach/
+ // detach has been implemented
+ dst->releaseRenderTarget();
+#endif
+
+ SkGrPixelRef* pixelRef = SkNEW_ARGS(SkGrPixelRef, (dst));
+ GrSafeUnref(dst);
+ return pixelRef;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGrPixelRef::SkGrPixelRef(GrSurface* surface, bool transferCacheLock) {
+ // TODO: figure out if this is responsible for Chrome canvas errors
+#if 0
+ // The GrTexture has a ref to the GrRenderTarget but not vice versa.
+ // If the GrTexture exists take a ref to that (rather than the render
+ // target)
+ fSurface = surface->asTexture();
+#else
+ fSurface = NULL;
+#endif
+ if (NULL == fSurface) {
+ fSurface = surface;
+ }
+ fUnlock = transferCacheLock;
+ GrSafeRef(surface);
+}
+
+SkGrPixelRef::~SkGrPixelRef() {
+ if (fUnlock) {
+ GrContext* context = fSurface->getContext();
+ GrTexture* texture = fSurface->asTexture();
+ if (NULL != context && NULL != texture) {
+ context->unlockScratchTexture(texture);
+ }
+ }
+ GrSafeUnref(fSurface);
+}
+
+GrTexture* SkGrPixelRef::getTexture() {
+ if (NULL != fSurface) {
+ return fSurface->asTexture();
+ }
+ return NULL;
+}
+
+SkPixelRef* SkGrPixelRef::deepCopy(SkBitmap::Config dstConfig, const SkIRect* subset) {
+ if (NULL == fSurface) {
+ return NULL;
+ }
+
+ // Note that when copying a render-target-backed pixel ref, we
+ // return a texture-backed pixel ref instead. This is because
+ // render-target pixel refs are usually created in conjunction with
+ // 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, subset);
+}
+
+bool SkGrPixelRef::onReadPixels(SkBitmap* dst, const SkIRect* subset) {
+ if (NULL == fSurface || !fSurface->isValid()) {
+ return false;
+ }
+
+ int left, top, width, height;
+ if (NULL != subset) {
+ left = subset->fLeft;
+ width = subset->width();
+ top = subset->fTop;
+ height = subset->height();
+ } else {
+ left = 0;
+ width = fSurface->width();
+ top = 0;
+ height = fSurface->height();
+ }
+ dst->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ 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,
+ kSkia8888_GrPixelConfig,
+ buffer, dst->rowBytes());
+}
diff --git a/gpu/SkGrTexturePixelRef.cpp b/gpu/SkGrTexturePixelRef.cpp
new file mode 100644
index 00000000..ae2bbddc
--- /dev/null
+++ b/gpu/SkGrTexturePixelRef.cpp
@@ -0,0 +1,11 @@
+
+/*
+ * 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 "SkGrTexturePixelRef.h"
diff --git a/gpu/effects/Gr1DKernelEffect.h b/gpu/effects/Gr1DKernelEffect.h
new file mode 100644
index 00000000..17127336
--- /dev/null
+++ b/gpu/effects/Gr1DKernelEffect.h
@@ -0,0 +1,54 @@
+/*
+ * 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 Gr1DKernelEffect_DEFINED
+#define Gr1DKernelEffect_DEFINED
+
+#include "GrSingleTextureEffect.h"
+#include "SkMatrix.h"
+
+/**
+ * Base class for 1D kernel effects. The kernel operates either in X or Y and
+ * has a pixel radius. The kernel is specified in the src texture's space
+ * and the kernel center is pinned to a texel's center. The radius specifies
+ * the number of texels on either side of the center texel in X or Y that are
+ * read. Since the center pixel is also read, the total width is one larger than
+ * two times the radius.
+ */
+
+class Gr1DKernelEffect : public GrSingleTextureEffect {
+
+public:
+ enum Direction {
+ kX_Direction,
+ kY_Direction,
+ };
+
+ Gr1DKernelEffect(GrTexture* texture,
+ Direction direction,
+ int radius)
+ : GrSingleTextureEffect(texture, MakeDivByTextureWHMatrix(texture))
+ , fDirection(direction)
+ , fRadius(radius) {}
+
+ virtual ~Gr1DKernelEffect() {};
+
+ static int WidthFromRadius(int radius) { return 2 * radius + 1; }
+
+ int radius() const { return fRadius; }
+ int width() const { return WidthFromRadius(fRadius); }
+ Direction direction() const { return fDirection; }
+
+private:
+
+ Direction fDirection;
+ int fRadius;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrConfigConversionEffect.cpp b/gpu/effects/GrConfigConversionEffect.cpp
new file mode 100644
index 00000000..d1c7fea1
--- /dev/null
+++ b/gpu/effects/GrConfigConversionEffect.cpp
@@ -0,0 +1,296 @@
+/*
+ * 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 "GrConfigConversionEffect.h"
+#include "GrContext.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrSimpleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "SkMatrix.h"
+
+class GrGLConfigConversionEffect : public GrGLEffect {
+public:
+ GrGLConfigConversionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED (factory)
+ , fEffectMatrix(drawEffect.castEffect<GrConfigConversionEffect>().coordsType()) {
+ const GrConfigConversionEffect& effect = drawEffect.castEffect<GrConfigConversionEffect>();
+ fSwapRedAndBlue = effect.swapsRedAndBlue();
+ fPMConversion = effect.pmConversion();
+ }
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const char* coords;
+ GrSLType coordsType = fEffectMatrix.emitCode(builder, key, &coords);
+ builder->fsCodeAppendf("\t\t%s = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ coords,
+ coordsType);
+ builder->fsCodeAppend(";\n");
+ if (GrConfigConversionEffect::kNone_PMConversion == fPMConversion) {
+ GrAssert(fSwapRedAndBlue);
+ builder->fsCodeAppendf("\t%s = %s.bgra;\n", outputColor, outputColor);
+ } else {
+ const char* swiz = fSwapRedAndBlue ? "bgr" : "rgb";
+ switch (fPMConversion) {
+ case GrConfigConversionEffect::kMulByAlpha_RoundUp_PMConversion:
+ builder->fsCodeAppendf(
+ "\t\t%s = vec4(ceil(%s.%s * %s.a * 255.0) / 255.0, %s.a);\n",
+ outputColor, outputColor, swiz, outputColor, outputColor);
+ break;
+ case GrConfigConversionEffect::kMulByAlpha_RoundDown_PMConversion:
+ // Add a compensation(0.001) here to avoid the side effect of the floor operation.
+ // In Intel GPUs, the integer value converted from floor(%s.r * 255.0) / 255.0
+ // is less than the integer value converted from %s.r by 1 when the %s.r is
+ // converted from the integer value 2^n, such as 1, 2, 4, 8, etc.
+ builder->fsCodeAppendf(
+ "\t\t%s = vec4(floor(%s.%s * %s.a * 255.0 + 0.001) / 255.0, %s.a);\n",
+ outputColor, outputColor, swiz, outputColor, outputColor);
+ break;
+ case GrConfigConversionEffect::kDivByAlpha_RoundUp_PMConversion:
+ builder->fsCodeAppendf("\t\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(ceil(%s.%s / %s.a * 255.0) / 255.0, %s.a);\n",
+ outputColor, outputColor, outputColor, swiz, outputColor, outputColor);
+ break;
+ case GrConfigConversionEffect::kDivByAlpha_RoundDown_PMConversion:
+ builder->fsCodeAppendf("\t\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(floor(%s.%s / %s.a * 255.0) / 255.0, %s.a);\n",
+ outputColor, outputColor, outputColor, swiz, outputColor, outputColor);
+ break;
+ default:
+ GrCrash("Unknown conversion op.");
+ break;
+ }
+ }
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+ }
+
+ void setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ const GrConfigConversionEffect& conv = drawEffect.castEffect<GrConfigConversionEffect>();
+ fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrConfigConversionEffect& conv = drawEffect.castEffect<GrConfigConversionEffect>();
+ EffectKey key = static_cast<EffectKey>(conv.swapsRedAndBlue()) | (conv.pmConversion() << 1);
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(conv.getMatrix(),
+ drawEffect,
+ conv.coordsType(),
+ conv.texture(0));
+ GrAssert(!(matrixKey & key));
+ return matrixKey | key;
+ }
+
+private:
+ bool fSwapRedAndBlue;
+ GrConfigConversionEffect::PMConversion fPMConversion;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrConfigConversionEffect::GrConfigConversionEffect(GrTexture* texture,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix)
+ : GrSingleTextureEffect(texture, matrix)
+ , fSwapRedAndBlue(swapRedAndBlue)
+ , fPMConversion(pmConversion) {
+ GrAssert(kRGBA_8888_GrPixelConfig == texture->config() ||
+ kBGRA_8888_GrPixelConfig == texture->config());
+ // Why did we pollute our texture cache instead of using a GrSingleTextureEffect?
+ GrAssert(swapRedAndBlue || kNone_PMConversion != pmConversion);
+}
+
+const GrBackendEffectFactory& GrConfigConversionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrConfigConversionEffect>::getInstance();
+}
+
+bool GrConfigConversionEffect::onIsEqual(const GrEffect& s) const {
+ const GrConfigConversionEffect& other = CastEffect<GrConfigConversionEffect>(s);
+ return this->texture(0) == s.texture(0) &&
+ other.fSwapRedAndBlue == fSwapRedAndBlue &&
+ other.fPMConversion == fPMConversion;
+}
+
+void GrConfigConversionEffect::getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const {
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrConfigConversionEffect);
+
+GrEffectRef* GrConfigConversionEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ PMConversion pmConv = static_cast<PMConversion>(random->nextULessThan(kPMConversionCnt));
+ bool swapRB;
+ if (kNone_PMConversion == pmConv) {
+ swapRB = true;
+ } else {
+ swapRB = random->nextBool();
+ }
+ AutoEffectUnref effect(SkNEW_ARGS(GrConfigConversionEffect,
+ (textures[GrEffectUnitTest::kSkiaPMTextureIdx],
+ swapRB,
+ pmConv,
+ GrEffectUnitTest::TestMatrix(random))));
+ return CreateEffectRef(effect);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void GrConfigConversionEffect::TestForPreservingPMConversions(GrContext* context,
+ PMConversion* pmToUPMRule,
+ PMConversion* upmToPMRule) {
+ *pmToUPMRule = kNone_PMConversion;
+ *upmToPMRule = kNone_PMConversion;
+ SkAutoTMalloc<uint32_t> data(256 * 256 * 3);
+ uint32_t* srcData = data.get();
+ uint32_t* firstRead = data.get() + 256 * 256;
+ uint32_t* secondRead = data.get() + 2 * 256 * 256;
+
+ // Fill with every possible premultiplied A, color channel value. There will be 256-y duplicate
+ // values in row y. We set r,g, and b to the same value since they are handled identically.
+ for (int y = 0; y < 256; ++y) {
+ for (int x = 0; x < 256; ++x) {
+ uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[256*y + x]);
+ color[3] = y;
+ color[2] = GrMin(x, y);
+ color[1] = GrMin(x, y);
+ color[0] = GrMin(x, y);
+ }
+ }
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit |
+ kNoStencil_GrTextureFlagBit;
+ desc.fWidth = 256;
+ desc.fHeight = 256;
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+
+ SkAutoTUnref<GrTexture> readTex(context->createUncachedTexture(desc, NULL, 0));
+ if (!readTex.get()) {
+ return;
+ }
+ SkAutoTUnref<GrTexture> tempTex(context->createUncachedTexture(desc, NULL, 0));
+ if (!tempTex.get()) {
+ return;
+ }
+ desc.fFlags = kNone_GrTextureFlags;
+ SkAutoTUnref<GrTexture> dataTex(context->createUncachedTexture(desc, data, 0));
+ if (!dataTex.get()) {
+ return;
+ }
+
+ static const PMConversion kConversionRules[][2] = {
+ {kDivByAlpha_RoundDown_PMConversion, kMulByAlpha_RoundUp_PMConversion},
+ {kDivByAlpha_RoundUp_PMConversion, kMulByAlpha_RoundDown_PMConversion},
+ };
+
+ GrContext::AutoWideOpenIdentityDraw awoid(context, NULL);
+
+ bool failed = true;
+
+ for (size_t i = 0; i < GR_ARRAY_COUNT(kConversionRules) && failed; ++i) {
+ *pmToUPMRule = kConversionRules[i][0];
+ *upmToPMRule = kConversionRules[i][1];
+
+ static const SkRect kDstRect = SkRect::MakeWH(SkIntToScalar(256), SkIntToScalar(256));
+ static const SkRect kSrcRect = SkRect::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.
+
+ AutoEffectUnref pmToUPM1(SkNEW_ARGS(GrConfigConversionEffect, (dataTex,
+ false,
+ *pmToUPMRule,
+ SkMatrix::I())));
+ AutoEffectUnref upmToPM(SkNEW_ARGS(GrConfigConversionEffect, (readTex,
+ false,
+ *upmToPMRule,
+ SkMatrix::I())));
+ AutoEffectUnref pmToUPM2(SkNEW_ARGS(GrConfigConversionEffect, (tempTex,
+ false,
+ *pmToUPMRule,
+ SkMatrix::I())));
+
+ SkAutoTUnref<GrEffectRef> pmToUPMEffect1(CreateEffectRef(pmToUPM1));
+ SkAutoTUnref<GrEffectRef> upmToPMEffect(CreateEffectRef(upmToPM));
+ SkAutoTUnref<GrEffectRef> pmToUPMEffect2(CreateEffectRef(pmToUPM2));
+
+ context->setRenderTarget(readTex->asRenderTarget());
+ GrPaint paint1;
+ paint1.addColorEffect(pmToUPMEffect1);
+ context->drawRectToRect(paint1, kDstRect, kSrcRect);
+
+ readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, firstRead);
+
+ context->setRenderTarget(tempTex->asRenderTarget());
+ GrPaint paint2;
+ paint2.addColorEffect(upmToPMEffect);
+ context->drawRectToRect(paint2, kDstRect, kSrcRect);
+ context->setRenderTarget(readTex->asRenderTarget());
+
+ GrPaint paint3;
+ paint3.addColorEffect(pmToUPMEffect2);
+ context->drawRectToRect(paint3, kDstRect, kSrcRect);
+
+ readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, secondRead);
+
+ failed = false;
+ for (int y = 0; y < 256 && !failed; ++y) {
+ for (int x = 0; x <= y; ++x) {
+ if (firstRead[256 * y + x] != secondRead[256 * y + x]) {
+ failed = true;
+ break;
+ }
+ }
+ }
+ }
+ if (failed) {
+ *pmToUPMRule = kNone_PMConversion;
+ *upmToPMRule = kNone_PMConversion;
+ }
+}
+
+const GrEffectRef* GrConfigConversionEffect::Create(GrTexture* texture,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix) {
+ if (!swapRedAndBlue && kNone_PMConversion == pmConversion) {
+ // If we returned a GrConfigConversionEffect that was equivalent to a GrSimpleTextureEffect
+ // then we may pollute our texture cache with redundant shaders. So in the case that no
+ // conversions were requested we instead return a GrSimpleTextureEffect.
+ return GrSimpleTextureEffect::Create(texture, matrix);
+ } 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;
+ }
+ AutoEffectUnref effect(SkNEW_ARGS(GrConfigConversionEffect, (texture,
+ swapRedAndBlue,
+ pmConversion,
+ matrix)));
+ return CreateEffectRef(effect);
+ }
+}
diff --git a/gpu/effects/GrConfigConversionEffect.h b/gpu/effects/GrConfigConversionEffect.h
new file mode 100644
index 00000000..169c3d7d
--- /dev/null
+++ b/gpu/effects/GrConfigConversionEffect.h
@@ -0,0 +1,78 @@
+/*
+ * 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 GrConfigConversionEffect_DEFINED
+#define GrConfigConversionEffect_DEFINED
+
+#include "GrSingleTextureEffect.h"
+
+class GrEffectStage;
+class GrGLConfigConversionEffect;
+
+/**
+ * This class is used to perform config conversions. Clients may want to read/write data that is
+ * unpremultiplied. Also on some systems reading/writing BGRA or RGBA is faster. In those cases we
+ * read/write using the faster path and perform an R/B swap in the shader if the client data is in
+ * the slower config.
+ */
+class GrConfigConversionEffect : public GrSingleTextureEffect {
+public:
+ /**
+ * The PM->UPM or UPM->PM conversions to apply.
+ */
+ enum PMConversion {
+ kNone_PMConversion = 0,
+ kMulByAlpha_RoundUp_PMConversion,
+ kMulByAlpha_RoundDown_PMConversion,
+ kDivByAlpha_RoundUp_PMConversion,
+ kDivByAlpha_RoundDown_PMConversion,
+
+ kPMConversionCnt
+ };
+
+ // Installs an effect in the GrEffectStage to perform a config conversion.
+ static const GrEffectRef* Create(GrTexture*,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix);
+
+ static const char* Name() { return "Config Conversion"; }
+ typedef GrGLConfigConversionEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ bool swapsRedAndBlue() const { return fSwapRedAndBlue; }
+ PMConversion pmConversion() const { return fPMConversion; }
+
+ // This function determines whether it is possible to choose PM->UPM and UPM->PM conversions
+ // for which in any PM->UPM->PM->UPM sequence the two UPM values are the same. This means that
+ // if pixels are read back to a UPM buffer, written back to PM to the GPU, and read back again
+ // both reads will produce the same result. This test is quite expensive and should not be run
+ // multiple times for a given context.
+ static void TestForPreservingPMConversions(GrContext* context,
+ PMConversion* PMToUPMRule,
+ PMConversion* UPMToPMRule);
+
+private:
+ GrConfigConversionEffect(GrTexture*,
+ bool swapRedAndBlue,
+ PMConversion pmConversion,
+ const SkMatrix& matrix);
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ bool fSwapRedAndBlue;
+ PMConversion fPMConversion;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrConvolutionEffect.cpp b/gpu/effects/GrConvolutionEffect.cpp
new file mode 100644
index 00000000..e7fb8e54
--- /dev/null
+++ b/gpu/effects/GrConvolutionEffect.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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 "GrConvolutionEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrTBackendEffectFactory.h"
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+class GrGLConvolutionEffect : public GrGLEffect {
+public:
+ GrGLConvolutionEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+ int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
+ bool useBounds() const { return fUseBounds; }
+ Gr1DKernelEffect::Direction direction() const { return fDirection; }
+
+ int fRadius;
+ bool fUseBounds;
+ Gr1DKernelEffect::Direction fDirection;
+ UniformHandle fKernelUni;
+ UniformHandle fImageIncrementUni;
+ UniformHandle fBoundsUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fKernelUni(kInvalidUniformHandle)
+ , fImageIncrementUni(kInvalidUniformHandle)
+ , fBoundsUni(kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrConvolutionEffect>().coordsType()) {
+ const GrConvolutionEffect& c = drawEffect.castEffect<GrConvolutionEffect>();
+ fRadius = c.radius();
+ fUseBounds = c.useBounds();
+ fDirection = c.direction();
+}
+
+void GrGLConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+ if (this->useBounds()) {
+ fBoundsUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "Bounds");
+ }
+ fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Kernel", this->width());
+
+ builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
+
+ int width = this->width();
+ const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+
+ builder->fsCodeAppendf("\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++) {
+ SkString index;
+ SkString kernelIndex;
+ index.appendS32(i);
+ kernel.appendArrayAccess(index.c_str(), &kernelIndex);
+ builder->fsCodeAppendf("\t\t%s += ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "coord");
+ if (this->useBounds()) {
+ const char* bounds = builder->getUniformCStr(fBoundsUni);
+ const char* component = this->direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x";
+ builder->fsCodeAppendf(" * float(coord.%s >= %s.x && coord.%s <= %s.y)",
+ component, bounds, component, bounds);
+ }
+ builder->fsCodeAppendf(" * %s;\n", kernelIndex.c_str());
+ builder->fsCodeAppendf("\t\tcoord += %s;\n", imgInc);
+ }
+
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrConvolutionEffect& conv = drawEffect.castEffect<GrConvolutionEffect>();
+ GrTexture& texture = *conv.texture(0);
+ // the code we generated was for a specific kernel radius
+ GrAssert(conv.radius() == fRadius);
+ float imageIncrement[2] = { 0 };
+ float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
+ switch (conv.direction()) {
+ case Gr1DKernelEffect::kX_Direction:
+ imageIncrement[0] = 1.0f / texture.width();
+ break;
+ case Gr1DKernelEffect::kY_Direction:
+ imageIncrement[1] = ySign / texture.height();
+ break;
+ default:
+ GrCrash("Unknown filter direction.");
+ }
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ if (conv.useBounds()) {
+ const float* bounds = conv.bounds();
+ if (Gr1DKernelEffect::kY_Direction == conv.direction() &&
+ texture.origin() != kTopLeft_GrSurfaceOrigin) {
+ uman.set2f(fBoundsUni, 1.0f - bounds[1], 1.0f - bounds[0]);
+ } else {
+ uman.set2f(fBoundsUni, bounds[0], bounds[1]);
+ }
+ }
+ uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
+ fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrConvolutionEffect& conv = drawEffect.castEffect<GrConvolutionEffect>();
+ EffectKey key = conv.radius();
+ key <<= 2;
+ if (conv.useBounds()) {
+ key |= 0x2;
+ key |= GrConvolutionEffect::kY_Direction == conv.direction() ? 0x1 : 0x0;
+ }
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(conv.getMatrix(),
+ drawEffect,
+ conv.coordsType(),
+ conv.texture(0));
+ return key | matrixKey;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
+ Direction direction,
+ int radius,
+ const float* kernel,
+ bool useBounds,
+ float bounds[2])
+ : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
+ GrAssert(radius <= kMaxKernelRadius);
+ GrAssert(NULL != kernel);
+ int width = this->width();
+ for (int i = 0; i < width; i++) {
+ fKernel[i] = kernel[i];
+ }
+ memcpy(fBounds, bounds, sizeof(fBounds));
+}
+
+GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
+ Direction direction,
+ int radius,
+ float gaussianSigma,
+ bool useBounds,
+ float bounds[2])
+ : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
+ GrAssert(radius <= kMaxKernelRadius);
+ int width = this->width();
+
+ float sum = 0.0f;
+ float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
+ for (int i = 0; i < width; ++i) {
+ float x = static_cast<float>(i - this->radius());
+ // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
+ // is dropped here, since we renormalize the kernel below.
+ fKernel[i] = sk_float_exp(- x * x * denom);
+ sum += fKernel[i];
+ }
+ // Normalize the kernel
+ float scale = 1.0f / sum;
+ for (int i = 0; i < width; ++i) {
+ fKernel[i] *= scale;
+ }
+ memcpy(fBounds, bounds, sizeof(fBounds));
+}
+
+GrConvolutionEffect::~GrConvolutionEffect() {
+}
+
+const GrBackendEffectFactory& GrConvolutionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrConvolutionEffect>::getInstance();
+}
+
+bool GrConvolutionEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrConvolutionEffect& s = CastEffect<GrConvolutionEffect>(sBase);
+ return (this->texture(0) == s.texture(0) &&
+ this->radius() == s.radius() &&
+ this->direction() == s.direction() &&
+ this->useBounds() == s.useBounds() &&
+ 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
+ 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrConvolutionEffect);
+
+GrEffectRef* GrConvolutionEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ 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];
+ for (int i = 0; i < kMaxKernelRadius; ++i) {
+ kernel[i] = random->nextSScalar1();
+ }
+ float bounds[2];
+ for (int i = 0; i < 2; ++i) {
+ bounds[i] = random->nextF();
+ }
+
+ bool useBounds = random->nextBool();
+ return GrConvolutionEffect::Create(textures[texIdx],
+ dir,
+ radius,
+ kernel,
+ useBounds,
+ bounds);
+}
diff --git a/gpu/effects/GrConvolutionEffect.h b/gpu/effects/GrConvolutionEffect.h
new file mode 100644
index 00000000..56a54b4a
--- /dev/null
+++ b/gpu/effects/GrConvolutionEffect.h
@@ -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.
+ */
+
+#ifndef GrConvolutionEffect_DEFINED
+#define GrConvolutionEffect_DEFINED
+
+#include "Gr1DKernelEffect.h"
+
+class GrGLConvolutionEffect;
+
+/**
+ * A convolution effect. The kernel is specified as an array of 2 * half-width
+ * + 1 weights. Each texel is multiplied by it's weight and summed to determine
+ * the output color. The output color is modulated by the input color.
+ */
+class GrConvolutionEffect : public Gr1DKernelEffect {
+
+public:
+
+ /// Convolve with an arbitrary user-specified kernel
+ static GrEffectRef* Create(GrTexture* tex,
+ Direction dir,
+ int halfWidth,
+ const float* kernel,
+ bool useBounds,
+ float bounds[2]) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
+ dir,
+ halfWidth,
+ kernel,
+ useBounds,
+ bounds)));
+ return CreateEffectRef(effect);
+ }
+
+ /// Convolve with a Gaussian kernel
+ static GrEffectRef* CreateGaussian(GrTexture* tex,
+ Direction dir,
+ int halfWidth,
+ float gaussianSigma,
+ bool useBounds,
+ float bounds[2]) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
+ dir,
+ halfWidth,
+ gaussianSigma,
+ useBounds,
+ bounds)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrConvolutionEffect();
+
+ const float* kernel() const { return fKernel; }
+
+ const float* bounds() const { return fBounds; }
+ bool useBounds() const { return fUseBounds; }
+
+ static const char* Name() { return "Convolution"; }
+
+ typedef GrGLConvolutionEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ virtual void getConstantColorComponents(GrColor*, uint32_t* validFlags) const {
+ // If the texture was opaque we could know that the output color if we knew the sum of the
+ // kernel values.
+ *validFlags = 0;
+ }
+
+ enum {
+ // This was decided based on the min allowed value for the max texture
+ // samples per fragment program run in DX9SM2 (32). A sigma param of 4.0
+ // on a blur filter gives a kernel width of 25 while a sigma of 5.0
+ // would exceed a 32 wide kernel.
+ kMaxKernelRadius = 12,
+ // With a C++11 we could have a constexpr version of WidthFromRadius()
+ // and not have to duplicate this calculation.
+ kMaxKernelWidth = 2 * kMaxKernelRadius + 1,
+ };
+
+protected:
+
+ float fKernel[kMaxKernelWidth];
+ bool fUseBounds;
+ float fBounds[2];
+
+private:
+ GrConvolutionEffect(GrTexture*, Direction,
+ int halfWidth,
+ const float* kernel,
+ bool useBounds,
+ float bounds[2]);
+
+ /// Convolve with a Gaussian kernel
+ GrConvolutionEffect(GrTexture*, Direction,
+ int halfWidth,
+ float gaussianSigma,
+ bool useBounds,
+ float bounds[2]);
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef Gr1DKernelEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrSimpleTextureEffect.cpp b/gpu/effects/GrSimpleTextureEffect.cpp
new file mode 100644
index 00000000..06615d29
--- /dev/null
+++ b/gpu/effects/GrSimpleTextureEffect.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 "GrSimpleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrTexture.h"
+
+class GrGLSimpleTextureEffect : public GrGLEffect {
+public:
+ GrGLSimpleTextureEffect(const GrBackendEffectFactory& factory, const GrDrawEffect& drawEffect)
+ : INHERITED (factory) {
+ GrEffect::CoordsType coordsType =
+ drawEffect.castEffect<GrSimpleTextureEffect>().coordsType();
+ if (GrEffect::kCustom_CoordsType != coordsType) {
+ SkNEW_IN_TLAZY(&fEffectMatrix, GrGLEffectMatrix, (coordsType));
+ }
+ }
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const GrSimpleTextureEffect& ste = drawEffect.castEffect<GrSimpleTextureEffect>();
+ const char* fsCoordName;
+ GrSLType fsCoordSLType;
+ if (GrEffect::kCustom_CoordsType == ste.coordsType()) {
+ GrAssert(ste.getMatrix().isIdentity());
+ GrAssert(1 == ste.numVertexAttribs());
+ fsCoordSLType = kVec2f_GrSLType;
+ const char* vsVaryingName;
+ builder->addVarying(kVec2f_GrSLType, "textureCoords", &vsVaryingName, &fsCoordName);
+ const char* attrName =
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0])->c_str();
+ builder->vsCodeAppendf("\t%s = %s;\n", vsVaryingName, attrName);
+ } else {
+ fsCoordSLType = fEffectMatrix.get()->emitCode(builder, key, &fsCoordName);
+ }
+ builder->fsCodeAppendf("\t%s = ", outputColor);
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ samplers[0],
+ fsCoordName,
+ fsCoordSLType);
+ builder->fsCodeAppend(";\n");
+ }
+
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrSimpleTextureEffect& ste = drawEffect.castEffect<GrSimpleTextureEffect>();
+ if (GrEffect::kCustom_CoordsType == ste.coordsType()) {
+ return 1 << GrGLEffectMatrix::kKeyBits;
+ } else {
+ return GrGLEffectMatrix::GenKey(ste.getMatrix(),
+ drawEffect,
+ ste.coordsType(),
+ ste.texture(0));
+ }
+ }
+
+ virtual void setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) SK_OVERRIDE {
+ const GrSimpleTextureEffect& ste = drawEffect.castEffect<GrSimpleTextureEffect>();
+ if (GrEffect::kCustom_CoordsType == ste.coordsType()) {
+ GrAssert(ste.getMatrix().isIdentity());
+ } else {
+ fEffectMatrix.get()->setData(uman, ste.getMatrix(), drawEffect, ste.texture(0));
+ }
+ }
+
+private:
+ SkTLazy<GrGLEffectMatrix> fEffectMatrix;
+ typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrSimpleTextureEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+const GrBackendEffectFactory& GrSimpleTextureEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrSimpleTextureEffect>::getInstance();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrSimpleTextureEffect);
+
+GrEffectRef* GrSimpleTextureEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ static const SkShader::TileMode kTileModes[] = {
+ SkShader::kClamp_TileMode,
+ SkShader::kRepeat_TileMode,
+ SkShader::kMirror_TileMode,
+ };
+ SkShader::TileMode tileModes[] = {
+ kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+ kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+ };
+ GrTextureParams params(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode :
+ GrTextureParams::kNone_FilterMode);
+
+ static const CoordsType kCoordsTypes[] = {
+ kLocal_CoordsType,
+ kPosition_CoordsType,
+ kCustom_CoordsType
+ };
+ CoordsType coordsType = kCoordsTypes[random->nextULessThan(GR_ARRAY_COUNT(kCoordsTypes))];
+
+ if (kCustom_CoordsType == coordsType) {
+ return GrSimpleTextureEffect::CreateWithCustomCoords(textures[texIdx], params);
+ } else {
+ const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random);
+ return GrSimpleTextureEffect::Create(textures[texIdx], matrix);
+ }
+}
diff --git a/gpu/effects/GrSimpleTextureEffect.h b/gpu/effects/GrSimpleTextureEffect.h
new file mode 100644
index 00000000..f80ff8da
--- /dev/null
+++ b/gpu/effects/GrSimpleTextureEffect.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrSimpleTextureEffect_DEFINED
+#define GrSimpleTextureEffect_DEFINED
+
+#include "GrSingleTextureEffect.h"
+
+class GrGLSimpleTextureEffect;
+
+/**
+ * The output color of this effect is a modulation of the input color and a sample from a texture.
+ * It allows explicit specification of the filtering and wrap modes (GrTextureParams). It can use
+ * local coords, positions, or a custom vertex attribute as input texture coords. The input coords
+ * can have a matrix applied in the VS in both the local and position cases but not with a custom
+ * attribute coords at this time. It will add a varying to input interpolate texture coords to the
+ * FS.
+ */
+class GrSimpleTextureEffect : public GrSingleTextureEffect {
+public:
+ /* unfiltered, clamp mode */
+ static GrEffectRef* Create(GrTexture* tex,
+ const SkMatrix& matrix,
+ CoordsType coordsType = kLocal_CoordsType) {
+ GrAssert(kLocal_CoordsType == coordsType || kPosition_CoordsType == coordsType);
+ AutoEffectUnref effect(SkNEW_ARGS(GrSimpleTextureEffect, (tex, matrix, GrTextureParams::kNone_FilterMode, coordsType)));
+ return CreateEffectRef(effect);
+ }
+
+ /* clamp mode */
+ static GrEffectRef* Create(GrTexture* tex,
+ const SkMatrix& matrix,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType coordsType = kLocal_CoordsType) {
+ GrAssert(kLocal_CoordsType == coordsType || kPosition_CoordsType == coordsType);
+ AutoEffectUnref effect(
+ SkNEW_ARGS(GrSimpleTextureEffect, (tex, matrix, filterMode, coordsType)));
+ return CreateEffectRef(effect);
+ }
+
+ static GrEffectRef* Create(GrTexture* tex,
+ const SkMatrix& matrix,
+ const GrTextureParams& p,
+ CoordsType coordsType = kLocal_CoordsType) {
+ GrAssert(kLocal_CoordsType == coordsType || kPosition_CoordsType == coordsType);
+ AutoEffectUnref effect(SkNEW_ARGS(GrSimpleTextureEffect, (tex, matrix, p, coordsType)));
+ return CreateEffectRef(effect);
+ }
+
+ /** Variant that requires the client to install a custom kVec2 vertex attribute that will be
+ the source of the coords. No matrix is allowed in this mode. */
+ static GrEffectRef* CreateWithCustomCoords(GrTexture* tex, const GrTextureParams& p) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSimpleTextureEffect, (tex,
+ SkMatrix::I(),
+ p,
+ kCustom_CoordsType)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrSimpleTextureEffect() {}
+
+ static const char* Name() { return "Texture"; }
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ typedef GrGLSimpleTextureEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+private:
+ GrSimpleTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType coordsType)
+ : GrSingleTextureEffect(texture, matrix, filterMode, coordsType) {
+ GrAssert(kLocal_CoordsType == coordsType || kPosition_CoordsType == coordsType);
+ }
+
+ GrSimpleTextureEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const GrTextureParams& params,
+ CoordsType coordsType)
+ : GrSingleTextureEffect(texture, matrix, params, coordsType) {
+ if (kCustom_CoordsType == coordsType) {
+ GrAssert(matrix.isIdentity());
+ this->addVertexAttrib(kVec2f_GrSLType);
+ }
+ }
+
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
+ const GrSimpleTextureEffect& ste = CastEffect<GrSimpleTextureEffect>(other);
+ return this->hasSameTextureParamsMatrixAndCoordsType(ste);
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrSingleTextureEffect.cpp b/gpu/effects/GrSingleTextureEffect.cpp
new file mode 100644
index 00000000..532ce042
--- /dev/null
+++ b/gpu/effects/GrSingleTextureEffect.cpp
@@ -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 "effects/GrSingleTextureEffect.h"
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
+ const SkMatrix& m,
+ CoordsType coordsType)
+ : fTextureAccess(texture)
+ , fMatrix(m)
+ , fCoordsType(coordsType) {
+ this->addTextureAccess(&fTextureAccess);
+}
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
+ const SkMatrix& m,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType coordsType)
+ : fTextureAccess(texture, filterMode)
+ , fMatrix(m)
+ , fCoordsType(coordsType) {
+ this->addTextureAccess(&fTextureAccess);
+}
+
+GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
+ const SkMatrix& m,
+ const GrTextureParams& params,
+ CoordsType coordsType)
+ : fTextureAccess(texture, params)
+ , fMatrix(m)
+ , fCoordsType(coordsType) {
+ this->addTextureAccess(&fTextureAccess);
+}
+
+GrSingleTextureEffect::~GrSingleTextureEffect() {
+}
diff --git a/gpu/effects/GrSingleTextureEffect.h b/gpu/effects/GrSingleTextureEffect.h
new file mode 100644
index 00000000..1331ae4a
--- /dev/null
+++ b/gpu/effects/GrSingleTextureEffect.h
@@ -0,0 +1,74 @@
+/*
+ * 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 GrSingleTextureEffect_DEFINED
+#define GrSingleTextureEffect_DEFINED
+
+#include "GrEffect.h"
+#include "SkMatrix.h"
+
+class GrTexture;
+
+/**
+ * A base class for effects that draw a single texture with a texture matrix. This effect has no
+ * backend implementations. One must be provided by the subclass.
+ */
+class GrSingleTextureEffect : public GrEffect {
+public:
+ virtual ~GrSingleTextureEffect();
+
+ const SkMatrix& getMatrix() const { return fMatrix; }
+
+ /** Indicates whether the matrix operates on local coords or positions */
+ CoordsType coordsType() const { return fCoordsType; }
+
+protected:
+ /** unfiltered, clamp mode */
+ GrSingleTextureEffect(GrTexture*, const SkMatrix&, CoordsType = kLocal_CoordsType);
+ /** clamp mode */
+ GrSingleTextureEffect(GrTexture*, const SkMatrix&, GrTextureParams::FilterMode filterMode,
+ CoordsType = kLocal_CoordsType);
+ GrSingleTextureEffect(GrTexture*,
+ const SkMatrix&,
+ const GrTextureParams&,
+ CoordsType = kLocal_CoordsType);
+
+ /**
+ * Helper for subclass onIsEqual() functions.
+ */
+ bool hasSameTextureParamsMatrixAndCoordsType(const GrSingleTextureEffect& other) const {
+ const GrTextureAccess& otherAccess = other.fTextureAccess;
+ // We don't have to check the accesses' swizzles because they are inferred from the texture.
+ return fTextureAccess.getTexture() == otherAccess.getTexture() &&
+ fTextureAccess.getParams() == otherAccess.getParams() &&
+ this->getMatrix().cheapEqualTo(other.getMatrix()) &&
+ fCoordsType == other.fCoordsType;
+ }
+
+ /**
+ * Can be used as a helper to implement subclass getConstantColorComponents(). It assumes that
+ * the subclass output color will be a modulation of the input color with a value read from the
+ * texture.
+ */
+ void updateConstantColorComponentsForModulation(GrColor* color, uint32_t* validFlags) const {
+ if ((*validFlags & kA_GrColorComponentFlag) && 0xFF == GrColorUnpackA(*color) &&
+ GrPixelConfigIsOpaque(this->texture(0)->config())) {
+ *validFlags = kA_GrColorComponentFlag;
+ } else {
+ *validFlags = 0;
+ }
+ }
+
+private:
+ GrTextureAccess fTextureAccess;
+ SkMatrix fMatrix;
+ CoordsType fCoordsType;
+
+ typedef GrEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrTextureDomainEffect.cpp b/gpu/effects/GrTextureDomainEffect.cpp
new file mode 100644
index 00000000..eb4001d9
--- /dev/null
+++ b/gpu/effects/GrTextureDomainEffect.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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 "GrTextureDomainEffect.h"
+#include "GrSimpleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "SkFloatingPoint.h"
+
+class GrGLTextureDomainEffect : public GrGLEffect {
+public:
+ GrGLTextureDomainEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+ GrGLUniformManager::UniformHandle fNameUni;
+ GrGLEffectMatrix fEffectMatrix;
+ GrGLfloat fPrevDomain[4];
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLTextureDomainEffect::GrGLTextureDomainEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fNameUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrTextureDomainEffect>().coordsType()) {
+ fPrevDomain[0] = SK_FloatNaN;
+}
+
+void GrGLTextureDomainEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const GrTextureDomainEffect& texDom = drawEffect.castEffect<GrTextureDomainEffect>();
+
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ const char* domain;
+ fNameUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "TexDom", &domain);
+ if (GrTextureDomainEffect::kClamp_WrapMode == texDom.wrapMode()) {
+
+ builder->fsCodeAppendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n",
+ coords, domain, domain);
+
+ builder->fsCodeAppendf("\t%s = ", outputColor);
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ samplers[0],
+ "clampCoord");
+ builder->fsCodeAppend(";\n");
+ } else {
+ GrAssert(GrTextureDomainEffect::kDecal_WrapMode == texDom.wrapMode());
+
+ if (kImagination_GrGLVendor == builder->ctxInfo().vendor()) {
+ // On the NexusS and GalaxyNexus, the other path (with the 'any'
+ // call) causes the compilation error "Calls to any function that
+ // may require a gradient calculation inside a conditional block
+ // may return undefined results". This appears to be an issue with
+ // the 'any' call since even the simple "result=black; if (any())
+ // result=white;" code fails to compile.
+ builder->fsCodeAppend("\tvec4 outside = vec4(0.0, 0.0, 0.0, 0.0);\n");
+ builder->fsCodeAppend("\tvec4 inside = ");
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ samplers[0],
+ coords);
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\tfloat x = abs(2.0*(%s.x - %s.x)/(%s.z - %s.x) - 1.0);\n",
+ coords, domain, domain, domain);
+ builder->fsCodeAppendf("\tfloat y = abs(2.0*(%s.y - %s.y)/(%s.w - %s.y) - 1.0);\n",
+ coords, domain, domain, domain);
+ builder->fsCodeAppend("\tfloat blend = step(1.0, max(x, y));\n");
+ builder->fsCodeAppendf("\t%s = mix(inside, outside, blend);\n", outputColor);
+ } else {
+ builder->fsCodeAppend("\tbvec4 outside;\n");
+ builder->fsCodeAppendf("\toutside.xy = lessThan(%s, %s.xy);\n", coords, domain);
+ builder->fsCodeAppendf("\toutside.zw = greaterThan(%s, %s.zw);\n", coords, domain);
+ builder->fsCodeAppendf("\t%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ", outputColor);
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ samplers[0],
+ coords);
+ builder->fsCodeAppend(";\n");
+ }
+ }
+}
+
+void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrTextureDomainEffect& texDom = drawEffect.castEffect<GrTextureDomainEffect>();
+ const SkRect& domain = texDom.domain();
+
+ float values[4] = {
+ SkScalarToFloat(domain.left()),
+ SkScalarToFloat(domain.top()),
+ SkScalarToFloat(domain.right()),
+ SkScalarToFloat(domain.bottom())
+ };
+ // vertical flip if necessary
+ if (kBottomLeft_GrSurfaceOrigin == texDom.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]);
+ }
+ if (0 != memcmp(values, fPrevDomain, 4 * sizeof(GrGLfloat))) {
+ uman.set4fv(fNameUni, 0, 1, values);
+ memcpy(fPrevDomain, values, 4 * sizeof(GrGLfloat));
+ }
+ fEffectMatrix.setData(uman,
+ texDom.getMatrix(),
+ drawEffect,
+ texDom.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLTextureDomainEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrTextureDomainEffect& texDom = drawEffect.castEffect<GrTextureDomainEffect>();
+ EffectKey key = texDom.wrapMode();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(texDom.getMatrix(),
+ drawEffect,
+ texDom.coordsType(),
+ texDom.texture(0));
+ return key | matrixKey;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrEffectRef* GrTextureDomainEffect::Create(GrTexture* texture,
+ const SkMatrix& matrix,
+ const SkRect& domain,
+ WrapMode wrapMode,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType coordsType) {
+ static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1};
+ if (kClamp_WrapMode == wrapMode && domain.contains(kFullRect)) {
+ return GrSimpleTextureEffect::Create(texture, matrix, filterMode);
+ } 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);
+
+ AutoEffectUnref effect(SkNEW_ARGS(GrTextureDomainEffect, (texture,
+ matrix,
+ clippedDomain,
+ wrapMode,
+ filterMode,
+ coordsType)));
+ return CreateEffectRef(effect);
+
+ }
+}
+
+GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture,
+ const SkMatrix& matrix,
+ const SkRect& domain,
+ WrapMode wrapMode,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType coordsType)
+ : GrSingleTextureEffect(texture, matrix, filterMode, coordsType)
+ , fWrapMode(wrapMode)
+ , fTextureDomain(domain) {
+}
+
+GrTextureDomainEffect::~GrTextureDomainEffect() {
+
+}
+
+const GrBackendEffectFactory& GrTextureDomainEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrTextureDomainEffect>::getInstance();
+}
+
+bool GrTextureDomainEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrTextureDomainEffect& s = CastEffect<GrTextureDomainEffect>(sBase);
+ return this->hasSameTextureParamsMatrixAndCoordsType(s) &&
+ this->fTextureDomain == s.fTextureDomain;
+}
+
+void GrTextureDomainEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ if (kDecal_WrapMode == fWrapMode) {
+ *validFlags = 0;
+ } else {
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrTextureDomainEffect);
+
+GrEffectRef* GrTextureDomainEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ SkRect 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);
+ WrapMode wrapMode = random->nextBool() ? kClamp_WrapMode : kDecal_WrapMode;
+ const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random);
+ bool bilerp = random->nextBool();
+ CoordsType coords = random->nextBool() ? kLocal_CoordsType : kPosition_CoordsType;
+ return GrTextureDomainEffect::Create(textures[texIdx],
+ matrix,
+ domain,
+ wrapMode,
+ bilerp ? GrTextureParams::kBilerp_FilterMode : GrTextureParams::kNone_FilterMode,
+ coords);
+}
diff --git a/gpu/effects/GrTextureDomainEffect.h b/gpu/effects/GrTextureDomainEffect.h
new file mode 100644
index 00000000..d07f2fc0
--- /dev/null
+++ b/gpu/effects/GrTextureDomainEffect.h
@@ -0,0 +1,89 @@
+/*
+ * 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 GrTextureDomainEffect_DEFINED
+#define GrTextureDomainEffect_DEFINED
+
+#include "GrSingleTextureEffect.h"
+
+class GrGLTextureDomainEffect;
+struct SkRect;
+
+/**
+ * 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:
+ /**
+ * 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 GrEffectRef* Create(GrTexture*,
+ const SkMatrix&,
+ const SkRect& domain,
+ WrapMode,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType = kLocal_CoordsType);
+
+ virtual ~GrTextureDomainEffect();
+
+ static const char* Name() { return "TextureDomain"; }
+
+ typedef GrGLTextureDomainEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ const SkRect& domain() const { return fTextureDomain; }
+ WrapMode wrapMode() const { return fWrapMode; }
+
+ /* 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;
+ }
+
+protected:
+ WrapMode fWrapMode;
+ SkRect fTextureDomain;
+
+private:
+ GrTextureDomainEffect(GrTexture*,
+ const SkMatrix&,
+ const SkRect& domain,
+ WrapMode,
+ GrTextureParams::FilterMode filterMode,
+ CoordsType type);
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+#endif
diff --git a/gpu/effects/GrTextureStripAtlas.cpp b/gpu/effects/GrTextureStripAtlas.cpp
new file mode 100644
index 00000000..e1caceca
--- /dev/null
+++ b/gpu/effects/GrTextureStripAtlas.cpp
@@ -0,0 +1,348 @@
+
+/*
+ * 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 "GrTextureStripAtlas.h"
+#include "SkPixelRef.h"
+#include "SkTSearch.h"
+#include "GrTexture.h"
+
+#ifdef SK_DEBUG
+ #define VALIDATE this->validate()
+#else
+ #define VALIDATE
+#endif
+
+int32_t GrTextureStripAtlas::gCacheCount = 0;
+
+GrTHashTable<GrTextureStripAtlas::AtlasEntry,
+ GrTextureStripAtlas::AtlasHashKey, 8>*
+ GrTextureStripAtlas::gAtlasCache = NULL;
+
+GrTHashTable<GrTextureStripAtlas::AtlasEntry, GrTextureStripAtlas::AtlasHashKey, 8>*
+GrTextureStripAtlas::GetCache() {
+
+ if (NULL == gAtlasCache) {
+ gAtlasCache = SkNEW((GrTHashTable<AtlasEntry, AtlasHashKey, 8>));
+ }
+
+ return gAtlasCache;
+}
+
+// Remove the specified atlas from the cache
+void GrTextureStripAtlas::CleanUp(const GrContext*, void* info) {
+ GrAssert(NULL != info);
+
+ AtlasEntry* entry = static_cast<AtlasEntry*>(info);
+
+ // remove the cache entry
+ GetCache()->remove(entry->fKey, entry);
+
+ // remove the actual entry
+ SkDELETE(entry);
+
+ if (0 == GetCache()->count()) {
+ SkDELETE(gAtlasCache);
+ gAtlasCache = NULL;
+ }
+}
+
+GrTextureStripAtlas* GrTextureStripAtlas::GetAtlas(const GrTextureStripAtlas::Desc& desc) {
+ AtlasHashKey key;
+ key.setKeyData(desc.asKey());
+ AtlasEntry* entry = GetCache()->find(key);
+ if (NULL == entry) {
+ entry = SkNEW(AtlasEntry);
+
+ entry->fAtlas = SkNEW_ARGS(GrTextureStripAtlas, (desc));
+ entry->fKey = key;
+
+ desc.fContext->addCleanUp(CleanUp, entry);
+
+ GetCache()->insert(key, entry);
+ }
+
+ return entry->fAtlas;
+}
+
+GrTextureStripAtlas::GrTextureStripAtlas(GrTextureStripAtlas::Desc desc)
+ : fCacheKey(sk_atomic_inc(&gCacheCount))
+ , fLockedRows(0)
+ , fDesc(desc)
+ , fNumRows(desc.fHeight / desc.fRowHeight)
+ , fTexture(NULL)
+ , fRows(SkNEW_ARRAY(AtlasRow, fNumRows))
+ , fLRUFront(NULL)
+ , fLRUBack(NULL) {
+ GrAssert(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
+ this->initLRU();
+ VALIDATE;
+}
+
+GrTextureStripAtlas::~GrTextureStripAtlas() {
+ SkDELETE_ARRAY(fRows);
+}
+
+int GrTextureStripAtlas::lockRow(const SkBitmap& data) {
+ VALIDATE;
+ if (0 == fLockedRows) {
+ this->lockTexture();
+ }
+
+ int key = data.getGenerationID();
+ int rowNumber = -1;
+ int index = this->searchByKey(key);
+
+ if (index >= 0) {
+ // We already have the data in a row, so we can just return that row
+ AtlasRow* row = fKeyTable[index];
+ if (0 == row->fLocks) {
+ this->removeFromLRU(row);
+ }
+ ++row->fLocks;
+ ++fLockedRows;
+
+ // Since all the rows are always stored in a contiguous array, we can save the memory
+ // required for storing row numbers and just compute it with some pointer arithmetic
+ rowNumber = static_cast<int>(row - fRows);
+ } else {
+ // ~index is the index where we will insert the new key to keep things sorted
+ index = ~index;
+
+ // We don't have this data cached, so pick the least recently used row to copy into
+ AtlasRow* row = this->getLRU();
+
+ ++fLockedRows;
+
+ if (NULL == row) {
+ // force a flush, which should unlock all the rows; then try again
+ fDesc.fContext->flush();
+ row = this->getLRU();
+ if (NULL == row) {
+ --fLockedRows;
+ return -1;
+ }
+ }
+
+ this->removeFromLRU(row);
+
+ uint32_t oldKey = row->fKey;
+
+ // If we are writing into a row that already held bitmap data, we need to remove the
+ // reference to that genID which is stored in our sorted table of key values.
+ if (oldKey != kEmptyAtlasRowKey) {
+
+ // Find the entry in the list; if it's before the index where we plan on adding the new
+ // entry, we decrement since it will shift elements ahead of it back by one.
+ int oldIndex = this->searchByKey(oldKey);
+ if (oldIndex < index) {
+ --index;
+ }
+
+ fKeyTable.remove(oldIndex);
+ }
+
+ row->fKey = key;
+ row->fLocks = 1;
+ fKeyTable.insert(index, 1, &row);
+ rowNumber = static_cast<int>(row - fRows);
+
+ SkAutoLockPixels lock(data);
+
+ // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
+ // that is not currently in use
+ fDesc.fContext->writeTexturePixels(fTexture,
+ 0, rowNumber * fDesc.fRowHeight,
+ fDesc.fWidth, fDesc.fRowHeight,
+ SkBitmapConfig2GrPixelConfig(data.config()),
+ data.getPixels(),
+ data.rowBytes(),
+ GrContext::kDontFlush_PixelOpsFlag);
+ }
+
+ GrAssert(rowNumber >= 0);
+ VALIDATE;
+ return rowNumber;
+}
+
+void GrTextureStripAtlas::unlockRow(int row) {
+ VALIDATE;
+ --fRows[row].fLocks;
+ --fLockedRows;
+ GrAssert(fRows[row].fLocks >= 0 && fLockedRows >= 0);
+ if (0 == fRows[row].fLocks) {
+ this->appendLRU(fRows + row);
+ }
+ if (0 == fLockedRows) {
+ this->unlockTexture();
+ }
+ VALIDATE;
+}
+
+GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
+ // Front is least-recently-used
+ AtlasRow* row = fLRUFront;
+ return row;
+}
+
+void GrTextureStripAtlas::lockTexture() {
+ GrTextureParams params;
+ GrTextureDesc texDesc;
+ texDesc.fWidth = fDesc.fWidth;
+ texDesc.fHeight = fDesc.fHeight;
+ texDesc.fConfig = fDesc.fConfig;
+
+ static const GrCacheID::Domain gTextureStripAtlasDomain = GrCacheID::GenerateDomain();
+ GrCacheID::Key key;
+ *key.fData32 = fCacheKey;
+ memset(key.fData32 + 1, 0, sizeof(key) - sizeof(uint32_t));
+ GrCacheID cacheID(gTextureStripAtlasDomain, key);
+
+ fTexture = fDesc.fContext->findAndRefTexture(texDesc, cacheID, &params);
+ if (NULL == fTexture) {
+ fTexture = fDesc.fContext->createTexture(&params, texDesc, cacheID, NULL, 0);
+ // This is a new texture, so all of our cache info is now invalid
+ this->initLRU();
+ fKeyTable.rewind();
+ }
+ GrAssert(NULL != fTexture);
+}
+
+void GrTextureStripAtlas::unlockTexture() {
+ GrAssert(NULL != fTexture && 0 == fLockedRows);
+ fTexture->unref();
+ fTexture = NULL;
+ fDesc.fContext->purgeCache();
+}
+
+void GrTextureStripAtlas::initLRU() {
+ fLRUFront = NULL;
+ fLRUBack = NULL;
+ // Initially all the rows are in the LRU list
+ for (int i = 0; i < fNumRows; ++i) {
+ fRows[i].fKey = kEmptyAtlasRowKey;
+ fRows[i].fNext = NULL;
+ fRows[i].fPrev = NULL;
+ this->appendLRU(fRows + i);
+ }
+ GrAssert(NULL == fLRUFront || NULL == fLRUFront->fPrev);
+ GrAssert(NULL == fLRUBack || NULL == fLRUBack->fNext);
+}
+
+void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
+ GrAssert(NULL == row->fPrev && NULL == row->fNext);
+ if (NULL == fLRUFront && NULL == fLRUBack) {
+ fLRUFront = row;
+ fLRUBack = row;
+ } else {
+ row->fPrev = fLRUBack;
+ fLRUBack->fNext = row;
+ fLRUBack = row;
+ }
+}
+
+void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
+ GrAssert(NULL != row);
+ if (NULL != row->fNext && NULL != row->fPrev) {
+ row->fPrev->fNext = row->fNext;
+ row->fNext->fPrev = row->fPrev;
+ } else {
+ if (NULL == row->fNext) {
+ GrAssert(row == fLRUBack);
+ fLRUBack = row->fPrev;
+ if (fLRUBack) {
+ fLRUBack->fNext = NULL;
+ }
+ }
+ if (NULL == row->fPrev) {
+ GrAssert(row == fLRUFront);
+ fLRUFront = row->fNext;
+ if (fLRUFront) {
+ fLRUFront->fPrev = NULL;
+ }
+ }
+ }
+ row->fNext = NULL;
+ row->fPrev = NULL;
+}
+
+int GrTextureStripAtlas::searchByKey(uint32_t key) {
+ AtlasRow target;
+ target.fKey = key;
+ return SkTSearch<const AtlasRow,
+ GrTextureStripAtlas::KeyLess>((const AtlasRow**)fKeyTable.begin(),
+ fKeyTable.count(),
+ &target,
+ sizeof(AtlasRow*));
+}
+
+#ifdef SK_DEBUG
+void GrTextureStripAtlas::validate() {
+
+ // Our key table should be sorted
+ uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
+ for (int i = 1; i < fKeyTable.count(); ++i) {
+ GrAssert(prev < fKeyTable[i]->fKey);
+ GrAssert(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
+ prev = fKeyTable[i]->fKey;
+ }
+
+ int lruCount = 0;
+ // Validate LRU pointers, and count LRU entries
+ GrAssert(NULL == fLRUFront || NULL == fLRUFront->fPrev);
+ GrAssert(NULL == fLRUBack || NULL == fLRUBack->fNext);
+ for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
+ if (NULL == r->fNext) {
+ GrAssert(r == fLRUBack);
+ } else {
+ GrAssert(r->fNext->fPrev == r);
+ }
+ ++lruCount;
+ }
+
+ int rowLocks = 0;
+ int freeRows = 0;
+
+ for (int i = 0; i < fNumRows; ++i) {
+ rowLocks += fRows[i].fLocks;
+ if (0 == fRows[i].fLocks) {
+ ++freeRows;
+ bool inLRU = false;
+ // Step through the LRU and make sure it's present
+ for (AtlasRow* r = fLRUFront; r != NULL; r = r->fNext) {
+ if (r == &fRows[i]) {
+ inLRU = true;
+ break;
+ }
+ }
+ GrAssert(inLRU);
+ } else {
+ // If we are locked, we should have a key
+ GrAssert(kEmptyAtlasRowKey != fRows[i].fKey);
+ }
+
+ // If we have a key != kEmptyAtlasRowKey, it should be in the key table
+ GrAssert(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
+ }
+
+ // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
+ // in which case we'll have one more lock than recorded in the rows (to represent the pending
+ // lock of a row; which ensures we don't unlock the texture prematurely).
+ GrAssert(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
+
+ // We should have one lru entry for each free row
+ GrAssert(freeRows == lruCount);
+
+ // If we have locked rows, we should have a locked texture, otherwise
+ // it should be unlocked
+ if (fLockedRows == 0) {
+ GrAssert(NULL == fTexture);
+ } else {
+ GrAssert(NULL != fTexture);
+ }
+}
+#endif
diff --git a/gpu/effects/GrTextureStripAtlas.h b/gpu/effects/GrTextureStripAtlas.h
new file mode 100644
index 00000000..0148665a
--- /dev/null
+++ b/gpu/effects/GrTextureStripAtlas.h
@@ -0,0 +1,181 @@
+
+/*
+ * 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 GrTextureStripAtlas_DEFINED
+#define GrTextureStripAtlas_DEFINED
+
+#include "SkBitmap.h"
+#include "GrTHashCache.h"
+#include "SkGr.h"
+#include "SkTDArray.h"
+#include "GrBinHashKey.h"
+
+/**
+ * Maintains a single large texture whose rows store many textures of a small fixed height,
+ * stored in rows across the x-axis such that we can safely wrap/repeat them horizontally.
+ */
+class GrTextureStripAtlas {
+public:
+ /**
+ * Descriptor struct which we'll use as a hash table key
+ **/
+ struct Desc {
+ Desc() { memset(this, 0, sizeof(*this)); }
+ uint16_t fWidth, fHeight, fRowHeight;
+ GrPixelConfig fConfig;
+ GrContext* fContext;
+ const uint32_t* asKey() const { return reinterpret_cast<const uint32_t*>(this); }
+ };
+
+ /**
+ * Try to find an atlas with the required parameters, creates a new one if necessary
+ */
+ static GrTextureStripAtlas* GetAtlas(const Desc& desc);
+
+ ~GrTextureStripAtlas();
+
+ /**
+ * Add a texture to the atlas
+ * @param data Bitmap data to copy into the row
+ * @return The row index we inserted into, or -1 if we failed to find an open row. The caller
+ * is responsible for calling unlockRow() with this row index when it's done with it.
+ */
+ int lockRow(const SkBitmap& data);
+ void unlockRow(int row);
+
+ /**
+ * These functions help turn an integer row index in [0, 1, 2, ... numRows] into a scalar y
+ * texture coordinate in [0, 1] that we can use in a shader.
+ *
+ * If a regular texture access without using the atlas looks like:
+ *
+ * texture2D(sampler, vec2(x, y))
+ *
+ * Then when using the atlas we'd replace it with:
+ *
+ * texture2D(sampler, vec2(x, yOffset + y * scaleFactor))
+ *
+ * Where yOffset, returned by getYOffset(), is the offset to the start of the row within the
+ * atlas and scaleFactor, returned by getVerticalScaleFactor(), is the y-scale of the row,
+ * relative to the height of the overall atlas texture.
+ */
+ 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; }
+
+private:
+
+ // Key to indicate an atlas row without any meaningful data stored in it
+ const static uint32_t kEmptyAtlasRowKey = 0xffffffff;
+
+ /**
+ * The state of a single row in our cache, next/prev pointers allow these to be chained
+ * together to represent LRU status
+ */
+ struct AtlasRow : public GrNoncopyable {
+ AtlasRow() : fKey(kEmptyAtlasRowKey), fLocks(0), fNext(NULL), fPrev(NULL) { }
+ // GenerationID of the bitmap that is represented by this row, 0xffffffff means "empty"
+ uint32_t fKey;
+ // How many times this has been locked (0 == unlocked)
+ int32_t fLocks;
+ // We maintain an LRU linked list between unlocked nodes with these pointers
+ AtlasRow* fNext;
+ AtlasRow* fPrev;
+ };
+
+ /**
+ * We'll only allow construction via the static GrTextureStripAtlas::GetAtlas
+ */
+ GrTextureStripAtlas(Desc desc);
+
+ void lockTexture();
+ void unlockTexture();
+
+ /**
+ * Initialize our LRU list (if one already exists, clear it and start anew)
+ */
+ void initLRU();
+
+ /**
+ * Grabs the least recently used free row out of the LRU list, returns NULL if no rows are free.
+ */
+ AtlasRow* getLRU();
+
+ void appendLRU(AtlasRow* row);
+ void removeFromLRU(AtlasRow* row);
+
+ /**
+ * Searches the key table for a key and returns the index if found; if not found, it returns
+ * the bitwise not of the index at which we could insert the key to maintain a sorted list.
+ **/
+ int searchByKey(uint32_t key);
+
+ /**
+ * Compare two atlas rows by key, so we can sort/search by key
+ */
+ static bool KeyLess(const AtlasRow& lhs, const AtlasRow& rhs) {
+ return lhs.fKey < rhs.fKey;
+ }
+
+#ifdef SK_DEBUG
+ void validate();
+#endif
+
+ /**
+ * Clean up callback registered with GrContext. Allows this class to
+ * free up any allocated AtlasEntry and GrTextureStripAtlas objects
+ */
+ static void CleanUp(const GrContext* context, void* info);
+
+ // Hash table entry for atlases
+ class AtlasEntry;
+ typedef GrTBinHashKey<AtlasEntry, sizeof(GrTextureStripAtlas::Desc)> AtlasHashKey;
+ class AtlasEntry : public ::GrNoncopyable {
+ public:
+ AtlasEntry() : fAtlas(NULL) {}
+ ~AtlasEntry() { SkDELETE(fAtlas); }
+ int compare(const AtlasHashKey& key) const { return fKey.compare(key); }
+ AtlasHashKey fKey;
+ GrTextureStripAtlas* fAtlas;
+ };
+
+ static GrTHashTable<AtlasEntry, AtlasHashKey, 8>* gAtlasCache;
+
+ static GrTHashTable<AtlasEntry, AtlasHashKey, 8>* GetCache();
+
+ // We increment gCacheCount for each atlas
+ static int32_t gCacheCount;
+
+ // A unique ID for this texture (formed with: gCacheCount++), so we can be sure that if we
+ // get a texture back from the texture cache, that it's the same one we last used.
+ const int32_t fCacheKey;
+
+ // Total locks on all rows (when this reaches zero, we can unlock our texture)
+ int32_t fLockedRows;
+
+ const Desc fDesc;
+ const uint16_t fNumRows;
+ GrTexture* fTexture;
+
+ // Array of AtlasRows which store the state of all our rows. Stored in a contiguous array, in
+ // order that they appear in our texture, this means we can subtract this pointer from a row
+ // pointer to get its index in the texture, and can save storing a row number in AtlasRow.
+ AtlasRow* fRows;
+
+ // Head and tail for linked list of least-recently-used rows (front = least recently used).
+ // Note that when a texture is locked, it gets removed from this list until it is unlocked.
+ AtlasRow* fLRUFront;
+ AtlasRow* fLRUBack;
+
+ // A list of pointers to AtlasRows that currently contain cached images, sorted by key
+ SkTDArray<AtlasRow*> fKeyTable;
+};
+
+#endif
diff --git a/gpu/gl/GrGLBufferImpl.cpp b/gpu/gl/GrGLBufferImpl.cpp
new file mode 100644
index 00000000..2dfa9d5d
--- /dev/null
+++ b/gpu/gl/GrGLBufferImpl.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLBufferImpl.h"
+#include "GrGpuGL.h"
+
+#define GL_CALL(GPU, X) GR_GL_CALL(GPU->glInterface(), X)
+
+#if GR_DEBUG
+#define VALIDATE() this->validate()
+#else
+#define VALIDATE() do {} while(false)
+#endif
+
+// GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a client's vertex buffer
+// objects are implemented as client-side-arrays on tile-deferred architectures.
+#define DYNAMIC_USAGE_PARAM GR_GL_STREAM_DRAW
+
+GrGLBufferImpl::GrGLBufferImpl(GrGpuGL* gpu, const Desc& desc, GrGLenum bufferType)
+ : fDesc(desc)
+ , fBufferType(bufferType)
+ , fLockPtr(NULL) {
+ if (0 == desc.fID) {
+ fCPUData = sk_malloc_flags(desc.fSizeInBytes, SK_MALLOC_THROW);
+ } else {
+ fCPUData = NULL;
+ }
+ VALIDATE();
+}
+
+void GrGLBufferImpl::release(GrGpuGL* gpu) {
+ // make sure we've not been abandoned or already released
+ if (NULL != fCPUData) {
+ VALIDATE();
+ sk_free(fCPUData);
+ fCPUData = NULL;
+ } else if (fDesc.fID && !fDesc.fIsWrapped) {
+ VALIDATE();
+ GL_CALL(gpu, DeleteBuffers(1, &fDesc.fID));
+ if (GR_GL_ARRAY_BUFFER == fBufferType) {
+ gpu->notifyVertexBufferDelete(fDesc.fID);
+ } else {
+ GrAssert(GR_GL_ELEMENT_ARRAY_BUFFER == fBufferType);
+ gpu->notifyIndexBufferDelete(fDesc.fID);
+ }
+ fDesc.fID = 0;
+ }
+ fLockPtr = NULL;
+}
+
+void GrGLBufferImpl::abandon() {
+ fDesc.fID = 0;
+ fLockPtr = NULL;
+ sk_free(fCPUData);
+ fCPUData = NULL;
+}
+
+void GrGLBufferImpl::bind(GrGpuGL* gpu) const {
+ VALIDATE();
+ if (GR_GL_ARRAY_BUFFER == fBufferType) {
+ gpu->bindVertexBuffer(fDesc.fID);
+ } else {
+ GrAssert(GR_GL_ELEMENT_ARRAY_BUFFER == fBufferType);
+ gpu->bindIndexBufferAndDefaultVertexArray(fDesc.fID);
+ }
+}
+
+void* GrGLBufferImpl::lock(GrGpuGL* gpu) {
+ VALIDATE();
+ GrAssert(!this->isLocked());
+ if (0 == fDesc.fID) {
+ fLockPtr = fCPUData;
+ } else if (gpu->caps()->bufferLockSupport()) {
+ this->bind(gpu);
+ // Let driver know it can discard the old data
+ GL_CALL(gpu, BufferData(fBufferType,
+ fDesc.fSizeInBytes,
+ NULL,
+ fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW));
+ GR_GL_CALL_RET(gpu->glInterface(),
+ fLockPtr,
+ MapBuffer(fBufferType, GR_GL_WRITE_ONLY));
+ }
+ return fLockPtr;
+}
+
+void GrGLBufferImpl::unlock(GrGpuGL* gpu) {
+ VALIDATE();
+ GrAssert(this->isLocked());
+ if (0 != fDesc.fID) {
+ GrAssert(gpu->caps()->bufferLockSupport());
+ this->bind(gpu);
+ GL_CALL(gpu, UnmapBuffer(fBufferType));
+ }
+ fLockPtr = NULL;
+}
+
+bool GrGLBufferImpl::isLocked() const {
+ VALIDATE();
+ return NULL != fLockPtr;
+}
+
+bool GrGLBufferImpl::updateData(GrGpuGL* gpu, const void* src, size_t srcSizeInBytes) {
+ GrAssert(!this->isLocked());
+ VALIDATE();
+ if (srcSizeInBytes > fDesc.fSizeInBytes) {
+ return false;
+ }
+ if (0 == fDesc.fID) {
+ memcpy(fCPUData, src, srcSizeInBytes);
+ return true;
+ }
+ this->bind(gpu);
+ GrGLenum usage = fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW;
+
+#if GR_GL_USE_BUFFER_DATA_NULL_HINT
+ if (fDesc.fSizeInBytes == srcSizeInBytes) {
+ GL_CALL(gpu, BufferData(fBufferType, srcSizeInBytes, src, usage));
+ } else {
+ // Before we call glBufferSubData we give the driver a hint using
+ // glBufferData with NULL. This makes the old buffer contents
+ // inaccessible to future draws. The GPU may still be processing
+ // draws that reference the old contents. With this hint it can
+ // assign a different allocation for the new contents to avoid
+ // flushing the gpu past draws consuming the old contents.
+ GL_CALL(gpu, BufferData(fBufferType, fDesc.fSizeInBytes, NULL, usage));
+ GL_CALL(gpu, BufferSubData(fBufferType, 0, srcSizeInBytes, src));
+ }
+#else
+ // Note that we're cheating on the size here. Currently no methods
+ // allow a partial update that preserves contents of non-updated
+ // portions of the buffer (lock() does a glBufferData(..size, NULL..))
+ bool doSubData = false;
+#if GR_GL_MAC_BUFFER_OBJECT_PERFOMANCE_WORKAROUND
+ static int N = 0;
+ // 128 was chosen experimentally. At 256 a slight hitchiness was noticed
+ // when dragging a Chromium window around with a canvas tab backgrounded.
+ doSubData = 0 == (N % 128);
+ ++N;
+#endif
+ if (doSubData) {
+ // The workaround is to do a glBufferData followed by glBufferSubData.
+ // Chromium's command buffer may turn a glBufferSubData where the size
+ // exactly matches the buffer size into a glBufferData. So we tack 1
+ // extra byte onto the glBufferData.
+ GL_CALL(gpu, BufferData(fBufferType, srcSizeInBytes + 1, NULL, usage));
+ GL_CALL(gpu, BufferSubData(fBufferType, 0, srcSizeInBytes, src));
+ } else {
+ GL_CALL(gpu, BufferData(fBufferType, srcSizeInBytes, src, usage));
+ }
+#endif
+ return true;
+}
+
+void GrGLBufferImpl::validate() const {
+ GrAssert(GR_GL_ARRAY_BUFFER == fBufferType || GR_GL_ELEMENT_ARRAY_BUFFER == fBufferType);
+ // The following assert isn't valid when the buffer has been abandoned:
+ // GrAssert((0 == fDesc.fID) == (NULL != fCPUData));
+ GrAssert(0 != fDesc.fID || !fDesc.fIsWrapped);
+ GrAssert(NULL == fCPUData || NULL == fLockPtr || fCPUData == fLockPtr);
+}
diff --git a/gpu/gl/GrGLBufferImpl.h b/gpu/gl/GrGLBufferImpl.h
new file mode 100644
index 00000000..1fd8ce07
--- /dev/null
+++ b/gpu/gl/GrGLBufferImpl.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLBufferImpl_DEFINED
+#define GrGLBufferImpl_DEFINED
+
+#include "GrNoncopyable.h"
+#include "gl/GrGLFunctions.h"
+
+class GrGpuGL;
+
+/**
+ * This class serves as the implementation of GrGL*Buffer classes. It was written to avoid code
+ * duplication in those classes.
+ */
+class GrGLBufferImpl : public GrNoncopyable {
+public:
+ struct Desc {
+ bool fIsWrapped;
+ GrGLuint fID; // set to 0 to indicate buffer is CPU-backed and not a VBO.
+ size_t fSizeInBytes;
+ bool fDynamic;
+ };
+
+ GrGLBufferImpl(GrGpuGL*, const Desc&, GrGLenum bufferType);
+ ~GrGLBufferImpl() {
+ // either release or abandon should have been called by the owner of this object.
+ GrAssert(0 == fDesc.fID);
+ }
+
+ void abandon();
+ void release(GrGpuGL* gpu);
+
+ GrGLuint bufferID() const { return fDesc.fID; }
+ size_t baseOffset() const { return reinterpret_cast<size_t>(fCPUData); }
+
+ void bind(GrGpuGL* gpu) const;
+
+ void* lock(GrGpuGL* gpu);
+ void* lockPtr() const { return fLockPtr; }
+ void unlock(GrGpuGL* gpu);
+ bool isLocked() const;
+ bool updateData(GrGpuGL* gpu, const void* src, size_t srcSizeInBytes);
+
+private:
+ void validate() const;
+
+ Desc fDesc;
+ GrGLenum fBufferType; // GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER
+ void* fCPUData;
+ void* fLockPtr;
+
+ typedef GrNoncopyable INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLCaps.cpp b/gpu/gl/GrGLCaps.cpp
new file mode 100644
index 00000000..ec5b8215
--- /dev/null
+++ b/gpu/gl/GrGLCaps.cpp
@@ -0,0 +1,599 @@
+/*
+ * 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 "GrGLCaps.h"
+#include "GrGLContext.h"
+#include "SkTSearch.h"
+#include "SkTSort.h"
+
+SK_DEFINE_INST_COUNT(GrGLCaps)
+
+GrGLCaps::GrGLCaps() {
+ this->reset();
+}
+
+void GrGLCaps::reset() {
+ INHERITED::reset();
+
+ fVerifiedColorConfigs.reset();
+ fStencilFormats.reset();
+ fStencilVerifiedColorConfigs.reset();
+ fMSFBOType = kNone_MSFBOType;
+ fCoverageAAType = kNone_CoverageAAType;
+ fFBFetchType = kNone_FBFetchType;
+ fMaxFragmentUniformVectors = 0;
+ fMaxVertexAttributes = 0;
+ fMaxFragmentTextureUnits = 0;
+ fRGBA8RenderbufferSupport = false;
+ fBGRAFormatSupport = false;
+ fBGRAIsInternalFormat = false;
+ fTextureSwizzleSupport = false;
+ fUnpackRowLengthSupport = false;
+ fUnpackFlipYSupport = false;
+ fPackRowLengthSupport = false;
+ fPackFlipYSupport = false;
+ fTextureUsageSupport = false;
+ fTexStorageSupport = false;
+ fTextureRedSupport = false;
+ fImagingSupport = false;
+ fTwoFormatLimit = false;
+ fFragCoordsConventionSupport = false;
+ fVertexArrayObjectSupport = false;
+ fUseNonVBOVertexAndIndexDynamicData = false;
+ fIsCoreProfile = false;
+ fDiscardFBSupport = false;
+}
+
+GrGLCaps::GrGLCaps(const GrGLCaps& caps) : GrDrawTargetCaps() {
+ *this = caps;
+}
+
+GrGLCaps& GrGLCaps::operator = (const GrGLCaps& caps) {
+ INHERITED::operator=(caps);
+ fVerifiedColorConfigs = caps.fVerifiedColorConfigs;
+ fStencilFormats = caps.fStencilFormats;
+ fStencilVerifiedColorConfigs = caps.fStencilVerifiedColorConfigs;
+ fMaxFragmentUniformVectors = caps.fMaxFragmentUniformVectors;
+ fMaxVertexAttributes = caps.fMaxVertexAttributes;
+ fMaxFragmentTextureUnits = caps.fMaxFragmentTextureUnits;
+ fMSFBOType = caps.fMSFBOType;
+ fCoverageAAType = caps.fCoverageAAType;
+ fMSAACoverageModes = caps.fMSAACoverageModes;
+ fFBFetchType = caps.fFBFetchType;
+ fRGBA8RenderbufferSupport = caps.fRGBA8RenderbufferSupport;
+ fBGRAFormatSupport = caps.fBGRAFormatSupport;
+ fBGRAIsInternalFormat = caps.fBGRAIsInternalFormat;
+ fTextureSwizzleSupport = caps.fTextureSwizzleSupport;
+ fUnpackRowLengthSupport = caps.fUnpackRowLengthSupport;
+ fUnpackFlipYSupport = caps.fUnpackFlipYSupport;
+ fPackRowLengthSupport = caps.fPackRowLengthSupport;
+ fPackFlipYSupport = caps.fPackFlipYSupport;
+ fTextureUsageSupport = caps.fTextureUsageSupport;
+ fTexStorageSupport = caps.fTexStorageSupport;
+ fTextureRedSupport = caps.fTextureRedSupport;
+ fImagingSupport = caps.fImagingSupport;
+ fTwoFormatLimit = caps.fTwoFormatLimit;
+ fFragCoordsConventionSupport = caps.fFragCoordsConventionSupport;
+ fVertexArrayObjectSupport = caps.fVertexArrayObjectSupport;
+ fUseNonVBOVertexAndIndexDynamicData = caps.fUseNonVBOVertexAndIndexDynamicData;
+ fIsCoreProfile = caps.fIsCoreProfile;
+ fDiscardFBSupport = caps.fDiscardFBSupport;
+
+ return *this;
+}
+
+void GrGLCaps::init(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) {
+
+ this->reset();
+ if (!ctxInfo.isInitialized()) {
+ return;
+ }
+
+ GrGLBinding binding = ctxInfo.binding();
+ GrGLVersion version = ctxInfo.version();
+
+ /**************************************************************************
+ * Caps specific to GrGLCaps
+ **************************************************************************/
+
+ if (kES2_GrGLBinding == binding) {
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS,
+ &fMaxFragmentUniformVectors);
+ } else {
+ GrAssert(kDesktop_GrGLBinding == binding);
+ GrGLint max;
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &max);
+ fMaxFragmentUniformVectors = max / 4;
+ }
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_VERTEX_ATTRIBS, &fMaxVertexAttributes);
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_IMAGE_UNITS, &fMaxFragmentTextureUnits);
+
+ if (kDesktop_GrGLBinding == binding) {
+ fRGBA8RenderbufferSupport = true;
+ } else {
+ fRGBA8RenderbufferSupport = ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
+ ctxInfo.hasExtension("GL_ARM_rgba8");
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ fBGRAFormatSupport = version >= GR_GL_VER(1,2) ||
+ ctxInfo.hasExtension("GL_EXT_bgra");
+ } else {
+ if (ctxInfo.hasExtension("GL_APPLE_texture_format_BGRA8888")) {
+ fBGRAFormatSupport = true;
+ } else if (ctxInfo.hasExtension("GL_EXT_texture_format_BGRA8888")) {
+ fBGRAFormatSupport = true;
+ fBGRAIsInternalFormat = true;
+ }
+ GrAssert(fBGRAFormatSupport ||
+ kSkia8888_GrPixelConfig != kBGRA_8888_GrPixelConfig);
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ fTextureSwizzleSupport = version >= GR_GL_VER(3,3) ||
+ ctxInfo.hasExtension("GL_ARB_texture_swizzle");
+ } else {
+ fTextureSwizzleSupport = false;
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ fUnpackRowLengthSupport = true;
+ fUnpackFlipYSupport = false;
+ fPackRowLengthSupport = true;
+ fPackFlipYSupport = false;
+ } else {
+ fUnpackRowLengthSupport =ctxInfo.hasExtension("GL_EXT_unpack_subimage");
+ fUnpackFlipYSupport = ctxInfo.hasExtension("GL_CHROMIUM_flipy");
+ // no extension for pack row length
+ fPackRowLengthSupport = false;
+ fPackFlipYSupport =
+ ctxInfo.hasExtension("GL_ANGLE_pack_reverse_row_order");
+ }
+
+ fTextureUsageSupport = (kES2_GrGLBinding == binding) &&
+ ctxInfo.hasExtension("GL_ANGLE_texture_usage");
+
+ // Tex storage is in desktop 4.2 and can be an extension to desktop or ES.
+ fTexStorageSupport = (kDesktop_GrGLBinding == binding &&
+ version >= GR_GL_VER(4,2)) ||
+ ctxInfo.hasExtension("GL_ARB_texture_storage") ||
+ ctxInfo.hasExtension("GL_EXT_texture_storage");
+
+ // ARB_texture_rg is part of OpenGL 3.0, but mesa doesn't support it if
+ // it doesn't have ARB_texture_rg extension.
+ if (kDesktop_GrGLBinding == binding) {
+ if (ctxInfo.isMesa()) {
+ fTextureRedSupport = ctxInfo.hasExtension("GL_ARB_texture_rg");
+ } else {
+ fTextureRedSupport = version >= GR_GL_VER(3,0) ||
+ ctxInfo.hasExtension("GL_ARB_texture_rg");
+ }
+ } else {
+ fTextureRedSupport = ctxInfo.hasExtension("GL_EXT_texture_rg");
+ }
+
+ fImagingSupport = kDesktop_GrGLBinding == binding &&
+ ctxInfo.hasExtension("GL_ARB_imaging");
+
+ // ES 2 only guarantees RGBA/uchar + one other format/type combo for
+ // ReadPixels. The other format has to checked at run-time since it
+ // 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");
+ }
+
+ // SGX and Mali GPUs that are based on a tiled-deferred architecture that have trouble with
+ // frequently changing VBOs. We've measured a performance increase using non-VBO vertex
+ // data for dynamic content on these GPUs. Perhaps we should read the renderer string and
+ // limit this decision to specific GPU families rather than basing it on the vendor alone.
+ if (!GR_GL_MUST_USE_VBO &&
+ (kARM_GrGLVendor == ctxInfo.vendor() || kImagination_GrGLVendor == ctxInfo.vendor())) {
+ fUseNonVBOVertexAndIndexDynamicData = true;
+ }
+
+ if (kDesktop_GrGLBinding == binding && version >= GR_GL_VER(3, 2)) {
+ GrGLint profileMask;
+ GR_GL_GetIntegerv(gli, GR_GL_CONTEXT_PROFILE_MASK, &profileMask);
+ fIsCoreProfile = SkToBool(profileMask & GR_GL_CONTEXT_CORE_PROFILE_BIT);
+ }
+
+ fDiscardFBSupport = ctxInfo.hasExtension("GL_EXT_discard_framebuffer");
+
+ if (kDesktop_GrGLBinding == binding) {
+ fVertexArrayObjectSupport = version >= GR_GL_VER(3, 0) ||
+ ctxInfo.hasExtension("GL_ARB_vertex_array_object");
+ } else {
+ fVertexArrayObjectSupport = ctxInfo.hasExtension("GL_OES_vertex_array_object");
+ }
+
+ if (kES2_GrGLBinding == binding) {
+ if (ctxInfo.hasExtension("GL_EXT_shader_framebuffer_fetch")) {
+ fFBFetchType = kEXT_FBFetchType;
+ } else if (ctxInfo.hasExtension("GL_NV_shader_framebuffer_fetch")) {
+ fFBFetchType = kNV_FBFetchType;
+ }
+ }
+
+ this->initFSAASupport(ctxInfo, gli);
+ this->initStencilFormats(ctxInfo);
+
+ /**************************************************************************
+ * GrDrawTargetCaps fields
+ **************************************************************************/
+ GrGLint numFormats;
+ GR_GL_GetIntegerv(gli, GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numFormats);
+ if (numFormats) {
+ SkAutoSTMalloc<10, GrGLint> formats(numFormats);
+ GR_GL_GetIntegerv(gli, GR_GL_COMPRESSED_TEXTURE_FORMATS, formats);
+ for (int i = 0; i < numFormats; ++i) {
+ if (formats[i] == GR_GL_PALETTE8_RGBA8) {
+ f8BitPaletteSupport = true;
+ break;
+ }
+ }
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ // we could also look for GL_ATI_separate_stencil extension or
+ // GL_EXT_stencil_two_side but they use different function signatures
+ // than GL2.0+ (and than each other).
+ fTwoSidedStencilSupport = (ctxInfo.version() >= GR_GL_VER(2,0));
+ // supported on GL 1.4 and higher or by extension
+ fStencilWrapOpsSupport = (ctxInfo.version() >= GR_GL_VER(1,4)) ||
+ ctxInfo.hasExtension("GL_EXT_stencil_wrap");
+ } else {
+ // ES 2 has two sided stencil and stencil wrap
+ fTwoSidedStencilSupport = true;
+ fStencilWrapOpsSupport = true;
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ fBufferLockSupport = true; // we require VBO support and the desktop VBO extension includes
+ // glMapBuffer.
+ } else {
+ fBufferLockSupport = ctxInfo.hasExtension("GL_OES_mapbuffer");
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ if (ctxInfo.version() >= GR_GL_VER(2,0) ||
+ ctxInfo.hasExtension("GL_ARB_texture_non_power_of_two")) {
+ fNPOTTextureTileSupport = true;
+ } else {
+ fNPOTTextureTileSupport = false;
+ }
+ } else {
+ // Unextended ES2 supports NPOT textures with clamp_to_edge and non-mip filters only
+ fNPOTTextureTileSupport = ctxInfo.hasExtension("GL_OES_texture_npot");
+ }
+
+ fHWAALineSupport = (kDesktop_GrGLBinding == binding);
+
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_SIZE, &fMaxTextureSize);
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_RENDERBUFFER_SIZE, &fMaxRenderTargetSize);
+ // Our render targets are always created with textures as the color
+ // attachment, hence this min:
+ fMaxRenderTargetSize = GrMin(fMaxTextureSize, fMaxRenderTargetSize);
+
+ fPathStencilingSupport = GR_GL_USE_NV_PATH_RENDERING &&
+ ctxInfo.hasExtension("GL_NV_path_rendering");
+
+ fDstReadInShaderSupport = kNone_FBFetchType != fFBFetchType;
+
+#if 0
+ // This has to be temporarily disabled. On Android it causes the texture
+ // usage to become front loaded and the OS kills the process. It can
+ // be re-enabled once the more dynamic (ref-driven) cache clearing
+ // system is in place.
+ fReuseScratchTextures = kARM_GrGLVendor != ctxInfo.vendor();
+#else
+ fReuseScratchTextures = true;
+#endif
+
+ // Enable supported shader-related caps
+ if (kDesktop_GrGLBinding == binding) {
+ fDualSourceBlendingSupport = ctxInfo.version() >= GR_GL_VER(3,3) ||
+ ctxInfo.hasExtension("GL_ARB_blend_func_extended");
+ fShaderDerivativeSupport = true;
+ // we don't support GL_ARB_geometry_shader4, just GL 3.2+ GS
+ fGeometryShaderSupport = ctxInfo.version() >= GR_GL_VER(3,2) &&
+ ctxInfo.glslGeneration() >= k150_GrGLSLGeneration;
+ } else {
+ fShaderDerivativeSupport = ctxInfo.hasExtension("GL_OES_standard_derivatives");
+ }
+
+ if (GrGLCaps::kES_IMG_MsToTexture_MSFBOType == fMSFBOType) {
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_SAMPLES_IMG, &fMaxSampleCount);
+ } else if (GrGLCaps::kNone_MSFBOType != fMSFBOType) {
+ GR_GL_GetIntegerv(gli, GR_GL_MAX_SAMPLES, &fMaxSampleCount);
+ }
+}
+
+bool GrGLCaps::readPixelsSupported(const GrGLInterface* intf,
+ GrGLenum format,
+ GrGLenum type) const {
+ if (GR_GL_RGBA == format && GR_GL_UNSIGNED_BYTE == type) {
+ // ES 2 guarantees this format is supported
+ return true;
+ }
+
+ if (!fTwoFormatLimit) {
+ // not limited by ES 2's constraints
+ return true;
+ }
+
+ GrGLint otherFormat = GR_GL_RGBA;
+ GrGLint otherType = GR_GL_UNSIGNED_BYTE;
+
+ // The other supported format/type combo supported for ReadPixels
+ // can change based on which render target is bound
+ GR_GL_GetIntegerv(intf,
+ GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT,
+ &otherFormat);
+
+ GR_GL_GetIntegerv(intf,
+ GR_GL_IMPLEMENTATION_COLOR_READ_TYPE,
+ &otherType);
+
+ return (GrGLenum)otherFormat == format && (GrGLenum)otherType == type;
+}
+
+namespace {
+bool cov_mode_less(const GrGLCaps::MSAACoverageMode& left,
+ const GrGLCaps::MSAACoverageMode& right) {
+ if (left.fCoverageSampleCnt < right.fCoverageSampleCnt) {
+ return true;
+ } else if (right.fCoverageSampleCnt < left.fCoverageSampleCnt) {
+ return false;
+ } else if (left.fColorSampleCnt < right.fColorSampleCnt) {
+ return true;
+ }
+ return false;
+}
+}
+
+void GrGLCaps::initFSAASupport(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) {
+
+ fMSFBOType = kNone_MSFBOType;
+ if (kDesktop_GrGLBinding != ctxInfo.binding()) {
+ if (ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_multisample")) {
+ // chrome's extension is equivalent to the EXT msaa
+ // and fbo_blit extensions.
+ fMSFBOType = kDesktop_EXT_MSFBOType;
+ } else if (ctxInfo.hasExtension("GL_APPLE_framebuffer_multisample")) {
+ fMSFBOType = kES_Apple_MSFBOType;
+ } else if (ctxInfo.hasExtension("GL_EXT_multisampled_render_to_texture")) {
+ fMSFBOType = kES_EXT_MsToTexture_MSFBOType;
+ } else if (ctxInfo.hasExtension("GL_IMG_multisampled_render_to_texture")) {
+ fMSFBOType = kES_IMG_MsToTexture_MSFBOType;
+ }
+ } else {
+ if ((ctxInfo.version() >= GR_GL_VER(3,0)) ||
+ ctxInfo.hasExtension("GL_ARB_framebuffer_object")) {
+ fMSFBOType = GrGLCaps::kDesktop_ARB_MSFBOType;
+ } else if (ctxInfo.hasExtension("GL_EXT_framebuffer_multisample") &&
+ ctxInfo.hasExtension("GL_EXT_framebuffer_blit")) {
+ fMSFBOType = GrGLCaps::kDesktop_EXT_MSFBOType;
+ }
+ // TODO: We could populate fMSAACoverageModes using GetInternalformativ
+ // on GL 4.2+. It's format-specific, though. See also
+ // http://code.google.com/p/skia/issues/detail?id=470 about using actual
+ // rather than requested sample counts in cache key.
+ if (ctxInfo.hasExtension("GL_NV_framebuffer_multisample_coverage")) {
+ fCoverageAAType = kNVDesktop_CoverageAAType;
+ GrGLint count;
+ GR_GL_GetIntegerv(gli,
+ GR_GL_MAX_MULTISAMPLE_COVERAGE_MODES,
+ &count);
+ fMSAACoverageModes.setCount(count);
+ GR_GL_GetIntegerv(gli,
+ GR_GL_MULTISAMPLE_COVERAGE_MODES,
+ (int*)&fMSAACoverageModes[0]);
+ // The NV driver seems to return the modes already sorted but the
+ // spec doesn't require this. So we sort.
+ typedef SkTLessFunctionToFunctorAdaptor<MSAACoverageMode, cov_mode_less> SortFunctor;
+ SortFunctor sortFunctor;
+ SkTQSort<MSAACoverageMode, SortFunctor>(fMSAACoverageModes.begin(),
+ fMSAACoverageModes.end() - 1,
+ sortFunctor);
+ }
+ }
+}
+
+const GrGLCaps::MSAACoverageMode& GrGLCaps::getMSAACoverageMode(int desiredSampleCount) const {
+ static const MSAACoverageMode kNoneMode = {0, 0};
+ if (0 == fMSAACoverageModes.count()) {
+ return kNoneMode;
+ } else {
+ GrAssert(kNone_CoverageAAType != fCoverageAAType);
+ int max = (fMSAACoverageModes.end() - 1)->fCoverageSampleCnt;
+ desiredSampleCount = GrMin(desiredSampleCount, max);
+ MSAACoverageMode desiredMode = {desiredSampleCount, 0};
+ int idx = SkTSearch<const MSAACoverageMode, cov_mode_less>(&fMSAACoverageModes[0],
+ fMSAACoverageModes.count(),
+ desiredMode,
+ sizeof(MSAACoverageMode));
+ if (idx < 0) {
+ idx = ~idx;
+ }
+ GrAssert(idx >= 0 && idx < fMSAACoverageModes.count());
+ return fMSAACoverageModes[idx];
+ }
+}
+
+namespace {
+const GrGLuint kUnknownBitCount = GrGLStencilBuffer::kUnknownBitCount;
+}
+
+void GrGLCaps::initStencilFormats(const GrGLContextInfo& ctxInfo) {
+
+ // Build up list of legal stencil formats (though perhaps not supported on
+ // the particular gpu/driver) from most preferred to least.
+
+ // these consts are in order of most preferred to least preferred
+ // we don't bother with GL_STENCIL_INDEX1 or GL_DEPTH32F_STENCIL8
+
+ static const StencilFormat
+ // internal Format stencil bits total bits packed?
+ gS8 = {GR_GL_STENCIL_INDEX8, 8, 8, false},
+ gS16 = {GR_GL_STENCIL_INDEX16, 16, 16, false},
+ gD24S8 = {GR_GL_DEPTH24_STENCIL8, 8, 32, true },
+ gS4 = {GR_GL_STENCIL_INDEX4, 4, 4, false},
+ // gS = {GR_GL_STENCIL_INDEX, kUnknownBitCount, kUnknownBitCount, false},
+ gDS = {GR_GL_DEPTH_STENCIL, kUnknownBitCount, kUnknownBitCount, true };
+
+ if (kDesktop_GrGLBinding == ctxInfo.binding()) {
+ bool supportsPackedDS =
+ ctxInfo.version() >= GR_GL_VER(3,0) ||
+ ctxInfo.hasExtension("GL_EXT_packed_depth_stencil") ||
+ ctxInfo.hasExtension("GL_ARB_framebuffer_object");
+
+ // S1 thru S16 formats are in GL 3.0+, EXT_FBO, and ARB_FBO since we
+ // require FBO support we can expect these are legal formats and don't
+ // check. These also all support the unsized GL_STENCIL_INDEX.
+ fStencilFormats.push_back() = gS8;
+ fStencilFormats.push_back() = gS16;
+ if (supportsPackedDS) {
+ fStencilFormats.push_back() = gD24S8;
+ }
+ fStencilFormats.push_back() = gS4;
+ if (supportsPackedDS) {
+ fStencilFormats.push_back() = gDS;
+ }
+ } else {
+ // ES2 has STENCIL_INDEX8 without extensions but requires extensions
+ // for other formats.
+ // ES doesn't support using the unsized format.
+
+ fStencilFormats.push_back() = gS8;
+ //fStencilFormats.push_back() = gS16;
+ if (ctxInfo.hasExtension("GL_OES_packed_depth_stencil")) {
+ fStencilFormats.push_back() = gD24S8;
+ }
+ if (ctxInfo.hasExtension("GL_OES_stencil4")) {
+ fStencilFormats.push_back() = gS4;
+ }
+ }
+ GrAssert(0 == fStencilVerifiedColorConfigs.count());
+ fStencilVerifiedColorConfigs.push_back_n(fStencilFormats.count());
+}
+
+void GrGLCaps::markColorConfigAndStencilFormatAsVerified(
+ GrPixelConfig config,
+ const GrGLStencilBuffer::Format& format) {
+#if !GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT
+ return;
+#endif
+ GrAssert((unsigned)config < (unsigned)kGrPixelConfigCnt);
+ GrAssert(fStencilFormats.count() == fStencilVerifiedColorConfigs.count());
+ int count = fStencilFormats.count();
+ // we expect a really small number of possible formats so linear search
+ // should be OK
+ GrAssert(count < 16);
+ for (int i = 0; i < count; ++i) {
+ if (format.fInternalFormat ==
+ fStencilFormats[i].fInternalFormat) {
+ fStencilVerifiedColorConfigs[i].markVerified(config);
+ return;
+ }
+ }
+ GrCrash("Why are we seeing a stencil format that "
+ "GrGLCaps doesn't know about.");
+}
+
+bool GrGLCaps::isColorConfigAndStencilFormatVerified(
+ GrPixelConfig config,
+ const GrGLStencilBuffer::Format& format) const {
+#if !GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT
+ return false;
+#endif
+ GrAssert((unsigned)config < (unsigned)kGrPixelConfigCnt);
+ int count = fStencilFormats.count();
+ // we expect a really small number of possible formats so linear search
+ // should be OK
+ GrAssert(count < 16);
+ for (int i = 0; i < count; ++i) {
+ if (format.fInternalFormat ==
+ fStencilFormats[i].fInternalFormat) {
+ return fStencilVerifiedColorConfigs[i].isVerified(config);
+ }
+ }
+ GrCrash("Why are we seeing a stencil format that "
+ "GLCaps doesn't know about.");
+ return false;
+}
+
+void GrGLCaps::print() const {
+
+ INHERITED::print();
+
+ GrPrintf("--- GL-Specific ---\n");
+ for (int i = 0; i < fStencilFormats.count(); ++i) {
+ GrPrintf("Stencil Format %d, stencil bits: %02d, total bits: %02d\n",
+ i,
+ fStencilFormats[i].fStencilBits,
+ fStencilFormats[i].fTotalBits);
+ }
+
+ static const char* kMSFBOExtStr[] = {
+ "None",
+ "ARB",
+ "EXT",
+ "Apple",
+ "IMG MS To Texture",
+ "EXT MS To Texture",
+ };
+ GR_STATIC_ASSERT(0 == kNone_MSFBOType);
+ GR_STATIC_ASSERT(1 == kDesktop_ARB_MSFBOType);
+ GR_STATIC_ASSERT(2 == kDesktop_EXT_MSFBOType);
+ GR_STATIC_ASSERT(3 == kES_Apple_MSFBOType);
+ GR_STATIC_ASSERT(4 == kES_IMG_MsToTexture_MSFBOType);
+ GR_STATIC_ASSERT(5 == kES_EXT_MsToTexture_MSFBOType);
+ GR_STATIC_ASSERT(GR_ARRAY_COUNT(kMSFBOExtStr) == kLast_MSFBOType + 1);
+
+ static const char* kFBFetchTypeStr[] = {
+ "None",
+ "EXT",
+ "NV",
+ };
+ GR_STATIC_ASSERT(0 == kNone_FBFetchType);
+ GR_STATIC_ASSERT(1 == kEXT_FBFetchType);
+ GR_STATIC_ASSERT(2 == kNV_FBFetchType);
+ GR_STATIC_ASSERT(GR_ARRAY_COUNT(kFBFetchTypeStr) == kLast_FBFetchType + 1);
+
+
+ GrPrintf("MSAA Type: %s\n", kMSFBOExtStr[fMSFBOType]);
+ GrPrintf("FB Fetch Type: %s\n", kFBFetchTypeStr[fFBFetchType]);
+ GrPrintf("Max FS Uniform Vectors: %d\n", fMaxFragmentUniformVectors);
+ GrPrintf("Max Vertex Attributes: %d\n", fMaxVertexAttributes);
+ GrPrintf("Support RGBA8 Render Buffer: %s\n", (fRGBA8RenderbufferSupport ? "YES": "NO"));
+ GrPrintf("BGRA support: %s\n", (fBGRAFormatSupport ? "YES": "NO"));
+ GrPrintf("BGRA is an internal format: %s\n", (fBGRAIsInternalFormat ? "YES": "NO"));
+ GrPrintf("Support texture swizzle: %s\n", (fTextureSwizzleSupport ? "YES": "NO"));
+ GrPrintf("Unpack Row length support: %s\n", (fUnpackRowLengthSupport ? "YES": "NO"));
+ GrPrintf("Unpack Flip Y support: %s\n", (fUnpackFlipYSupport ? "YES": "NO"));
+ GrPrintf("Pack Row length support: %s\n", (fPackRowLengthSupport ? "YES": "NO"));
+ GrPrintf("Pack Flip Y support: %s\n", (fPackFlipYSupport ? "YES": "NO"));
+
+ GrPrintf("Texture Usage support: %s\n", (fTextureUsageSupport ? "YES": "NO"));
+ GrPrintf("Texture Storage support: %s\n", (fTexStorageSupport ? "YES": "NO"));
+ GrPrintf("GL_R support: %s\n", (fTextureRedSupport ? "YES": "NO"));
+ GrPrintf("GL_ARB_imaging support: %s\n", (fImagingSupport ? "YES": "NO"));
+ GrPrintf("Two Format Limit: %s\n", (fTwoFormatLimit ? "YES": "NO"));
+ GrPrintf("Fragment coord conventions support: %s\n",
+ (fFragCoordsConventionSupport ? "YES": "NO"));
+ GrPrintf("Vertex array object support: %s\n", (fVertexArrayObjectSupport ? "YES": "NO"));
+ GrPrintf("Use non-VBO for dynamic data: %s\n",
+ (fUseNonVBOVertexAndIndexDynamicData ? "YES" : "NO"));
+ GrPrintf("Core Profile: %s\n", (fIsCoreProfile ? "YES" : "NO"));
+ GrPrintf("Discard FrameBuffer support: %s\n", (fDiscardFBSupport ? "YES" : "NO"));
+}
diff --git a/gpu/gl/GrGLCaps.h b/gpu/gl/GrGLCaps.h
new file mode 100644
index 00000000..0b625a5a
--- /dev/null
+++ b/gpu/gl/GrGLCaps.h
@@ -0,0 +1,368 @@
+/*
+ * 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 GrGLCaps_DEFINED
+#define GrGLCaps_DEFINED
+
+#include "GrDrawTargetCaps.h"
+#include "GrGLStencilBuffer.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+
+class GrGLContextInfo;
+
+/**
+ * Stores some capabilities of a GL context. Most are determined by the GL
+ * version and the extensions string. It also tracks formats that have passed
+ * the FBO completeness test.
+ */
+class GrGLCaps : public GrDrawTargetCaps {
+public:
+ SK_DECLARE_INST_COUNT(GrGLCaps)
+
+ typedef GrGLStencilBuffer::Format StencilFormat;
+
+ /**
+ * Represents a supported multisampling/coverage-sampling mode.
+ */
+ struct MSAACoverageMode {
+ // "Coverage samples" includes samples that actually have color, depth,
+ // stencil, ... as well as those that don't (coverage only). All samples
+ // are coverage samples. (We're using the word "coverage sample" to
+ // match the NV extension language.)
+ int fCoverageSampleCnt;
+
+ // Color samples are samples that store data values (color, stencil,
+ // depth) rather than just representing coverage. They are a subset
+ // of coverage samples. (Again the wording was chosen to match the
+ // extension.)
+ int fColorSampleCnt;
+ };
+
+ /**
+ * The type of MSAA for FBOs supported. Different extensions have different
+ * semantics of how / when a resolve is performed.
+ */
+ enum MSFBOType {
+ /**
+ * no support for MSAA FBOs
+ */
+ kNone_MSFBOType = 0,
+ /**
+ * GL3.0-style MSAA FBO (GL_ARB_framebuffer_object)
+ */
+ kDesktop_ARB_MSFBOType,
+ /**
+ * earlier GL_EXT_framebuffer* extensions
+ */
+ kDesktop_EXT_MSFBOType,
+ /**
+ * GL_APPLE_framebuffer_multisample ES extension
+ */
+ kES_Apple_MSFBOType,
+ /**
+ * GL_IMG_multisampled_render_to_texture. This variation does not have MSAA renderbuffers.
+ * Instead the texture is multisampled when bound to the FBO and then resolved automatically
+ * when read. It also defines an alternate value for GL_MAX_SAMPLES (which we call
+ * GR_GL_MAX_SAMPLES_IMG).
+ */
+ kES_IMG_MsToTexture_MSFBOType,
+ /**
+ * GL_EXT_multisampled_render_to_texture. Same as the IMG one above but uses the standard
+ * GL_MAX_SAMPLES value.
+ */
+ kES_EXT_MsToTexture_MSFBOType,
+
+ kLast_MSFBOType = kES_EXT_MsToTexture_MSFBOType
+ };
+
+ enum FBFetchType {
+ kNone_FBFetchType,
+ /** GL_EXT_shader_framebuffer_fetch */
+ kEXT_FBFetchType,
+ /** GL_NV_shader_framebuffer_fetch */
+ kNV_FBFetchType,
+
+ kLast_FBFetchType = kNV_FBFetchType,
+ };
+
+ enum CoverageAAType {
+ /**
+ * No coverage sample support
+ */
+ kNone_CoverageAAType,
+
+ /**
+ * GL_NV_framebuffer_multisample_coverage
+ */
+ kNVDesktop_CoverageAAType,
+ };
+
+ /**
+ * Creates a GrGLCaps that advertises no support for any extensions,
+ * formats, etc. Call init to initialize from a GrGLContextInfo.
+ */
+ GrGLCaps();
+
+ GrGLCaps(const GrGLCaps& caps);
+
+ GrGLCaps& operator = (const GrGLCaps& caps);
+
+ /**
+ * Resets the caps such that nothing is supported.
+ */
+ virtual void reset() SK_OVERRIDE;
+
+ /**
+ * Initializes the GrGLCaps to the set of features supported in the current
+ * OpenGL context accessible via ctxInfo.
+ */
+ void init(const GrGLContextInfo& ctxInfo, const GrGLInterface* interface);
+
+ /**
+ * Call to note that a color config has been verified as a valid color
+ * attachment. This may save future calls to glCheckFramebufferStatus
+ * using isConfigVerifiedColorAttachment().
+ */
+ void markConfigAsValidColorAttachment(GrPixelConfig config) {
+ fVerifiedColorConfigs.markVerified(config);
+ }
+
+ /**
+ * Call to check whether a config has been verified as a valid color
+ * attachment.
+ */
+ bool isConfigVerifiedColorAttachment(GrPixelConfig config) const {
+ return fVerifiedColorConfigs.isVerified(config);
+ }
+
+ /**
+ * Call to note that a color config / stencil format pair passed
+ * FBO status check. We may skip calling glCheckFramebufferStatus for
+ * this combination in the future using
+ * isColorConfigAndStencilFormatVerified().
+ */
+ void markColorConfigAndStencilFormatAsVerified(
+ GrPixelConfig config,
+ const GrGLStencilBuffer::Format& format);
+
+ /**
+ * Call to check whether color config / stencil format pair has already
+ * passed FBO status check.
+ */
+ bool isColorConfigAndStencilFormatVerified(
+ GrPixelConfig config,
+ const GrGLStencilBuffer::Format& format) const;
+
+ /**
+ * Reports the type of MSAA FBO support.
+ */
+ MSFBOType msFBOType() const { return fMSFBOType; }
+
+ /**
+ * Does the supported MSAA FBO extension have MSAA renderbuffers?
+ */
+ bool usesMSAARenderBuffers() const {
+ return kNone_MSFBOType != fMSFBOType &&
+ kES_IMG_MsToTexture_MSFBOType != fMSFBOType &&
+ kES_EXT_MsToTexture_MSFBOType != fMSFBOType;
+ }
+
+ /**
+ * Is the MSAA FBO extension one where the texture is multisampled when bound to an FBO and
+ * then implicitly resolved when read.
+ */
+ bool usesImplicitMSAAResolve() const {
+ return kES_IMG_MsToTexture_MSFBOType == fMSFBOType ||
+ kES_EXT_MsToTexture_MSFBOType == fMSFBOType;
+ }
+
+ /**
+ * Reports the type of coverage sample AA support.
+ */
+ CoverageAAType coverageAAType() const { return fCoverageAAType; }
+
+ /**
+ * Chooses a supported coverage mode based on a desired sample count. The
+ * desired sample count is rounded up the next supported coverage sample
+ * count unless a it is larger than the max in which case it is rounded
+ * down. Once a coverage sample count is decided, the supported mode with
+ * the fewest color samples is chosen.
+ */
+ const MSAACoverageMode& getMSAACoverageMode(int desiredSampleCount) const;
+
+ FBFetchType fbFetchType() const { return fFBFetchType; }
+
+ /**
+ * Prints the caps info using GrPrintf.
+ */
+ virtual void print() const SK_OVERRIDE;
+
+ /**
+ * Gets an array of legal stencil formats. These formats are not guaranteed
+ * to be supported by the driver but are legal GLenum names given the GL
+ * version and extensions supported.
+ */
+ const SkTArray<StencilFormat, true>& stencilFormats() const {
+ return fStencilFormats;
+ }
+
+ /// The maximum number of fragment uniform vectors (GLES has min. 16).
+ int maxFragmentUniformVectors() const { return fMaxFragmentUniformVectors; }
+
+ /// maximum number of attribute values per vertex
+ int maxVertexAttributes() const { return fMaxVertexAttributes; }
+
+ /// maximum number of texture units accessible in the fragment shader.
+ int maxFragmentTextureUnits() const { return fMaxFragmentTextureUnits; }
+
+ /// ES requires an extension to support RGBA8 in RenderBufferStorage
+ bool rgba8RenderbufferSupport() const { return fRGBA8RenderbufferSupport; }
+
+ /// Is GL_BGRA supported
+ bool bgraFormatSupport() const { return fBGRAFormatSupport; }
+
+ /**
+ * Depending on the ES extensions present the BGRA external format may
+ * correspond either a BGRA or RGBA internalFormat. On desktop GL it is
+ * RGBA.
+ */
+ bool bgraIsInternalFormat() const { return fBGRAIsInternalFormat; }
+
+ /// GL_ARB_texture_swizzle support
+ bool textureSwizzleSupport() const { return fTextureSwizzleSupport; }
+
+ /// Is there support for GL_UNPACK_ROW_LENGTH
+ bool unpackRowLengthSupport() const { return fUnpackRowLengthSupport; }
+
+ /// Is there support for GL_UNPACK_FLIP_Y
+ bool unpackFlipYSupport() const { return fUnpackFlipYSupport; }
+
+ /// Is there support for GL_PACK_ROW_LENGTH
+ bool packRowLengthSupport() const { return fPackRowLengthSupport; }
+
+ /// Is there support for GL_PACK_REVERSE_ROW_ORDER
+ bool packFlipYSupport() const { return fPackFlipYSupport; }
+
+ /// Is there support for texture parameter GL_TEXTURE_USAGE
+ bool textureUsageSupport() const { return fTextureUsageSupport; }
+
+ /// Is there support for glTexStorage
+ bool texStorageSupport() const { return fTexStorageSupport; }
+
+ /// Is there support for GL_RED and GL_R8
+ bool textureRedSupport() const { return fTextureRedSupport; }
+
+ /// Is GL_ARB_IMAGING supported
+ bool imagingSupport() const { return fImagingSupport; }
+
+ /// Is GL_ARB_fragment_coord_conventions supported?
+ bool fragCoordConventionsSupport() const { return fFragCoordsConventionSupport; }
+
+ /// Is there support for Vertex Array Objects?
+ bool vertexArrayObjectSupport() const { return fVertexArrayObjectSupport; }
+
+ /// Use indices or vertices in CPU arrays rather than VBOs for dynamic content.
+ bool useNonVBOVertexAndIndexDynamicData() const {
+ return fUseNonVBOVertexAndIndexDynamicData;
+ }
+
+ /// Does ReadPixels support the provided format/type combo?
+ bool readPixelsSupported(const GrGLInterface* intf,
+ GrGLenum format,
+ GrGLenum type) const;
+
+ bool isCoreProfile() const { return fIsCoreProfile; }
+
+ /// Is there support for discarding the frame buffer
+ bool discardFBSupport() const { return fDiscardFBSupport; }
+
+private:
+ /**
+ * Maintains a bit per GrPixelConfig. It is used to avoid redundantly
+ * performing glCheckFrameBufferStatus for the same config.
+ */
+ struct VerifiedColorConfigs {
+ VerifiedColorConfigs() {
+ this->reset();
+ }
+
+ void reset() {
+ for (int i = 0; i < kNumUints; ++i) {
+ fVerifiedColorConfigs[i] = 0;
+ }
+ }
+
+ static const int kNumUints = (kGrPixelConfigCnt + 31) / 32;
+ uint32_t fVerifiedColorConfigs[kNumUints];
+
+ void markVerified(GrPixelConfig config) {
+#if !GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT
+ return;
+#endif
+ int u32Idx = config / 32;
+ int bitIdx = config % 32;
+ fVerifiedColorConfigs[u32Idx] |= 1 << bitIdx;
+ }
+
+ bool isVerified(GrPixelConfig config) const {
+#if !GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT
+ return false;
+#endif
+ int u32Idx = config / 32;
+ int bitIdx = config % 32;
+ return SkToBool(fVerifiedColorConfigs[u32Idx] & (1 << bitIdx));
+ }
+ };
+
+ void initFSAASupport(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli);
+ void initStencilFormats(const GrGLContextInfo& ctxInfo);
+
+ // tracks configs that have been verified to pass the FBO completeness when
+ // used as a color attachment
+ VerifiedColorConfigs fVerifiedColorConfigs;
+
+ SkTArray<StencilFormat, true> fStencilFormats;
+ // tracks configs that have been verified to pass the FBO completeness when
+ // used as a color attachment when a particular stencil format is used
+ // as a stencil attachment.
+ SkTArray<VerifiedColorConfigs, true> fStencilVerifiedColorConfigs;
+
+ int fMaxFragmentUniformVectors;
+ int fMaxVertexAttributes;
+ int fMaxFragmentTextureUnits;
+
+ MSFBOType fMSFBOType;
+ CoverageAAType fCoverageAAType;
+ SkTDArray<MSAACoverageMode> fMSAACoverageModes;
+
+ FBFetchType fFBFetchType;
+
+ bool fRGBA8RenderbufferSupport : 1;
+ bool fBGRAFormatSupport : 1;
+ bool fBGRAIsInternalFormat : 1;
+ bool fTextureSwizzleSupport : 1;
+ bool fUnpackRowLengthSupport : 1;
+ bool fUnpackFlipYSupport : 1;
+ bool fPackRowLengthSupport : 1;
+ bool fPackFlipYSupport : 1;
+ bool fTextureUsageSupport : 1;
+ bool fTexStorageSupport : 1;
+ bool fTextureRedSupport : 1;
+ bool fImagingSupport : 1;
+ bool fTwoFormatLimit : 1;
+ bool fFragCoordsConventionSupport : 1;
+ bool fVertexArrayObjectSupport : 1;
+ bool fUseNonVBOVertexAndIndexDynamicData : 1;
+ bool fIsCoreProfile : 1;
+ bool fDiscardFBSupport : 1;
+
+ typedef GrDrawTargetCaps INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLContext.cpp b/gpu/gl/GrGLContext.cpp
new file mode 100644
index 00000000..1d0a01fc
--- /dev/null
+++ b/gpu/gl/GrGLContext.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLContext.h"
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLContextInfo& GrGLContextInfo::operator= (const GrGLContextInfo& ctxInfo) {
+ fBindingInUse = ctxInfo.fBindingInUse;
+ fGLVersion = ctxInfo.fGLVersion;
+ fGLSLGeneration = ctxInfo.fGLSLGeneration;
+ fVendor = ctxInfo.fVendor;
+ fExtensions = ctxInfo.fExtensions;
+ fIsMesa = ctxInfo.fIsMesa;
+ *fGLCaps = *ctxInfo.fGLCaps.get();
+ return *this;
+}
+
+bool GrGLContextInfo::initialize(const GrGLInterface* interface) {
+ this->reset();
+ // We haven't validated the GrGLInterface yet, so check for GetString
+ // function pointer
+ if (interface->fGetString) {
+ const GrGLubyte* verUByte;
+ GR_GL_CALL_RET(interface, verUByte, GetString(GR_GL_VERSION));
+ const char* ver = reinterpret_cast<const char*>(verUByte);
+ GrGLBinding binding = GrGLGetBindingInUseFromString(ver);
+
+ if (0 != binding && interface->validate(binding) && fExtensions.init(binding, interface)) {
+ fBindingInUse = binding;
+
+ fGLVersion = GrGLGetVersionFromString(ver);
+
+ fGLSLGeneration = GrGetGLSLGeneration(fBindingInUse, interface);
+
+ fVendor = GrGLGetVendor(interface);
+
+ fIsMesa = GrGLIsMesaFromVersionString(ver);
+
+ fGLCaps->init(*this, interface);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GrGLContextInfo::isInitialized() const {
+ return kNone_GrGLBinding != fBindingInUse;
+}
+
+void GrGLContextInfo::reset() {
+ fBindingInUse = kNone_GrGLBinding;
+ fGLVersion = GR_GL_VER(0, 0);
+ fGLSLGeneration = static_cast<GrGLSLGeneration>(0);
+ fVendor = kOther_GrGLVendor;
+ fIsMesa = false;
+ fExtensions.reset();
+ fGLCaps->reset();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLContext::GrGLContext(const GrGLInterface* interface) {
+ fInterface = NULL;
+ this->initialize(interface);
+}
+
+GrGLContext::GrGLContext(const GrGLContext& ctx) {
+ fInterface = NULL;
+ *this = ctx;
+}
+
+GrGLContext& GrGLContext::operator = (const GrGLContext& ctx) {
+ GrSafeAssign(fInterface, ctx.fInterface);
+ fInfo = ctx.fInfo;
+ return *this;
+}
+
+void GrGLContext::reset() {
+ GrSafeSetNull(fInterface);
+ fInfo.reset();
+}
+
+bool GrGLContext::initialize(const GrGLInterface* interface) {
+ if (fInfo.initialize(interface)) {
+ fInterface = interface;
+ interface->ref();
+ return true;
+ }
+ return false;
+}
diff --git a/gpu/gl/GrGLContext.h b/gpu/gl/GrGLContext.h
new file mode 100644
index 00000000..34f2190f
--- /dev/null
+++ b/gpu/gl/GrGLContext.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef GrGLContext_DEFINED
+#define GrGLContext_DEFINED
+
+#include "gl/GrGLExtensions.h"
+#include "gl/GrGLInterface.h"
+#include "GrGLCaps.h"
+#include "GrGLSL.h"
+#include "GrGLUtil.h"
+
+#include "SkString.h"
+
+/**
+ * Encapsulates information about an OpenGL context including the OpenGL
+ * version, the GrGLBinding type of the context, and GLSL version.
+ */
+class GrGLContextInfo {
+public:
+ /**
+ * Default constructor
+ */
+ GrGLContextInfo() {
+ fGLCaps.reset(SkNEW(GrGLCaps));
+ this->reset();
+ }
+
+ /**
+ * Copies a GrGLContextInfo
+ */
+ GrGLContextInfo& operator= (const GrGLContextInfo& ctxInfo);
+
+ /**
+ * Initializes a GrGLContextInfo from a GrGLInterface and the currently
+ * bound OpenGL context accessible by the GrGLInterface.
+ */
+ bool initialize(const GrGLInterface* interface);
+ bool isInitialized() const;
+
+ GrGLBinding binding() const { return fBindingInUse; }
+ GrGLVersion version() const { return fGLVersion; }
+ GrGLSLGeneration glslGeneration() const { return fGLSLGeneration; }
+ GrGLVendor vendor() const { return fVendor; }
+ /** Is this a mesa-based driver. Does not mean it is the osmesa software rasterizer. */
+ bool isMesa() const { return fIsMesa; }
+ const GrGLCaps* caps() const { return fGLCaps.get(); }
+ GrGLCaps* caps() { return fGLCaps; }
+ const GrGLExtensions& extensions() const { return fExtensions; }
+
+ /**
+ * Shortcut for extensions().has(ext);
+ */
+ bool hasExtension(const char* ext) const {
+ if (!this->isInitialized()) {
+ return false;
+ }
+ return fExtensions.has(ext);
+ }
+
+ /**
+ * Reset the information
+ */
+ void reset();
+
+private:
+
+ GrGLBinding fBindingInUse;
+ GrGLVersion fGLVersion;
+ GrGLSLGeneration fGLSLGeneration;
+ GrGLVendor fVendor;
+ GrGLExtensions fExtensions;
+ bool fIsMesa;
+ SkAutoTUnref<GrGLCaps> fGLCaps;
+};
+
+/**
+ * Encapsulates the GrGLInterface used to make GL calls plus information
+ * about the context (via GrGLContextInfo).
+ */
+class GrGLContext {
+public:
+ /**
+ * Default constructor
+ */
+ GrGLContext() { this->reset(); }
+
+ /**
+ * Creates a GrGLContext from a GrGLInterface and the currently
+ * bound OpenGL context accessible by the GrGLInterface.
+ */
+ explicit GrGLContext(const GrGLInterface* interface);
+
+ /**
+ * Copies a GrGLContext
+ */
+ GrGLContext(const GrGLContext& ctx);
+
+ ~GrGLContext() { GrSafeUnref(fInterface); }
+
+ /**
+ * Copies a GrGLContext
+ */
+ GrGLContext& operator= (const GrGLContext& ctx);
+
+ /**
+ * Initializes a GrGLContext from a GrGLInterface and the currently
+ * bound OpenGL context accessible by the GrGLInterface.
+ */
+ bool initialize(const GrGLInterface* interface);
+ bool isInitialized() const { return fInfo.isInitialized(); }
+
+ const GrGLInterface* interface() const { return fInterface; }
+ const GrGLContextInfo& info() const { return fInfo; }
+ GrGLContextInfo& info() { return fInfo; }
+
+private:
+ void reset();
+
+ const GrGLInterface* fInterface;
+ GrGLContextInfo fInfo;
+};
+
+#endif
diff --git a/gpu/gl/GrGLCreateNativeInterface_none.cpp b/gpu/gl/GrGLCreateNativeInterface_none.cpp
new file mode 100644
index 00000000..e0c72c53
--- /dev/null
+++ b/gpu/gl/GrGLCreateNativeInterface_none.cpp
@@ -0,0 +1,12 @@
+/*
+ * 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"
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ return NULL;
+}
diff --git a/gpu/gl/GrGLCreateNullInterface.cpp b/gpu/gl/GrGLCreateNullInterface.cpp
new file mode 100644
index 00000000..0d539c95
--- /dev/null
+++ b/gpu/gl/GrGLCreateNullInterface.cpp
@@ -0,0 +1,395 @@
+/*
+ * 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 "GrGLDefines.h"
+#include "SkTDArray.h"
+#include "GrGLNoOpInterface.h"
+
+// Functions not declared in GrGLBogusInterface.h (not common with the Debug GL interface).
+
+namespace { // added to suppress 'no previous prototype' warning
+
+class GrBufferObj {
+public:
+ GrBufferObj(GrGLuint id) : fID(id), fDataPtr(NULL), fSize(0), fMapped(false) {
+ }
+ ~GrBufferObj() { SkDELETE_ARRAY(fDataPtr); }
+
+ void allocate(GrGLsizeiptr size, const GrGLchar* dataPtr) {
+ if (NULL != fDataPtr) {
+ GrAssert(0 != fSize);
+ SkDELETE_ARRAY(fDataPtr);
+ }
+
+ fSize = size;
+ fDataPtr = SkNEW_ARRAY(char, size);
+ }
+
+ GrGLuint id() const { return fID; }
+ GrGLchar* dataPtr() { return fDataPtr; }
+ GrGLsizeiptr size() const { return fSize; }
+
+ void setMapped(bool mapped) { fMapped = mapped; }
+ bool mapped() const { return fMapped; }
+
+private:
+ GrGLuint fID;
+ GrGLchar* fDataPtr;
+ GrGLsizeiptr fSize; // size in bytes
+ bool fMapped;
+};
+
+// In debug builds we do asserts that ensure we agree with GL about when a buffer
+// is mapped.
+static SkTDArray<GrBufferObj*> gBuffers; // slot 0 is reserved for head of free list
+static GrGLuint gCurrArrayBuffer;
+static GrGLuint gCurrElementArrayBuffer;
+
+static GrBufferObj* look_up(GrGLuint id) {
+ GrBufferObj* buffer = gBuffers[id];
+ GrAssert(NULL != buffer && buffer->id() == id);
+ return buffer;
+}
+
+static GrBufferObj* create_buffer() {
+ if (0 == gBuffers.count()) {
+ // slot zero is reserved for the head of the free list
+ *gBuffers.append() = NULL;
+ }
+
+ GrGLuint id;
+ GrBufferObj* buffer;
+
+ if (NULL == gBuffers[0]) {
+ // no free slots - create a new one
+ id = gBuffers.count();
+ buffer = SkNEW_ARGS(GrBufferObj, (id));
+ gBuffers.append(1, &buffer);
+ } else {
+ // recycle a slot from the free list
+ id = SkTCast<GrGLuint>(gBuffers[0]);
+ gBuffers[0] = gBuffers[id];
+
+ buffer = SkNEW_ARGS(GrBufferObj, (id));
+ gBuffers[id] = buffer;
+ }
+
+ return buffer;
+}
+
+static void delete_buffer(GrBufferObj* buffer) {
+ GrAssert(gBuffers.count() > 0);
+
+ GrGLuint id = buffer->id();
+ SkDELETE(buffer);
+
+ // Add this slot to the free list
+ gBuffers[id] = gBuffers[0];
+ gBuffers[0] = SkTCast<GrBufferObj*>((const void*)(intptr_t)id);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLActiveTexture(GrGLenum texture) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLAttachShader(GrGLuint program, GrGLuint shader) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBeginQuery(GrGLenum target, GrGLuint id) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindAttribLocation(GrGLuint program, GrGLuint index, const char* name) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindTexture(GrGLenum target, GrGLuint texture) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindVertexArray(GrGLuint id) {}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLGenBuffers(GrGLsizei n, GrGLuint* ids) {
+
+ for (int i = 0; i < n; ++i) {
+ GrBufferObj* buffer = create_buffer();
+ ids[i] = buffer->id();
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLGenerateMipmap(GrGLenum target) {}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBufferData(GrGLenum target,
+ GrGLsizeiptr size,
+ const GrGLvoid* data,
+ GrGLenum usage) {
+ GrGLuint id = 0;
+
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ id = gCurrArrayBuffer;
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ id = gCurrElementArrayBuffer;
+ break;
+ default:
+ GrCrash("Unexpected target to nullGLBufferData");
+ break;
+ }
+
+ if (id > 0) {
+ GrBufferObj* buffer = look_up(id);
+ buffer->allocate(size, (const GrGLchar*) data);
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLPixelStorei(GrGLenum pname, GrGLint param) {}
+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 nullGLUseProgram(GrGLuint program) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLViewport(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindFramebuffer(GrGLenum target, GrGLuint framebuffer) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindRenderbuffer(GrGLenum target, GrGLuint renderbuffer) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteFramebuffers(GrGLsizei n, const GrGLuint *framebuffers) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteRenderbuffers(GrGLsizei n, const GrGLuint *renderbuffers) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLFramebufferRenderbuffer(GrGLenum target, GrGLenum attachment, GrGLenum renderbuffertarget, GrGLuint renderbuffer) {}
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLFramebufferTexture2D(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level) {}
+
+GrGLuint GR_GL_FUNCTION_TYPE nullGLCreateProgram() {
+ static GrGLuint gCurrID = 0;
+ return ++gCurrID;
+}
+
+GrGLuint GR_GL_FUNCTION_TYPE nullGLCreateShader(GrGLenum type) {
+ static GrGLuint gCurrID = 0;
+ return ++gCurrID;
+}
+
+// same delete used for shaders and programs
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLDelete(GrGLuint program) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindBuffer(GrGLenum target, GrGLuint buffer) {
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ gCurrArrayBuffer = buffer;
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ gCurrElementArrayBuffer = buffer;
+ break;
+ }
+}
+
+// deleting a bound buffer has the side effect of binding 0
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteBuffers(GrGLsizei n, const GrGLuint* ids) {
+ for (int i = 0; i < n; ++i) {
+ if (ids[i] == gCurrArrayBuffer) {
+ gCurrArrayBuffer = 0;
+ }
+ if (ids[i] == gCurrElementArrayBuffer) {
+ gCurrElementArrayBuffer = 0;
+ }
+
+ GrBufferObj* buffer = look_up(ids[i]);
+ delete_buffer(buffer);
+ }
+}
+
+GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) {
+
+ GrGLuint id = 0;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ id = gCurrArrayBuffer;
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ id = gCurrElementArrayBuffer;
+ break;
+ }
+
+ if (id > 0) {
+ GrBufferObj* buffer = look_up(id);
+ GrAssert(!buffer->mapped());
+ buffer->setMapped(true);
+ return buffer->dataPtr();
+ }
+
+ GrAssert(false);
+ return NULL; // no buffer bound to target
+}
+
+GrGLboolean GR_GL_FUNCTION_TYPE nullGLUnmapBuffer(GrGLenum target) {
+ GrGLuint id = 0;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ id = gCurrArrayBuffer;
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ id = gCurrElementArrayBuffer;
+ break;
+ }
+ if (id > 0) {
+ GrBufferObj* buffer = look_up(id);
+ GrAssert(buffer->mapped());
+ buffer->setMapped(false);
+ return GR_GL_TRUE;
+ }
+
+ GrAlwaysAssert(false);
+ return GR_GL_FALSE; // GR_GL_INVALID_OPERATION;
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE nullGLGetBufferParameteriv(GrGLenum target, GrGLenum pname, GrGLint* params) {
+ switch (pname) {
+ case GR_GL_BUFFER_MAPPED: {
+ *params = GR_GL_FALSE;
+ GrGLuint id = 0;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ id = gCurrArrayBuffer;
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ id = gCurrElementArrayBuffer;
+ break;
+ }
+ if (id > 0) {
+ GrBufferObj* buffer = look_up(id);
+ if (buffer->mapped()) {
+ *params = GR_GL_TRUE;
+ }
+ }
+ break; }
+ default:
+ GrCrash("Unexpected pname to GetBufferParamateriv");
+ break;
+ }
+};
+
+} // end anonymous namespace
+
+const GrGLInterface* GrGLCreateNullInterface() {
+ // The gl functions are not context-specific so we create one global
+ // interface
+ static SkAutoTUnref<GrGLInterface> glInterface;
+ if (!glInterface.get()) {
+ GrGLInterface* interface = SkNEW(GrGLInterface);
+ glInterface.reset(interface);
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+ interface->fActiveTexture = nullGLActiveTexture;
+ interface->fAttachShader = nullGLAttachShader;
+ interface->fBeginQuery = nullGLBeginQuery;
+ interface->fBindAttribLocation = nullGLBindAttribLocation;
+ interface->fBindBuffer = nullGLBindBuffer;
+ interface->fBindFragDataLocation = noOpGLBindFragDataLocation;
+ interface->fBindTexture = nullGLBindTexture;
+ interface->fBindVertexArray = nullGLBindVertexArray;
+ interface->fBlendColor = noOpGLBlendColor;
+ interface->fBlendFunc = noOpGLBlendFunc;
+ interface->fBufferData = nullGLBufferData;
+ interface->fBufferSubData = noOpGLBufferSubData;
+ interface->fClear = noOpGLClear;
+ interface->fClearColor = noOpGLClearColor;
+ interface->fClearStencil = noOpGLClearStencil;
+ interface->fColorMask = noOpGLColorMask;
+ interface->fCompileShader = noOpGLCompileShader;
+ interface->fCompressedTexImage2D = noOpGLCompressedTexImage2D;
+ interface->fCopyTexSubImage2D = noOpGLCopyTexSubImage2D;
+ interface->fCreateProgram = nullGLCreateProgram;
+ interface->fCreateShader = nullGLCreateShader;
+ interface->fCullFace = noOpGLCullFace;
+ interface->fDeleteBuffers = nullGLDeleteBuffers;
+ interface->fDeleteProgram = nullGLDelete;
+ interface->fDeleteQueries = noOpGLDeleteIds;
+ interface->fDeleteShader = nullGLDelete;
+ interface->fDeleteTextures = noOpGLDeleteIds;
+ interface->fDeleteVertexArrays = noOpGLDeleteIds;
+ interface->fDepthMask = noOpGLDepthMask;
+ interface->fDisable = noOpGLDisable;
+ interface->fDisableVertexAttribArray = noOpGLDisableVertexAttribArray;
+ interface->fDrawArrays = noOpGLDrawArrays;
+ interface->fDrawBuffer = noOpGLDrawBuffer;
+ interface->fDrawBuffers = noOpGLDrawBuffers;
+ interface->fDrawElements = noOpGLDrawElements;
+ interface->fEnable = noOpGLEnable;
+ interface->fEnableVertexAttribArray = noOpGLEnableVertexAttribArray;
+ interface->fEndQuery = noOpGLEndQuery;
+ interface->fFinish = noOpGLFinish;
+ interface->fFlush = noOpGLFlush;
+ interface->fFrontFace = noOpGLFrontFace;
+ interface->fGenBuffers = nullGLGenBuffers;
+ interface->fGenerateMipmap = nullGLGenerateMipmap;
+ interface->fGenQueries = noOpGLGenIds;
+ interface->fGenTextures = noOpGLGenIds;
+ interface->fGenVertexArrays = noOpGLGenIds;
+ interface->fGetBufferParameteriv = nullGLGetBufferParameteriv;
+ interface->fGetError = noOpGLGetError;
+ interface->fGetIntegerv = noOpGLGetIntegerv;
+ interface->fGetQueryObjecti64v = noOpGLGetQueryObjecti64v;
+ interface->fGetQueryObjectiv = noOpGLGetQueryObjectiv;
+ interface->fGetQueryObjectui64v = noOpGLGetQueryObjectui64v;
+ interface->fGetQueryObjectuiv = noOpGLGetQueryObjectuiv;
+ interface->fGetQueryiv = noOpGLGetQueryiv;
+ interface->fGetProgramInfoLog = noOpGLGetInfoLog;
+ interface->fGetProgramiv = noOpGLGetShaderOrProgramiv;
+ interface->fGetShaderInfoLog = noOpGLGetInfoLog;
+ interface->fGetShaderiv = noOpGLGetShaderOrProgramiv;
+ interface->fGetString = noOpGLGetString;
+ interface->fGetStringi = noOpGLGetStringi;
+ interface->fGetTexLevelParameteriv = noOpGLGetTexLevelParameteriv;
+ interface->fGetUniformLocation = noOpGLGetUniformLocation;
+ interface->fLineWidth = noOpGLLineWidth;
+ interface->fLinkProgram = noOpGLLinkProgram;
+ interface->fPixelStorei = nullGLPixelStorei;
+ interface->fQueryCounter = noOpGLQueryCounter;
+ interface->fReadBuffer = noOpGLReadBuffer;
+ interface->fReadPixels = nullGLReadPixels;
+ interface->fScissor = noOpGLScissor;
+ interface->fShaderSource = noOpGLShaderSource;
+ interface->fStencilFunc = noOpGLStencilFunc;
+ interface->fStencilFuncSeparate = noOpGLStencilFuncSeparate;
+ interface->fStencilMask = noOpGLStencilMask;
+ interface->fStencilMaskSeparate = noOpGLStencilMaskSeparate;
+ interface->fStencilOp = noOpGLStencilOp;
+ interface->fStencilOpSeparate = noOpGLStencilOpSeparate;
+ interface->fTexImage2D = noOpGLTexImage2D;
+ interface->fTexParameteri = noOpGLTexParameteri;
+ interface->fTexParameteriv = noOpGLTexParameteriv;
+ interface->fTexSubImage2D = noOpGLTexSubImage2D;
+ interface->fTexStorage2D = noOpGLTexStorage2D;
+ interface->fDiscardFramebuffer = noOpGLDiscardFramebuffer;
+ interface->fUniform1f = noOpGLUniform1f;
+ interface->fUniform1i = noOpGLUniform1i;
+ interface->fUniform1fv = noOpGLUniform1fv;
+ interface->fUniform1iv = noOpGLUniform1iv;
+ interface->fUniform2f = noOpGLUniform2f;
+ interface->fUniform2i = noOpGLUniform2i;
+ interface->fUniform2fv = noOpGLUniform2fv;
+ interface->fUniform2iv = noOpGLUniform2iv;
+ interface->fUniform3f = noOpGLUniform3f;
+ interface->fUniform3i = noOpGLUniform3i;
+ interface->fUniform3fv = noOpGLUniform3fv;
+ interface->fUniform3iv = noOpGLUniform3iv;
+ interface->fUniform4f = noOpGLUniform4f;
+ interface->fUniform4i = noOpGLUniform4i;
+ interface->fUniform4fv = noOpGLUniform4fv;
+ interface->fUniform4iv = noOpGLUniform4iv;
+ interface->fUniformMatrix2fv = noOpGLUniformMatrix2fv;
+ interface->fUniformMatrix3fv = noOpGLUniformMatrix3fv;
+ interface->fUniformMatrix4fv = noOpGLUniformMatrix4fv;
+ interface->fUseProgram = nullGLUseProgram;
+ interface->fVertexAttrib4fv = noOpGLVertexAttrib4fv;
+ interface->fVertexAttribPointer = noOpGLVertexAttribPointer;
+ interface->fViewport = nullGLViewport;
+ interface->fBindFramebuffer = nullGLBindFramebuffer;
+ interface->fBindRenderbuffer = nullGLBindRenderbuffer;
+ interface->fCheckFramebufferStatus = noOpGLCheckFramebufferStatus;
+ interface->fDeleteFramebuffers = nullGLDeleteFramebuffers;
+ interface->fDeleteRenderbuffers = nullGLDeleteRenderbuffers;
+ interface->fFramebufferRenderbuffer = nullGLFramebufferRenderbuffer;
+ interface->fFramebufferTexture2D = nullGLFramebufferTexture2D;
+ interface->fGenFramebuffers = noOpGLGenIds;
+ interface->fGenRenderbuffers = noOpGLGenIds;
+ interface->fGetFramebufferAttachmentParameteriv = noOpGLGetFramebufferAttachmentParameteriv;
+ interface->fGetRenderbufferParameteriv = noOpGLGetRenderbufferParameteriv;
+ interface->fRenderbufferStorage = noOpGLRenderbufferStorage;
+ interface->fRenderbufferStorageMultisample = noOpGLRenderbufferStorageMultisample;
+ interface->fBlitFramebuffer = noOpGLBlitFramebuffer;
+ interface->fResolveMultisampleFramebuffer = noOpGLResolveMultisampleFramebuffer;
+ interface->fMapBuffer = nullGLMapBuffer;
+ interface->fUnmapBuffer = nullGLUnmapBuffer;
+ interface->fBindFragDataLocationIndexed = noOpGLBindFragDataLocationIndexed;
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/gpu/gl/GrGLDefaultInterface_native.cpp b/gpu/gl/GrGLDefaultInterface_native.cpp
new file mode 100644
index 00000000..e695f15a
--- /dev/null
+++ b/gpu/gl/GrGLDefaultInterface_native.cpp
@@ -0,0 +1,12 @@
+/*
+ * 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"
+
+const GrGLInterface* GrGLDefaultInterface() {
+ return GrGLCreateNativeInterface();
+}
diff --git a/gpu/gl/GrGLDefaultInterface_none.cpp b/gpu/gl/GrGLDefaultInterface_none.cpp
new file mode 100644
index 00000000..84c7f7c3
--- /dev/null
+++ b/gpu/gl/GrGLDefaultInterface_none.cpp
@@ -0,0 +1,12 @@
+/*
+ * 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"
+
+const GrGLInterface* GrGLDefaultInterface() {
+ return NULL;
+}
diff --git a/gpu/gl/GrGLDefines.h b/gpu/gl/GrGLDefines.h
new file mode 100644
index 00000000..2ab95708
--- /dev/null
+++ b/gpu/gl/GrGLDefines.h
@@ -0,0 +1,882 @@
+/*
+ * 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 GrGLDefines_DEFINED
+#define GrGLDefines_DEFINED
+
+/* Profiles */
+#define GR_GL_CONTEXT_PROFILE_MASK 0x9126
+#define GR_GL_CONTEXT_CORE_PROFILE_BIT 0x00000001
+#define GR_GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
+
+// The following constants consist of the intersection of GL constants
+// exported by GLES 1.0, GLES 2.0, and desktop GL required by the system.
+
+#define GR_GL_DEPTH_BUFFER_BIT 0x00000100
+#define GR_GL_STENCIL_BUFFER_BIT 0x00000400
+#define GR_GL_COLOR_BUFFER_BIT 0x00004000
+
+/* Boolean */
+#define GR_GL_FALSE 0
+#define GR_GL_TRUE 1
+
+/* BeginMode */
+#define GR_GL_POINTS 0x0000
+#define GR_GL_LINES 0x0001
+#define GR_GL_LINE_LOOP 0x0002
+#define GR_GL_LINE_STRIP 0x0003
+#define GR_GL_TRIANGLES 0x0004
+#define GR_GL_TRIANGLE_STRIP 0x0005
+#define GR_GL_TRIANGLE_FAN 0x0006
+
+/* AlphaFunction (not supported in ES20) */
+/* GL_NEVER */
+/* GL_LESS */
+/* GL_EQUAL */
+/* GL_LEQUAL */
+/* GL_GREATER */
+/* GL_NOTEQUAL */
+/* GL_GEQUAL */
+/* GL_ALWAYS */
+
+/* BlendingFactorDest */
+#define GR_GL_ZERO 0
+#define GR_GL_ONE 1
+#define GR_GL_SRC_COLOR 0x0300
+#define GR_GL_ONE_MINUS_SRC_COLOR 0x0301
+#define GR_GL_SRC_ALPHA 0x0302
+#define GR_GL_ONE_MINUS_SRC_ALPHA 0x0303
+#define GR_GL_DST_ALPHA 0x0304
+#define GR_GL_ONE_MINUS_DST_ALPHA 0x0305
+
+/* BlendingFactorSrc */
+/* GL_ZERO */
+/* GL_ONE */
+#define GR_GL_DST_COLOR 0x0306
+#define GR_GL_ONE_MINUS_DST_COLOR 0x0307
+#define GR_GL_SRC_ALPHA_SATURATE 0x0308
+/* GL_SRC_ALPHA */
+/* GL_ONE_MINUS_SRC_ALPHA */
+/* GL_DST_ALPHA */
+/* GL_ONE_MINUS_DST_ALPHA */
+
+/* ExtendedBlendFactors */
+#define GR_GL_SRC1_COLOR 0x88F9
+#define GR_GL_ONE_MINUS_SRC1_COLOR 0x88FA
+/* GL_SRC1_ALPHA */
+#define GR_GL_ONE_MINUS_SRC1_ALPHA 0x88FB
+
+/* Separate Blend Functions */
+#define GR_GL_BLEND_DST_RGB 0x80C8
+#define GR_GL_BLEND_SRC_RGB 0x80C9
+#define GR_GL_BLEND_DST_ALPHA 0x80CA
+#define GR_GL_BLEND_SRC_ALPHA 0x80CB
+#define GR_GL_CONSTANT_COLOR 0x8001
+#define GR_GL_ONE_MINUS_CONSTANT_COLOR 0x8002
+#define GR_GL_CONSTANT_ALPHA 0x8003
+#define GR_GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
+#define GR_GL_BLEND_COLOR 0x8005
+
+/* Buffer Objects */
+#define GR_GL_ARRAY_BUFFER 0x8892
+#define GR_GL_ELEMENT_ARRAY_BUFFER 0x8893
+#define GR_GL_ARRAY_BUFFER_BINDING 0x8894
+#define GR_GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895
+
+#define GR_GL_STREAM_DRAW 0x88E0
+#define GR_GL_STATIC_DRAW 0x88E4
+#define GR_GL_DYNAMIC_DRAW 0x88E8
+
+#define GR_GL_BUFFER_SIZE 0x8764
+#define GR_GL_BUFFER_USAGE 0x8765
+
+#define GR_GL_CURRENT_VERTEX_ATTRIB 0x8626
+
+/* CullFaceMode */
+#define GR_GL_FRONT 0x0404
+#define GR_GL_BACK 0x0405
+#define GR_GL_FRONT_AND_BACK 0x0408
+
+/* DepthFunction */
+/* GL_NEVER */
+/* GL_LESS */
+/* GL_EQUAL */
+/* GL_LEQUAL */
+/* GL_GREATER */
+/* GL_NOTEQUAL */
+/* GL_GEQUAL */
+/* GL_ALWAYS */
+
+/* EnableCap */
+#define GR_GL_TEXTURE_2D 0x0DE1
+#define GR_GL_CULL_FACE 0x0B44
+#define GR_GL_BLEND 0x0BE2
+#define GR_GL_DITHER 0x0BD0
+#define GR_GL_STENCIL_TEST 0x0B90
+#define GR_GL_DEPTH_TEST 0x0B71
+#define GR_GL_SCISSOR_TEST 0x0C11
+#define GR_GL_POLYGON_OFFSET_FILL 0x8037
+#define GR_GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
+#define GR_GL_SAMPLE_COVERAGE 0x80A0
+#define GR_GL_POLYGON_OFFSET_FILL 0x8037
+#define GR_GL_POLYGON_SMOOTH 0x0B41
+#define GR_GL_POLYGON_STIPPLE 0x0B42
+#define GR_GL_COLOR_LOGIC_OP 0x0BF2
+#define GR_GL_COLOR_TABLE 0x80D0
+#define GR_GL_INDEX_LOGIC_OP 0x0BF1
+#define GR_GL_VERTEX_PROGRAM_POINT_SIZE 0x8642
+#define GR_GL_LINE_STIPPLE 0x0B24
+
+/* ErrorCode */
+#define GR_GL_NO_ERROR 0
+#define GR_GL_INVALID_ENUM 0x0500
+#define GR_GL_INVALID_VALUE 0x0501
+#define GR_GL_INVALID_OPERATION 0x0502
+#define GR_GL_OUT_OF_MEMORY 0x0505
+#define GR_GL_CONTEXT_LOST 0x300E // TODO(gman): What value?
+
+/* FrontFaceDirection */
+#define GR_GL_CW 0x0900
+#define GR_GL_CCW 0x0901
+
+/* GetPName */
+#define GR_GL_LINE_WIDTH 0x0B21
+#define GR_GL_ALIASED_POINT_SIZE_RANGE 0x846D
+#define GR_GL_ALIASED_LINE_WIDTH_RANGE 0x846E
+#define GR_GL_CULL_FACE_MODE 0x0B45
+#define GR_GL_FRONT_FACE 0x0B46
+#define GR_GL_DEPTH_RANGE 0x0B70
+#define GR_GL_DEPTH_WRITEMASK 0x0B72
+#define GR_GL_DEPTH_CLEAR_VALUE 0x0B73
+#define GR_GL_DEPTH_FUNC 0x0B74
+#define GR_GL_STENCIL_CLEAR_VALUE 0x0B91
+#define GR_GL_STENCIL_FUNC 0x0B92
+#define GR_GL_STENCIL_FAIL 0x0B94
+#define GR_GL_STENCIL_PASS_DEPTH_FAIL 0x0B95
+#define GR_GL_STENCIL_PASS_DEPTH_PASS 0x0B96
+#define GR_GL_STENCIL_REF 0x0B97
+#define GR_GL_STENCIL_VALUE_MASK 0x0B93
+#define GR_GL_STENCIL_WRITEMASK 0x0B98
+#define GR_GL_STENCIL_BACK_FUNC 0x8800
+#define GR_GL_STENCIL_BACK_FAIL 0x8801
+#define GR_GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802
+#define GR_GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803
+#define GR_GL_STENCIL_BACK_REF 0x8CA3
+#define GR_GL_STENCIL_BACK_VALUE_MASK 0x8CA4
+#define GR_GL_STENCIL_BACK_WRITEMASK 0x8CA5
+#define GR_GL_VIEWPORT 0x0BA2
+#define GR_GL_SCISSOR_BOX 0x0C10
+/* GL_SCISSOR_TEST */
+#define GR_GL_COLOR_CLEAR_VALUE 0x0C22
+#define GR_GL_COLOR_WRITEMASK 0x0C23
+#define GR_GL_UNPACK_ALIGNMENT 0x0CF5
+#define GR_GL_UNPACK_FLIP_Y 0x9240
+#define GR_GL_PACK_ALIGNMENT 0x0D05
+#define GR_GL_PACK_REVERSE_ROW_ORDER 0x93A4
+#define GR_GL_MAX_TEXTURE_SIZE 0x0D33
+#define GR_GL_MAX_VIEWPORT_DIMS 0x0D3A
+#define GR_GL_SUBPIXEL_BITS 0x0D50
+#define GR_GL_RED_BITS 0x0D52
+#define GR_GL_GREEN_BITS 0x0D53
+#define GR_GL_BLUE_BITS 0x0D54
+#define GR_GL_ALPHA_BITS 0x0D55
+#define GR_GL_DEPTH_BITS 0x0D56
+#define GR_GL_STENCIL_BITS 0x0D57
+#define GR_GL_POLYGON_OFFSET_UNITS 0x2A00
+/* GL_POLYGON_OFFSET_FILL */
+#define GR_GL_POLYGON_OFFSET_FACTOR 0x8038
+#define GR_GL_TEXTURE_BINDING_2D 0x8069
+#define GR_GL_SAMPLE_BUFFERS 0x80A8
+#define GR_GL_SAMPLES 0x80A9
+#define GR_GL_SAMPLE_COVERAGE_VALUE 0x80AA
+#define GR_GL_SAMPLE_COVERAGE_INVERT 0x80AB
+#define GR_GL_RENDERBUFFER_COVERAGE_SAMPLES 0x8CAB
+#define GR_GL_RENDERBUFFER_COLOR_SAMPLES 0x8E10
+#define GR_GL_MAX_MULTISAMPLE_COVERAGE_MODES 0x8E11
+#define GR_GL_MULTISAMPLE_COVERAGE_MODES 0x8E12
+
+/* GetTextureParameter */
+/* GL_TEXTURE_MAG_FILTER */
+/* GL_TEXTURE_MIN_FILTER */
+/* GL_TEXTURE_WRAP_S */
+/* GL_TEXTURE_WRAP_T */
+
+#define GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2
+#define GR_GL_COMPRESSED_TEXTURE_FORMATS 0x86A3
+
+/* HintMode */
+#define GR_GL_DONT_CARE 0x1100
+#define GR_GL_FASTEST 0x1101
+#define GR_GL_NICEST 0x1102
+
+/* HintTarget */
+#define GR_GL_GENERATE_MIPMAP_HINT 0x8192
+
+/* DataType */
+#define GR_GL_BYTE 0x1400
+#define GR_GL_UNSIGNED_BYTE 0x1401
+#define GR_GL_SHORT 0x1402
+#define GR_GL_UNSIGNED_SHORT 0x1403
+#define GR_GL_INT 0x1404
+#define GR_GL_UNSIGNED_INT 0x1405
+#define GR_GL_FLOAT 0x1406
+#define GR_GL_FIXED 0x140C
+
+/* Lighting */
+#define GR_GL_LIGHTING 0x0B50
+#define GR_GL_LIGHT0 0x4000
+#define GR_GL_LIGHT1 0x4001
+#define GR_GL_LIGHT2 0x4002
+#define GR_GL_LIGHT3 0x4003
+#define GR_GL_LIGHT4 0x4004
+#define GR_GL_LIGHT5 0x4005
+#define GR_GL_LIGHT6 0x4006
+#define GR_GL_LIGHT7 0x4007
+#define GR_GL_SPOT_EXPONENT 0x1205
+#define GR_GL_SPOT_CUTOFF 0x1206
+#define GR_GL_CONSTANT_ATTENUATION 0x1207
+#define GR_GL_LINEAR_ATTENUATION 0x1208
+#define GR_GL_QUADRATIC_ATTENUATION 0x1209
+#define GR_GL_AMBIENT 0x1200
+#define GR_GL_DIFFUSE 0x1201
+#define GR_GL_SPECULAR 0x1202
+#define GR_GL_SHININESS 0x1601
+#define GR_GL_EMISSION 0x1600
+#define GR_GL_POSITION 0x1203
+#define GR_GL_SPOT_DIRECTION 0x1204
+#define GR_GL_AMBIENT_AND_DIFFUSE 0x1602
+#define GR_GL_COLOR_INDEXES 0x1603
+#define GR_GL_LIGHT_MODEL_TWO_SIDE 0x0B52
+#define GR_GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51
+#define GR_GL_LIGHT_MODEL_AMBIENT 0x0B53
+#define GR_GL_FRONT_AND_BACK 0x0408
+#define GR_GL_SHADE_MODEL 0x0B54
+#define GR_GL_FLAT 0x1D00
+#define GR_GL_SMOOTH 0x1D01
+#define GR_GL_COLOR_MATERIAL 0x0B57
+#define GR_GL_COLOR_MATERIAL_FACE 0x0B55
+#define GR_GL_COLOR_MATERIAL_PARAMETER 0x0B56
+#define GR_GL_NORMALIZE 0x0BA1
+
+/* Matrix Mode */
+#define GR_GL_MATRIX_MODE 0x0BA0
+#define GR_GL_MODELVIEW 0x1700
+#define GR_GL_PROJECTION 0x1701
+#define GR_GL_TEXTURE 0x1702
+
+/* multisample */
+#define GR_GL_MULTISAMPLE 0x809D
+
+/* Points */
+#define GR_GL_POINT_SMOOTH 0x0B10
+#define GR_GL_POINT_SIZE 0x0B11
+#define GR_GL_POINT_SIZE_GRANULARITY 0x0B13
+#define GR_GL_POINT_SIZE_RANGE 0x0B12
+
+/* Lines */
+#define GR_GL_LINE_SMOOTH 0x0B20
+#define GR_GL_LINE_STIPPLE 0x0B24
+#define GR_GL_LINE_STIPPLE_PATTERN 0x0B25
+#define GR_GL_LINE_STIPPLE_REPEAT 0x0B26
+#define GR_GL_LINE_WIDTH 0x0B21
+#define GR_GL_LINE_WIDTH_GRANULARITY 0x0B23
+#define GR_GL_LINE_WIDTH_RANGE 0x0B22
+
+/* PixelFormat */
+#define GR_GL_DEPTH_COMPONENT 0x1902
+#define GR_GL_RED 0x1903
+#define GR_GL_GREEN 0x1904
+#define GR_GL_BLUE 0x1905
+#define GR_GL_ALPHA 0x1906
+#define GR_GL_RGB 0x1907
+#define GR_GL_RGBA 0x1908
+#define GR_GL_BGRA 0x80E1
+#define GR_GL_LUMINANCE 0x1909
+#define GR_GL_LUMINANCE_ALPHA 0x190A
+#define GR_GL_PALETTE8_RGBA8 0x8B96
+#define GR_GL_ALPHA8 0x803C
+
+#define GR_GL_R8 0x8229
+
+/* PixelType */
+/* GL_UNSIGNED_BYTE */
+#define GR_GL_UNSIGNED_SHORT_4_4_4_4 0x8033
+#define GR_GL_UNSIGNED_SHORT_5_5_5_1 0x8034
+#define GR_GL_UNSIGNED_SHORT_5_6_5 0x8363
+
+/* Shaders */
+#define GR_GL_FRAGMENT_SHADER 0x8B30
+#define GR_GL_VERTEX_SHADER 0x8B31
+#define GR_GL_GEOMETRY_SHADER 0x8DD9
+#define GR_GL_MAX_VERTEX_ATTRIBS 0x8869
+#define GR_GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB
+#define GR_GL_MAX_VARYING_VECTORS 0x8DFC
+#define GR_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
+#define GR_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C
+#define GR_GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
+#define GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD
+#define GR_GL_SHADER_TYPE 0x8B4F
+#define GR_GL_DELETE_STATUS 0x8B80
+#define GR_GL_LINK_STATUS 0x8B82
+#define GR_GL_VALIDATE_STATUS 0x8B83
+#define GR_GL_ATTACHED_SHADERS 0x8B85
+#define GR_GL_ACTIVE_UNIFORMS 0x8B86
+#define GR_GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87
+#define GR_GL_ACTIVE_ATTRIBUTES 0x8B89
+#define GR_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A
+#define GR_GL_SHADING_LANGUAGE_VERSION 0x8B8C
+#define GR_GL_CURRENT_PROGRAM 0x8B8D
+#define GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49
+#define GR_GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A
+
+/* StencilFunction */
+#define GR_GL_NEVER 0x0200
+#define GR_GL_LESS 0x0201
+#define GR_GL_EQUAL 0x0202
+#define GR_GL_LEQUAL 0x0203
+#define GR_GL_GREATER 0x0204
+#define GR_GL_NOTEQUAL 0x0205
+#define GR_GL_GEQUAL 0x0206
+#define GR_GL_ALWAYS 0x0207
+
+/* StencilOp */
+/* GL_ZERO */
+#define GR_GL_KEEP 0x1E00
+#define GR_GL_REPLACE 0x1E01
+#define GR_GL_INCR 0x1E02
+#define GR_GL_DECR 0x1E03
+#define GR_GL_INVERT 0x150A
+#define GR_GL_INCR_WRAP 0x8507
+#define GR_GL_DECR_WRAP 0x8508
+
+/* StringName */
+#define GR_GL_VENDOR 0x1F00
+#define GR_GL_RENDERER 0x1F01
+#define GR_GL_VERSION 0x1F02
+#define GR_GL_EXTENSIONS 0x1F03
+
+/* StringCounts */
+#define GR_GL_NUM_EXTENSIONS 0x821D
+
+/* Pixel Mode / Transfer */
+#define GR_GL_UNPACK_ROW_LENGTH 0x0CF2
+#define GR_GL_PACK_ROW_LENGTH 0x0D02
+
+
+/* TextureMagFilter */
+#define GR_GL_NEAREST 0x2600
+#define GR_GL_LINEAR 0x2601
+
+/* TextureMinFilter */
+/* GL_NEAREST */
+/* GL_LINEAR */
+#define GR_GL_NEAREST_MIPMAP_NEAREST 0x2700
+#define GR_GL_LINEAR_MIPMAP_NEAREST 0x2701
+#define GR_GL_NEAREST_MIPMAP_LINEAR 0x2702
+#define GR_GL_LINEAR_MIPMAP_LINEAR 0x2703
+
+/* TextureUsage */
+#define GR_GL_FRAMEBUFFER_ATTACHMENT 0x93A3
+
+/* TextureParameterName */
+#define GR_GL_TEXTURE_MAG_FILTER 0x2800
+#define GR_GL_TEXTURE_MIN_FILTER 0x2801
+#define GR_GL_TEXTURE_WRAP_S 0x2802
+#define GR_GL_TEXTURE_WRAP_T 0x2803
+#define GR_GL_TEXTURE_USAGE 0x93A2
+
+/* TextureTarget */
+/* GL_TEXTURE_2D */
+#define GR_GL_TEXTURE 0x1702
+#define GR_GL_TEXTURE_CUBE_MAP 0x8513
+#define GR_GL_TEXTURE_BINDING_CUBE_MAP 0x8514
+#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
+#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
+#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
+#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
+#define GR_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
+#define GR_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
+#define GR_GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C
+
+/* TextureUnit */
+#define GR_GL_TEXTURE0 0x84C0
+#define GR_GL_TEXTURE1 0x84C1
+#define GR_GL_TEXTURE2 0x84C2
+#define GR_GL_TEXTURE3 0x84C3
+#define GR_GL_TEXTURE4 0x84C4
+#define GR_GL_TEXTURE5 0x84C5
+#define GR_GL_TEXTURE6 0x84C6
+#define GR_GL_TEXTURE7 0x84C7
+#define GR_GL_TEXTURE8 0x84C8
+#define GR_GL_TEXTURE9 0x84C9
+#define GR_GL_TEXTURE10 0x84CA
+#define GR_GL_TEXTURE11 0x84CB
+#define GR_GL_TEXTURE12 0x84CC
+#define GR_GL_TEXTURE13 0x84CD
+#define GR_GL_TEXTURE14 0x84CE
+#define GR_GL_TEXTURE15 0x84CF
+#define GR_GL_TEXTURE16 0x84D0
+#define GR_GL_TEXTURE17 0x84D1
+#define GR_GL_TEXTURE18 0x84D2
+#define GR_GL_TEXTURE19 0x84D3
+#define GR_GL_TEXTURE20 0x84D4
+#define GR_GL_TEXTURE21 0x84D5
+#define GR_GL_TEXTURE22 0x84D6
+#define GR_GL_TEXTURE23 0x84D7
+#define GR_GL_TEXTURE24 0x84D8
+#define GR_GL_TEXTURE25 0x84D9
+#define GR_GL_TEXTURE26 0x84DA
+#define GR_GL_TEXTURE27 0x84DB
+#define GR_GL_TEXTURE28 0x84DC
+#define GR_GL_TEXTURE29 0x84DD
+#define GR_GL_TEXTURE30 0x84DE
+#define GR_GL_TEXTURE31 0x84DF
+#define GR_GL_ACTIVE_TEXTURE 0x84E0
+#define GR_GL_MAX_TEXTURE_UNITS 0x84E2
+
+/* TextureWrapMode */
+#define GR_GL_REPEAT 0x2901
+#define GR_GL_CLAMP_TO_EDGE 0x812F
+#define GR_GL_MIRRORED_REPEAT 0x8370
+
+/* Texture Swizzle */
+#define GR_GL_TEXTURE_SWIZZLE_R 0x8E42
+#define GR_GL_TEXTURE_SWIZZLE_G 0x8E43
+#define GR_GL_TEXTURE_SWIZZLE_B 0x8E44
+#define GR_GL_TEXTURE_SWIZZLE_A 0x8E45
+#define GR_GL_TEXTURE_SWIZZLE_RGBA 0x8E46
+
+/* Texture mapping */
+#define GR_GL_TEXTURE_ENV 0x2300
+#define GR_GL_TEXTURE_ENV_MODE 0x2200
+#define GR_GL_TEXTURE_1D 0x0DE0
+/* GL_TEXTURE_2D */
+/* GL_TEXTURE_WRAP_S */
+/* GL_TEXTURE_WRAP_T */
+/* GL_TEXTURE_MAG_FILTER */
+/* GL_TEXTURE_MIN_FILTER */
+#define GR_GL_TEXTURE_ENV_COLOR 0x2201
+#define GR_GL_TEXTURE_GEN_S 0x0C60
+#define GR_GL_TEXTURE_GEN_T 0x0C61
+#define GR_GL_TEXTURE_GEN_MODE 0x2500
+#define GR_GL_TEXTURE_BORDER_COLOR 0x1004
+#define GR_GL_TEXTURE_WIDTH 0x1000
+#define GR_GL_TEXTURE_HEIGHT 0x1001
+#define GR_GL_TEXTURE_BORDER 0x1005
+#define GR_GL_TEXTURE_COMPONENTS 0x1003
+#define GR_GL_TEXTURE_RED_SIZE 0x805C
+#define GR_GL_TEXTURE_GREEN_SIZE 0x805D
+#define GR_GL_TEXTURE_BLUE_SIZE 0x805E
+#define GR_GL_TEXTURE_ALPHA_SIZE 0x805F
+#define GR_GL_TEXTURE_LUMINANCE_SIZE 0x8060
+#define GR_GL_TEXTURE_INTENSITY_SIZE 0x8061
+#define GR_GL_TEXTURE_INTERNAL_FORMAT 0x1003
+/* GL_NEAREST_MIPMAP_NEAREST */
+/* GL_NEAREST_MIPMAP_LINEAR */
+/* GL_LINEAR_MIPMAP_NEAREST */
+/* GL_LINEAR_MIPMAP_LINEAR */
+#define GR_GL_OBJECT_LINEAR 0x2401
+#define GR_GL_OBJECT_PLANE 0x2501
+#define GR_GL_EYE_LINEAR 0x2400
+#define GR_GL_EYE_PLANE 0x2502
+#define GR_GL_SPHERE_MAP 0x2402
+#define GR_GL_DECAL 0x2101
+#define GR_GL_MODULATE 0x2100
+/* GL_NEAREST */
+/* GL_REPEAT */
+#define GR_GL_CLAMP 0x2900
+#define GR_GL_S 0x2000
+#define GR_GL_T 0x2001
+#define GR_GL_R 0x2002
+#define GR_GL_Q 0x2003
+#define GR_GL_TEXTURE_GEN_R 0x0C62
+#define GR_GL_TEXTURE_GEN_Q 0x0C63
+
+/* texture_env_combine */
+#define GR_GL_COMBINE 0x8570
+#define GR_GL_COMBINE_RGB 0x8571
+#define GR_GL_COMBINE_ALPHA 0x8572
+#define GR_GL_SOURCE0_RGB 0x8580
+#define GR_GL_SOURCE1_RGB 0x8581
+#define GR_GL_SOURCE2_RGB 0x8582
+#define GR_GL_SOURCE0_ALPHA 0x8588
+#define GR_GL_SOURCE1_ALPHA 0x8589
+#define GR_GL_SOURCE2_ALPHA 0x858A
+#define GR_GL_OPERAND0_RGB 0x8590
+#define GR_GL_OPERAND1_RGB 0x8591
+#define GR_GL_OPERAND2_RGB 0x8592
+#define GR_GL_OPERAND0_ALPHA 0x8598
+#define GR_GL_OPERAND1_ALPHA 0x8599
+#define GR_GL_OPERAND2_ALPHA 0x859A
+#define GR_GL_RGB_SCALE 0x8573
+#define GR_GL_ADD_SIGNED 0x8574
+#define GR_GL_INTERPOLATE 0x8575
+#define GR_GL_SUBTRACT 0x84E7
+#define GR_GL_CONSTANT 0x8576
+#define GR_GL_PRIMARY_COLOR 0x8577
+#define GR_GL_PREVIOUS 0x8578
+#define GR_GL_SRC0_RGB 0x8580
+#define GR_GL_SRC1_RGB 0x8581
+#define GR_GL_SRC2_RGB 0x8582
+#define GR_GL_SRC0_ALPHA 0x8588
+#define GR_GL_SRC1_ALPHA 0x8589
+#define GR_GL_SRC2_ALPHA 0x858A
+
+/* Uniform Types */
+#define GR_GL_FLOAT_VEC2 0x8B50
+#define GR_GL_FLOAT_VEC3 0x8B51
+#define GR_GL_FLOAT_VEC4 0x8B52
+#define GR_GL_INT_VEC2 0x8B53
+#define GR_GL_INT_VEC3 0x8B54
+#define GR_GL_INT_VEC4 0x8B55
+#define GR_GL_BOOL 0x8B56
+#define GR_GL_BOOL_VEC2 0x8B57
+#define GR_GL_BOOL_VEC3 0x8B58
+#define GR_GL_BOOL_VEC4 0x8B59
+#define GR_GL_FLOAT_MAT2 0x8B5A
+#define GR_GL_FLOAT_MAT3 0x8B5B
+#define GR_GL_FLOAT_MAT4 0x8B5C
+#define GR_GL_SAMPLER_2D 0x8B5E
+#define GR_GL_SAMPLER_CUBE 0x8B60
+
+/* Vertex Arrays */
+#define GR_GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622
+#define GR_GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623
+#define GR_GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624
+#define GR_GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625
+#define GR_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A
+#define GR_GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645
+#define GR_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F
+#define GR_GL_VERTEX_ARRAY 0x8074
+#define GR_GL_NORMAL_ARRAY 0x8075
+#define GR_GL_COLOR_ARRAY 0x8076
+#define GR_GL_INDEX_ARRAY 0x8077
+#define GR_GL_TEXTURE_COORD_ARRAY 0x8078
+#define GR_GL_EDGE_FLAG_ARRAY 0x8079
+#define GR_GL_VERTEX_ARRAY_SIZE 0x807A
+#define GR_GL_VERTEX_ARRAY_TYPE 0x807B
+#define GR_GL_VERTEX_ARRAY_STRIDE 0x807C
+#define GR_GL_NORMAL_ARRAY_TYPE 0x807E
+#define GR_GL_NORMAL_ARRAY_STRIDE 0x807F
+#define GR_GL_COLOR_ARRAY_SIZE 0x8081
+#define GR_GL_COLOR_ARRAY_TYPE 0x8082
+#define GR_GL_COLOR_ARRAY_STRIDE 0x8083
+#define GR_GL_INDEX_ARRAY_TYPE 0x8085
+#define GR_GL_INDEX_ARRAY_STRIDE 0x8086
+#define GR_GL_TEXTURE_COORD_ARRAY_SIZE 0x8088
+#define GR_GL_TEXTURE_COORD_ARRAY_TYPE 0x8089
+#define GR_GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A
+#define GR_GL_EDGE_FLAG_ARRAY_STRIDE 0x808C
+#define GR_GL_VERTEX_ARRAY_POINTER 0x808E
+#define GR_GL_NORMAL_ARRAY_POINTER 0x808F
+#define GR_GL_COLOR_ARRAY_POINTER 0x8090
+#define GR_GL_INDEX_ARRAY_POINTER 0x8091
+#define GR_GL_TEXTURE_COORD_ARRAY_POINTER 0x8092
+#define GR_GL_EDGE_FLAG_ARRAY_POINTER 0x8093
+#define GR_GL_V2F 0x2A20
+#define GR_GL_V3F 0x2A21
+#define GR_GL_C4UB_V2F 0x2A22
+#define GR_GL_C4UB_V3F 0x2A23
+#define GR_GL_C3F_V3F 0x2A24
+#define GR_GL_N3F_V3F 0x2A25
+#define GR_GL_C4F_N3F_V3F 0x2A26
+#define GR_GL_T2F_V3F 0x2A27
+#define GR_GL_T4F_V4F 0x2A28
+#define GR_GL_T2F_C4UB_V3F 0x2A29
+#define GR_GL_T2F_C3F_V3F 0x2A2A
+#define GR_GL_T2F_N3F_V3F 0x2A2B
+#define GR_GL_T2F_C4F_N3F_V3F 0x2A2C
+#define GR_GL_T4F_C4F_N3F_V4F 0x2A2D
+
+/* Vertex Buffer Object */
+#define GR_GL_WRITE_ONLY 0x88B9
+#define GR_GL_BUFFER_MAPPED 0x88BC
+/* Read Format */
+#define GR_GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A
+#define GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B
+
+/* Shader Source */
+#define GR_GL_COMPILE_STATUS 0x8B81
+#define GR_GL_INFO_LOG_LENGTH 0x8B84
+#define GR_GL_SHADER_SOURCE_LENGTH 0x8B88
+#define GR_GL_SHADER_COMPILER 0x8DFA
+
+/* Shader Binary */
+#define GR_GL_SHADER_BINARY_FORMATS 0x8DF8
+#define GR_GL_NUM_SHADER_BINARY_FORMATS 0x8DF9
+
+/* Shader Precision-Specified Types */
+#define GR_GL_LOW_FLOAT 0x8DF0
+#define GR_GL_MEDIUM_FLOAT 0x8DF1
+#define GR_GL_HIGH_FLOAT 0x8DF2
+#define GR_GL_LOW_INT 0x8DF3
+#define GR_GL_MEDIUM_INT 0x8DF4
+#define GR_GL_HIGH_INT 0x8DF5
+
+/* Queries */
+#define GR_GL_QUERY_COUNTER_BITS 0x8864
+#define GR_GL_CURRENT_QUERY 0x8865
+#define GR_GL_QUERY_RESULT 0x8866
+#define GR_GL_QUERY_RESULT_AVAILABLE 0x8867
+#define GR_GL_SAMPLES_PASSED 0x8914
+#define GR_GL_ANY_SAMPLES_PASSED 0x8C2F
+#define GR_GL_TIME_ELAPSED 0x88BF
+#define GR_GL_TIMESTAMP 0x8E28
+#define GR_GL_PRIMITIVES_GENERATED 0x8C87
+#define GR_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88
+
+
+/* Framebuffer Object. */
+#define GR_GL_FRAMEBUFFER 0x8D40
+#define GR_GL_READ_FRAMEBUFFER 0x8CA8
+#define GR_GL_DRAW_FRAMEBUFFER 0x8CA9
+
+#define GR_GL_RENDERBUFFER 0x8D41
+
+#define GR_GL_RGBA4 0x8056
+#define GR_GL_RGB5_A1 0x8057
+#define GR_GL_RGB565 0x8D62
+#define GR_GL_RGBA8 0x8058
+#define GR_GL_RGB8 0x8051
+#define GR_GL_BGRA8 0x93A1
+#define GR_GL_SRGB 0x8C40
+#define GR_GL_SRGB8 0x8C41
+#define GR_GL_SRGB_ALPHA 0x8C42
+#define GR_GL_SRGB8_ALPHA8 0x8C43
+#define GR_GL_DEPTH_COMPONENT16 0x81A5
+#define GR_GL_STENCIL_INDEX 0x1901
+#define GR_GL_STENCIL_INDEX4 0x8D47
+#define GR_GL_STENCIL_INDEX8 0x8D48
+#define GR_GL_STENCIL_INDEX16 0x8D49
+#define GR_GL_DEPTH_STENCIL 0x84F9
+#define GR_GL_DEPTH24_STENCIL8 0x88F0
+
+#define GR_GL_MAX_SAMPLES 0x8D57
+// GL_IMG_multisampled_render_to_texture uses a different value for GL_MAX_SAMPLES
+#define GR_GL_MAX_SAMPLES_IMG 0x9135
+
+#define GR_GL_RENDERBUFFER_WIDTH 0x8D42
+#define GR_GL_RENDERBUFFER_HEIGHT 0x8D43
+#define GR_GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44
+#define GR_GL_RENDERBUFFER_RED_SIZE 0x8D50
+#define GR_GL_RENDERBUFFER_GREEN_SIZE 0x8D51
+#define GR_GL_RENDERBUFFER_BLUE_SIZE 0x8D52
+#define GR_GL_RENDERBUFFER_ALPHA_SIZE 0x8D53
+#define GR_GL_RENDERBUFFER_DEPTH_SIZE 0x8D54
+#define GR_GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
+
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216
+#define GR_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217
+
+#define GR_GL_COLOR_ATTACHMENT0 0x8CE0
+#define GR_GL_DEPTH_ATTACHMENT 0x8D00
+#define GR_GL_STENCIL_ATTACHMENT 0x8D20
+
+#define GR_GL_NONE 0
+
+#define GR_GL_FRAMEBUFFER_COMPLETE 0x8CD5
+#define GR_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
+#define GR_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
+#define GR_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9
+#define GR_GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
+
+#define GR_GL_FRAMEBUFFER_BINDING 0x8CA6
+#define GR_GL_RENDERBUFFER_BINDING 0x8CA7
+#define GR_GL_MAX_RENDERBUFFER_SIZE 0x84E8
+
+#define GR_GL_INVALID_FRAMEBUFFER_OPERATION 0x0506
+
+/* Path Rendering */
+// commands
+#define GR_GL_CLOSE_PATH 0x00
+#define GR_GL_MOVE_TO 0x02
+#define GR_GL_RELATIVE_MOVE_TO 0x03
+#define GR_GL_LINE_TO 0x04
+#define GR_GL_RELATIVE_LINE_TO 0x05
+#define GR_GL_HORIZONTAL_LINE_TO 0x06
+#define GR_GL_RELATIVE_HORIZONTAL_LINE_TO 0x07
+#define GR_GL_VERTICAL_LINE_TO 0x08
+#define GR_GL_RELATIVE_VERTICAL_LINE_TO 0x09
+#define GR_GL_QUADRATIC_CURVE_TO 0x0A
+#define GR_GL_RELATIVE_QUADRATIC_CURVE_TO 0x0B
+#define GR_GL_CUBIC_CURVE_TO 0x0C
+#define GR_GL_RELATIVE_CUBIC_CURVE_TO 0x0D
+#define GR_GL_SMOOTH_QUADRATIC_CURVE_TO 0x0E
+#define GR_GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO 0x0F
+#define GR_GL_SMOOTH_CUBIC_CURVE_TO 0x10
+#define GR_GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO 0x11
+#define GR_GL_SMALL_CCW_ARC_TO 0x12
+#define GR_GL_RELATIVE_SMALL_CCW_ARC_TO 0x13
+#define GR_GL_SMALL_CW_ARC_TO 0x14
+#define GR_GL_RELATIVE_SMALL_CW_ARC_TO 0x15
+#define GR_GL_LARGE_CCW_ARC_TO 0x16
+#define GR_GL_RELATIVE_LARGE_CCW_ARC_TO 0x17
+#define GR_GL_LARGE_CW_ARC_TO 0x18
+#define GR_GL_RELATIVE_LARGE_CW_ARC_TO 0x19
+#define GR_GL_CIRCULAR_CCW_ARC_TO 0xF8
+#define GR_GL_CIRCULAR_CW_ARC_TO 0xFA
+#define GR_GL_CIRCULAR_TANGENT_ARC_TO 0xFC
+#define GR_GL_ARC_TO 0xFE
+#define GR_GL_RELATIVE_ARC_TO 0xFF
+
+// path string formats
+#define GR_GL_PATH_FORMAT_SVG 0x9070
+#define GR_GL_PATH_FORMAT_PS 0x9071
+
+// font targets
+#define GR_GL_STANDARD_FONT_NAME 0x9072
+#define GR_GL_SYSTEM_FONT_NAME 0x9073
+#define GR_GL_FILE_NAME 0x9074
+
+// handle missing glyphs
+#define GR_GL_SKIP_MISSING_GLYPH 0x90A9
+#define GR_GL_USE_MISSING_GLYPH 0x90AA
+
+// path parameters
+#define GR_GL_PATH_STROKE_WIDTH 0x9075
+#define GR_GL_PATH_INITIAL_END_CAP 0x9077
+#define GR_GL_PATH_TERMINAL_END_CAP 0x9078
+#define GR_GL_PATH_JOIN_STYLE 0x9079
+#define GR_GL_PATH_MITER_LIMIT 0x907A
+#define GR_GL_PATH_INITIAL_DASH_CAP 0x907C
+#define GR_GL_PATH_TERMINAL_DASH_CAP 0x907D
+#define GR_GL_PATH_DASH_OFFSET 0x907E
+#define GR_GL_PATH_CLIENT_LENGTH 0x907F
+#define GR_GL_PATH_DASH_OFFSET_RESET 0x90B4
+#define GR_GL_PATH_FILL_MODE 0x9080
+#define GR_GL_PATH_FILL_MASK 0x9081
+#define GR_GL_PATH_FILL_COVER_MODE 0x9082
+#define GR_GL_PATH_STROKE_COVER_MODE 0x9083
+#define GR_GL_PATH_STROKE_MASK 0x9084
+#define GR_GL_PATH_END_CAPS 0x9076
+#define GR_GL_PATH_DASH_CAPS 0x907B
+#define GR_GL_PATH_COMMAND_COUNT 0x909D
+#define GR_GL_PATH_COORD_COUNT 0x909E
+#define GR_GL_PATH_DASH_ARRAY_COUNT 0x909F
+#define GR_GL_PATH_FILL_BOUNDING_BOX 0x90A1
+#define GR_GL_PATH_STROKE_BOUNDING_BOX 0x90A2
+
+// fill modes
+/* GL_INVERT */
+#define GR_GL_COUNT_UP 0x9088
+#define GR_GL_COUNT_DOWN 0x9089
+/* GL_PATH_FILL_MODE_NV */
+
+// path color gen
+/* GL_PRIMARY_COLOR */
+#define GR_GL_SECONDARY_COLOR 0x852D
+
+// gen mode
+/* GL_NONE */
+/* GL_EYE_LINEAR */
+/* GL_OBJECT_LINEAR */
+#define GR_GL_PATH_OBJECT_BOUNDING_BOX 0x908A
+
+// cover mode
+#define GR_GL_CONVEX_HULL 0x908B
+#define GR_GL_BOUNDING_BOX 0x908D
+#define GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES 0x909C
+/* GL_PATH_FILL_COVER_MODE_NV */
+
+// transform type
+/* GL_NONE */
+#define GR_GL_TRANSLATE_X 0x908E
+#define GR_GL_TRANSLATE_Y 0x908F
+#define GR_GL_TRANSLATE_2D 0x9090
+#define GR_GL_TRANSLATE_3D 0x9091
+#define GR_GL_AFFINE_2D 0x9092
+#define GR_GL_AFFINE_3D 0x9094
+#define GR_GL_TRANSPOSE_AFFINE_2D 0x9096
+#define GR_GL_TRANSPOSE_AFFINE_3D 0x9098
+
+// path string types
+#define GR_GL_UTF8 0x909A
+#define GR_GL_UTF16 0x909B
+
+#define GR_GL_PATH_COMPUTED_LENGTH 0x90A0
+
+// cap/dash values
+/* GL_FLAT */
+#define GR_GL_SQUARE 0x90A3
+#define GR_GL_ROUND 0x90A4
+#define GR_GL_TRIANGULAR 0x90A5
+
+// join values
+/* GL_NONE */
+/* GL_ROUND_NV */
+#define GR_GL_BEVEL 0x90A6
+#define GR_GL_MITER_REVERT 0x90A7
+#define GR_GL_MITER_TRUNCATE 0x90A8
+
+// path dash reset values
+#define GR_GL_MOVE_TO_RESETS 0x90B5
+#define GR_GL_MOVE_TO_CONTINUES 0x90B6
+
+// font styles
+/* GL_NONE */
+#define GR_GL_BOLD_BIT 0x01
+#define GR_GL_ITALIC_BIT 0x02
+
+// pnames for glGet
+#define GR_GL_PATH_ERROR_POSITION 0x90AB
+#define GR_GL_PATH_FOG_GEN_MODE 0x90AC
+#define GR_GL_PATH_STENCIL_FUNC 0x90B7
+#define GR_GL_PATH_STENCIL_REF 0x90B8
+#define GR_GL_PATH_STENCIL_VALUE_MASK 0x90B9
+#define GR_GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR 0x90BD
+#define GR_GL_PATH_STENCIL_DEPTH_OFFSET_UNITS 0x90BE
+#define GR_GL_PATH_COVER_DEPTH_FUNC 0x90BF
+
+// per-glyph metrics bits in metric mask query
+#define GR_GL_GLYPH_WIDTH_BIT 0x01
+#define GR_GL_GLYPH_HEIGHT_BIT 0x02
+#define GR_GL_GLYPH_HORIZONTAL_BEARING_X_BIT 0x04
+#define GR_GL_GLYPH_HORIZONTAL_BEARING_Y_BIT 0x08
+#define GR_GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT 0x10
+#define GR_GL_GLYPH_VERTICAL_BEARING_X_BIT 0x20
+#define GR_GL_GLYPH_VERTICAL_BEARING_Y_BIT 0x40
+#define GR_GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT 0x80
+#define GR_GL_GLYPH_HAS_KERNING 0x100
+
+// per-font face metrics in metric mask query
+#define GR_GL_FONT_X_MIN_BOUNDS 0x00010000
+#define GR_GL_FONT_Y_MIN_BOUNDS 0x00020000
+#define GR_GL_FONT_X_MAX_BOUNDS 0x00040000
+#define GR_GL_FONT_Y_MAX_BOUNDS 0x00080000
+#define GR_GL_FONT_UNITS_PER_EM 0x00100000
+#define GR_GL_FONT_ASCENDER 0x00200000
+#define GR_GL_FONT_DESCENDER 0x00400000
+#define GR_GL_FONT_HEIGHT 0x00800000
+#define GR_GL_FONT_MAX_ADVANCE_WIDTH 0x01000000
+#define GR_GL_FONT_MAX_ADVANCE_HEIGHT 0x02000000
+#define GR_GL_FONT_UNDERLINE_POSITION 0x04000000
+#define GR_GL_FONT_UNDERLINE_THICKNESS 0x08000000
+#define GR_GL_FONT_HAS_KERNING 0x10000000
+
+// path list modes (glGetPathSpacing)
+#define GR_GL_ACCUM_ADJACENT_PAIRS 0x90AD
+#define GR_GL_ADJACENT_PAIRS 0x90AE
+#define GR_GL_FIRST_TO_REST 0x90AF
+
+//path gen modes
+#define GR_GL_PATH_GEN_MODE 0x90B0
+#define GR_GL_PATH_GEN_COEFF 0x90B1
+#define GR_GL_PATH_GEN_COLOR_FORMAT 0x90B2
+#define GR_GL_PATH_GEN_COMPONENTS 0x90B3
+
+#endif
diff --git a/gpu/gl/GrGLEffect.cpp b/gpu/gl/GrGLEffect.cpp
new file mode 100644
index 00000000..e21ab1da
--- /dev/null
+++ b/gpu/gl/GrGLEffect.cpp
@@ -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.
+ */
+
+#include "GrGLSL.h"
+#include "GrGLEffect.h"
+#include "GrDrawEffect.h"
+
+GrGLEffect::GrGLEffect(const GrBackendEffectFactory& factory)
+ : fFactory(factory) {
+}
+
+GrGLEffect::~GrGLEffect() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLEffect::setData(const GrGLUniformManager&, const GrDrawEffect&) {
+}
+
+GrGLEffect::EffectKey GrGLEffect::GenTextureKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps& caps) {
+ EffectKey key = 0;
+ int numTextures = (*drawEffect.effect())->numTextures();
+ for (int index = 0; index < numTextures; ++index) {
+ const GrTextureAccess& access = (*drawEffect.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;
+}
+
+GrGLEffect::EffectKey GrGLEffect::GenAttribKey(const GrDrawEffect& drawEffect) {
+ EffectKey key = 0;
+
+ int numAttributes = drawEffect.getVertexAttribIndexCount();
+ GrAssert(numAttributes <= 2);
+ const int* attributeIndices = drawEffect.getVertexAttribIndices();
+ for (int index = 0; index < numAttributes; ++index) {
+ EffectKey value = attributeIndices[index] << 3*index;
+ GrAssert(0 == (value & key)); // keys for each attribute ought not to overlap
+ key |= value;
+ }
+
+ return key;
+}
diff --git a/gpu/gl/GrGLEffect.h b/gpu/gl/GrGLEffect.h
new file mode 100644
index 00000000..5df22811
--- /dev/null
+++ b/gpu/gl/GrGLEffect.h
@@ -0,0 +1,99 @@
+/*
+ * 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 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 GrDrawEffect&, 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.
+
+ The GrGLEffect subclass must also have a constructor of the form:
+ EffectSubclass::EffectSubclass(const GrBackendEffectFactory&, const GrDrawEffect&)
+ The effect held by the GrDrawEffect is guaranteed to be of the type that generated the
+ GrGLEffect subclass instance.
+
+ These objects are created by the factory object returned by the GrEffect::getFactory().
+*/
+
+class GrDrawEffect;
+
+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 drawEffect A wrapper on the effect 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 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 GrDrawEffect& drawEffect,
+ EffectKey key,
+ 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. Effects that use local coords have
+ to consider whether the GrEffectStage's coord change matrix should be used. When explicit
+ local coordinates are used it can be ignored. */
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&);
+
+ const char* name() const { return fFactory.name(); }
+
+ static EffectKey GenTextureKey(const GrDrawEffect&, const GrGLCaps&);
+ static EffectKey GenAttribKey(const GrDrawEffect& stage);
+
+protected:
+ const GrBackendEffectFactory& fFactory;
+};
+
+#endif
diff --git a/gpu/gl/GrGLEffectMatrix.cpp b/gpu/gl/GrGLEffectMatrix.cpp
new file mode 100644
index 00000000..523b37e7
--- /dev/null
+++ b/gpu/gl/GrGLEffectMatrix.cpp
@@ -0,0 +1,238 @@
+/*
+ * 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 "GrDrawEffect.h"
+#include "GrTexture.h"
+
+GrGLEffect::EffectKey GrGLEffectMatrix::GenKey(const SkMatrix& effectMatrix,
+ const GrDrawEffect& drawEffect,
+ CoordsType coordsType,
+ const GrTexture* texture) {
+ EffectKey key = 0;
+ SkMatrix::TypeMask type0 = effectMatrix.getType();
+ SkMatrix::TypeMask type1;
+ if (GrEffect::kLocal_CoordsType == coordsType) {
+ type1 = drawEffect.getCoordChangeMatrix().getType();
+ } else {
+ if (drawEffect.programHasExplicitLocalCoords()) {
+ // We only make the key indicate that device coords are referenced when the local coords
+ // are not actually determined by positions.
+ key |= kPositionCoords_Flag;
+ }
+ type1 = SkMatrix::kIdentity_Mask;
+ }
+
+ int combinedTypes = type0 | type1;
+
+ bool reverseY = (NULL != texture) && kBottomLeft_GrSurfaceOrigin == texture->origin();
+
+ if (SkMatrix::kPerspective_Mask & combinedTypes) {
+ key |= kGeneral_MatrixType;
+ } else if (((SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask) & combinedTypes) || reverseY) {
+ key |= kNoPersp_MatrixType;
+ } else if (SkMatrix::kTranslate_Mask & combinedTypes) {
+ key |= kTrans_MatrixType;
+ } else {
+ key |= kIdentity_MatrixType;
+ }
+ return key;
+}
+
+GrSLType GrGLEffectMatrix::emitCode(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char** fsCoordName,
+ const char** vsCoordName,
+ const char* suffix) {
+ GrSLType varyingType = kVoid_GrSLType;
+ const char* uniName;
+ key &= kKeyMask;
+ switch (key & kMatrixTypeKeyMask) {
+ case kIdentity_MatrixType:
+ fUniType = kVoid_GrSLType;
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kTrans_MatrixType:
+ fUniType = kVec2f_GrSLType;
+ uniName = "StageTranslate";
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kNoPersp_MatrixType:
+ fUniType = kMat33f_GrSLType;
+ uniName = "StageMatrix";
+ varyingType = kVec2f_GrSLType;
+ break;
+ case kGeneral_MatrixType:
+ 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 = "MatrixCoord";
+ 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);
+
+ const GrGLShaderVar* coords;
+ switch (fCoordsType) {
+ case GrEffect::kLocal_CoordsType:
+ GrAssert(!(kPositionCoords_Flag & key));
+ coords = &builder->localCoordsAttribute();
+ break;
+ case GrEffect::kPosition_CoordsType:
+ GrAssert((kPositionCoords_Flag & key) || !builder->hasExplicitLocalCoords());
+ coords = &builder->positionAttribute();
+ break;
+ default:
+ coords = NULL; // prevents warning
+ GrCrash("Unexpected coords type.");
+ }
+ // varying = matrix * coords (logically)
+ switch (fUniType) {
+ case kVoid_GrSLType:
+ GrAssert(kVec2f_GrSLType == varyingType);
+ builder->vsCodeAppendf("\t%s = %s;\n", vsVaryingName, coords->c_str());
+ break;
+ case kVec2f_GrSLType:
+ GrAssert(kVec2f_GrSLType == varyingType);
+ builder->vsCodeAppendf("\t%s = %s + %s;\n",
+ vsVaryingName, uniName, coords->c_str());
+ break;
+ case kMat33f_GrSLType: {
+ GrAssert(kVec2f_GrSLType == varyingType || kVec3f_GrSLType == varyingType);
+ if (kVec2f_GrSLType == varyingType) {
+ builder->vsCodeAppendf("\t%s = (%s * vec3(%s, 1)).xy;\n",
+ vsVaryingName, uniName, coords->c_str());
+ } else {
+ builder->vsCodeAppendf("\t%s = %s * vec3(%s, 1);\n",
+ vsVaryingName, uniName, coords->c_str());
+ }
+ 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** fsCoordName,
+ const char** vsVaryingName,
+ GrSLType* vsVaryingType,
+ const char* suffix) {
+ const char* fsVaryingName;
+
+ GrSLType varyingType = this->emitCode(builder,
+ key,
+ &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->fsCodeAppendf("\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 GrDrawEffect& drawEffect,
+ const GrTexture* texture) {
+ GrAssert((GrGLUniformManager::kInvalidUniformHandle == fUni) ==
+ (kVoid_GrSLType == fUniType));
+ const SkMatrix& coordChangeMatrix = GrEffect::kLocal_CoordsType == fCoordsType ?
+ drawEffect.getCoordChangeMatrix() :
+ SkMatrix::I();
+ switch (fUniType) {
+ case kVoid_GrSLType:
+ GrAssert(matrix.isIdentity());
+ GrAssert(coordChangeMatrix.isIdentity());
+ GrAssert(NULL == texture || kTopLeft_GrSurfaceOrigin == texture->origin());
+ return;
+ case kVec2f_GrSLType: {
+ GrAssert(SkMatrix::kTranslate_Mask == (matrix.getType() | coordChangeMatrix.getType()));
+ GrAssert(NULL == texture || kTopLeft_GrSurfaceOrigin == 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 && kBottomLeft_GrSurfaceOrigin == 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/gpu/gl/GrGLEffectMatrix.h b/gpu/gl/GrGLEffectMatrix.h
new file mode 100644
index 00000000..fc4b7831
--- /dev/null
+++ b/gpu/gl/GrGLEffectMatrix.h
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+/**
+ * This is a helper to implement a matrix in a GrGLEffect that operates on incoming coords in the
+ * vertex shader and writes them to an attribute to be used in the fragment shader. When the input
+ * coords in the vertex shader are local coordinates this class accounts for the coord change matrix
+ * communicated via GrDrawEffect. The input coords may also be positions and in this case the coord
+ * change matrix is ignored. The GrGLEffectMatrix will emit different code based on the type of
+ * matrix and thus must contribute to the effect's key.
+ *
+ * This class cannot be used to apply a matrix to coordinates that come in the form of custom vertex
+ * attributes.
+ */
+class GrGLEffectMatrix {
+private:
+ // We specialize the generated code for each of these matrix types.
+ enum MatrixTypes {
+ kIdentity_MatrixType = 0,
+ kTrans_MatrixType = 1,
+ kNoPersp_MatrixType = 2,
+ kGeneral_MatrixType = 3,
+ };
+ // The key for is made up of a matrix type and a bit that indicates the source of the input
+ // coords.
+ enum {
+ kMatrixTypeKeyBits = 2,
+ kMatrixTypeKeyMask = (1 << kMatrixTypeKeyBits) - 1,
+ kPositionCoords_Flag = (1 << kMatrixTypeKeyBits),
+ kKeyBitsPrivate = kMatrixTypeKeyBits + 1,
+ };
+
+public:
+
+ typedef GrEffect::CoordsType CoordsType;
+
+ 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 = kKeyBitsPrivate,
+ kKeyMask = (1 << kKeyBits) - 1,
+ };
+
+ GrGLEffectMatrix(CoordsType coordsType)
+ : fUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fCoordsType(coordsType) {
+ GrAssert(GrEffect::kLocal_CoordsType == coordsType ||
+ GrEffect::kPosition_CoordsType == coordsType);
+ 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 lookup, or if the GrGLEffect subclass
+ * wants to handle origin adjustments in some other manner. The coords type param must match the
+ * param that would be used to initialize GrGLEffectMatrix for the generating GrEffect.
+ */
+ static EffectKey GenKey(const SkMatrix& effectMatrix,
+ const GrDrawEffect&,
+ CoordsType,
+ 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** 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** 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 effectMatrix and texture
+ * params should match those used with GenKey.
+ */
+ void setData(const GrGLUniformManager& uniformManager,
+ const SkMatrix& effectMatrix,
+ const GrDrawEffect& drawEffect,
+ const GrTexture*);
+
+ GrGLUniformManager::UniformHandle fUni;
+ GrSLType fUniType;
+ SkMatrix fPrevMatrix;
+ CoordsType fCoordsType;
+};
+
+#endif
diff --git a/gpu/gl/GrGLExtensions.cpp b/gpu/gl/GrGLExtensions.cpp
new file mode 100644
index 00000000..e886aff9
--- /dev/null
+++ b/gpu/gl/GrGLExtensions.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gl/GrGLExtensions.h"
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLUtil.h"
+
+#include "SkTSearch.h"
+#include "SkTSort.h"
+
+namespace {
+inline bool extension_compare(const SkString& a, const SkString& b) {
+ return strcmp(a.c_str(), b.c_str()) < 0;
+}
+}
+
+bool GrGLExtensions::init(GrGLBinding binding,
+ GrGLGetStringProc getString,
+ GrGLGetStringiProc getStringi,
+ GrGLGetIntegervProc getIntegerv) {
+ fStrings.reset();
+ if (NULL == getString) {
+ return false;
+ }
+ bool indexed = false;
+ if (kDesktop_GrGLBinding == binding) {
+ const GrGLubyte* verString = getString(GR_GL_VERSION);
+ if (NULL == verString) {
+ return false;
+ }
+ GrGLVersion version = GrGLGetVersionFromString((const char*) verString);
+ indexed = version >= GR_GL_VER(3, 0);
+ }
+ if (indexed) {
+ if (NULL == getStringi || NULL == getIntegerv) {
+ return false;
+ }
+ GrGLint extensionCnt = 0;
+ getIntegerv(GR_GL_NUM_EXTENSIONS, &extensionCnt);
+ fStrings.push_back_n(extensionCnt);
+ for (int i = 0; i < extensionCnt; ++i) {
+ const char* ext = (const char*) getStringi(GR_GL_EXTENSIONS, i);
+ fStrings[i] = ext;
+ }
+ } else {
+ const char* extensions = (const char*) getString(GR_GL_EXTENSIONS);
+ if (NULL == extensions) {
+ return false;
+ }
+ while (true) {
+ // skip over multiple spaces between extensions
+ while (' ' == *extensions) {
+ ++extensions;
+ }
+ // quit once we reach the end of the string.
+ if ('\0' == *extensions) {
+ break;
+ }
+ // we found an extension
+ size_t length = strcspn(extensions, " ");
+ fStrings.push_back().set(extensions, length);
+ extensions += length;
+ }
+ }
+ if (!fStrings.empty()) {
+ SkTLessFunctionToFunctorAdaptor<SkString, extension_compare> cmp;
+ SkTQSort(&fStrings.front(), &fStrings.back(), cmp);
+ }
+ return true;
+}
+
+bool GrGLExtensions::has(const char* ext) const {
+ if (fStrings.empty()) {
+ return false;
+ }
+ SkString extensionStr(ext);
+ int idx = SkTSearch<SkString, extension_compare>(&fStrings.front(),
+ fStrings.count(),
+ extensionStr,
+ sizeof(SkString));
+ return idx >= 0;
+}
+
+void GrGLExtensions::print(const char* sep) const {
+ if (NULL == sep) {
+ sep = " ";
+ }
+ int cnt = fStrings.count();
+ for (int i = 0; i < cnt; ++i) {
+ GrPrintf("%s%s", fStrings[i].c_str(), (i < cnt - 1) ? sep : "");
+ }
+}
diff --git a/gpu/gl/GrGLIRect.h b/gpu/gl/GrGLIRect.h
new file mode 100644
index 00000000..f171cf13
--- /dev/null
+++ b/gpu/gl/GrGLIRect.h
@@ -0,0 +1,79 @@
+/*
+ * 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 GrGLIRect_DEFINED
+#define GrGLIRect_DEFINED
+
+#include "gl/GrGLInterface.h"
+#include "GrGLUtil.h"
+
+/**
+ * Helper struct for dealing with the fact that Ganesh and GL use different
+ * window coordinate systems (top-down vs bottom-up)
+ */
+struct GrGLIRect {
+ GrGLint fLeft;
+ GrGLint fBottom;
+ GrGLsizei fWidth;
+ GrGLsizei fHeight;
+
+ void pushToGLViewport(const GrGLInterface* gl) const {
+ GR_GL_CALL(gl, Viewport(fLeft, fBottom, fWidth, fHeight));
+ }
+
+ void pushToGLScissor(const GrGLInterface* gl) const {
+ GR_GL_CALL(gl, Scissor(fLeft, fBottom, fWidth, fHeight));
+ }
+
+ void setFromGLViewport(const GrGLInterface* gl) {
+ GR_STATIC_ASSERT(sizeof(GrGLIRect) == 4*sizeof(GrGLint));
+ GR_GL_GetIntegerv(gl, GR_GL_VIEWPORT, (GrGLint*) this);
+ }
+
+ // sometimes we have a SkIRect from the client that we
+ // want to simultaneously make relative to GL's viewport
+ // and (optionally) convert from top-down to bottom-up.
+ void setRelativeTo(const GrGLIRect& glRect,
+ int leftOffset,
+ int topOffset,
+ int width,
+ int height,
+ GrSurfaceOrigin origin) {
+ fLeft = glRect.fLeft + leftOffset;
+ fWidth = width;
+ if (kBottomLeft_GrSurfaceOrigin == origin) {
+ fBottom = glRect.fBottom + (glRect.fHeight - topOffset - height);
+ } else {
+ fBottom = glRect.fBottom + topOffset;
+ }
+ fHeight = height;
+
+ GrAssert(fLeft >= 0);
+ GrAssert(fWidth >= 0);
+ GrAssert(fBottom >= 0);
+ GrAssert(fHeight >= 0);
+ }
+
+ bool contains(const GrGLIRect& glRect) const {
+ return fLeft <= glRect.fLeft &&
+ fBottom <= glRect.fBottom &&
+ fLeft + fWidth >= glRect.fLeft + glRect.fWidth &&
+ fBottom + fHeight >= glRect.fBottom + glRect.fHeight;
+ }
+
+ void invalidate() {fLeft = fWidth = fBottom = fHeight = -1;}
+
+ bool operator ==(const GrGLIRect& glRect) const {
+ return 0 == memcmp(this, &glRect, sizeof(GrGLIRect));
+ }
+
+ bool operator !=(const GrGLIRect& glRect) const {return !(*this == glRect);}
+};
+
+#endif
diff --git a/gpu/gl/GrGLIndexBuffer.cpp b/gpu/gl/GrGLIndexBuffer.cpp
new file mode 100644
index 00000000..b6290b18
--- /dev/null
+++ b/gpu/gl/GrGLIndexBuffer.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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 "GrGLIndexBuffer.h"
+#include "GrGpuGL.h"
+
+GrGLIndexBuffer::GrGLIndexBuffer(GrGpuGL* gpu, const Desc& desc)
+ : INHERITED(gpu, desc.fIsWrapped, desc.fSizeInBytes, desc.fDynamic, 0 == desc.fID)
+ , fImpl(gpu, desc, GR_GL_ELEMENT_ARRAY_BUFFER) {
+}
+
+void GrGLIndexBuffer::onRelease() {
+ if (this->isValid()) {
+ fImpl.release(this->getGpuGL());
+ }
+
+ INHERITED::onRelease();
+}
+
+void GrGLIndexBuffer::onAbandon() {
+ fImpl.abandon();
+ INHERITED::onAbandon();
+}
+
+void* GrGLIndexBuffer::lock() {
+ if (this->isValid()) {
+ return fImpl.lock(this->getGpuGL());
+ } else {
+ return NULL;
+ }
+}
+
+void* GrGLIndexBuffer::lockPtr() const {
+ return fImpl.lockPtr();
+}
+
+void GrGLIndexBuffer::unlock() {
+ if (this->isValid()) {
+ fImpl.unlock(this->getGpuGL());
+ }
+}
+
+bool GrGLIndexBuffer::isLocked() const {
+ return fImpl.isLocked();
+}
+
+bool GrGLIndexBuffer::updateData(const void* src, size_t srcSizeInBytes) {
+ if (this->isValid()) {
+ return fImpl.updateData(this->getGpuGL(), src, srcSizeInBytes);
+ } else {
+ return false;
+ }
+}
diff --git a/gpu/gl/GrGLIndexBuffer.h b/gpu/gl/GrGLIndexBuffer.h
new file mode 100644
index 00000000..5c8588b2
--- /dev/null
+++ b/gpu/gl/GrGLIndexBuffer.h
@@ -0,0 +1,57 @@
+/*
+ * 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 GrGLIndexBuffer_DEFINED
+#define GrGLIndexBuffer_DEFINED
+
+#include "GrIndexBuffer.h"
+#include "GrGLBufferImpl.h"
+#include "gl/GrGLInterface.h"
+
+class GrGpuGL;
+
+class GrGLIndexBuffer : public GrIndexBuffer {
+
+public:
+ typedef GrGLBufferImpl::Desc Desc;
+
+ GrGLIndexBuffer(GrGpuGL* gpu, const Desc& desc);
+ virtual ~GrGLIndexBuffer() { this->release(); }
+
+ GrGLuint bufferID() const { return fImpl.bufferID(); }
+ size_t baseOffset() const { return fImpl.baseOffset(); }
+
+ void bind() const {
+ if (this->isValid()) {
+ fImpl.bind(this->getGpuGL());
+ }
+ }
+
+ // overrides of GrIndexBuffer
+ virtual void* lock();
+ virtual void* lockPtr() const;
+ virtual void unlock();
+ virtual bool isLocked() const;
+ virtual bool updateData(const void* src, size_t srcSizeInBytes);
+
+protected:
+ // overrides of GrResource
+ virtual void onAbandon() SK_OVERRIDE;
+ virtual void onRelease() SK_OVERRIDE;
+
+private:
+ GrGpuGL* getGpuGL() const {
+ GrAssert(this->isValid());
+ return (GrGpuGL*)(this->getGpu());
+ }
+
+ GrGLBufferImpl fImpl;
+
+ typedef GrIndexBuffer INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLInterface.cpp b/gpu/gl/GrGLInterface.cpp
new file mode 100644
index 00000000..72d7934c
--- /dev/null
+++ b/gpu/gl/GrGLInterface.cpp
@@ -0,0 +1,391 @@
+/*
+ * 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 "gl/GrGLExtensions.h"
+#include "gl/GrGLUtil.h"
+
+#include <stdio.h>
+
+SK_DEFINE_INST_COUNT(GrGLInterface)
+
+#if GR_GL_PER_GL_FUNC_CALLBACK
+namespace {
+void GrGLDefaultInterfaceCallback(const GrGLInterface*) {}
+}
+#endif
+
+GrGLInterface::GrGLInterface() {
+ fBindingsExported = kNone_GrGLBinding;
+
+#if GR_GL_PER_GL_FUNC_CALLBACK
+ fCallback = GrGLDefaultInterfaceCallback;
+ fCallbackData = 0;
+#endif
+}
+
+bool GrGLInterface::validate(GrGLBinding binding) const {
+
+ // kNone must be 0 so that the check we're about to do can never succeed if
+ // binding == kNone.
+ GR_STATIC_ASSERT(kNone_GrGLBinding == 0);
+
+ if (0 == (binding & fBindingsExported)) {
+ return false;
+ }
+
+ GrGLExtensions extensions;
+ if (!extensions.init(binding, this)) {
+ return false;
+ }
+
+ // functions that are always required
+ if (NULL == fActiveTexture ||
+ NULL == fAttachShader ||
+ NULL == fBindAttribLocation ||
+ NULL == fBindBuffer ||
+ NULL == fBindTexture ||
+ NULL == fBlendFunc ||
+ NULL == fBlendColor || // -> GL >= 1.4, ES >= 2.0 or extension
+ NULL == fBufferData ||
+ NULL == fBufferSubData ||
+ NULL == fClear ||
+ NULL == fClearColor ||
+ NULL == fClearStencil ||
+ NULL == fColorMask ||
+ NULL == fCompileShader ||
+ NULL == fCopyTexSubImage2D ||
+ NULL == fCreateProgram ||
+ NULL == fCreateShader ||
+ NULL == fCullFace ||
+ NULL == fDeleteBuffers ||
+ NULL == fDeleteProgram ||
+ NULL == fDeleteShader ||
+ NULL == fDeleteTextures ||
+ NULL == fDepthMask ||
+ NULL == fDisable ||
+ NULL == fDisableVertexAttribArray ||
+ NULL == fDrawArrays ||
+ NULL == fDrawElements ||
+ NULL == fEnable ||
+ NULL == fEnableVertexAttribArray ||
+ NULL == fFrontFace ||
+ NULL == fGenBuffers ||
+ NULL == fGenTextures ||
+ NULL == fGetBufferParameteriv ||
+#ifndef SKIA_IGNORE_GPU_MIPMAPS
+ NULL == fGenerateMipmap ||
+#endif
+ NULL == fGetError ||
+ NULL == fGetIntegerv ||
+ NULL == fGetProgramInfoLog ||
+ NULL == fGetProgramiv ||
+ NULL == fGetShaderInfoLog ||
+ NULL == fGetShaderiv ||
+ NULL == fGetString ||
+ NULL == fGetUniformLocation ||
+ NULL == fLinkProgram ||
+ NULL == fPixelStorei ||
+ NULL == fReadPixels ||
+ NULL == fScissor ||
+ NULL == fShaderSource ||
+ NULL == fStencilFunc ||
+ NULL == fStencilMask ||
+ NULL == fStencilOp ||
+ NULL == fTexImage2D ||
+ NULL == fTexParameteri ||
+ NULL == fTexParameteriv ||
+ NULL == fTexSubImage2D ||
+ NULL == fUniform1f ||
+ NULL == fUniform1i ||
+ NULL == fUniform1fv ||
+ NULL == fUniform1iv ||
+ NULL == fUniform2f ||
+ NULL == fUniform2i ||
+ NULL == fUniform2fv ||
+ NULL == fUniform2iv ||
+ NULL == fUniform3f ||
+ NULL == fUniform3i ||
+ NULL == fUniform3fv ||
+ NULL == fUniform3iv ||
+ NULL == fUniform4f ||
+ NULL == fUniform4i ||
+ NULL == fUniform4fv ||
+ NULL == fUniform4iv ||
+ NULL == fUniformMatrix2fv ||
+ NULL == fUniformMatrix3fv ||
+ NULL == fUniformMatrix4fv ||
+ NULL == fUseProgram ||
+ NULL == fVertexAttrib4fv ||
+ NULL == fVertexAttribPointer ||
+ NULL == fViewport ||
+ NULL == fBindFramebuffer ||
+ NULL == fBindRenderbuffer ||
+ NULL == fCheckFramebufferStatus ||
+ NULL == fDeleteFramebuffers ||
+ NULL == fDeleteRenderbuffers ||
+ NULL == fFinish ||
+ NULL == fFlush ||
+ NULL == fFramebufferRenderbuffer ||
+ NULL == fFramebufferTexture2D ||
+ NULL == fGetFramebufferAttachmentParameteriv ||
+ NULL == fGetRenderbufferParameteriv ||
+ NULL == fGenFramebuffers ||
+ NULL == fGenRenderbuffers ||
+ NULL == fRenderbufferStorage) {
+ return false;
+ }
+
+ GrGLVersion glVer = GrGLGetVersion(this);
+
+ // Now check that baseline ES/Desktop fns not covered above are present
+ // and that we have fn pointers for any advertised extensions that we will
+ // try to use.
+
+ // these functions are part of ES2, we assume they are available
+ // On the desktop we assume they are available if the extension
+ // is present or GL version is high enough.
+ if (kES2_GrGLBinding == binding) {
+ if (NULL == fStencilFuncSeparate ||
+ NULL == fStencilMaskSeparate ||
+ NULL == fStencilOpSeparate) {
+ return false;
+ }
+ } else if (kDesktop_GrGLBinding == binding) {
+
+ if (glVer >= GR_GL_VER(2,0)) {
+ if (NULL == fStencilFuncSeparate ||
+ NULL == fStencilMaskSeparate ||
+ NULL == fStencilOpSeparate) {
+ return false;
+ }
+ }
+ if (glVer >= GR_GL_VER(3,0) && NULL == fBindFragDataLocation) {
+ return false;
+ }
+ if (glVer >= GR_GL_VER(2,0) || extensions.has("GL_ARB_draw_buffers")) {
+ if (NULL == fDrawBuffers) {
+ return false;
+ }
+ }
+
+ if (glVer >= GR_GL_VER(1,5) || extensions.has("GL_ARB_occlusion_query")) {
+ if (NULL == fGenQueries ||
+ NULL == fDeleteQueries ||
+ NULL == fBeginQuery ||
+ NULL == fEndQuery ||
+ NULL == fGetQueryiv ||
+ NULL == fGetQueryObjectiv ||
+ NULL == fGetQueryObjectuiv) {
+ return false;
+ }
+ }
+ if (glVer >= GR_GL_VER(3,3) ||
+ extensions.has("GL_ARB_timer_query") ||
+ extensions.has("GL_EXT_timer_query")) {
+ if (NULL == fGetQueryObjecti64v ||
+ NULL == fGetQueryObjectui64v) {
+ return false;
+ }
+ }
+ if (glVer >= GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
+ if (NULL == fQueryCounter) {
+ return false;
+ }
+ }
+ // The below two blocks are checks for functions used with
+ // GL_NV_path_rendering. We're not enforcing that they be non-NULL
+ // because they aren't actually called at this time.
+ if (false &&
+ (NULL == fMatrixMode ||
+ NULL == fLoadIdentity ||
+ NULL == fLoadMatrixf)) {
+ return false;
+ }
+ if (false && extensions.has("GL_NV_path_rendering")) {
+ if (NULL == fPathCommands ||
+ NULL == fPathCoords ||
+ NULL == fPathSubCommands ||
+ NULL == fPathSubCoords ||
+ NULL == fPathString ||
+ NULL == fPathGlyphs ||
+ NULL == fPathGlyphRange ||
+ NULL == fWeightPaths ||
+ NULL == fCopyPath ||
+ NULL == fInterpolatePaths ||
+ NULL == fTransformPath ||
+ NULL == fPathParameteriv ||
+ NULL == fPathParameteri ||
+ NULL == fPathParameterfv ||
+ NULL == fPathParameterf ||
+ NULL == fPathDashArray ||
+ NULL == fGenPaths ||
+ NULL == fDeletePaths ||
+ NULL == fIsPath ||
+ NULL == fPathStencilFunc ||
+ NULL == fPathStencilDepthOffset ||
+ NULL == fStencilFillPath ||
+ NULL == fStencilStrokePath ||
+ NULL == fStencilFillPathInstanced ||
+ NULL == fStencilStrokePathInstanced ||
+ NULL == fPathCoverDepthFunc ||
+ NULL == fPathColorGen ||
+ NULL == fPathTexGen ||
+ NULL == fPathFogGen ||
+ NULL == fCoverFillPath ||
+ NULL == fCoverStrokePath ||
+ NULL == fCoverFillPathInstanced ||
+ NULL == fCoverStrokePathInstanced ||
+ NULL == fGetPathParameteriv ||
+ NULL == fGetPathParameterfv ||
+ NULL == fGetPathCommands ||
+ NULL == fGetPathCoords ||
+ NULL == fGetPathDashArray ||
+ NULL == fGetPathMetrics ||
+ NULL == fGetPathMetricRange ||
+ NULL == fGetPathSpacing ||
+ NULL == fGetPathColorGeniv ||
+ NULL == fGetPathColorGenfv ||
+ NULL == fGetPathTexGeniv ||
+ NULL == fGetPathTexGenfv ||
+ NULL == fIsPointInFillPath ||
+ NULL == fIsPointInStrokePath ||
+ NULL == fGetPathLength ||
+ NULL == fPointAlongPath) {
+ return false;
+ }
+ }
+ }
+
+ // optional function on desktop before 1.3
+ if (kDesktop_GrGLBinding != binding ||
+ (glVer >= GR_GL_VER(1,3)) ||
+ extensions.has("GL_ARB_texture_compression")) {
+ if (NULL == fCompressedTexImage2D) {
+ return false;
+ }
+ }
+
+ // part of desktop GL, but not ES
+ if (kDesktop_GrGLBinding == binding &&
+ (NULL == fLineWidth ||
+ NULL == fGetTexLevelParameteriv ||
+ NULL == fDrawBuffer ||
+ NULL == fReadBuffer)) {
+ return false;
+ }
+
+ // GL_EXT_texture_storage is part of desktop 4.2
+ // There is a desktop ARB extension and an ES+desktop EXT extension
+ if (kDesktop_GrGLBinding == binding) {
+ if (glVer >= GR_GL_VER(4,2) ||
+ extensions.has("GL_ARB_texture_storage") ||
+ extensions.has("GL_EXT_texture_storage")) {
+ if (NULL == fTexStorage2D) {
+ return false;
+ }
+ }
+ } else if (extensions.has("GL_EXT_texture_storage")) {
+ if (NULL == fTexStorage2D) {
+ return false;
+ }
+ }
+
+ if (extensions.has("GL_EXT_discard_framebuffer")) {
+// FIXME: Remove this once Chromium is updated to provide this function
+#if 0
+ if (NULL == fDiscardFramebuffer) {
+ return false;
+ }
+#endif
+ }
+
+ // FBO MSAA
+ if (kDesktop_GrGLBinding == binding) {
+ // GL 3.0 and the ARB extension have multisample + blit
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
+ if (NULL == fRenderbufferStorageMultisample ||
+ NULL == fBlitFramebuffer) {
+ return false;
+ }
+ } else {
+ if (extensions.has("GL_EXT_framebuffer_blit") &&
+ NULL == fBlitFramebuffer) {
+ return false;
+ }
+ if (extensions.has("GL_EXT_framebuffer_multisample") &&
+ NULL == fRenderbufferStorageMultisample) {
+ return false;
+ }
+ }
+ } else {
+ if (extensions.has("GL_CHROMIUM_framebuffer_multisample")) {
+ if (NULL == fRenderbufferStorageMultisample ||
+ NULL == fBlitFramebuffer) {
+ return false;
+ }
+ }
+ if (extensions.has("GL_APPLE_framebuffer_multisample")) {
+ if (NULL == fRenderbufferStorageMultisample ||
+ NULL == fResolveMultisampleFramebuffer) {
+ return false;
+ }
+ }
+ if (extensions.has("GL_IMG_multisampled_render_to_texture") ||
+ extensions.has("GL_EXT_multisampled_render_to_texture")) {
+ if (NULL == fRenderbufferStorageMultisample ||
+ NULL == fFramebufferTexture2DMultisample) {
+ return false;
+ }
+ }
+ }
+
+ // On ES buffer mapping is an extension. On Desktop
+ // buffer mapping was part of original VBO extension
+ // which we require.
+ if (kDesktop_GrGLBinding == binding || extensions.has("GL_OES_mapbuffer")) {
+ if (NULL == fMapBuffer ||
+ NULL == fUnmapBuffer) {
+ return false;
+ }
+ }
+
+ // Dual source blending
+ if (kDesktop_GrGLBinding == binding &&
+ (glVer >= GR_GL_VER(3,3) || extensions.has("GL_ARB_blend_func_extended"))) {
+ if (NULL == fBindFragDataLocationIndexed) {
+ return false;
+ }
+ }
+
+ if (kDesktop_GrGLBinding == binding && glVer >= GR_GL_VER(3, 0)) {
+ if (NULL == fGetStringi) {
+ return false;
+ }
+ }
+
+ if (kDesktop_GrGLBinding == binding) {
+ if (glVer >= GR_GL_VER(3, 0) || extensions.has("GL_ARB_vertex_array_object")) {
+ if (NULL == fBindVertexArray ||
+ NULL == fDeleteVertexArrays ||
+ NULL == fGenVertexArrays) {
+ return false;
+ }
+ }
+ } else {
+ if (extensions.has("GL_OES_vertex_array_object")) {
+ if (NULL == fBindVertexArray ||
+ NULL == fDeleteVertexArrays ||
+ NULL == fGenVertexArrays) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/gpu/gl/GrGLNoOpInterface.cpp b/gpu/gl/GrGLNoOpInterface.cpp
new file mode 100644
index 00000000..db7b8e3c
--- /dev/null
+++ b/gpu/gl/GrGLNoOpInterface.cpp
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLNoOpInterface.h"
+#include "SkString.h"
+#include "SkThread.h"
+
+// the OpenGLES 2.0 spec says this must be >= 128
+static const GrGLint kDefaultMaxVertexUniformVectors = 128;
+
+// the OpenGLES 2.0 spec says this must be >=16
+static const GrGLint kDefaultMaxFragmentUniformVectors = 16;
+
+// the OpenGLES 2.0 spec says this must be >= 8
+static const GrGLint kDefaultMaxVertexAttribs = 8;
+
+// the OpenGLES 2.0 spec says this must be >= 8
+static const GrGLint kDefaultMaxVaryingVectors = 8;
+
+static const char* kExtensions[] = {
+ "GL_ARB_framebuffer_object",
+ "GL_ARB_blend_func_extended",
+ "GL_ARB_timer_query",
+ "GL_ARB_draw_buffers",
+ "GL_ARB_occlusion_query",
+ "GL_EXT_blend_color",
+ "GL_EXT_stencil_wrap"
+};
+
+namespace {
+const GrGLubyte* combined_extensions_string() {
+ static SkString gExtString;
+ static SkMutex gMutex;
+ gMutex.acquire();
+ if (0 == gExtString.size()) {
+ for (size_t i = 0; i < GR_ARRAY_COUNT(kExtensions) - 1; ++i) {
+ gExtString.append(kExtensions[i]);
+ gExtString.append(" ");
+ }
+ gExtString.append(kExtensions[GR_ARRAY_COUNT(kExtensions) - 1]);
+ }
+ gMutex.release();
+ return (const GrGLubyte*) gExtString.c_str();
+}
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlendColor(GrGLclampf red,
+ GrGLclampf green,
+ GrGLclampf blue,
+ GrGLclampf alpha) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBindFragDataLocation(GrGLuint program,
+ GrGLuint colorNumber,
+ const GrGLchar* name) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlendFunc(GrGLenum sfactor,
+ GrGLenum dfactor) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBufferSubData(GrGLenum target,
+ GrGLintptr offset,
+ GrGLsizeiptr size,
+ const GrGLvoid* data) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClear(GrGLbitfield mask) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClearColor(GrGLclampf red,
+ GrGLclampf green,
+ GrGLclampf blue,
+ GrGLclampf alpha) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClearStencil(GrGLint s) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLColorMask(GrGLboolean red,
+ GrGLboolean green,
+ GrGLboolean blue,
+ GrGLboolean alpha) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCompileShader(GrGLuint shader) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCompressedTexImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLint border,
+ GrGLsizei imageSize,
+ const GrGLvoid* data) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCopyTexSubImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint xoffset,
+ GrGLint yoffset,
+ GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCullFace(GrGLenum mode) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDepthMask(GrGLboolean flag) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDisable(GrGLenum cap) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDisableVertexAttribArray(GrGLuint index) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawArrays(GrGLenum mode,
+ GrGLint first,
+ GrGLsizei count) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawBuffer(GrGLenum mode) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawBuffers(GrGLsizei n,
+ const GrGLenum* bufs) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawElements(GrGLenum mode,
+ GrGLsizei count,
+ GrGLenum type,
+ const GrGLvoid* indices) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEnable(GrGLenum cap) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEnableVertexAttribArray(GrGLuint index) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEndQuery(GrGLenum target) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFinish() {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFlush() {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFrontFace(GrGLenum mode) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLLineWidth(GrGLfloat width) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLLinkProgram(GrGLuint program) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLQueryCounter(GrGLuint id, GrGLenum target) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLReadBuffer(GrGLenum src) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLScissor(GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLShaderSource(GrGLuint shader,
+ GrGLsizei count,
+#if GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE
+ const char* const * str,
+#else
+ const char** str,
+#endif
+ const GrGLint* length) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilFuncSeparate(GrGLenum face,
+ GrGLenum func,
+ GrGLint ref,
+ GrGLuint mask) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilMask(GrGLuint mask) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilMaskSeparate(GrGLenum face, GrGLuint mask) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilOp(GrGLenum fail, GrGLenum zfail, GrGLenum zpass) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilOpSeparate(GrGLenum face,
+ GrGLenum fail,
+ GrGLenum zfail,
+ GrGLenum zpass) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint internalformat,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLint border,
+ GrGLenum format,
+ GrGLenum type,
+ const GrGLvoid* pixels) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexParameteri(GrGLenum target,
+ GrGLenum pname,
+ GrGLint param) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexParameteriv(GrGLenum target,
+ GrGLenum pname,
+ const GrGLint* params) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexStorage2D(GrGLenum target,
+ GrGLsizei levels,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDiscardFramebuffer(GrGLenum target,
+ GrGLsizei numAttachments,
+ const GrGLenum* attachments) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexSubImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint xoffset,
+ GrGLint yoffset,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLenum format,
+ GrGLenum type,
+ const GrGLvoid* pixels) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1f(GrGLint location, GrGLfloat v0) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1i(GrGLint location, GrGLint v0) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2f(GrGLint location, GrGLfloat v0, GrGLfloat v1) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2i(GrGLint location, GrGLint v0, GrGLint v1) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3f(GrGLint location,
+ GrGLfloat v0,
+ GrGLfloat v1,
+ GrGLfloat v2) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3i(GrGLint location,
+ GrGLint v0,
+ GrGLint v1,
+ GrGLint v2) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4f(GrGLint location,
+ GrGLfloat v0,
+ GrGLfloat v1,
+ GrGLfloat v2,
+ GrGLfloat v3) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4i(GrGLint location,
+ GrGLint v0,
+ GrGLint v1,
+ GrGLint v2,
+ GrGLint v3) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix2fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix3fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix4fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value) {
+}
+
+ GrGLvoid GR_GL_FUNCTION_TYPE noOpGLVertexAttrib4fv(GrGLuint indx, const GrGLfloat* values) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLVertexAttribPointer(GrGLuint indx,
+ GrGLint size,
+ GrGLenum type,
+ GrGLboolean normalized,
+ GrGLsizei stride,
+ const GrGLvoid* ptr) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLViewport(GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+ GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetFramebufferAttachmentParameteriv(GrGLenum target,
+ GrGLenum attachment,
+ GrGLenum pname,
+ GrGLint* params) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetRenderbufferParameteriv(GrGLenum target,
+ GrGLenum pname,
+ GrGLint* params) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLRenderbufferStorage(GrGLenum target,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLRenderbufferStorageMultisample(GrGLenum target,
+ GrGLsizei samples,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlitFramebuffer(GrGLint srcX0,
+ GrGLint srcY0,
+ GrGLint srcX1,
+ GrGLint srcY1,
+ GrGLint dstX0,
+ GrGLint dstY0,
+ GrGLint dstX1,
+ GrGLint dstY1,
+ GrGLbitfield mask,
+ GrGLenum filter) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLResolveMultisampleFramebuffer() {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBindFragDataLocationIndexed(GrGLuint program,
+ GrGLuint colorNumber,
+ GrGLuint index,
+ const GrGLchar * name) {
+}
+
+GrGLenum GR_GL_FUNCTION_TYPE noOpGLCheckFramebufferStatus(GrGLenum target) {
+
+ GrAlwaysAssert(GR_GL_FRAMEBUFFER == target);
+
+ return GR_GL_FRAMEBUFFER_COMPLETE;
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGenIds(GrGLsizei n, GrGLuint* ids) {
+ static int gCurrID = 1;
+ for (int i = 0; i < n; ++i) {
+ ids[i] = ++gCurrID;
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDeleteIds(GrGLsizei n, const GrGLuint* ids) {
+}
+
+GrGLenum GR_GL_FUNCTION_TYPE noOpGLGetError() {
+ return GR_GL_NO_ERROR;
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetIntegerv(GrGLenum pname, GrGLint* params) {
+ // TODO: remove from Ganesh the #defines for gets we don't use.
+ // We would like to minimize gets overall due to performance issues
+ switch (pname) {
+ case GR_GL_CONTEXT_PROFILE_MASK:
+ *params = GR_GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
+ break;
+ case GR_GL_STENCIL_BITS:
+ *params = 8;
+ break;
+ case GR_GL_SAMPLES:
+ *params = 1;
+ break;
+ case GR_GL_FRAMEBUFFER_BINDING:
+ *params = 0;
+ break;
+ case GR_GL_VIEWPORT:
+ params[0] = 0;
+ params[1] = 0;
+ params[2] = 800;
+ params[3] = 600;
+ break;
+ case GR_GL_MAX_TEXTURE_IMAGE_UNITS:
+ *params = 8;
+ break;
+ case GR_GL_MAX_VERTEX_UNIFORM_VECTORS:
+ *params = kDefaultMaxVertexUniformVectors;
+ break;
+ case GR_GL_MAX_FRAGMENT_UNIFORM_VECTORS:
+ *params = kDefaultMaxFragmentUniformVectors;
+ break;
+ case GR_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS:
+ *params = 16 * 4;
+ break;
+ case GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS:
+ *params = 0;
+ break;
+ case GR_GL_COMPRESSED_TEXTURE_FORMATS:
+ break;
+ case GR_GL_MAX_TEXTURE_SIZE:
+ *params = 8192;
+ break;
+ case GR_GL_MAX_RENDERBUFFER_SIZE:
+ *params = 8192;
+ break;
+ case GR_GL_MAX_SAMPLES:
+ *params = 32;
+ break;
+ case GR_GL_MAX_VERTEX_ATTRIBS:
+ *params = kDefaultMaxVertexAttribs;
+ break;
+ case GR_GL_MAX_VARYING_VECTORS:
+ *params = kDefaultMaxVaryingVectors;
+ break;
+ case GR_GL_NUM_EXTENSIONS:
+ *params = GR_ARRAY_COUNT(kExtensions);
+ break;
+ default:
+ GrCrash("Unexpected pname to GetIntegerv");
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetInfoLog(GrGLuint program,
+ GrGLsizei bufsize,
+ GrGLsizei* length,
+ char* infolog) {
+ if (length) {
+ *length = 0;
+ }
+ if (bufsize > 0) {
+ *infolog = 0;
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetShaderOrProgramiv(GrGLuint program,
+ GrGLenum pname,
+ GrGLint* params) {
+ switch (pname) {
+ case GR_GL_LINK_STATUS: // fallthru
+ case GR_GL_COMPILE_STATUS:
+ *params = GR_GL_TRUE;
+ break;
+ case GR_GL_INFO_LOG_LENGTH:
+ *params = 0;
+ break;
+ // we don't expect any other pnames
+ default:
+ GrCrash("Unexpected pname to GetProgramiv");
+ break;
+ }
+}
+
+namespace {
+template <typename T>
+void query_result(GrGLenum GLtarget, GrGLenum pname, T *params) {
+ switch (pname) {
+ case GR_GL_QUERY_RESULT_AVAILABLE:
+ *params = GR_GL_TRUE;
+ break;
+ case GR_GL_QUERY_RESULT:
+ *params = 0;
+ break;
+ default:
+ GrCrash("Unexpected pname passed to GetQueryObject.");
+ break;
+ }
+}
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryiv(GrGLenum GLtarget,
+ GrGLenum pname,
+ GrGLint *params) {
+ switch (pname) {
+ case GR_GL_CURRENT_QUERY:
+ *params = 0;
+ break;
+ case GR_GL_QUERY_COUNTER_BITS:
+ *params = 32;
+ break;
+ default:
+ GrCrash("Unexpected pname passed GetQueryiv.");
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjecti64v(GrGLuint id,
+ GrGLenum pname,
+ GrGLint64 *params) {
+ query_result(id, pname, params);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectiv(GrGLuint id,
+ GrGLenum pname,
+ GrGLint *params) {
+ query_result(id, pname, params);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectui64v(GrGLuint id,
+ GrGLenum pname,
+ GrGLuint64 *params) {
+ query_result(id, pname, params);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectuiv(GrGLuint id,
+ GrGLenum pname,
+ GrGLuint *params) {
+ query_result(id, pname, params);
+}
+
+const GrGLubyte* GR_GL_FUNCTION_TYPE noOpGLGetString(GrGLenum name) {
+ switch (name) {
+ case GR_GL_EXTENSIONS:
+ return combined_extensions_string();
+ case GR_GL_VERSION:
+ return (const GrGLubyte*)"4.0 Debug GL";
+ case GR_GL_SHADING_LANGUAGE_VERSION:
+ return (const GrGLubyte*)"4.20.8 Debug GLSL";
+ case GR_GL_VENDOR:
+ return (const GrGLubyte*)"Debug Vendor";
+ case GR_GL_RENDERER:
+ return (const GrGLubyte*)"The Debug (Non-)Renderer";
+ default:
+ GrCrash("Unexpected name passed to GetString");
+ return NULL;
+ }
+}
+
+const GrGLubyte* GR_GL_FUNCTION_TYPE noOpGLGetStringi(GrGLenum name, GrGLuint i) {
+ switch (name) {
+ case GR_GL_EXTENSIONS:
+ if (static_cast<size_t>(i) <= GR_ARRAY_COUNT(kExtensions)) {
+ return (const GrGLubyte*) kExtensions[i];
+ } else {
+ return NULL;
+ }
+ default:
+ GrCrash("Unexpected name passed to GetStringi");
+ return NULL;
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetTexLevelParameteriv(GrGLenum target,
+ GrGLint level,
+ GrGLenum pname,
+ GrGLint* params) {
+ // we used to use this to query stuff about externally created textures,
+ // now we just require clients to tell us everything about the texture.
+ GrCrash("Should never query texture parameters.");
+}
+
+GrGLint GR_GL_FUNCTION_TYPE noOpGLGetUniformLocation(GrGLuint program, const char* name) {
+ static int gUniLocation = 0;
+ return ++gUniLocation;
+}
diff --git a/gpu/gl/GrGLNoOpInterface.h b/gpu/gl/GrGLNoOpInterface.h
new file mode 100644
index 00000000..0451e9cc
--- /dev/null
+++ b/gpu/gl/GrGLNoOpInterface.h
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLNoOpInterface_DEFINED
+#define GrGLNoOpInterface_DEFINED
+
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLFunctions.h"
+
+// These are constants/functions that are common to the Null and Debug GL interface implementations.
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlendColor(GrGLclampf red,
+ GrGLclampf green,
+ GrGLclampf blue,
+ GrGLclampf alpha);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBindFragDataLocation(GrGLuint program,
+ GrGLuint colorNumber,
+ const GrGLchar* name);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlendFunc(GrGLenum sfactor,
+ GrGLenum dfactor);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBufferSubData(GrGLenum target,
+ GrGLintptr offset,
+ GrGLsizeiptr size,
+ const GrGLvoid* data);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClear(GrGLbitfield mask);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClearColor(GrGLclampf red,
+ GrGLclampf green,
+ GrGLclampf blue,
+ GrGLclampf alpha);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLClearStencil(GrGLint s);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLColorMask(GrGLboolean red,
+ GrGLboolean green,
+ GrGLboolean blue,
+ GrGLboolean alpha);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCompileShader(GrGLuint shader);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCompressedTexImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLint border,
+ GrGLsizei imageSize,
+ const GrGLvoid* data);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCopyTexSubImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint xoffset,
+ GrGLint yoffset,
+ GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLCullFace(GrGLenum mode);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDepthMask(GrGLboolean flag);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDisable(GrGLenum cap);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDisableVertexAttribArray(GrGLuint index);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawArrays(GrGLenum mode, GrGLint first, GrGLsizei count);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawBuffer(GrGLenum mode);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawBuffers(GrGLsizei n,
+ const GrGLenum* bufs);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDrawElements(GrGLenum mode,
+ GrGLsizei count,
+ GrGLenum type,
+ const GrGLvoid* indices);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEnable(GrGLenum cap);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEnableVertexAttribArray(GrGLuint index);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLEndQuery(GrGLenum target);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFinish();
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFlush();
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLFrontFace(GrGLenum mode);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLLineWidth(GrGLfloat width);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLLinkProgram(GrGLuint program);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLQueryCounter(GrGLuint id,
+ GrGLenum target);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLReadBuffer(GrGLenum src);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLScissor(GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLShaderSource(GrGLuint shader,
+ GrGLsizei count,
+#if GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE
+ const char* const * str,
+#else
+ const char** str,
+#endif
+ const GrGLint* length);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilFuncSeparate(GrGLenum face,
+ GrGLenum func,
+ GrGLint ref,
+ GrGLuint mask);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilMask(GrGLuint mask);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilMaskSeparate(GrGLenum face, GrGLuint mask);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilOp(GrGLenum fail, GrGLenum zfail, GrGLenum zpass);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLStencilOpSeparate(GrGLenum face,
+ GrGLenum fail,
+ GrGLenum zfail,
+ GrGLenum zpass);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint internalformat,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLint border,
+ GrGLenum format,
+ GrGLenum type,
+ const GrGLvoid* pixels);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexParameteri(GrGLenum target, GrGLenum pname, GrGLint param);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexParameteriv(GrGLenum target,
+ GrGLenum pname,
+ const GrGLint* params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexStorage2D(GrGLenum target,
+ GrGLsizei levels,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDiscardFramebuffer(GrGLenum target,
+ GrGLsizei numAttachments,
+ const GrGLenum* attachments);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLTexSubImage2D(GrGLenum target,
+ GrGLint level,
+ GrGLint xoffset,
+ GrGLint yoffset,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLenum format,
+ GrGLenum type,
+ const GrGLvoid* pixels);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1f(GrGLint location, GrGLfloat v0);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1i(GrGLint location, GrGLint v0);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform1iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2f(GrGLint location,
+ GrGLfloat v0,
+ GrGLfloat v1);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2i(GrGLint location, GrGLint v0, GrGLint v1);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform2iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3f(GrGLint location,
+ GrGLfloat v0,
+ GrGLfloat v1,
+ GrGLfloat v2);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3i(GrGLint location,
+ GrGLint v0,
+ GrGLint v1,
+ GrGLint v2);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform3iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4f(GrGLint location,
+ GrGLfloat v0,
+ GrGLfloat v1,
+ GrGLfloat v2,
+ GrGLfloat v3);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4i(GrGLint location,
+ GrGLint v0,
+ GrGLint v1,
+ GrGLint v2,
+ GrGLint v3);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4fv(GrGLint location,
+ GrGLsizei count,
+ const GrGLfloat* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniform4iv(GrGLint location,
+ GrGLsizei count,
+ const GrGLint* v);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix2fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix3fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLUniformMatrix4fv(GrGLint location,
+ GrGLsizei count,
+ GrGLboolean transpose,
+ const GrGLfloat* value);
+
+ GrGLvoid GR_GL_FUNCTION_TYPE noOpGLVertexAttrib4fv(GrGLuint indx, const GrGLfloat* values);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLVertexAttribPointer(GrGLuint indx,
+ GrGLint size,
+ GrGLenum type,
+ GrGLboolean normalized,
+ GrGLsizei stride,
+ const GrGLvoid* ptr);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLViewport(GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height);
+
+ GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetFramebufferAttachmentParameteriv(GrGLenum target,
+ GrGLenum attachment,
+ GrGLenum pname,
+ GrGLint* params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetRenderbufferParameteriv(GrGLenum target,
+ GrGLenum pname,
+ GrGLint* params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLRenderbufferStorage(GrGLenum target,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLRenderbufferStorageMultisample(GrGLenum target,
+ GrGLsizei samples,
+ GrGLenum internalformat,
+ GrGLsizei width,
+ GrGLsizei height);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBlitFramebuffer(GrGLint srcX0,
+ GrGLint srcY0,
+ GrGLint srcX1,
+ GrGLint srcY1,
+ GrGLint dstX0,
+ GrGLint dstY0,
+ GrGLint dstX1,
+ GrGLint dstY1,
+ GrGLbitfield mask,
+ GrGLenum filter);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLResolveMultisampleFramebuffer();
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLBindFragDataLocationIndexed(GrGLuint program,
+ GrGLuint colorNumber,
+ GrGLuint index,
+ const GrGLchar * name);
+
+GrGLenum GR_GL_FUNCTION_TYPE noOpGLCheckFramebufferStatus(GrGLenum target);
+
+// this function can be used for all glGen*(GLsize i, GLuint*) functions
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGenIds(GrGLsizei n, GrGLuint* ids);
+
+// this function function can be used for all glDelete*(GLsize i, const GLuint*)
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLDeleteIds(GrGLsizei n, const GrGLuint* ids);
+
+GrGLenum GR_GL_FUNCTION_TYPE noOpGLGetError();
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetIntegerv(GrGLenum pname, GrGLint* params);
+
+// can be used for both the program and shader info logs
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetInfoLog(GrGLuint program,
+ GrGLsizei bufsize,
+ GrGLsizei* length,
+ char* infolog);
+
+// can be used for both the program and shader params
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetShaderOrProgramiv(GrGLuint program,
+ GrGLenum pname,
+ GrGLint* params);
+
+// Queries on bogus GLs just don't do anything at all. We could potentially make the timers work.
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryiv(GrGLenum GLtarget,
+ GrGLenum pname,
+ GrGLint *params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjecti64v(GrGLuint id,
+ GrGLenum pname,
+ GrGLint64 *params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectiv(GrGLuint id, GrGLenum pname, GrGLint *params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectui64v(GrGLuint id,
+ GrGLenum pname,
+ GrGLuint64 *params);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetQueryObjectuiv(GrGLuint id,
+ GrGLenum pname,
+ GrGLuint *params);
+
+const GrGLubyte* GR_GL_FUNCTION_TYPE noOpGLGetString(GrGLenum name);
+
+const GrGLubyte* GR_GL_FUNCTION_TYPE noOpGLGetStringi(GrGLenum name, GrGLuint i);
+
+GrGLvoid GR_GL_FUNCTION_TYPE noOpGLGetTexLevelParameteriv(GrGLenum target,
+ GrGLint level,
+ GrGLenum pname,
+ GrGLint* params);
+
+GrGLint GR_GL_FUNCTION_TYPE noOpGLGetUniformLocation(GrGLuint program, const char* name);
+
+#endif
diff --git a/gpu/gl/GrGLPath.cpp b/gpu/gl/GrGLPath.cpp
new file mode 100644
index 00000000..d46fa03b
--- /dev/null
+++ b/gpu/gl/GrGLPath.cpp
@@ -0,0 +1,112 @@
+
+/*
+ * 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 "GrGLPath.h"
+#include "GrGpuGL.h"
+
+#define GPUGL static_cast<GrGpuGL*>(this->getGpu())
+
+#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X)
+#define GL_CALL_RET(R, X) GR_GL_CALL_RET(GPUGL->glInterface(), R, X)
+
+namespace {
+inline GrGLubyte verb_to_gl_path_cmd(const SkPath::Verb verb) {
+ static const GrGLubyte gTable[] = {
+ GR_GL_MOVE_TO,
+ GR_GL_LINE_TO,
+ GR_GL_QUADRATIC_CURVE_TO,
+ 0xFF, // conic
+ GR_GL_CUBIC_CURVE_TO,
+ GR_GL_CLOSE_PATH,
+ };
+ GR_STATIC_ASSERT(0 == SkPath::kMove_Verb);
+ GR_STATIC_ASSERT(1 == SkPath::kLine_Verb);
+ GR_STATIC_ASSERT(2 == SkPath::kQuad_Verb);
+ GR_STATIC_ASSERT(4 == SkPath::kCubic_Verb);
+ GR_STATIC_ASSERT(5 == SkPath::kClose_Verb);
+
+ GrAssert(verb >= 0 && (size_t)verb < GR_ARRAY_COUNT(gTable));
+ return gTable[verb];
+}
+
+#ifdef SK_DEBUG
+inline int num_pts(const SkPath::Verb verb) {
+ static const int gTable[] = {
+ 1, // move
+ 1, // line
+ 2, // quad
+ 2, // conic
+ 3, // cubic
+ 0, // close
+ };
+ GR_STATIC_ASSERT(0 == SkPath::kMove_Verb);
+ GR_STATIC_ASSERT(1 == SkPath::kLine_Verb);
+ GR_STATIC_ASSERT(2 == SkPath::kQuad_Verb);
+ GR_STATIC_ASSERT(4 == SkPath::kCubic_Verb);
+ GR_STATIC_ASSERT(5 == SkPath::kClose_Verb);
+
+ GrAssert(verb >= 0 && (size_t)verb < GR_ARRAY_COUNT(gTable));
+ return gTable[verb];
+}
+#endif
+}
+
+static const bool kIsWrapped = false; // The constructor creates the GL path object.
+
+GrGLPath::GrGLPath(GrGpuGL* gpu, const SkPath& path) : INHERITED(gpu, kIsWrapped) {
+#ifndef SK_SCALAR_IS_FLOAT
+ GrCrash("Assumes scalar is float.");
+#endif
+ SkASSERT(!path.isEmpty());
+
+ GL_CALL_RET(fPathID, GenPaths(1));
+
+ SkSTArray<16, GrGLubyte, true> pathCommands;
+ SkSTArray<16, SkPoint, true> pathPoints;
+
+ int verbCnt = path.countVerbs();
+ int pointCnt = path.countPoints();
+ pathCommands.resize_back(verbCnt);
+ pathPoints.resize_back(pointCnt);
+
+ // TODO: Direct access to path points since we could pass them on directly.
+ path.getPoints(&pathPoints[0], pointCnt);
+ path.getVerbs(&pathCommands[0], verbCnt);
+
+ GR_DEBUGCODE(int numPts = 0);
+ for (int i = 0; i < verbCnt; ++i) {
+ SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
+ pathCommands[i] = verb_to_gl_path_cmd(v);
+ GR_DEBUGCODE(numPts += num_pts(v));
+ }
+ GrAssert(pathPoints.count() == numPts);
+
+ GL_CALL(PathCommands(fPathID,
+ verbCnt, &pathCommands[0],
+ 2 * pointCnt, GR_GL_FLOAT, &pathPoints[0]));
+ fBounds = path.getBounds();
+}
+
+GrGLPath::~GrGLPath() {
+ this->release();
+}
+
+void GrGLPath::onRelease() {
+ if (0 != fPathID && !this->isWrapped()) {
+ GL_CALL(DeletePaths(fPathID, 1));
+ fPathID = 0;
+ }
+
+ INHERITED::onRelease();
+}
+
+void GrGLPath::onAbandon() {
+ fPathID = 0;
+
+ INHERITED::onAbandon();
+}
diff --git a/gpu/gl/GrGLPath.h b/gpu/gl/GrGLPath.h
new file mode 100644
index 00000000..dfe34054
--- /dev/null
+++ b/gpu/gl/GrGLPath.h
@@ -0,0 +1,43 @@
+
+/*
+ * 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 GrGLPath_DEFINED
+#define GrGLPath_DEFINED
+
+#include "../GrPath.h"
+#include "gl/GrGLFunctions.h"
+
+class GrGpuGL;
+class SkPath;
+
+/**
+ * Currently this represents a path built using GL_NV_path_rendering. If we
+ * support other GL path extensions then this would have to have a type enum
+ * and/or be subclassed.
+ */
+
+class GrGLPath : public GrPath {
+public:
+ GrGLPath(GrGpuGL* gpu, const SkPath& path);
+ virtual ~GrGLPath();
+ GrGLuint pathID() const { return fPathID; }
+ // TODO: Figure out how to get an approximate size of the path in Gpu
+ // memory.
+ virtual size_t sizeInBytes() const SK_OVERRIDE { return 100; }
+
+protected:
+ virtual void onRelease() SK_OVERRIDE;
+ virtual void onAbandon() SK_OVERRIDE;
+
+private:
+ GrGLuint fPathID;
+
+ typedef GrPath INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLProgram.cpp b/gpu/gl/GrGLProgram.cpp
new file mode 100644
index 00000000..8a24d60c
--- /dev/null
+++ b/gpu/gl/GrGLProgram.cpp
@@ -0,0 +1,1010 @@
+/*
+ * 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 "GrGLProgram.h"
+
+#include "GrAllocator.h"
+#include "GrEffect.h"
+#include "GrDrawEffect.h"
+#include "GrGLEffect.h"
+#include "GrGpuGL.h"
+#include "GrGLShaderVar.h"
+#include "GrGLSL.h"
+#include "SkTrace.h"
+#include "SkXfermode.h"
+
+#include "SkRTConf.h"
+
+SK_DEFINE_INST_COUNT(GrGLProgram)
+
+#define GL_CALL(X) GR_GL_CALL(fContext.interface(), X)
+#define GL_CALL_RET(R, X) GR_GL_CALL_RET(fContext.interface(), R, X)
+
+SK_CONF_DECLARE(bool, c_PrintShaders, "gpu.printShaders", false,
+ "Print the source code for all shaders generated.");
+
+#define COL_ATTR_NAME "aColor"
+#define COV_ATTR_NAME "aCoverage"
+#define EDGE_ATTR_NAME "aEdge"
+
+namespace {
+inline const char* declared_color_output_name() { return "fsColorOut"; }
+inline const char* dual_source_output_name() { return "dualSourceOut"; }
+}
+
+GrGLProgram* GrGLProgram::Create(const GrGLContext& gl,
+ const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]) {
+ GrGLProgram* program = SkNEW_ARGS(GrGLProgram, (gl, desc, colorStages, coverageStages));
+ if (!program->succeeded()) {
+ delete program;
+ program = NULL;
+ }
+ return program;
+}
+
+GrGLProgram::GrGLProgram(const GrGLContext& gl,
+ const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[])
+: fContext(gl)
+, fUniformManager(gl) {
+ fDesc = desc;
+ fVShaderID = 0;
+ fGShaderID = 0;
+ fFShaderID = 0;
+ fProgramID = 0;
+
+ fDstCopyTexUnit = -1;
+
+ fColor = GrColor_ILLEGAL;
+ fColorFilterColor = GrColor_ILLEGAL;
+
+ fColorEffects.reset(desc.numColorEffects());
+ fCoverageEffects.reset(desc.numCoverageEffects());
+
+ this->genProgram(colorStages, coverageStages);
+}
+
+GrGLProgram::~GrGLProgram() {
+ if (fVShaderID) {
+ GL_CALL(DeleteShader(fVShaderID));
+ }
+ if (fGShaderID) {
+ GL_CALL(DeleteShader(fGShaderID));
+ }
+ if (fFShaderID) {
+ GL_CALL(DeleteShader(fFShaderID));
+ }
+ if (fProgramID) {
+ GL_CALL(DeleteProgram(fProgramID));
+ }
+}
+
+void GrGLProgram::abandon() {
+ fVShaderID = 0;
+ fGShaderID = 0;
+ fFShaderID = 0;
+ fProgramID = 0;
+}
+
+void GrGLProgram::overrideBlend(GrBlendCoeff* srcCoeff,
+ GrBlendCoeff* dstCoeff) const {
+ switch (fDesc.getHeader().fCoverageOutput) {
+ case GrGLProgramDesc::kModulate_CoverageOutput:
+ break;
+ // The prog will write a coverage value to the secondary
+ // output and the dst is blended by one minus that value.
+ case GrGLProgramDesc::kSecondaryCoverage_CoverageOutput:
+ case GrGLProgramDesc::kSecondaryCoverageISA_CoverageOutput:
+ case GrGLProgramDesc::kSecondaryCoverageISC_CoverageOutput:
+ *dstCoeff = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
+ break;
+ case GrGLProgramDesc::kCombineWithDst_CoverageOutput:
+ // We should only have set this if the blend was specified as (1, 0)
+ GrAssert(kOne_GrBlendCoeff == *srcCoeff && kZero_GrBlendCoeff == *dstCoeff);
+ break;
+ default:
+ GrCrash("Unexpected coverage output");
+ break;
+ }
+}
+
+namespace {
+// given two blend coefficients determine whether the src
+// and/or dst computation can be omitted.
+inline void need_blend_inputs(SkXfermode::Coeff srcCoeff,
+ SkXfermode::Coeff dstCoeff,
+ bool* needSrcValue,
+ bool* needDstValue) {
+ if (SkXfermode::kZero_Coeff == srcCoeff) {
+ switch (dstCoeff) {
+ // these all read the src
+ case SkXfermode::kSC_Coeff:
+ case SkXfermode::kISC_Coeff:
+ case SkXfermode::kSA_Coeff:
+ case SkXfermode::kISA_Coeff:
+ *needSrcValue = true;
+ break;
+ default:
+ *needSrcValue = false;
+ break;
+ }
+ } else {
+ *needSrcValue = true;
+ }
+ if (SkXfermode::kZero_Coeff == dstCoeff) {
+ switch (srcCoeff) {
+ // these all read the dst
+ case SkXfermode::kDC_Coeff:
+ case SkXfermode::kIDC_Coeff:
+ case SkXfermode::kDA_Coeff:
+ case SkXfermode::kIDA_Coeff:
+ *needDstValue = true;
+ break;
+ default:
+ *needDstValue = false;
+ break;
+ }
+ } else {
+ *needDstValue = true;
+ }
+}
+
+/**
+ * Create a blend_coeff * value string to be used in shader code. Sets empty
+ * string if result is trivially zero.
+ */
+inline void blend_term_string(SkString* str, SkXfermode::Coeff coeff,
+ const char* src, const char* dst,
+ const char* value) {
+ switch (coeff) {
+ case SkXfermode::kZero_Coeff: /** 0 */
+ *str = "";
+ break;
+ case SkXfermode::kOne_Coeff: /** 1 */
+ *str = value;
+ break;
+ case SkXfermode::kSC_Coeff:
+ str->printf("(%s * %s)", src, value);
+ break;
+ case SkXfermode::kISC_Coeff:
+ str->printf("((%s - %s) * %s)", GrGLSLOnesVecf(4), src, value);
+ break;
+ case SkXfermode::kDC_Coeff:
+ str->printf("(%s * %s)", dst, value);
+ break;
+ case SkXfermode::kIDC_Coeff:
+ str->printf("((%s - %s) * %s)", GrGLSLOnesVecf(4), dst, value);
+ break;
+ case SkXfermode::kSA_Coeff: /** src alpha */
+ str->printf("(%s.a * %s)", src, value);
+ break;
+ case SkXfermode::kISA_Coeff: /** inverse src alpha (i.e. 1 - sa) */
+ str->printf("((1.0 - %s.a) * %s)", src, value);
+ break;
+ case SkXfermode::kDA_Coeff: /** dst alpha */
+ str->printf("(%s.a * %s)", dst, value);
+ break;
+ case SkXfermode::kIDA_Coeff: /** inverse dst alpha (i.e. 1 - da) */
+ str->printf("((1.0 - %s.a) * %s)", dst, value);
+ break;
+ default:
+ GrCrash("Unexpected xfer coeff.");
+ break;
+ }
+}
+/**
+ * Adds a line to the fragment shader code which modifies the color by
+ * the specified color filter.
+ */
+void add_color_filter(GrGLShaderBuilder* builder,
+ const char * outputVar,
+ SkXfermode::Coeff uniformCoeff,
+ SkXfermode::Coeff colorCoeff,
+ const char* filterColor,
+ const char* inColor) {
+ SkString colorStr, constStr;
+ blend_term_string(&colorStr, colorCoeff, filterColor, inColor, inColor);
+ blend_term_string(&constStr, uniformCoeff, filterColor, inColor, filterColor);
+
+ SkString sum;
+ GrGLSLAddf<4>(&sum, colorStr.c_str(), constStr.c_str());
+ builder->fsCodeAppendf("\t%s = %s;\n", outputVar, sum.c_str());
+}
+}
+
+GrSLConstantVec GrGLProgram::genInputColor(GrGLShaderBuilder* builder, SkString* inColor) {
+ switch (fDesc.getHeader().fColorInput) {
+ case GrGLProgramDesc::kAttribute_ColorInput: {
+ builder->addAttribute(kVec4f_GrSLType, COL_ATTR_NAME);
+ const char *vsName, *fsName;
+ builder->addVarying(kVec4f_GrSLType, "Color", &vsName, &fsName);
+ builder->vsCodeAppendf("\t%s = " COL_ATTR_NAME ";\n", vsName);
+ *inColor = fsName;
+ return kNone_GrSLConstantVec;
+ }
+ case GrGLProgramDesc::kUniform_ColorInput: {
+ const char* name;
+ fUniformHandles.fColorUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "Color", &name);
+ *inColor = name;
+ return kNone_GrSLConstantVec;
+ }
+ case GrGLProgramDesc::kTransBlack_ColorInput:
+ inColor->reset();
+ return kZeros_GrSLConstantVec;
+ case GrGLProgramDesc::kSolidWhite_ColorInput:
+ inColor->reset();
+ return kOnes_GrSLConstantVec;
+ default:
+ GrCrash("Unknown color type.");
+ return kNone_GrSLConstantVec;
+ }
+}
+
+GrSLConstantVec GrGLProgram::genInputCoverage(GrGLShaderBuilder* builder, SkString* inCoverage) {
+ switch (fDesc.getHeader().fCoverageInput) {
+ case GrGLProgramDesc::kAttribute_ColorInput: {
+ builder->addAttribute(kVec4f_GrSLType, COV_ATTR_NAME);
+ const char *vsName, *fsName;
+ builder->addVarying(kVec4f_GrSLType, "Coverage", &vsName, &fsName);
+ builder->vsCodeAppendf("\t%s = " COV_ATTR_NAME ";\n", vsName);
+ *inCoverage = fsName;
+ return kNone_GrSLConstantVec;
+ }
+ case GrGLProgramDesc::kUniform_ColorInput: {
+ const char* name;
+ fUniformHandles.fCoverageUni =
+ builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "Coverage", &name);
+ *inCoverage = name;
+ return kNone_GrSLConstantVec;
+ }
+ case GrGLProgramDesc::kTransBlack_ColorInput:
+ inCoverage->reset();
+ return kZeros_GrSLConstantVec;
+ case GrGLProgramDesc::kSolidWhite_ColorInput:
+ inCoverage->reset();
+ return kOnes_GrSLConstantVec;
+ default:
+ GrCrash("Unknown color type.");
+ return kNone_GrSLConstantVec;
+ }
+}
+
+void GrGLProgram::genGeometryShader(GrGLShaderBuilder* builder) const {
+#if GR_GL_EXPERIMENTAL_GS
+ // TODO: The builder should add all this glue code.
+ if (fDesc.getHeader().fExperimentalGS) {
+ GrAssert(fContext.info().glslGeneration() >= k150_GrGLSLGeneration);
+ builder->fGSHeader.append("layout(triangles) in;\n"
+ "layout(triangle_strip, max_vertices = 6) out;\n");
+ builder->gsCodeAppend("\tfor (int i = 0; i < 3; ++i) {\n"
+ "\t\tgl_Position = gl_in[i].gl_Position;\n");
+ if (fDesc.getHeader().fEmitsPointSize) {
+ builder->gsCodeAppend("\t\tgl_PointSize = 1.0;\n");
+ }
+ GrAssert(builder->fGSInputs.count() == builder->fGSOutputs.count());
+ int count = builder->fGSInputs.count();
+ for (int i = 0; i < count; ++i) {
+ builder->gsCodeAppendf("\t\t%s = %s[i];\n",
+ builder->fGSOutputs[i].getName().c_str(),
+ builder->fGSInputs[i].getName().c_str());
+ }
+ builder->gsCodeAppend("\t\tEmitVertex();\n"
+ "\t}\n"
+ "\tEndPrimitive();\n");
+ }
+#endif
+}
+
+const char* GrGLProgram::adjustInColor(const SkString& inColor) const {
+ if (inColor.size()) {
+ return inColor.c_str();
+ } else {
+ if (GrGLProgramDesc::kSolidWhite_ColorInput == fDesc.getHeader().fColorInput) {
+ return GrGLSLOnesVecf(4);
+ } else {
+ return GrGLSLZerosVecf(4);
+ }
+ }
+}
+
+namespace {
+// prints a shader using params similar to glShaderSource
+void print_shader(GrGLint stringCnt,
+ const GrGLchar** strings,
+ GrGLint* stringLengths) {
+ for (int i = 0; i < stringCnt; ++i) {
+ if (NULL == stringLengths || stringLengths[i] < 0) {
+ GrPrintf(strings[i]);
+ } else {
+ GrPrintf("%.*s", stringLengths[i], strings[i]);
+ }
+ }
+}
+
+// Compiles a GL shader, returns shader ID or 0 if failed params have same meaning as glShaderSource
+GrGLuint compile_shader(const GrGLContext& gl,
+ GrGLenum type,
+ int stringCnt,
+ const char** strings,
+ int* stringLengths) {
+ SK_TRACE_EVENT1("GrGLProgram::CompileShader",
+ "stringCount", SkStringPrintf("%i", stringCnt).c_str());
+
+ GrGLuint shader;
+ GR_GL_CALL_RET(gl.interface(), shader, CreateShader(type));
+ if (0 == shader) {
+ return 0;
+ }
+
+ const GrGLInterface* gli = gl.interface();
+ GrGLint compiled = GR_GL_INIT_ZERO;
+ GR_GL_CALL(gli, ShaderSource(shader, stringCnt, strings, stringLengths));
+ GR_GL_CALL(gli, CompileShader(shader));
+ GR_GL_CALL(gli, GetShaderiv(shader, GR_GL_COMPILE_STATUS, &compiled));
+
+ if (!compiled) {
+ GrGLint infoLen = GR_GL_INIT_ZERO;
+ GR_GL_CALL(gli, GetShaderiv(shader, GR_GL_INFO_LOG_LENGTH, &infoLen));
+ SkAutoMalloc log(sizeof(char)*(infoLen+1)); // outside if for debugger
+ if (infoLen > 0) {
+ // retrieve length even though we don't need it to workaround bug in chrome cmd buffer
+ // param validation.
+ GrGLsizei length = GR_GL_INIT_ZERO;
+ GR_GL_CALL(gli, GetShaderInfoLog(shader, infoLen+1,
+ &length, (char*)log.get()));
+ print_shader(stringCnt, strings, stringLengths);
+ GrPrintf("\n%s", log.get());
+ }
+ GrAssert(!"Shader compilation failed!");
+ GR_GL_CALL(gli, DeleteShader(shader));
+ return 0;
+ }
+ return shader;
+}
+
+// helper version of above for when shader is already flattened into a single SkString
+GrGLuint compile_shader(const GrGLContext& gl, GrGLenum type, const SkString& shader) {
+ const GrGLchar* str = shader.c_str();
+ int length = shader.size();
+ return compile_shader(gl, type, 1, &str, &length);
+}
+
+void expand_known_value4f(SkString* string, GrSLConstantVec vec) {
+ GrAssert(string->isEmpty() == (vec != kNone_GrSLConstantVec));
+ switch (vec) {
+ case kNone_GrSLConstantVec:
+ break;
+ case kZeros_GrSLConstantVec:
+ *string = GrGLSLZerosVecf(4);
+ break;
+ case kOnes_GrSLConstantVec:
+ *string = GrGLSLOnesVecf(4);
+ break;
+ }
+}
+
+}
+
+// compiles all the shaders from builder and stores the shader IDs
+bool GrGLProgram::compileShaders(const GrGLShaderBuilder& builder) {
+
+ SkString shader;
+
+ builder.getShader(GrGLShaderBuilder::kVertex_ShaderType, &shader);
+ if (c_PrintShaders) {
+ GrPrintf(shader.c_str());
+ GrPrintf("\n");
+ }
+
+ if (!(fVShaderID = compile_shader(fContext, GR_GL_VERTEX_SHADER, shader))) {
+ return false;
+ }
+
+ fGShaderID = 0;
+#if GR_GL_EXPERIMENTAL_GS
+ if (fDesc.getHeader().fExperimentalGS) {
+ builder.getShader(GrGLShaderBuilder::kGeometry_ShaderType, &shader);
+ if (c_PrintShaders) {
+ GrPrintf(shader.c_str());
+ GrPrintf("\n");
+ }
+ if (!(fGShaderID = compile_shader(fContext, GR_GL_GEOMETRY_SHADER, shader))) {
+ return false;
+ }
+ }
+#endif
+
+ builder.getShader(GrGLShaderBuilder::kFragment_ShaderType, &shader);
+ if (c_PrintShaders) {
+ GrPrintf(shader.c_str());
+ GrPrintf("\n");
+ }
+ if (!(fFShaderID = compile_shader(fContext, GR_GL_FRAGMENT_SHADER, shader))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool GrGLProgram::genProgram(const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]) {
+ GrAssert(0 == fProgramID);
+
+ const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader();
+
+ GrGLShaderBuilder builder(fContext.info(), fUniformManager, fDesc);
+
+ // the dual source output has no canonical var name, have to
+ // declare an output, which is incompatible with gl_FragColor/gl_FragData.
+ bool dualSourceOutputWritten = false;
+
+ GrGLShaderVar colorOutput;
+ bool isColorDeclared = GrGLSLSetupFSColorOuput(fContext.info().glslGeneration(),
+ declared_color_output_name(),
+ &colorOutput);
+ if (isColorDeclared) {
+ builder.fFSOutputs.push_back(colorOutput);
+ }
+
+ const char* viewMName;
+ fUniformHandles.fViewMatrixUni = builder.addUniform(GrGLShaderBuilder::kVertex_ShaderType,
+ kMat33f_GrSLType, "ViewM", &viewMName);
+
+
+ builder.vsCodeAppendf("\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;
+ GrSLConstantVec knownColorValue = this->genInputColor(&builder, &inColor);
+
+ // we output point size in the GS if present
+ if (header.fEmitsPointSize
+#if GR_GL_EXPERIMENTAL_GS
+ && !header.fExperimentalGS
+#endif
+ ) {
+ builder.vsCodeAppend("\tgl_PointSize = 1.0;\n");
+ }
+
+ // Get the coeffs for the Mode-based color filter, determine if color is needed.
+ SkXfermode::Coeff colorCoeff;
+ SkXfermode::Coeff filterColorCoeff;
+ SkAssertResult(
+ SkXfermode::ModeAsCoeff(static_cast<SkXfermode::Mode>(header.fColorFilterXfermode),
+ &filterColorCoeff,
+ &colorCoeff));
+ bool needColor, needFilterColor;
+ need_blend_inputs(filterColorCoeff, colorCoeff, &needFilterColor, &needColor);
+
+ // used in order for builder to return the per-stage uniform handles.
+ typedef SkTArray<GrGLUniformManager::UniformHandle, true>* UniHandleArrayPtr;
+ int maxColorOrCovEffectCnt = GrMax(fDesc.numColorEffects(), fDesc.numCoverageEffects());
+ SkAutoTArray<UniHandleArrayPtr> effectUniformArrays(maxColorOrCovEffectCnt);
+ SkAutoTArray<GrGLEffect*> glEffects(maxColorOrCovEffectCnt);
+
+ if (needColor) {
+ for (int e = 0; e < fDesc.numColorEffects(); ++e) {
+ effectUniformArrays[e] = &fColorEffects[e].fSamplerUnis;
+ }
+
+ builder.emitEffects(colorStages,
+ fDesc.effectKeys(),
+ fDesc.numColorEffects(),
+ &inColor,
+ &knownColorValue,
+ effectUniformArrays.get(),
+ glEffects.get());
+
+ for (int e = 0; e < fDesc.numColorEffects(); ++e) {
+ fColorEffects[e].fGLEffect = glEffects[e];
+ }
+ }
+
+ // Insert the color filter. This will soon be replaced by a color effect.
+ if (SkXfermode::kDst_Mode != header.fColorFilterXfermode) {
+ const char* colorFilterColorUniName = NULL;
+ fUniformHandles.fColorFilterUni = builder.addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "FilterColor",
+ &colorFilterColorUniName);
+
+ builder.fsCodeAppend("\tvec4 filteredColor;\n");
+ const char* color;
+ // add_color_filter requires a real input string.
+ if (knownColorValue == kOnes_GrSLConstantVec) {
+ color = GrGLSLOnesVecf(4);
+ } else if (knownColorValue == kZeros_GrSLConstantVec) {
+ color = GrGLSLZerosVecf(4);
+ } else {
+ color = inColor.c_str();
+ }
+ add_color_filter(&builder, "filteredColor", filterColorCoeff,
+ colorCoeff, colorFilterColorUniName, color);
+ inColor = "filteredColor";
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // compute the partial coverage
+ SkString inCoverage;
+ GrSLConstantVec knownCoverageValue = this->genInputCoverage(&builder, &inCoverage);
+
+ for (int e = 0; e < fDesc.numCoverageEffects(); ++e) {
+ effectUniformArrays[e] = &fCoverageEffects[e].fSamplerUnis;
+ }
+
+ builder.emitEffects(coverageStages,
+ fDesc.getEffectKeys() + fDesc.numColorEffects(),
+ fDesc.numCoverageEffects(),
+ &inCoverage,
+ &knownCoverageValue,
+ effectUniformArrays.get(),
+ glEffects.get());
+ for (int e = 0; e < fDesc.numCoverageEffects(); ++e) {
+ fCoverageEffects[e].fGLEffect = glEffects[e];
+ }
+
+ // discard if coverage is zero
+ if (header.fDiscardIfZeroCoverage && kOnes_GrSLConstantVec != knownCoverageValue) {
+ if (kZeros_GrSLConstantVec == knownCoverageValue) {
+ // This is unfortunate.
+ builder.fsCodeAppend("\tdiscard;\n");
+ } else {
+ builder.fsCodeAppendf("\tif (all(lessThanEqual(%s, vec4(0.0)))) {\n\t\tdiscard;\n\t}\n",
+ inCoverage.c_str());
+ }
+ }
+
+ GrGLProgramDesc::CoverageOutput coverageOutput =
+ static_cast<GrGLProgramDesc::CoverageOutput>(header.fCoverageOutput);
+ if (GrGLProgramDesc::CoverageOutputUsesSecondaryOutput(coverageOutput)) {
+ builder.fFSOutputs.push_back().set(kVec4f_GrSLType,
+ GrGLShaderVar::kOut_TypeModifier,
+ dual_source_output_name());
+ // default coeff to ones for kCoverage_DualSrcOutput
+ SkString coeff;
+ GrSLConstantVec knownCoeffValue = kOnes_GrSLConstantVec;
+ if (GrGLProgramDesc::kSecondaryCoverageISA_CoverageOutput == header.fCoverageOutput) {
+ // Get (1-A) into coeff
+ SkString inColorAlpha;
+ GrGLSLGetComponent4f(&inColorAlpha,
+ inColor.c_str(),
+ kA_GrColorComponentFlag,
+ knownColorValue,
+ true);
+ knownCoeffValue = GrGLSLSubtractf<1>(&coeff,
+ NULL,
+ inColorAlpha.c_str(),
+ kOnes_GrSLConstantVec,
+ knownColorValue,
+ true);
+ } else if (GrGLProgramDesc::kSecondaryCoverageISC_CoverageOutput == coverageOutput) {
+ // Get (1-RGBA) into coeff
+ knownCoeffValue = GrGLSLSubtractf<4>(&coeff,
+ NULL,
+ inColor.c_str(),
+ kOnes_GrSLConstantVec,
+ knownColorValue,
+ true);
+ }
+ // Get coeff * coverage into modulate and then write that to the dual source output.
+ SkString modulate;
+ GrGLSLModulatef<4>(&modulate,
+ coeff.c_str(),
+ inCoverage.c_str(),
+ knownCoeffValue,
+ knownCoverageValue,
+ false);
+ builder.fsCodeAppendf("\t%s = %s;\n", dual_source_output_name(), modulate.c_str());
+ dualSourceOutputWritten = true;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // combine color and coverage as frag color
+
+ // Get "color * coverage" into fragColor
+ SkString fragColor;
+ GrSLConstantVec knownFragColorValue = GrGLSLModulatef<4>(&fragColor,
+ inColor.c_str(),
+ inCoverage.c_str(),
+ knownColorValue,
+ knownCoverageValue,
+ true);
+ // Now tack on "+(1-coverage)dst onto the frag color if we were asked to do so.
+ if (GrGLProgramDesc::kCombineWithDst_CoverageOutput == coverageOutput) {
+ SkString dstCoeff;
+ GrSLConstantVec knownDstCoeffValue = GrGLSLSubtractf<4>(&dstCoeff,
+ NULL,
+ inCoverage.c_str(),
+ kOnes_GrSLConstantVec,
+ knownCoverageValue,
+ true);
+ SkString dstContribution;
+ GrSLConstantVec knownDstContributionValue = GrGLSLModulatef<4>(&dstContribution,
+ dstCoeff.c_str(),
+ builder.dstColor(),
+ knownDstCoeffValue,
+ kNone_GrSLConstantVec,
+ true);
+ SkString oldFragColor = fragColor;
+ fragColor.reset();
+ GrGLSLAddf<4>(&fragColor,
+ oldFragColor.c_str(),
+ dstContribution.c_str(),
+ knownFragColorValue,
+ knownDstContributionValue,
+ false);
+ } else {
+ expand_known_value4f(&fragColor, knownFragColorValue);
+ }
+ builder.fsCodeAppendf("\t%s = %s;\n", colorOutput.getName().c_str(), fragColor.c_str());
+
+ ///////////////////////////////////////////////////////////////////////////
+ // insert GS
+#if GR_DEBUG
+ this->genGeometryShader(&builder);
+#endif
+
+ ///////////////////////////////////////////////////////////////////////////
+ // compile and setup attribs and unis
+
+ if (!this->compileShaders(builder)) {
+ return false;
+ }
+
+ if (!this->bindOutputsAttribsAndLinkProgram(builder,
+ isColorDeclared,
+ dualSourceOutputWritten)) {
+ return false;
+ }
+
+ builder.finished(fProgramID);
+ fUniformHandles.fRTHeightUni = builder.getRTHeightUniform();
+ fUniformHandles.fDstCopyTopLeftUni = builder.getDstCopyTopLeftUniform();
+ fUniformHandles.fDstCopyScaleUni = builder.getDstCopyScaleUniform();
+ fUniformHandles.fDstCopySamplerUni = builder.getDstCopySamplerUniform();
+ // This must be called after we set fDstCopySamplerUni above.
+ this->initSamplerUniforms();
+
+ return true;
+}
+
+bool GrGLProgram::bindOutputsAttribsAndLinkProgram(const GrGLShaderBuilder& builder,
+ bool bindColorOut,
+ bool bindDualSrcOut) {
+ GL_CALL_RET(fProgramID, CreateProgram());
+ if (!fProgramID) {
+ return false;
+ }
+
+ GL_CALL(AttachShader(fProgramID, fVShaderID));
+ if (fGShaderID) {
+ GL_CALL(AttachShader(fProgramID, fGShaderID));
+ }
+ GL_CALL(AttachShader(fProgramID, fFShaderID));
+
+ if (bindColorOut) {
+ GL_CALL(BindFragDataLocation(fProgramID, 0, declared_color_output_name()));
+ }
+ if (bindDualSrcOut) {
+ GL_CALL(BindFragDataLocationIndexed(fProgramID, 0, 1, dual_source_output_name()));
+ }
+
+ const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader();
+
+ // Bind the attrib locations to same values for all shaders
+ GL_CALL(BindAttribLocation(fProgramID,
+ header.fPositionAttributeIndex,
+ builder.positionAttribute().c_str()));
+ if (-1 != header.fLocalCoordAttributeIndex) {
+ GL_CALL(BindAttribLocation(fProgramID,
+ header.fLocalCoordAttributeIndex,
+ builder.localCoordsAttribute().c_str()));
+ }
+ if (-1 != header.fColorAttributeIndex) {
+ GL_CALL(BindAttribLocation(fProgramID, header.fColorAttributeIndex, COL_ATTR_NAME));
+ }
+ if (-1 != header.fCoverageAttributeIndex) {
+ GL_CALL(BindAttribLocation(fProgramID, header.fCoverageAttributeIndex, COV_ATTR_NAME));
+ }
+
+ const GrGLShaderBuilder::AttributePair* attribEnd = builder.getEffectAttributes().end();
+ for (const GrGLShaderBuilder::AttributePair* attrib = builder.getEffectAttributes().begin();
+ attrib != attribEnd;
+ ++attrib) {
+ GL_CALL(BindAttribLocation(fProgramID, attrib->fIndex, attrib->fName.c_str()));
+ }
+
+ GL_CALL(LinkProgram(fProgramID));
+
+ GrGLint linked = GR_GL_INIT_ZERO;
+ GL_CALL(GetProgramiv(fProgramID, GR_GL_LINK_STATUS, &linked));
+ if (!linked) {
+ GrGLint infoLen = GR_GL_INIT_ZERO;
+ GL_CALL(GetProgramiv(fProgramID, GR_GL_INFO_LOG_LENGTH, &infoLen));
+ SkAutoMalloc log(sizeof(char)*(infoLen+1)); // outside if for debugger
+ if (infoLen > 0) {
+ // retrieve length even though we don't need it to workaround
+ // bug in chrome cmd buffer param validation.
+ GrGLsizei length = GR_GL_INIT_ZERO;
+ GL_CALL(GetProgramInfoLog(fProgramID,
+ infoLen+1,
+ &length,
+ (char*)log.get()));
+ GrPrintf((char*)log.get());
+ }
+ GrAssert(!"Error linking program");
+ GL_CALL(DeleteProgram(fProgramID));
+ fProgramID = 0;
+ return false;
+ }
+ return true;
+}
+
+void GrGLProgram::initSamplerUniforms() {
+ GL_CALL(UseProgram(fProgramID));
+ GrGLint texUnitIdx = 0;
+ if (GrGLUniformManager::kInvalidUniformHandle != fUniformHandles.fDstCopySamplerUni) {
+ fUniformManager.setSampler(fUniformHandles.fDstCopySamplerUni, texUnitIdx);
+ fDstCopyTexUnit = texUnitIdx++;
+ }
+
+ for (int e = 0; e < fColorEffects.count(); ++e) {
+ this->initEffectSamplerUniforms(&fColorEffects[e], &texUnitIdx);
+ }
+
+ for (int e = 0; e < fCoverageEffects.count(); ++e) {
+ this->initEffectSamplerUniforms(&fCoverageEffects[e], &texUnitIdx);
+ }
+}
+
+void GrGLProgram::initEffectSamplerUniforms(EffectAndSamplers* effect, int* texUnitIdx) {
+ int numSamplers = effect->fSamplerUnis.count();
+ effect->fTextureUnits.reset(numSamplers);
+ for (int s = 0; s < numSamplers; ++s) {
+ UniformHandle handle = effect->fSamplerUnis[s];
+ if (GrGLUniformManager::kInvalidUniformHandle != handle) {
+ fUniformManager.setSampler(handle, *texUnitIdx);
+ effect->fTextureUnits[s] = (*texUnitIdx)++;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLProgram::setEffectData(GrGpuGL* gpu,
+ const GrEffectStage& stage,
+ const EffectAndSamplers& effect) {
+
+ // Let the GrGLEffect set its data.
+ bool explicitLocalCoords = -1 != fDesc.getHeader().fLocalCoordAttributeIndex;
+ GrDrawEffect drawEffect(stage, explicitLocalCoords);
+ effect.fGLEffect->setData(fUniformManager, drawEffect);
+
+ // Bind the texures for the effect.
+ int numSamplers = effect.fSamplerUnis.count();
+ GrAssert((*stage.getEffect())->numTextures() == numSamplers);
+ for (int s = 0; s < numSamplers; ++s) {
+ UniformHandle handle = effect.fSamplerUnis[s];
+ if (GrGLUniformManager::kInvalidUniformHandle != handle) {
+ const GrTextureAccess& access = (*stage.getEffect())->textureAccess(s);
+ GrGLTexture* texture = static_cast<GrGLTexture*>(access.getTexture());
+ int unit = effect.fTextureUnits[s];
+ gpu->bindTexture(unit, access.getParams(), texture);
+ }
+ }
+}
+
+void GrGLProgram::setData(GrGpuGL* gpu,
+ GrDrawState::BlendOptFlags blendOpts,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[],
+ const GrDeviceCoordTexture* dstCopy,
+ SharedGLState* sharedState) {
+ const GrDrawState& drawState = gpu->getDrawState();
+
+ GrColor color;
+ GrColor coverage;
+ if (blendOpts & GrDrawState::kEmitTransBlack_BlendOptFlag) {
+ color = 0;
+ coverage = 0;
+ } else if (blendOpts & GrDrawState::kEmitCoverage_BlendOptFlag) {
+ color = 0xffffffff;
+ coverage = drawState.getCoverage();
+ } else {
+ color = drawState.getColor();
+ coverage = drawState.getCoverage();
+ }
+
+ this->setColor(drawState, color, sharedState);
+ this->setCoverage(drawState, coverage, sharedState);
+ this->setMatrixAndRenderTargetHeight(drawState);
+
+ // Setup the SkXfermode::Mode-based colorfilter uniform if necessary
+ if (GrGLUniformManager::kInvalidUniformHandle != fUniformHandles.fColorFilterUni &&
+ fColorFilterColor != drawState.getColorFilterColor()) {
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(drawState.getColorFilterColor(), c);
+ fUniformManager.set4fv(fUniformHandles.fColorFilterUni, 0, 1, c);
+ fColorFilterColor = drawState.getColorFilterColor();
+ }
+
+ if (NULL != dstCopy) {
+ if (GrGLUniformManager::kInvalidUniformHandle != fUniformHandles.fDstCopyTopLeftUni) {
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle != fUniformHandles.fDstCopyScaleUni);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle !=
+ fUniformHandles.fDstCopySamplerUni);
+ fUniformManager.set2f(fUniformHandles.fDstCopyTopLeftUni,
+ static_cast<GrGLfloat>(dstCopy->offset().fX),
+ static_cast<GrGLfloat>(dstCopy->offset().fY));
+ fUniformManager.set2f(fUniformHandles.fDstCopyScaleUni,
+ 1.f / dstCopy->texture()->width(),
+ 1.f / dstCopy->texture()->height());
+ GrGLTexture* texture = static_cast<GrGLTexture*>(dstCopy->texture());
+ static GrTextureParams kParams; // the default is clamp, nearest filtering.
+ gpu->bindTexture(fDstCopyTexUnit, kParams, texture);
+ } else {
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle ==
+ fUniformHandles.fDstCopyScaleUni);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle ==
+ fUniformHandles.fDstCopySamplerUni);
+ }
+ } else {
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fUniformHandles.fDstCopyTopLeftUni);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fUniformHandles.fDstCopyScaleUni);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fUniformHandles.fDstCopySamplerUni);
+ }
+
+ for (int e = 0; e < fColorEffects.count(); ++e) {
+ // We may have omitted the GrGLEffect because of the color filter logic in genProgram.
+ // This can be removed when the color filter is an effect.
+ if (NULL != fColorEffects[e].fGLEffect) {
+ this->setEffectData(gpu, *colorStages[e], fColorEffects[e]);
+ }
+ }
+
+ for (int e = 0; e < fCoverageEffects.count(); ++e) {
+ if (NULL != fCoverageEffects[e].fGLEffect) {
+ this->setEffectData(gpu, *coverageStages[e], fCoverageEffects[e]);
+ }
+ }
+}
+
+void GrGLProgram::setColor(const GrDrawState& drawState,
+ GrColor color,
+ SharedGLState* sharedState) {
+ const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader();
+ if (!drawState.hasColorVertexAttribute()) {
+ switch (header.fColorInput) {
+ case GrGLProgramDesc::kAttribute_ColorInput:
+ GrAssert(-1 != header.fColorAttributeIndex);
+ if (sharedState->fConstAttribColor != color ||
+ sharedState->fConstAttribColorIndex != header.fColorAttributeIndex) {
+ // OpenGL ES only supports the float varieties of glVertexAttrib
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(color, c);
+ GL_CALL(VertexAttrib4fv(header.fColorAttributeIndex, c));
+ sharedState->fConstAttribColor = color;
+ sharedState->fConstAttribColorIndex = header.fColorAttributeIndex;
+ }
+ break;
+ case GrGLProgramDesc::kUniform_ColorInput:
+ if (fColor != color) {
+ // OpenGL ES doesn't support unsigned byte varieties of glUniform
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(color, c);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle !=
+ fUniformHandles.fColorUni);
+ fUniformManager.set4fv(fUniformHandles.fColorUni, 0, 1, c);
+ fColor = color;
+ }
+ sharedState->fConstAttribColorIndex = -1;
+ break;
+ case GrGLProgramDesc::kSolidWhite_ColorInput:
+ case GrGLProgramDesc::kTransBlack_ColorInput:
+ sharedState->fConstAttribColorIndex = -1;
+ break;
+ default:
+ GrCrash("Unknown color type.");
+ }
+ } else {
+ sharedState->fConstAttribColorIndex = -1;
+ }
+}
+
+void GrGLProgram::setCoverage(const GrDrawState& drawState,
+ GrColor coverage,
+ SharedGLState* sharedState) {
+ const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader();
+ if (!drawState.hasCoverageVertexAttribute()) {
+ switch (header.fCoverageInput) {
+ case GrGLProgramDesc::kAttribute_ColorInput:
+ if (sharedState->fConstAttribCoverage != coverage ||
+ sharedState->fConstAttribCoverageIndex != header.fCoverageAttributeIndex) {
+ // OpenGL ES only supports the float varieties of glVertexAttrib
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(coverage, c);
+ GL_CALL(VertexAttrib4fv(header.fCoverageAttributeIndex, c));
+ sharedState->fConstAttribCoverage = coverage;
+ sharedState->fConstAttribCoverageIndex = header.fCoverageAttributeIndex;
+ }
+ break;
+ case GrGLProgramDesc::kUniform_ColorInput:
+ if (fCoverage != coverage) {
+ // OpenGL ES doesn't support unsigned byte varieties of glUniform
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(coverage, c);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle !=
+ fUniformHandles.fCoverageUni);
+ fUniformManager.set4fv(fUniformHandles.fCoverageUni, 0, 1, c);
+ fCoverage = coverage;
+ }
+ sharedState->fConstAttribCoverageIndex = -1;
+ break;
+ case GrGLProgramDesc::kSolidWhite_ColorInput:
+ case GrGLProgramDesc::kTransBlack_ColorInput:
+ sharedState->fConstAttribCoverageIndex = -1;
+ break;
+ default:
+ GrCrash("Unknown coverage type.");
+ }
+ } else {
+ sharedState->fConstAttribCoverageIndex = -1;
+ }
+}
+
+void GrGLProgram::setMatrixAndRenderTargetHeight(const GrDrawState& drawState) {
+ const GrRenderTarget* rt = drawState.getRenderTarget();
+ SkISize size;
+ size.set(rt->width(), rt->height());
+
+ // Load the RT height uniform if it is needed to y-flip gl_FragCoord.
+ if (GrGLUniformManager::kInvalidUniformHandle != fUniformHandles.fRTHeightUni &&
+ fMatrixState.fRenderTargetSize.fHeight != size.fHeight) {
+ fUniformManager.set1f(fUniformHandles.fRTHeightUni, SkIntToScalar(size.fHeight));
+ }
+
+ if (fMatrixState.fRenderTargetOrigin != rt->origin() ||
+ !fMatrixState.fViewMatrix.cheapEqualTo(drawState.getViewMatrix()) ||
+ fMatrixState.fRenderTargetSize != size) {
+ SkMatrix m;
+ if (kBottomLeft_GrSurfaceOrigin == rt->origin()) {
+ m.setAll(
+ SkIntToScalar(2) / size.fWidth, 0, -SK_Scalar1,
+ 0,-SkIntToScalar(2) / size.fHeight, SK_Scalar1,
+ 0, 0, SkMatrix::I()[8]);
+ } else {
+ m.setAll(
+ SkIntToScalar(2) / size.fWidth, 0, -SK_Scalar1,
+ 0, SkIntToScalar(2) / size.fHeight,-SK_Scalar1,
+ 0, 0, SkMatrix::I()[8]);
+ }
+ m.setConcat(m, drawState.getViewMatrix());
+
+ // ES doesn't allow you to pass true to the transpose param so we do our own transpose.
+ GrGLfloat mt[] = {
+ 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])
+ };
+ fUniformManager.setMatrix3f(fUniformHandles.fViewMatrixUni, mt);
+ fMatrixState.fViewMatrix = drawState.getViewMatrix();
+ fMatrixState.fRenderTargetSize = size;
+ fMatrixState.fRenderTargetOrigin = rt->origin();
+ }
+}
diff --git a/gpu/gl/GrGLProgram.h b/gpu/gl/GrGLProgram.h
new file mode 100644
index 00000000..3534552f
--- /dev/null
+++ b/gpu/gl/GrGLProgram.h
@@ -0,0 +1,230 @@
+/*
+ * 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 GrGLProgram_DEFINED
+#define GrGLProgram_DEFINED
+
+#include "GrDrawState.h"
+#include "GrGLContext.h"
+#include "GrGLProgramDesc.h"
+#include "GrGLSL.h"
+#include "GrGLTexture.h"
+#include "GrGLUniformManager.h"
+
+#include "SkString.h"
+#include "SkXfermode.h"
+
+class GrBinHashKeyBuilder;
+class GrGLEffect;
+class GrGLShaderBuilder;
+
+/**
+ * This class manages a GPU program and records per-program information.
+ * We can specify the attribute locations so that they are constant
+ * across our shaders. But the driver determines the uniform locations
+ * at link time. We don't need to remember the sampler uniform location
+ * because we will bind a texture slot to it and never change it
+ * Uniforms are program-local so we can't rely on fHWState to hold the
+ * previous uniform state after a program change.
+ */
+class GrGLProgram : public GrRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(GrGLProgram)
+
+ static GrGLProgram* Create(const GrGLContext& gl,
+ const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]);
+
+ virtual ~GrGLProgram();
+
+ /**
+ * Call to abandon GL objects owned by this program.
+ */
+ void abandon();
+
+ /**
+ * The shader may modify the blend coefficients. Params are in/out.
+ */
+ void overrideBlend(GrBlendCoeff* srcCoeff, GrBlendCoeff* dstCoeff) const;
+
+ const GrGLProgramDesc& getDesc() { return fDesc; }
+
+ /**
+ * Gets the GL program ID for this program.
+ */
+ GrGLuint programID() const { return fProgramID; }
+
+ /**
+ * Some GL state that is relevant to programs is not stored per-program. In particular color
+ * and coverage attributes can be global state. This struct is read and updated by
+ * GrGLProgram::setColor and GrGLProgram::setCoverage to allow us to avoid setting this state
+ * redundantly.
+ */
+ struct SharedGLState {
+ GrColor fConstAttribColor;
+ int fConstAttribColorIndex;
+ GrColor fConstAttribCoverage;
+ int fConstAttribCoverageIndex;
+
+ SharedGLState() { this->invalidate(); }
+ void invalidate() {
+ fConstAttribColor = GrColor_ILLEGAL;
+ fConstAttribColorIndex = -1;
+ fConstAttribCoverage = GrColor_ILLEGAL;
+ fConstAttribCoverageIndex = -1;
+ }
+ };
+
+ /**
+ * The GrDrawState's view matrix along with the aspects of the render target determine the
+ * matrix sent to GL. The size of the render target affects the GL matrix because we must
+ * convert from Skia device coords to GL's normalized coords. Also the origin of the render
+ * target may require us to perform a mirror-flip.
+ */
+ struct MatrixState {
+ SkMatrix fViewMatrix;
+ SkISize fRenderTargetSize;
+ GrSurfaceOrigin fRenderTargetOrigin;
+
+ MatrixState() { this->invalidate(); }
+ void invalidate() {
+ fViewMatrix = SkMatrix::InvalidMatrix();
+ fRenderTargetSize.fWidth = -1;
+ fRenderTargetSize.fHeight = -1;
+ fRenderTargetOrigin = (GrSurfaceOrigin) -1;
+ }
+ };
+
+ /**
+ * This function uploads uniforms and calls each GrGLEffect's setData. It is called before a
+ * draw occurs using the program after the program has already been bound. It also uses the
+ * GrGpuGL object to bind the textures required by the GrGLEffects. The color and coverage
+ * stages come from GrGLProgramDesc::Build().
+ */
+ void setData(GrGpuGL*,
+ GrDrawState::BlendOptFlags,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[],
+ const GrDeviceCoordTexture* dstCopy, // can be NULL
+ SharedGLState*);
+
+private:
+ typedef GrGLUniformManager::UniformHandle UniformHandle;
+
+ // handles for uniforms (aside from per-effect samplers)
+ struct UniformHandles {
+ UniformHandle fViewMatrixUni;
+ UniformHandle fColorUni;
+ UniformHandle fCoverageUni;
+ UniformHandle fColorFilterUni;
+
+ // We use the render target height to provide a y-down frag coord when specifying
+ // origin_upper_left is not supported.
+ UniformHandle fRTHeightUni;
+
+ // Uniforms for computing texture coords to do the dst-copy lookup
+ UniformHandle fDstCopyTopLeftUni;
+ UniformHandle fDstCopyScaleUni;
+ UniformHandle fDstCopySamplerUni;
+
+ UniformHandles() {
+ fViewMatrixUni = GrGLUniformManager::kInvalidUniformHandle;
+ fColorUni = GrGLUniformManager::kInvalidUniformHandle;
+ fCoverageUni = GrGLUniformManager::kInvalidUniformHandle;
+ fColorFilterUni = GrGLUniformManager::kInvalidUniformHandle;
+ fRTHeightUni = GrGLUniformManager::kInvalidUniformHandle;
+ fDstCopyTopLeftUni = GrGLUniformManager::kInvalidUniformHandle;
+ fDstCopyScaleUni = GrGLUniformManager::kInvalidUniformHandle;
+ fDstCopySamplerUni = GrGLUniformManager::kInvalidUniformHandle;
+ }
+ };
+
+ typedef SkSTArray<4, UniformHandle, true> SamplerUniSArray;
+ typedef SkSTArray<4, int, true> TextureUnitSArray;
+
+ struct EffectAndSamplers {
+ EffectAndSamplers() : fGLEffect(NULL) {}
+ ~EffectAndSamplers() { delete fGLEffect; }
+ GrGLEffect* fGLEffect;
+ SamplerUniSArray fSamplerUnis; // sampler uni handles for effect's GrTextureAccess
+ TextureUnitSArray fTextureUnits; // texture unit used for each entry of fSamplerUnis
+ };
+
+ GrGLProgram(const GrGLContext& gl,
+ const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]);
+
+ bool succeeded() const { return 0 != fProgramID; }
+
+ /**
+ * This is the heavy initialization routine for building a GLProgram. colorStages and
+ * coverageStages correspond to the output of GrGLProgramDesc::Build().
+ */
+ bool genProgram(const GrEffectStage* colorStages[], const GrEffectStage* coverageStages[]);
+
+ GrSLConstantVec genInputColor(GrGLShaderBuilder* builder, SkString* inColor);
+
+ GrSLConstantVec genInputCoverage(GrGLShaderBuilder* builder, SkString* inCoverage);
+
+ void genGeometryShader(GrGLShaderBuilder* segments) const;
+
+ // Creates a GL program ID, binds shader attributes to GL vertex attrs, and links the program
+ bool bindOutputsAttribsAndLinkProgram(const GrGLShaderBuilder& builder,
+ bool bindColorOut,
+ bool bindDualSrcOut);
+
+ // Sets the texture units for samplers
+ void initSamplerUniforms();
+ void initEffectSamplerUniforms(EffectAndSamplers* effect, int* texUnitIdx);
+
+ bool compileShaders(const GrGLShaderBuilder& builder);
+
+ const char* adjustInColor(const SkString& inColor) const;
+
+ // Helper for setData().
+ void setEffectData(GrGpuGL* gpu, const GrEffectStage& stage, const EffectAndSamplers& effect);
+
+ // Helper for setData(). Makes GL calls to specify the initial color when there is not
+ // per-vertex colors.
+ void setColor(const GrDrawState&, GrColor color, SharedGLState*);
+
+ // Helper for setData(). Makes GL calls to specify the initial coverage when there is not
+ // per-vertex coverages.
+ void setCoverage(const GrDrawState&, GrColor coverage, SharedGLState*);
+
+ // Helper for setData() that sets the view matrix and loads the render target height uniform
+ void setMatrixAndRenderTargetHeight(const GrDrawState&);
+
+ // GL IDs
+ GrGLuint fVShaderID;
+ GrGLuint fGShaderID;
+ GrGLuint fFShaderID;
+ GrGLuint fProgramID;
+
+ // these reflect the current values of uniforms (GL uniform values travel with program)
+ MatrixState fMatrixState;
+ GrColor fColor;
+ GrColor fCoverage;
+ GrColor fColorFilterColor;
+ int fDstCopyTexUnit;
+
+ SkTArray<EffectAndSamplers> fColorEffects;
+ SkTArray<EffectAndSamplers> fCoverageEffects;
+
+ GrGLProgramDesc fDesc;
+ const GrGLContext& fContext;
+
+ GrGLUniformManager fUniformManager;
+ UniformHandles fUniformHandles;
+
+ typedef GrRefCnt INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLProgramDesc.cpp b/gpu/gl/GrGLProgramDesc.cpp
new file mode 100644
index 00000000..ee9775f4
--- /dev/null
+++ b/gpu/gl/GrGLProgramDesc.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLProgramDesc.h"
+#include "GrBackendEffectFactory.h"
+#include "GrDrawEffect.h"
+#include "GrEffect.h"
+#include "GrGLShaderBuilder.h"
+#include "GrGpuGL.h"
+
+#include "SkChecksum.h"
+
+namespace {
+inline GrGLEffect::EffectKey get_key_and_update_stats(const GrEffectStage& stage,
+ const GrGLCaps& caps,
+ bool useExplicitLocalCoords,
+ bool* setTrueIfReadsDst,
+ bool* setTrueIfReadsPos) {
+ const GrEffectRef& effect = *stage.getEffect();
+ const GrBackendEffectFactory& factory = effect->getFactory();
+ GrDrawEffect drawEffect(stage, useExplicitLocalCoords);
+ if (effect->willReadDstColor()) {
+ *setTrueIfReadsDst = true;
+ }
+ if (effect->willReadFragmentPosition()) {
+ *setTrueIfReadsPos = true;
+ }
+ return factory.glEffectKey(drawEffect, caps);
+}
+}
+void GrGLProgramDesc::Build(const GrDrawState& drawState,
+ bool isPoints,
+ GrDrawState::BlendOptFlags blendOpts,
+ GrBlendCoeff srcCoeff,
+ GrBlendCoeff dstCoeff,
+ const GrGpuGL* gpu,
+ const GrDeviceCoordTexture* dstCopy,
+ SkTArray<const GrEffectStage*, true>* colorStages,
+ SkTArray<const GrEffectStage*, true>* coverageStages,
+ GrGLProgramDesc* desc) {
+ colorStages->reset();
+ coverageStages->reset();
+
+ // This should already have been caught
+ GrAssert(!(GrDrawState::kSkipDraw_BlendOptFlag & blendOpts));
+
+ bool skipCoverage = SkToBool(blendOpts & GrDrawState::kEmitTransBlack_BlendOptFlag);
+
+ bool skipColor = SkToBool(blendOpts & (GrDrawState::kEmitTransBlack_BlendOptFlag |
+ GrDrawState::kEmitCoverage_BlendOptFlag));
+
+ // The descriptor is used as a cache key. Thus when a field of the
+ // descriptor will not affect program generation (because of the attribute
+ // bindings in use or other descriptor field settings) it should be set
+ // to a canonical value to avoid duplicate programs with different keys.
+
+ bool requiresColorAttrib = !skipColor && drawState.hasColorVertexAttribute();
+ bool requiresCoverageAttrib = !skipCoverage && drawState.hasCoverageVertexAttribute();
+ // we only need the local coords if we're actually going to generate effect code
+ bool requiresLocalCoordAttrib = !(skipCoverage && skipColor) &&
+ drawState.hasLocalCoordAttribute();
+
+ bool colorIsTransBlack = SkToBool(blendOpts & GrDrawState::kEmitTransBlack_BlendOptFlag);
+ bool colorIsSolidWhite = (blendOpts & GrDrawState::kEmitCoverage_BlendOptFlag) ||
+ (!requiresColorAttrib && 0xffffffff == drawState.getColor());
+
+ int numEffects = (skipColor ? 0 : drawState.numColorStages()) +
+ (skipCoverage ? 0 : drawState.numCoverageStages());
+
+ size_t newKeyLength = KeyLength(numEffects);
+ bool allocChanged;
+ desc->fKey.reset(newKeyLength, SkAutoMalloc::kAlloc_OnShrink, &allocChanged);
+ if (allocChanged || !desc->fInitialized) {
+ // make sure any padding in the header is zero if we we haven't used this allocation before.
+ memset(desc->header(), 0, kHeaderSize);
+ }
+ // write the key length
+ *desc->atOffset<uint32_t, kLengthOffset>() = newKeyLength;
+
+ KeyHeader* header = desc->header();
+ EffectKey* effectKeys = desc->effectKeys();
+
+ int currEffectKey = 0;
+ bool readsDst = false;
+ bool readFragPosition = false;
+ if (!skipColor) {
+ for (int s = 0; s < drawState.numColorStages(); ++s) {
+ effectKeys[currEffectKey++] =
+ get_key_and_update_stats(drawState.getColorStage(s), gpu->glCaps(),
+ requiresLocalCoordAttrib, &readsDst, &readFragPosition);
+ }
+ }
+ if (!skipCoverage) {
+ for (int s = 0; s < drawState.numCoverageStages(); ++s) {
+ effectKeys[currEffectKey++] =
+ get_key_and_update_stats(drawState.getCoverageStage(s), gpu->glCaps(),
+ requiresLocalCoordAttrib, &readsDst, &readFragPosition);
+ }
+ }
+
+ header->fEmitsPointSize = isPoints;
+ header->fColorFilterXfermode = skipColor ? SkXfermode::kDst_Mode : drawState.getColorFilterMode();
+
+ // Currently the experimental GS will only work with triangle prims (and it doesn't do anything
+ // other than pass through values from the VS to the FS anyway).
+#if GR_GL_EXPERIMENTAL_GS
+#if 0
+ header->fExperimentalGS = gpu->caps().geometryShaderSupport();
+#else
+ header->fExperimentalGS = false;
+#endif
+#endif
+ if (colorIsTransBlack) {
+ header->fColorInput = kTransBlack_ColorInput;
+ } else if (colorIsSolidWhite) {
+ header->fColorInput = kSolidWhite_ColorInput;
+ } else if (GR_GL_NO_CONSTANT_ATTRIBUTES && !requiresColorAttrib) {
+ header->fColorInput = kUniform_ColorInput;
+ } else {
+ header->fColorInput = kAttribute_ColorInput;
+ }
+
+ bool covIsSolidWhite = !requiresCoverageAttrib && 0xffffffff == drawState.getCoverage();
+
+ if (skipCoverage) {
+ header->fCoverageInput = kTransBlack_ColorInput;
+ } else if (covIsSolidWhite) {
+ header->fCoverageInput = kSolidWhite_ColorInput;
+ } else if (GR_GL_NO_CONSTANT_ATTRIBUTES && !requiresCoverageAttrib) {
+ header->fCoverageInput = kUniform_ColorInput;
+ } else {
+ header->fCoverageInput = kAttribute_ColorInput;
+ }
+
+ if (readsDst) {
+ GrAssert(NULL != dstCopy || gpu->caps()->dstReadInShaderSupport());
+ const GrTexture* dstCopyTexture = NULL;
+ if (NULL != dstCopy) {
+ dstCopyTexture = dstCopy->texture();
+ }
+ header->fDstReadKey = GrGLShaderBuilder::KeyForDstRead(dstCopyTexture, gpu->glCaps());
+ GrAssert(0 != header->fDstReadKey);
+ } else {
+ header->fDstReadKey = 0;
+ }
+
+ if (readFragPosition) {
+ header->fFragPosKey = GrGLShaderBuilder::KeyForFragmentPosition(drawState.getRenderTarget(),
+ gpu->glCaps());
+ } else {
+ header->fFragPosKey = 0;
+ }
+
+ // Record attribute indices
+ header->fPositionAttributeIndex = drawState.positionAttributeIndex();
+ header->fLocalCoordAttributeIndex = drawState.localCoordAttributeIndex();
+
+ // For constant color and coverage we need an attribute with an index beyond those already set
+ int availableAttributeIndex = drawState.getVertexAttribCount();
+ if (requiresColorAttrib) {
+ header->fColorAttributeIndex = drawState.colorVertexAttributeIndex();
+ } else if (GrGLProgramDesc::kAttribute_ColorInput == header->fColorInput) {
+ GrAssert(availableAttributeIndex < GrDrawState::kMaxVertexAttribCnt);
+ header->fColorAttributeIndex = availableAttributeIndex;
+ availableAttributeIndex++;
+ } else {
+ header->fColorAttributeIndex = -1;
+ }
+
+ if (requiresCoverageAttrib) {
+ header->fCoverageAttributeIndex = drawState.coverageVertexAttributeIndex();
+ } else if (GrGLProgramDesc::kAttribute_ColorInput == header->fCoverageInput) {
+ GrAssert(availableAttributeIndex < GrDrawState::kMaxVertexAttribCnt);
+ header->fCoverageAttributeIndex = availableAttributeIndex;
+ } else {
+ header->fCoverageAttributeIndex = -1;
+ }
+
+ // Here we deal with whether/how we handle color and coverage separately.
+
+ // Set these defaults and then possibly change our mind if there is coverage.
+ header->fDiscardIfZeroCoverage = false;
+ header->fCoverageOutput = kModulate_CoverageOutput;
+
+ // If we do have coverage determine whether it matters.
+ bool separateCoverageFromColor = false;
+ if (!drawState.isCoverageDrawing() && !skipCoverage &&
+ (drawState.numCoverageStages() > 0 || requiresCoverageAttrib)) {
+ // color filter is applied between color/coverage computation
+ if (SkXfermode::kDst_Mode != header->fColorFilterXfermode) {
+ separateCoverageFromColor = true;
+ }
+
+ // If we're stenciling then we want to discard samples that have zero coverage
+ if (drawState.getStencil().doesWrite()) {
+ header->fDiscardIfZeroCoverage = true;
+ separateCoverageFromColor = true;
+ }
+
+ if (gpu->caps()->dualSourceBlendingSupport() &&
+ !(blendOpts & (GrDrawState::kEmitCoverage_BlendOptFlag |
+ GrDrawState::kCoverageAsAlpha_BlendOptFlag))) {
+ if (kZero_GrBlendCoeff == dstCoeff) {
+ // write the coverage value to second color
+ header->fCoverageOutput = kSecondaryCoverage_CoverageOutput;
+ separateCoverageFromColor = true;
+ } else if (kSA_GrBlendCoeff == dstCoeff) {
+ // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
+ header->fCoverageOutput = kSecondaryCoverageISA_CoverageOutput;
+ separateCoverageFromColor = true;
+ } else if (kSC_GrBlendCoeff == dstCoeff) {
+ // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
+ header->fCoverageOutput = kSecondaryCoverageISC_CoverageOutput;
+ separateCoverageFromColor = true;
+ }
+ } else if (readsDst &&
+ kOne_GrBlendCoeff == srcCoeff &&
+ kZero_GrBlendCoeff == dstCoeff) {
+ header->fCoverageOutput = kCombineWithDst_CoverageOutput;
+ separateCoverageFromColor = true;
+ }
+ }
+ if (!skipColor) {
+ for (int s = 0; s < drawState.numColorStages(); ++s) {
+ colorStages->push_back(&drawState.getColorStage(s));
+ }
+ header->fColorEffectCnt = drawState.numColorStages();
+ }
+ if (!skipCoverage) {
+ SkTArray<const GrEffectStage*, true>* array;
+ if (separateCoverageFromColor) {
+ array = coverageStages;
+ header->fCoverageEffectCnt = drawState.numCoverageStages();
+ } else {
+ array = colorStages;
+ header->fColorEffectCnt += drawState.numCoverageStages();
+ }
+ for (int s = 0; s < drawState.numCoverageStages(); ++s) {
+ array->push_back(&drawState.getCoverageStage(s));
+ }
+ }
+
+ *desc->checksum() = 0;
+ *desc->checksum() = SkChecksum::Compute(reinterpret_cast<uint32_t*>(desc->fKey.get()),
+ newKeyLength);
+ desc->fInitialized = true;
+}
+
+GrGLProgramDesc& GrGLProgramDesc::operator= (const GrGLProgramDesc& other) {
+ fInitialized = other.fInitialized;
+ if (fInitialized) {
+ size_t keyLength = other.keyLength();
+ fKey.reset(keyLength);
+ memcpy(fKey.get(), other.fKey.get(), keyLength);
+ }
+ return *this;
+}
diff --git a/gpu/gl/GrGLProgramDesc.h b/gpu/gl/GrGLProgramDesc.h
new file mode 100644
index 00000000..e85133cc
--- /dev/null
+++ b/gpu/gl/GrGLProgramDesc.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLProgramDesc_DEFINED
+#define GrGLProgramDesc_DEFINED
+
+#include "GrGLEffect.h"
+#include "GrDrawState.h"
+#include "GrGLShaderBuilder.h"
+
+class GrGpuGL;
+
+// optionally compile the experimental GS code. Set to GR_DEBUG so that debug build bots will
+// execute the code.
+#define GR_GL_EXPERIMENTAL_GS GR_DEBUG
+
+
+/** This class describes a program to generate. It also serves as a program cache key. Very little
+ of this is GL-specific. There is the generation of GrGLEffect::EffectKeys and the dst-read part
+ of the key set by GrGLShaderBuilder. If the interfaces that set those portions were abstracted
+ to be API-neutral then so could this class. */
+class GrGLProgramDesc {
+public:
+ GrGLProgramDesc() : fInitialized(false) {}
+ GrGLProgramDesc(const GrGLProgramDesc& desc) { *this = desc; }
+
+ // Returns this as a uint32_t array to be used as a key in the program cache.
+ const uint32_t* asKey() const {
+ GrAssert(fInitialized);
+ return reinterpret_cast<const uint32_t*>(fKey.get());
+ }
+
+ // Gets the number of bytes in asKey(). It will be a 4-byte aligned value. When comparing two
+ // keys the size of either key can be used with memcmp() since the lengths themselves begin the
+ // keys and thus the memcmp will exit early if the keys are of different lengths.
+ uint32_t keyLength() const { return *this->atOffset<uint32_t, kLengthOffset>(); }
+
+ // Gets the a checksum of the key. Can be used as a hash value for a fast lookup in a cache.
+ uint32_t getChecksum() const { return *this->atOffset<uint32_t, kChecksumOffset>(); }
+
+ // For unit testing.
+ void setRandom(SkMWCRandom*,
+ const GrGpuGL* gpu,
+ const GrRenderTarget* dummyDstRenderTarget,
+ const GrTexture* dummyDstCopyTexture,
+ const GrEffectStage* stages[],
+ int numColorStages,
+ int numCoverageStages,
+ int currAttribIndex);
+
+ /**
+ * Builds a program descriptor from a GrDrawState. Whether the primitive type is points, the
+ * output of GrDrawState::getBlendOpts, and the caps of the GrGpuGL are also inputs. It also
+ * outputs the color and coverage stages referenced by the generated descriptor. This may
+ * not contain all stages from the draw state and coverage stages from the drawState may
+ * be treated as color stages in the output.
+ */
+ static void Build(const GrDrawState&,
+ bool isPoints,
+ GrDrawState::BlendOptFlags,
+ GrBlendCoeff srcCoeff,
+ GrBlendCoeff dstCoeff,
+ const GrGpuGL* gpu,
+ const GrDeviceCoordTexture* dstCopy,
+ SkTArray<const GrEffectStage*, true>* outColorStages,
+ SkTArray<const GrEffectStage*, true>* outCoverageStages,
+ GrGLProgramDesc* outDesc);
+
+ int numColorEffects() const {
+ GrAssert(fInitialized);
+ return this->getHeader().fColorEffectCnt;
+ }
+
+ int numCoverageEffects() const {
+ GrAssert(fInitialized);
+ return this->getHeader().fCoverageEffectCnt;
+ }
+
+ int numTotalEffects() const { return this->numColorEffects() + this->numCoverageEffects(); }
+
+ GrGLProgramDesc& operator= (const GrGLProgramDesc& other);
+
+ bool operator== (const GrGLProgramDesc& other) const {
+ GrAssert(fInitialized && other.fInitialized);
+ // The length is masked as a hint to the compiler that the address will be 4 byte aligned.
+ return 0 == memcmp(this->asKey(), other.asKey(), this->keyLength() & ~0x3);
+ }
+
+ bool operator!= (const GrGLProgramDesc& other) const {
+ return !(*this == other);
+ }
+
+ static bool Less(const GrGLProgramDesc& a, const GrGLProgramDesc& b) {
+ return memcmp(a.asKey(), b.asKey(), a.keyLength() & ~0x3) < 0;
+ }
+
+private:
+ // Specifies where the initial color comes from before the stages are applied.
+ enum ColorInput {
+ kSolidWhite_ColorInput,
+ kTransBlack_ColorInput,
+ kAttribute_ColorInput,
+ kUniform_ColorInput,
+
+ kColorInputCnt
+ };
+
+ enum CoverageOutput {
+ // modulate color and coverage, write result as the color output.
+ kModulate_CoverageOutput,
+ // Writes color*coverage as the primary color output and also writes coverage as the
+ // secondary output. Only set if dual source blending is supported.
+ kSecondaryCoverage_CoverageOutput,
+ // Writes color*coverage as the primary color output and also writes coverage * (1 - colorA)
+ // as the secondary output. Only set if dual source blending is supported.
+ kSecondaryCoverageISA_CoverageOutput,
+ // Writes color*coverage as the primary color output and also writes coverage *
+ // (1 - colorRGB) as the secondary output. Only set if dual source blending is supported.
+ kSecondaryCoverageISC_CoverageOutput,
+ // Combines the coverage, dst, and color as coverage * color + (1 - coverage) * dst. This
+ // can only be set if fDstReadKey is non-zero.
+ kCombineWithDst_CoverageOutput,
+
+ kCoverageOutputCnt
+ };
+
+ static bool CoverageOutputUsesSecondaryOutput(CoverageOutput co) {
+ switch (co) {
+ case kSecondaryCoverage_CoverageOutput: // fallthru
+ case kSecondaryCoverageISA_CoverageOutput:
+ case kSecondaryCoverageISC_CoverageOutput:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ struct KeyHeader {
+ GrGLShaderBuilder::DstReadKey fDstReadKey; // set by GrGLShaderBuilder if there
+ // are effects that must read the dst.
+ // Otherwise, 0.
+ GrGLShaderBuilder::FragPosKey fFragPosKey; // set by GrGLShaderBuilder if there are
+ // effects that read the fragment position.
+ // Otherwise, 0.
+
+ // should the FS discard if the coverage is zero (to avoid stencil manipulation)
+ SkBool8 fDiscardIfZeroCoverage;
+
+ uint8_t fColorInput; // casts to enum ColorInput
+ uint8_t fCoverageInput; // casts to enum ColorInput
+ uint8_t fCoverageOutput; // casts to enum CoverageOutput
+
+ SkBool8 fEmitsPointSize;
+ uint8_t fColorFilterXfermode; // casts to enum SkXfermode::Mode
+
+ // To enable experimental geometry shader code (not for use in
+ // production)
+#if GR_GL_EXPERIMENTAL_GS
+ SkBool8 fExperimentalGS;
+#endif
+
+ int8_t fPositionAttributeIndex;
+ int8_t fLocalCoordAttributeIndex;
+ int8_t fColorAttributeIndex;
+ int8_t fCoverageAttributeIndex;
+
+ int8_t fColorEffectCnt;
+ int8_t fCoverageEffectCnt;
+ };
+
+ // The key is 1 uint32_t for the length, followed another for the checksum, the header, and then
+ // the effect keys. Everything is fixed length except the effect key array.
+ enum {
+ kLengthOffset = 0,
+ kChecksumOffset = kLengthOffset + sizeof(uint32_t),
+ kHeaderOffset = kChecksumOffset + sizeof(uint32_t),
+ kHeaderSize = SkAlign4(sizeof(KeyHeader)),
+ kEffectKeyOffset = kHeaderOffset + kHeaderSize,
+ };
+
+ template<typename T, size_t OFFSET> T* atOffset() {
+ return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(fKey.get()) + OFFSET);
+ }
+
+ template<typename T, size_t OFFSET> const T* atOffset() const {
+ return reinterpret_cast<const T*>(reinterpret_cast<intptr_t>(fKey.get()) + OFFSET);
+ }
+
+ typedef GrGLEffect::EffectKey EffectKey;
+
+ uint32_t* checksum() { return this->atOffset<uint32_t, kChecksumOffset>(); }
+ KeyHeader* header() { return this->atOffset<KeyHeader, kHeaderOffset>(); }
+ EffectKey* effectKeys() { return this->atOffset<EffectKey, kEffectKeyOffset>(); }
+
+ const KeyHeader& getHeader() const { return *this->atOffset<KeyHeader, kHeaderOffset>(); }
+ const EffectKey* getEffectKeys() const { return this->atOffset<EffectKey, kEffectKeyOffset>(); }
+
+ static size_t KeyLength(int effectCnt) {
+ GR_STATIC_ASSERT(!(sizeof(EffectKey) & 0x3));
+ return kEffectKeyOffset + effectCnt * sizeof(EffectKey);
+ }
+
+ enum {
+ kMaxPreallocEffects = 16,
+ kPreAllocSize = kEffectKeyOffset + kMaxPreallocEffects * sizeof(EffectKey),
+ };
+
+ SkAutoSMalloc<kPreAllocSize> fKey;
+ bool fInitialized;
+
+ // GrGLProgram and GrGLShaderBuilder read the private fields to generate code. TODO: Move all
+ // code generation to GrGLShaderBuilder (and maybe add getters rather than friending).
+ friend class GrGLProgram;
+ friend class GrGLShaderBuilder;
+};
+
+#endif
diff --git a/gpu/gl/GrGLRenderTarget.cpp b/gpu/gl/GrGLRenderTarget.cpp
new file mode 100644
index 00000000..a6fc1862
--- /dev/null
+++ b/gpu/gl/GrGLRenderTarget.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "GrGLRenderTarget.h"
+
+#include "GrGpuGL.h"
+
+#define GPUGL static_cast<GrGpuGL*>(getGpu())
+
+#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X)
+
+void GrGLRenderTarget::init(const Desc& desc,
+ const GrGLIRect& viewport,
+ GrGLTexID* texID) {
+ fRTFBOID = desc.fRTFBOID;
+ fTexFBOID = desc.fTexFBOID;
+ fMSColorRenderbufferID = desc.fMSColorRenderbufferID;
+ fViewport = viewport;
+ fTexIDObj.reset(SkSafeRef(texID));
+}
+
+namespace {
+GrTextureDesc MakeDesc(GrTextureFlags flags,
+ int width, int height,
+ GrPixelConfig config, int sampleCnt,
+ GrSurfaceOrigin origin) {
+ GrTextureDesc temp;
+ temp.fFlags = flags;
+ temp.fWidth = width;
+ temp.fHeight = height;
+ temp.fConfig = config;
+ temp.fSampleCnt = sampleCnt;
+ temp.fOrigin = origin;
+ return temp;
+}
+
+};
+
+GrGLRenderTarget::GrGLRenderTarget(GrGpuGL* gpu,
+ const Desc& desc,
+ const GrGLIRect& viewport,
+ GrGLTexID* texID,
+ GrGLTexture* texture)
+ : INHERITED(gpu,
+ desc.fIsWrapped,
+ texture,
+ MakeDesc(kNone_GrTextureFlags,
+ viewport.fWidth, viewport.fHeight,
+ desc.fConfig, desc.fSampleCnt,
+ desc.fOrigin)) {
+ GrAssert(NULL != texID);
+ GrAssert(NULL != texture);
+ // FBO 0 can't also be a texture, right?
+ GrAssert(0 != desc.fRTFBOID);
+ GrAssert(0 != desc.fTexFBOID);
+
+ // we assume this is true, TODO: get rid of viewport as a param.
+ GrAssert(viewport.fWidth == texture->width());
+ GrAssert(viewport.fHeight == texture->height());
+
+ this->init(desc, viewport, texID);
+}
+
+GrGLRenderTarget::GrGLRenderTarget(GrGpuGL* gpu,
+ const Desc& desc,
+ const GrGLIRect& viewport)
+ : INHERITED(gpu,
+ desc.fIsWrapped,
+ NULL,
+ MakeDesc(kNone_GrTextureFlags,
+ viewport.fWidth, viewport.fHeight,
+ desc.fConfig, desc.fSampleCnt,
+ desc.fOrigin)) {
+ this->init(desc, viewport, NULL);
+}
+
+void GrGLRenderTarget::onRelease() {
+ GPUGL->notifyRenderTargetDelete(this);
+ if (!this->isWrapped()) {
+ if (fTexFBOID) {
+ GL_CALL(DeleteFramebuffers(1, &fTexFBOID));
+ }
+ if (fRTFBOID && fRTFBOID != fTexFBOID) {
+ GL_CALL(DeleteFramebuffers(1, &fRTFBOID));
+ }
+ if (fMSColorRenderbufferID) {
+ GL_CALL(DeleteRenderbuffers(1, &fMSColorRenderbufferID));
+ }
+ }
+ fRTFBOID = 0;
+ fTexFBOID = 0;
+ fMSColorRenderbufferID = 0;
+ fTexIDObj.reset(NULL);
+ INHERITED::onRelease();
+}
+
+void GrGLRenderTarget::onAbandon() {
+ fRTFBOID = 0;
+ fTexFBOID = 0;
+ fMSColorRenderbufferID = 0;
+ if (NULL != fTexIDObj.get()) {
+ fTexIDObj->abandon();
+ fTexIDObj.reset(NULL);
+ }
+ INHERITED::onAbandon();
+}
diff --git a/gpu/gl/GrGLRenderTarget.h b/gpu/gl/GrGLRenderTarget.h
new file mode 100644
index 00000000..de9f4c1a
--- /dev/null
+++ b/gpu/gl/GrGLRenderTarget.h
@@ -0,0 +1,107 @@
+/*
+ * 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 GrGLRenderTarget_DEFINED
+#define GrGLRenderTarget_DEFINED
+
+#include "GrGLIRect.h"
+#include "GrRenderTarget.h"
+#include "SkScalar.h"
+
+class GrGpuGL;
+class GrGLTexture;
+class GrGLTexID;
+
+class GrGLRenderTarget : public GrRenderTarget {
+
+public:
+ // set fTexFBOID to this value to indicate that it is multisampled but
+ // Gr doesn't know how to resolve it.
+ enum { kUnresolvableFBOID = 0 };
+
+ struct Desc {
+ GrGLuint fRTFBOID;
+ GrGLuint fTexFBOID;
+ GrGLuint fMSColorRenderbufferID;
+ bool fIsWrapped;
+ GrPixelConfig fConfig;
+ int fSampleCnt;
+ GrSurfaceOrigin fOrigin;
+ bool fCheckAllocation;
+ };
+
+ // creates a GrGLRenderTarget associated with a texture
+ GrGLRenderTarget(GrGpuGL* gpu,
+ const Desc& desc,
+ const GrGLIRect& viewport,
+ GrGLTexID* texID,
+ GrGLTexture* texture);
+
+ // creates an independent GrGLRenderTarget
+ GrGLRenderTarget(GrGpuGL* gpu,
+ const Desc& desc,
+ const GrGLIRect& viewport);
+
+ virtual ~GrGLRenderTarget() { this->release(); }
+
+ void setViewport(const GrGLIRect& rect) { fViewport = rect; }
+ const GrGLIRect& getViewport() const { return fViewport; }
+
+ // The following two functions return the same ID when a
+ // texture/render target is multisampled, and different IDs when
+ // it is.
+ // FBO ID used to render into
+ GrGLuint renderFBOID() const { return fRTFBOID; }
+ // FBO ID that has texture ID attached.
+ GrGLuint textureFBOID() const { return fTexFBOID; }
+
+ // override of GrRenderTarget
+ virtual GrBackendObject getRenderTargetHandle() const {
+ return this->renderFBOID();
+ }
+ virtual GrBackendObject getRenderTargetResolvedHandle() const {
+ return this->textureFBOID();
+ }
+ virtual ResolveType getResolveType() const {
+
+ if (!this->isMultisampled() ||
+ fRTFBOID == fTexFBOID) {
+ // catches FBO 0 and non MSAA case
+ return kAutoResolves_ResolveType;
+ } else if (kUnresolvableFBOID == fTexFBOID) {
+ return kCantResolve_ResolveType;
+ } else {
+ return kCanResolve_ResolveType;
+ }
+ }
+
+protected:
+ // override of GrResource
+ virtual void onAbandon() SK_OVERRIDE;
+ virtual void onRelease() SK_OVERRIDE;
+
+private:
+ GrGLuint fRTFBOID;
+ GrGLuint fTexFBOID;
+
+ GrGLuint fMSColorRenderbufferID;
+
+ // 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;
+
+ // non-NULL if this RT was created by Gr with an associated GrGLTexture.
+ SkAutoTUnref<GrGLTexID> fTexIDObj;
+
+ void init(const Desc& desc, const GrGLIRect& viewport, GrGLTexID* texID);
+
+ typedef GrRenderTarget INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLSL.cpp b/gpu/gl/GrGLSL.cpp
new file mode 100644
index 00000000..dcaa85e8
--- /dev/null
+++ b/gpu/gl/GrGLSL.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 "GrGLSL.h"
+#include "GrGLShaderVar.h"
+#include "SkString.h"
+
+GrGLSLGeneration GrGetGLSLGeneration(GrGLBinding binding, const GrGLInterface* gl) {
+ GrGLSLVersion ver = GrGLGetGLSLVersion(gl);
+ switch (binding) {
+ case kDesktop_GrGLBinding:
+ 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 {
+ return k110_GrGLSLGeneration;
+ }
+ case kES2_GrGLBinding:
+ // version 1.00 of ES GLSL based on ver 1.20 of desktop GLSL
+ GrAssert(ver >= GR_GL_VER(1,00));
+ return k110_GrGLSLGeneration;
+ default:
+ GrCrash("Unknown GL Binding");
+ return k110_GrGLSLGeneration; // suppress warning
+ }
+}
+
+const char* GrGetGLSLVersionDecl(GrGLBinding binding, GrGLSLGeneration gen) {
+ switch (gen) {
+ case k110_GrGLSLGeneration:
+ if (kES2_GrGLBinding == binding) {
+ // ES2s shader language is based on version 1.20 but is version
+ // 1.00 of the ES language.
+ return "#version 100\n";
+ } else {
+ GrAssert(kDesktop_GrGLBinding == binding);
+ return "#version 110\n";
+ }
+ 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";
+ default:
+ GrCrash("Unknown GL version.");
+ return ""; // suppress warning
+ }
+}
+
+bool GrGLSLSetupFSColorOuput(GrGLSLGeneration gen, const char* nameIfDeclared, GrGLShaderVar* var) {
+ bool declaredOutput = k110_GrGLSLGeneration != gen;
+ var->set(kVec4f_GrSLType,
+ GrGLShaderVar::kOut_TypeModifier,
+ declaredOutput ? nameIfDeclared : "gl_FragColor");
+ return declaredOutput;
+}
+
+const char* GrGLSLVectorHomogCoord(int count) {
+ static const char* HOMOGS[] = {"ERROR", "", ".y", ".z", ".w"};
+ GrAssert(count >= 1 && count < (int)GR_ARRAY_COUNT(HOMOGS));
+ return HOMOGS[count];
+}
+
+const char* GrGLSLVectorHomogCoord(GrSLType type) {
+ return GrGLSLVectorHomogCoord(GrSLTypeToVecLength(type));
+}
+
+const char* GrGLSLVectorNonhomogCoords(int count) {
+ static const char* NONHOMOGS[] = {"ERROR", "", ".x", ".xy", ".xyz"};
+ GrAssert(count >= 1 && count < (int)GR_ARRAY_COUNT(NONHOMOGS));
+ return NONHOMOGS[count];
+}
+
+const char* GrGLSLVectorNonhomogCoords(GrSLType type) {
+ return GrGLSLVectorNonhomogCoords(GrSLTypeToVecLength(type));
+}
+
+namespace {
+ void append_tabs(SkString* outAppend, int tabCnt) {
+ static const char kTabs[] = "\t\t\t\t\t\t\t\t";
+ while (tabCnt) {
+ int cnt = GrMin((int)GR_ARRAY_COUNT(kTabs), tabCnt);
+ outAppend->append(kTabs, cnt);
+ tabCnt -= cnt;
+ }
+ }
+}
+
+GrSLConstantVec GrGLSLMulVarBy4f(SkString* outAppend,
+ int tabCnt,
+ const char* vec4VarName,
+ const char* mulFactor,
+ GrSLConstantVec mulFactorDefault) {
+ bool haveFactor = NULL != mulFactor && '\0' != *mulFactor;
+
+ GrAssert(NULL != outAppend);
+ GrAssert(NULL != vec4VarName);
+ GrAssert(kNone_GrSLConstantVec != mulFactorDefault || haveFactor);
+
+ if (!haveFactor) {
+ if (kOnes_GrSLConstantVec == mulFactorDefault) {
+ return kNone_GrSLConstantVec;
+ } else {
+ GrAssert(kZeros_GrSLConstantVec == mulFactorDefault);
+ append_tabs(outAppend, tabCnt);
+ outAppend->appendf("%s = vec4(0, 0, 0, 0);\n", vec4VarName);
+ return kZeros_GrSLConstantVec;
+ }
+ }
+ append_tabs(outAppend, tabCnt);
+ outAppend->appendf("%s *= %s;\n", vec4VarName, mulFactor);
+ return kNone_GrSLConstantVec;
+}
+
+GrSLConstantVec GrGLSLGetComponent4f(SkString* outAppend,
+ const char* expr,
+ GrColorComponentFlags component,
+ GrSLConstantVec defaultExpr,
+ bool omitIfConst) {
+ if (NULL == expr || '\0' == *expr) {
+ GrAssert(defaultExpr != kNone_GrSLConstantVec);
+ if (!omitIfConst) {
+ if (kOnes_GrSLConstantVec == defaultExpr) {
+ outAppend->append("1.0");
+ } else {
+ GrAssert(kZeros_GrSLConstantVec == defaultExpr);
+ outAppend->append("0.0");
+ }
+ }
+ return defaultExpr;
+ } else {
+ outAppend->appendf("(%s).%c", expr, GrColorComponentFlagToChar(component));
+ return kNone_GrSLConstantVec;
+ }
+}
diff --git a/gpu/gl/GrGLSL.h b/gpu/gl/GrGLSL.h
new file mode 100644
index 00000000..883cc9b4
--- /dev/null
+++ b/gpu/gl/GrGLSL.h
@@ -0,0 +1,232 @@
+/*
+ * 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 GrGLSL_DEFINED
+#define GrGLSL_DEFINED
+
+#include "gl/GrGLInterface.h"
+#include "GrColor.h"
+#include "GrTypesPriv.h"
+
+class GrGLShaderVar;
+class SkString;
+
+// Limited set of GLSL versions we build shaders for. Caller should round
+// down the GLSL version to one of these enums.
+enum GrGLSLGeneration {
+ /**
+ * Desktop GLSL 1.10 and ES2 shading language (based on desktop GLSL 1.20)
+ */
+ k110_GrGLSLGeneration,
+ /**
+ * Desktop GLSL 1.30
+ */
+ k130_GrGLSLGeneration,
+ /**
+ * Desktop GLSL 1.40
+ */
+ k140_GrGLSLGeneration,
+ /**
+ * Desktop GLSL 1.50
+ */
+ k150_GrGLSLGeneration,
+};
+
+enum GrSLConstantVec {
+ kZeros_GrSLConstantVec,
+ kOnes_GrSLConstantVec,
+ kNone_GrSLConstantVec,
+};
+
+namespace {
+static inline int GrSLTypeToVecLength(GrSLType type) {
+ static const int kVecLengths[] = {
+ 0, // kVoid_GrSLType
+ 1, // kFloat_GrSLType
+ 2, // kVec2f_GrSLType
+ 3, // kVec3f_GrSLType
+ 4, // kVec4f_GrSLType
+ 1, // kMat33f_GrSLType
+ 1, // kMat44f_GrSLType
+ 1, // kSampler2D_GrSLType
+ };
+ GR_STATIC_ASSERT(kGrSLTypeCount == GR_ARRAY_COUNT(kVecLengths));
+ return kVecLengths[type];
+}
+
+static inline const char* GrGLSLOnesVecf(int count) {
+ static const char* kONESVEC[] = {"ERROR", "1.0", "vec2(1,1)",
+ "vec3(1,1,1)", "vec4(1,1,1,1)"};
+ GrAssert(count >= 1 && count < (int)GR_ARRAY_COUNT(kONESVEC));
+ return kONESVEC[count];
+}
+
+static inline const char* GrGLSLZerosVecf(int count) {
+ static const char* kZEROSVEC[] = {"ERROR", "0.0", "vec2(0,0)",
+ "vec3(0,0,0)", "vec4(0,0,0,0)"};
+ GrAssert(count >= 1 && count < (int)GR_ARRAY_COUNT(kZEROSVEC));
+ return kZEROSVEC[count];
+}
+}
+
+/**
+ * Gets the most recent GLSL Generation compatible with the OpenGL context.
+ */
+GrGLSLGeneration GrGetGLSLGeneration(GrGLBinding binding,
+ const GrGLInterface* gl);
+
+/**
+ * Returns a string to include at the beginning of a shader to declare the GLSL
+ * version.
+ */
+const char* GrGetGLSLVersionDecl(GrGLBinding binding,
+ GrGLSLGeneration v);
+
+/**
+ * Depending on the GLSL version being emitted there may be an assumed output
+ * variable from the fragment shader for the color. Otherwise, the shader must
+ * declare an output variable for the color. If this function returns true:
+ * * Parameter var's name will be set to nameIfDeclared
+ * * The variable must be declared in the fragment shader
+ * * The variable has to be bound as the color output
+ * (using glBindFragDataLocation)
+ * If the function returns false:
+ * * Parameter var's name will be set to the GLSL built-in color output name.
+ * * Do not declare the variable in the shader.
+ * * Do not use glBindFragDataLocation to bind the variable
+ * In either case var is initialized to represent the color output in the
+ * shader.
+ */
+bool GrGLSLSetupFSColorOuput(GrGLSLGeneration gen,
+ const char* nameIfDeclared,
+ GrGLShaderVar* var);
+/**
+ * Converts a GrSLType to a string containing the name of the equivalent GLSL type.
+ */
+static const char* GrGLSLTypeString(GrSLType t) {
+ switch (t) {
+ case kVoid_GrSLType:
+ return "void";
+ case kFloat_GrSLType:
+ return "float";
+ case kVec2f_GrSLType:
+ return "vec2";
+ case kVec3f_GrSLType:
+ return "vec3";
+ case kVec4f_GrSLType:
+ return "vec4";
+ case kMat33f_GrSLType:
+ return "mat3";
+ case kMat44f_GrSLType:
+ return "mat4";
+ case kSampler2D_GrSLType:
+ return "sampler2D";
+ default:
+ GrCrash("Unknown shader var type.");
+ return ""; // suppress warning
+ }
+}
+
+/** Return the type enum for a vector of floats of length n (1..4),
+ e.g. 1 -> "float", 2 -> "vec2", ... */
+static inline const char* GrGLSLFloatVectorTypeString(int n) {
+ return GrGLSLTypeString(GrSLFloatVectorType(n));
+}
+
+/** Return the GLSL swizzle operator for a homogenous component of a vector
+ with the given number of coordinates, e.g. 2 -> ".y", 3 -> ".z" */
+const char* GrGLSLVectorHomogCoord(int count);
+const char* GrGLSLVectorHomogCoord(GrSLType type);
+
+/** Return the GLSL swizzle operator for a nonhomogenous components of a vector
+ with the given number of coordinates, e.g. 2 -> ".x", 3 -> ".xy" */
+const char* GrGLSLVectorNonhomogCoords(int count);
+const char* GrGLSLVectorNonhomogCoords(GrSLType type);
+
+/**
+ * Produces a string that is the result of modulating two inputs. The inputs must be vecN or
+ * float. The result is always a vecN. The inputs may be expressions, not just identifier names.
+ * Either can be NULL or "" in which case the default params control whether a vector of ones or
+ * zeros. It is an error to pass kNone for default<i> if in<i> is NULL or "". Note that when the
+ * function determines that the result is a zeros or ones vec then any expression represented by
+ * or in1 will not be emitted (side effects won't occur). The return value indicates whether a
+ * known zeros or ones vector resulted. The output can be suppressed when known vector is produced
+ * by passing true for omitIfConstVec.
+ */
+template <int N>
+GrSLConstantVec GrGLSLModulatef(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0 = kOnes_GrSLConstantVec,
+ GrSLConstantVec default1 = kOnes_GrSLConstantVec,
+ bool omitIfConstVec = false);
+
+/**
+ * Produces a string that is the result of adding two inputs. The inputs must be vecN or
+ * float. The result is always a vecN. The inputs may be expressions, not just identifier names.
+ * Either can be NULL or "" in which case the default params control whether a vector of ones or
+ * zeros. It is an error to pass kNone for default<i> if in<i> is NULL or "". Note that when the
+ * function determines that the result is a zeros or ones vec then any expression represented by
+ * or in1 will not be emitted (side effects won't occur). The return value indicates whether a
+ * known zeros or ones vector resulted. The output can be suppressed when known vector is produced
+ * by passing true for omitIfConstVec.
+ */
+template <int N>
+GrSLConstantVec GrGLSLAddf(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0 = kZeros_GrSLConstantVec,
+ GrSLConstantVec default1 = kZeros_GrSLConstantVec,
+ bool omitIfConstVec = false);
+
+/**
+ * Produces a string that is the result of subtracting two inputs. The inputs must be vecN or
+ * float. The result is always a vecN. The inputs may be expressions, not just identifier names.
+ * Either can be NULL or "" in which case the default params control whether a vector of ones or
+ * zeros. It is an error to pass kNone for default<i> if in<i> is NULL or "". Note that when the
+ * function determines that the result is a zeros or ones vec then any expression represented by
+ * or in1 will not be emitted (side effects won't occur). The return value indicates whether a
+ * known zeros or ones vector resulted. The output can be suppressed when known vector is produced
+ * by passing true for omitIfConstVec.
+ */
+template <int N>
+GrSLConstantVec GrGLSLSubtractf(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0 = kZeros_GrSLConstantVec,
+ GrSLConstantVec default1 = kZeros_GrSLConstantVec,
+ bool omitIfConstVec = false);
+
+/**
+ * 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 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 GrGLEffect-generated lines.) If a zeros vec is assigned then the return value is
+ * kZeros, otherwise kNone.
+ */
+GrSLConstantVec GrGLSLMulVarBy4f(SkString* outAppend,
+ int tabCnt,
+ const char* vec4VarName,
+ const char* mulFactor,
+ GrSLConstantVec mulFactorDefault = kOnes_GrSLConstantVec);
+
+/**
+ * Given an expression that evaluates to a GLSL vec4, extract a component. If expr is NULL or ""
+ * the value of defaultExpr is used. It is an error to pass an empty expr and have set defaultExpr
+ * to kNone. The return value indicates whether the value is known to be 0 or 1. If omitIfConst is
+ * set then nothing is appended when the return is not kNone.
+ */
+GrSLConstantVec GrGLSLGetComponent4f(SkString* outAppend,
+ const char* expr,
+ GrColorComponentFlags component,
+ GrSLConstantVec defaultExpr = kNone_GrSLConstantVec,
+ bool omitIfConst = false);
+
+#include "GrGLSL_impl.h"
+
+#endif
diff --git a/gpu/gl/GrGLSL_impl.h b/gpu/gl/GrGLSL_impl.h
new file mode 100644
index 00000000..3940926a
--- /dev/null
+++ b/gpu/gl/GrGLSL_impl.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLSL_impl_DEFINED
+#define GrGLSL_impl_DEFINED
+
+#include "SkString.h"
+
+namespace {
+template<int N>
+GrSLConstantVec return_const_vecf(GrSLConstantVec constVec, SkString* outAppend, bool omitAppend) {
+ GrAssert(kNone_GrSLConstantVec != constVec);
+ if (!omitAppend) {
+ if (kZeros_GrSLConstantVec == constVec) {
+ outAppend->append(GrGLSLZerosVecf(N));
+ } else {
+ outAppend->append(GrGLSLOnesVecf(N));
+ }
+ }
+ return constVec;
+}
+}
+
+template <int N>
+GrSLConstantVec GrGLSLModulatef(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0,
+ GrSLConstantVec default1,
+ bool omitIfConstVec) {
+ GrAssert(N > 0 && N <= 4);
+ GrAssert(NULL != outAppend);
+
+ bool has0 = NULL != in0 && '\0' != *in0;
+ bool has1 = NULL != in1 && '\0' != *in1;
+
+ GrAssert(has0 || kNone_GrSLConstantVec != default0);
+ GrAssert(has1 || kNone_GrSLConstantVec != default1);
+
+ if (!has0 && !has1) {
+ GrAssert(kZeros_GrSLConstantVec == default0 || kOnes_GrSLConstantVec == default0);
+ GrAssert(kZeros_GrSLConstantVec == default1 || kOnes_GrSLConstantVec == default1);
+ if (kZeros_GrSLConstantVec == default0 || kZeros_GrSLConstantVec == default1) {
+ return return_const_vecf<N>(kZeros_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else {
+ // both inputs are ones vectors
+ return return_const_vecf<N>(kOnes_GrSLConstantVec, outAppend, omitIfConstVec);
+ }
+ } else if (!has0) {
+ GrAssert(kZeros_GrSLConstantVec == default0 || kOnes_GrSLConstantVec == default0);
+ if (kZeros_GrSLConstantVec == default0) {
+ return return_const_vecf<N>(kZeros_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else {
+ outAppend->appendf("%s(%s)", GrGLSLFloatVectorTypeString(N), in1);
+ return kNone_GrSLConstantVec;
+ }
+ } else if (!has1) {
+ GrAssert(kZeros_GrSLConstantVec == default1 || kOnes_GrSLConstantVec == default1);
+ if (kZeros_GrSLConstantVec == default1) {
+ return return_const_vecf<N>(kZeros_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else {
+ outAppend->appendf("%s(%s)", GrGLSLFloatVectorTypeString(N), in0);
+ return kNone_GrSLConstantVec;
+ }
+ } else {
+ outAppend->appendf("%s((%s) * (%s))", GrGLSLFloatVectorTypeString(N), in0, in1);
+ return kNone_GrSLConstantVec;
+ }
+}
+
+template <int N>
+GrSLConstantVec GrGLSLAddf(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0,
+ GrSLConstantVec default1,
+ bool omitIfConstVec) {
+ GrAssert(N > 0 && N <= 4);
+ GrAssert(NULL != outAppend);
+
+ bool has0 = NULL != in0 && '\0' != *in0;
+ bool has1 = NULL != in1 && '\0' != *in1;
+
+ if (!has0 && !has1) {
+ GrAssert(kNone_GrSLConstantVec != default0);
+ GrAssert(kNone_GrSLConstantVec != default1);
+ int sum = (kOnes_GrSLConstantVec == default0) + (kOnes_GrSLConstantVec == default1);
+ if (0 == sum) {
+ return return_const_vecf<N>(kZeros_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else if (1 == sum) {
+ outAppend->append(GrGLSLOnesVecf(N));
+ return return_const_vecf<N>(kOnes_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else {
+ GrAssert(2 == sum);
+ outAppend->appendf("%s(2)", GrGLSLFloatVectorTypeString(N));
+ return kNone_GrSLConstantVec;
+ }
+ } else if (!has0) {
+ GrAssert(kNone_GrSLConstantVec != default0);
+ if (kZeros_GrSLConstantVec == default0) {
+ outAppend->appendf("%s(%s)", GrGLSLFloatVectorTypeString(N), in1);
+ } else {
+ outAppend->appendf("%s(%s) + %s",
+ GrGLSLFloatVectorTypeString(N),
+ in1,
+ GrGLSLOnesVecf(N));
+ }
+ return kNone_GrSLConstantVec;
+ } else if (!has1) {
+ GrAssert(kNone_GrSLConstantVec != default1);
+ if (kZeros_GrSLConstantVec == default1) {
+ outAppend->appendf("%s(%s)", GrGLSLFloatVectorTypeString(N), in0);
+ } else {
+ outAppend->appendf("%s(%s) + %s",
+ GrGLSLFloatVectorTypeString(N),
+ in0,
+ GrGLSLOnesVecf(N));
+ }
+ return kNone_GrSLConstantVec;
+ } else {
+ outAppend->appendf("(%s(%s) + %s(%s))",
+ GrGLSLFloatVectorTypeString(N),
+ in0,
+ GrGLSLFloatVectorTypeString(N),
+ in1);
+ return kNone_GrSLConstantVec;
+ }
+}
+
+template <int N>
+GrSLConstantVec GrGLSLSubtractf(SkString* outAppend,
+ const char* in0,
+ const char* in1,
+ GrSLConstantVec default0,
+ GrSLConstantVec default1,
+ bool omitIfConstVec) {
+ GrAssert(N > 0 && N <= 4);
+ GrAssert(NULL != outAppend);
+
+ bool has0 = NULL != in0 && '\0' != *in0;
+ bool has1 = NULL != in1 && '\0' != *in1;
+
+ if (!has0 && !has1) {
+ GrAssert(kNone_GrSLConstantVec != default0);
+ GrAssert(kNone_GrSLConstantVec != default1);
+ int diff = (kOnes_GrSLConstantVec == default0) - (kOnes_GrSLConstantVec == default1);
+ if (-1 == diff) {
+ outAppend->appendf("%s(-1)", GrGLSLFloatVectorTypeString(N));
+ return kNone_GrSLConstantVec;
+ } else if (0 == diff) {
+ return return_const_vecf<N>(kZeros_GrSLConstantVec, outAppend, omitIfConstVec);
+ } else {
+ GrAssert(1 == diff);
+ return return_const_vecf<N>(kOnes_GrSLConstantVec, outAppend, omitIfConstVec);
+ }
+ } else if (!has0) {
+ GrAssert(kNone_GrSLConstantVec != default0);
+ if (kZeros_GrSLConstantVec == default0) {
+ outAppend->appendf("-%s(%s)", GrGLSLFloatVectorTypeString(N), in1);
+ } else {
+ outAppend->appendf("%s - %s(%s)",
+ GrGLSLOnesVecf(N),
+ GrGLSLFloatVectorTypeString(N),
+ in1);
+ }
+ return kNone_GrSLConstantVec;
+ } else if (!has1) {
+ GrAssert(kNone_GrSLConstantVec != default1);
+ if (kZeros_GrSLConstantVec == default1) {
+ outAppend->appendf("%s(%s)", GrGLSLFloatVectorTypeString(N), in0);
+ } else {
+ outAppend->appendf("%s(%s) - %s",
+ GrGLSLFloatVectorTypeString(N),
+ in0,
+ GrGLSLOnesVecf(N));
+ }
+ return kNone_GrSLConstantVec;
+ } else {
+ outAppend->appendf("(%s(%s) - %s(%s))",
+ GrGLSLFloatVectorTypeString(N),
+ in0,
+ GrGLSLFloatVectorTypeString(N),
+ in1);
+ return kNone_GrSLConstantVec;
+ }
+}
+
+#endif
diff --git a/gpu/gl/GrGLShaderBuilder.cpp b/gpu/gl/GrGLShaderBuilder.cpp
new file mode 100644
index 00000000..36eb6d51
--- /dev/null
+++ b/gpu/gl/GrGLShaderBuilder.cpp
@@ -0,0 +1,736 @@
+/*
+ * 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/GrGLShaderBuilder.h"
+#include "gl/GrGLProgram.h"
+#include "gl/GrGLUniformHandle.h"
+#include "GrDrawEffect.h"
+#include "GrTexture.h"
+
+// number of each input/output type in a single allocation block
+static const int kVarsPerBlock = 8;
+
+// except FS outputs where we expect 2 at most.
+static const int kMaxFSOutputs = 2;
+
+// ES2 FS only guarantees mediump and lowp support
+static const GrGLShaderVar::Precision kDefaultFragmentPrecision = GrGLShaderVar::kMedium_Precision;
+
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+inline const char* sample_function_name(GrSLType type, GrGLSLGeneration glslGen) {
+ if (kVec2f_GrSLType == type) {
+ return glslGen >= k130_GrGLSLGeneration ? "texture" : "texture2D";
+ } else {
+ GrAssert(kVec3f_GrSLType == type);
+ return glslGen >= k130_GrGLSLGeneration ? "textureProj" : "texture2DProj";
+ }
+}
+
+/**
+ * Do we need to either map r,g,b->a or a->r. configComponentMask indicates which channels are
+ * present in the texture's config. swizzleComponentMask indicates the channels present in the
+ * shader swizzle.
+ */
+inline bool swizzle_requires_alpha_remapping(const GrGLCaps& caps,
+ uint32_t configComponentMask,
+ uint32_t swizzleComponentMask) {
+ if (caps.textureSwizzleSupport()) {
+ // Any remapping is handled using texture swizzling not shader modifications.
+ return false;
+ }
+ // check if the texture is alpha-only
+ if (kA_GrColorComponentFlag == configComponentMask) {
+ if (caps.textureRedSupport() && (kA_GrColorComponentFlag & swizzleComponentMask)) {
+ // we must map the swizzle 'a's to 'r'.
+ return true;
+ }
+ if (kRGB_GrColorComponentFlags & swizzleComponentMask) {
+ // The 'r', 'g', and/or 'b's must be mapped to 'a' according to our semantics that
+ // alpha-only textures smear alpha across all four channels when read.
+ return true;
+ }
+ }
+ return false;
+}
+
+void append_swizzle(SkString* outAppend,
+ const GrGLShaderBuilder::TextureSampler& texSampler,
+ const GrGLCaps& caps) {
+ const char* swizzle = texSampler.swizzle();
+ char mangledSwizzle[5];
+
+ // The swizzling occurs using texture params instead of shader-mangling if ARB_texture_swizzle
+ // is available.
+ if (!caps.textureSwizzleSupport() &&
+ (kA_GrColorComponentFlag == texSampler.configComponentMask())) {
+ char alphaChar = caps.textureRedSupport() ? 'r' : 'a';
+ int i;
+ for (i = 0; '\0' != swizzle[i]; ++i) {
+ mangledSwizzle[i] = alphaChar;
+ }
+ mangledSwizzle[i] ='\0';
+ swizzle = mangledSwizzle;
+ }
+ // For shader prettiness we omit the swizzle rather than appending ".rgba".
+ if (memcmp(swizzle, "rgba", 4)) {
+ outAppend->appendf(".%s", swizzle);
+ }
+}
+
+}
+
+static const char kDstCopyColorName[] = "_dstColor";
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLShaderBuilder::GrGLShaderBuilder(const GrGLContextInfo& ctxInfo,
+ GrGLUniformManager& uniformManager,
+ const GrGLProgramDesc& desc)
+ : fUniforms(kVarsPerBlock)
+ , fVSAttrs(kVarsPerBlock)
+ , fVSOutputs(kVarsPerBlock)
+ , fGSInputs(kVarsPerBlock)
+ , fGSOutputs(kVarsPerBlock)
+ , fFSInputs(kVarsPerBlock)
+ , fFSOutputs(kMaxFSOutputs)
+ , fCtxInfo(ctxInfo)
+ , fUniformManager(uniformManager)
+ , fFSFeaturesAddedMask(0)
+#if GR_GL_EXPERIMENTAL_GS
+ , fUsesGS(SkToBool(desc.getHeader().fExperimentalGS))
+#else
+ , fUsesGS(false)
+#endif
+ , fSetupFragPosition(false)
+ , fRTHeightUniform(GrGLUniformManager::kInvalidUniformHandle)
+ , fDstCopyTopLeftUniform (GrGLUniformManager::kInvalidUniformHandle)
+ , fDstCopyScaleUniform (GrGLUniformManager::kInvalidUniformHandle)
+ , fTopLeftFragPosRead(kTopLeftFragPosRead_FragPosKey == desc.getHeader().fFragPosKey) {
+
+ const GrGLProgramDesc::KeyHeader& header = desc.getHeader();
+
+ fPositionVar = &fVSAttrs.push_back();
+ fPositionVar->set(kVec2f_GrSLType, GrGLShaderVar::kAttribute_TypeModifier, "aPosition");
+ if (-1 != header.fLocalCoordAttributeIndex) {
+ fLocalCoordsVar = &fVSAttrs.push_back();
+ fLocalCoordsVar->set(kVec2f_GrSLType,
+ GrGLShaderVar::kAttribute_TypeModifier,
+ "aLocalCoords");
+ } else {
+ fLocalCoordsVar = fPositionVar;
+ }
+ // Emit code to read the dst copy textue if necessary.
+ if (kNoDstRead_DstReadKey != header.fDstReadKey &&
+ GrGLCaps::kNone_FBFetchType == ctxInfo.caps()->fbFetchType()) {
+ bool topDown = SkToBool(kTopLeftOrigin_DstReadKeyBit & header.fDstReadKey);
+ const char* dstCopyTopLeftName;
+ const char* dstCopyCoordScaleName;
+ uint32_t configMask;
+ if (SkToBool(kUseAlphaConfig_DstReadKeyBit & header.fDstReadKey)) {
+ configMask = kA_GrColorComponentFlag;
+ } else {
+ configMask = kRGBA_GrColorComponentFlags;
+ }
+ fDstCopySampler.init(this, configMask, "rgba", 0);
+
+ fDstCopyTopLeftUniform = this->addUniform(kFragment_ShaderType,
+ kVec2f_GrSLType,
+ "DstCopyUpperLeft",
+ &dstCopyTopLeftName);
+ fDstCopyScaleUniform = this->addUniform(kFragment_ShaderType,
+ kVec2f_GrSLType,
+ "DstCopyCoordScale",
+ &dstCopyCoordScaleName);
+ const char* fragPos = this->fragmentPosition();
+ this->fsCodeAppend("\t// Read color from copy of the destination.\n");
+ this->fsCodeAppendf("\tvec2 _dstTexCoord = (%s.xy - %s) * %s;\n",
+ fragPos, dstCopyTopLeftName, dstCopyCoordScaleName);
+ if (!topDown) {
+ this->fsCodeAppend("\t_dstTexCoord.y = 1.0 - _dstTexCoord.y;\n");
+ }
+ this->fsCodeAppendf("\tvec4 %s = ", kDstCopyColorName);
+ this->appendTextureLookup(kFragment_ShaderType, fDstCopySampler, "_dstTexCoord");
+ this->fsCodeAppend(";\n\n");
+ }
+}
+
+bool GrGLShaderBuilder::enableFeature(GLSLFeature feature) {
+ switch (feature) {
+ case kStandardDerivatives_GLSLFeature:
+ if (!fCtxInfo.caps()->shaderDerivativeSupport()) {
+ return false;
+ }
+ if (kES2_GrGLBinding == fCtxInfo.binding()) {
+ this->addFSFeature(1 << kStandardDerivatives_GLSLFeature,
+ "GL_OES_standard_derivatives");
+ }
+ return true;
+ default:
+ GrCrash("Unexpected GLSLFeature requested.");
+ return false;
+ }
+}
+
+bool GrGLShaderBuilder::enablePrivateFeature(GLSLPrivateFeature feature) {
+ switch (feature) {
+ case kFragCoordConventions_GLSLPrivateFeature:
+ if (!fCtxInfo.caps()->fragCoordConventionsSupport()) {
+ return false;
+ }
+ if (fCtxInfo.glslGeneration() < k150_GrGLSLGeneration) {
+ this->addFSFeature(1 << kFragCoordConventions_GLSLPrivateFeature,
+ "GL_ARB_fragment_coord_conventions");
+ }
+ return true;
+ case kEXTShaderFramebufferFetch_GLSLPrivateFeature:
+ if (GrGLCaps::kEXT_FBFetchType != fCtxInfo.caps()->fbFetchType()) {
+ return false;
+ }
+ this->addFSFeature(1 << kEXTShaderFramebufferFetch_GLSLPrivateFeature,
+ "GL_EXT_shader_framebuffer_fetch");
+ return true;
+ case kNVShaderFramebufferFetch_GLSLPrivateFeature:
+ if (GrGLCaps::kNV_FBFetchType != fCtxInfo.caps()->fbFetchType()) {
+ return false;
+ }
+ this->addFSFeature(1 << kNVShaderFramebufferFetch_GLSLPrivateFeature,
+ "GL_NV_shader_framebuffer_fetch");
+ return true;
+ default:
+ GrCrash("Unexpected GLSLPrivateFeature requested.");
+ return false;
+ }
+}
+
+void GrGLShaderBuilder::addFSFeature(uint32_t featureBit, const char* extensionName) {
+ if (!(featureBit & fFSFeaturesAddedMask)) {
+ fFSExtensions.appendf("#extension %s: require\n", extensionName);
+ fFSFeaturesAddedMask |= featureBit;
+ }
+}
+
+void GrGLShaderBuilder::nameVariable(SkString* out, char prefix, const char* name) {
+ if ('\0' == prefix) {
+ *out = name;
+ } else {
+ out->printf("%c%s", prefix, name);
+ }
+ if (fCodeStage.inStageCode()) {
+ if (out->endsWith('_')) {
+ // Names containing "__" are reserved.
+ out->append("x");
+ }
+ out->appendf("_Stage%d", fCodeStage.stageIndex());
+ }
+}
+
+const char* GrGLShaderBuilder::dstColor() {
+ if (fCodeStage.inStageCode()) {
+ const GrEffectRef& effect = *fCodeStage.effectStage()->getEffect();
+ if (!effect->willReadDstColor()) {
+ GrDebugCrash("GrGLEffect asked for dst color but its generating GrEffect "
+ "did not request access.");
+ return "";
+ }
+ }
+ static const char kFBFetchColorName[] = "gl_LastFragData[0]";
+ GrGLCaps::FBFetchType fetchType = fCtxInfo.caps()->fbFetchType();
+ if (GrGLCaps::kEXT_FBFetchType == fetchType) {
+ SkAssertResult(this->enablePrivateFeature(kEXTShaderFramebufferFetch_GLSLPrivateFeature));
+ return kFBFetchColorName;
+ } else if (GrGLCaps::kNV_FBFetchType == fetchType) {
+ SkAssertResult(this->enablePrivateFeature(kNVShaderFramebufferFetch_GLSLPrivateFeature));
+ return kFBFetchColorName;
+ } else if (fDstCopySampler.isInitialized()) {
+ return kDstCopyColorName;
+ } else {
+ return "";
+ }
+}
+
+void GrGLShaderBuilder::codeAppendf(ShaderType type, const char format[], va_list args) {
+ SkString* string = NULL;
+ switch (type) {
+ case kVertex_ShaderType:
+ string = &fVSCode;
+ break;
+ case kGeometry_ShaderType:
+ string = &fGSCode;
+ break;
+ case kFragment_ShaderType:
+ string = &fFSCode;
+ break;
+ default:
+ GrCrash("Invalid shader type");
+ }
+ string->appendf(format, args);
+}
+
+void GrGLShaderBuilder::codeAppend(ShaderType type, const char* str) {
+ SkString* string = NULL;
+ switch (type) {
+ case kVertex_ShaderType:
+ string = &fVSCode;
+ break;
+ case kGeometry_ShaderType:
+ string = &fGSCode;
+ break;
+ case kFragment_ShaderType:
+ string = &fFSCode;
+ break;
+ default:
+ GrCrash("Invalid shader type");
+ }
+ string->append(str);
+}
+
+void GrGLShaderBuilder::appendTextureLookup(SkString* out,
+ const GrGLShaderBuilder::TextureSampler& sampler,
+ const char* coordName,
+ GrSLType varyingType) const {
+ GrAssert(NULL != coordName);
+
+ out->appendf("%s(%s, %s)",
+ sample_function_name(varyingType, fCtxInfo.glslGeneration()),
+ this->getUniformCStr(sampler.fSamplerUniform),
+ coordName);
+ append_swizzle(out, sampler, *fCtxInfo.caps());
+}
+
+void GrGLShaderBuilder::appendTextureLookup(ShaderType type,
+ const GrGLShaderBuilder::TextureSampler& sampler,
+ const char* coordName,
+ GrSLType varyingType) {
+ GrAssert(kFragment_ShaderType == type);
+ this->appendTextureLookup(&fFSCode, sampler, coordName, varyingType);
+}
+
+void GrGLShaderBuilder::appendTextureLookupAndModulate(
+ ShaderType type,
+ const char* modulation,
+ const GrGLShaderBuilder::TextureSampler& sampler,
+ const char* coordName,
+ GrSLType varyingType) {
+ GrAssert(kFragment_ShaderType == type);
+ SkString lookup;
+ this->appendTextureLookup(&lookup, sampler, coordName, varyingType);
+ GrGLSLModulatef<4>(&fFSCode, modulation, lookup.c_str());
+}
+
+GrBackendEffectFactory::EffectKey GrGLShaderBuilder::KeyForTextureAccess(
+ const GrTextureAccess& access,
+ const GrGLCaps& caps) {
+ uint32_t configComponentMask = GrPixelConfigComponentMask(access.getTexture()->config());
+ if (swizzle_requires_alpha_remapping(caps, configComponentMask, access.swizzleMask())) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+GrGLShaderBuilder::DstReadKey GrGLShaderBuilder::KeyForDstRead(const GrTexture* dstCopy,
+ const GrGLCaps& caps) {
+ uint32_t key = kYesDstRead_DstReadKeyBit;
+ if (GrGLCaps::kNone_FBFetchType != caps.fbFetchType()) {
+ return key;
+ }
+ GrAssert(NULL != dstCopy);
+ if (!caps.textureSwizzleSupport() && GrPixelConfigIsAlphaOnly(dstCopy->config())) {
+ // The fact that the config is alpha-only must be considered when generating code.
+ key |= kUseAlphaConfig_DstReadKeyBit;
+ }
+ if (kTopLeft_GrSurfaceOrigin == dstCopy->origin()) {
+ key |= kTopLeftOrigin_DstReadKeyBit;
+ }
+ GrAssert(static_cast<DstReadKey>(key) == key);
+ return static_cast<DstReadKey>(key);
+}
+
+GrGLShaderBuilder::FragPosKey GrGLShaderBuilder::KeyForFragmentPosition(const GrRenderTarget* dst,
+ const GrGLCaps&) {
+ if (kTopLeft_GrSurfaceOrigin == dst->origin()) {
+ return kTopLeftFragPosRead_FragPosKey;
+ } else {
+ return kBottomLeftFragPosRead_FragPosKey;
+ }
+}
+
+
+const GrGLenum* GrGLShaderBuilder::GetTexParamSwizzle(GrPixelConfig config, const GrGLCaps& caps) {
+ if (caps.textureSwizzleSupport() && GrPixelConfigIsAlphaOnly(config)) {
+ if (caps.textureRedSupport()) {
+ static const GrGLenum gRedSmear[] = { GR_GL_RED, GR_GL_RED, GR_GL_RED, GR_GL_RED };
+ return gRedSmear;
+ } else {
+ static const GrGLenum gAlphaSmear[] = { GR_GL_ALPHA, GR_GL_ALPHA,
+ GR_GL_ALPHA, GR_GL_ALPHA };
+ return gAlphaSmear;
+ }
+ } else {
+ static const GrGLenum gStraight[] = { GR_GL_RED, GR_GL_GREEN, GR_GL_BLUE, GR_GL_ALPHA };
+ return gStraight;
+ }
+}
+
+GrGLUniformManager::UniformHandle GrGLShaderBuilder::addUniformArray(uint32_t visibility,
+ GrSLType type,
+ const char* name,
+ int count,
+ const char** outName) {
+ GrAssert(name && strlen(name));
+ SkDEBUGCODE(static const uint32_t kVisibilityMask = kVertex_ShaderType | kFragment_ShaderType);
+ GrAssert(0 == (~kVisibilityMask & visibility));
+ GrAssert(0 != visibility);
+
+ BuilderUniform& uni = fUniforms.push_back();
+ UniformHandle h = index_to_handle(fUniforms.count() - 1);
+ GR_DEBUGCODE(UniformHandle h2 =)
+ fUniformManager.appendUniform(type, count);
+ // We expect the uniform manager to initially have no uniforms and that all uniforms are added
+ // by this function. Therefore, the handles should match.
+ GrAssert(h2 == h);
+ uni.fVariable.setType(type);
+ uni.fVariable.setTypeModifier(GrGLShaderVar::kUniform_TypeModifier);
+ this->nameVariable(uni.fVariable.accessName(), 'u', name);
+ uni.fVariable.setArrayCount(count);
+ uni.fVisibility = visibility;
+
+ // If it is visible in both the VS and FS, the precision must match.
+ // We declare a default FS precision, but not a default VS. So set the var
+ // to use the default FS precision.
+ if ((kVertex_ShaderType | kFragment_ShaderType) == visibility) {
+ // the fragment and vertex precisions must match
+ uni.fVariable.setPrecision(kDefaultFragmentPrecision);
+ }
+
+ if (NULL != outName) {
+ *outName = uni.fVariable.c_str();
+ }
+
+ return h;
+}
+
+const GrGLShaderVar& GrGLShaderBuilder::getUniformVariable(UniformHandle u) const {
+ return fUniforms[handle_to_index(u)].fVariable;
+}
+
+bool GrGLShaderBuilder::addAttribute(GrSLType type,
+ const char* name) {
+ for (int i = 0; i < fVSAttrs.count(); ++i) {
+ const GrGLShaderVar& attr = fVSAttrs[i];
+ // if attribute already added, don't add it again
+ if (attr.getName().equals(name)) {
+ GrAssert(attr.getType() == type);
+ return false;
+ }
+ }
+ fVSAttrs.push_back().set(type,
+ GrGLShaderVar::kAttribute_TypeModifier,
+ name);
+ return true;
+}
+
+void GrGLShaderBuilder::addVarying(GrSLType type,
+ const char* name,
+ const char** vsOutName,
+ const char** fsInName) {
+ fVSOutputs.push_back();
+ fVSOutputs.back().setType(type);
+ fVSOutputs.back().setTypeModifier(GrGLShaderVar::kVaryingOut_TypeModifier);
+ this->nameVariable(fVSOutputs.back().accessName(), 'v', name);
+
+ if (vsOutName) {
+ *vsOutName = fVSOutputs.back().getName().c_str();
+ }
+ // input to FS comes either from VS or GS
+ const SkString* fsName;
+ if (fUsesGS) {
+ // if we have a GS take each varying in as an array
+ // and output as non-array.
+ fGSInputs.push_back();
+ fGSInputs.back().setType(type);
+ fGSInputs.back().setTypeModifier(GrGLShaderVar::kVaryingIn_TypeModifier);
+ fGSInputs.back().setUnsizedArray();
+ *fGSInputs.back().accessName() = fVSOutputs.back().getName();
+ fGSOutputs.push_back();
+ fGSOutputs.back().setType(type);
+ fGSOutputs.back().setTypeModifier(GrGLShaderVar::kVaryingOut_TypeModifier);
+ this->nameVariable(fGSOutputs.back().accessName(), 'g', name);
+ fsName = fGSOutputs.back().accessName();
+ } else {
+ fsName = fVSOutputs.back().accessName();
+ }
+ fFSInputs.push_back();
+ fFSInputs.back().setType(type);
+ fFSInputs.back().setTypeModifier(GrGLShaderVar::kVaryingIn_TypeModifier);
+ fFSInputs.back().setName(*fsName);
+ if (fsInName) {
+ *fsInName = fsName->c_str();
+ }
+}
+
+const char* GrGLShaderBuilder::fragmentPosition() {
+ if (fCodeStage.inStageCode()) {
+ const GrEffectRef& effect = *fCodeStage.effectStage()->getEffect();
+ if (!effect->willReadFragmentPosition()) {
+ GrDebugCrash("GrGLEffect asked for frag position but its generating GrEffect "
+ "did not request access.");
+ return "";
+ }
+ }
+ if (fTopLeftFragPosRead) {
+ if (!fSetupFragPosition) {
+ fFSInputs.push_back().set(kVec4f_GrSLType,
+ GrGLShaderVar::kIn_TypeModifier,
+ "gl_FragCoord",
+ GrGLShaderVar::kDefault_Precision);
+ fSetupFragPosition = true;
+ }
+ return "gl_FragCoord";
+ } else if (fCtxInfo.caps()->fragCoordConventionsSupport()) {
+ if (!fSetupFragPosition) {
+ SkAssertResult(this->enablePrivateFeature(kFragCoordConventions_GLSLPrivateFeature));
+ 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) {
+ // temporarily change the stage index because we're inserting non-stage code.
+ CodeStage::AutoStageRestore csar(&fCodeStage, NULL);
+
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fRTHeightUniform);
+ const char* rtHeightName;
+
+ fRTHeightUniform = this->addUniform(kFragment_ShaderType,
+ kFloat_GrSLType,
+ "RTHeight",
+ &rtHeightName);
+
+ 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,
+ int argCnt,
+ const GrGLShaderVar* args,
+ const char* body,
+ SkString* outName) {
+ GrAssert(kFragment_ShaderType == shader);
+ fFSFunctions.append(GrGLSLTypeString(returnType));
+ this->nameVariable(outName, '\0', name);
+ fFSFunctions.appendf(" %s", outName->c_str());
+ fFSFunctions.append("(");
+ for (int i = 0; i < argCnt; ++i) {
+ args[i].appendDecl(fCtxInfo, &fFSFunctions);
+ if (i < argCnt - 1) {
+ fFSFunctions.append(", ");
+ }
+ }
+ fFSFunctions.append(") {\n");
+ fFSFunctions.append(body);
+ fFSFunctions.append("}\n\n");
+}
+
+namespace {
+
+inline void append_default_precision_qualifier(GrGLShaderVar::Precision p,
+ GrGLBinding binding,
+ SkString* str) {
+ // Desktop GLSL has added precision qualifiers but they don't do anything.
+ if (kES2_GrGLBinding == binding) {
+ switch (p) {
+ case GrGLShaderVar::kHigh_Precision:
+ str->append("precision highp float;\n");
+ break;
+ case GrGLShaderVar::kMedium_Precision:
+ str->append("precision mediump float;\n");
+ break;
+ case GrGLShaderVar::kLow_Precision:
+ str->append("precision lowp float;\n");
+ break;
+ case GrGLShaderVar::kDefault_Precision:
+ GrCrash("Default precision now allowed.");
+ default:
+ GrCrash("Unknown precision value.");
+ }
+ }
+}
+}
+
+void GrGLShaderBuilder::appendDecls(const VarArray& vars, SkString* out) const {
+ for (int i = 0; i < vars.count(); ++i) {
+ vars[i].appendDecl(fCtxInfo, out);
+ out->append(";\n");
+ }
+}
+
+void GrGLShaderBuilder::appendUniformDecls(ShaderType stype, SkString* out) const {
+ for (int i = 0; i < fUniforms.count(); ++i) {
+ if (fUniforms[i].fVisibility & stype) {
+ fUniforms[i].fVariable.appendDecl(fCtxInfo, out);
+ out->append(";\n");
+ }
+ }
+}
+
+void GrGLShaderBuilder::getShader(ShaderType type, SkString* shaderStr) const {
+ const char* version = GrGetGLSLVersionDecl(fCtxInfo.binding(), fCtxInfo.glslGeneration());
+
+ switch (type) {
+ case kVertex_ShaderType:
+ *shaderStr = version;
+ 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) {
+ *shaderStr = version;
+ 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();
+ }
+ break;
+ case kFragment_ShaderType:
+ *shaderStr = version;
+ shaderStr->append(fFSExtensions);
+ append_default_precision_qualifier(kDefaultFragmentPrecision,
+ fCtxInfo.binding(),
+ shaderStr);
+ this->appendUniformDecls(kFragment_ShaderType, shaderStr);
+ this->appendDecls(fFSInputs, shaderStr);
+ // We shouldn't have declared outputs on 1.10
+ GrAssert(k110_GrGLSLGeneration != fCtxInfo.glslGeneration() || fFSOutputs.empty());
+ this->appendDecls(fFSOutputs, shaderStr);
+ shaderStr->append(fFSFunctions);
+ shaderStr->append("void main() {\n");
+ shaderStr->append(fFSCode);
+ shaderStr->append("}\n");
+ break;
+ }
+ }
+
+void GrGLShaderBuilder::finished(GrGLuint programID) {
+ fUniformManager.getUniformLocations(programID, fUniforms);
+}
+
+void GrGLShaderBuilder::emitEffects(
+ const GrEffectStage* effectStages[],
+ const GrBackendEffectFactory::EffectKey effectKeys[],
+ int effectCnt,
+ SkString* fsInOutColor,
+ GrSLConstantVec* fsInOutColorKnownValue,
+ SkTArray<GrGLUniformManager::UniformHandle, true>* effectSamplerHandles[],
+ GrGLEffect* glEffects[]) {
+ bool effectEmitted = false;
+
+ SkString inColor = *fsInOutColor;
+ SkString outColor;
+
+ for (int e = 0; e < effectCnt; ++e) {
+ GrAssert(NULL != effectStages[e] && NULL != effectStages[e]->getEffect());
+ const GrEffectStage& stage = *effectStages[e];
+ const GrEffectRef& effect = *stage.getEffect();
+
+ CodeStage::AutoStageRestore csar(&fCodeStage, &stage);
+
+ int numTextures = effect->numTextures();
+ SkSTArray<8, GrGLShaderBuilder::TextureSampler> textureSamplers;
+ textureSamplers.push_back_n(numTextures);
+ for (int t = 0; t < numTextures; ++t) {
+ textureSamplers[t].init(this, &effect->textureAccess(t), t);
+ effectSamplerHandles[e]->push_back(textureSamplers[t].fSamplerUniform);
+ }
+ GrDrawEffect drawEffect(stage, this->hasExplicitLocalCoords());
+
+ int numAttributes = stage.getVertexAttribIndexCount();
+ const int* attributeIndices = stage.getVertexAttribIndices();
+ SkSTArray<GrEffect::kMaxVertexAttribs, SkString> attributeNames;
+ for (int a = 0; a < numAttributes; ++a) {
+ // TODO: Make addAttribute mangle the name.
+ SkString attributeName("aAttr");
+ attributeName.appendS32(attributeIndices[a]);
+ if (this->addAttribute(effect->vertexAttribType(a), attributeName.c_str())) {
+ fEffectAttributes.push_back().set(attributeIndices[a], attributeName);
+ }
+ }
+
+ glEffects[e] = effect->getFactory().createGLInstance(drawEffect);
+
+ if (kZeros_GrSLConstantVec == *fsInOutColorKnownValue) {
+ // Effects have no way to communicate zeros, they treat an empty string as ones.
+ this->nameVariable(&inColor, '\0', "input");
+ this->fsCodeAppendf("\tvec4 %s = %s;\n", inColor.c_str(), GrGLSLZerosVecf(4));
+ }
+
+ // create var to hold stage result
+ this->nameVariable(&outColor, '\0', "output");
+ this->fsCodeAppendf("\tvec4 %s;\n", outColor.c_str());
+
+ // Enclose custom code in a block to avoid namespace conflicts
+ SkString openBrace;
+ openBrace.printf("\t{ // Stage %d: %s\n", fCodeStage.stageIndex(), glEffects[e]->name());
+ this->fVSCode.append(openBrace);
+ this->fFSCode.append(openBrace);
+
+ glEffects[e]->emitCode(this,
+ drawEffect,
+ effectKeys[e],
+ outColor.c_str(),
+ inColor.isEmpty() ? NULL : inColor.c_str(),
+ textureSamplers);
+ this->fVSCode.append("\t}\n");
+ this->fFSCode.append("\t}\n");
+
+ inColor = outColor;
+ *fsInOutColorKnownValue = kNone_GrSLConstantVec;
+ effectEmitted = true;
+ }
+
+ if (effectEmitted) {
+ *fsInOutColor = outColor;
+ }
+}
+
+const SkString* GrGLShaderBuilder::getEffectAttributeName(int attributeIndex) const {
+ const AttributePair* attribEnd = this->getEffectAttributes().end();
+ for (const AttributePair* attrib = this->getEffectAttributes().begin();
+ attrib != attribEnd;
+ ++attrib) {
+ if (attrib->fIndex == attributeIndex) {
+ return &attrib->fName;
+ }
+ }
+
+ return NULL;
+}
diff --git a/gpu/gl/GrGLShaderBuilder.h b/gpu/gl/GrGLShaderBuilder.h
new file mode 100644
index 00000000..b67846a6
--- /dev/null
+++ b/gpu/gl/GrGLShaderBuilder.h
@@ -0,0 +1,467 @@
+/*
+ * 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 GrGLShaderBuilder_DEFINED
+#define GrGLShaderBuilder_DEFINED
+
+#include "GrAllocator.h"
+#include "GrBackendEffectFactory.h"
+#include "GrColor.h"
+#include "GrEffect.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLUniformManager.h"
+
+#include <stdarg.h>
+
+class GrGLContextInfo;
+class GrEffectStage;
+class GrGLProgramDesc;
+
+/**
+ Contains all the incremental state of a shader as it is being built,as well as helpers to
+ manipulate that state.
+*/
+class GrGLShaderBuilder {
+public:
+ /**
+ * Passed to GrGLEffects to add texture reads to their shader code.
+ */
+ class TextureSampler {
+ public:
+ TextureSampler()
+ : fConfigComponentMask(0)
+ , fSamplerUniform(GrGLUniformManager::kInvalidUniformHandle) {
+ // we will memcpy the first 4 bytes from passed in swizzle. This ensures the string is
+ // terminated.
+ fSwizzle[4] = '\0';
+ }
+
+ TextureSampler(const TextureSampler& other) { *this = other; }
+
+ TextureSampler& operator= (const TextureSampler& other) {
+ GrAssert(0 == fConfigComponentMask);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fSamplerUniform);
+
+ fConfigComponentMask = other.fConfigComponentMask;
+ fSamplerUniform = other.fSamplerUniform;
+ return *this;
+ }
+
+ // bitfield of GrColorComponentFlags present in the texture's config.
+ uint32_t configComponentMask() const { return fConfigComponentMask; }
+
+ const char* swizzle() const { return fSwizzle; }
+
+ bool isInitialized() const { return 0 != fConfigComponentMask; }
+
+ private:
+ // The idx param is used to ensure multiple samplers within a single effect have unique
+ // uniform names. swizzle is a four char max string made up of chars 'r', 'g', 'b', and 'a'.
+ void init(GrGLShaderBuilder* builder,
+ uint32_t configComponentMask,
+ const char* swizzle,
+ int idx) {
+ GrAssert(!this->isInitialized());
+ GrAssert(0 != configComponentMask);
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle == fSamplerUniform);
+
+ GrAssert(NULL != builder);
+ SkString name;
+ name.printf("Sampler%d", idx);
+ fSamplerUniform = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kSampler2D_GrSLType,
+ name.c_str());
+ GrAssert(GrGLUniformManager::kInvalidUniformHandle != fSamplerUniform);
+
+ fConfigComponentMask = configComponentMask;
+ memcpy(fSwizzle, swizzle, 4);
+ }
+
+ void init(GrGLShaderBuilder* builder, const GrTextureAccess* access, int idx) {
+ GrAssert(NULL != access);
+ this->init(builder,
+ GrPixelConfigComponentMask(access->getTexture()->config()),
+ access->getSwizzle(),
+ idx);
+ }
+
+ uint32_t fConfigComponentMask;
+ char fSwizzle[5];
+ GrGLUniformManager::UniformHandle fSamplerUniform;
+
+ friend class GrGLShaderBuilder; // to call init().
+ };
+
+ typedef SkTArray<TextureSampler> TextureSamplerArray;
+
+ enum ShaderType {
+ kVertex_ShaderType = 0x1,
+ kGeometry_ShaderType = 0x2,
+ kFragment_ShaderType = 0x4,
+ };
+
+ GrGLShaderBuilder(const GrGLContextInfo&, GrGLUniformManager&, const GrGLProgramDesc&);
+
+ /**
+ * Use of these features may require a GLSL extension to be enabled. Shaders may not compile
+ * if code is added that uses one of these features without calling enableFeature()
+ */
+ enum GLSLFeature {
+ kStandardDerivatives_GLSLFeature = 0,
+
+ kLastGLSLFeature = kStandardDerivatives_GLSLFeature
+ };
+
+ /**
+ * If the feature is supported then true is returned and any necessary #extension declarations
+ * are added to the shaders. If the feature is not supported then false will be returned.
+ */
+ bool enableFeature(GLSLFeature);
+
+ /**
+ * Called by GrGLEffects to add code to one of the shaders.
+ */
+ void vsCodeAppendf(const char format[], ...) SK_PRINTF_LIKE(2, 3) {
+ va_list args;
+ va_start(args, format);
+ this->codeAppendf(kVertex_ShaderType, format, args);
+ va_end(args);
+ }
+
+ void gsCodeAppendf(const char format[], ...) SK_PRINTF_LIKE(2, 3) {
+ va_list args;
+ va_start(args, format);
+ this->codeAppendf(kGeometry_ShaderType, format, args);
+ va_end(args);
+ }
+
+ void fsCodeAppendf(const char format[], ...) SK_PRINTF_LIKE(2, 3) {
+ va_list args;
+ va_start(args, format);
+ this->codeAppendf(kFragment_ShaderType, format, args);
+ va_end(args);
+ }
+
+ void vsCodeAppend(const char* str) { this->codeAppend(kVertex_ShaderType, str); }
+ void gsCodeAppend(const char* str) { this->codeAppend(kGeometry_ShaderType, str); }
+ void fsCodeAppend(const char* str) { this->codeAppend(kFragment_ShaderType, str); }
+
+ /** 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,
+ GrSLType coordType = kVec2f_GrSLType) const;
+
+ /** Version of above that appends the result to the shader code rather than an SkString.
+ Currently the shader type must be kFragment */
+ void appendTextureLookup(ShaderType,
+ const TextureSampler&,
+ const char* coordName,
+ GrSLType coordType = kVec2f_GrSLType);
+
+
+ /** Does the work of appendTextureLookup and modulates the result by modulation. The result is
+ always a vec4. modulation and the swizzle specified by TextureSampler must both be vec4 or
+ float. If modulation is "" or NULL it this function acts as though appendTextureLookup were
+ called. */
+ void appendTextureLookupAndModulate(ShaderType,
+ const char* modulation,
+ const TextureSampler&,
+ const char* coordName,
+ GrSLType coordType = kVec2f_GrSLType);
+
+ /** Emits a helper function outside of main(). Currently ShaderType must be
+ kFragment_ShaderType. */
+ void emitFunction(ShaderType shader,
+ GrSLType returnType,
+ const char* name,
+ int argCnt,
+ const GrGLShaderVar* args,
+ const char* body,
+ SkString* outName);
+
+ /** 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 GrBackendEffectFactory::EffectKey KeyForTextureAccess(const GrTextureAccess&,
+ const GrGLCaps&);
+
+ typedef uint8_t DstReadKey;
+ typedef uint8_t FragPosKey;
+
+ /** Returns a key for adding code to read the copy-of-dst color in service of effects that
+ require reading the dst. It must not return 0 because 0 indicates that there is no dst
+ copy read at all (in which case this function should not be called). */
+ static DstReadKey KeyForDstRead(const GrTexture* dstCopy, const GrGLCaps&);
+
+ /** Returns a key for reading the fragment location. This should only be called if there is an
+ effect that will requires the fragment position. If the fragment position is not required,
+ the key is 0. */
+ static FragPosKey KeyForFragmentPosition(const GrRenderTarget* dst, 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 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
+ it will refer to the final uniform name after return. Use the addUniformArray variant to add
+ an array of uniforms.
+ */
+ GrGLUniformManager::UniformHandle addUniform(uint32_t visibility,
+ GrSLType type,
+ const char* name,
+ const char** outName = NULL) {
+ return this->addUniformArray(visibility, type, name, GrGLShaderVar::kNonArray, outName);
+ }
+ GrGLUniformManager::UniformHandle addUniformArray(uint32_t visibility,
+ GrSLType type,
+ const char* name,
+ int arrayCount,
+ const char** outName = NULL);
+
+ const GrGLShaderVar& getUniformVariable(GrGLUniformManager::UniformHandle) const;
+
+ /**
+ * Shortcut for getUniformVariable(u).c_str()
+ */
+ const char* getUniformCStr(GrGLUniformManager::UniformHandle u) const {
+ return this->getUniformVariable(u).c_str();
+ }
+
+ /** Add a vertex attribute to the current program that is passed in from the vertex data.
+ Returns false if the attribute was already there, true otherwise. */
+ bool addAttribute(GrSLType type, const char* name);
+
+ /** Add a varying variable to the current program to pass values between vertex and fragment
+ shaders. If the last two parameters are non-NULL, they are filled in with the name
+ generated. */
+ void addVarying(GrSLType type,
+ const char* name,
+ 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; }
+
+ /** Returns a vertex attribute that represents the local coords in the VS. This may be the same
+ as positionAttribute() or it may not be. It depends upon whether the rendering code
+ specified explicit local coords or not in the GrDrawState. */
+ const GrGLShaderVar& localCoordsAttribute() const { return *fLocalCoordsVar; }
+
+ /** Returns the color of the destination pixel. This may be NULL if no effect advertised
+ that it will read the destination. */
+ const char* dstColor();
+
+ /**
+ * Are explicit local coordinates provided as input to the vertex shader.
+ */
+ bool hasExplicitLocalCoords() const { return (fLocalCoordsVar != fPositionVar); }
+
+ /**
+ * Interfaces used by GrGLProgram.
+ * TODO: Hide these from the GrEffects using friend or splitting this into two related classes.
+ * Also, GrGLProgram's shader string construction should be moved to this class.
+ */
+
+ /** Called after building is complete to get the final shader string. */
+ void getShader(ShaderType, SkString*) const;
+
+ /**
+ * Adds code for effects. effectStages contains the effects to add. effectKeys[i] is the key
+ * generated from effectStages[i]. An entry in effectStages can be NULL, in which case it is
+ * skipped. Moreover, if the corresponding key is GrGLEffect::NoEffectKey then it is skipped.
+ * inOutFSColor specifies the input color to the first stage and is updated to be the
+ * output color of the last stage. fsInOutColorKnownValue specifies whether the input color
+ * has a known constant value and is updated to refer to the status of the output color.
+ * The handles to texture samplers for effectStage[i] are added to effectSamplerHandles[i]. The
+ * glEffects array is updated to contain the GrGLEffect generated for each entry in
+ * effectStages.
+ */
+ void emitEffects(const GrEffectStage* effectStages[],
+ const GrBackendEffectFactory::EffectKey effectKeys[],
+ int effectCnt,
+ SkString* inOutFSColor,
+ GrSLConstantVec* fsInOutColorKnownValue,
+ SkTArray<GrGLUniformManager::UniformHandle, true>* effectSamplerHandles[],
+ GrGLEffect* glEffects[]);
+
+ GrGLUniformManager::UniformHandle getRTHeightUniform() const { return fRTHeightUniform; }
+ GrGLUniformManager::UniformHandle getDstCopyTopLeftUniform() const {
+ return fDstCopyTopLeftUniform;
+ }
+ GrGLUniformManager::UniformHandle getDstCopyScaleUniform() const {
+ return fDstCopyScaleUniform;
+ }
+ GrGLUniformManager::UniformHandle getDstCopySamplerUniform() const {
+ return fDstCopySampler.fSamplerUniform;
+ }
+
+ struct AttributePair {
+ void set(int index, const SkString& name) {
+ fIndex = index; fName = name;
+ }
+ int fIndex;
+ SkString fName;
+ };
+ const SkTArray<AttributePair, true>& getEffectAttributes() const {
+ return fEffectAttributes;
+ }
+ const SkString* getEffectAttributeName(int attributeIndex) const;
+
+ // TODO: Make this do all the compiling, linking, etc.
+ void finished(GrGLuint programID);
+
+ const GrGLContextInfo& ctxInfo() const { return fCtxInfo; }
+
+private:
+ void codeAppendf(ShaderType type, const char format[], va_list args);
+ void codeAppend(ShaderType type, const char* str);
+
+ typedef GrTAllocator<GrGLShaderVar> VarArray;
+
+ void appendDecls(const VarArray&, SkString*) const;
+ void appendUniformDecls(ShaderType, SkString*) const;
+
+ typedef GrGLUniformManager::BuilderUniform BuilderUniform;
+ GrGLUniformManager::BuilderUniformArray fUniforms;
+
+ // TODO: Everything below here private.
+public:
+
+ VarArray fVSAttrs;
+ VarArray fVSOutputs;
+ VarArray fGSInputs;
+ VarArray fGSOutputs;
+ VarArray fFSInputs;
+ SkString fGSHeader; // layout qualifiers specific to GS
+ VarArray fFSOutputs;
+
+private:
+ class CodeStage : GrNoncopyable {
+ public:
+ CodeStage() : fNextIndex(0), fCurrentIndex(-1), fEffectStage(NULL) {}
+
+ bool inStageCode() const {
+ this->validate();
+ return NULL != fEffectStage;
+ }
+
+ const GrEffectStage* effectStage() const {
+ this->validate();
+ return fEffectStage;
+ }
+
+ int stageIndex() const {
+ this->validate();
+ return fCurrentIndex;
+ }
+
+ class AutoStageRestore : GrNoncopyable {
+ public:
+ AutoStageRestore(CodeStage* codeStage, const GrEffectStage* newStage) {
+ GrAssert(NULL != codeStage);
+ fSavedIndex = codeStage->fCurrentIndex;
+ fSavedEffectStage = codeStage->fEffectStage;
+
+ if (NULL == newStage) {
+ codeStage->fCurrentIndex = -1;
+ } else {
+ codeStage->fCurrentIndex = codeStage->fNextIndex++;
+ }
+ codeStage->fEffectStage = newStage;
+
+ fCodeStage = codeStage;
+ }
+ ~AutoStageRestore() {
+ fCodeStage->fCurrentIndex = fSavedIndex;
+ fCodeStage->fEffectStage = fSavedEffectStage;
+ }
+ private:
+ CodeStage* fCodeStage;
+ int fSavedIndex;
+ const GrEffectStage* fSavedEffectStage;
+ };
+ private:
+ void validate() const { GrAssert((NULL == fEffectStage) == (-1 == fCurrentIndex)); }
+ int fNextIndex;
+ int fCurrentIndex;
+ const GrEffectStage* fEffectStage;
+ } fCodeStage;
+
+ /**
+ * Features that should only be enabled by GrGLShaderBuilder itself.
+ */
+ enum GLSLPrivateFeature {
+ kFragCoordConventions_GLSLPrivateFeature = kLastGLSLFeature + 1,
+ kEXTShaderFramebufferFetch_GLSLPrivateFeature,
+ kNVShaderFramebufferFetch_GLSLPrivateFeature,
+ };
+ bool enablePrivateFeature(GLSLPrivateFeature);
+
+ // If we ever have VS/GS features we can expand this to take a bitmask of ShaderType and track
+ // the enables separately for each shader.
+ void addFSFeature(uint32_t featureBit, const char* extensionName);
+
+ // Generates a name for a variable. The generated string will be name prefixed by the prefix
+ // char (unless the prefix is '\0'). It also mangles the name to be stage-specific if we're
+ // generating stage code.
+ void nameVariable(SkString* out, char prefix, const char* name);
+
+ // Interpretation of DstReadKey when generating code
+ enum {
+ kNoDstRead_DstReadKey = 0,
+ kYesDstRead_DstReadKeyBit = 0x1, // Set if we do a dst-copy-read.
+ kUseAlphaConfig_DstReadKeyBit = 0x2, // Set if dst-copy config is alpha only.
+ kTopLeftOrigin_DstReadKeyBit = 0x4, // Set if dst-copy origin is top-left.
+ };
+
+ enum {
+ kNoFragPosRead_FragPosKey = 0, // The fragment positition will not be needed.
+ kTopLeftFragPosRead_FragPosKey = 0x1,// Read frag pos relative to top-left.
+ kBottomLeftFragPosRead_FragPosKey = 0x2,// Read frag pos relative to bottom-left.
+ };
+
+ const GrGLContextInfo& fCtxInfo;
+ GrGLUniformManager& fUniformManager;
+ uint32_t fFSFeaturesAddedMask;
+ SkString fFSFunctions;
+ SkString fFSExtensions;
+
+ bool fUsesGS;
+
+ SkString fFSCode;
+ SkString fVSCode;
+ SkString fGSCode;
+
+ bool fSetupFragPosition;
+ TextureSampler fDstCopySampler;
+
+ GrGLUniformManager::UniformHandle fRTHeightUniform;
+ GrGLUniformManager::UniformHandle fDstCopyTopLeftUniform;
+ GrGLUniformManager::UniformHandle fDstCopyScaleUniform;
+
+ bool fTopLeftFragPosRead;
+
+ SkSTArray<10, AttributePair, true> fEffectAttributes;
+
+ GrGLShaderVar* fPositionVar;
+ GrGLShaderVar* fLocalCoordsVar;
+
+};
+
+#endif
diff --git a/gpu/gl/GrGLShaderVar.h b/gpu/gl/GrGLShaderVar.h
new file mode 100644
index 00000000..e03ed3e5
--- /dev/null
+++ b/gpu/gl/GrGLShaderVar.h
@@ -0,0 +1,360 @@
+/*
+ * 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 GrGLShaderVar_DEFINED
+#define GrGLShaderVar_DEFINED
+
+#include "GrGLContext.h"
+#include "GrGLSL.h"
+#include "SkString.h"
+
+#define USE_UNIFORM_FLOAT_ARRAYS true
+
+/**
+ * Represents a variable in a shader
+ */
+class GrGLShaderVar {
+public:
+
+ /**
+ * Early versions of GLSL have Varying and Attribute; those are later
+ * deprecated, but we still need to know whether a Varying variable
+ * should be treated as In or Out.
+ */
+ enum TypeModifier {
+ kNone_TypeModifier,
+ kOut_TypeModifier,
+ kIn_TypeModifier,
+ kInOut_TypeModifier,
+ kUniform_TypeModifier,
+ kAttribute_TypeModifier,
+ kVaryingIn_TypeModifier,
+ kVaryingOut_TypeModifier
+ };
+
+ enum Precision {
+ kLow_Precision, // lowp
+ kMedium_Precision, // mediump
+ kHigh_Precision, // highp
+ kDefault_Precision, // Default for the current context. We make
+ // fragment shaders default to mediump on ES2
+ // because highp support is not guaranteed (and
+ // we haven't been motivated to test for it).
+ // Otherwise, highp.
+ };
+
+ /**
+ * 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() {
+ fType = kFloat_GrSLType;
+ fTypeModifier = kNone_TypeModifier;
+ fCount = kNonArray;
+ fPrecision = kDefault_Precision;
+ fOrigin = kDefault_Origin;
+ fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
+ }
+
+ GrGLShaderVar(const char* name, GrSLType type, int arrayCount = kNonArray,
+ Precision precision = kDefault_Precision) {
+ GrAssert(kVoid_GrSLType != type);
+ fType = type;
+ fTypeModifier = kNone_TypeModifier;
+ fCount = arrayCount;
+ fPrecision = precision;
+ fOrigin = kDefault_Origin;
+ fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
+ fName = name;
+ }
+
+ GrGLShaderVar(const GrGLShaderVar& var)
+ : fType(var.fType)
+ , fTypeModifier(var.fTypeModifier)
+ , fName(var.fName)
+ , fCount(var.fCount)
+ , fPrecision(var.fPrecision)
+ , fOrigin(var.fOrigin)
+ , fUseUniformFloatArrays(var.fUseUniformFloatArrays) {
+ GrAssert(kVoid_GrSLType != var.fType);
+ }
+
+ /**
+ * Values for array count that have special meaning. We allow 1-sized arrays.
+ */
+ enum {
+ kNonArray = 0, // not an array
+ kUnsizedArray = -1, // an unsized array (declared with [])
+ };
+
+ /**
+ * Sets as a non-array.
+ */
+ void set(GrSLType type,
+ 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;
+ fTypeModifier = typeModifier;
+ fName = name;
+ fCount = kNonArray;
+ fPrecision = precision;
+ fOrigin = origin;
+ fUseUniformFloatArrays = useUniformFloatArrays;
+ }
+
+ /**
+ * Sets as a non-array.
+ */
+ void set(GrSLType type,
+ 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;
+ fTypeModifier = typeModifier;
+ fName = name;
+ fCount = kNonArray;
+ fPrecision = precision;
+ fOrigin = origin;
+ fUseUniformFloatArrays = useUniformFloatArrays;
+ }
+
+ /**
+ * Set all var options
+ */
+ void set(GrSLType type,
+ TypeModifier typeModifier,
+ 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;
+ fTypeModifier = typeModifier;
+ fName = name;
+ fCount = count;
+ fPrecision = precision;
+ fOrigin = origin;
+ fUseUniformFloatArrays = useUniformFloatArrays;
+ }
+
+ /**
+ * Set all var options
+ */
+ void set(GrSLType type,
+ TypeModifier typeModifier,
+ 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;
+ fTypeModifier = typeModifier;
+ fName = name;
+ fCount = count;
+ fPrecision = precision;
+ fOrigin = origin;
+ fUseUniformFloatArrays = useUniformFloatArrays;
+ }
+
+ /**
+ * Is the var an array.
+ */
+ bool isArray() const { return kNonArray != fCount; }
+ /**
+ * Is this an unsized array, (i.e. declared with []).
+ */
+ bool isUnsizedArray() const { return kUnsizedArray == fCount; }
+ /**
+ * Get the array length of the var.
+ */
+ int getArrayCount() const { return fCount; }
+ /**
+ * Set the array length of the var
+ */
+ void setArrayCount(int count) { fCount = count; }
+ /**
+ * Set to be a non-array.
+ */
+ void setNonArray() { fCount = kNonArray; }
+ /**
+ * Set to be an unsized array.
+ */
+ void setUnsizedArray() { fCount = kUnsizedArray; }
+
+ /**
+ * Access the var name as a writable string
+ */
+ SkString* accessName() { return &fName; }
+ /**
+ * Set the var name
+ */
+ void setName(const SkString& n) { fName = n; }
+ void setName(const char* n) { fName = n; }
+
+ /**
+ * Get the var name.
+ */
+ const SkString& getName() const { return fName; }
+
+ /**
+ * Shortcut for this->getName().c_str();
+ */
+ const char* c_str() const { return this->getName().c_str(); }
+
+ /**
+ * Get the type of the var
+ */
+ GrSLType getType() const { return fType; }
+ /**
+ * Set the type of the var
+ */
+ void setType(GrSLType type) { fType = type; }
+
+ TypeModifier getTypeModifier() const { return fTypeModifier; }
+ void setTypeModifier(TypeModifier type) { fTypeModifier = type; }
+
+ /**
+ * Get the precision of the var
+ */
+ Precision getPrecision() const { return fPrecision; }
+
+ /**
+ * Set the precision of the var
+ */
+ 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& ctxInfo, 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(),
+ ctxInfo.glslGeneration()));
+ out->append(" ");
+ }
+ out->append(PrecisionString(fPrecision, ctxInfo.binding()));
+ GrSLType effectiveType = this->getType();
+ if (this->isArray()) {
+ if (this->isUnsizedArray()) {
+ out->appendf("%s %s[]",
+ GrGLSLTypeString(effectiveType),
+ this->getName().c_str());
+ } else {
+ GrAssert(this->getArrayCount() > 0);
+ out->appendf("%s %s[%d]",
+ GrGLSLTypeString(effectiveType),
+ this->getName().c_str(),
+ this->getArrayCount());
+ }
+ } else {
+ out->appendf("%s %s",
+ GrGLSLTypeString(effectiveType),
+ this->getName().c_str());
+ }
+ }
+
+ void appendArrayAccess(int index, SkString* out) const {
+ out->appendf("%s[%d]%s",
+ this->getName().c_str(),
+ index,
+ fUseUniformFloatArrays ? "" : ".x");
+ }
+
+ void appendArrayAccess(const char* indexName, SkString* out) const {
+ out->appendf("%s[%s]%s",
+ this->getName().c_str(),
+ indexName,
+ fUseUniformFloatArrays ? "" : ".x");
+ }
+
+ static const char* PrecisionString(Precision p, GrGLBinding binding) {
+ // Desktop GLSL has added precision qualifiers but they don't do anything.
+ if (kES2_GrGLBinding == binding) {
+ switch (p) {
+ case kLow_Precision:
+ return "lowp ";
+ case kMedium_Precision:
+ return "mediump ";
+ case kHigh_Precision:
+ return "highp ";
+ case kDefault_Precision:
+ return "";
+ default:
+ GrCrash("Unexpected precision type.");
+ }
+ }
+ return "";
+ }
+
+private:
+ static const char* TypeModifierString(TypeModifier t, GrGLSLGeneration gen) {
+ switch (t) {
+ case kNone_TypeModifier:
+ return "";
+ case kIn_TypeModifier:
+ return "in";
+ case kInOut_TypeModifier:
+ return "inout";
+ case kOut_TypeModifier:
+ return "out";
+ case kUniform_TypeModifier:
+ return "uniform";
+ case kAttribute_TypeModifier:
+ return k110_GrGLSLGeneration == gen ? "attribute" : "in";
+ case kVaryingIn_TypeModifier:
+ return k110_GrGLSLGeneration == gen ? "varying" : "in";
+ case kVaryingOut_TypeModifier:
+ return k110_GrGLSLGeneration == gen ? "varying" : "out";
+ default:
+ GrCrash("Unknown shader variable type modifier.");
+ return ""; // suppress warning
+ }
+ }
+
+ GrSLType fType;
+ TypeModifier fTypeModifier;
+ SkString fName;
+ int fCount;
+ Precision fPrecision;
+ Origin fOrigin;
+ /// Work around driver bugs on some hardware that don't correctly
+ /// support uniform float []
+ bool fUseUniformFloatArrays;
+};
+
+#endif
diff --git a/gpu/gl/GrGLStencilBuffer.cpp b/gpu/gl/GrGLStencilBuffer.cpp
new file mode 100644
index 00000000..d9322c26
--- /dev/null
+++ b/gpu/gl/GrGLStencilBuffer.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "GrGLStencilBuffer.h"
+#include "GrGpuGL.h"
+
+GrGLStencilBuffer::~GrGLStencilBuffer() {
+ this->release();
+}
+
+size_t GrGLStencilBuffer::sizeInBytes() const {
+ uint64_t size = this->width();
+ size *= this->height();
+ size *= fFormat.fTotalBits;
+ size *= GrMax(1,this->numSamples());
+ return static_cast<size_t>(size / 8);
+}
+
+void GrGLStencilBuffer::onRelease() {
+ if (0 != fRenderbufferID && !this->isWrapped()) {
+ GrGpuGL* gpuGL = (GrGpuGL*) this->getGpu();
+ const GrGLInterface* gl = gpuGL->glInterface();
+ GR_GL_CALL(gl, DeleteRenderbuffers(1, &fRenderbufferID));
+ fRenderbufferID = 0;
+ }
+
+ INHERITED::onRelease();
+}
+
+void GrGLStencilBuffer::onAbandon() {
+ fRenderbufferID = 0;
+
+ INHERITED::onAbandon();
+}
diff --git a/gpu/gl/GrGLStencilBuffer.h b/gpu/gl/GrGLStencilBuffer.h
new file mode 100644
index 00000000..2bf33ef7
--- /dev/null
+++ b/gpu/gl/GrGLStencilBuffer.h
@@ -0,0 +1,62 @@
+/*
+ * 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 GrGLStencilBuffer_DEFINED
+#define GrGLStencilBuffer_DEFINED
+
+#include "gl/GrGLInterface.h"
+#include "GrStencilBuffer.h"
+
+class GrGLStencilBuffer : public GrStencilBuffer {
+public:
+ static const GrGLenum kUnknownInternalFormat = ~0U;
+ static const GrGLuint kUnknownBitCount = ~0U;
+ struct Format {
+ GrGLenum fInternalFormat;
+ GrGLuint fStencilBits;
+ GrGLuint fTotalBits;
+ bool fPacked;
+ };
+
+ GrGLStencilBuffer(GrGpu* gpu,
+ bool isWrapped,
+ GrGLint rbid,
+ int width, int height,
+ int sampleCnt,
+ const Format& format)
+ : GrStencilBuffer(gpu, isWrapped, width, height, format.fStencilBits, sampleCnt)
+ , fFormat(format)
+ , fRenderbufferID(rbid) {
+ }
+
+ virtual ~GrGLStencilBuffer();
+
+ virtual size_t sizeInBytes() const SK_OVERRIDE;
+
+ GrGLuint renderbufferID() const {
+ return fRenderbufferID;
+ }
+
+ const Format& format() const { return fFormat; }
+
+protected:
+ // overrides of GrResource
+ virtual void onRelease() SK_OVERRIDE;
+ virtual void onAbandon() SK_OVERRIDE;
+
+private:
+ Format fFormat;
+ // may be zero for external SBs associated with external RTs
+ // (we don't require the client to give us the id, just tell
+ // us how many bits of stencil there are).
+ GrGLuint fRenderbufferID;
+
+ typedef GrStencilBuffer INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLTexture.cpp b/gpu/gl/GrGLTexture.cpp
new file mode 100644
index 00000000..aee25bc0
--- /dev/null
+++ b/gpu/gl/GrGLTexture.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "GrGLTexture.h"
+#include "GrGpuGL.h"
+
+SK_DEFINE_INST_COUNT(GrGLTexID)
+
+#define GPUGL static_cast<GrGpuGL*>(getGpu())
+
+#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X)
+
+void GrGLTexture::init(GrGpuGL* gpu,
+ const Desc& textureDesc,
+ const GrGLRenderTarget::Desc* rtDesc) {
+
+ GrAssert(0 != textureDesc.fTextureID);
+
+ fTexParams.invalidate();
+ fTexParamsTimestamp = GrGpu::kExpiredTimestamp;
+ fTexIDObj.reset(SkNEW_ARGS(GrGLTexID, (GPUGL->glInterface(),
+ textureDesc.fTextureID,
+ textureDesc.fIsWrapped)));
+
+ if (NULL != rtDesc) {
+ GrGLIRect vp;
+ vp.fLeft = 0;
+ vp.fWidth = textureDesc.fWidth;
+ vp.fBottom = 0;
+ vp.fHeight = textureDesc.fHeight;
+
+ fRenderTarget.reset(SkNEW_ARGS(GrGLRenderTarget, (gpu, *rtDesc, vp, fTexIDObj, this)));
+ }
+}
+
+GrGLTexture::GrGLTexture(GrGpuGL* gpu,
+ const Desc& textureDesc)
+ : INHERITED(gpu, textureDesc.fIsWrapped, textureDesc) {
+ this->init(gpu, textureDesc, NULL);
+}
+
+GrGLTexture::GrGLTexture(GrGpuGL* gpu,
+ const Desc& textureDesc,
+ const GrGLRenderTarget::Desc& rtDesc)
+ : INHERITED(gpu, textureDesc.fIsWrapped, textureDesc) {
+ this->init(gpu, textureDesc, &rtDesc);
+}
+
+void GrGLTexture::onRelease() {
+ GPUGL->notifyTextureDelete(this);
+ fTexIDObj.reset(NULL);
+ INHERITED::onRelease();
+}
+
+void GrGLTexture::onAbandon() {
+ if (NULL != fTexIDObj.get()) {
+ fTexIDObj->abandon();
+ fTexIDObj.reset(NULL);
+ }
+
+ INHERITED::onAbandon();
+}
+
+GrBackendObject GrGLTexture::getTextureHandle() const {
+ return static_cast<GrBackendObject>(this->textureID());
+}
diff --git a/gpu/gl/GrGLTexture.h b/gpu/gl/GrGLTexture.h
new file mode 100644
index 00000000..e41ebc87
--- /dev/null
+++ b/gpu/gl/GrGLTexture.h
@@ -0,0 +1,111 @@
+/*
+ * 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 GrGLTexture_DEFINED
+#define GrGLTexture_DEFINED
+
+#include "GrGpu.h"
+#include "GrGLRenderTarget.h"
+
+/**
+ * A ref counted tex id that deletes the texture in its destructor.
+ */
+class GrGLTexID : public GrRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(GrGLTexID)
+
+ GrGLTexID(const GrGLInterface* gl, GrGLuint texID, bool isWrapped)
+ : fGL(gl)
+ , fTexID(texID)
+ , fIsWrapped(isWrapped) {
+ }
+
+ virtual ~GrGLTexID() {
+ if (0 != fTexID && !fIsWrapped) {
+ GR_GL_CALL(fGL, DeleteTextures(1, &fTexID));
+ }
+ }
+
+ void abandon() { fTexID = 0; }
+ GrGLuint id() const { return fTexID; }
+
+private:
+ const GrGLInterface* fGL;
+ GrGLuint fTexID;
+ bool fIsWrapped;
+
+ typedef GrRefCnt INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+class GrGLTexture : public GrTexture {
+
+public:
+ struct TexParams {
+ GrGLenum fMinFilter;
+ GrGLenum fMagFilter;
+ GrGLenum fWrapS;
+ GrGLenum fWrapT;
+ GrGLenum fSwizzleRGBA[4];
+ void invalidate() { memset(this, 0xff, sizeof(TexParams)); }
+ };
+
+ struct Desc : public GrTextureDesc {
+ GrGLuint fTextureID;
+ bool fIsWrapped;
+ };
+
+ // creates a texture that is also an RT
+ GrGLTexture(GrGpuGL* gpu,
+ const Desc& textureDesc,
+ const GrGLRenderTarget::Desc& rtDesc);
+
+ // creates a non-RT texture
+ GrGLTexture(GrGpuGL* gpu,
+ const Desc& textureDesc);
+
+ virtual ~GrGLTexture() { this->release(); }
+
+ virtual GrBackendObject getTextureHandle() const SK_OVERRIDE;
+
+ virtual void invalidateCachedState() SK_OVERRIDE { fTexParams.invalidate(); }
+
+ // These functions are used to track the texture parameters associated with the texture.
+ const TexParams& getCachedTexParams(GrGpu::ResetTimestamp* timestamp) const {
+ *timestamp = fTexParamsTimestamp;
+ return fTexParams;
+ }
+
+ void setCachedTexParams(const TexParams& texParams,
+ GrGpu::ResetTimestamp timestamp) {
+ fTexParams = texParams;
+ fTexParamsTimestamp = timestamp;
+ }
+
+ GrGLuint textureID() const { return (NULL != fTexIDObj.get()) ? fTexIDObj->id() : 0; }
+
+protected:
+ // overrides of GrTexture
+ virtual void onAbandon() SK_OVERRIDE;
+ virtual void onRelease() SK_OVERRIDE;
+
+private:
+ TexParams fTexParams;
+ GrGpu::ResetTimestamp fTexParamsTimestamp;
+ SkAutoTUnref<GrGLTexID> fTexIDObj;
+
+ void init(GrGpuGL* gpu,
+ const Desc& textureDesc,
+ const GrGLRenderTarget::Desc* rtDesc);
+
+ typedef GrTexture INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLUniformHandle.h b/gpu/gl/GrGLUniformHandle.h
new file mode 100644
index 00000000..231dd31c
--- /dev/null
+++ b/gpu/gl/GrGLUniformHandle.h
@@ -0,0 +1,16 @@
+/*
+ * 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 GrUniformHandle_DEFINED
+#define GrUniformHandle_DEFINED
+
+namespace {
+inline int handle_to_index(GrGLUniformManager::UniformHandle h) { return ~h; }
+inline GrGLUniformManager::UniformHandle index_to_handle(int i) { return ~i; }
+}
+
+#endif
diff --git a/gpu/gl/GrGLUniformManager.cpp b/gpu/gl/GrGLUniformManager.cpp
new file mode 100644
index 00000000..da6726bb
--- /dev/null
+++ b/gpu/gl/GrGLUniformManager.cpp
@@ -0,0 +1,269 @@
+/*
+ * 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/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 || \
+ (0 == offset && 1 == arrayCount && GrGLShaderVar::kNonArray == uni.fArrayCount))
+
+GrGLUniformManager::UniformHandle GrGLUniformManager::appendUniform(GrSLType type, int arrayCount) {
+ int idx = fUniforms.count();
+ Uniform& uni = fUniforms.push_back();
+ GrAssert(GrGLShaderVar::kNonArray == arrayCount || arrayCount > 0);
+ uni.fArrayCount = arrayCount;
+ uni.fType = type;
+ uni.fVSLocation = kUnusedUniform;
+ uni.fFSLocation = kUnusedUniform;
+ return index_to_handle(idx);
+}
+
+void GrGLUniformManager::setSampler(UniformHandle u, GrGLint texUnit) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kSampler2D_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ // FIXME: We still insert a single sampler uniform for every stage. If the shader does not
+ // reference the sampler then the compiler may have optimized it out. Uncomment this assert
+ // once stages insert their own samplers.
+ // GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1i(uni.fFSLocation, texUnit));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1i(uni.fVSLocation, texUnit));
+ }
+}
+
+void GrGLUniformManager::set1f(UniformHandle u, GrGLfloat v0) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kFloat_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1f(uni.fFSLocation, v0));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1f(uni.fVSLocation, v0));
+ }
+}
+
+void GrGLUniformManager::set1fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat v[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kFloat_GrSLType);
+ GrAssert(arrayCount > 0);
+ ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, offset, arrayCount);
+ // This assert fires in some instances of the two-pt gradient for its VSParams.
+ // Once the uniform manager is responsible for inserting the duplicate uniform
+ // arrays in VS and FS driver bug workaround, this can be enabled.
+ //GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1fv(uni.fFSLocation + offset, arrayCount, v));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform1fv(uni.fVSLocation + offset, arrayCount, v));
+ }
+}
+
+void GrGLUniformManager::set2f(UniformHandle u, GrGLfloat v0, GrGLfloat v1) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec2f_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform2f(uni.fFSLocation, v0, v1));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform2f(uni.fVSLocation, v0, v1));
+ }
+}
+
+void GrGLUniformManager::set2fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat v[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec2f_GrSLType);
+ GrAssert(arrayCount > 0);
+ ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, offset, arrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform2fv(uni.fFSLocation + offset, arrayCount, v));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform2fv(uni.fVSLocation + offset, arrayCount, v));
+ }
+}
+
+void GrGLUniformManager::set3f(UniformHandle u, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec3f_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform3f(uni.fFSLocation, v0, v1, v2));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform3f(uni.fVSLocation, v0, v1, v2));
+ }
+}
+
+void GrGLUniformManager::set3fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat v[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec3f_GrSLType);
+ GrAssert(arrayCount > 0);
+ ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, offset, arrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform3fv(uni.fFSLocation + offset, arrayCount, v));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform3fv(uni.fVSLocation + offset, arrayCount, v));
+ }
+}
+
+void GrGLUniformManager::set4f(UniformHandle u,
+ GrGLfloat v0,
+ GrGLfloat v1,
+ GrGLfloat v2,
+ GrGLfloat v3) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec4f_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform4f(uni.fFSLocation, v0, v1, v2, v3));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform4f(uni.fVSLocation, v0, v1, v2, v3));
+ }
+}
+
+void GrGLUniformManager::set4fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat v[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kVec4f_GrSLType);
+ GrAssert(arrayCount > 0);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform4fv(uni.fFSLocation + offset, arrayCount, v));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), Uniform4fv(uni.fVSLocation + offset, arrayCount, v));
+ }
+}
+
+void GrGLUniformManager::setMatrix3f(UniformHandle u, const GrGLfloat matrix[]) const {
+ 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 effects
+ // GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), UniformMatrix3fv(uni.fFSLocation, 1, false, matrix));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), UniformMatrix3fv(uni.fVSLocation, 1, false, matrix));
+ }
+}
+
+void GrGLUniformManager::setMatrix4f(UniformHandle u, const GrGLfloat matrix[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kMat44f_GrSLType);
+ GrAssert(GrGLShaderVar::kNonArray == uni.fArrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), UniformMatrix4fv(uni.fFSLocation, 1, false, matrix));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(), UniformMatrix4fv(uni.fVSLocation, 1, false, matrix));
+ }
+}
+
+void GrGLUniformManager::setMatrix3fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat matrices[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kMat33f_GrSLType);
+ GrAssert(arrayCount > 0);
+ ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, offset, arrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(),
+ UniformMatrix3fv(uni.fFSLocation + offset, arrayCount, false, matrices));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(),
+ UniformMatrix3fv(uni.fVSLocation + offset, arrayCount, false, matrices));
+ }
+}
+
+void GrGLUniformManager::setMatrix4fv(UniformHandle u,
+ int offset,
+ int arrayCount,
+ const GrGLfloat matrices[]) const {
+ const Uniform& uni = fUniforms[handle_to_index(u)];
+ GrAssert(uni.fType == kMat44f_GrSLType);
+ GrAssert(arrayCount > 0);
+ ASSERT_ARRAY_UPLOAD_IN_BOUNDS(uni, offset, arrayCount);
+ GrAssert(kUnusedUniform != uni.fFSLocation || kUnusedUniform != uni.fVSLocation);
+ if (kUnusedUniform != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(),
+ UniformMatrix4fv(uni.fFSLocation + offset, arrayCount, false, matrices));
+ }
+ if (kUnusedUniform != uni.fVSLocation && uni.fVSLocation != uni.fFSLocation) {
+ GR_GL_CALL(fContext.interface(),
+ UniformMatrix4fv(uni.fVSLocation + offset, arrayCount, false, matrices));
+ }
+}
+
+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();
+ for (int i = 0; i < count; ++i) {
+ GrAssert(uniforms[i].fVariable.getType() == fUniforms[i].fType);
+ GrAssert(uniforms[i].fVariable.getArrayCount() == fUniforms[i].fArrayCount);
+ GrGLint location;
+ // TODO: Move the Xoom uniform array in both FS and VS bug workaround here.
+ GR_GL_CALL_RET(fContext.interface(), location,
+ GetUniformLocation(programID, uniforms[i].fVariable.c_str()));
+ if (GrGLShaderBuilder::kVertex_ShaderType & uniforms[i].fVisibility) {
+ fUniforms[i].fVSLocation = location;
+ }
+ if (GrGLShaderBuilder::kFragment_ShaderType & uniforms[i].fVisibility) {
+ fUniforms[i].fFSLocation = location;
+ }
+ }
+}
diff --git a/gpu/gl/GrGLUniformManager.h b/gpu/gl/GrGLUniformManager.h
new file mode 100644
index 00000000..ee693a6a
--- /dev/null
+++ b/gpu/gl/GrGLUniformManager.h
@@ -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.
+ */
+
+#ifndef GrGLUniformManager_DEFINED
+#define GrGLUniformManager_DEFINED
+
+#include "gl/GrGLShaderVar.h"
+#include "gl/GrGLSL.h"
+#include "GrAllocator.h"
+
+#include "SkTArray.h"
+
+class GrGLContext;
+class SkMatrix;
+
+/** Manages a program's uniforms.
+*/
+class GrGLUniformManager {
+public:
+ // Opaque handle to a uniform
+ typedef int UniformHandle;
+ static const UniformHandle kInvalidUniformHandle = 0;
+
+ GrGLUniformManager(const GrGLContext& context) : fContext(context) {}
+
+ UniformHandle appendUniform(GrSLType type, int arrayCount = GrGLShaderVar::kNonArray);
+
+ /** Functions for uploading uniform values. The varities ending in v can be used to upload to an
+ * array of uniforms. offset + arrayCount must be <= the array count of the uniform.
+ */
+ void setSampler(UniformHandle, GrGLint texUnit) const;
+ void set1f(UniformHandle, GrGLfloat v0) const;
+ void set1fv(UniformHandle, int offset, int arrayCount, const GrGLfloat v[]) const;
+ void set2f(UniformHandle, GrGLfloat, GrGLfloat) const;
+ void set2fv(UniformHandle, int offset, int arrayCount, const GrGLfloat v[]) const;
+ void set3f(UniformHandle, GrGLfloat, GrGLfloat, GrGLfloat) const;
+ void set3fv(UniformHandle, int offset, int arrayCount, const GrGLfloat v[]) const;
+ void set4f(UniformHandle, GrGLfloat, GrGLfloat, GrGLfloat, GrGLfloat) const;
+ void set4fv(UniformHandle, int offset, int arrayCount, const GrGLfloat v[]) const;
+ // matrices are column-major, the first three upload a single matrix, the latter three upload
+ // arrayCount matrices into a uniform array.
+ void setMatrix3f(UniformHandle, const GrGLfloat matrix[]) const;
+ void setMatrix4f(UniformHandle, const GrGLfloat matrix[]) const;
+ 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;
+ };
+ // This uses an allocator rather than array so that the GrGLShaderVars don't move in memory
+ // after they are inserted. Users of GrGLShaderBuilder get refs to the vars and ptrs to their
+ // name strings. Otherwise, we'd have to hand out copies.
+ typedef GrTAllocator<BuilderUniform> BuilderUniformArray;
+
+ /**
+ * Called by the GrGLShaderBuilder to get GL locations for all uniforms.
+ */
+ void getUniformLocations(GrGLuint programID, const BuilderUniformArray& uniforms);
+
+private:
+ enum {
+ kUnusedUniform = -1,
+ };
+
+ struct Uniform {
+ GrGLint fVSLocation;
+ GrGLint fFSLocation;
+ GrSLType fType;
+ int fArrayCount;
+ };
+
+ SkTArray<Uniform, true> fUniforms;
+ const GrGLContext& fContext;
+};
+
+#endif
diff --git a/gpu/gl/GrGLUtil.cpp b/gpu/gl/GrGLUtil.cpp
new file mode 100644
index 00000000..a13b03de
--- /dev/null
+++ b/gpu/gl/GrGLUtil.cpp
@@ -0,0 +1,235 @@
+/*
+ * 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 "GrGLUtil.h"
+
+void GrGLClearErr(const GrGLInterface* gl) {
+ while (GR_GL_NO_ERROR != gl->fGetError()) {}
+}
+
+namespace {
+const char *get_error_string(uint32_t err) {
+ switch (err) {
+ case GR_GL_NO_ERROR:
+ return "";
+ case GR_GL_INVALID_ENUM:
+ return "Invalid Enum";
+ case GR_GL_INVALID_VALUE:
+ return "Invalid Value";
+ case GR_GL_INVALID_OPERATION:
+ return "Invalid Operation";
+ case GR_GL_OUT_OF_MEMORY:
+ return "Out of Memory";
+ case GR_GL_CONTEXT_LOST:
+ return "Context Lost";
+ }
+ return "Unknown";
+}
+}
+
+void GrGLCheckErr(const GrGLInterface* gl,
+ const char* location,
+ const char* call) {
+ uint32_t err = GR_GL_GET_ERROR(gl);
+ if (GR_GL_NO_ERROR != err) {
+ GrPrintf("---- glGetError 0x%x(%s)", err, get_error_string(err));
+ if (NULL != location) {
+ GrPrintf(" at\n\t%s", location);
+ }
+ if (NULL != call) {
+ GrPrintf("\n\t\t%s", call);
+ }
+ GrPrintf("\n");
+ }
+}
+
+namespace {
+// Mesa uses a non-standard version string of format: 1.4 Mesa <mesa_major>.<mesa_minor>.
+// The mapping of from mesa version to GL version came from here: http://www.mesa3d.org/intro.html
+bool get_gl_version_for_mesa(int mesaMajorVersion, int* major, int* minor) {
+ switch (mesaMajorVersion) {
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ *major = 1;
+ *minor = mesaMajorVersion - 1;
+ return true;
+ case 7:
+ *major = 2;
+ *minor = 1;
+ return true;
+ case 8:
+ *major = 3;
+ *minor = 0;
+ return true;
+ case 9:
+ *major = 3;
+ *minor = 1;
+ return true;
+ default:
+ return false;
+ }
+}
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_GL_LOG_CALLS
+ bool gLogCallsGL = !!(GR_GL_LOG_CALLS_START);
+#endif
+
+#if GR_GL_CHECK_ERROR
+ bool gCheckErrorGL = !!(GR_GL_CHECK_ERROR_START);
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLBinding GrGLGetBindingInUseFromString(const char* versionString) {
+ if (NULL == versionString) {
+ GrAssert(!"NULL GL version string.");
+ return kNone_GrGLBinding;
+ }
+
+ int major, minor;
+
+ // check for desktop
+ int n = sscanf(versionString, "%d.%d", &major, &minor);
+ if (2 == n) {
+ return kDesktop_GrGLBinding;
+ }
+
+ // check for ES 1
+ char profile[2];
+ n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1, &major, &minor);
+ if (4 == n) {
+ // we no longer support ES1.
+ return kNone_GrGLBinding;
+ }
+
+ // check for ES2
+ n = sscanf(versionString, "OpenGL ES %d.%d", &major, &minor);
+ if (2 == n) {
+ return kES2_GrGLBinding;
+ }
+ return kNone_GrGLBinding;
+}
+
+bool GrGLIsMesaFromVersionString(const char* versionString) {
+ int major, minor, mesaMajor, mesaMinor;
+ int n = sscanf(versionString, "%d.%d Mesa %d.%d", &major, &minor, &mesaMajor, &mesaMinor);
+ return 4 == n;
+}
+
+GrGLVersion GrGLGetVersionFromString(const char* versionString) {
+ if (NULL == versionString) {
+ GrAssert(!"NULL GL version string.");
+ return 0;
+ }
+
+ int major, minor;
+
+ // check for mesa
+ int mesaMajor, mesaMinor;
+ int n = sscanf(versionString, "%d.%d Mesa %d.%d", &major, &minor, &mesaMajor, &mesaMinor);
+ if (4 == n) {
+ if (get_gl_version_for_mesa(mesaMajor, &major, &minor)) {
+ return GR_GL_VER(major, minor);
+ } else {
+ return 0;
+ }
+ }
+
+ n = sscanf(versionString, "%d.%d", &major, &minor);
+ if (2 == n) {
+ return GR_GL_VER(major, minor);
+ }
+
+ char profile[2];
+ n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1,
+ &major, &minor);
+ if (4 == n) {
+ return GR_GL_VER(major, minor);
+ }
+
+ n = sscanf(versionString, "OpenGL ES %d.%d", &major, &minor);
+ if (2 == n) {
+ return GR_GL_VER(major, minor);
+ }
+
+ return 0;
+}
+
+GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString) {
+ if (NULL == versionString) {
+ GrAssert(!"NULL GLSL version string.");
+ return 0;
+ }
+
+ int major, minor;
+
+ int n = sscanf(versionString, "%d.%d", &major, &minor);
+ if (2 == n) {
+ return GR_GLSL_VER(major, minor);
+ }
+
+ n = sscanf(versionString, "OpenGL ES GLSL ES %d.%d", &major, &minor);
+ if (2 == n) {
+ return GR_GLSL_VER(major, minor);
+ }
+
+#ifdef SK_BUILD_FOR_ANDROID
+ // android hack until the gpu vender updates their drivers
+ n = sscanf(versionString, "OpenGL ES GLSL %d.%d", &major, &minor);
+ if (2 == n) {
+ return GR_GLSL_VER(major, minor);
+ }
+#endif
+
+ return 0;
+}
+
+GrGLVendor GrGLGetVendorFromString(const char* vendorString) {
+ if (NULL != vendorString) {
+ if (0 == strcmp(vendorString, "ARM")) {
+ return kARM_GrGLVendor;
+ }
+ if (0 == strcmp(vendorString, "Imagination Technologies")) {
+ return kImagination_GrGLVendor;
+ }
+ if (0 == strcmp(vendorString, "Intel")) {
+ return kIntel_GrGLVendor;
+ }
+ }
+ return kOther_GrGLVendor;
+}
+
+GrGLBinding GrGLGetBindingInUse(const GrGLInterface* gl) {
+ const GrGLubyte* v;
+ GR_GL_CALL_RET(gl, v, GetString(GR_GL_VERSION));
+ return GrGLGetBindingInUseFromString((const char*) v);
+}
+
+GrGLVersion GrGLGetVersion(const GrGLInterface* gl) {
+ const GrGLubyte* v;
+ GR_GL_CALL_RET(gl, v, GetString(GR_GL_VERSION));
+ return GrGLGetVersionFromString((const char*) v);
+}
+
+GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface* gl) {
+ const GrGLubyte* v;
+ 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/gpu/gl/GrGLUtil.h b/gpu/gl/GrGLUtil.h
new file mode 100644
index 00000000..b8a96e51
--- /dev/null
+++ b/gpu/gl/GrGLUtil.h
@@ -0,0 +1,162 @@
+/*
+ * 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 GrGLUtil_DEFINED
+#define GrGLUtil_DEFINED
+
+#include "gl/GrGLInterface.h"
+#include "GrGLDefines.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+typedef uint32_t GrGLVersion;
+typedef uint32_t GrGLSLVersion;
+
+/**
+ * This list is lazily updated as required.
+ */
+enum GrGLVendor {
+ kARM_GrGLVendor,
+ kImagination_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) | \
+ static_cast<int>(minor))
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Some drivers want the var-int arg to be zero-initialized on input.
+ */
+#define GR_GL_INIT_ZERO 0
+#define GR_GL_GetIntegerv(gl, e, p) \
+ do { \
+ *(p) = GR_GL_INIT_ZERO; \
+ GR_GL_CALL(gl, GetIntegerv(e, p)); \
+ } while (0)
+
+#define GR_GL_GetFramebufferAttachmentParameteriv(gl, t, a, pname, p) \
+ do { \
+ *(p) = GR_GL_INIT_ZERO; \
+ GR_GL_CALL(gl, GetFramebufferAttachmentParameteriv(t, a, pname, p)); \
+ } while (0)
+
+#define GR_GL_GetRenderbufferParameteriv(gl, t, pname, p) \
+ do { \
+ *(p) = GR_GL_INIT_ZERO; \
+ GR_GL_CALL(gl, GetRenderbufferParameteriv(t, pname, p)); \
+ } while (0)
+#define GR_GL_GetTexLevelParameteriv(gl, t, l, pname, p) \
+ do { \
+ *(p) = GR_GL_INIT_ZERO; \
+ GR_GL_CALL(gl, GetTexLevelParameteriv(t, l, pname, p)); \
+ } while (0)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Helpers for glGetString()
+ */
+
+// these variants assume caller already has a string from glGetString()
+GrGLVersion GrGLGetVersionFromString(const char* versionString);
+GrGLBinding GrGLGetBindingInUseFromString(const char* versionString);
+GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString);
+bool GrGLIsMesaFromVersionString(const char* versionString);
+GrGLVendor GrGLGetVendorFromString(const char* vendorString);
+
+// these variants call glGetString()
+GrGLBinding GrGLGetBindingInUse(const GrGLInterface*);
+GrGLVersion GrGLGetVersion(const GrGLInterface*);
+GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface*);
+GrGLVendor GrGLGetVendor(const GrGLInterface*);
+
+/**
+ * Helpers for glGetError()
+ */
+
+void GrGLCheckErr(const GrGLInterface* gl,
+ const char* location,
+ const char* call);
+
+void GrGLClearErr(const GrGLInterface* gl);
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Macros for using GrGLInterface to make GL calls
+ */
+
+// internal macro to conditionally call glGetError based on compile-time and
+// run-time flags.
+#if GR_GL_CHECK_ERROR
+ extern bool gCheckErrorGL;
+ #define GR_GL_CHECK_ERROR_IMPL(IFACE, X) \
+ if (gCheckErrorGL) \
+ GrGLCheckErr(IFACE, GR_FILE_AND_LINE_STR, #X)
+#else
+ #define GR_GL_CHECK_ERROR_IMPL(IFACE, X)
+#endif
+
+// internal macro to conditionally log the gl call using GrPrintf based on
+// compile-time and run-time flags.
+#if GR_GL_LOG_CALLS
+ extern bool gLogCallsGL;
+ #define GR_GL_LOG_CALLS_IMPL(X) \
+ if (gLogCallsGL) \
+ GrPrintf(GR_FILE_AND_LINE_STR "GL: " #X "\n")
+#else
+ #define GR_GL_LOG_CALLS_IMPL(X)
+#endif
+
+// internal macro that does the per-GL-call callback (if necessary)
+#if GR_GL_PER_GL_FUNC_CALLBACK
+ #define GR_GL_CALLBACK_IMPL(IFACE) (IFACE)->fCallback(IFACE)
+#else
+ #define GR_GL_CALLBACK_IMPL(IFACE)
+#endif
+
+// makes a GL call on the interface and does any error checking and logging
+#define GR_GL_CALL(IFACE, X) \
+ do { \
+ GR_GL_CALL_NOERRCHECK(IFACE, X); \
+ GR_GL_CHECK_ERROR_IMPL(IFACE, X); \
+ } while (false)
+
+// Variant of above that always skips the error check. This is useful when
+// the caller wants to do its own glGetError() call and examine the error value.
+#define GR_GL_CALL_NOERRCHECK(IFACE, X) \
+ do { \
+ GR_GL_CALLBACK_IMPL(IFACE); \
+ (IFACE)->f##X; \
+ GR_GL_LOG_CALLS_IMPL(X); \
+ } while (false)
+
+// same as GR_GL_CALL but stores the return value of the gl call in RET
+#define GR_GL_CALL_RET(IFACE, RET, X) \
+ do { \
+ GR_GL_CALL_RET_NOERRCHECK(IFACE, RET, X); \
+ GR_GL_CHECK_ERROR_IMPL(IFACE, X); \
+ } while (false)
+
+// same as GR_GL_CALL_RET but always skips the error check.
+#define GR_GL_CALL_RET_NOERRCHECK(IFACE, RET, X) \
+ do { \
+ GR_GL_CALLBACK_IMPL(IFACE); \
+ (RET) = (IFACE)->f##X; \
+ GR_GL_LOG_CALLS_IMPL(X); \
+ } while (false)
+
+// call glGetError without doing a redundant error check or logging.
+#define GR_GL_GET_ERROR(IFACE) (IFACE)->fGetError()
+
+#endif
diff --git a/gpu/gl/GrGLVertexArray.cpp b/gpu/gl/GrGLVertexArray.cpp
new file mode 100644
index 00000000..a10f9873
--- /dev/null
+++ b/gpu/gl/GrGLVertexArray.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGLVertexArray.h"
+#include "GrGpuGL.h"
+
+#define GPUGL static_cast<GrGpuGL*>(this->getGpu())
+#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X);
+
+void GrGLAttribArrayState::set(const GrGpuGL* gpu,
+ int index,
+ GrGLVertexBuffer* buffer,
+ GrGLint size,
+ GrGLenum type,
+ GrGLboolean normalized,
+ GrGLsizei stride,
+ GrGLvoid* offset) {
+ GrAssert(index >= 0 && index < fAttribArrayStates.count());
+ AttribArrayState* array = &fAttribArrayStates[index];
+ if (!array->fEnableIsValid || !array->fEnabled) {
+ GR_GL_CALL(gpu->glInterface(), EnableVertexAttribArray(index));
+ array->fEnableIsValid = true;
+ array->fEnabled = true;
+ }
+ if (!array->fAttribPointerIsValid ||
+ array->fVertexBufferID != buffer->bufferID() ||
+ array->fSize != size ||
+ array->fNormalized != normalized ||
+ array->fStride != stride ||
+ array->fOffset != offset) {
+
+ buffer->bind();
+ GR_GL_CALL(gpu->glInterface(), VertexAttribPointer(index,
+ size,
+ type,
+ normalized,
+ stride,
+ offset));
+ array->fAttribPointerIsValid = true;
+ array->fVertexBufferID = buffer->bufferID();
+ array->fSize = size;
+ array->fNormalized = normalized;
+ array->fStride = stride;
+ array->fOffset = offset;
+ }
+}
+
+void GrGLAttribArrayState::disableUnusedAttribArrays(const GrGpuGL* gpu, uint64_t usedMask) {
+ int count = fAttribArrayStates.count();
+ for (int i = 0; i < count; ++i) {
+ if (!(usedMask & 0x1)) {
+ if (!fAttribArrayStates[i].fEnableIsValid || fAttribArrayStates[i].fEnabled) {
+ GR_GL_CALL(gpu->glInterface(), DisableVertexAttribArray(i));
+ fAttribArrayStates[i].fEnableIsValid = true;
+ fAttribArrayStates[i].fEnabled = false;
+ }
+ }
+ // if the count is greater than 64 then this will become 0 and we will disable arrays 64+.
+ usedMask >>= 1;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+GrGLVertexArray::GrGLVertexArray(GrGpuGL* gpu, GrGLint id, int attribCount)
+ : GrResource(gpu, false)
+ , fID(id)
+ , fAttribArrays(attribCount)
+ , fIndexBufferIDIsValid(false) {
+}
+
+void GrGLVertexArray::onAbandon() {
+ fID = 0;
+ INHERITED::onAbandon();
+}
+
+void GrGLVertexArray::onRelease() {
+ if (0 != fID) {
+ GL_CALL(DeleteVertexArrays(1, &fID));
+ GPUGL->notifyVertexArrayDelete(fID);
+ fID = 0;
+ }
+ INHERITED::onRelease();
+}
+
+GrGLAttribArrayState* GrGLVertexArray::bind() {
+ if (0 == fID) {
+ return NULL;
+ }
+ GPUGL->bindVertexArray(fID);
+ return &fAttribArrays;
+}
+
+GrGLAttribArrayState* GrGLVertexArray::bindWithIndexBuffer(const GrGLIndexBuffer* buffer) {
+ GrGLAttribArrayState* state = this->bind();
+ if (NULL != state && NULL != buffer) {
+ GrGLuint bufferID = buffer->bufferID();
+ if (!fIndexBufferIDIsValid || bufferID != fIndexBufferID) {
+ GL_CALL(BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, bufferID));
+ fIndexBufferIDIsValid = true;
+ fIndexBufferID = bufferID;
+ }
+ }
+ return state;
+}
+
+void GrGLVertexArray::notifyIndexBufferDelete(GrGLuint bufferID) {
+ if (fIndexBufferIDIsValid && bufferID == fIndexBufferID) {
+ fIndexBufferID = 0;
+ }
+ }
+
+void GrGLVertexArray::invalidateCachedState() {
+ int count = fAttribArrays.count();
+ for (int i = 0; i < count; ++i) {
+ fAttribArrays.invalidate();
+ }
+ fIndexBufferIDIsValid = false;
+}
diff --git a/gpu/gl/GrGLVertexArray.h b/gpu/gl/GrGLVertexArray.h
new file mode 100644
index 00000000..2d1ff295
--- /dev/null
+++ b/gpu/gl/GrGLVertexArray.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGLVertexArray_DEFINED
+#define GrGLVertexArray_DEFINED
+
+#include "GrResource.h"
+#include "GrTypesPriv.h"
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLFunctions.h"
+
+#include "SkTArray.h"
+
+class GrGLVertexBuffer;
+class GrGLIndexBuffer;
+class GrGpuGL;
+
+struct GrGLAttribLayout {
+ GrGLint fCount;
+ GrGLenum fType;
+ GrGLboolean fNormalized;
+};
+
+static inline const GrGLAttribLayout& GrGLAttribTypeToLayout(GrVertexAttribType type) {
+ GrAssert(type >= 0 && type < kGrVertexAttribTypeCount);
+ static const GrGLAttribLayout kLayouts[kGrVertexAttribTypeCount] = {
+ {1, GR_GL_FLOAT, false}, // kFloat_GrVertexAttribType
+ {2, GR_GL_FLOAT, false}, // kVec2f_GrVertexAttribType
+ {3, GR_GL_FLOAT, false}, // kVec3f_GrVertexAttribType
+ {4, GR_GL_FLOAT, false}, // kVec4f_GrVertexAttribType
+ {4, GR_GL_UNSIGNED_BYTE, true}, // kVec4ub_GrVertexAttribType
+ };
+ GR_STATIC_ASSERT(0 == kFloat_GrVertexAttribType);
+ GR_STATIC_ASSERT(1 == kVec2f_GrVertexAttribType);
+ GR_STATIC_ASSERT(2 == kVec3f_GrVertexAttribType);
+ GR_STATIC_ASSERT(3 == kVec4f_GrVertexAttribType);
+ GR_STATIC_ASSERT(4 == kVec4ub_GrVertexAttribType);
+ GR_STATIC_ASSERT(SK_ARRAY_COUNT(kLayouts) == kGrVertexAttribTypeCount);
+ return kLayouts[type];
+}
+
+/**
+ * This sets and tracks the vertex attribute array state. It is used internally by GrGLVertexArray
+ * (below) but is separate because it is also used to track the state of vertex array object 0.
+ */
+class GrGLAttribArrayState {
+public:
+ explicit GrGLAttribArrayState(int arrayCount = 0) { this->resize(arrayCount); }
+
+ void resize(int newCount) {
+ fAttribArrayStates.resize_back(newCount);
+ for (int i = 0; i < newCount; ++i) {
+ fAttribArrayStates[i].invalidate();
+ }
+ }
+
+ /**
+ * This function enables and sets vertex attrib state for the specified attrib index. It is
+ * assumed that the GrGLAttribArrayState is tracking the state of the currently bound vertex
+ * array object.
+ */
+ void set(const GrGpuGL*,
+ int index,
+ GrGLVertexBuffer*,
+ GrGLint size,
+ GrGLenum type,
+ GrGLboolean normalized,
+ GrGLsizei stride,
+ GrGLvoid* offset);
+
+ /**
+ * This function disables vertex attribs not present in the mask. It is assumed that the
+ * GrGLAttribArrayState is tracking the state of the currently bound vertex array object.
+ */
+ void disableUnusedAttribArrays(const GrGpuGL*, uint64_t usedAttribArrayMask);
+
+ void invalidate() {
+ int count = fAttribArrayStates.count();
+ for (int i = 0; i < count; ++i) {
+ fAttribArrayStates[i].invalidate();
+ }
+ }
+
+ void notifyVertexBufferDelete(GrGLuint id) {
+ int count = fAttribArrayStates.count();
+ for (int i = 0; i < count; ++i) {
+ if (fAttribArrayStates[i].fAttribPointerIsValid &&
+ id == fAttribArrayStates[i].fVertexBufferID) {
+ fAttribArrayStates[i].invalidate();
+ }
+ }
+ }
+
+ /**
+ * The number of attrib arrays that this object is configured to track.
+ */
+ int count() const { return fAttribArrayStates.count(); }
+
+private:
+ /**
+ * Tracks the state of glVertexAttribArray for an attribute index.
+ */
+ struct AttribArrayState {
+ void invalidate() {
+ fEnableIsValid = false;
+ fAttribPointerIsValid = false;
+ }
+
+ bool fEnableIsValid;
+ bool fAttribPointerIsValid;
+ bool fEnabled;
+ GrGLuint fVertexBufferID;
+ GrGLint fSize;
+ GrGLenum fType;
+ GrGLboolean fNormalized;
+ GrGLsizei fStride;
+ GrGLvoid* fOffset;
+ };
+
+ SkSTArray<16, AttribArrayState, true> fAttribArrayStates;
+};
+
+/**
+ * This class represents an OpenGL vertex array object. It manages the lifetime of the vertex array
+ * and is used to track the state of the vertex array to avoid redundant GL calls.
+ */
+class GrGLVertexArray : public GrResource {
+public:
+ GrGLVertexArray(GrGpuGL* gpu, GrGLint id, int attribCount);
+
+ /**
+ * Binds this vertex array. If the ID has been deleted or abandoned then NULL is returned.
+ * Otherwise, the GrGLAttribArrayState that is tracking this vertex array's attrib bindings is
+ * returned.
+ */
+ GrGLAttribArrayState* bind();
+
+ /**
+ * This is a version of the above function that also binds an index buffer to the vertex
+ * array object.
+ */
+ GrGLAttribArrayState* bindWithIndexBuffer(const GrGLIndexBuffer* indexBuffer);
+
+ void notifyIndexBufferDelete(GrGLuint bufferID);
+
+ void notifyVertexBufferDelete(GrGLuint id) {
+ fAttribArrays.notifyVertexBufferDelete(id);
+ }
+
+ GrGLuint arrayID() const { return fID; }
+
+ void invalidateCachedState();
+
+ virtual size_t sizeInBytes() const SK_OVERRIDE { return 0; }
+
+protected:
+ virtual void onAbandon() SK_OVERRIDE;
+
+ virtual void onRelease() SK_OVERRIDE;
+
+private:
+ GrGLuint fID;
+ GrGLAttribArrayState fAttribArrays;
+ GrGLuint fIndexBufferID;
+ bool fIndexBufferIDIsValid;
+
+ typedef GrResource INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGLVertexBuffer.cpp b/gpu/gl/GrGLVertexBuffer.cpp
new file mode 100644
index 00000000..685166c9
--- /dev/null
+++ b/gpu/gl/GrGLVertexBuffer.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "GrGLVertexBuffer.h"
+#include "GrGpuGL.h"
+
+GrGLVertexBuffer::GrGLVertexBuffer(GrGpuGL* gpu, const Desc& desc)
+ : INHERITED(gpu, desc.fIsWrapped, desc.fSizeInBytes, desc.fDynamic, 0 == desc.fID)
+ , fImpl(gpu, desc, GR_GL_ARRAY_BUFFER) {
+}
+
+void GrGLVertexBuffer::onRelease() {
+ if (this->isValid()) {
+ fImpl.release(this->getGpuGL());
+ }
+
+ INHERITED::onRelease();
+}
+
+
+void GrGLVertexBuffer::onAbandon() {
+ fImpl.abandon();
+ INHERITED::onAbandon();
+}
+
+void* GrGLVertexBuffer::lock() {
+ if (this->isValid()) {
+ return fImpl.lock(this->getGpuGL());
+ } else {
+ return NULL;
+ }
+}
+
+void* GrGLVertexBuffer::lockPtr() const {
+ return fImpl.lockPtr();
+}
+
+void GrGLVertexBuffer::unlock() {
+ if (this->isValid()) {
+ fImpl.unlock(this->getGpuGL());
+ }
+}
+
+bool GrGLVertexBuffer::isLocked() const {
+ return fImpl.isLocked();
+}
+
+bool GrGLVertexBuffer::updateData(const void* src, size_t srcSizeInBytes) {
+ if (this->isValid()) {
+ return fImpl.updateData(this->getGpuGL(), src, srcSizeInBytes);
+ } else {
+ return false;
+ }
+}
diff --git a/gpu/gl/GrGLVertexBuffer.h b/gpu/gl/GrGLVertexBuffer.h
new file mode 100644
index 00000000..f15a5dab
--- /dev/null
+++ b/gpu/gl/GrGLVertexBuffer.h
@@ -0,0 +1,57 @@
+/*
+ * 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 GrGLVertexBuffer_DEFINED
+#define GrGLVertexBuffer_DEFINED
+
+#include "GrVertexBuffer.h"
+#include "GrGLBufferImpl.h"
+#include "gl/GrGLInterface.h"
+
+class GrGpuGL;
+
+class GrGLVertexBuffer : public GrVertexBuffer {
+
+public:
+ typedef GrGLBufferImpl::Desc Desc;
+
+ GrGLVertexBuffer(GrGpuGL* gpu, const Desc& desc);
+ virtual ~GrGLVertexBuffer() { this->release(); }
+
+ GrGLuint bufferID() const { return fImpl.bufferID(); }
+ size_t baseOffset() const { return fImpl.baseOffset(); }
+
+ void bind() const {
+ if (this->isValid()) {
+ fImpl.bind(this->getGpuGL());
+ }
+ }
+
+ // overrides of GrVertexBuffer
+ virtual void* lock();
+ virtual void* lockPtr() const;
+ virtual void unlock();
+ virtual bool isLocked() const;
+ virtual bool updateData(const void* src, size_t srcSizeInBytes);
+
+protected:
+ // overrides of GrResource
+ virtual void onAbandon() SK_OVERRIDE;
+ virtual void onRelease() SK_OVERRIDE;
+
+private:
+ GrGpuGL* getGpuGL() const {
+ GrAssert(this->isValid());
+ return (GrGpuGL*)(this->getGpu());
+ }
+
+ GrGLBufferImpl fImpl;
+
+ typedef GrVertexBuffer INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGpuGL.cpp b/gpu/gl/GrGpuGL.cpp
new file mode 100644
index 00000000..8d500e36
--- /dev/null
+++ b/gpu/gl/GrGpuGL.cpp
@@ -0,0 +1,2563 @@
+/*
+ * 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 "GrGpuGL.h"
+#include "GrGLStencilBuffer.h"
+#include "GrGLPath.h"
+#include "GrGLShaderBuilder.h"
+#include "GrTemplates.h"
+#include "GrTypes.h"
+#include "SkTemplates.h"
+
+static const GrGLuint GR_MAX_GLUINT = ~0U;
+static const GrGLint GR_INVAL_GLINT = ~0;
+
+#define GL_CALL(X) GR_GL_CALL(this->glInterface(), X)
+#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glInterface(), RET, X)
+
+
+#define SKIP_CACHE_CHECK true
+
+#if GR_GL_CHECK_ALLOC_WITH_GET_ERROR
+ #define CLEAR_ERROR_BEFORE_ALLOC(iface) GrGLClearErr(iface)
+ #define GL_ALLOC_CALL(iface, call) GR_GL_CALL_NOERRCHECK(iface, call)
+ #define CHECK_ALLOC_ERROR(iface) GR_GL_GET_ERROR(iface)
+#else
+ #define CLEAR_ERROR_BEFORE_ALLOC(iface)
+ #define GL_ALLOC_CALL(iface, call) GR_GL_CALL(iface, call)
+ #define CHECK_ALLOC_ERROR(iface) GR_GL_NO_ERROR
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const GrGLenum gXfermodeCoeff2Blend[] = {
+ GR_GL_ZERO,
+ GR_GL_ONE,
+ GR_GL_SRC_COLOR,
+ GR_GL_ONE_MINUS_SRC_COLOR,
+ GR_GL_DST_COLOR,
+ GR_GL_ONE_MINUS_DST_COLOR,
+ GR_GL_SRC_ALPHA,
+ GR_GL_ONE_MINUS_SRC_ALPHA,
+ GR_GL_DST_ALPHA,
+ GR_GL_ONE_MINUS_DST_ALPHA,
+ GR_GL_CONSTANT_COLOR,
+ GR_GL_ONE_MINUS_CONSTANT_COLOR,
+ GR_GL_CONSTANT_ALPHA,
+ GR_GL_ONE_MINUS_CONSTANT_ALPHA,
+
+ // extended blend coeffs
+ GR_GL_SRC1_COLOR,
+ GR_GL_ONE_MINUS_SRC1_COLOR,
+ GR_GL_SRC1_ALPHA,
+ GR_GL_ONE_MINUS_SRC1_ALPHA,
+};
+
+bool GrGpuGL::BlendCoeffReferencesConstant(GrBlendCoeff coeff) {
+ static const bool gCoeffReferencesBlendConst[] = {
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ true,
+
+ // extended blend coeffs
+ false,
+ false,
+ false,
+ false,
+ };
+ return gCoeffReferencesBlendConst[coeff];
+ GR_STATIC_ASSERT(kTotalGrBlendCoeffCount ==
+ GR_ARRAY_COUNT(gCoeffReferencesBlendConst));
+
+ GR_STATIC_ASSERT(0 == kZero_GrBlendCoeff);
+ GR_STATIC_ASSERT(1 == kOne_GrBlendCoeff);
+ GR_STATIC_ASSERT(2 == kSC_GrBlendCoeff);
+ GR_STATIC_ASSERT(3 == kISC_GrBlendCoeff);
+ GR_STATIC_ASSERT(4 == kDC_GrBlendCoeff);
+ GR_STATIC_ASSERT(5 == kIDC_GrBlendCoeff);
+ GR_STATIC_ASSERT(6 == kSA_GrBlendCoeff);
+ GR_STATIC_ASSERT(7 == kISA_GrBlendCoeff);
+ GR_STATIC_ASSERT(8 == kDA_GrBlendCoeff);
+ GR_STATIC_ASSERT(9 == kIDA_GrBlendCoeff);
+ GR_STATIC_ASSERT(10 == kConstC_GrBlendCoeff);
+ GR_STATIC_ASSERT(11 == kIConstC_GrBlendCoeff);
+ GR_STATIC_ASSERT(12 == kConstA_GrBlendCoeff);
+ GR_STATIC_ASSERT(13 == kIConstA_GrBlendCoeff);
+
+ GR_STATIC_ASSERT(14 == kS2C_GrBlendCoeff);
+ GR_STATIC_ASSERT(15 == kIS2C_GrBlendCoeff);
+ GR_STATIC_ASSERT(16 == kS2A_GrBlendCoeff);
+ GR_STATIC_ASSERT(17 == kIS2A_GrBlendCoeff);
+
+ // assertion for gXfermodeCoeff2Blend have to be in GrGpu scope
+ GR_STATIC_ASSERT(kTotalGrBlendCoeffCount ==
+ GR_ARRAY_COUNT(gXfermodeCoeff2Blend));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool gPrintStartupSpew;
+
+GrGpuGL::GrGpuGL(const GrGLContext& ctx, GrContext* context)
+ : GrGpu(context)
+ , fGLContext(ctx) {
+
+ GrAssert(ctx.isInitialized());
+
+ fCaps.reset(SkRef(ctx.info().caps()));
+
+ fHWBoundTextures.reset(ctx.info().caps()->maxFragmentTextureUnits());
+
+ fillInConfigRenderableTable();
+
+
+ GrGLClearErr(fGLContext.interface());
+
+ if (gPrintStartupSpew) {
+ const GrGLubyte* vendor;
+ const GrGLubyte* renderer;
+ const GrGLubyte* version;
+ GL_CALL_RET(vendor, GetString(GR_GL_VENDOR));
+ GL_CALL_RET(renderer, GetString(GR_GL_RENDERER));
+ GL_CALL_RET(version, GetString(GR_GL_VERSION));
+ GrPrintf("------------------------- create GrGpuGL %p --------------\n",
+ this);
+ GrPrintf("------ VENDOR %s\n", vendor);
+ GrPrintf("------ RENDERER %s\n", renderer);
+ GrPrintf("------ VERSION %s\n", version);
+ GrPrintf("------ EXTENSIONS\n");
+ ctx.info().extensions().print();
+ GrPrintf("\n");
+ ctx.info().caps()->print();
+ }
+
+ fProgramCache = SkNEW_ARGS(ProgramCache, (this->glContext()));
+
+ GrAssert(this->glCaps().maxVertexAttributes() >= GrDrawState::kMaxVertexAttribCnt);
+
+ fLastSuccessfulStencilFmtIdx = 0;
+ fHWProgramID = 0;
+}
+
+GrGpuGL::~GrGpuGL() {
+ if (0 != fHWProgramID) {
+ // detach the current program so there is no confusion on OpenGL's part
+ // that we want it to be deleted
+ GrAssert(fHWProgramID == fCurrentProgram->programID());
+ GL_CALL(UseProgram(0));
+ }
+
+ delete fProgramCache;
+
+ // This must be called by before the GrDrawTarget destructor
+ this->releaseGeometry();
+ // This subclass must do this before the base class destructor runs
+ // since we will unref the GrGLInterface.
+ this->releaseResources();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGpuGL::fillInConfigRenderableTable() {
+
+ // OpenGL < 3.0
+ // no support for render targets unless the GL_ARB_framebuffer_object
+ // extension is supported (in which case we get ALPHA, RED, RG, RGB,
+ // RGBA (ALPHA8, RGBA4, RGBA8) for OpenGL > 1.1). Note that we
+ // probably don't get R8 in this case.
+
+ // OpenGL 3.0
+ // base color renderable: ALPHA, RED, RG, RGB, and RGBA
+ // sized derivatives: ALPHA8, R8, RGBA4, RGBA8
+
+ // >= OpenGL 3.1
+ // base color renderable: RED, RG, RGB, and RGBA
+ // sized derivatives: R8, RGBA4, RGBA8
+ // if the GL_ARB_compatibility extension is supported then we get back
+ // support for GL_ALPHA and ALPHA8
+
+ // GL_EXT_bgra adds BGRA render targets to any version
+
+ // ES 2.0
+ // color renderable: RGBA4, RGB5_A1, RGB565
+ // GL_EXT_texture_rg adds support for R8 as a color render target
+ // GL_OES_rgb8_rgba8 and/or GL_ARM_rgba8 adds support for RGBA8
+ // GL_EXT_texture_format_BGRA8888 and/or GL_APPLE_texture_format_BGRA8888 added BGRA support
+
+ if (kDesktop_GrGLBinding == this->glBinding()) {
+ // Post 3.0 we will get R8
+ // Prior to 3.0 we will get ALPHA8 (with GL_ARB_framebuffer_object)
+ if (this->glVersion() >= GR_GL_VER(3,0) ||
+ this->hasExtension("GL_ARB_framebuffer_object")) {
+ fConfigRenderSupport[kAlpha_8_GrPixelConfig] = true;
+ }
+ } else {
+ // On ES we can only hope for R8
+ fConfigRenderSupport[kAlpha_8_GrPixelConfig] =
+ this->glCaps().textureRedSupport();
+ }
+
+ if (kDesktop_GrGLBinding != this->glBinding()) {
+ // only available in ES
+ fConfigRenderSupport[kRGB_565_GrPixelConfig] = true;
+ }
+
+ // we no longer support 444 as a render target
+ fConfigRenderSupport[kRGBA_4444_GrPixelConfig] = false;
+
+ if (this->glCaps().rgba8RenderbufferSupport()) {
+ fConfigRenderSupport[kRGBA_8888_GrPixelConfig] = true;
+ }
+
+ if (this->glCaps().bgraFormatSupport()) {
+ fConfigRenderSupport[kBGRA_8888_GrPixelConfig] = true;
+ }
+}
+
+GrPixelConfig GrGpuGL::preferredReadPixelsConfig(GrPixelConfig readConfig,
+ GrPixelConfig surfaceConfig) const {
+ if (GR_GL_RGBA_8888_PIXEL_OPS_SLOW && kRGBA_8888_GrPixelConfig == readConfig) {
+ return kBGRA_8888_GrPixelConfig;
+ } else if (fGLContext.info().isMesa() &&
+ GrBytesPerPixel(readConfig) == 4 &&
+ GrPixelConfigSwapRAndB(readConfig) == surfaceConfig) {
+ // Mesa 3D takes a slow path on when reading back BGRA from an RGBA surface and vice-versa.
+ // Perhaps this should be guarded by some compiletime or runtime check.
+ return surfaceConfig;
+ } else if (readConfig == kBGRA_8888_GrPixelConfig &&
+ !this->glCaps().readPixelsSupported(this->glInterface(),
+ GR_GL_BGRA, GR_GL_UNSIGNED_BYTE)) {
+ return kRGBA_8888_GrPixelConfig;
+ } else {
+ return readConfig;
+ }
+}
+
+GrPixelConfig GrGpuGL::preferredWritePixelsConfig(GrPixelConfig writeConfig,
+ GrPixelConfig surfaceConfig) const {
+ if (GR_GL_RGBA_8888_PIXEL_OPS_SLOW && kRGBA_8888_GrPixelConfig == writeConfig) {
+ return kBGRA_8888_GrPixelConfig;
+ } else {
+ return writeConfig;
+ }
+}
+
+bool GrGpuGL::canWriteTexturePixels(const GrTexture* texture, GrPixelConfig srcConfig) const {
+ if (kIndex_8_GrPixelConfig == srcConfig || kIndex_8_GrPixelConfig == texture->config()) {
+ return false;
+ }
+ if (srcConfig != texture->config() && kES2_GrGLBinding == this->glBinding()) {
+ // In general ES2 requires the internal format of the texture and the format of the src
+ // pixels to match. However, It may or may not be possible to upload BGRA data to a RGBA
+ // texture. It depends upon which extension added BGRA. The Apple extension allows it
+ // (BGRA's internal format is RGBA) while the EXT extension does not (BGRA is its own
+ // internal format).
+ if (this->glCaps().bgraFormatSupport() &&
+ !this->glCaps().bgraIsInternalFormat() &&
+ kBGRA_8888_GrPixelConfig == srcConfig &&
+ kRGBA_8888_GrPixelConfig == texture->config()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+}
+
+bool GrGpuGL::fullReadPixelsIsFasterThanPartial() const {
+ return SkToBool(GR_GL_FULL_READPIXELS_FASTER_THAN_PARTIAL);
+}
+
+void GrGpuGL::onResetContext(uint32_t resetBits) {
+ // we don't use the zb at all
+ if (resetBits & kMisc_GrGLBackendState) {
+ GL_CALL(Disable(GR_GL_DEPTH_TEST));
+ GL_CALL(DepthMask(GR_GL_FALSE));
+
+ fHWDrawFace = GrDrawState::kInvalid_DrawFace;
+ fHWDitherEnabled = kUnknown_TriState;
+
+ if (kDesktop_GrGLBinding == this->glBinding()) {
+ // Desktop-only state that we never change
+ if (!this->glCaps().isCoreProfile()) {
+ GL_CALL(Disable(GR_GL_POINT_SMOOTH));
+ GL_CALL(Disable(GR_GL_LINE_SMOOTH));
+ GL_CALL(Disable(GR_GL_POLYGON_SMOOTH));
+ GL_CALL(Disable(GR_GL_POLYGON_STIPPLE));
+ GL_CALL(Disable(GR_GL_COLOR_LOGIC_OP));
+ GL_CALL(Disable(GR_GL_INDEX_LOGIC_OP));
+ }
+ // The windows NVIDIA driver has GL_ARB_imaging in the extension string when using a
+ // core profile. This seems like a bug since the core spec removes any mention of
+ // GL_ARB_imaging.
+ if (this->glCaps().imagingSupport() && !this->glCaps().isCoreProfile()) {
+ GL_CALL(Disable(GR_GL_COLOR_TABLE));
+ }
+ GL_CALL(Disable(GR_GL_POLYGON_OFFSET_FILL));
+ // Since ES doesn't support glPointSize at all we always use the VS to
+ // set the point size
+ GL_CALL(Enable(GR_GL_VERTEX_PROGRAM_POINT_SIZE));
+
+ // We should set glPolygonMode(FRONT_AND_BACK,FILL) here, too. It isn't
+ // currently part of our gl interface. There are probably others as
+ // well.
+ }
+ fHWWriteToColor = kUnknown_TriState;
+ // we only ever use lines in hairline mode
+ GL_CALL(LineWidth(1));
+ }
+
+ if (resetBits & kAA_GrGLBackendState) {
+ fHWAAState.invalidate();
+ }
+
+ // invalid
+ if (resetBits & kTextureBinding_GrGLBackendState) {
+ fHWActiveTextureUnitIdx = -1;
+ for (int s = 0; s < fHWBoundTextures.count(); ++s) {
+ fHWBoundTextures[s] = NULL;
+ }
+ }
+
+ if (resetBits & kBlend_GrGLBackendState) {
+ fHWBlendState.invalidate();
+ }
+
+ if (resetBits & kView_GrGLBackendState) {
+ fHWScissorSettings.invalidate();
+ fHWViewport.invalidate();
+ }
+
+ if (resetBits & kStencil_GrGLBackendState) {
+ fHWStencilSettings.invalidate();
+ fHWStencilTestEnabled = kUnknown_TriState;
+ }
+
+ // Vertex
+ if (resetBits & kVertex_GrGLBackendState) {
+ fHWGeometryState.invalidate();
+ }
+
+ if (resetBits & kRenderTarget_GrGLBackendState) {
+ fHWBoundRenderTarget = NULL;
+ }
+
+ if (resetBits & kPathStencil_GrGLBackendState) {
+ fHWPathStencilMatrixState.invalidate();
+ if (this->caps()->pathStencilingSupport()) {
+ // we don't use the model view matrix.
+ GL_CALL(MatrixMode(GR_GL_MODELVIEW));
+ GL_CALL(LoadIdentity());
+ }
+ }
+
+ // we assume these values
+ if (resetBits & kPixelStore_GrGLBackendState) {
+ if (this->glCaps().unpackRowLengthSupport()) {
+ GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0));
+ }
+ if (this->glCaps().packRowLengthSupport()) {
+ GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0));
+ }
+ if (this->glCaps().unpackFlipYSupport()) {
+ GL_CALL(PixelStorei(GR_GL_UNPACK_FLIP_Y, GR_GL_FALSE));
+ }
+ if (this->glCaps().packFlipYSupport()) {
+ GL_CALL(PixelStorei(GR_GL_PACK_REVERSE_ROW_ORDER, GR_GL_FALSE));
+ }
+ }
+
+ if (resetBits & kProgram_GrGLBackendState) {
+ fHWProgramID = 0;
+ fSharedGLProgramState.invalidate();
+ }
+}
+
+namespace {
+
+GrSurfaceOrigin resolve_origin(GrSurfaceOrigin origin, bool renderTarget) {
+ // By default, GrRenderTargets are GL's normal orientation so that they
+ // can be drawn to by the outside world without the client having
+ // to render upside down.
+ if (kDefault_GrSurfaceOrigin == origin) {
+ return renderTarget ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin;
+ } else {
+ return origin;
+ }
+}
+
+}
+
+GrTexture* GrGpuGL::onWrapBackendTexture(const GrBackendTextureDesc& desc) {
+ if (!this->configToGLFormats(desc.fConfig, false, NULL, NULL, NULL)) {
+ return NULL;
+ }
+
+ if (0 == desc.fTextureHandle) {
+ return NULL;
+ }
+
+ int maxSize = this->caps()->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;
+ glTexDesc.fConfig = desc.fConfig;
+ glTexDesc.fSampleCnt = desc.fSampleCnt;
+ glTexDesc.fTextureID = static_cast<GrGLuint>(desc.fTextureHandle);
+ glTexDesc.fIsWrapped = true;
+ bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrBackendTextureFlag);
+ // FIXME: this should be calling resolve_origin(), but Chrome code is currently
+ // assuming the old behaviour, which is that backend textures are always
+ // BottomLeft, even for non-RT's. Once Chrome is fixed, change this to:
+ // glTexDesc.fOrigin = resolve_origin(desc.fOrigin, renderTarget);
+ if (kDefault_GrSurfaceOrigin == desc.fOrigin) {
+ glTexDesc.fOrigin = kBottomLeft_GrSurfaceOrigin;
+ } else {
+ glTexDesc.fOrigin = desc.fOrigin;
+ }
+
+ GrGLTexture* texture = NULL;
+ if (renderTarget) {
+ GrGLRenderTarget::Desc glRTDesc;
+ glRTDesc.fRTFBOID = 0;
+ glRTDesc.fTexFBOID = 0;
+ glRTDesc.fMSColorRenderbufferID = 0;
+ glRTDesc.fConfig = desc.fConfig;
+ glRTDesc.fSampleCnt = desc.fSampleCnt;
+ glRTDesc.fOrigin = glTexDesc.fOrigin;
+ glRTDesc.fCheckAllocation = false;
+ if (!this->createRenderTargetObjects(glTexDesc.fWidth,
+ glTexDesc.fHeight,
+ glTexDesc.fTextureID,
+ &glRTDesc)) {
+ return NULL;
+ }
+ texture = SkNEW_ARGS(GrGLTexture, (this, glTexDesc, glRTDesc));
+ } else {
+ texture = SkNEW_ARGS(GrGLTexture, (this, glTexDesc));
+ }
+ if (NULL == texture) {
+ return NULL;
+ }
+
+ return texture;
+}
+
+GrRenderTarget* GrGpuGL::onWrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) {
+ GrGLRenderTarget::Desc glDesc;
+ glDesc.fConfig = desc.fConfig;
+ glDesc.fRTFBOID = static_cast<GrGLuint>(desc.fRenderTargetHandle);
+ glDesc.fMSColorRenderbufferID = 0;
+ glDesc.fTexFBOID = GrGLRenderTarget::kUnresolvableFBOID;
+ glDesc.fSampleCnt = desc.fSampleCnt;
+ glDesc.fIsWrapped = true;
+ glDesc.fCheckAllocation = false;
+
+ glDesc.fOrigin = resolve_origin(desc.fOrigin, true);
+ GrGLIRect viewport;
+ viewport.fLeft = 0;
+ viewport.fBottom = 0;
+ viewport.fWidth = desc.fWidth;
+ viewport.fHeight = desc.fHeight;
+
+ GrRenderTarget* tgt = SkNEW_ARGS(GrGLRenderTarget,
+ (this, glDesc, viewport));
+ if (desc.fStencilBits) {
+ GrGLStencilBuffer::Format format;
+ format.fInternalFormat = GrGLStencilBuffer::kUnknownInternalFormat;
+ format.fPacked = false;
+ format.fStencilBits = desc.fStencilBits;
+ format.fTotalBits = desc.fStencilBits;
+ static const bool kIsSBWrapped = false;
+ GrGLStencilBuffer* sb = SkNEW_ARGS(GrGLStencilBuffer,
+ (this,
+ kIsSBWrapped,
+ 0,
+ desc.fWidth,
+ desc.fHeight,
+ desc.fSampleCnt,
+ format));
+ tgt->setStencilBuffer(sb);
+ sb->unref();
+ }
+ return tgt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool GrGpuGL::onWriteTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes) {
+ if (NULL == buffer) {
+ return false;
+ }
+ GrGLTexture* glTex = static_cast<GrGLTexture*>(texture);
+
+ this->setScratchTextureUnit();
+ GL_CALL(BindTexture(GR_GL_TEXTURE_2D, glTex->textureID()));
+ GrGLTexture::Desc desc;
+ desc.fFlags = glTex->desc().fFlags;
+ desc.fWidth = glTex->width();
+ desc.fHeight = glTex->height();
+ desc.fConfig = glTex->config();
+ desc.fSampleCnt = glTex->desc().fSampleCnt;
+ desc.fTextureID = glTex->textureID();
+ desc.fOrigin = glTex->origin();
+
+ if (this->uploadTexData(desc, false,
+ left, top, width, height,
+ config, buffer, rowBytes)) {
+ texture->dirtyMipMaps(true);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+namespace {
+bool adjust_pixel_ops_params(int surfaceWidth,
+ int surfaceHeight,
+ size_t bpp,
+ int* left, int* top, int* width, int* height,
+ const void** data,
+ size_t* rowBytes) {
+ if (!*rowBytes) {
+ *rowBytes = *width * bpp;
+ }
+
+ SkIRect subRect = SkIRect::MakeXYWH(*left, *top, *width, *height);
+ SkIRect bounds = SkIRect::MakeWH(surfaceWidth, surfaceHeight);
+
+ if (!subRect.intersect(bounds)) {
+ return false;
+ }
+ *data = reinterpret_cast<const void*>(reinterpret_cast<intptr_t>(*data) +
+ (subRect.fTop - *top) * *rowBytes + (subRect.fLeft - *left) * bpp);
+
+ *left = subRect.fLeft;
+ *top = subRect.fTop;
+ *width = subRect.width();
+ *height = subRect.height();
+ return true;
+}
+
+GrGLenum check_alloc_error(const GrTextureDesc& desc, const GrGLInterface* interface) {
+ if (SkToBool(desc.fFlags & kCheckAllocation_GrTextureFlagBit)) {
+ return GR_GL_GET_ERROR(interface);
+ } else {
+ return CHECK_ALLOC_ERROR(interface);
+ }
+}
+
+}
+
+bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
+ bool isNewTexture,
+ int left, int top, int width, int height,
+ GrPixelConfig dataConfig,
+ const void* data,
+ size_t rowBytes) {
+ GrAssert(NULL != data || isNewTexture);
+
+ size_t bpp = GrBytesPerPixel(dataConfig);
+ if (!adjust_pixel_ops_params(desc.fWidth, desc.fHeight, bpp, &left, &top,
+ &width, &height, &data, &rowBytes)) {
+ return false;
+ }
+ size_t trimRowBytes = width * bpp;
+
+ // in case we need a temporary, trimmed copy of the src pixels
+ SkAutoSMalloc<128 * 128> tempStorage;
+
+ // paletted textures cannot be partially updated
+ bool useTexStorage = isNewTexture &&
+ desc.fConfig != kIndex_8_GrPixelConfig &&
+ this->glCaps().texStorageSupport();
+
+ if (useTexStorage && kDesktop_GrGLBinding == this->glBinding()) {
+ // 565 is not a sized internal format on desktop GL. So on desktop with
+ // 565 we always use an unsized internal format to let the system pick
+ // the best sized format to convert the 565 data to. Since TexStorage
+ // only allows sized internal formats we will instead use TexImage2D.
+ useTexStorage = desc.fConfig != kRGB_565_GrPixelConfig;
+ }
+
+ GrGLenum internalFormat;
+ GrGLenum externalFormat;
+ GrGLenum externalType;
+ // glTexStorage requires sized internal formats on both desktop and ES. ES
+ // glTexImage requires an unsized format.
+ if (!this->configToGLFormats(dataConfig, useTexStorage, &internalFormat,
+ &externalFormat, &externalType)) {
+ return false;
+ }
+
+ if (!isNewTexture && GR_GL_PALETTE8_RGBA8 == internalFormat) {
+ // paletted textures cannot be updated
+ return false;
+ }
+
+ /*
+ * check whether to allocate a temporary buffer for flipping y or
+ * because our srcData has extra bytes past each row. If so, we need
+ * to trim those off here, since GL ES may not let us specify
+ * GL_UNPACK_ROW_LENGTH.
+ */
+ bool restoreGLRowLength = false;
+ bool swFlipY = false;
+ bool glFlipY = false;
+ if (NULL != data) {
+ if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) {
+ if (this->glCaps().unpackFlipYSupport()) {
+ glFlipY = true;
+ } else {
+ swFlipY = true;
+ }
+ }
+ if (this->glCaps().unpackRowLengthSupport() && !swFlipY) {
+ // can't use this for flipping, only non-neg values allowed. :(
+ if (rowBytes != trimRowBytes) {
+ GrGLint rowLength = static_cast<GrGLint>(rowBytes / bpp);
+ GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowLength));
+ restoreGLRowLength = true;
+ }
+ } else {
+ if (trimRowBytes != rowBytes || swFlipY) {
+ // copy data into our new storage, skipping the trailing bytes
+ size_t trimSize = height * trimRowBytes;
+ const char* src = (const char*)data;
+ if (swFlipY) {
+ src += (height - 1) * rowBytes;
+ }
+ char* dst = (char*)tempStorage.reset(trimSize);
+ for (int y = 0; y < height; y++) {
+ memcpy(dst, src, trimRowBytes);
+ if (swFlipY) {
+ src -= rowBytes;
+ } else {
+ src += rowBytes;
+ }
+ dst += trimRowBytes;
+ }
+ // now point data to our copied version
+ data = tempStorage.get();
+ }
+ }
+ if (glFlipY) {
+ GL_CALL(PixelStorei(GR_GL_UNPACK_FLIP_Y, GR_GL_TRUE));
+ }
+ GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, static_cast<GrGLint>(bpp)));
+ }
+ bool succeeded = true;
+ if (isNewTexture &&
+ 0 == left && 0 == top &&
+ desc.fWidth == width && desc.fHeight == height) {
+ CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
+ if (useTexStorage) {
+ // We never resize or change formats of textures. We don't use
+ // mipmaps currently.
+ GL_ALLOC_CALL(this->glInterface(),
+ TexStorage2D(GR_GL_TEXTURE_2D,
+ 1, // levels
+ internalFormat,
+ desc.fWidth, desc.fHeight));
+ } else {
+ if (GR_GL_PALETTE8_RGBA8 == internalFormat) {
+ GrGLsizei imageSize = desc.fWidth * desc.fHeight +
+ kGrColorTableSize;
+ GL_ALLOC_CALL(this->glInterface(),
+ CompressedTexImage2D(GR_GL_TEXTURE_2D,
+ 0, // level
+ internalFormat,
+ desc.fWidth, desc.fHeight,
+ 0, // border
+ imageSize,
+ data));
+ } else {
+ GL_ALLOC_CALL(this->glInterface(),
+ TexImage2D(GR_GL_TEXTURE_2D,
+ 0, // level
+ internalFormat,
+ desc.fWidth, desc.fHeight,
+ 0, // border
+ externalFormat, externalType,
+ data));
+ }
+ }
+ GrGLenum error = check_alloc_error(desc, this->glInterface());
+ if (error != GR_GL_NO_ERROR) {
+ succeeded = false;
+ } else {
+ // if we have data and we used TexStorage to create the texture, we
+ // now upload with TexSubImage.
+ if (NULL != data && useTexStorage) {
+ GL_CALL(TexSubImage2D(GR_GL_TEXTURE_2D,
+ 0, // level
+ left, top,
+ width, height,
+ externalFormat, externalType,
+ data));
+ }
+ }
+ } else {
+ if (swFlipY || glFlipY) {
+ top = desc.fHeight - (top + height);
+ }
+ GL_CALL(TexSubImage2D(GR_GL_TEXTURE_2D,
+ 0, // level
+ left, top,
+ width, height,
+ externalFormat, externalType, data));
+ }
+
+ if (restoreGLRowLength) {
+ GrAssert(this->glCaps().unpackRowLengthSupport());
+ GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0));
+ }
+ if (glFlipY) {
+ GL_CALL(PixelStorei(GR_GL_UNPACK_FLIP_Y, GR_GL_FALSE));
+ }
+ return succeeded;
+}
+
+namespace {
+bool renderbuffer_storage_msaa(GrGLContext& ctx,
+ int sampleCount,
+ GrGLenum format,
+ int width, int height) {
+ CLEAR_ERROR_BEFORE_ALLOC(ctx.interface());
+ GrAssert(GrGLCaps::kNone_MSFBOType != ctx.info().caps()->msFBOType());
+ bool created = false;
+ if (GrGLCaps::kNVDesktop_CoverageAAType ==
+ ctx.info().caps()->coverageAAType()) {
+ const GrGLCaps::MSAACoverageMode& mode =
+ ctx.info().caps()->getMSAACoverageMode(sampleCount);
+ GL_ALLOC_CALL(ctx.interface(),
+ RenderbufferStorageMultisampleCoverage(GR_GL_RENDERBUFFER,
+ mode.fCoverageSampleCnt,
+ mode.fColorSampleCnt,
+ format,
+ width, height));
+ created = (GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(ctx.interface()));
+ }
+ if (!created) {
+ GL_ALLOC_CALL(ctx.interface(),
+ RenderbufferStorageMultisample(GR_GL_RENDERBUFFER,
+ sampleCount,
+ format,
+ width, height));
+ created = (GR_GL_NO_ERROR == CHECK_ALLOC_ERROR(ctx.interface()));
+ }
+ return created;
+}
+}
+
+bool GrGpuGL::createRenderTargetObjects(int width, int height,
+ GrGLuint texID,
+ GrGLRenderTarget::Desc* desc) {
+ desc->fMSColorRenderbufferID = 0;
+ desc->fRTFBOID = 0;
+ desc->fTexFBOID = 0;
+ desc->fIsWrapped = false;
+
+ GrGLenum status;
+
+ GrGLenum msColorFormat = 0; // suppress warning
+
+ if (desc->fSampleCnt > 0 && GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType()) {
+ goto FAILED;
+ }
+
+ GL_CALL(GenFramebuffers(1, &desc->fTexFBOID));
+ if (!desc->fTexFBOID) {
+ goto FAILED;
+ }
+
+
+ // If we are using multisampling we will create two FBOS. We render to one and then resolve to
+ // the texture bound to the other. The exception is the IMG multisample extension. With this
+ // extension the texture is multisampled when rendered to and then auto-resolves it when it is
+ // rendered from.
+ if (desc->fSampleCnt > 0 && this->glCaps().usesMSAARenderBuffers()) {
+ GL_CALL(GenFramebuffers(1, &desc->fRTFBOID));
+ GL_CALL(GenRenderbuffers(1, &desc->fMSColorRenderbufferID));
+ if (!desc->fRTFBOID ||
+ !desc->fMSColorRenderbufferID ||
+ !this->configToGLFormats(desc->fConfig,
+ // GLES requires sized internal formats
+ kES2_GrGLBinding == this->glBinding(),
+ &msColorFormat,
+ NULL,
+ NULL)) {
+ goto FAILED;
+ }
+ } else {
+ desc->fRTFBOID = desc->fTexFBOID;
+ }
+
+ // below here we may bind the FBO
+ fHWBoundRenderTarget = NULL;
+ if (desc->fRTFBOID != desc->fTexFBOID) {
+ GrAssert(desc->fSampleCnt > 0);
+ GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER,
+ desc->fMSColorRenderbufferID));
+ if (!renderbuffer_storage_msaa(fGLContext,
+ desc->fSampleCnt,
+ msColorFormat,
+ width, height)) {
+ goto FAILED;
+ }
+ GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, desc->fRTFBOID));
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_COLOR_ATTACHMENT0,
+ GR_GL_RENDERBUFFER,
+ desc->fMSColorRenderbufferID));
+ if (desc->fCheckAllocation ||
+ !this->glCaps().isConfigVerifiedColorAttachment(desc->fConfig)) {
+ GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+ if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
+ goto FAILED;
+ }
+ fGLContext.info().caps()->markConfigAsValidColorAttachment(desc->fConfig);
+ }
+ }
+ GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, desc->fTexFBOID));
+
+ if (this->glCaps().usesImplicitMSAAResolve() && desc->fSampleCnt > 0) {
+ GL_CALL(FramebufferTexture2DMultisample(GR_GL_FRAMEBUFFER,
+ GR_GL_COLOR_ATTACHMENT0,
+ GR_GL_TEXTURE_2D,
+ texID, 0, desc->fSampleCnt));
+ } else {
+ GL_CALL(FramebufferTexture2D(GR_GL_FRAMEBUFFER,
+ GR_GL_COLOR_ATTACHMENT0,
+ GR_GL_TEXTURE_2D,
+ texID, 0));
+ }
+ if (desc->fCheckAllocation ||
+ !this->glCaps().isConfigVerifiedColorAttachment(desc->fConfig)) {
+ GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+ if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
+ goto FAILED;
+ }
+ fGLContext.info().caps()->markConfigAsValidColorAttachment(desc->fConfig);
+ }
+
+ return true;
+
+FAILED:
+ if (desc->fMSColorRenderbufferID) {
+ GL_CALL(DeleteRenderbuffers(1, &desc->fMSColorRenderbufferID));
+ }
+ if (desc->fRTFBOID != desc->fTexFBOID) {
+ GL_CALL(DeleteFramebuffers(1, &desc->fRTFBOID));
+ }
+ if (desc->fTexFBOID) {
+ GL_CALL(DeleteFramebuffers(1, &desc->fTexFBOID));
+ }
+ return false;
+}
+
+// good to set a break-point here to know when createTexture fails
+static GrTexture* return_null_texture() {
+// GrAssert(!"null texture");
+ return NULL;
+}
+
+#if 0 && GR_DEBUG
+static size_t as_size_t(int x) {
+ return x;
+}
+#endif
+
+GrTexture* GrGpuGL::onCreateTexture(const GrTextureDesc& desc,
+ const void* srcData,
+ size_t rowBytes) {
+
+ GrGLTexture::Desc glTexDesc;
+ GrGLRenderTarget::Desc glRTDesc;
+
+ // Attempt to catch un- or wrongly initialized sample counts;
+ GrAssert(desc.fSampleCnt >= 0 && desc.fSampleCnt <= 64);
+ // We fail if the MSAA was requested and is not available.
+ if (GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType() && desc.fSampleCnt) {
+ //GrPrintf("MSAA RT requested but not supported on this platform.");
+ return return_null_texture();
+ }
+ // If the sample count exceeds the max then we clamp it.
+ glTexDesc.fSampleCnt = GrMin(desc.fSampleCnt, this->caps()->maxSampleCount());
+
+ glTexDesc.fFlags = desc.fFlags;
+ glTexDesc.fWidth = desc.fWidth;
+ glTexDesc.fHeight = desc.fHeight;
+ glTexDesc.fConfig = desc.fConfig;
+ glTexDesc.fIsWrapped = false;
+
+ glRTDesc.fMSColorRenderbufferID = 0;
+ glRTDesc.fRTFBOID = 0;
+ glRTDesc.fTexFBOID = 0;
+ glRTDesc.fIsWrapped = false;
+ glRTDesc.fConfig = glTexDesc.fConfig;
+ glRTDesc.fCheckAllocation = SkToBool(desc.fFlags & kCheckAllocation_GrTextureFlagBit);
+
+ bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrTextureFlagBit);
+
+ glTexDesc.fOrigin = resolve_origin(desc.fOrigin, renderTarget);
+ glRTDesc.fOrigin = glTexDesc.fOrigin;
+
+ glRTDesc.fSampleCnt = glTexDesc.fSampleCnt;
+ if (GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType() &&
+ desc.fSampleCnt) {
+ //GrPrintf("MSAA RT requested but not supported on this platform.");
+ return return_null_texture();
+ }
+
+ if (renderTarget) {
+ int maxRTSize = this->caps()->maxRenderTargetSize();
+ if (glTexDesc.fWidth > maxRTSize || glTexDesc.fHeight > maxRTSize) {
+ return return_null_texture();
+ }
+ } else {
+ int maxSize = this->caps()->maxTextureSize();
+ if (glTexDesc.fWidth > maxSize || glTexDesc.fHeight > maxSize) {
+ return return_null_texture();
+ }
+ }
+
+ GL_CALL(GenTextures(1, &glTexDesc.fTextureID));
+
+ if (!glTexDesc.fTextureID) {
+ return return_null_texture();
+ }
+
+ this->setScratchTextureUnit();
+ GL_CALL(BindTexture(GR_GL_TEXTURE_2D, glTexDesc.fTextureID));
+
+ if (renderTarget && this->glCaps().textureUsageSupport()) {
+ // provides a hint about how this texture will be used
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_USAGE,
+ GR_GL_FRAMEBUFFER_ATTACHMENT));
+ }
+
+ // Some drivers like to know filter/wrap before seeing glTexImage2D. Some
+ // drivers have a bug where an FBO won't be complete if it includes a
+ // texture that is not mipmap complete (considering the filter in use).
+ GrGLTexture::TexParams initialTexParams;
+ // we only set a subset here so invalidate first
+ initialTexParams.invalidate();
+ initialTexParams.fMinFilter = GR_GL_NEAREST;
+ initialTexParams.fMagFilter = GR_GL_NEAREST;
+ initialTexParams.fWrapS = GR_GL_CLAMP_TO_EDGE;
+ initialTexParams.fWrapT = GR_GL_CLAMP_TO_EDGE;
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_MAG_FILTER,
+ initialTexParams.fMagFilter));
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_MIN_FILTER,
+ initialTexParams.fMinFilter));
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_WRAP_S,
+ initialTexParams.fWrapS));
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_WRAP_T,
+ initialTexParams.fWrapT));
+ if (!this->uploadTexData(glTexDesc, true, 0, 0,
+ glTexDesc.fWidth, glTexDesc.fHeight,
+ desc.fConfig, srcData, rowBytes)) {
+ GL_CALL(DeleteTextures(1, &glTexDesc.fTextureID));
+ return return_null_texture();
+ }
+
+ GrGLTexture* tex;
+ if (renderTarget) {
+ // unbind the texture from the texture unit before binding it to the frame buffer
+ GL_CALL(BindTexture(GR_GL_TEXTURE_2D, 0));
+
+ if (!this->createRenderTargetObjects(glTexDesc.fWidth,
+ glTexDesc.fHeight,
+ glTexDesc.fTextureID,
+ &glRTDesc)) {
+ GL_CALL(DeleteTextures(1, &glTexDesc.fTextureID));
+ return return_null_texture();
+ }
+ tex = SkNEW_ARGS(GrGLTexture, (this, glTexDesc, glRTDesc));
+ } else {
+ tex = SkNEW_ARGS(GrGLTexture, (this, glTexDesc));
+ }
+ tex->setCachedTexParams(initialTexParams, this->getResetTimestamp());
+#ifdef TRACE_TEXTURE_CREATION
+ GrPrintf("--- new texture [%d] size=(%d %d) config=%d\n",
+ glTexDesc.fTextureID, desc.fWidth, desc.fHeight, desc.fConfig);
+#endif
+ return tex;
+}
+
+namespace {
+
+const GrGLuint kUnknownBitCount = GrGLStencilBuffer::kUnknownBitCount;
+
+void inline get_stencil_rb_sizes(const GrGLInterface* gl,
+ GrGLStencilBuffer::Format* format) {
+
+ // we shouldn't ever know one size and not the other
+ GrAssert((kUnknownBitCount == format->fStencilBits) ==
+ (kUnknownBitCount == format->fTotalBits));
+ if (kUnknownBitCount == format->fStencilBits) {
+ GR_GL_GetRenderbufferParameteriv(gl, GR_GL_RENDERBUFFER,
+ GR_GL_RENDERBUFFER_STENCIL_SIZE,
+ (GrGLint*)&format->fStencilBits);
+ if (format->fPacked) {
+ GR_GL_GetRenderbufferParameteriv(gl, GR_GL_RENDERBUFFER,
+ GR_GL_RENDERBUFFER_DEPTH_SIZE,
+ (GrGLint*)&format->fTotalBits);
+ format->fTotalBits += format->fStencilBits;
+ } else {
+ format->fTotalBits = format->fStencilBits;
+ }
+ }
+}
+}
+
+bool GrGpuGL::createStencilBufferForRenderTarget(GrRenderTarget* rt,
+ int width, int height) {
+
+ // All internally created RTs are also textures. We don't create
+ // SBs for a client's standalone RT (that is a RT that isn't also a texture).
+ GrAssert(rt->asTexture());
+ GrAssert(width >= rt->width());
+ GrAssert(height >= rt->height());
+
+ int samples = rt->numSamples();
+ GrGLuint sbID;
+ GL_CALL(GenRenderbuffers(1, &sbID));
+ if (!sbID) {
+ return false;
+ }
+
+ int stencilFmtCnt = this->glCaps().stencilFormats().count();
+ for (int i = 0; i < stencilFmtCnt; ++i) {
+ GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, sbID));
+ // we start with the last stencil format that succeeded in hopes
+ // that we won't go through this loop more than once after the
+ // first (painful) stencil creation.
+ int sIdx = (i + fLastSuccessfulStencilFmtIdx) % stencilFmtCnt;
+ const GrGLCaps::StencilFormat& sFmt =
+ this->glCaps().stencilFormats()[sIdx];
+ CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
+ // we do this "if" so that we don't call the multisample
+ // version on a GL that doesn't have an MSAA extension.
+ bool created;
+ if (samples > 0) {
+ created = renderbuffer_storage_msaa(fGLContext,
+ samples,
+ sFmt.fInternalFormat,
+ width, height);
+ } else {
+ GL_ALLOC_CALL(this->glInterface(),
+ RenderbufferStorage(GR_GL_RENDERBUFFER,
+ sFmt.fInternalFormat,
+ width, height));
+ created =
+ (GR_GL_NO_ERROR == check_alloc_error(rt->desc(), this->glInterface()));
+ }
+ if (created) {
+ // After sized formats we attempt an unsized format and take
+ // whatever sizes GL gives us. In that case we query for the size.
+ GrGLStencilBuffer::Format format = sFmt;
+ get_stencil_rb_sizes(this->glInterface(), &format);
+ static const bool kIsWrapped = false;
+ SkAutoTUnref<GrStencilBuffer> sb(SkNEW_ARGS(GrGLStencilBuffer,
+ (this, kIsWrapped, sbID, width, height,
+ samples, format)));
+ if (this->attachStencilBufferToRenderTarget(sb, rt)) {
+ fLastSuccessfulStencilFmtIdx = sIdx;
+ sb->transferToCache();
+ rt->setStencilBuffer(sb);
+ return true;
+ }
+ sb->abandon(); // otherwise we lose sbID
+ }
+ }
+ GL_CALL(DeleteRenderbuffers(1, &sbID));
+ return false;
+}
+
+bool GrGpuGL::attachStencilBufferToRenderTarget(GrStencilBuffer* sb, GrRenderTarget* rt) {
+ GrGLRenderTarget* glrt = (GrGLRenderTarget*) rt;
+
+ GrGLuint fbo = glrt->renderFBOID();
+
+ if (NULL == sb) {
+ if (NULL != rt->getStencilBuffer()) {
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_STENCIL_ATTACHMENT,
+ GR_GL_RENDERBUFFER, 0));
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_DEPTH_ATTACHMENT,
+ GR_GL_RENDERBUFFER, 0));
+#if GR_DEBUG
+ GrGLenum status;
+ GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+ GrAssert(GR_GL_FRAMEBUFFER_COMPLETE == status);
+#endif
+ }
+ return true;
+ } else {
+ GrGLStencilBuffer* glsb = static_cast<GrGLStencilBuffer*>(sb);
+ GrGLuint rb = glsb->renderbufferID();
+
+ fHWBoundRenderTarget = NULL;
+ GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, fbo));
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_STENCIL_ATTACHMENT,
+ GR_GL_RENDERBUFFER, rb));
+ if (glsb->format().fPacked) {
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_DEPTH_ATTACHMENT,
+ GR_GL_RENDERBUFFER, rb));
+ } else {
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_DEPTH_ATTACHMENT,
+ GR_GL_RENDERBUFFER, 0));
+ }
+
+ GrGLenum status;
+ if (!this->glCaps().isColorConfigAndStencilFormatVerified(rt->config(), glsb->format())) {
+ GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+ if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_STENCIL_ATTACHMENT,
+ GR_GL_RENDERBUFFER, 0));
+ if (glsb->format().fPacked) {
+ GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_DEPTH_ATTACHMENT,
+ GR_GL_RENDERBUFFER, 0));
+ }
+ return false;
+ } else {
+ fGLContext.info().caps()->markColorConfigAndStencilFormatAsVerified(
+ rt->config(),
+ glsb->format());
+ }
+ }
+ return true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+GrVertexBuffer* GrGpuGL::onCreateVertexBuffer(uint32_t size, bool dynamic) {
+ GrGLVertexBuffer::Desc desc;
+ desc.fDynamic = dynamic;
+ desc.fSizeInBytes = size;
+ desc.fIsWrapped = false;
+
+ if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) {
+ desc.fID = 0;
+ GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this, desc));
+ return vertexBuffer;
+ } else {
+ GL_CALL(GenBuffers(1, &desc.fID));
+ if (desc.fID) {
+ fHWGeometryState.setVertexBufferID(this, desc.fID);
+ CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
+ // make sure driver can allocate memory for this buffer
+ GL_ALLOC_CALL(this->glInterface(),
+ BufferData(GR_GL_ARRAY_BUFFER,
+ desc.fSizeInBytes,
+ NULL, // data ptr
+ desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_STATIC_DRAW));
+ if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) {
+ GL_CALL(DeleteBuffers(1, &desc.fID));
+ this->notifyVertexBufferDelete(desc.fID);
+ return NULL;
+ }
+ GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this, desc));
+ return vertexBuffer;
+ }
+ return NULL;
+ }
+}
+
+GrIndexBuffer* GrGpuGL::onCreateIndexBuffer(uint32_t size, bool dynamic) {
+ GrGLIndexBuffer::Desc desc;
+ desc.fDynamic = dynamic;
+ desc.fSizeInBytes = size;
+ desc.fIsWrapped = false;
+
+ if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) {
+ desc.fID = 0;
+ GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc));
+ return indexBuffer;
+ } else {
+ GL_CALL(GenBuffers(1, &desc.fID));
+ if (desc.fID) {
+ fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID);
+ CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
+ // make sure driver can allocate memory for this buffer
+ GL_ALLOC_CALL(this->glInterface(),
+ BufferData(GR_GL_ELEMENT_ARRAY_BUFFER,
+ desc.fSizeInBytes,
+ NULL, // data ptr
+ desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_STATIC_DRAW));
+ if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) {
+ GL_CALL(DeleteBuffers(1, &desc.fID));
+ this->notifyIndexBufferDelete(desc.fID);
+ return NULL;
+ }
+ GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc));
+ return indexBuffer;
+ }
+ return NULL;
+ }
+}
+
+GrPath* GrGpuGL::onCreatePath(const SkPath& inPath) {
+ GrAssert(this->caps()->pathStencilingSupport());
+ return SkNEW_ARGS(GrGLPath, (this, inPath));
+}
+
+void GrGpuGL::flushScissor() {
+ const GrDrawState& drawState = this->getDrawState();
+ const GrGLRenderTarget* rt =
+ static_cast<const GrGLRenderTarget*>(drawState.getRenderTarget());
+
+ GrAssert(NULL != rt);
+ const GrGLIRect& vp = rt->getViewport();
+
+ if (fScissorState.fEnabled) {
+ GrGLIRect scissor;
+ scissor.setRelativeTo(vp,
+ fScissorState.fRect.fLeft,
+ fScissorState.fRect.fTop,
+ fScissorState.fRect.width(),
+ fScissorState.fRect.height(),
+ rt->origin());
+ // if the scissor fully contains the viewport then we fall through and
+ // disable the scissor test.
+ if (!scissor.contains(vp)) {
+ if (fHWScissorSettings.fRect != scissor) {
+ scissor.pushToGLScissor(this->glInterface());
+ fHWScissorSettings.fRect = scissor;
+ }
+ if (kYes_TriState != fHWScissorSettings.fEnabled) {
+ GL_CALL(Enable(GR_GL_SCISSOR_TEST));
+ fHWScissorSettings.fEnabled = kYes_TriState;
+ }
+ return;
+ }
+ }
+ if (kNo_TriState != fHWScissorSettings.fEnabled) {
+ GL_CALL(Disable(GR_GL_SCISSOR_TEST));
+ fHWScissorSettings.fEnabled = kNo_TriState;
+ return;
+ }
+}
+
+void GrGpuGL::onClear(const SkIRect* rect, GrColor color) {
+ const GrDrawState& drawState = this->getDrawState();
+ const GrRenderTarget* rt = drawState.getRenderTarget();
+ // parent class should never let us get here with no RT
+ GrAssert(NULL != rt);
+
+ SkIRect clippedRect;
+ if (NULL != rect) {
+ // flushScissor expects rect to be clipped to the target.
+ clippedRect = *rect;
+ SkIRect rtRect = SkIRect::MakeWH(rt->width(), rt->height());
+ if (clippedRect.intersect(rtRect)) {
+ rect = &clippedRect;
+ } else {
+ return;
+ }
+ }
+ this->flushRenderTarget(rect);
+ GrAutoTRestore<ScissorState> asr(&fScissorState);
+ fScissorState.fEnabled = (NULL != rect);
+ if (fScissorState.fEnabled) {
+ fScissorState.fRect = *rect;
+ }
+ this->flushScissor();
+
+ GrGLfloat r, g, b, a;
+ static const GrGLfloat scale255 = 1.f / 255.f;
+ a = GrColorUnpackA(color) * scale255;
+ GrGLfloat scaleRGB = scale255;
+ r = GrColorUnpackR(color) * scaleRGB;
+ g = GrColorUnpackG(color) * scaleRGB;
+ b = GrColorUnpackB(color) * scaleRGB;
+
+ GL_CALL(ColorMask(GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE));
+ fHWWriteToColor = kYes_TriState;
+ GL_CALL(ClearColor(r, g, b, a));
+ GL_CALL(Clear(GR_GL_COLOR_BUFFER_BIT));
+}
+
+void GrGpuGL::clearStencil() {
+ if (NULL == this->getDrawState().getRenderTarget()) {
+ return;
+ }
+
+ this->flushRenderTarget(&SkIRect::EmptyIRect());
+
+ GrAutoTRestore<ScissorState> asr(&fScissorState);
+ fScissorState.fEnabled = false;
+ this->flushScissor();
+
+ GL_CALL(StencilMask(0xffffffff));
+ GL_CALL(ClearStencil(0));
+ GL_CALL(Clear(GR_GL_STENCIL_BUFFER_BIT));
+ fHWStencilSettings.invalidate();
+}
+
+void GrGpuGL::clearStencilClip(const SkIRect& rect, bool insideClip) {
+ const GrDrawState& drawState = this->getDrawState();
+ const GrRenderTarget* rt = drawState.getRenderTarget();
+ GrAssert(NULL != rt);
+
+ // this should only be called internally when we know we have a
+ // stencil buffer.
+ GrAssert(NULL != rt->getStencilBuffer());
+ GrGLint stencilBitCount = rt->getStencilBuffer()->bits();
+#if 0
+ GrAssert(stencilBitCount > 0);
+ GrGLint clipStencilMask = (1 << (stencilBitCount - 1));
+#else
+ // we could just clear the clip bit but when we go through
+ // ANGLE a partial stencil mask will cause clears to be
+ // turned into draws. Our contract on GrDrawTarget says that
+ // changing the clip between stencil passes may or may not
+ // zero the client's clip bits. So we just clear the whole thing.
+ static const GrGLint clipStencilMask = ~0;
+#endif
+ GrGLint value;
+ if (insideClip) {
+ value = (1 << (stencilBitCount - 1));
+ } else {
+ value = 0;
+ }
+ this->flushRenderTarget(&SkIRect::EmptyIRect());
+
+ GrAutoTRestore<ScissorState> asr(&fScissorState);
+ fScissorState.fEnabled = true;
+ fScissorState.fRect = rect;
+ this->flushScissor();
+
+ GL_CALL(StencilMask((uint32_t) clipStencilMask));
+ GL_CALL(ClearStencil(value));
+ GL_CALL(Clear(GR_GL_STENCIL_BUFFER_BIT));
+ fHWStencilSettings.invalidate();
+}
+
+void GrGpuGL::onForceRenderTargetFlush() {
+ this->flushRenderTarget(&SkIRect::EmptyIRect());
+}
+
+bool GrGpuGL::readPixelsWillPayForYFlip(GrRenderTarget* renderTarget,
+ int left, int top,
+ int width, int height,
+ GrPixelConfig config,
+ size_t rowBytes) const {
+ // If this rendertarget is aready TopLeft, we don't need to flip.
+ if (kTopLeft_GrSurfaceOrigin == renderTarget->origin()) {
+ return false;
+ }
+
+ // if GL can do the flip then we'll never pay for it.
+ if (this->glCaps().packFlipYSupport()) {
+ return false;
+ }
+
+ // If we have to do memcpy to handle non-trim rowBytes then we
+ // get the flip for free. Otherwise it costs.
+ if (this->glCaps().packRowLengthSupport()) {
+ return true;
+ }
+ // If we have to do memcpys to handle rowBytes then y-flip is free
+ // Note the rowBytes might be tight to the passed in data, but if data
+ // gets clipped in x to the target the rowBytes will no longer be tight.
+ if (left >= 0 && (left + width) < renderTarget->width()) {
+ return 0 == rowBytes ||
+ GrBytesPerPixel(config) * width == rowBytes;
+ } else {
+ return false;
+ }
+}
+
+bool GrGpuGL::onReadPixels(GrRenderTarget* target,
+ int left, int top,
+ int width, int height,
+ GrPixelConfig config,
+ void* buffer,
+ size_t rowBytes) {
+ GrGLenum format;
+ GrGLenum type;
+ bool flipY = kBottomLeft_GrSurfaceOrigin == target->origin();
+ if (!this->configToGLFormats(config, false, NULL, &format, &type)) {
+ return false;
+ }
+ size_t bpp = GrBytesPerPixel(config);
+ if (!adjust_pixel_ops_params(target->width(), target->height(), bpp,
+ &left, &top, &width, &height,
+ const_cast<const void**>(&buffer),
+ &rowBytes)) {
+ return false;
+ }
+
+ // resolve the render target if necessary
+ GrGLRenderTarget* tgt = static_cast<GrGLRenderTarget*>(target);
+ GrDrawState::AutoRenderTargetRestore artr;
+ switch (tgt->getResolveType()) {
+ case GrGLRenderTarget::kCantResolve_ResolveType:
+ return false;
+ case GrGLRenderTarget::kAutoResolves_ResolveType:
+ artr.set(this->drawState(), target);
+ this->flushRenderTarget(&SkIRect::EmptyIRect());
+ break;
+ case GrGLRenderTarget::kCanResolve_ResolveType:
+ this->onResolveRenderTarget(tgt);
+ // we don't track the state of the READ FBO ID.
+ GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER,
+ tgt->textureFBOID()));
+ break;
+ default:
+ GrCrash("Unknown resolve type");
+ }
+
+ const GrGLIRect& glvp = tgt->getViewport();
+
+ // the read rect is viewport-relative
+ GrGLIRect readRect;
+ readRect.setRelativeTo(glvp, left, top, width, height, target->origin());
+
+ size_t tightRowBytes = bpp * width;
+ if (0 == rowBytes) {
+ rowBytes = tightRowBytes;
+ }
+ size_t readDstRowBytes = tightRowBytes;
+ void* readDst = buffer;
+
+ // determine if GL can read using the passed rowBytes or if we need
+ // a scratch buffer.
+ SkAutoSMalloc<32 * sizeof(GrColor)> scratch;
+ if (rowBytes != tightRowBytes) {
+ if (this->glCaps().packRowLengthSupport()) {
+ GrAssert(!(rowBytes % sizeof(GrColor)));
+ GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, rowBytes / sizeof(GrColor)));
+ readDstRowBytes = rowBytes;
+ } else {
+ scratch.reset(tightRowBytes * height);
+ readDst = scratch.get();
+ }
+ }
+ if (flipY && this->glCaps().packFlipYSupport()) {
+ GL_CALL(PixelStorei(GR_GL_PACK_REVERSE_ROW_ORDER, 1));
+ }
+ GL_CALL(ReadPixels(readRect.fLeft, readRect.fBottom,
+ readRect.fWidth, readRect.fHeight,
+ format, type, readDst));
+ if (readDstRowBytes != tightRowBytes) {
+ GrAssert(this->glCaps().packRowLengthSupport());
+ GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0));
+ }
+ if (flipY && this->glCaps().packFlipYSupport()) {
+ GL_CALL(PixelStorei(GR_GL_PACK_REVERSE_ROW_ORDER, 0));
+ flipY = false;
+ }
+
+ // now reverse the order of the rows, since GL's are bottom-to-top, but our
+ // API presents top-to-bottom. We must preserve the padding contents. Note
+ // that the above readPixels did not overwrite the padding.
+ if (readDst == buffer) {
+ GrAssert(rowBytes == readDstRowBytes);
+ if (flipY) {
+ scratch.reset(tightRowBytes);
+ void* tmpRow = scratch.get();
+ // flip y in-place by rows
+ const int halfY = height >> 1;
+ char* top = reinterpret_cast<char*>(buffer);
+ char* bottom = top + (height - 1) * rowBytes;
+ for (int y = 0; y < halfY; y++) {
+ memcpy(tmpRow, top, tightRowBytes);
+ memcpy(top, bottom, tightRowBytes);
+ memcpy(bottom, tmpRow, tightRowBytes);
+ top += rowBytes;
+ bottom -= rowBytes;
+ }
+ }
+ } else {
+ GrAssert(readDst != buffer); GrAssert(rowBytes != tightRowBytes);
+ // copy from readDst to buffer while flipping y
+ // const int halfY = height >> 1;
+ const char* src = reinterpret_cast<const char*>(readDst);
+ char* dst = reinterpret_cast<char*>(buffer);
+ if (flipY) {
+ dst += (height-1) * rowBytes;
+ }
+ for (int y = 0; y < height; y++) {
+ memcpy(dst, src, tightRowBytes);
+ src += readDstRowBytes;
+ if (!flipY) {
+ dst += rowBytes;
+ } else {
+ dst -= rowBytes;
+ }
+ }
+ }
+ return true;
+}
+
+void GrGpuGL::flushRenderTarget(const SkIRect* bound) {
+
+ GrGLRenderTarget* rt =
+ static_cast<GrGLRenderTarget*>(this->drawState()->getRenderTarget());
+ GrAssert(NULL != rt);
+
+ if (fHWBoundRenderTarget != rt) {
+ GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, rt->renderFBOID()));
+#if GR_DEBUG
+ GrGLenum status;
+ GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+ if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
+ GrPrintf("GrGpuGL::flushRenderTarget glCheckFramebufferStatus %x\n", status);
+ }
+#endif
+ fHWBoundRenderTarget = rt;
+ const GrGLIRect& vp = rt->getViewport();
+ if (fHWViewport != vp) {
+ vp.pushToGLViewport(this->glInterface());
+ fHWViewport = vp;
+ }
+ }
+ if (NULL == bound || !bound->isEmpty()) {
+ rt->flagAsNeedingResolve(bound);
+ }
+
+ GrTexture *texture = rt->asTexture();
+ if (texture) {
+ texture->dirtyMipMaps(true);
+ }
+}
+
+GrGLenum gPrimitiveType2GLMode[] = {
+ GR_GL_TRIANGLES,
+ GR_GL_TRIANGLE_STRIP,
+ GR_GL_TRIANGLE_FAN,
+ GR_GL_POINTS,
+ GR_GL_LINES,
+ GR_GL_LINE_STRIP
+};
+
+#define SWAP_PER_DRAW 0
+
+#if SWAP_PER_DRAW
+ #if GR_MAC_BUILD
+ #include <AGL/agl.h>
+ #elif GR_WIN32_BUILD
+ #include <gl/GL.h>
+ void SwapBuf() {
+ DWORD procID = GetCurrentProcessId();
+ HWND hwnd = GetTopWindow(GetDesktopWindow());
+ while(hwnd) {
+ DWORD wndProcID = 0;
+ GetWindowThreadProcessId(hwnd, &wndProcID);
+ if(wndProcID == procID) {
+ SwapBuffers(GetDC(hwnd));
+ }
+ hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
+ }
+ }
+ #endif
+#endif
+
+void GrGpuGL::onGpuDraw(const DrawInfo& info) {
+ size_t indexOffsetInBytes;
+ this->setupGeometry(info, &indexOffsetInBytes);
+
+ GrAssert((size_t)info.primitiveType() < GR_ARRAY_COUNT(gPrimitiveType2GLMode));
+
+ if (info.isIndexed()) {
+ GrGLvoid* indices =
+ reinterpret_cast<GrGLvoid*>(indexOffsetInBytes + sizeof(uint16_t) * info.startIndex());
+ // info.startVertex() was accounted for by setupGeometry.
+ GL_CALL(DrawElements(gPrimitiveType2GLMode[info.primitiveType()],
+ info.indexCount(),
+ GR_GL_UNSIGNED_SHORT,
+ indices));
+ } else {
+ // Pass 0 for parameter first. We have to adjust glVertexAttribPointer() to account for
+ // startVertex in the DrawElements case. So we always rely on setupGeometry to have
+ // accounted for startVertex.
+ GL_CALL(DrawArrays(gPrimitiveType2GLMode[info.primitiveType()], 0, info.vertexCount()));
+ }
+#if SWAP_PER_DRAW
+ glFlush();
+ #if GR_MAC_BUILD
+ aglSwapBuffers(aglGetCurrentContext());
+ int set_a_break_pt_here = 9;
+ aglSwapBuffers(aglGetCurrentContext());
+ #elif GR_WIN32_BUILD
+ SwapBuf();
+ int set_a_break_pt_here = 9;
+ SwapBuf();
+ #endif
+#endif
+}
+
+namespace {
+
+static const uint16_t kOnes16 = static_cast<uint16_t>(~0);
+const GrStencilSettings& winding_nv_path_stencil_settings() {
+ GR_STATIC_CONST_SAME_STENCIL_STRUCT(gSettings,
+ kIncClamp_StencilOp,
+ kIncClamp_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ kOnes16, kOnes16, kOnes16);
+ return *GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(&gSettings);
+}
+const GrStencilSettings& even_odd_nv_path_stencil_settings() {
+ GR_STATIC_CONST_SAME_STENCIL_STRUCT(gSettings,
+ kInvert_StencilOp,
+ kInvert_StencilOp,
+ kAlwaysIfInClip_StencilFunc,
+ kOnes16, kOnes16, kOnes16);
+ return *GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(&gSettings);
+}
+}
+
+void GrGpuGL::setStencilPathSettings(const GrPath&,
+ SkPath::FillType fill,
+ GrStencilSettings* settings) {
+ switch (fill) {
+ case SkPath::kEvenOdd_FillType:
+ *settings = even_odd_nv_path_stencil_settings();
+ return;
+ case SkPath::kWinding_FillType:
+ *settings = winding_nv_path_stencil_settings();
+ return;
+ default:
+ GrCrash("Unexpected path fill.");
+ }
+}
+
+void GrGpuGL::onGpuStencilPath(const GrPath* path, SkPath::FillType fill) {
+ GrAssert(this->caps()->pathStencilingSupport());
+
+ GrGLuint id = static_cast<const GrGLPath*>(path)->pathID();
+ GrDrawState* drawState = this->drawState();
+ GrAssert(NULL != drawState->getRenderTarget());
+ if (NULL == drawState->getRenderTarget()->getStencilBuffer()) {
+ return;
+ }
+
+ // Decide how to manipulate the stencil buffer based on the fill rule.
+ // Also, assert that the stencil settings we set in setStencilPathSettings
+ // are present.
+ GrAssert(!fStencilSettings.isTwoSided());
+ GrGLenum fillMode;
+ switch (fill) {
+ 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 SkPath::kEvenOdd_FillType:
+ fillMode = GR_GL_INVERT;
+ GrAssert(kInvert_StencilOp ==
+ fStencilSettings.passOp(GrStencilSettings::kFront_Face));
+ GrAssert(kInvert_StencilOp ==
+ fStencilSettings.failOp(GrStencilSettings::kFront_Face));
+ break;
+ default:
+ // Only the above two fill rules are allowed.
+ GrCrash("Unexpected path fill.");
+ return; // suppress unused var warning.
+ }
+ GrGLint writeMask = fStencilSettings.writeMask(GrStencilSettings::kFront_Face);
+ GL_CALL(StencilFillPath(id, fillMode, writeMask));
+}
+
+void GrGpuGL::onResolveRenderTarget(GrRenderTarget* target) {
+ GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(target);
+ if (rt->needsResolve()) {
+ // Some extensions automatically resolves the texture when it is read.
+ if (this->glCaps().usesMSAARenderBuffers()) {
+ GrAssert(rt->textureFBOID() != rt->renderFBOID());
+ GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER, rt->renderFBOID()));
+ GL_CALL(BindFramebuffer(GR_GL_DRAW_FRAMEBUFFER, rt->textureFBOID()));
+ // make sure we go through flushRenderTarget() since we've modified
+ // the bound DRAW FBO ID.
+ fHWBoundRenderTarget = NULL;
+ const GrGLIRect& vp = rt->getViewport();
+ const SkIRect dirtyRect = rt->getResolveRect();
+ GrGLIRect r;
+ r.setRelativeTo(vp, dirtyRect.fLeft, dirtyRect.fTop,
+ dirtyRect.width(), dirtyRect.height(), target->origin());
+
+ GrAutoTRestore<ScissorState> asr;
+ if (GrGLCaps::kES_Apple_MSFBOType == this->glCaps().msFBOType()) {
+ // Apple's extension uses the scissor as the blit bounds.
+ asr.reset(&fScissorState);
+ fScissorState.fEnabled = true;
+ fScissorState.fRect = dirtyRect;
+ this->flushScissor();
+ GL_CALL(ResolveMultisampleFramebuffer());
+ } else {
+ if (GrGLCaps::kDesktop_EXT_MSFBOType == this->glCaps().msFBOType()) {
+ // this respects the scissor during the blit, so disable it.
+ asr.reset(&fScissorState);
+ fScissorState.fEnabled = false;
+ this->flushScissor();
+ }
+ int right = r.fLeft + r.fWidth;
+ int top = r.fBottom + r.fHeight;
+ GL_CALL(BlitFramebuffer(r.fLeft, r.fBottom, right, top,
+ r.fLeft, r.fBottom, right, top,
+ GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST));
+ }
+ }
+ rt->flagAsResolved();
+ }
+}
+
+namespace {
+
+GrGLenum gr_to_gl_stencil_func(GrStencilFunc basicFunc) {
+ static const GrGLenum gTable[] = {
+ GR_GL_ALWAYS, // kAlways_StencilFunc
+ GR_GL_NEVER, // kNever_StencilFunc
+ GR_GL_GREATER, // kGreater_StencilFunc
+ GR_GL_GEQUAL, // kGEqual_StencilFunc
+ GR_GL_LESS, // kLess_StencilFunc
+ GR_GL_LEQUAL, // kLEqual_StencilFunc,
+ GR_GL_EQUAL, // kEqual_StencilFunc,
+ GR_GL_NOTEQUAL, // kNotEqual_StencilFunc,
+ };
+ GR_STATIC_ASSERT(GR_ARRAY_COUNT(gTable) == kBasicStencilFuncCount);
+ GR_STATIC_ASSERT(0 == kAlways_StencilFunc);
+ GR_STATIC_ASSERT(1 == kNever_StencilFunc);
+ GR_STATIC_ASSERT(2 == kGreater_StencilFunc);
+ GR_STATIC_ASSERT(3 == kGEqual_StencilFunc);
+ GR_STATIC_ASSERT(4 == kLess_StencilFunc);
+ GR_STATIC_ASSERT(5 == kLEqual_StencilFunc);
+ GR_STATIC_ASSERT(6 == kEqual_StencilFunc);
+ GR_STATIC_ASSERT(7 == kNotEqual_StencilFunc);
+ GrAssert((unsigned) basicFunc < kBasicStencilFuncCount);
+
+ return gTable[basicFunc];
+}
+
+GrGLenum gr_to_gl_stencil_op(GrStencilOp op) {
+ static const GrGLenum gTable[] = {
+ GR_GL_KEEP, // kKeep_StencilOp
+ GR_GL_REPLACE, // kReplace_StencilOp
+ GR_GL_INCR_WRAP, // kIncWrap_StencilOp
+ GR_GL_INCR, // kIncClamp_StencilOp
+ GR_GL_DECR_WRAP, // kDecWrap_StencilOp
+ GR_GL_DECR, // kDecClamp_StencilOp
+ GR_GL_ZERO, // kZero_StencilOp
+ GR_GL_INVERT, // kInvert_StencilOp
+ };
+ GR_STATIC_ASSERT(GR_ARRAY_COUNT(gTable) == kStencilOpCount);
+ GR_STATIC_ASSERT(0 == kKeep_StencilOp);
+ GR_STATIC_ASSERT(1 == kReplace_StencilOp);
+ GR_STATIC_ASSERT(2 == kIncWrap_StencilOp);
+ GR_STATIC_ASSERT(3 == kIncClamp_StencilOp);
+ GR_STATIC_ASSERT(4 == kDecWrap_StencilOp);
+ GR_STATIC_ASSERT(5 == kDecClamp_StencilOp);
+ GR_STATIC_ASSERT(6 == kZero_StencilOp);
+ GR_STATIC_ASSERT(7 == kInvert_StencilOp);
+ GrAssert((unsigned) op < kStencilOpCount);
+ return gTable[op];
+}
+
+void set_gl_stencil(const GrGLInterface* gl,
+ const GrStencilSettings& settings,
+ GrGLenum glFace,
+ GrStencilSettings::Face grFace) {
+ GrGLenum glFunc = gr_to_gl_stencil_func(settings.func(grFace));
+ GrGLenum glFailOp = gr_to_gl_stencil_op(settings.failOp(grFace));
+ GrGLenum glPassOp = gr_to_gl_stencil_op(settings.passOp(grFace));
+
+ GrGLint ref = settings.funcRef(grFace);
+ GrGLint mask = settings.funcMask(grFace);
+ GrGLint writeMask = settings.writeMask(grFace);
+
+ if (GR_GL_FRONT_AND_BACK == glFace) {
+ // we call the combined func just in case separate stencil is not
+ // supported.
+ GR_GL_CALL(gl, StencilFunc(glFunc, ref, mask));
+ GR_GL_CALL(gl, StencilMask(writeMask));
+ GR_GL_CALL(gl, StencilOp(glFailOp, glPassOp, glPassOp));
+ } else {
+ GR_GL_CALL(gl, StencilFuncSeparate(glFace, glFunc, ref, mask));
+ GR_GL_CALL(gl, StencilMaskSeparate(glFace, writeMask));
+ GR_GL_CALL(gl, StencilOpSeparate(glFace, glFailOp, glPassOp, glPassOp));
+ }
+}
+}
+
+void GrGpuGL::flushStencil(DrawType type) {
+ if (kStencilPath_DrawType == type) {
+ GrAssert(!fStencilSettings.isTwoSided());
+ // Just the func, ref, and mask is set here. The op and write mask are params to the call
+ // that draws the path to the SB (glStencilFillPath)
+ GrGLenum func =
+ gr_to_gl_stencil_func(fStencilSettings.func(GrStencilSettings::kFront_Face));
+ GL_CALL(PathStencilFunc(func,
+ fStencilSettings.funcRef(GrStencilSettings::kFront_Face),
+ fStencilSettings.funcMask(GrStencilSettings::kFront_Face)));
+ } else if (fHWStencilSettings != fStencilSettings) {
+ if (fStencilSettings.isDisabled()) {
+ if (kNo_TriState != fHWStencilTestEnabled) {
+ GL_CALL(Disable(GR_GL_STENCIL_TEST));
+ fHWStencilTestEnabled = kNo_TriState;
+ }
+ } else {
+ if (kYes_TriState != fHWStencilTestEnabled) {
+ GL_CALL(Enable(GR_GL_STENCIL_TEST));
+ fHWStencilTestEnabled = kYes_TriState;
+ }
+ }
+ if (!fStencilSettings.isDisabled()) {
+ if (this->caps()->twoSidedStencilSupport()) {
+ set_gl_stencil(this->glInterface(),
+ fStencilSettings,
+ GR_GL_FRONT,
+ GrStencilSettings::kFront_Face);
+ set_gl_stencil(this->glInterface(),
+ fStencilSettings,
+ GR_GL_BACK,
+ GrStencilSettings::kBack_Face);
+ } else {
+ set_gl_stencil(this->glInterface(),
+ fStencilSettings,
+ GR_GL_FRONT_AND_BACK,
+ GrStencilSettings::kFront_Face);
+ }
+ }
+ fHWStencilSettings = fStencilSettings;
+ }
+}
+
+void GrGpuGL::flushAAState(DrawType type) {
+// At least some ATI linux drivers will render GL_LINES incorrectly when MSAA state is enabled but
+// the target is not multisampled. Single pixel wide lines are rendered thicker than 1 pixel wide.
+#if 0
+ // Replace RT_HAS_MSAA with this definition once this driver bug is no longer a relevant concern
+ #define RT_HAS_MSAA rt->isMultisampled()
+#else
+ #define RT_HAS_MSAA (rt->isMultisampled() || kDrawLines_DrawType == type)
+#endif
+
+ const GrRenderTarget* rt = this->getDrawState().getRenderTarget();
+ if (kDesktop_GrGLBinding == this->glBinding()) {
+ // ES doesn't support toggling GL_MULTISAMPLE and doesn't have
+ // smooth lines.
+ // we prefer smooth lines over multisampled lines
+ bool smoothLines = false;
+
+ if (kDrawLines_DrawType == type) {
+ smoothLines = this->willUseHWAALines();
+ if (smoothLines) {
+ if (kYes_TriState != fHWAAState.fSmoothLineEnabled) {
+ GL_CALL(Enable(GR_GL_LINE_SMOOTH));
+ fHWAAState.fSmoothLineEnabled = kYes_TriState;
+ // must disable msaa to use line smoothing
+ if (RT_HAS_MSAA &&
+ kNo_TriState != fHWAAState.fMSAAEnabled) {
+ GL_CALL(Disable(GR_GL_MULTISAMPLE));
+ fHWAAState.fMSAAEnabled = kNo_TriState;
+ }
+ }
+ } else {
+ if (kNo_TriState != fHWAAState.fSmoothLineEnabled) {
+ GL_CALL(Disable(GR_GL_LINE_SMOOTH));
+ fHWAAState.fSmoothLineEnabled = kNo_TriState;
+ }
+ }
+ }
+ if (!smoothLines && RT_HAS_MSAA) {
+ // FIXME: GL_NV_pr doesn't seem to like MSAA disabled. The paths
+ // convex hulls of each segment appear to get filled.
+ bool enableMSAA = kStencilPath_DrawType == type ||
+ this->getDrawState().isHWAntialiasState();
+ if (enableMSAA) {
+ if (kYes_TriState != fHWAAState.fMSAAEnabled) {
+ GL_CALL(Enable(GR_GL_MULTISAMPLE));
+ fHWAAState.fMSAAEnabled = kYes_TriState;
+ }
+ } else {
+ if (kNo_TriState != fHWAAState.fMSAAEnabled) {
+ GL_CALL(Disable(GR_GL_MULTISAMPLE));
+ fHWAAState.fMSAAEnabled = kNo_TriState;
+ }
+ }
+ }
+ }
+}
+
+void GrGpuGL::flushBlend(bool isLines,
+ GrBlendCoeff srcCoeff,
+ GrBlendCoeff dstCoeff) {
+ if (isLines && this->willUseHWAALines()) {
+ if (kYes_TriState != fHWBlendState.fEnabled) {
+ GL_CALL(Enable(GR_GL_BLEND));
+ fHWBlendState.fEnabled = kYes_TriState;
+ }
+ if (kSA_GrBlendCoeff != fHWBlendState.fSrcCoeff ||
+ kISA_GrBlendCoeff != fHWBlendState.fDstCoeff) {
+ GL_CALL(BlendFunc(gXfermodeCoeff2Blend[kSA_GrBlendCoeff],
+ gXfermodeCoeff2Blend[kISA_GrBlendCoeff]));
+ fHWBlendState.fSrcCoeff = kSA_GrBlendCoeff;
+ fHWBlendState.fDstCoeff = kISA_GrBlendCoeff;
+ }
+ } else {
+ // any optimization to disable blending should
+ // have already been applied and tweaked the coeffs
+ // to (1, 0).
+ bool blendOff = kOne_GrBlendCoeff == srcCoeff &&
+ kZero_GrBlendCoeff == dstCoeff;
+ if (blendOff) {
+ if (kNo_TriState != fHWBlendState.fEnabled) {
+ GL_CALL(Disable(GR_GL_BLEND));
+ fHWBlendState.fEnabled = kNo_TriState;
+ }
+ } else {
+ if (kYes_TriState != fHWBlendState.fEnabled) {
+ GL_CALL(Enable(GR_GL_BLEND));
+ fHWBlendState.fEnabled = kYes_TriState;
+ }
+ if (fHWBlendState.fSrcCoeff != srcCoeff ||
+ fHWBlendState.fDstCoeff != dstCoeff) {
+ GL_CALL(BlendFunc(gXfermodeCoeff2Blend[srcCoeff],
+ gXfermodeCoeff2Blend[dstCoeff]));
+ fHWBlendState.fSrcCoeff = srcCoeff;
+ fHWBlendState.fDstCoeff = dstCoeff;
+ }
+ GrColor blendConst = this->getDrawState().getBlendConstant();
+ if ((BlendCoeffReferencesConstant(srcCoeff) ||
+ BlendCoeffReferencesConstant(dstCoeff)) &&
+ (!fHWBlendState.fConstColorValid ||
+ fHWBlendState.fConstColor != blendConst)) {
+ GrGLfloat c[4];
+ GrColorToRGBAFloat(blendConst, c);
+ GL_CALL(BlendColor(c[0], c[1], c[2], c[3]));
+ fHWBlendState.fConstColor = blendConst;
+ fHWBlendState.fConstColorValid = true;
+ }
+ }
+ }
+}
+namespace {
+
+inline void set_tex_swizzle(GrGLenum swizzle[4], const GrGLInterface* gl) {
+ GR_GL_CALL(gl, TexParameteriv(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_SWIZZLE_RGBA,
+ reinterpret_cast<const GrGLint*>(swizzle)));
+}
+
+inline GrGLenum tile_to_gl_wrap(SkShader::TileMode tm) {
+ static const GrGLenum gWrapModes[] = {
+ GR_GL_CLAMP_TO_EDGE,
+ GR_GL_REPEAT,
+ GR_GL_MIRRORED_REPEAT
+ };
+ GR_STATIC_ASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gWrapModes));
+ GR_STATIC_ASSERT(0 == SkShader::kClamp_TileMode);
+ GR_STATIC_ASSERT(1 == SkShader::kRepeat_TileMode);
+ GR_STATIC_ASSERT(2 == SkShader::kMirror_TileMode);
+ return gWrapModes[tm];
+}
+
+}
+
+void GrGpuGL::bindTexture(int unitIdx, const GrTextureParams& params, GrGLTexture* texture) {
+ GrAssert(NULL != texture);
+
+ // If we created a rt/tex and rendered to it without using a texture and now we're texturing
+ // from the rt it will still be the last bound texture, but it needs resolving. So keep this
+ // out of the "last != next" check.
+ GrGLRenderTarget* texRT = static_cast<GrGLRenderTarget*>(texture->asRenderTarget());
+ if (NULL != texRT) {
+ this->onResolveRenderTarget(texRT);
+ }
+
+ if (fHWBoundTextures[unitIdx] != texture) {
+ this->setTextureUnit(unitIdx);
+ GL_CALL(BindTexture(GR_GL_TEXTURE_2D, texture->textureID()));
+ fHWBoundTextures[unitIdx] = texture;
+ }
+
+ ResetTimestamp timestamp;
+ const GrGLTexture::TexParams& oldTexParams = texture->getCachedTexParams(&timestamp);
+ bool setAll = timestamp < this->getResetTimestamp();
+ GrGLTexture::TexParams newTexParams;
+
+ static GrGLenum glMinFilterModes[] = {
+ GR_GL_NEAREST,
+ GR_GL_LINEAR,
+ GR_GL_LINEAR_MIPMAP_LINEAR
+ };
+ static GrGLenum glMagFilterModes[] = {
+ GR_GL_NEAREST,
+ GR_GL_LINEAR,
+ GR_GL_LINEAR
+ };
+ newTexParams.fMinFilter = glMinFilterModes[params.filterMode()];
+ newTexParams.fMagFilter = glMagFilterModes[params.filterMode()];
+
+#ifndef SKIA_IGNORE_GPU_MIPMAPS
+ if (params.filterMode() == GrTextureParams::kMipMap_FilterMode &&
+ texture->mipMapsAreDirty()) {
+// GL_CALL(Hint(GR_GL_GENERATE_MIPMAP_HINT,GR_GL_NICEST));
+ GL_CALL(GenerateMipmap(GR_GL_TEXTURE_2D));
+ texture->dirtyMipMaps(false);
+ }
+#endif
+
+ newTexParams.fWrapS = tile_to_gl_wrap(params.getTileModeX());
+ newTexParams.fWrapT = tile_to_gl_wrap(params.getTileModeY());
+ memcpy(newTexParams.fSwizzleRGBA,
+ GrGLShaderBuilder::GetTexParamSwizzle(texture->config(), this->glCaps()),
+ sizeof(newTexParams.fSwizzleRGBA));
+ if (setAll || newTexParams.fMagFilter != oldTexParams.fMagFilter) {
+ this->setTextureUnit(unitIdx);
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_MAG_FILTER,
+ newTexParams.fMagFilter));
+ }
+ if (setAll || newTexParams.fMinFilter != oldTexParams.fMinFilter) {
+ this->setTextureUnit(unitIdx);
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_MIN_FILTER,
+ newTexParams.fMinFilter));
+ }
+ if (setAll || newTexParams.fWrapS != oldTexParams.fWrapS) {
+ this->setTextureUnit(unitIdx);
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_WRAP_S,
+ newTexParams.fWrapS));
+ }
+ if (setAll || newTexParams.fWrapT != oldTexParams.fWrapT) {
+ this->setTextureUnit(unitIdx);
+ GL_CALL(TexParameteri(GR_GL_TEXTURE_2D,
+ GR_GL_TEXTURE_WRAP_T,
+ newTexParams.fWrapT));
+ }
+ if (this->glCaps().textureSwizzleSupport() &&
+ (setAll || memcmp(newTexParams.fSwizzleRGBA,
+ oldTexParams.fSwizzleRGBA,
+ sizeof(newTexParams.fSwizzleRGBA)))) {
+ this->setTextureUnit(unitIdx);
+ set_tex_swizzle(newTexParams.fSwizzleRGBA,
+ this->glInterface());
+ }
+ texture->setCachedTexParams(newTexParams, this->getResetTimestamp());
+}
+
+void GrGpuGL::flushMiscFixedFunctionState() {
+
+ const GrDrawState& drawState = this->getDrawState();
+
+ if (drawState.isDitherState()) {
+ if (kYes_TriState != fHWDitherEnabled) {
+ GL_CALL(Enable(GR_GL_DITHER));
+ fHWDitherEnabled = kYes_TriState;
+ }
+ } else {
+ if (kNo_TriState != fHWDitherEnabled) {
+ GL_CALL(Disable(GR_GL_DITHER));
+ fHWDitherEnabled = kNo_TriState;
+ }
+ }
+
+ if (drawState.isColorWriteDisabled()) {
+ if (kNo_TriState != fHWWriteToColor) {
+ GL_CALL(ColorMask(GR_GL_FALSE, GR_GL_FALSE,
+ GR_GL_FALSE, GR_GL_FALSE));
+ fHWWriteToColor = kNo_TriState;
+ }
+ } else {
+ if (kYes_TriState != fHWWriteToColor) {
+ GL_CALL(ColorMask(GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE, GR_GL_TRUE));
+ fHWWriteToColor = kYes_TriState;
+ }
+ }
+
+ if (fHWDrawFace != drawState.getDrawFace()) {
+ switch (this->getDrawState().getDrawFace()) {
+ case GrDrawState::kCCW_DrawFace:
+ GL_CALL(Enable(GR_GL_CULL_FACE));
+ GL_CALL(CullFace(GR_GL_BACK));
+ break;
+ case GrDrawState::kCW_DrawFace:
+ GL_CALL(Enable(GR_GL_CULL_FACE));
+ GL_CALL(CullFace(GR_GL_FRONT));
+ break;
+ case GrDrawState::kBoth_DrawFace:
+ GL_CALL(Disable(GR_GL_CULL_FACE));
+ break;
+ default:
+ GrCrash("Unknown draw face.");
+ }
+ fHWDrawFace = drawState.getDrawFace();
+ }
+}
+
+void GrGpuGL::notifyRenderTargetDelete(GrRenderTarget* renderTarget) {
+ GrAssert(NULL != renderTarget);
+ if (fHWBoundRenderTarget == renderTarget) {
+ fHWBoundRenderTarget = NULL;
+ }
+}
+
+void GrGpuGL::notifyTextureDelete(GrGLTexture* texture) {
+ for (int s = 0; s < fHWBoundTextures.count(); ++s) {
+ if (fHWBoundTextures[s] == texture) {
+ // deleting bound texture does implied bind to 0
+ fHWBoundTextures[s] = NULL;
+ }
+ }
+}
+
+bool GrGpuGL::configToGLFormats(GrPixelConfig config,
+ bool getSizedInternalFormat,
+ GrGLenum* internalFormat,
+ GrGLenum* externalFormat,
+ GrGLenum* externalType) {
+ GrGLenum dontCare;
+ if (NULL == internalFormat) {
+ internalFormat = &dontCare;
+ }
+ if (NULL == externalFormat) {
+ externalFormat = &dontCare;
+ }
+ if (NULL == externalType) {
+ externalType = &dontCare;
+ }
+
+ switch (config) {
+ case kRGBA_8888_GrPixelConfig:
+ *internalFormat = GR_GL_RGBA;
+ *externalFormat = GR_GL_RGBA;
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_RGBA8;
+ } else {
+ *internalFormat = GR_GL_RGBA;
+ }
+ *externalType = GR_GL_UNSIGNED_BYTE;
+ break;
+ case kBGRA_8888_GrPixelConfig:
+ if (!this->glCaps().bgraFormatSupport()) {
+ return false;
+ }
+ if (this->glCaps().bgraIsInternalFormat()) {
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_BGRA8;
+ } else {
+ *internalFormat = GR_GL_BGRA;
+ }
+ } else {
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_RGBA8;
+ } else {
+ *internalFormat = GR_GL_RGBA;
+ }
+ }
+ *externalFormat = GR_GL_BGRA;
+ *externalType = GR_GL_UNSIGNED_BYTE;
+ break;
+ case kRGB_565_GrPixelConfig:
+ *internalFormat = GR_GL_RGB;
+ *externalFormat = GR_GL_RGB;
+ if (getSizedInternalFormat) {
+ if (this->glBinding() == kDesktop_GrGLBinding) {
+ return false;
+ } else {
+ *internalFormat = GR_GL_RGB565;
+ }
+ } else {
+ *internalFormat = GR_GL_RGB;
+ }
+ *externalType = GR_GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case kRGBA_4444_GrPixelConfig:
+ *internalFormat = GR_GL_RGBA;
+ *externalFormat = GR_GL_RGBA;
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_RGBA4;
+ } else {
+ *internalFormat = GR_GL_RGBA;
+ }
+ *externalType = GR_GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ case kIndex_8_GrPixelConfig:
+ if (this->caps()->eightBitPaletteSupport()) {
+ *internalFormat = GR_GL_PALETTE8_RGBA8;
+ // glCompressedTexImage doesn't take external params
+ *externalFormat = GR_GL_PALETTE8_RGBA8;
+ // no sized/unsized internal format distinction here
+ *internalFormat = GR_GL_PALETTE8_RGBA8;
+ // unused with CompressedTexImage
+ *externalType = GR_GL_UNSIGNED_BYTE;
+ } else {
+ return false;
+ }
+ break;
+ case kAlpha_8_GrPixelConfig:
+ if (this->glCaps().textureRedSupport()) {
+ *internalFormat = GR_GL_RED;
+ *externalFormat = GR_GL_RED;
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_R8;
+ } else {
+ *internalFormat = GR_GL_RED;
+ }
+ *externalType = GR_GL_UNSIGNED_BYTE;
+ } else {
+ *internalFormat = GR_GL_ALPHA;
+ *externalFormat = GR_GL_ALPHA;
+ if (getSizedInternalFormat) {
+ *internalFormat = GR_GL_ALPHA8;
+ } else {
+ *internalFormat = GR_GL_ALPHA;
+ }
+ *externalType = GR_GL_UNSIGNED_BYTE;
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void GrGpuGL::setTextureUnit(int unit) {
+ GrAssert(unit >= 0 && unit < fHWBoundTextures.count());
+ if (unit != fHWActiveTextureUnitIdx) {
+ GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + unit));
+ fHWActiveTextureUnitIdx = unit;
+ }
+}
+
+void GrGpuGL::setScratchTextureUnit() {
+ // Bind the last texture unit since it is the least likely to be used by GrGLProgram.
+ int lastUnitIdx = fHWBoundTextures.count() - 1;
+ if (lastUnitIdx != fHWActiveTextureUnitIdx) {
+ GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + lastUnitIdx));
+ fHWActiveTextureUnitIdx = lastUnitIdx;
+ }
+ // clear out the this field so that if a program does use this unit it will rebind the correct
+ // texture.
+ fHWBoundTextures[lastUnitIdx] = NULL;
+}
+
+namespace {
+// Determines whether glBlitFramebuffer could be used between src and dst.
+inline bool can_blit_framebuffer(const GrSurface* dst,
+ const GrSurface* src,
+ const GrGpuGL* gpu,
+ bool* wouldNeedTempFBO = NULL) {
+ if (gpu->isConfigRenderable(dst->config()) &&
+ gpu->isConfigRenderable(src->config()) &&
+ gpu->glCaps().usesMSAARenderBuffers()) {
+ if (NULL != wouldNeedTempFBO) {
+ *wouldNeedTempFBO = NULL == dst->asRenderTarget() || NULL == src->asRenderTarget();
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+inline bool can_copy_texsubimage(const GrSurface* dst,
+ const GrSurface* src,
+ const GrGpuGL* gpu,
+ bool* wouldNeedTempFBO = NULL) {
+ // Table 3.9 of the ES2 spec indicates the supported formats with CopyTexSubImage
+ // and BGRA isn't in the spec. There doesn't appear to be any extension that adds it. Perhaps
+ // many drivers would allow it to work, but ANGLE does not.
+ if (kES2_GrGLBinding == gpu->glBinding() && gpu->glCaps().bgraIsInternalFormat() &&
+ (kBGRA_8888_GrPixelConfig == dst->config() || kBGRA_8888_GrPixelConfig == src->config())) {
+ return false;
+ }
+ const GrGLRenderTarget* dstRT = static_cast<const GrGLRenderTarget*>(dst->asRenderTarget());
+ // If dst is multisampled (and uses an extension where there is a separate MSAA renderbuffer)
+ // then we don't want to copy to the texture but to the MSAA buffer.
+ if (NULL != dstRT && dstRT->renderFBOID() != dstRT->textureFBOID()) {
+ return false;
+ }
+ const GrGLRenderTarget* srcRT = static_cast<const GrGLRenderTarget*>(src->asRenderTarget());
+ // If the src is multisampled (and uses an extension where there is a separate MSAA
+ // renderbuffer) then it is an invalid operation to call CopyTexSubImage
+ if (NULL != srcRT && srcRT->renderFBOID() != srcRT->textureFBOID()) {
+ return false;
+ }
+ if (gpu->isConfigRenderable(src->config()) && NULL != dst->asTexture() &&
+ dst->origin() == src->origin() && kIndex_8_GrPixelConfig != src->config()) {
+ if (NULL != wouldNeedTempFBO) {
+ *wouldNeedTempFBO = NULL == src->asRenderTarget();
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// If a temporary FBO was created, its non-zero ID is returned. The viewport that the copy rect is
+// relative to is output.
+inline GrGLuint bind_surface_as_fbo(const GrGLInterface* gl,
+ GrSurface* surface,
+ GrGLenum fboTarget,
+ GrGLIRect* viewport) {
+ GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(surface->asRenderTarget());
+ GrGLuint tempFBOID;
+ if (NULL == rt) {
+ GrAssert(NULL != surface->asTexture());
+ GrGLuint texID = static_cast<GrGLTexture*>(surface->asTexture())->textureID();
+ GR_GL_CALL(gl, GenFramebuffers(1, &tempFBOID));
+ GR_GL_CALL(gl, BindFramebuffer(fboTarget, tempFBOID));
+ GR_GL_CALL(gl, FramebufferTexture2D(fboTarget,
+ GR_GL_COLOR_ATTACHMENT0,
+ GR_GL_TEXTURE_2D,
+ texID,
+ 0));
+ viewport->fLeft = 0;
+ viewport->fBottom = 0;
+ viewport->fWidth = surface->width();
+ viewport->fHeight = surface->height();
+ } else {
+ tempFBOID = 0;
+ GR_GL_CALL(gl, BindFramebuffer(fboTarget, rt->renderFBOID()));
+ *viewport = rt->getViewport();
+ }
+ return tempFBOID;
+}
+
+}
+
+void GrGpuGL::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+ // Check for format issues with glCopyTexSubImage2D
+ if (kES2_GrGLBinding == this->glBinding() && this->glCaps().bgraIsInternalFormat() &&
+ kBGRA_8888_GrPixelConfig == src->config()) {
+ // glCopyTexSubImage2D doesn't work with this config. We'll want to make it a render target
+ // in order to call glBlitFramebuffer or to copy to it by rendering.
+ INHERITED::initCopySurfaceDstDesc(src, desc);
+ return;
+ } else if (NULL == src->asRenderTarget()) {
+ // We don't want to have to create an FBO just to use glCopyTexSubImage2D. Let the base
+ // class handle it by rendering.
+ INHERITED::initCopySurfaceDstDesc(src, desc);
+ return;
+ }
+
+ const GrGLRenderTarget* srcRT = static_cast<const GrGLRenderTarget*>(src->asRenderTarget());
+ if (NULL != srcRT && srcRT->renderFBOID() != srcRT->textureFBOID()) {
+ // It's illegal to call CopyTexSubImage2D on a MSAA renderbuffer.
+ INHERITED::initCopySurfaceDstDesc(src, desc);
+ } else {
+ desc->fConfig = src->config();
+ desc->fOrigin = src->origin();
+ desc->fFlags = kNone_GrTextureFlags;
+ }
+}
+
+bool GrGpuGL::onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ bool inheritedCouldCopy = INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
+ bool copied = false;
+ bool wouldNeedTempFBO = false;
+ if (can_copy_texsubimage(dst, src, this, &wouldNeedTempFBO) &&
+ (!wouldNeedTempFBO || !inheritedCouldCopy)) {
+ GrGLuint srcFBO;
+ GrGLIRect srcVP;
+ srcFBO = bind_surface_as_fbo(this->glInterface(), src, GR_GL_FRAMEBUFFER, &srcVP);
+ GrGLTexture* dstTex = static_cast<GrGLTexture*>(dst->asTexture());
+ GrAssert(NULL != dstTex);
+ // We modified the bound FBO
+ fHWBoundRenderTarget = NULL;
+ GrGLIRect srcGLRect;
+ srcGLRect.setRelativeTo(srcVP,
+ srcRect.fLeft,
+ srcRect.fTop,
+ srcRect.width(),
+ srcRect.height(),
+ src->origin());
+
+ this->setScratchTextureUnit();
+ GL_CALL(BindTexture(GR_GL_TEXTURE_2D, dstTex->textureID()));
+ GrGLint dstY;
+ if (kBottomLeft_GrSurfaceOrigin == dst->origin()) {
+ dstY = dst->height() - (dstPoint.fY + srcGLRect.fHeight);
+ } else {
+ dstY = dstPoint.fY;
+ }
+ GL_CALL(CopyTexSubImage2D(GR_GL_TEXTURE_2D, 0,
+ dstPoint.fX, dstY,
+ srcGLRect.fLeft, srcGLRect.fBottom,
+ srcGLRect.fWidth, srcGLRect.fHeight));
+ copied = true;
+ if (srcFBO) {
+ GL_CALL(DeleteFramebuffers(1, &srcFBO));
+ }
+ } else if (can_blit_framebuffer(dst, src, this, &wouldNeedTempFBO) &&
+ (!wouldNeedTempFBO || !inheritedCouldCopy)) {
+ SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
+ srcRect.width(), srcRect.height());
+ bool selfOverlap = false;
+ if (dst->isSameAs(src)) {
+ selfOverlap = SkIRect::IntersectsNoEmptyCheck(dstRect, srcRect);
+ }
+
+ if (!selfOverlap) {
+ GrGLuint dstFBO;
+ GrGLuint srcFBO;
+ GrGLIRect dstVP;
+ GrGLIRect srcVP;
+ dstFBO = bind_surface_as_fbo(this->glInterface(), dst, GR_GL_DRAW_FRAMEBUFFER, &dstVP);
+ srcFBO = bind_surface_as_fbo(this->glInterface(), src, GR_GL_READ_FRAMEBUFFER, &srcVP);
+ // We modified the bound FBO
+ fHWBoundRenderTarget = NULL;
+ GrGLIRect srcGLRect;
+ GrGLIRect dstGLRect;
+ srcGLRect.setRelativeTo(srcVP,
+ srcRect.fLeft,
+ srcRect.fTop,
+ srcRect.width(),
+ srcRect.height(),
+ src->origin());
+ dstGLRect.setRelativeTo(dstVP,
+ dstRect.fLeft,
+ dstRect.fTop,
+ dstRect.width(),
+ dstRect.height(),
+ dst->origin());
+
+ GrAutoTRestore<ScissorState> asr;
+ if (GrGLCaps::kDesktop_EXT_MSFBOType == this->glCaps().msFBOType()) {
+ // The EXT version applies the scissor during the blit, so disable it.
+ asr.reset(&fScissorState);
+ fScissorState.fEnabled = false;
+ this->flushScissor();
+ }
+ GrGLint srcY0;
+ GrGLint srcY1;
+ // Does the blit need to y-mirror or not?
+ if (src->origin() == dst->origin()) {
+ srcY0 = srcGLRect.fBottom;
+ srcY1 = srcGLRect.fBottom + srcGLRect.fHeight;
+ } else {
+ srcY0 = srcGLRect.fBottom + srcGLRect.fHeight;
+ srcY1 = srcGLRect.fBottom;
+ }
+ GL_CALL(BlitFramebuffer(srcGLRect.fLeft,
+ srcY0,
+ srcGLRect.fLeft + srcGLRect.fWidth,
+ srcY1,
+ dstGLRect.fLeft,
+ dstGLRect.fBottom,
+ dstGLRect.fLeft + dstGLRect.fWidth,
+ dstGLRect.fBottom + dstGLRect.fHeight,
+ GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST));
+ if (dstFBO) {
+ GL_CALL(DeleteFramebuffers(1, &dstFBO));
+ }
+ if (srcFBO) {
+ GL_CALL(DeleteFramebuffers(1, &srcFBO));
+ }
+ copied = true;
+ }
+ }
+ if (!copied && inheritedCouldCopy) {
+ copied = INHERITED::onCopySurface(dst, src, srcRect, dstPoint);
+ GrAssert(copied);
+ }
+ return copied;
+}
+
+bool GrGpuGL::onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) {
+ // This mirrors the logic in onCopySurface.
+ if (can_copy_texsubimage(dst, src, this)) {
+ return true;
+ }
+ if (can_blit_framebuffer(dst, src, this)) {
+ if (dst->isSameAs(src)) {
+ SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
+ srcRect.width(), srcRect.height());
+ if(!SkIRect::IntersectsNoEmptyCheck(dstRect, srcRect)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLAttribArrayState* GrGpuGL::HWGeometryState::bindArrayAndBuffersToDraw(
+ GrGpuGL* gpu,
+ const GrGLVertexBuffer* vbuffer,
+ const GrGLIndexBuffer* ibuffer) {
+ GrAssert(NULL != vbuffer);
+ GrGLAttribArrayState* attribState;
+
+ // We use a vertex array if we're on a core profile and the verts are in a VBO.
+ if (gpu->glCaps().isCoreProfile() && !vbuffer->isCPUBacked()) {
+ if (NULL == fVBOVertexArray || !fVBOVertexArray->isValid()) {
+ SkSafeUnref(fVBOVertexArray);
+ GrGLuint arrayID;
+ GR_GL_CALL(gpu->glInterface(), GenVertexArrays(1, &arrayID));
+ int attrCount = gpu->glCaps().maxVertexAttributes();
+ fVBOVertexArray = SkNEW_ARGS(GrGLVertexArray, (gpu, arrayID, attrCount));
+ }
+ attribState = fVBOVertexArray->bindWithIndexBuffer(ibuffer);
+ } else {
+ if (NULL != ibuffer) {
+ this->setIndexBufferIDOnDefaultVertexArray(gpu, ibuffer->bufferID());
+ } else {
+ this->setVertexArrayID(gpu, 0);
+ }
+ int attrCount = gpu->glCaps().maxVertexAttributes();
+ if (fDefaultVertexArrayAttribState.count() != attrCount) {
+ fDefaultVertexArrayAttribState.resize(attrCount);
+ }
+ attribState = &fDefaultVertexArrayAttribState;
+ }
+ return attribState;
+}
diff --git a/gpu/gl/GrGpuGL.h b/gpu/gl/GrGpuGL.h
new file mode 100644
index 00000000..b5ea2a7b
--- /dev/null
+++ b/gpu/gl/GrGpuGL.h
@@ -0,0 +1,439 @@
+/*
+ * 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 GrGpuGL_DEFINED
+#define GrGpuGL_DEFINED
+
+#include "GrBinHashKey.h"
+#include "GrDrawState.h"
+#include "GrGpu.h"
+#include "GrGLContext.h"
+#include "GrGLIndexBuffer.h"
+#include "GrGLIRect.h"
+#include "GrGLProgram.h"
+#include "GrGLStencilBuffer.h"
+#include "GrGLTexture.h"
+#include "GrGLVertexArray.h"
+#include "GrGLVertexBuffer.h"
+#include "../GrTHashCache.h"
+
+#ifdef SK_DEVELOPER
+#define PROGRAM_CACHE_STATS
+#endif
+
+class GrGpuGL : public GrGpu {
+public:
+ GrGpuGL(const GrGLContext& ctx, GrContext* context);
+ virtual ~GrGpuGL();
+
+ const GrGLInterface* glInterface() const { return fGLContext.interface(); }
+ GrGLBinding glBinding() const { return fGLContext.info().binding(); }
+ GrGLVersion glVersion() const { return fGLContext.info().version(); }
+ GrGLSLGeneration glslGeneration() const { return fGLContext.info().glslGeneration(); }
+
+ // Used by GrGLProgram to bind necessary textures for GrGLEffects.
+ void bindTexture(int unitIdx, const GrTextureParams& params, GrGLTexture* texture);
+
+ bool programUnitTest(int maxStages);
+
+ // GrGpu overrides
+ virtual GrPixelConfig preferredReadPixelsConfig(GrPixelConfig readConfig,
+ GrPixelConfig surfaceConfig) const SK_OVERRIDE;
+ virtual GrPixelConfig preferredWritePixelsConfig(GrPixelConfig writeConfig,
+ GrPixelConfig surfaceConfig) const SK_OVERRIDE;
+ virtual bool canWriteTexturePixels(const GrTexture*, GrPixelConfig srcConfig) const SK_OVERRIDE;
+ virtual bool readPixelsWillPayForYFlip(
+ GrRenderTarget* renderTarget,
+ int left, int top,
+ int width, int height,
+ GrPixelConfig config,
+ size_t rowBytes) const SK_OVERRIDE;
+ virtual bool fullReadPixelsIsFasterThanPartial() const SK_OVERRIDE;
+
+ virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) SK_OVERRIDE;
+
+ virtual void abandonResources() SK_OVERRIDE;
+
+ const GrGLCaps& glCaps() const { return *fGLContext.info().caps(); }
+
+ // These functions should be used to bind GL objects. They track the GL state and skip redundant
+ // bindings. Making the equivalent glBind calls directly will confuse the state tracking.
+ void bindVertexArray(GrGLuint id) {
+ fHWGeometryState.setVertexArrayID(this, id);
+ }
+ void bindIndexBufferAndDefaultVertexArray(GrGLuint id) {
+ fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, id);
+ }
+ void bindVertexBuffer(GrGLuint id) {
+ fHWGeometryState.setVertexBufferID(this, id);
+ }
+
+ // These callbacks update state tracking when GL objects are deleted. They are called from
+ // GrGLResource onRelease functions.
+ void notifyVertexArrayDelete(GrGLuint id) {
+ fHWGeometryState.notifyVertexArrayDelete(id);
+ }
+ void notifyVertexBufferDelete(GrGLuint id) {
+ fHWGeometryState.notifyVertexBufferDelete(id);
+ }
+ void notifyIndexBufferDelete(GrGLuint id) {
+ fHWGeometryState.notifyIndexBufferDelete(id);
+ }
+ void notifyTextureDelete(GrGLTexture* texture);
+ void notifyRenderTargetDelete(GrRenderTarget* renderTarget);
+
+protected:
+ virtual bool onCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) SK_OVERRIDE;
+
+ virtual bool onCanCopySurface(GrSurface* dst,
+ GrSurface* src,
+ const SkIRect& srcRect,
+ const SkIPoint& dstPoint) SK_OVERRIDE;
+
+private:
+ // GrGpu overrides
+ virtual void onResetContext(uint32_t resetBits) SK_OVERRIDE;
+
+ virtual GrTexture* onCreateTexture(const GrTextureDesc& desc,
+ const void* srcData,
+ size_t rowBytes) SK_OVERRIDE;
+ virtual GrVertexBuffer* onCreateVertexBuffer(uint32_t size,
+ bool dynamic) SK_OVERRIDE;
+ virtual GrIndexBuffer* onCreateIndexBuffer(uint32_t size,
+ bool dynamic) SK_OVERRIDE;
+ virtual GrPath* onCreatePath(const SkPath&) 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;
+ virtual bool attachStencilBufferToRenderTarget(
+ GrStencilBuffer* sb,
+ GrRenderTarget* rt) SK_OVERRIDE;
+
+ virtual void onClear(const SkIRect* rect, GrColor color) SK_OVERRIDE;
+
+ virtual void onForceRenderTargetFlush() SK_OVERRIDE;
+
+ virtual bool onReadPixels(GrRenderTarget* target,
+ int left, int top,
+ int width, int height,
+ GrPixelConfig,
+ void* buffer,
+ size_t rowBytes) SK_OVERRIDE;
+
+ virtual bool onWriteTexturePixels(GrTexture* texture,
+ int left, int top, int width, int height,
+ GrPixelConfig config, const void* buffer,
+ size_t rowBytes) SK_OVERRIDE;
+
+ virtual void onResolveRenderTarget(GrRenderTarget* target) SK_OVERRIDE;
+
+ virtual void onGpuDraw(const DrawInfo&) SK_OVERRIDE;
+
+ virtual void setStencilPathSettings(const GrPath&,
+ SkPath::FillType,
+ GrStencilSettings* settings)
+ SK_OVERRIDE;
+ virtual void onGpuStencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
+
+ virtual void clearStencil() SK_OVERRIDE;
+ virtual void clearStencilClip(const SkIRect& rect,
+ bool insideClip) SK_OVERRIDE;
+ virtual bool flushGraphicsState(DrawType, const GrDeviceCoordTexture* dstCopy) SK_OVERRIDE;
+
+ // binds texture unit in GL
+ void setTextureUnit(int unitIdx);
+
+ // Sets up vertex attribute pointers and strides. On return indexOffsetInBytes gives the offset
+ // an into the index buffer. It does not account for drawInfo.startIndex() but rather the start
+ // index is relative to the returned offset.
+ void setupGeometry(const DrawInfo& info, size_t* indexOffsetInBytes);
+
+ // Subclasses should call this to flush the blend state.
+ // The params should be the final coefficients to apply
+ // (after any blending optimizations or dual source blending considerations
+ // have been accounted for).
+ void flushBlend(bool isLines, GrBlendCoeff srcCoeff, GrBlendCoeff dstCoeff);
+
+ bool hasExtension(const char* ext) const { return fGLContext.info().hasExtension(ext); }
+
+ const GrGLContext& glContext() const { return fGLContext; }
+
+ static bool BlendCoeffReferencesConstant(GrBlendCoeff coeff);
+
+ class ProgramCache : public ::GrNoncopyable {
+ public:
+ ProgramCache(const GrGLContext& gl);
+ ~ProgramCache();
+
+ void abandon();
+ GrGLProgram* getProgram(const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]);
+
+ private:
+ enum {
+ // We may actually have kMaxEntries+1 shaders in the GL context because we create a new
+ // shader before evicting from the cache.
+ kMaxEntries = 32,
+ kHashBits = 6,
+ };
+
+ struct Entry;
+
+ struct ProgDescLess;
+
+ // binary search for entry matching desc. returns index into fEntries that matches desc or ~
+ // of the index of where it should be inserted.
+ int search(const GrGLProgramDesc& desc) const;
+
+ // sorted array of all the entries
+ Entry* fEntries[kMaxEntries];
+ // hash table based on lowest kHashBits bits of the program key. Used to avoid binary
+ // searching fEntries.
+ Entry* fHashTable[1 << kHashBits];
+
+ int fCount;
+ unsigned int fCurrLRUStamp;
+ const GrGLContext& fGL;
+#ifdef PROGRAM_CACHE_STATS
+ int fTotalRequests;
+ int fCacheMisses;
+ int fHashMisses; // cache hit but hash table missed
+#endif
+ };
+
+ // sets the matrix for path stenciling (uses the GL fixed pipe matrices)
+ void flushPathStencilMatrix();
+
+ // flushes dithering, color-mask, and face culling stat
+ void flushMiscFixedFunctionState();
+
+ // flushes the scissor. see the note on flushBoundTextureAndParams about
+ // flushing the scissor after that function is called.
+ void flushScissor();
+
+ void initFSAASupport();
+
+ // determines valid stencil formats
+ void initStencilFormats();
+
+ // sets a texture unit to use for texture operations other than binding a texture to a program.
+ // ensures that such operations don't negatively interact with tracking bound textures.
+ void setScratchTextureUnit();
+
+ // bound is region that may be modified and therefore has to be resolved.
+ // NULL means whole target. Can be an empty rect.
+ void flushRenderTarget(const SkIRect* bound);
+ void flushStencil(DrawType);
+ void flushAAState(DrawType);
+
+ bool configToGLFormats(GrPixelConfig config,
+ bool getSizedInternal,
+ GrGLenum* internalFormat,
+ GrGLenum* externalFormat,
+ GrGLenum* externalType);
+ // helper for onCreateTexture and writeTexturePixels
+ bool uploadTexData(const GrGLTexture::Desc& desc,
+ bool isNewTexture,
+ int left, int top, int width, int height,
+ GrPixelConfig dataConfig,
+ const void* data,
+ size_t rowBytes);
+
+ bool createRenderTargetObjects(int width, int height,
+ GrGLuint texID,
+ GrGLRenderTarget::Desc* desc);
+
+ void fillInConfigRenderableTable();
+
+ GrGLContext fGLContext;
+
+ // GL program-related state
+ ProgramCache* fProgramCache;
+ SkAutoTUnref<GrGLProgram> fCurrentProgram;
+
+ ///////////////////////////////////////////////////////////////////////////
+ ///@name Caching of GL State
+ ///@{
+ int fHWActiveTextureUnitIdx;
+ GrGLuint fHWProgramID;
+
+ GrGLProgram::SharedGLState fSharedGLProgramState;
+
+ enum TriState {
+ kNo_TriState,
+ kYes_TriState,
+ kUnknown_TriState
+ };
+
+ // last scissor / viewport scissor state seen by the GL.
+ struct {
+ TriState fEnabled;
+ GrGLIRect fRect;
+ void invalidate() {
+ fEnabled = kUnknown_TriState;
+ fRect.invalidate();
+ }
+ } fHWScissorSettings;
+
+ GrGLIRect fHWViewport;
+
+ /**
+ * Tracks bound vertex and index buffers and vertex attrib array state.
+ */
+ class HWGeometryState {
+ public:
+ HWGeometryState() { fVBOVertexArray = NULL; this->invalidate(); }
+
+ ~HWGeometryState() { SkSafeUnref(fVBOVertexArray); }
+
+ void invalidate() {
+ fBoundVertexArrayIDIsValid = false;
+ fBoundVertexBufferIDIsValid = false;
+ fDefaultVertexArrayBoundIndexBufferID = false;
+ fDefaultVertexArrayBoundIndexBufferIDIsValid = false;
+ fDefaultVertexArrayAttribState.invalidate();
+ }
+
+ void notifyVertexArrayDelete(GrGLuint id) {
+ if (fBoundVertexArrayIDIsValid && fBoundVertexArrayID == id) {
+ // Does implicit bind to 0
+ fBoundVertexArrayID = 0;
+ }
+ }
+
+ void setVertexArrayID(GrGpuGL* gpu, GrGLuint arrayID) {
+ if (!gpu->glCaps().vertexArrayObjectSupport()) {
+ GrAssert(0 == arrayID);
+ return;
+ }
+ if (!fBoundVertexArrayIDIsValid || arrayID != fBoundVertexArrayID) {
+ GR_GL_CALL(gpu->glInterface(), BindVertexArray(arrayID));
+ fBoundVertexArrayIDIsValid = true;
+ fBoundVertexArrayID = arrayID;
+ }
+ }
+
+ void notifyVertexBufferDelete(GrGLuint id) {
+ if (fBoundVertexBufferIDIsValid && id == fBoundVertexBufferID) {
+ fBoundVertexBufferID = 0;
+ }
+ if (NULL != fVBOVertexArray) {
+ fVBOVertexArray->notifyVertexBufferDelete(id);
+ }
+ fDefaultVertexArrayAttribState.notifyVertexBufferDelete(id);
+ }
+
+ void notifyIndexBufferDelete(GrGLuint id) {
+ if (fDefaultVertexArrayBoundIndexBufferIDIsValid &&
+ id == fDefaultVertexArrayBoundIndexBufferID) {
+ fDefaultVertexArrayBoundIndexBufferID = 0;
+ }
+ if (NULL != fVBOVertexArray) {
+ fVBOVertexArray->notifyIndexBufferDelete(id);
+ }
+ }
+
+ void setVertexBufferID(GrGpuGL* gpu, GrGLuint id) {
+ if (!fBoundVertexBufferIDIsValid || id != fBoundVertexBufferID) {
+ GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ARRAY_BUFFER, id));
+ fBoundVertexBufferIDIsValid = true;
+ fBoundVertexBufferID = id;
+ }
+ }
+
+ /**
+ * Binds the default vertex array and binds the index buffer. This is used when binding
+ * an index buffer in order to update it.
+ */
+ void setIndexBufferIDOnDefaultVertexArray(GrGpuGL* gpu, GrGLuint id) {
+ this->setVertexArrayID(gpu, 0);
+ if (!fDefaultVertexArrayBoundIndexBufferIDIsValid ||
+ id != fDefaultVertexArrayBoundIndexBufferID) {
+ GR_GL_CALL(gpu->glInterface(), BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, id));
+ fDefaultVertexArrayBoundIndexBufferIDIsValid = true;
+ fDefaultVertexArrayBoundIndexBufferID = id;
+ }
+ }
+
+ /**
+ * Binds the vertex array object that should be used to render from the vertex buffer.
+ * The vertex array is bound and its attrib array state object is returned. The vertex
+ * buffer is bound. The index buffer (if non-NULL) is bound to the vertex array. The
+ * returned GrGLAttribArrayState should be used to set vertex attribute arrays.
+ */
+ GrGLAttribArrayState* bindArrayAndBuffersToDraw(GrGpuGL* gpu,
+ const GrGLVertexBuffer* vbuffer,
+ const GrGLIndexBuffer* ibuffer);
+
+ private:
+ GrGLuint fBoundVertexArrayID;
+ GrGLuint fBoundVertexBufferID;
+ bool fBoundVertexArrayIDIsValid;
+ bool fBoundVertexBufferIDIsValid;
+
+ GrGLuint fDefaultVertexArrayBoundIndexBufferID;
+ bool fDefaultVertexArrayBoundIndexBufferIDIsValid;
+ // We return a non-const pointer to this from bindArrayAndBuffersToDraw when vertex array 0
+ // is bound. However, this class is internal to GrGpuGL and this object never leaks out of
+ // GrGpuGL.
+ GrGLAttribArrayState fDefaultVertexArrayAttribState;
+
+ // This is used when we're using a core profile and the vertices are in a VBO.
+ GrGLVertexArray* fVBOVertexArray;
+ } fHWGeometryState;
+
+ struct {
+ GrBlendCoeff fSrcCoeff;
+ GrBlendCoeff fDstCoeff;
+ GrColor fConstColor;
+ bool fConstColorValid;
+ TriState fEnabled;
+
+ void invalidate() {
+ fSrcCoeff = kInvalid_GrBlendCoeff;
+ fDstCoeff = kInvalid_GrBlendCoeff;
+ fConstColorValid = false;
+ fEnabled = kUnknown_TriState;
+ }
+ } fHWBlendState;
+
+ struct {
+ TriState fMSAAEnabled;
+ TriState fSmoothLineEnabled;
+ void invalidate() {
+ fMSAAEnabled = kUnknown_TriState;
+ fSmoothLineEnabled = kUnknown_TriState;
+ }
+ } fHWAAState;
+
+
+ GrGLProgram::MatrixState fHWPathStencilMatrixState;
+
+ GrStencilSettings fHWStencilSettings;
+ TriState fHWStencilTestEnabled;
+
+ GrDrawState::DrawFace fHWDrawFace;
+ TriState fHWWriteToColor;
+ TriState fHWDitherEnabled;
+ GrRenderTarget* fHWBoundRenderTarget;
+ SkTArray<GrTexture*, true> fHWBoundTextures;
+ ///@}
+
+ // we record what stencil format worked last time to hopefully exit early
+ // from our loop that tries stencil formats and calls check fb status.
+ int fLastSuccessfulStencilFmtIdx;
+
+ typedef GrGpu INHERITED;
+};
+
+#endif
diff --git a/gpu/gl/GrGpuGL_program.cpp b/gpu/gl/GrGpuGL_program.cpp
new file mode 100644
index 00000000..786640c4
--- /dev/null
+++ b/gpu/gl/GrGpuGL_program.cpp
@@ -0,0 +1,408 @@
+/*
+ * 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 "GrGpuGL.h"
+
+#include "GrEffect.h"
+#include "GrGLEffect.h"
+#include "SkTSearch.h"
+
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+struct GrGpuGL::ProgramCache::Entry {
+ SK_DECLARE_INST_COUNT_ROOT(Entry);
+ Entry() : fProgram(NULL), fLRUStamp(0) {}
+
+ SkAutoTUnref<GrGLProgram> fProgram;
+ unsigned int fLRUStamp;
+};
+
+SK_DEFINE_INST_COUNT(GrGpuGL::ProgramCache::Entry);
+
+struct GrGpuGL::ProgramCache::ProgDescLess {
+ bool operator() (const GrGLProgramDesc& desc, const Entry* entry) {
+ GrAssert(NULL != entry->fProgram.get());
+ return GrGLProgramDesc::Less(desc, entry->fProgram->getDesc());
+ }
+
+ bool operator() (const Entry* entry, const GrGLProgramDesc& desc) {
+ GrAssert(NULL != entry->fProgram.get());
+ return GrGLProgramDesc::Less(entry->fProgram->getDesc(), desc);
+ }
+};
+
+GrGpuGL::ProgramCache::ProgramCache(const GrGLContext& gl)
+ : fCount(0)
+ , fCurrLRUStamp(0)
+ , fGL(gl)
+#ifdef PROGRAM_CACHE_STATS
+ , fTotalRequests(0)
+ , fCacheMisses(0)
+ , fHashMisses(0)
+#endif
+{
+ for (int i = 0; i < 1 << kHashBits; ++i) {
+ fHashTable[i] = NULL;
+ }
+}
+
+GrGpuGL::ProgramCache::~ProgramCache() {
+ for (int i = 0; i < fCount; ++i){
+ SkDELETE(fEntries[i]);
+ }
+ // dump stats
+#ifdef PROGRAM_CACHE_STATS
+ SkDebugf("--- Program Cache ---\n");
+ SkDebugf("Total requests: %d\n", fTotalRequests);
+ SkDebugf("Cache misses: %d\n", fCacheMisses);
+ SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ?
+ 100.f * fCacheMisses / fTotalRequests :
+ 0.f);
+ int cacheHits = fTotalRequests - fCacheMisses;
+ SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
+ SkDebugf("---------------------\n");
+#endif
+}
+
+void GrGpuGL::ProgramCache::abandon() {
+ for (int i = 0; i < fCount; ++i) {
+ GrAssert(NULL != fEntries[i]->fProgram.get());
+ fEntries[i]->fProgram->abandon();
+ fEntries[i]->fProgram.reset(NULL);
+ }
+ fCount = 0;
+}
+
+int GrGpuGL::ProgramCache::search(const GrGLProgramDesc& desc) const {
+ ProgDescLess less;
+ return SkTSearch(fEntries, fCount, desc, sizeof(Entry*), less);
+}
+
+GrGLProgram* GrGpuGL::ProgramCache::getProgram(const GrGLProgramDesc& desc,
+ const GrEffectStage* colorStages[],
+ const GrEffectStage* coverageStages[]) {
+#ifdef PROGRAM_CACHE_STATS
+ ++fTotalRequests;
+#endif
+
+ Entry* entry = NULL;
+
+ uint32_t hashIdx = desc.getChecksum();
+ hashIdx ^= hashIdx >> 16;
+ if (kHashBits <= 8) {
+ hashIdx ^= hashIdx >> 8;
+ }
+ hashIdx &=((1 << kHashBits) - 1);
+ Entry* hashedEntry = fHashTable[hashIdx];
+ if (NULL != hashedEntry && hashedEntry->fProgram->getDesc() == desc) {
+ GrAssert(NULL != hashedEntry->fProgram);
+ entry = hashedEntry;
+ }
+
+ int entryIdx;
+ if (NULL == entry) {
+ entryIdx = this->search(desc);
+ if (entryIdx >= 0) {
+ entry = fEntries[entryIdx];
+#ifdef PROGRAM_CACHE_STATS
+ ++fHashMisses;
+#endif
+ }
+ }
+
+ if (NULL == entry) {
+ // We have a cache miss
+#ifdef PROGRAM_CACHE_STATS
+ ++fCacheMisses;
+#endif
+ GrGLProgram* program = GrGLProgram::Create(fGL, desc, colorStages, coverageStages);
+ if (NULL == program) {
+ return NULL;
+ }
+ int purgeIdx = 0;
+ if (fCount < kMaxEntries) {
+ entry = SkNEW(Entry);
+ purgeIdx = fCount++;
+ fEntries[purgeIdx] = entry;
+ } else {
+ GrAssert(fCount == kMaxEntries);
+ purgeIdx = 0;
+ for (int i = 1; i < kMaxEntries; ++i) {
+ if (fEntries[i]->fLRUStamp < fEntries[purgeIdx]->fLRUStamp) {
+ purgeIdx = i;
+ }
+ }
+ entry = fEntries[purgeIdx];
+ int purgedHashIdx = entry->fProgram->getDesc().getChecksum() & ((1 << kHashBits) - 1);
+ if (fHashTable[purgedHashIdx] == entry) {
+ fHashTable[purgedHashIdx] = NULL;
+ }
+ }
+ GrAssert(fEntries[purgeIdx] == entry);
+ entry->fProgram.reset(program);
+ // We need to shift fEntries around so that the entry currently at purgeIdx is placed
+ // just before the entry at ~entryIdx (in order to keep fEntries sorted by descriptor).
+ entryIdx = ~entryIdx;
+ if (entryIdx < purgeIdx) {
+ // Let E and P be the entries at index entryIdx and purgeIdx, respectively.
+ // If the entries array looks like this:
+ // aaaaEbbbbbPccccc
+ // we rearrange it to look like this:
+ // aaaaPEbbbbbccccc
+ size_t copySize = (purgeIdx - entryIdx) * sizeof(Entry*);
+ memmove(fEntries + entryIdx + 1, fEntries + entryIdx, copySize);
+ fEntries[entryIdx] = entry;
+ } else if (purgeIdx < entryIdx) {
+ // If the entries array looks like this:
+ // aaaaPbbbbbEccccc
+ // we rearrange it to look like this:
+ // aaaabbbbbPEccccc
+ size_t copySize = (entryIdx - purgeIdx - 1) * sizeof(Entry*);
+ memmove(fEntries + purgeIdx, fEntries + purgeIdx + 1, copySize);
+ fEntries[entryIdx - 1] = entry;
+ }
+#if GR_DEBUG
+ GrAssert(NULL != fEntries[0]->fProgram.get());
+ for (int i = 0; i < fCount - 1; ++i) {
+ GrAssert(NULL != fEntries[i + 1]->fProgram.get());
+ const GrGLProgramDesc& a = fEntries[i]->fProgram->getDesc();
+ const GrGLProgramDesc& b = fEntries[i + 1]->fProgram->getDesc();
+ GrAssert(GrGLProgramDesc::Less(a, b));
+ GrAssert(!GrGLProgramDesc::Less(b, a));
+ }
+#endif
+ }
+
+ fHashTable[hashIdx] = entry;
+ entry->fLRUStamp = fCurrLRUStamp;
+
+ if (SK_MaxU32 == fCurrLRUStamp) {
+ // wrap around! just trash our LRU, one time hit.
+ for (int i = 0; i < fCount; ++i) {
+ fEntries[i]->fLRUStamp = 0;
+ }
+ }
+ ++fCurrLRUStamp;
+ return entry->fProgram;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrGpuGL::abandonResources(){
+ INHERITED::abandonResources();
+ fProgramCache->abandon();
+ fHWProgramID = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define GL_CALL(X) GR_GL_CALL(this->glInterface(), X)
+
+void GrGpuGL::flushPathStencilMatrix() {
+ const SkMatrix& viewMatrix = this->getDrawState().getViewMatrix();
+ const GrRenderTarget* rt = this->getDrawState().getRenderTarget();
+ SkISize size;
+ size.set(rt->width(), rt->height());
+ const SkMatrix& vm = this->getDrawState().getViewMatrix();
+
+ if (fHWPathStencilMatrixState.fRenderTargetOrigin != rt->origin() ||
+ !fHWPathStencilMatrixState.fViewMatrix.cheapEqualTo(viewMatrix) ||
+ fHWPathStencilMatrixState.fRenderTargetSize!= size) {
+ // rescale the coords from skia's "device" coords to GL's normalized coords,
+ // and perform a y-flip if required.
+ SkMatrix m;
+ if (kBottomLeft_GrSurfaceOrigin == rt->origin()) {
+ m.setScale(SkIntToScalar(2) / rt->width(), SkIntToScalar(-2) / rt->height());
+ m.postTranslate(-SK_Scalar1, SK_Scalar1);
+ } else {
+ 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
+ SkScalarToFloat(m[SkMatrix::kMScaleX]),
+ SkScalarToFloat(m[SkMatrix::kMSkewY]),
+ 0,
+ SkScalarToFloat(m[SkMatrix::kMPersp0]),
+
+ // col 1
+ SkScalarToFloat(m[SkMatrix::kMSkewX]),
+ SkScalarToFloat(m[SkMatrix::kMScaleY]),
+ 0,
+ SkScalarToFloat(m[SkMatrix::kMPersp1]),
+
+ // col 2
+ 0, 0, 0, 0,
+
+ // col3
+ SkScalarToFloat(m[SkMatrix::kMTransX]),
+ SkScalarToFloat(m[SkMatrix::kMTransY]),
+ 0.0f,
+ SkScalarToFloat(m[SkMatrix::kMPersp2])
+ };
+ GL_CALL(MatrixMode(GR_GL_PROJECTION));
+ GL_CALL(LoadMatrixf(mv));
+ fHWPathStencilMatrixState.fViewMatrix = vm;
+ fHWPathStencilMatrixState.fRenderTargetSize = size;
+ fHWPathStencilMatrixState.fRenderTargetOrigin = rt->origin();
+ }
+}
+
+bool GrGpuGL::flushGraphicsState(DrawType type, const GrDeviceCoordTexture* dstCopy) {
+ const GrDrawState& drawState = this->getDrawState();
+
+ // GrGpu::setupClipAndFlushState should have already checked this and bailed if not true.
+ GrAssert(NULL != drawState.getRenderTarget());
+
+ if (kStencilPath_DrawType == type) {
+ this->flushPathStencilMatrix();
+ } else {
+ this->flushMiscFixedFunctionState();
+
+ GrBlendCoeff srcCoeff;
+ GrBlendCoeff dstCoeff;
+ GrDrawState::BlendOptFlags blendOpts = drawState.getBlendOpts(false, &srcCoeff, &dstCoeff);
+ if (GrDrawState::kSkipDraw_BlendOptFlag & blendOpts) {
+ return false;
+ }
+
+ SkSTArray<8, const GrEffectStage*, true> colorStages;
+ SkSTArray<8, const GrEffectStage*, true> coverageStages;
+ GrGLProgramDesc desc;
+ GrGLProgramDesc::Build(this->getDrawState(),
+ kDrawPoints_DrawType == type,
+ blendOpts,
+ srcCoeff,
+ dstCoeff,
+ this,
+ dstCopy,
+ &colorStages,
+ &coverageStages,
+ &desc);
+
+ fCurrentProgram.reset(fProgramCache->getProgram(desc,
+ colorStages.begin(),
+ coverageStages.begin()));
+ if (NULL == fCurrentProgram.get()) {
+ GrAssert(!"Failed to create program!");
+ return false;
+ }
+ fCurrentProgram.get()->ref();
+
+ GrGLuint programID = fCurrentProgram->programID();
+ if (fHWProgramID != programID) {
+ GL_CALL(UseProgram(programID));
+ fHWProgramID = programID;
+ }
+
+ fCurrentProgram->overrideBlend(&srcCoeff, &dstCoeff);
+ this->flushBlend(kDrawLines_DrawType == type, srcCoeff, dstCoeff);
+
+ fCurrentProgram->setData(this,
+ blendOpts,
+ colorStages.begin(),
+ coverageStages.begin(),
+ dstCopy,
+ &fSharedGLProgramState);
+ }
+ this->flushStencil(type);
+ this->flushScissor();
+ this->flushAAState(type);
+
+ SkIRect* devRect = NULL;
+ SkIRect devClipBounds;
+ if (drawState.isClipState()) {
+ this->getClip()->getConservativeBounds(drawState.getRenderTarget(), &devClipBounds);
+ devRect = &devClipBounds;
+ }
+ // This must come after textures are flushed because a texture may need
+ // to be msaa-resolved (which will modify bound FBO state).
+ this->flushRenderTarget(devRect);
+
+ return true;
+}
+
+void GrGpuGL::setupGeometry(const DrawInfo& info, size_t* indexOffsetInBytes) {
+
+ GrGLsizei stride = this->getDrawState().getVertexSize();
+
+ size_t vertexOffsetInBytes = stride * info.startVertex();
+
+ const GeometryPoolState& geoPoolState = this->getGeomPoolState();
+
+ GrGLVertexBuffer* vbuf;
+ switch (this->getGeomSrc().fVertexSrc) {
+ case kBuffer_GeometrySrcType:
+ vbuf = (GrGLVertexBuffer*) this->getGeomSrc().fVertexBuffer;
+ break;
+ case kArray_GeometrySrcType:
+ case kReserved_GeometrySrcType:
+ this->finalizeReservedVertices();
+ vertexOffsetInBytes += geoPoolState.fPoolStartVertex * this->getGeomSrc().fVertexSize;
+ vbuf = (GrGLVertexBuffer*) geoPoolState.fPoolVertexBuffer;
+ break;
+ default:
+ vbuf = NULL; // suppress warning
+ GrCrash("Unknown geometry src type!");
+ }
+
+ GrAssert(NULL != vbuf);
+ GrAssert(!vbuf->isLocked());
+ vertexOffsetInBytes += vbuf->baseOffset();
+
+ GrGLIndexBuffer* ibuf = NULL;
+ if (info.isIndexed()) {
+ GrAssert(NULL != indexOffsetInBytes);
+
+ switch (this->getGeomSrc().fIndexSrc) {
+ case kBuffer_GeometrySrcType:
+ *indexOffsetInBytes = 0;
+ ibuf = (GrGLIndexBuffer*)this->getGeomSrc().fIndexBuffer;
+ break;
+ case kArray_GeometrySrcType:
+ case kReserved_GeometrySrcType:
+ this->finalizeReservedIndices();
+ *indexOffsetInBytes = geoPoolState.fPoolStartIndex * sizeof(GrGLushort);
+ ibuf = (GrGLIndexBuffer*) geoPoolState.fPoolIndexBuffer;
+ break;
+ default:
+ ibuf = NULL; // suppress warning
+ GrCrash("Unknown geometry src type!");
+ }
+
+ GrAssert(NULL != ibuf);
+ GrAssert(!ibuf->isLocked());
+ *indexOffsetInBytes += ibuf->baseOffset();
+ }
+ GrGLAttribArrayState* attribState =
+ fHWGeometryState.bindArrayAndBuffersToDraw(this, vbuf, ibuf);
+
+ uint32_t usedAttribArraysMask = 0;
+ const GrVertexAttrib* vertexAttrib = this->getDrawState().getVertexAttribs();
+ int vertexAttribCount = this->getDrawState().getVertexAttribCount();
+ for (int vertexAttribIndex = 0; vertexAttribIndex < vertexAttribCount;
+ ++vertexAttribIndex, ++vertexAttrib) {
+
+ usedAttribArraysMask |= (1 << vertexAttribIndex);
+ GrVertexAttribType attribType = vertexAttrib->fType;
+ attribState->set(this,
+ vertexAttribIndex,
+ vbuf,
+ GrGLAttribTypeToLayout(attribType).fCount,
+ GrGLAttribTypeToLayout(attribType).fType,
+ GrGLAttribTypeToLayout(attribType).fNormalized,
+ stride,
+ reinterpret_cast<GrGLvoid*>(
+ vertexOffsetInBytes + vertexAttrib->fOffset));
+ }
+
+ attribState->disableUnusedAttribArrays(this, usedAttribArraysMask);
+}
diff --git a/gpu/gl/SkGLContextHelper.cpp b/gpu/gl/SkGLContextHelper.cpp
new file mode 100644
index 00000000..9dcc8ecb
--- /dev/null
+++ b/gpu/gl/SkGLContextHelper.cpp
@@ -0,0 +1,139 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gl/SkGLContextHelper.h"
+#include "GrGLUtil.h"
+
+SK_DEFINE_INST_COUNT(SkGLContextHelper)
+
+SkGLContextHelper::SkGLContextHelper()
+ : fFBO(0)
+ , fColorBufferID(0)
+ , fDepthStencilBufferID(0)
+ , fGL(NULL) {
+}
+
+SkGLContextHelper::~SkGLContextHelper() {
+
+ if (fGL) {
+ // TODO: determine why DeleteFramebuffers is generating a GL error in tests
+ SK_GL_NOERRCHECK(*this, DeleteFramebuffers(1, &fFBO));
+ SK_GL_NOERRCHECK(*this, DeleteRenderbuffers(1, &fColorBufferID));
+ SK_GL_NOERRCHECK(*this, DeleteRenderbuffers(1, &fDepthStencilBufferID));
+ }
+
+ SkSafeUnref(fGL);
+}
+
+bool SkGLContextHelper::init(int width, int height) {
+ if (fGL) {
+ fGL->unref();
+ this->destroyGLContext();
+ }
+
+ fGL = this->createGLContext();
+ if (fGL) {
+ const GrGLubyte* temp;
+
+ GrGLBinding bindingInUse = GrGLGetBindingInUse(this->gl());
+
+ if (!fGL->validate(bindingInUse) || !fExtensions.init(bindingInUse, fGL)) {
+ fGL = NULL;
+ this->destroyGLContext();
+ return false;
+ }
+
+ SK_GL_RET(*this, temp, GetString(GR_GL_VERSION));
+ const char* versionStr = reinterpret_cast<const char*>(temp);
+ GrGLVersion version = GrGLGetVersionFromString(versionStr);
+
+ // clear any existing GL erorrs
+ GrGLenum error;
+ do {
+ SK_GL_RET(*this, error, GetError());
+ } while (GR_GL_NO_ERROR != error);
+
+ SK_GL(*this, GenFramebuffers(1, &fFBO));
+ SK_GL(*this, BindFramebuffer(GR_GL_FRAMEBUFFER, fFBO));
+ SK_GL(*this, GenRenderbuffers(1, &fColorBufferID));
+ SK_GL(*this, BindRenderbuffer(GR_GL_RENDERBUFFER, fColorBufferID));
+ if (kES2_GrGLBinding == bindingInUse) {
+ SK_GL(*this, RenderbufferStorage(GR_GL_RENDERBUFFER,
+ GR_GL_RGBA8,
+ width, height));
+ } else {
+ SK_GL(*this, RenderbufferStorage(GR_GL_RENDERBUFFER,
+ GR_GL_RGBA,
+ width, height));
+ }
+ SK_GL(*this, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_COLOR_ATTACHMENT0,
+ GR_GL_RENDERBUFFER,
+ fColorBufferID));
+ SK_GL(*this, GenRenderbuffers(1, &fDepthStencilBufferID));
+ SK_GL(*this, BindRenderbuffer(GR_GL_RENDERBUFFER, fDepthStencilBufferID));
+
+ // Some drivers that support packed depth stencil will only succeed
+ // in binding a packed format an FBO. However, we can't rely on packed
+ // depth stencil being available.
+ bool supportsPackedDepthStencil;
+ if (kES2_GrGLBinding == bindingInUse) {
+ supportsPackedDepthStencil = this->hasExtension("GL_OES_packed_depth_stencil");
+ } else {
+ supportsPackedDepthStencil = version >= GR_GL_VER(3,0) ||
+ this->hasExtension("GL_EXT_packed_depth_stencil") ||
+ this->hasExtension("GL_ARB_framebuffer_object");
+ }
+
+ if (supportsPackedDepthStencil) {
+ // ES2 requires sized internal formats for RenderbufferStorage
+ // On Desktop we let the driver decide.
+ GrGLenum format = kES2_GrGLBinding == bindingInUse ?
+ GR_GL_DEPTH24_STENCIL8 :
+ GR_GL_DEPTH_STENCIL;
+ SK_GL(*this, RenderbufferStorage(GR_GL_RENDERBUFFER,
+ format,
+ width, height));
+ SK_GL(*this, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_DEPTH_ATTACHMENT,
+ GR_GL_RENDERBUFFER,
+ fDepthStencilBufferID));
+ } else {
+ GrGLenum format = kES2_GrGLBinding == bindingInUse ?
+ GR_GL_STENCIL_INDEX8 :
+ GR_GL_STENCIL_INDEX;
+ SK_GL(*this, RenderbufferStorage(GR_GL_RENDERBUFFER,
+ format,
+ width, height));
+ }
+ SK_GL(*this, FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
+ GR_GL_STENCIL_ATTACHMENT,
+ GR_GL_RENDERBUFFER,
+ fDepthStencilBufferID));
+ SK_GL(*this, Viewport(0, 0, width, height));
+ SK_GL(*this, ClearStencil(0));
+ SK_GL(*this, Clear(GR_GL_STENCIL_BUFFER_BIT));
+
+ SK_GL_RET(*this, error, GetError());
+ GrGLenum status;
+ SK_GL_RET(*this, status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+
+ if (GR_GL_FRAMEBUFFER_COMPLETE != status ||
+ GR_GL_NO_ERROR != error) {
+ fFBO = 0;
+ fColorBufferID = 0;
+ fDepthStencilBufferID = 0;
+ fGL->unref();
+ fGL = NULL;
+ this->destroyGLContext();
+ return false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/gpu/gl/SkNullGLContext.cpp b/gpu/gl/SkNullGLContext.cpp
new file mode 100644
index 00000000..86c09b20
--- /dev/null
+++ b/gpu/gl/SkNullGLContext.cpp
@@ -0,0 +1,13 @@
+
+/*
+ * 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/SkNullGLContext.h"
+
+const GrGLInterface* SkNullGLContext::createGLContext() {
+ return GrGLCreateNullInterface();
+};
diff --git a/gpu/gl/android/GrGLCreateNativeInterface_android.cpp b/gpu/gl/android/GrGLCreateNativeInterface_android.cpp
new file mode 100644
index 00000000..cfd181a6
--- /dev/null
+++ b/gpu/gl/android/GrGLCreateNativeInterface_android.cpp
@@ -0,0 +1,169 @@
+// Modified from chromium/src/webkit/glue/gl_bindings_skia_cmd_buffer.cc
+
+// Copyright (c) 2011 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.
+
+#include "gl/GrGLExtensions.h"
+#include "gl/GrGLInterface.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <EGL/egl.h>
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ static SkAutoTUnref<GrGLInterface> glInterface;
+ if (!glInterface.get()) {
+ GrGLExtensions extensions;
+ if (!extensions.init(kES2_GrGLBinding, glGetString, NULL, glGetIntegerv)) {
+ return NULL;
+ }
+ GrGLInterface* interface = new GrGLInterface;
+ glInterface.reset(interface);
+ interface->fBindingsExported = kES2_GrGLBinding;
+ interface->fActiveTexture = glActiveTexture;
+ interface->fAttachShader = glAttachShader;
+ interface->fBindAttribLocation = glBindAttribLocation;
+ interface->fBindBuffer = glBindBuffer;
+ interface->fBindTexture = glBindTexture;
+ interface->fBindVertexArray = glBindVertexArrayOES;
+ interface->fBlendColor = glBlendColor;
+ interface->fBlendFunc = glBlendFunc;
+ 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->fCopyTexSubImage2D = glCopyTexSubImage2D;
+ interface->fCreateProgram = glCreateProgram;
+ interface->fCreateShader = glCreateShader;
+ interface->fCullFace = glCullFace;
+ interface->fDeleteBuffers = glDeleteBuffers;
+ interface->fDeleteProgram = glDeleteProgram;
+ interface->fDeleteShader = glDeleteShader;
+ interface->fDeleteTextures = glDeleteTextures;
+ interface->fDeleteVertexArrays = glDeleteVertexArraysOES;
+ interface->fDepthMask = glDepthMask;
+ interface->fDisable = glDisable;
+ interface->fDisableVertexAttribArray = glDisableVertexAttribArray;
+ interface->fDrawArrays = glDrawArrays;
+ interface->fDrawElements = glDrawElements;
+ interface->fEnable = glEnable;
+ interface->fEnableVertexAttribArray = glEnableVertexAttribArray;
+ interface->fFinish = glFinish;
+ interface->fFlush = glFlush;
+ interface->fFrontFace = glFrontFace;
+ interface->fGenBuffers = glGenBuffers;
+ interface->fGenerateMipmap = glGenerateMipmap;
+ interface->fGenTextures = glGenTextures;
+ interface->fGenVertexArrays = glGenVertexArraysOES;
+ interface->fGetBufferParameteriv = glGetBufferParameteriv;
+ interface->fGetError = glGetError;
+ interface->fGetIntegerv = glGetIntegerv;
+ interface->fGetProgramInfoLog = glGetProgramInfoLog;
+ interface->fGetProgramiv = glGetProgramiv;
+ interface->fGetShaderInfoLog = glGetShaderInfoLog;
+ interface->fGetShaderiv = glGetShaderiv;
+ interface->fGetString = glGetString;
+ interface->fGetUniformLocation = glGetUniformLocation;
+ interface->fLineWidth = glLineWidth;
+ interface->fLinkProgram = glLinkProgram;
+ interface->fPixelStorei = glPixelStorei;
+ interface->fReadPixels = glReadPixels;
+ interface->fScissor = glScissor;
+#if GR_GL_USE_NEW_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;
+ interface->fTexImage2D = glTexImage2D;
+ interface->fTexParameteri = glTexParameteri;
+ interface->fTexParameteriv = glTexParameteriv;
+ interface->fTexSubImage2D = glTexSubImage2D;
+#if GL_ARB_texture_storage
+ interface->fTexStorage2D = glTexStorage2D;
+#elif GL_EXT_texture_storage
+ interface->fTexStorage2D = glTexStorage2DEXT;
+#else
+ interface->fTexStorage2D = (GrGLTexStorage2DProc) eglGetProcAddress("glTexStorage2DEXT");
+#endif
+#if GL_EXT_discard_framebuffer
+ interface->fDiscardFramebuffer = glDiscardFramebufferEXT;
+#endif
+ 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->fUniformMatrix2fv = glUniformMatrix2fv;
+ interface->fUniformMatrix3fv = glUniformMatrix3fv;
+ interface->fUniformMatrix4fv = glUniformMatrix4fv;
+ interface->fUseProgram = glUseProgram;
+ interface->fVertexAttrib4fv = glVertexAttrib4fv;
+ interface->fVertexAttribPointer = glVertexAttribPointer;
+ interface->fViewport = glViewport;
+ interface->fBindFramebuffer = glBindFramebuffer;
+ interface->fBindRenderbuffer = glBindRenderbuffer;
+ interface->fCheckFramebufferStatus = glCheckFramebufferStatus;
+ interface->fDeleteFramebuffers = glDeleteFramebuffers;
+ interface->fDeleteRenderbuffers = glDeleteRenderbuffers;
+ interface->fFramebufferRenderbuffer = glFramebufferRenderbuffer;
+ interface->fFramebufferTexture2D = glFramebufferTexture2D;
+ if (extensions.has("GL_EXT_multisampled_render_to_texture")) {
+#if GL_EXT_multisampled_render_to_texture
+ interface->fFramebufferTexture2DMultisample = glFramebufferTexture2DMultisampleEXT;
+ interface->fRenderbufferStorageMultisample = glRenderbufferStorageMultisampleEXT;
+#else
+ interface->fFramebufferTexture2DMultisample = (GrGLFramebufferTexture2DMultisampleProc) eglGetProcAddress("glFramebufferTexture2DMultisampleEXT");
+ interface->fRenderbufferStorageMultisample = (GrGLRenderbufferStorageMultisampleProc) eglGetProcAddress("glRenderbufferStorageMultisampleEXT");
+#endif
+ } else if (extensions.has("GL_IMG_multisampled_render_to_texture")) {
+#if GL_IMG_multisampled_render_to_texture
+ interface->fFramebufferTexture2DMultisample = glFramebufferTexture2DMultisampleIMG;
+ interface->fRenderbufferStorageMultisample = glRenderbufferStorageMultisampleIMG;
+#else
+ interface->fFramebufferTexture2DMultisample = (GrGLFramebufferTexture2DMultisampleProc) eglGetProcAddress("glFramebufferTexture2DMultisampleIMG");
+ interface->fRenderbufferStorageMultisample = (GrGLRenderbufferStorageMultisampleProc) eglGetProcAddress("glRenderbufferStorageMultisampleIMG");
+#endif
+ }
+ interface->fGenFramebuffers = glGenFramebuffers;
+ interface->fGenRenderbuffers = glGenRenderbuffers;
+ interface->fGetFramebufferAttachmentParameteriv = glGetFramebufferAttachmentParameteriv;
+ interface->fGetRenderbufferParameteriv = glGetRenderbufferParameteriv;
+ interface->fRenderbufferStorage = glRenderbufferStorage;
+#if GL_OES_mapbuffer
+ interface->fMapBuffer = glMapBufferOES;
+ interface->fUnmapBuffer = glUnmapBufferOES;
+#else
+ interface->fMapBuffer = (GrGLMapBufferProc) eglGetProcAddress("glMapBufferOES");
+ interface->fUnmapBuffer = (GrGLUnmapBufferProc) eglGetProcAddress("glUnmapBufferOES");
+#endif
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/gpu/gl/android/SkNativeGLContext_android.cpp b/gpu/gl/android/SkNativeGLContext_android.cpp
new file mode 100644
index 00000000..f21eed86
--- /dev/null
+++ b/gpu/gl/android/SkNativeGLContext_android.cpp
@@ -0,0 +1,104 @@
+
+/*
+ * 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/SkNativeGLContext.h"
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldEGLContext = eglGetCurrentContext();
+ fOldDisplay = eglGetCurrentDisplay();
+ fOldSurface = eglGetCurrentSurface(EGL_DRAW);
+
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+ if (NULL != fOldDisplay) {
+ eglMakeCurrent(fOldDisplay, fOldSurface, fOldSurface, fOldEGLContext);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkNativeGLContext::SkNativeGLContext()
+ : fContext(EGL_NO_CONTEXT)
+ , fDisplay(EGL_NO_DISPLAY)
+ , fSurface(EGL_NO_SURFACE) {
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+ if (fDisplay) {
+ eglMakeCurrent(fDisplay, 0, 0, 0);
+
+ if (fContext) {
+ eglDestroyContext(fDisplay, fContext);
+ fContext = EGL_NO_CONTEXT;
+ }
+
+ if (fSurface) {
+ eglDestroySurface(fDisplay, fSurface);
+ fSurface = EGL_NO_SURFACE;
+ }
+
+ //TODO should we close the display?
+ fDisplay = EGL_NO_DISPLAY;
+ }
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ fDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ eglInitialize(fDisplay, &majorVersion, &minorVersion);
+
+ EGLint numConfigs;
+ static const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE
+ };
+
+ EGLConfig surfaceConfig;
+ eglChooseConfig(fDisplay, configAttribs, &surfaceConfig, 1, &numConfigs);
+
+ static const EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ fContext = eglCreateContext(fDisplay, surfaceConfig, NULL, contextAttribs);
+
+
+ static const EGLint surfaceAttribs[] = {
+ EGL_WIDTH, 1,
+ EGL_HEIGHT, 1,
+ EGL_NONE
+ };
+ fSurface = eglCreatePbufferSurface(fDisplay, surfaceConfig, surfaceAttribs);
+
+ eglMakeCurrent(fDisplay, fSurface, fSurface, fContext);
+
+ const GrGLInterface* interface = GrGLCreateNativeInterface();
+ if (!interface) {
+ SkDebugf("Failed to create gl interface");
+ this->destroyGLContext();
+ return NULL;
+ }
+ return interface;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+ if (!eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) {
+ SkDebugf("Could not set the context.\n");
+ }
+}
diff --git a/gpu/gl/angle/GrGLCreateANGLEInterface.cpp b/gpu/gl/angle/GrGLCreateANGLEInterface.cpp
new file mode 100644
index 00000000..d74e395f
--- /dev/null
+++ b/gpu/gl/angle/GrGLCreateANGLEInterface.cpp
@@ -0,0 +1,159 @@
+
+/*
+ * 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/GrGLInterface.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include "GLES2/gl2.h"
+#include "GLES2/gl2ext.h"
+#include "EGL/egl.h"
+
+#define GET_PROC(name) \
+ interface->f ## name = (GrGL ## name ## Proc) GetProcAddress(ghANGLELib, "gl" #name);
+
+const GrGLInterface* GrGLCreateANGLEInterface() {
+
+ static SkAutoTUnref<GrGLInterface> glInterface;
+ static HMODULE ghANGLELib = NULL;
+
+ if (NULL == ghANGLELib) {
+ // We load the ANGLE library and never let it go
+ ghANGLELib = LoadLibrary("libGLESv2.dll");
+ }
+ if (NULL == ghANGLELib) {
+ // We can't setup the interface correctly w/o the DLL
+ return NULL;
+ }
+
+ if (!glInterface.get()) {
+ GrGLInterface* interface = new GrGLInterface;
+ glInterface.reset(interface);
+ interface->fBindingsExported = kES2_GrGLBinding;
+
+ GET_PROC(ActiveTexture);
+ GET_PROC(AttachShader);
+ GET_PROC(BindAttribLocation);
+ GET_PROC(BindBuffer);
+ GET_PROC(BindTexture);
+ interface->fBindVertexArray =
+ (GrGLBindVertexArrayProc) eglGetProcAddress("glBindVertexArrayOES");
+ GET_PROC(BlendColor);
+ GET_PROC(BlendFunc);
+ GET_PROC(BufferData);
+ GET_PROC(BufferSubData);
+ GET_PROC(Clear);
+ GET_PROC(ClearColor);
+ GET_PROC(ClearStencil);
+ GET_PROC(ColorMask);
+ GET_PROC(CompileShader);
+ GET_PROC(CompressedTexImage2D);
+ GET_PROC(CopyTexSubImage2D);
+ GET_PROC(CreateProgram);
+ GET_PROC(CreateShader);
+ GET_PROC(CullFace);
+ GET_PROC(DeleteBuffers);
+ GET_PROC(DeleteProgram);
+ GET_PROC(DeleteShader);
+ GET_PROC(DeleteTextures);
+ interface->fDeleteVertexArrays =
+ (GrGLDeleteVertexArraysProc) eglGetProcAddress("glDeleteVertexArraysOES");
+ GET_PROC(DepthMask);
+ GET_PROC(Disable);
+ GET_PROC(DisableVertexAttribArray);
+ GET_PROC(DrawArrays);
+ GET_PROC(DrawElements);
+ GET_PROC(Enable);
+ GET_PROC(EnableVertexAttribArray);
+ GET_PROC(Finish);
+ GET_PROC(Flush);
+ GET_PROC(FrontFace);
+ GET_PROC(GenBuffers);
+ GET_PROC(GenerateMipmap);
+ GET_PROC(GenTextures);
+ interface->fGenVertexArrays =
+ (GrGLGenVertexArraysProc) eglGetProcAddress("glGenVertexArraysOES");
+ GET_PROC(GetBufferParameteriv);
+ GET_PROC(GetError);
+ GET_PROC(GetIntegerv);
+ GET_PROC(GetProgramInfoLog);
+ GET_PROC(GetProgramiv);
+ GET_PROC(GetShaderInfoLog);
+ GET_PROC(GetShaderiv);
+ GET_PROC(GetString);
+ GET_PROC(GetUniformLocation);
+ GET_PROC(LineWidth);
+ GET_PROC(LinkProgram);
+ GET_PROC(PixelStorei);
+ GET_PROC(ReadPixels);
+ GET_PROC(Scissor);
+ GET_PROC(ShaderSource);
+ GET_PROC(StencilFunc);
+ GET_PROC(StencilFuncSeparate);
+ GET_PROC(StencilMask);
+ GET_PROC(StencilMaskSeparate);
+ GET_PROC(StencilOp);
+ GET_PROC(StencilOpSeparate);
+ GET_PROC(TexImage2D);
+ GET_PROC(TexParameteri);
+ GET_PROC(TexParameteriv);
+ GET_PROC(TexSubImage2D);
+#if GL_ARB_texture_storage
+ GET_PROC(TexStorage2D);
+#elif GL_EXT_texture_storage
+ interface->fTexStorage2D = (GrGLTexStorage2DProc) eglGetProcAddress("glTexStorage2DEXT");
+#endif
+ GET_PROC(Uniform1f);
+ GET_PROC(Uniform1i);
+ GET_PROC(Uniform1fv);
+ GET_PROC(Uniform1iv);
+
+ GET_PROC(Uniform2f);
+ GET_PROC(Uniform2i);
+ GET_PROC(Uniform2fv);
+ GET_PROC(Uniform2iv);
+
+ GET_PROC(Uniform3f);
+ GET_PROC(Uniform3i);
+ GET_PROC(Uniform3fv);
+ GET_PROC(Uniform3iv);
+
+ GET_PROC(Uniform4f);
+ GET_PROC(Uniform4i);
+ GET_PROC(Uniform4fv);
+ GET_PROC(Uniform4iv);
+
+ GET_PROC(UniformMatrix2fv);
+ GET_PROC(UniformMatrix3fv);
+ GET_PROC(UniformMatrix4fv);
+ GET_PROC(UseProgram);
+ GET_PROC(VertexAttrib4fv);
+ GET_PROC(VertexAttribPointer);
+ GET_PROC(Viewport);
+ GET_PROC(BindFramebuffer);
+ GET_PROC(BindRenderbuffer);
+ GET_PROC(CheckFramebufferStatus);
+ GET_PROC(DeleteFramebuffers);
+ GET_PROC(DeleteRenderbuffers);
+ GET_PROC(FramebufferRenderbuffer);
+ GET_PROC(FramebufferTexture2D);
+ GET_PROC(GenFramebuffers);
+ GET_PROC(GenRenderbuffers);
+ GET_PROC(GetFramebufferAttachmentParameteriv);
+ GET_PROC(GetRenderbufferParameteriv);
+ GET_PROC(RenderbufferStorage);
+
+ interface->fMapBuffer = (GrGLMapBufferProc) eglGetProcAddress("glMapBufferOES");
+ interface->fUnmapBuffer = (GrGLUnmapBufferProc) eglGetProcAddress("glUnmapBufferOES");
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/gpu/gl/angle/SkANGLEGLContext.cpp b/gpu/gl/angle/SkANGLEGLContext.cpp
new file mode 100644
index 00000000..fea27622
--- /dev/null
+++ b/gpu/gl/angle/SkANGLEGLContext.cpp
@@ -0,0 +1,107 @@
+
+/*
+ * 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/SkANGLEGLContext.h"
+
+SkANGLEGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldEGLContext = eglGetCurrentContext();
+ fOldDisplay = eglGetCurrentDisplay();
+ fOldSurface = eglGetCurrentSurface(EGL_DRAW);
+
+}
+
+SkANGLEGLContext::AutoContextRestore::~AutoContextRestore() {
+ if (NULL != fOldDisplay) {
+ eglMakeCurrent(fOldDisplay, fOldSurface, fOldSurface, fOldEGLContext);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkANGLEGLContext::SkANGLEGLContext()
+ : fContext(EGL_NO_CONTEXT)
+ , fDisplay(EGL_NO_DISPLAY)
+ , fSurface(EGL_NO_SURFACE) {
+}
+
+SkANGLEGLContext::~SkANGLEGLContext() {
+ this->destroyGLContext();
+}
+
+void SkANGLEGLContext::destroyGLContext() {
+ if (fDisplay) {
+ eglMakeCurrent(fDisplay, 0, 0, 0);
+
+ if (fContext) {
+ eglDestroyContext(fDisplay, fContext);
+ fContext = EGL_NO_CONTEXT;
+ }
+
+ if (fSurface) {
+ eglDestroySurface(fDisplay, fSurface);
+ fSurface = EGL_NO_SURFACE;
+ }
+
+ //TODO should we close the display?
+ fDisplay = EGL_NO_DISPLAY;
+ }
+}
+
+const GrGLInterface* SkANGLEGLContext::createGLContext() {
+
+ fDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ eglInitialize(fDisplay, &majorVersion, &minorVersion);
+
+ EGLint numConfigs;
+ static const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE
+ };
+
+ EGLConfig surfaceConfig;
+ eglChooseConfig(fDisplay, configAttribs, &surfaceConfig, 1, &numConfigs);
+
+ static const EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ fContext = eglCreateContext(fDisplay, surfaceConfig, NULL, contextAttribs);
+
+
+ static const EGLint surfaceAttribs[] = {
+ EGL_WIDTH, 1,
+ EGL_HEIGHT, 1,
+ EGL_NONE
+ };
+ fSurface = eglCreatePbufferSurface(fDisplay, surfaceConfig, surfaceAttribs);
+
+ eglMakeCurrent(fDisplay, fSurface, fSurface, fContext);
+
+ const GrGLInterface* interface = GrGLCreateANGLEInterface();
+ if (NULL == interface) {
+ SkDebugf("Could not create ANGLE GL interface!\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ return interface;
+}
+
+void SkANGLEGLContext::makeCurrent() const {
+ if (!eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) {
+ SkDebugf("Could not set the context.\n");
+ }
+}
diff --git a/gpu/gl/debug/GrBufferObj.cpp b/gpu/gl/debug/GrBufferObj.cpp
new file mode 100644
index 00000000..37d4438e
--- /dev/null
+++ b/gpu/gl/debug/GrBufferObj.cpp
@@ -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.
+ */
+
+#include "GrBufferObj.h"
+
+void GrBufferObj::allocate(GrGLsizeiptr size, const GrGLchar *dataPtr) {
+ GrAlwaysAssert(size >= 0);
+
+ // delete pre-existing data
+ delete[] fDataPtr;
+
+ fSize = size;
+ fDataPtr = new GrGLchar[size];
+ if (dataPtr) {
+ memcpy(fDataPtr, dataPtr, fSize);
+ }
+ // TODO: w/ no dataPtr the data is unitialized - this could be tracked
+}
+
+void GrBufferObj::deleteAction() {
+
+ // buffers are automatically unmapped when deleted
+ this->resetMapped();
+
+ this->INHERITED::deleteAction();
+}
diff --git a/gpu/gl/debug/GrBufferObj.h b/gpu/gl/debug/GrBufferObj.h
new file mode 100644
index 00000000..fecfeb5e
--- /dev/null
+++ b/gpu/gl/debug/GrBufferObj.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 GrBufferObj_DEFINED
+#define GrBufferObj_DEFINED
+
+#include "GrFakeRefObj.h"
+#include "../GrGLDefines.h"
+
+////////////////////////////////////////////////////////////////////////////////
+class GrBufferObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrBufferObj);
+
+public:
+ GrBufferObj()
+ : GrFakeRefObj()
+ , fDataPtr(NULL)
+ , fMapped(false)
+ , fBound(false)
+ , fSize(0)
+ , fUsage(GR_GL_STATIC_DRAW) {
+ }
+ virtual ~GrBufferObj() {
+ delete[] fDataPtr;
+ }
+
+ void access() {
+ // cannot access the buffer if it is currently mapped
+ GrAlwaysAssert(!fMapped);
+ }
+
+ void setMapped() { fMapped = true; }
+ void resetMapped() { fMapped = false; }
+ bool getMapped() const { return fMapped; }
+
+ void setBound() { fBound = true; }
+ void resetBound() { fBound = false; }
+ bool getBound() const { return fBound; }
+
+ void allocate(GrGLsizeiptr size, const GrGLchar *dataPtr);
+ GrGLsizeiptr getSize() const { return fSize; }
+ GrGLchar *getDataPtr() { return fDataPtr; }
+
+ void setUsage(GrGLint usage) { fUsage = usage; }
+ GrGLint getUsage() const { return fUsage; }
+
+ virtual void deleteAction() SK_OVERRIDE;
+
+protected:
+private:
+
+ GrGLchar* fDataPtr;
+ bool fMapped; // is the buffer object mapped via "glMapBuffer"?
+ bool fBound; // is the buffer object bound via "glBindBuffer"?
+ GrGLsizeiptr fSize; // size in bytes
+ GrGLint fUsage; // one of: GL_STREAM_DRAW,
+ // GL_STATIC_DRAW,
+ // GL_DYNAMIC_DRAW
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrBufferObj_DEFINED
diff --git a/gpu/gl/debug/GrDebugGL.cpp b/gpu/gl/debug/GrDebugGL.cpp
new file mode 100644
index 00000000..fb390b6b
--- /dev/null
+++ b/gpu/gl/debug/GrDebugGL.cpp
@@ -0,0 +1,211 @@
+
+/*
+ * 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 "GrDebugGL.h"
+#include "GrTextureObj.h"
+#include "GrBufferObj.h"
+#include "GrRenderBufferObj.h"
+#include "GrFrameBufferObj.h"
+#include "GrShaderObj.h"
+#include "GrProgramObj.h"
+#include "GrTextureUnitObj.h"
+#include "GrVertexArrayObj.h"
+
+GrDebugGL* GrDebugGL::gObj = NULL;
+int GrDebugGL::gStaticRefCount = 0;
+GrDebugGL::Create GrDebugGL::gFactoryFunc[kObjTypeCount] = {
+ GrTextureObj::createGrTextureObj,
+ GrBufferObj::createGrBufferObj,
+ GrRenderBufferObj::createGrRenderBufferObj,
+ GrFrameBufferObj::createGrFrameBufferObj,
+ GrShaderObj::createGrShaderObj,
+ GrProgramObj::createGrProgramObj,
+ GrTextureUnitObj::createGrTextureUnitObj,
+ GrVertexArrayObj::createGrVertexArrayObj,
+};
+
+
+GrDebugGL::GrDebugGL()
+ : fPackRowLength(0)
+ , fUnPackRowLength(0)
+ , fCurTextureUnit(0)
+ , fArrayBuffer(NULL)
+ , fElementArrayBuffer(NULL)
+ , fFrameBuffer(NULL)
+ , fRenderBuffer(NULL)
+ , fProgram(NULL)
+ , fTexture(NULL)
+ , fVertexArray(NULL) {
+
+ for (int i = 0; i < kDefaultMaxTextureUnits; ++i) {
+
+ fTextureUnits[i] = reinterpret_cast<GrTextureUnitObj *>(
+ createObj(GrDebugGL::kTextureUnit_ObjTypes));
+ fTextureUnits[i]->ref();
+
+ fTextureUnits[i]->setNumber(i);
+ }
+}
+
+GrDebugGL::~GrDebugGL() {
+ // unref & delete the texture units first so they don't show up on the leak report
+ for (int i = 0; i < kDefaultMaxTextureUnits; ++i) {
+ fTextureUnits[i]->unref();
+ fTextureUnits[i]->deleteAction();
+ }
+
+ this->report();
+
+ for (int i = 0; i < fObjects.count(); ++i) {
+ delete fObjects[i];
+ }
+ fObjects.reset();
+
+ fArrayBuffer = NULL;
+ fElementArrayBuffer = NULL;
+ fFrameBuffer = NULL;
+ fRenderBuffer = NULL;
+ fProgram = NULL;
+ fTexture = NULL;
+ fVertexArray = NULL;
+}
+
+GrFakeRefObj *GrDebugGL::findObject(GrGLuint ID, GrObjTypes type) {
+ for (int i = 0; i < fObjects.count(); ++i) {
+ if (fObjects[i]->getID() == ID) { // && fObjects[i]->getType() == type) {
+ // The application shouldn't be accessing objects
+ // that (as far as OpenGL knows) were already deleted
+ GrAlwaysAssert(!fObjects[i]->getDeleted());
+ GrAlwaysAssert(!fObjects[i]->getMarkedForDeletion());
+ return fObjects[i];
+ }
+ }
+
+ return NULL;
+}
+
+void GrDebugGL::setArrayBuffer(GrBufferObj *arrayBuffer) {
+ if (fArrayBuffer) {
+ // automatically break the binding of the old buffer
+ GrAlwaysAssert(fArrayBuffer->getBound());
+ fArrayBuffer->resetBound();
+
+ GrAlwaysAssert(!fArrayBuffer->getDeleted());
+ fArrayBuffer->unref();
+ }
+
+ fArrayBuffer = arrayBuffer;
+
+ if (fArrayBuffer) {
+ GrAlwaysAssert(!fArrayBuffer->getDeleted());
+ fArrayBuffer->ref();
+
+ GrAlwaysAssert(!fArrayBuffer->getBound());
+ fArrayBuffer->setBound();
+ }
+}
+
+void GrDebugGL::setVertexArray(GrVertexArrayObj* vertexArray) {
+ if (NULL != vertexArray) {
+ GrAssert(!vertexArray->getDeleted());
+ }
+ SkRefCnt_SafeAssign(fVertexArray, vertexArray);
+}
+
+void GrDebugGL::setElementArrayBuffer(GrBufferObj *elementArrayBuffer) {
+ if (fElementArrayBuffer) {
+ // automatically break the binding of the old buffer
+ GrAlwaysAssert(fElementArrayBuffer->getBound());
+ fElementArrayBuffer->resetBound();
+
+ GrAlwaysAssert(!fElementArrayBuffer->getDeleted());
+ fElementArrayBuffer->unref();
+ }
+
+ fElementArrayBuffer = elementArrayBuffer;
+
+ if (fElementArrayBuffer) {
+ GrAlwaysAssert(!fElementArrayBuffer->getDeleted());
+ fElementArrayBuffer->ref();
+
+ GrAlwaysAssert(!fElementArrayBuffer->getBound());
+ fElementArrayBuffer->setBound();
+ }
+}
+
+void GrDebugGL::setTexture(GrTextureObj *texture) {
+ fTextureUnits[fCurTextureUnit]->setTexture(texture);
+}
+
+void GrDebugGL::setFrameBuffer(GrFrameBufferObj *frameBuffer) {
+ if (fFrameBuffer) {
+ GrAlwaysAssert(fFrameBuffer->getBound());
+ fFrameBuffer->resetBound();
+
+ GrAlwaysAssert(!fFrameBuffer->getDeleted());
+ fFrameBuffer->unref();
+ }
+
+ fFrameBuffer = frameBuffer;
+
+ if (fFrameBuffer) {
+ GrAlwaysAssert(!fFrameBuffer->getDeleted());
+ fFrameBuffer->ref();
+
+ GrAlwaysAssert(!fFrameBuffer->getBound());
+ fFrameBuffer->setBound();
+ }
+}
+
+void GrDebugGL::setRenderBuffer(GrRenderBufferObj *renderBuffer) {
+ if (fRenderBuffer) {
+ GrAlwaysAssert(fRenderBuffer->getBound());
+ fRenderBuffer->resetBound();
+
+ GrAlwaysAssert(!fRenderBuffer->getDeleted());
+ fRenderBuffer->unref();
+ }
+
+ fRenderBuffer = renderBuffer;
+
+ if (fRenderBuffer) {
+ GrAlwaysAssert(!fRenderBuffer->getDeleted());
+ fRenderBuffer->ref();
+
+ GrAlwaysAssert(!fRenderBuffer->getBound());
+ fRenderBuffer->setBound();
+ }
+}
+
+void GrDebugGL::useProgram(GrProgramObj *program) {
+ if (fProgram) {
+ GrAlwaysAssert(fProgram->getInUse());
+ fProgram->resetInUse();
+
+ GrAlwaysAssert(!fProgram->getDeleted());
+ fProgram->unref();
+ }
+
+ fProgram = program;
+
+ if (fProgram) {
+ GrAlwaysAssert(!fProgram->getDeleted());
+ fProgram->ref();
+
+ GrAlwaysAssert(!fProgram->getInUse());
+ fProgram->setInUse();
+ }
+}
+
+void GrDebugGL::report() const {
+ for (int i = 0; i < fObjects.count(); ++i) {
+ GrAlwaysAssert(0 == fObjects[i]->getRefCount());
+ GrAlwaysAssert(0 < fObjects[i]->getHighRefCount());
+ GrAlwaysAssert(fObjects[i]->getDeleted());
+ }
+}
diff --git a/gpu/gl/debug/GrDebugGL.h b/gpu/gl/debug/GrDebugGL.h
new file mode 100644
index 00000000..795e388f
--- /dev/null
+++ b/gpu/gl/debug/GrDebugGL.h
@@ -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.
+ */
+
+#ifndef GrDebugGL_DEFINED
+#define GrDebugGL_DEFINED
+
+#include "SkTArray.h"
+#include "gl/GrGLInterface.h"
+
+class GrBufferObj;
+class GrFakeRefObj;
+class GrFrameBufferObj;
+class GrProgramObj;
+class GrRenderBufferObj;
+class GrTextureObj;
+class GrTextureUnitObj;
+class GrVertexArrayObj;
+
+////////////////////////////////////////////////////////////////////////////////
+// This is the main debugging object. It is a singleton and keeps track of
+// all the other debug objects.
+class GrDebugGL {
+public:
+ enum GrObjTypes {
+ kTexture_ObjTypes = 0,
+ kBuffer_ObjTypes,
+ kRenderBuffer_ObjTypes,
+ kFrameBuffer_ObjTypes,
+ kShader_ObjTypes,
+ kProgram_ObjTypes,
+ kTextureUnit_ObjTypes,
+ kVertexArray_ObjTypes,
+ kObjTypeCount
+ };
+
+ GrFakeRefObj *createObj(GrObjTypes type) {
+ GrFakeRefObj *temp = (*gFactoryFunc[type])();
+
+ fObjects.push_back(temp);
+
+ return temp;
+ }
+
+ GrFakeRefObj *findObject(GrGLuint ID, GrObjTypes type);
+
+ GrGLuint getMaxTextureUnits() const { return kDefaultMaxTextureUnits; }
+
+ void setCurTextureUnit(GrGLuint curTextureUnit) { fCurTextureUnit = curTextureUnit; }
+ GrGLuint getCurTextureUnit() const { return fCurTextureUnit; }
+
+ GrTextureUnitObj *getTextureUnit(int iUnit) {
+ GrAlwaysAssert(0 <= iUnit && kDefaultMaxTextureUnits > iUnit);
+
+ return fTextureUnits[iUnit];
+ }
+
+ void setArrayBuffer(GrBufferObj *arrayBuffer);
+ GrBufferObj *getArrayBuffer() { return fArrayBuffer; }
+
+ void setElementArrayBuffer(GrBufferObj *elementArrayBuffer);
+ GrBufferObj *getElementArrayBuffer() { return fElementArrayBuffer; }
+
+ void setVertexArray(GrVertexArrayObj* vertexArray);
+ GrVertexArrayObj* getVertexArray() { return fVertexArray; }
+
+ void setTexture(GrTextureObj *texture);
+
+ void setFrameBuffer(GrFrameBufferObj *frameBuffer);
+ GrFrameBufferObj *getFrameBuffer() { return fFrameBuffer; }
+
+ void setRenderBuffer(GrRenderBufferObj *renderBuffer);
+ GrRenderBufferObj *getRenderBuffer() { return fRenderBuffer; }
+
+ void useProgram(GrProgramObj *program);
+
+ void setPackRowLength(GrGLint packRowLength) {
+ fPackRowLength = packRowLength;
+ }
+ GrGLint getPackRowLength() const { return fPackRowLength; }
+
+ void setUnPackRowLength(GrGLint unPackRowLength) {
+ fUnPackRowLength = unPackRowLength;
+ }
+ GrGLint getUnPackRowLength() const { return fUnPackRowLength; }
+
+ static GrDebugGL *getInstance() {
+ // someone should admit to actually using this class
+ GrAssert(0 < gStaticRefCount);
+
+ if (NULL == gObj) {
+ gObj = SkNEW(GrDebugGL);
+ }
+
+ return gObj;
+ }
+
+ void report() const;
+
+ static void staticRef() {
+ gStaticRefCount++;
+ }
+
+ static void staticUnRef() {
+ GrAssert(gStaticRefCount > 0);
+ gStaticRefCount--;
+ if (0 == gStaticRefCount) {
+ SkDELETE(gObj);
+ gObj = NULL;
+ }
+ }
+
+protected:
+
+private:
+ // the OpenGLES 2.0 spec says this must be >= 2
+ static const GrGLint kDefaultMaxTextureUnits = 8;
+
+ GrGLint fPackRowLength;
+ GrGLint fUnPackRowLength;
+ GrGLuint fCurTextureUnit;
+ GrBufferObj* fArrayBuffer;
+ GrBufferObj* fElementArrayBuffer;
+ GrFrameBufferObj* fFrameBuffer;
+ GrRenderBufferObj* fRenderBuffer;
+ GrProgramObj* fProgram;
+ GrTextureObj* fTexture;
+ GrTextureUnitObj *fTextureUnits[kDefaultMaxTextureUnits];
+ GrVertexArrayObj *fVertexArray;
+
+ typedef GrFakeRefObj *(*Create)();
+
+ static Create gFactoryFunc[kObjTypeCount];
+
+ static GrDebugGL* gObj;
+ static int gStaticRefCount;
+
+ // global store of all objects
+ SkTArray<GrFakeRefObj *> fObjects;
+
+ GrDebugGL();
+ ~GrDebugGL();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Helper macro to make creating an object (where you need to get back a derived
+// type) easier
+#define GR_CREATE(className, classEnum) \
+ reinterpret_cast<className *>(GrDebugGL::getInstance()->createObj(classEnum))
+
+////////////////////////////////////////////////////////////////////////////////
+// Helper macro to make finding objects less painful
+#define GR_FIND(id, className, classEnum) \
+ reinterpret_cast<className *>(GrDebugGL::getInstance()->findObject(id, classEnum))
+
+#endif // GrDebugGL_DEFINED
diff --git a/gpu/gl/debug/GrFBBindableObj.h b/gpu/gl/debug/GrFBBindableObj.h
new file mode 100644
index 00000000..e2b43a6a
--- /dev/null
+++ b/gpu/gl/debug/GrFBBindableObj.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 GrFBBindableObj_DEFINED
+#define GrFBBindableObj_DEFINED
+
+#include "SkTDArray.h"
+#include "GrFakeRefObj.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// A common base class for render buffers and textures
+class GrFBBindableObj : public GrFakeRefObj {
+
+public:
+ GrFBBindableObj()
+ : GrFakeRefObj() {
+ }
+
+ virtual ~GrFBBindableObj() {
+ GrAlwaysAssert(0 == fColorReferees.count());
+ GrAlwaysAssert(0 == fDepthReferees.count());
+ GrAlwaysAssert(0 == fStencilReferees.count());
+ }
+
+ void setColorBound(GrFakeRefObj *referee) {
+ fColorReferees.append(1, &referee);
+ }
+ void resetColorBound(GrFakeRefObj *referee) {
+ int index = fColorReferees.find(referee);
+ GrAlwaysAssert(0 <= index);
+ fColorReferees.removeShuffle(index);
+ }
+ bool getColorBound(GrFakeRefObj *referee) const {
+ int index = fColorReferees.find(referee);
+ return 0 <= index;
+ }
+ bool getColorBound() const {
+ return 0 != fColorReferees.count();
+ }
+
+ void setDepthBound(GrFakeRefObj *referee) {
+ fDepthReferees.append(1, &referee);
+ }
+ void resetDepthBound(GrFakeRefObj *referee) {
+ int index = fDepthReferees.find(referee);
+ GrAlwaysAssert(0 <= index);
+ fDepthReferees.removeShuffle(index);
+ }
+ bool getDepthBound(GrFakeRefObj *referee) const {
+ int index = fDepthReferees.find(referee);
+ return 0 <= index;
+ }
+ bool getDepthBound() const {
+ return 0 != fDepthReferees.count();
+ }
+
+ void setStencilBound(GrFakeRefObj *referee) {
+ fStencilReferees.append(1, &referee);
+ }
+ void resetStencilBound(GrFakeRefObj *referee) {
+ int index = fStencilReferees.find(referee);
+ GrAlwaysAssert(0 <= index);
+ fStencilReferees.removeShuffle(index);
+ }
+ bool getStencilBound(GrFakeRefObj *referee) const {
+ int index = fStencilReferees.find(referee);
+ return 0 <= index;
+ }
+ bool getStencilBound() const {
+ return 0 != fStencilReferees.count();
+ }
+
+
+protected:
+private:
+ SkTDArray<GrFakeRefObj *> fColorReferees; // frame buffers that use this as a color buffer (via "glFramebufferRenderbuffer" or "glFramebufferTexture2D")
+ SkTDArray<GrFakeRefObj *> fDepthReferees; // frame buffers that use this as a depth buffer (via "glFramebufferRenderbuffer" or "glFramebufferTexture2D")
+ SkTDArray<GrFakeRefObj *> fStencilReferees; // frame buffers that use this as a stencil buffer (via "glFramebufferRenderbuffer" or "glFramebufferTexture2D")
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrFBBindableObj_DEFINED
diff --git a/gpu/gl/debug/GrFakeRefObj.h b/gpu/gl/debug/GrFakeRefObj.h
new file mode 100644
index 00000000..7f21c941
--- /dev/null
+++ b/gpu/gl/debug/GrFakeRefObj.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 GrFakeRefObj_DEFINED
+#define GrFakeRefObj_DEFINED
+
+#include "gl/GrGLInterface.h"
+#include "GrNoncopyable.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// This object is used to track the OpenGL objects. We don't use real
+// reference counting (i.e., we don't free the objects when their ref count
+// goes to 0) so that we can detect invalid memory accesses. The refs we
+// are tracking in this class are actually OpenGL's references to the objects
+// not "ours"
+// Each object also gets a unique globally identifying ID
+class GrFakeRefObj : public GrNoncopyable {
+public:
+ GrFakeRefObj()
+ : fRef(0)
+ , fHighRefCount(0)
+ , fMarkedForDeletion(false)
+ , fDeleted(false) {
+
+ // source for globally unique IDs - 0 is reserved!
+ static int fNextID = 0;
+
+ fID = ++fNextID;
+ }
+ virtual ~GrFakeRefObj() {};
+
+ void ref() {
+ fRef++;
+ if (fHighRefCount < fRef) {
+ fHighRefCount = fRef;
+ }
+ }
+ void unref() {
+ fRef--;
+ GrAlwaysAssert(fRef >= 0);
+
+ // often in OpenGL a given object may still be in use when the
+ // delete call is made. In these cases the object is marked
+ // for deletion and then freed when it is no longer in use
+ if (0 == fRef && fMarkedForDeletion) {
+ this->deleteAction();
+ }
+ }
+ int getRefCount() const { return fRef; }
+ int getHighRefCount() const { return fHighRefCount; }
+
+ GrGLuint getID() const { return fID; }
+
+ void setMarkedForDeletion() { fMarkedForDeletion = true; }
+ bool getMarkedForDeletion() const { return fMarkedForDeletion; }
+
+ bool getDeleted() const { return fDeleted; }
+
+ // The deleteAction fires if the object has been marked for deletion but
+ // couldn't be deleted earlier due to refs
+ virtual void deleteAction() {
+ this->setDeleted();
+ }
+
+protected:
+private:
+ int fRef; // ref count
+ int fHighRefCount; // high water mark of the ref count
+ GrGLuint fID; // globally unique ID
+ bool fMarkedForDeletion;
+ // The deleted flag is only set when OpenGL thinks the object is deleted
+ // It is obviously still allocated w/in this framework
+ bool fDeleted;
+
+ // setDeleted should only ever appear in the deleteAction method!
+ void setDeleted() { fDeleted = true; }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Each class derived from GrFakeRefObj should use this macro to add a
+// factory creation entry point. This entry point is used by the GrGLDebug
+// object to instantiate the various objects
+// all globally unique IDs
+#define GR_DEFINE_CREATOR(className) \
+ public: \
+ static GrFakeRefObj *create ## className() { \
+ return SkNEW(className); \
+ }
+
+#endif // GrFakeRefObj_DEFINED
diff --git a/gpu/gl/debug/GrFrameBufferObj.cpp b/gpu/gl/debug/GrFrameBufferObj.cpp
new file mode 100644
index 00000000..7dc12aca
--- /dev/null
+++ b/gpu/gl/debug/GrFrameBufferObj.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 "GrFrameBufferObj.h"
+#include "GrFBBindableObj.h"
+
+void GrFrameBufferObj::setColor(GrFBBindableObj *buffer) {
+ if (fColorBuffer) {
+ // automatically break the binding of the old buffer
+ GrAlwaysAssert(fColorBuffer->getColorBound(this));
+ fColorBuffer->resetColorBound(this);
+
+ GrAlwaysAssert(!fColorBuffer->getDeleted());
+ fColorBuffer->unref();
+ }
+ fColorBuffer = buffer;
+ if (fColorBuffer) {
+ GrAlwaysAssert(!fColorBuffer->getDeleted());
+ fColorBuffer->ref();
+
+ GrAlwaysAssert(!fColorBuffer->getColorBound(this));
+ fColorBuffer->setColorBound(this);
+ }
+}
+
+void GrFrameBufferObj::setDepth(GrFBBindableObj *buffer) {
+ if (fDepthBuffer) {
+ // automatically break the binding of the old buffer
+ GrAlwaysAssert(fDepthBuffer->getDepthBound(this));
+ fDepthBuffer->resetDepthBound(this);
+
+ GrAlwaysAssert(!fDepthBuffer->getDeleted());
+ fDepthBuffer->unref();
+ }
+ fDepthBuffer = buffer;
+ if (fDepthBuffer) {
+ GrAlwaysAssert(!fDepthBuffer->getDeleted());
+ fDepthBuffer->ref();
+
+ GrAlwaysAssert(!fDepthBuffer->getDepthBound(this));
+ fDepthBuffer->setDepthBound(this);
+ }
+}
+
+void GrFrameBufferObj::setStencil(GrFBBindableObj *buffer) {
+ if (fStencilBuffer) {
+ // automatically break the binding of the old buffer
+ GrAlwaysAssert(fStencilBuffer->getStencilBound(this));
+ fStencilBuffer->resetStencilBound(this);
+
+ //GrAlwaysAssert(!fStencilBuffer->getDeleted());
+ fStencilBuffer->unref();
+ }
+ fStencilBuffer = buffer;
+ if (fStencilBuffer) {
+ GrAlwaysAssert(!fStencilBuffer->getDeleted());
+ fStencilBuffer->ref();
+
+ GrAlwaysAssert(!fStencilBuffer->getStencilBound(this));
+ fStencilBuffer->setStencilBound(this);
+ }
+}
diff --git a/gpu/gl/debug/GrFrameBufferObj.h b/gpu/gl/debug/GrFrameBufferObj.h
new file mode 100644
index 00000000..33af4f7a
--- /dev/null
+++ b/gpu/gl/debug/GrFrameBufferObj.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 GrFrameBufferObj_DEFINED
+#define GrFrameBufferObj_DEFINED
+
+#include "GrFakeRefObj.h"
+class GrFBBindableObj;
+
+////////////////////////////////////////////////////////////////////////////////
+// TODO: when a framebuffer obj is bound the GL_SAMPLES query must return 0
+// TODO: GL_STENCIL_BITS must also be redirected to the framebuffer
+class GrFrameBufferObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrFrameBufferObj);
+
+public:
+ GrFrameBufferObj()
+ : GrFakeRefObj()
+ , fBound(false)
+ , fColorBuffer(NULL)
+ , fDepthBuffer(NULL)
+ , fStencilBuffer(NULL) {
+ }
+
+ virtual ~GrFrameBufferObj() {
+ fColorBuffer = NULL;
+ fDepthBuffer = NULL;
+ fStencilBuffer = NULL;
+ }
+
+ void setBound() { fBound = true; }
+ void resetBound() { fBound = false; }
+ bool getBound() const { return fBound; }
+
+ void setColor(GrFBBindableObj *buffer);
+ GrFBBindableObj *getColor() { return fColorBuffer; }
+
+ void setDepth(GrFBBindableObj *buffer);
+ GrFBBindableObj *getDepth() { return fDepthBuffer; }
+
+ void setStencil(GrFBBindableObj *buffer);
+ GrFBBindableObj *getStencil() { return fStencilBuffer; }
+
+ virtual void deleteAction() SK_OVERRIDE {
+
+ setColor(NULL);
+ setDepth(NULL);
+ setStencil(NULL);
+
+ this->INHERITED::deleteAction();
+ }
+
+protected:
+private:
+ bool fBound; // is this frame buffer currently bound via "glBindFramebuffer"?
+ GrFBBindableObj * fColorBuffer;
+ GrFBBindableObj * fDepthBuffer;
+ GrFBBindableObj * fStencilBuffer;
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrFrameBufferObj_DEFINED
diff --git a/gpu/gl/debug/GrGLCreateDebugInterface.cpp b/gpu/gl/debug/GrGLCreateDebugInterface.cpp
new file mode 100644
index 00000000..d517aa86
--- /dev/null
+++ b/gpu/gl/debug/GrGLCreateDebugInterface.cpp
@@ -0,0 +1,918 @@
+
+/*
+ * 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/GrGLInterface.h"
+#include "GrDebugGL.h"
+#include "GrShaderObj.h"
+#include "GrProgramObj.h"
+#include "GrBufferObj.h"
+#include "GrTextureUnitObj.h"
+#include "GrTextureObj.h"
+#include "GrFrameBufferObj.h"
+#include "GrRenderBufferObj.h"
+#include "GrVertexArrayObj.h"
+#include "SkFloatingPoint.h"
+#include "../GrGLNoOpInterface.h"
+
+namespace { // suppress no previous prototype warning
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLActiveTexture(GrGLenum texture) {
+
+ // Ganesh offsets the texture unit indices
+ texture -= GR_GL_TEXTURE0;
+ GrAlwaysAssert(texture < GrDebugGL::getInstance()->getMaxTextureUnits());
+
+ GrDebugGL::getInstance()->setCurTextureUnit(texture);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLAttachShader(GrGLuint programID,
+ GrGLuint shaderID) {
+
+ GrProgramObj *program = GR_FIND(programID, GrProgramObj,
+ GrDebugGL::kProgram_ObjTypes);
+ GrAlwaysAssert(program);
+
+ GrShaderObj *shader = GR_FIND(shaderID,
+ GrShaderObj,
+ GrDebugGL::kShader_ObjTypes);
+ GrAlwaysAssert(shader);
+
+ program->AttachShader(shader);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBeginQuery(GrGLenum target, GrGLuint id) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindAttribLocation(GrGLuint program,
+ GrGLuint index,
+ const char* name) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindTexture(GrGLenum target,
+ GrGLuint textureID) {
+
+ // we don't use cube maps
+ GrAlwaysAssert(target == GR_GL_TEXTURE_2D);
+ // || target == GR_GL_TEXTURE_CUBE_MAP);
+
+ // a textureID of 0 is acceptable - it binds to the default texture target
+ GrTextureObj *texture = GR_FIND(textureID, GrTextureObj,
+ GrDebugGL::kTexture_ObjTypes);
+
+ GrDebugGL::getInstance()->setTexture(texture);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBufferData(GrGLenum target,
+ GrGLsizeiptr size,
+ const GrGLvoid* data,
+ GrGLenum usage) {
+ GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
+ GR_GL_ELEMENT_ARRAY_BUFFER == target);
+ GrAlwaysAssert(size >= 0);
+ GrAlwaysAssert(GR_GL_STREAM_DRAW == usage ||
+ GR_GL_STATIC_DRAW == usage ||
+ GR_GL_DYNAMIC_DRAW == usage);
+
+ GrBufferObj *buffer = NULL;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getArrayBuffer();
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
+ break;
+ default:
+ GrCrash("Unexpected target to glBufferData");
+ break;
+ }
+
+ GrAlwaysAssert(buffer);
+ GrAlwaysAssert(buffer->getBound());
+
+ buffer->allocate(size, reinterpret_cast<const GrGLchar *>(data));
+ buffer->setUsage(usage);
+}
+
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLPixelStorei(GrGLenum pname,
+ GrGLint param) {
+
+ switch (pname) {
+ case GR_GL_UNPACK_ROW_LENGTH:
+ GrDebugGL::getInstance()->setUnPackRowLength(param);
+ break;
+ case GR_GL_PACK_ROW_LENGTH:
+ GrDebugGL::getInstance()->setPackRowLength(param);
+ break;
+ case GR_GL_UNPACK_ALIGNMENT:
+ break;
+ case GR_GL_PACK_ALIGNMENT:
+ GrAlwaysAssert(false);
+ break;
+ default:
+ GrAlwaysAssert(false);
+ break;
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLReadPixels(GrGLint x,
+ GrGLint y,
+ GrGLsizei width,
+ GrGLsizei height,
+ GrGLenum format,
+ GrGLenum type,
+ GrGLvoid* pixels) {
+
+ GrGLint pixelsInRow = width;
+ if (0 < GrDebugGL::getInstance()->getPackRowLength()) {
+ pixelsInRow = GrDebugGL::getInstance()->getPackRowLength();
+ }
+
+ GrGLint componentsPerPixel = 0;
+
+ switch (format) {
+ case GR_GL_RGBA:
+ // fallthrough
+ case GR_GL_BGRA:
+ componentsPerPixel = 4;
+ break;
+ case GR_GL_RGB:
+ componentsPerPixel = 3;
+ break;
+ case GR_GL_RED:
+ componentsPerPixel = 1;
+ break;
+ default:
+ GrAlwaysAssert(false);
+ break;
+ }
+
+ GrGLint alignment = 4; // the pack alignment (one of 1, 2, 4 or 8)
+ // Ganesh currently doesn't support setting GR_GL_PACK_ALIGNMENT
+
+ GrGLint componentSize = 0; // size (in bytes) of a single component
+
+ switch (type) {
+ case GR_GL_UNSIGNED_BYTE:
+ componentSize = 1;
+ break;
+ default:
+ GrAlwaysAssert(false);
+ break;
+ }
+
+ GrGLint rowStride = 0; // number of components (not bytes) to skip
+ if (componentSize >= alignment) {
+ rowStride = componentsPerPixel * pixelsInRow;
+ } else {
+ float fTemp =
+ sk_float_ceil(componentSize * componentsPerPixel * pixelsInRow /
+ static_cast<float>(alignment));
+ rowStride = static_cast<GrGLint>(alignment * fTemp / componentSize);
+ }
+
+ GrGLchar *scanline = static_cast<GrGLchar *>(pixels);
+ for (int y = 0; y < height; ++y) {
+ memset(scanline, 0, componentsPerPixel * componentSize * width);
+ scanline += rowStride;
+ }
+}
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLUseProgram(GrGLuint programID) {
+
+ // A programID of 0 is legal
+ GrProgramObj *program = GR_FIND(programID,
+ GrProgramObj,
+ GrDebugGL::kProgram_ObjTypes);
+
+ GrDebugGL::getInstance()->useProgram(program);
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindFramebuffer(GrGLenum target,
+ GrGLuint frameBufferID) {
+
+ GrAlwaysAssert(GR_GL_FRAMEBUFFER == target ||
+ GR_GL_READ_FRAMEBUFFER == target ||
+ GR_GL_DRAW_FRAMEBUFFER);
+
+ // a frameBufferID of 0 is acceptable - it binds to the default
+ // frame buffer
+ GrFrameBufferObj *frameBuffer = GR_FIND(frameBufferID,
+ GrFrameBufferObj,
+ GrDebugGL::kFrameBuffer_ObjTypes);
+
+ GrDebugGL::getInstance()->setFrameBuffer(frameBuffer);
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindRenderbuffer(GrGLenum target, GrGLuint renderBufferID) {
+
+ GrAlwaysAssert(GR_GL_RENDERBUFFER == target);
+
+ // a renderBufferID of 0 is acceptable - it unbinds the bound render buffer
+ GrRenderBufferObj *renderBuffer = GR_FIND(renderBufferID,
+ GrRenderBufferObj,
+ GrDebugGL::kRenderBuffer_ObjTypes);
+
+ GrDebugGL::getInstance()->setRenderBuffer(renderBuffer);
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteTextures(GrGLsizei n, const GrGLuint* textures) {
+
+ // first potentially unbind the texture
+ // TODO: move this into GrDebugGL as unBindTexture?
+ for (unsigned int i = 0;
+ i < GrDebugGL::getInstance()->getMaxTextureUnits();
+ ++i) {
+ GrTextureUnitObj *pTU = GrDebugGL::getInstance()->getTextureUnit(i);
+
+ if (pTU->getTexture()) {
+ for (int j = 0; j < n; ++j) {
+
+ if (textures[j] == pTU->getTexture()->getID()) {
+ // this ID is the current texture - revert the binding to 0
+ pTU->setTexture(NULL);
+ }
+ }
+ }
+ }
+
+ // TODO: fuse the following block with DeleteRenderBuffers?
+ // Open GL will remove a deleted render buffer from the active
+ // frame buffer but not from any other frame buffer
+ if (GrDebugGL::getInstance()->getFrameBuffer()) {
+
+ GrFrameBufferObj *frameBuffer = GrDebugGL::getInstance()->getFrameBuffer();
+
+ for (int i = 0; i < n; ++i) {
+
+ if (NULL != frameBuffer->getColor() &&
+ textures[i] == frameBuffer->getColor()->getID()) {
+ frameBuffer->setColor(NULL);
+ }
+ if (NULL != frameBuffer->getDepth() &&
+ textures[i] == frameBuffer->getDepth()->getID()) {
+ frameBuffer->setDepth(NULL);
+ }
+ if (NULL != frameBuffer->getStencil() &&
+ textures[i] == frameBuffer->getStencil()->getID()) {
+ frameBuffer->setStencil(NULL);
+ }
+ }
+ }
+
+ // then actually "delete" the buffers
+ for (int i = 0; i < n; ++i) {
+ GrTextureObj *buffer = GR_FIND(textures[i],
+ GrTextureObj,
+ GrDebugGL::kTexture_ObjTypes);
+ GrAlwaysAssert(buffer);
+
+ // OpenGL gives no guarantees if a texture is deleted while attached to
+ // something other than the currently bound frame buffer
+ GrAlwaysAssert(!buffer->getBound());
+
+ GrAlwaysAssert(!buffer->getDeleted());
+ buffer->deleteAction();
+ }
+
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteFramebuffers(GrGLsizei n,
+ const GrGLuint *frameBuffers) {
+
+ // first potentially unbind the buffers
+ if (GrDebugGL::getInstance()->getFrameBuffer()) {
+ for (int i = 0; i < n; ++i) {
+
+ if (frameBuffers[i] ==
+ GrDebugGL::getInstance()->getFrameBuffer()->getID()) {
+ // this ID is the current frame buffer - rebind to the default
+ GrDebugGL::getInstance()->setFrameBuffer(NULL);
+ }
+ }
+ }
+
+ // then actually "delete" the buffers
+ for (int i = 0; i < n; ++i) {
+ GrFrameBufferObj *buffer = GR_FIND(frameBuffers[i],
+ GrFrameBufferObj,
+ GrDebugGL::kFrameBuffer_ObjTypes);
+ GrAlwaysAssert(buffer);
+
+ GrAlwaysAssert(!buffer->getDeleted());
+ buffer->deleteAction();
+ }
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteRenderbuffers(GrGLsizei n,
+ const GrGLuint *renderBuffers) {
+
+ // first potentially unbind the buffers
+ if (GrDebugGL::getInstance()->getRenderBuffer()) {
+ for (int i = 0; i < n; ++i) {
+
+ if (renderBuffers[i] ==
+ GrDebugGL::getInstance()->getRenderBuffer()->getID()) {
+ // this ID is the current render buffer - make no
+ // render buffer be bound
+ GrDebugGL::getInstance()->setRenderBuffer(NULL);
+ }
+ }
+ }
+
+ // TODO: fuse the following block with DeleteTextures?
+ // Open GL will remove a deleted render buffer from the active frame
+ // buffer but not from any other frame buffer
+ if (GrDebugGL::getInstance()->getFrameBuffer()) {
+
+ GrFrameBufferObj *frameBuffer =
+ GrDebugGL::getInstance()->getFrameBuffer();
+
+ for (int i = 0; i < n; ++i) {
+
+ if (NULL != frameBuffer->getColor() &&
+ renderBuffers[i] == frameBuffer->getColor()->getID()) {
+ frameBuffer->setColor(NULL);
+ }
+ if (NULL != frameBuffer->getDepth() &&
+ renderBuffers[i] == frameBuffer->getDepth()->getID()) {
+ frameBuffer->setDepth(NULL);
+ }
+ if (NULL != frameBuffer->getStencil() &&
+ renderBuffers[i] == frameBuffer->getStencil()->getID()) {
+ frameBuffer->setStencil(NULL);
+ }
+ }
+ }
+
+ // then actually "delete" the buffers
+ for (int i = 0; i < n; ++i) {
+ GrRenderBufferObj *buffer = GR_FIND(renderBuffers[i],
+ GrRenderBufferObj,
+ GrDebugGL::kRenderBuffer_ObjTypes);
+ GrAlwaysAssert(buffer);
+
+ // OpenGL gives no guarantees if a render buffer is deleted
+ // while attached to something other than the currently
+ // bound frame buffer
+ GrAlwaysAssert(!buffer->getColorBound());
+ GrAlwaysAssert(!buffer->getDepthBound());
+ // However, at GrContext destroy time we release all GrRsources and so stencil buffers
+ // may get deleted before FBOs that refer to them.
+ //GrAlwaysAssert(!buffer->getStencilBound());
+
+ GrAlwaysAssert(!buffer->getDeleted());
+ buffer->deleteAction();
+ }
+ }
+
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLFramebufferRenderbuffer(GrGLenum target,
+ GrGLenum attachment,
+ GrGLenum renderbuffertarget,
+ GrGLuint renderBufferID) {
+
+ GrAlwaysAssert(GR_GL_FRAMEBUFFER == target);
+ GrAlwaysAssert(GR_GL_COLOR_ATTACHMENT0 == attachment ||
+ GR_GL_DEPTH_ATTACHMENT == attachment ||
+ GR_GL_STENCIL_ATTACHMENT == attachment);
+ GrAlwaysAssert(GR_GL_RENDERBUFFER == renderbuffertarget);
+
+ GrFrameBufferObj *framebuffer = GrDebugGL::getInstance()->getFrameBuffer();
+ // A render buffer cannot be attached to the default framebuffer
+ GrAlwaysAssert(NULL != framebuffer);
+
+ // a renderBufferID of 0 is acceptable - it unbinds the current
+ // render buffer
+ GrRenderBufferObj *renderbuffer = GR_FIND(renderBufferID,
+ GrRenderBufferObj,
+ GrDebugGL::kRenderBuffer_ObjTypes);
+
+ switch (attachment) {
+ case GR_GL_COLOR_ATTACHMENT0:
+ framebuffer->setColor(renderbuffer);
+ break;
+ case GR_GL_DEPTH_ATTACHMENT:
+ framebuffer->setDepth(renderbuffer);
+ break;
+ case GR_GL_STENCIL_ATTACHMENT:
+ framebuffer->setStencil(renderbuffer);
+ break;
+ default:
+ GrAlwaysAssert(false);
+ break;
+ };
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ GrGLvoid GR_GL_FUNCTION_TYPE debugGLFramebufferTexture2D(GrGLenum target,
+ GrGLenum attachment,
+ GrGLenum textarget,
+ GrGLuint textureID,
+ GrGLint level) {
+
+ GrAlwaysAssert(GR_GL_FRAMEBUFFER == target);
+ GrAlwaysAssert(GR_GL_COLOR_ATTACHMENT0 == attachment ||
+ GR_GL_DEPTH_ATTACHMENT == attachment ||
+ GR_GL_STENCIL_ATTACHMENT == attachment);
+ GrAlwaysAssert(GR_GL_TEXTURE_2D == textarget);
+
+ GrFrameBufferObj *framebuffer = GrDebugGL::getInstance()->getFrameBuffer();
+ // A texture cannot be attached to the default framebuffer
+ GrAlwaysAssert(NULL != framebuffer);
+
+ // A textureID of 0 is allowed - it unbinds the currently bound texture
+ GrTextureObj *texture = GR_FIND(textureID, GrTextureObj,
+ GrDebugGL::kTexture_ObjTypes);
+ if (texture) {
+ // The texture shouldn't be bound to a texture unit - this
+ // could lead to a feedback loop
+ GrAlwaysAssert(!texture->getBound());
+ }
+
+ GrAlwaysAssert(0 == level);
+
+ switch (attachment) {
+ case GR_GL_COLOR_ATTACHMENT0:
+ framebuffer->setColor(texture);
+ break;
+ case GR_GL_DEPTH_ATTACHMENT:
+ framebuffer->setDepth(texture);
+ break;
+ case GR_GL_STENCIL_ATTACHMENT:
+ framebuffer->setStencil(texture);
+ break;
+ default:
+ GrAlwaysAssert(false);
+ break;
+ };
+ }
+
+GrGLuint GR_GL_FUNCTION_TYPE debugGLCreateProgram() {
+
+ GrProgramObj *program = GR_CREATE(GrProgramObj,
+ GrDebugGL::kProgram_ObjTypes);
+
+ return program->getID();
+}
+
+GrGLuint GR_GL_FUNCTION_TYPE debugGLCreateShader(GrGLenum type) {
+
+ GrAlwaysAssert(GR_GL_VERTEX_SHADER == type ||
+ GR_GL_FRAGMENT_SHADER == type);
+
+ GrShaderObj *shader = GR_CREATE(GrShaderObj, GrDebugGL::kShader_ObjTypes);
+ shader->setType(type);
+
+ return shader->getID();
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteProgram(GrGLuint programID) {
+
+ GrProgramObj *program = GR_FIND(programID,
+ GrProgramObj,
+ GrDebugGL::kProgram_ObjTypes);
+ GrAlwaysAssert(program);
+
+ if (program->getRefCount()) {
+ // someone is still using this program so we can't delete it here
+ program->setMarkedForDeletion();
+ } else {
+ program->deleteAction();
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteShader(GrGLuint shaderID) {
+
+ GrShaderObj *shader = GR_FIND(shaderID,
+ GrShaderObj,
+ GrDebugGL::kShader_ObjTypes);
+ GrAlwaysAssert(shader);
+
+ if (shader->getRefCount()) {
+ // someone is still using this shader so we can't delete it here
+ shader->setMarkedForDeletion();
+ } else {
+ shader->deleteAction();
+ }
+}
+
+GrGLvoid debugGenObjs(GrDebugGL::GrObjTypes type,
+ GrGLsizei n,
+ GrGLuint* ids) {
+
+ for (int i = 0; i < n; ++i) {
+ GrFakeRefObj *obj = GrDebugGL::getInstance()->createObj(type);
+ GrAlwaysAssert(obj);
+ ids[i] = obj->getID();
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenBuffers(GrGLsizei n, GrGLuint* ids) {
+ debugGenObjs(GrDebugGL::kBuffer_ObjTypes, n, ids);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenerateMipmap(GrGLenum level) {
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenFramebuffers(GrGLsizei n,
+ GrGLuint* ids) {
+ debugGenObjs(GrDebugGL::kFrameBuffer_ObjTypes, n, ids);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenRenderbuffers(GrGLsizei n,
+ GrGLuint* ids) {
+ debugGenObjs(GrDebugGL::kRenderBuffer_ObjTypes, n, ids);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenTextures(GrGLsizei n, GrGLuint* ids) {
+ debugGenObjs(GrDebugGL::kTexture_ObjTypes, n, ids);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGenVertexArrays(GrGLsizei n, GrGLuint* ids) {
+ debugGenObjs(GrDebugGL::kVertexArray_ObjTypes, n, ids);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteVertexArrays(GrGLsizei n, const GrGLuint* ids) {
+ for (GrGLsizei i = 0; i < n; ++i) {
+ GrVertexArrayObj* array =
+ GR_FIND(ids[i], GrVertexArrayObj, GrDebugGL::kVertexArray_ObjTypes);
+ GrAlwaysAssert(array);
+
+ // Deleting the current vertex array binds object 0
+ if (GrDebugGL::getInstance()->getVertexArray() == array) {
+ GrDebugGL::getInstance()->setVertexArray(NULL);
+ }
+
+ if (array->getRefCount()) {
+ // someone is still using this vertex array so we can't delete it here
+ array->setMarkedForDeletion();
+ } else {
+ array->deleteAction();
+ }
+ }
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindVertexArray(GrGLuint id) {
+ GrVertexArrayObj* array = GR_FIND(id, GrVertexArrayObj, GrDebugGL::kVertexArray_ObjTypes);
+ GrAlwaysAssert((0 == id) || NULL != array);
+ GrDebugGL::getInstance()->setVertexArray(array);
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLBindBuffer(GrGLenum target, GrGLuint bufferID) {
+ GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target || GR_GL_ELEMENT_ARRAY_BUFFER == target);
+
+ GrBufferObj *buffer = GR_FIND(bufferID,
+ GrBufferObj,
+ GrDebugGL::kBuffer_ObjTypes);
+ // 0 is a permissible bufferID - it unbinds the current buffer
+
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ GrDebugGL::getInstance()->setArrayBuffer(buffer);
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ GrDebugGL::getInstance()->setElementArrayBuffer(buffer);
+ break;
+ default:
+ GrCrash("Unexpected target to glBindBuffer");
+ break;
+ }
+}
+
+// deleting a bound buffer has the side effect of binding 0
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteBuffers(GrGLsizei n, const GrGLuint* ids) {
+ // first potentially unbind the buffers
+ for (int i = 0; i < n; ++i) {
+
+ if (GrDebugGL::getInstance()->getArrayBuffer() &&
+ ids[i] == GrDebugGL::getInstance()->getArrayBuffer()->getID()) {
+ // this ID is the current array buffer
+ GrDebugGL::getInstance()->setArrayBuffer(NULL);
+ }
+ if (GrDebugGL::getInstance()->getElementArrayBuffer() &&
+ ids[i] ==
+ GrDebugGL::getInstance()->getElementArrayBuffer()->getID()) {
+ // this ID is the current element array buffer
+ GrDebugGL::getInstance()->setElementArrayBuffer(NULL);
+ }
+ }
+
+ // then actually "delete" the buffers
+ for (int i = 0; i < n; ++i) {
+ GrBufferObj *buffer = GR_FIND(ids[i],
+ GrBufferObj,
+ GrDebugGL::kBuffer_ObjTypes);
+ GrAlwaysAssert(buffer);
+
+ GrAlwaysAssert(!buffer->getDeleted());
+ buffer->deleteAction();
+ }
+}
+
+// map a buffer to the caller's address space
+GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBuffer(GrGLenum target, GrGLenum access) {
+
+ GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
+ GR_GL_ELEMENT_ARRAY_BUFFER == target);
+ // GR_GL_READ_ONLY == access || || GR_GL_READ_WRIT == access);
+ GrAlwaysAssert(GR_GL_WRITE_ONLY == access);
+
+ GrBufferObj *buffer = NULL;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getArrayBuffer();
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
+ break;
+ default:
+ GrCrash("Unexpected target to glMapBuffer");
+ break;
+ }
+
+ if (buffer) {
+ GrAlwaysAssert(!buffer->getMapped());
+ buffer->setMapped();
+ return buffer->getDataPtr();
+ }
+
+ GrAlwaysAssert(false);
+ return NULL; // no buffer bound to the target
+}
+
+// remove a buffer from the caller's address space
+// TODO: check if the "access" method from "glMapBuffer" was honored
+GrGLboolean GR_GL_FUNCTION_TYPE debugGLUnmapBuffer(GrGLenum target) {
+
+ GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
+ GR_GL_ELEMENT_ARRAY_BUFFER == target);
+
+ GrBufferObj *buffer = NULL;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getArrayBuffer();
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
+ break;
+ default:
+ GrCrash("Unexpected target to glUnmapBuffer");
+ break;
+ }
+
+ if (buffer) {
+ GrAlwaysAssert(buffer->getMapped());
+ buffer->resetMapped();
+ return GR_GL_TRUE;
+ }
+
+ GrAlwaysAssert(false);
+ return GR_GL_FALSE; // GR_GL_INVALID_OPERATION;
+}
+
+GrGLvoid GR_GL_FUNCTION_TYPE debugGLGetBufferParameteriv(GrGLenum target,
+ GrGLenum value,
+ GrGLint* params) {
+
+ GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target ||
+ GR_GL_ELEMENT_ARRAY_BUFFER == target);
+ GrAlwaysAssert(GR_GL_BUFFER_SIZE == value ||
+ GR_GL_BUFFER_USAGE == value);
+
+ GrBufferObj *buffer = NULL;
+ switch (target) {
+ case GR_GL_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getArrayBuffer();
+ break;
+ case GR_GL_ELEMENT_ARRAY_BUFFER:
+ buffer = GrDebugGL::getInstance()->getElementArrayBuffer();
+ break;
+ }
+
+ GrAlwaysAssert(buffer);
+
+ switch (value) {
+ case GR_GL_BUFFER_MAPPED:
+ *params = GR_GL_FALSE;
+ if (buffer)
+ *params = buffer->getMapped() ? GR_GL_TRUE : GR_GL_FALSE;
+ break;
+ case GR_GL_BUFFER_SIZE:
+ *params = 0;
+ if (buffer)
+ *params = buffer->getSize();
+ break;
+ case GR_GL_BUFFER_USAGE:
+ *params = GR_GL_STATIC_DRAW;
+ if (buffer)
+ *params = buffer->getUsage();
+ break;
+ default:
+ GrCrash("Unexpected value to glGetBufferParamateriv");
+ break;
+ }
+};
+} // end of namespace
+
+////////////////////////////////////////////////////////////////////////////////
+struct GrDebugGLInterface : public GrGLInterface {
+
+public:
+ SK_DECLARE_INST_COUNT(GrDebugGLInterface)
+
+ GrDebugGLInterface()
+ : fWrapped(NULL) {
+ GrDebugGL::staticRef();
+ }
+
+ virtual ~GrDebugGLInterface() {
+ GrDebugGL::staticUnRef();
+ }
+
+ void setWrapped(GrGLInterface *interface) {
+ fWrapped.reset(interface);
+ }
+
+ // TODO: there are some issues w/ wrapping another GL interface inside the
+ // debug interface:
+ // Since none of the "gl" methods are member functions they don't get
+ // a "this" pointer through which to access "fWrapped"
+ // This could be worked around by having all of them access the
+ // "glInterface" pointer - i.e., treating the debug interface as a
+ // true singleton
+ //
+ // The problem with this is that we also want to handle OpenGL
+ // contexts. The natural way to do this is to have multiple debug
+ // interfaces. Each of which represents a separate context. The
+ // static ID count would still uniquify IDs across all of them.
+ // The problem then is that we couldn't treat the debug GL
+ // interface as a singleton (since there would be one for each
+ // context).
+ //
+ // The solution to this is probably to alter SkDebugGlContext's
+ // "makeCurrent" method to make a call like "makeCurrent(this)" to
+ // the debug GL interface (assuming that the application will create
+ // multiple SkGLContextHelper's) to let it switch between the active
+ // context. Everything in the GrDebugGL object would then need to be
+ // moved to a GrContextObj and the GrDebugGL object would just switch
+ // between them. Note that this approach would also require that
+ // SkDebugGLContext wrap an arbitrary other context
+ // and then pass the wrapped interface to the debug GL interface.
+
+protected:
+private:
+
+ SkAutoTUnref<GrGLInterface> fWrapped;
+
+ typedef GrGLInterface INHERITED;
+};
+
+SK_DEFINE_INST_COUNT(GrDebugGLInterface)
+
+////////////////////////////////////////////////////////////////////////////////
+const GrGLInterface* GrGLCreateDebugInterface() {
+ GrGLInterface* interface = SkNEW(GrDebugGLInterface);
+
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+ interface->fActiveTexture = debugGLActiveTexture;
+ interface->fAttachShader = debugGLAttachShader;
+ interface->fBeginQuery = debugGLBeginQuery;
+ interface->fBindAttribLocation = debugGLBindAttribLocation;
+ interface->fBindBuffer = debugGLBindBuffer;
+ interface->fBindFragDataLocation = noOpGLBindFragDataLocation;
+ interface->fBindTexture = debugGLBindTexture;
+ interface->fBindVertexArray = debugGLBindVertexArray;
+ interface->fBlendColor = noOpGLBlendColor;
+ interface->fBlendFunc = noOpGLBlendFunc;
+ interface->fBufferData = debugGLBufferData;
+ interface->fBufferSubData = noOpGLBufferSubData;
+ interface->fClear = noOpGLClear;
+ interface->fClearColor = noOpGLClearColor;
+ interface->fClearStencil = noOpGLClearStencil;
+ interface->fColorMask = noOpGLColorMask;
+ interface->fCompileShader = noOpGLCompileShader;
+ interface->fCompressedTexImage2D = noOpGLCompressedTexImage2D;
+ interface->fCopyTexSubImage2D = noOpGLCopyTexSubImage2D;
+ interface->fCreateProgram = debugGLCreateProgram;
+ interface->fCreateShader = debugGLCreateShader;
+ interface->fCullFace = noOpGLCullFace;
+ interface->fDeleteBuffers = debugGLDeleteBuffers;
+ interface->fDeleteProgram = debugGLDeleteProgram;
+ interface->fDeleteQueries = noOpGLDeleteIds;
+ interface->fDeleteShader = debugGLDeleteShader;
+ interface->fDeleteTextures = debugGLDeleteTextures;
+ interface->fDeleteVertexArrays = debugGLDeleteVertexArrays;
+ interface->fDepthMask = noOpGLDepthMask;
+ interface->fDisable = noOpGLDisable;
+ interface->fDisableVertexAttribArray = noOpGLDisableVertexAttribArray;
+ interface->fDrawArrays = noOpGLDrawArrays;
+ interface->fDrawBuffer = noOpGLDrawBuffer;
+ interface->fDrawBuffers = noOpGLDrawBuffers;
+ interface->fDrawElements = noOpGLDrawElements;
+ interface->fEnable = noOpGLEnable;
+ interface->fEnableVertexAttribArray = noOpGLEnableVertexAttribArray;
+ interface->fEndQuery = noOpGLEndQuery;
+ interface->fFinish = noOpGLFinish;
+ interface->fFlush = noOpGLFlush;
+ interface->fFrontFace = noOpGLFrontFace;
+ interface->fGenerateMipmap = debugGLGenerateMipmap;
+ interface->fGenBuffers = debugGLGenBuffers;
+ interface->fGenQueries = noOpGLGenIds;
+ interface->fGenTextures = debugGLGenTextures;
+ interface->fGetBufferParameteriv = debugGLGetBufferParameteriv;
+ interface->fGetError = noOpGLGetError;
+ interface->fGetIntegerv = noOpGLGetIntegerv;
+ interface->fGetQueryObjecti64v = noOpGLGetQueryObjecti64v;
+ interface->fGetQueryObjectiv = noOpGLGetQueryObjectiv;
+ interface->fGetQueryObjectui64v = noOpGLGetQueryObjectui64v;
+ interface->fGetQueryObjectuiv = noOpGLGetQueryObjectuiv;
+ interface->fGetQueryiv = noOpGLGetQueryiv;
+ interface->fGetProgramInfoLog = noOpGLGetInfoLog;
+ interface->fGetProgramiv = noOpGLGetShaderOrProgramiv;
+ interface->fGetShaderInfoLog = noOpGLGetInfoLog;
+ interface->fGetShaderiv = noOpGLGetShaderOrProgramiv;
+ interface->fGetString = noOpGLGetString;
+ interface->fGetStringi = noOpGLGetStringi;
+ interface->fGetTexLevelParameteriv = noOpGLGetTexLevelParameteriv;
+ interface->fGetUniformLocation = noOpGLGetUniformLocation;
+ interface->fGenVertexArrays = debugGLGenVertexArrays;
+ interface->fLineWidth = noOpGLLineWidth;
+ interface->fLinkProgram = noOpGLLinkProgram;
+ interface->fPixelStorei = debugGLPixelStorei;
+ interface->fQueryCounter = noOpGLQueryCounter;
+ interface->fReadBuffer = noOpGLReadBuffer;
+ interface->fReadPixels = debugGLReadPixels;
+ interface->fScissor = noOpGLScissor;
+ interface->fShaderSource = noOpGLShaderSource;
+ interface->fStencilFunc = noOpGLStencilFunc;
+ interface->fStencilFuncSeparate = noOpGLStencilFuncSeparate;
+ interface->fStencilMask = noOpGLStencilMask;
+ interface->fStencilMaskSeparate = noOpGLStencilMaskSeparate;
+ interface->fStencilOp = noOpGLStencilOp;
+ interface->fStencilOpSeparate = noOpGLStencilOpSeparate;
+ interface->fTexImage2D = noOpGLTexImage2D;
+ interface->fTexParameteri = noOpGLTexParameteri;
+ interface->fTexParameteriv = noOpGLTexParameteriv;
+ interface->fTexSubImage2D = noOpGLTexSubImage2D;
+ interface->fTexStorage2D = noOpGLTexStorage2D;
+ interface->fDiscardFramebuffer = noOpGLDiscardFramebuffer;
+ interface->fUniform1f = noOpGLUniform1f;
+ interface->fUniform1i = noOpGLUniform1i;
+ interface->fUniform1fv = noOpGLUniform1fv;
+ interface->fUniform1iv = noOpGLUniform1iv;
+ interface->fUniform2f = noOpGLUniform2f;
+ interface->fUniform2i = noOpGLUniform2i;
+ interface->fUniform2fv = noOpGLUniform2fv;
+ interface->fUniform2iv = noOpGLUniform2iv;
+ interface->fUniform3f = noOpGLUniform3f;
+ interface->fUniform3i = noOpGLUniform3i;
+ interface->fUniform3fv = noOpGLUniform3fv;
+ interface->fUniform3iv = noOpGLUniform3iv;
+ interface->fUniform4f = noOpGLUniform4f;
+ interface->fUniform4i = noOpGLUniform4i;
+ interface->fUniform4fv = noOpGLUniform4fv;
+ interface->fUniform4iv = noOpGLUniform4iv;
+ interface->fUniformMatrix2fv = noOpGLUniformMatrix2fv;
+ interface->fUniformMatrix3fv = noOpGLUniformMatrix3fv;
+ interface->fUniformMatrix4fv = noOpGLUniformMatrix4fv;
+ interface->fUseProgram = debugGLUseProgram;
+ interface->fVertexAttrib4fv = noOpGLVertexAttrib4fv;
+ interface->fVertexAttribPointer = noOpGLVertexAttribPointer;
+ interface->fViewport = noOpGLViewport;
+ interface->fBindFramebuffer = debugGLBindFramebuffer;
+ interface->fBindRenderbuffer = debugGLBindRenderbuffer;
+ interface->fCheckFramebufferStatus = noOpGLCheckFramebufferStatus;
+ interface->fDeleteFramebuffers = debugGLDeleteFramebuffers;
+ interface->fDeleteRenderbuffers = debugGLDeleteRenderbuffers;
+ interface->fFramebufferRenderbuffer = debugGLFramebufferRenderbuffer;
+ interface->fFramebufferTexture2D = debugGLFramebufferTexture2D;
+ interface->fGenFramebuffers = debugGLGenFramebuffers;
+ interface->fGenRenderbuffers = debugGLGenRenderbuffers;
+ interface->fGetFramebufferAttachmentParameteriv =
+ noOpGLGetFramebufferAttachmentParameteriv;
+ interface->fGetRenderbufferParameteriv = noOpGLGetRenderbufferParameteriv;
+ interface->fRenderbufferStorage = noOpGLRenderbufferStorage;
+ interface->fRenderbufferStorageMultisample =
+ noOpGLRenderbufferStorageMultisample;
+ interface->fBlitFramebuffer = noOpGLBlitFramebuffer;
+ interface->fResolveMultisampleFramebuffer =
+ noOpGLResolveMultisampleFramebuffer;
+ interface->fMapBuffer = debugGLMapBuffer;
+ interface->fUnmapBuffer = debugGLUnmapBuffer;
+ interface->fBindFragDataLocationIndexed =
+ noOpGLBindFragDataLocationIndexed;
+
+ return interface;
+}
diff --git a/gpu/gl/debug/GrProgramObj.cpp b/gpu/gl/debug/GrProgramObj.cpp
new file mode 100644
index 00000000..d6cc36bd
--- /dev/null
+++ b/gpu/gl/debug/GrProgramObj.cpp
@@ -0,0 +1,27 @@
+
+/*
+ * 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 "GrProgramObj.h"
+#include "GrShaderObj.h"
+
+void GrProgramObj::AttachShader(GrShaderObj *shader) {
+ shader->ref();
+ fShaders.push_back(shader);
+}
+
+void GrProgramObj::deleteAction() {
+
+ // shaders are automatically detached from a deleted program. They will only be
+ // deleted if they were marked for deletion by a prior call to glDeleteShader
+ for (int i = 0; i < fShaders.count(); ++i) {
+ fShaders[i]->unref();
+ }
+ fShaders.reset();
+
+ this->INHERITED::deleteAction();
+}
diff --git a/gpu/gl/debug/GrProgramObj.h b/gpu/gl/debug/GrProgramObj.h
new file mode 100644
index 00000000..eebcc888
--- /dev/null
+++ b/gpu/gl/debug/GrProgramObj.h
@@ -0,0 +1,43 @@
+
+/*
+ * 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 GrProgramObj_DEFINED
+#define GrProgramObj_DEFINED
+
+#include "SkTArray.h"
+#include "GrFakeRefObj.h"
+class GrShaderObj;
+
+////////////////////////////////////////////////////////////////////////////////
+class GrProgramObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrProgramObj);
+
+public:
+ GrProgramObj()
+ : GrFakeRefObj()
+ , fInUse(false) {}
+
+ void AttachShader(GrShaderObj *shader);
+
+ virtual void deleteAction() SK_OVERRIDE;
+
+ // TODO: this flag system won't work w/ multiple contexts!
+ void setInUse() { fInUse = true; }
+ void resetInUse() { fInUse = false; }
+ bool getInUse() const { return fInUse; }
+
+protected:
+
+private:
+ SkTArray<GrShaderObj *> fShaders;
+ bool fInUse; // has this program been activated by a glUseProgram call?
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrProgramObj_DEFINED
diff --git a/gpu/gl/debug/GrRenderBufferObj.h b/gpu/gl/debug/GrRenderBufferObj.h
new file mode 100644
index 00000000..344b90e5
--- /dev/null
+++ b/gpu/gl/debug/GrRenderBufferObj.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.
+ */
+
+#ifndef GrRenderBufferObj_DEFINED
+#define GrRenderBufferObj_DEFINED
+
+#include "GrFBBindableObj.h"
+
+////////////////////////////////////////////////////////////////////////////////
+class GrRenderBufferObj : public GrFBBindableObj {
+ GR_DEFINE_CREATOR(GrRenderBufferObj);
+
+public:
+ GrRenderBufferObj()
+ : GrFBBindableObj()
+ , fBound(false) {
+ }
+
+ void setBound() { fBound = true; }
+ void resetBound() { fBound = false; }
+ bool getBound() const { return fBound; }
+
+ virtual void deleteAction() SK_OVERRIDE {
+
+ this->INHERITED::deleteAction();
+ }
+
+protected:
+private:
+ bool fBound; // is this render buffer currently bound via "glBindRenderbuffer"?
+
+ typedef GrFBBindableObj INHERITED;
+};
+
+#endif // GrRenderBufferObj_DEFINED
diff --git a/gpu/gl/debug/GrShaderObj.cpp b/gpu/gl/debug/GrShaderObj.cpp
new file mode 100644
index 00000000..8d3caa1e
--- /dev/null
+++ b/gpu/gl/debug/GrShaderObj.cpp
@@ -0,0 +1,14 @@
+
+/*
+ * 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 "GrShaderObj.h"
+
+void GrShaderObj::deleteAction() {
+
+ this->INHERITED::deleteAction();
+}
diff --git a/gpu/gl/debug/GrShaderObj.h b/gpu/gl/debug/GrShaderObj.h
new file mode 100644
index 00000000..0b888fa0
--- /dev/null
+++ b/gpu/gl/debug/GrShaderObj.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 GrShaderObj_DEFINED
+#define GrShaderObj_DEFINED
+
+#include "GrFakeRefObj.h"
+#include "../GrGLDefines.h"
+
+////////////////////////////////////////////////////////////////////////////////
+class GrShaderObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrShaderObj);
+
+public:
+ GrShaderObj()
+ : GrFakeRefObj()
+ , fType(GR_GL_VERTEX_SHADER) {}
+
+ void setType(GrGLenum type) { fType = type; }
+ GrGLenum getType() { return fType; }
+
+ virtual void deleteAction() SK_OVERRIDE;
+
+protected:
+private:
+ GrGLenum fType; // either GR_GL_VERTEX_SHADER or GR_GL_FRAGMENT_SHADER
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrShaderObj_DEFINED
diff --git a/gpu/gl/debug/GrTextureObj.cpp b/gpu/gl/debug/GrTextureObj.cpp
new file mode 100644
index 00000000..86063fbc
--- /dev/null
+++ b/gpu/gl/debug/GrTextureObj.cpp
@@ -0,0 +1,14 @@
+
+/*
+ * 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 "GrTextureObj.h"
+
+void GrTextureObj::deleteAction() {
+
+ this->INHERITED::deleteAction();
+}
diff --git a/gpu/gl/debug/GrTextureObj.h b/gpu/gl/debug/GrTextureObj.h
new file mode 100644
index 00000000..7673cd11
--- /dev/null
+++ b/gpu/gl/debug/GrTextureObj.h
@@ -0,0 +1,57 @@
+
+/*
+ * 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 GrTextureObj_DEFINED
+#define GrTextureObj_DEFINED
+
+#include "GrFBBindableObj.h"
+
+class GrTextureUnitObj;
+
+////////////////////////////////////////////////////////////////////////////////
+class GrTextureObj : public GrFBBindableObj {
+ GR_DEFINE_CREATOR(GrTextureObj);
+
+public:
+ GrTextureObj()
+ : GrFBBindableObj() {
+ }
+
+ virtual ~GrTextureObj() {
+ GrAlwaysAssert(0 == fTextureUnitReferees.count());
+ }
+
+ void setBound(GrTextureUnitObj *referee) {
+ fTextureUnitReferees.append(1, &referee);
+ }
+
+ void resetBound(GrTextureUnitObj *referee) {
+ int index = fTextureUnitReferees.find(referee);
+ GrAlwaysAssert(0 <= index);
+ fTextureUnitReferees.removeShuffle(index);
+ }
+ bool getBound(GrTextureUnitObj *referee) const {
+ int index = fTextureUnitReferees.find(referee);
+ return 0 <= index;
+ }
+ bool getBound() const {
+ return 0 != fTextureUnitReferees.count();
+ }
+
+ virtual void deleteAction() SK_OVERRIDE;
+
+protected:
+
+private:
+ // texture units that bind this texture (via "glBindTexture")
+ SkTDArray<GrTextureUnitObj *> fTextureUnitReferees;
+
+ typedef GrFBBindableObj INHERITED;
+};
+
+#endif // GrTextureObj_DEFINED
diff --git a/gpu/gl/debug/GrTextureUnitObj.cpp b/gpu/gl/debug/GrTextureUnitObj.cpp
new file mode 100644
index 00000000..316dcecd
--- /dev/null
+++ b/gpu/gl/debug/GrTextureUnitObj.cpp
@@ -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.
+ */
+
+#include "GrTextureUnitObj.h"
+#include "GrTextureObj.h"
+
+void GrTextureUnitObj::setTexture(GrTextureObj *texture) {
+
+ if (fTexture) {
+ GrAlwaysAssert(fTexture->getBound(this));
+ fTexture->resetBound(this);
+
+ GrAlwaysAssert(!fTexture->getDeleted());
+ fTexture->unref();
+ }
+
+ fTexture = texture;
+
+ if (fTexture) {
+ GrAlwaysAssert(!fTexture->getDeleted());
+ fTexture->ref();
+
+ GrAlwaysAssert(!fTexture->getBound(this));
+ fTexture->setBound(this);
+ }
+}
diff --git a/gpu/gl/debug/GrTextureUnitObj.h b/gpu/gl/debug/GrTextureUnitObj.h
new file mode 100644
index 00000000..7d9ed2e6
--- /dev/null
+++ b/gpu/gl/debug/GrTextureUnitObj.h
@@ -0,0 +1,44 @@
+/*
+ * 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 GrTextureUnitObj_DEFINED
+#define GrTextureUnitObj_DEFINED
+
+#include "GrFakeRefObj.h"
+class GrTextureObj;
+
+////////////////////////////////////////////////////////////////////////////////
+// Although texture unit objects are allocated & deallocated like the other
+// GL emulation objects they are derived from GrFakeRefObj to provide some
+// uniformity in how the GrDebugGL class manages resources
+class GrTextureUnitObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrTextureUnitObj);
+
+public:
+ GrTextureUnitObj()
+ : GrFakeRefObj()
+ , fNumber(0)
+ , fTexture(NULL) {
+ }
+
+ void setNumber(GrGLenum number) {
+ fNumber = number;
+ }
+ GrGLenum getNumber() const { return fNumber; }
+
+ void setTexture(GrTextureObj *texture);
+ GrTextureObj *getTexture() { return fTexture; }
+
+protected:
+private:
+ GrGLenum fNumber;
+ GrTextureObj *fTexture;
+
+ typedef GrFakeRefObj INHERITED;
+};
+
+#endif // GrTextureUnitObj_DEFINED
diff --git a/gpu/gl/debug/GrVertexArrayObj.h b/gpu/gl/debug/GrVertexArrayObj.h
new file mode 100644
index 00000000..989c6109
--- /dev/null
+++ b/gpu/gl/debug/GrVertexArrayObj.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVertexArrayObj_DEFINED
+#define GrVertexArrayObj_DEFINED
+
+#include "GrFakeRefObj.h"
+
+class GrVertexArrayObj : public GrFakeRefObj {
+ GR_DEFINE_CREATOR(GrVertexArrayObj);
+
+public:
+ GrVertexArrayObj() : GrFakeRefObj() {}
+
+ typedef GrFakeRefObj INHERITED;
+};
+#endif
diff --git a/gpu/gl/debug/SkDebugGLContext.cpp b/gpu/gl/debug/SkDebugGLContext.cpp
new file mode 100644
index 00000000..ae422272
--- /dev/null
+++ b/gpu/gl/debug/SkDebugGLContext.cpp
@@ -0,0 +1,13 @@
+
+/*
+ * 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/SkDebugGLContext.h"
+
+const GrGLInterface* SkDebugGLContext::createGLContext() {
+ return GrGLCreateDebugInterface();
+};
diff --git a/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp b/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp
new file mode 100644
index 00000000..62a39e38
--- /dev/null
+++ b/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp
@@ -0,0 +1,152 @@
+
+/*
+ * 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"
+
+#import <OpenGLES/ES2/gl.h>
+#import <OpenGLES/ES2/glext.h>
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ static SkAutoTUnref<GrGLInterface> glInterface;
+ if (!glInterface.get()) {
+ GrGLInterface* interface = SkNEW(GrGLInterface);
+ glInterface.reset(interface);
+
+ interface->fActiveTexture = glActiveTexture;
+ interface->fAttachShader = glAttachShader;
+ interface->fBindAttribLocation = glBindAttribLocation;
+ interface->fBindBuffer = glBindBuffer;
+ interface->fBindTexture = glBindTexture;
+ interface->fBlendColor = glBlendColor;
+ interface->fBlendFunc = glBlendFunc;
+ interface->fBufferData = (GrGLBufferDataProc)glBufferData;
+ interface->fBufferSubData = (GrGLBufferSubDataProc)glBufferSubData;
+ interface->fClear = glClear;
+ interface->fClearColor = glClearColor;
+ interface->fClearStencil = glClearStencil;
+ interface->fColorMask = glColorMask;
+ interface->fCompileShader = glCompileShader;
+ interface->fCompressedTexImage2D = glCompressedTexImage2D;
+ interface->fCopyTexSubImage2D = glCopyTexSubImage2D;
+ interface->fCreateProgram = glCreateProgram;
+ interface->fCreateShader = glCreateShader;
+ interface->fCullFace = glCullFace;
+ interface->fDeleteBuffers = glDeleteBuffers;
+ interface->fDeleteProgram = glDeleteProgram;
+ interface->fDeleteShader = glDeleteShader;
+ interface->fDeleteTextures = glDeleteTextures;
+ interface->fDepthMask = glDepthMask;
+ interface->fDisable = glDisable;
+ interface->fDisableVertexAttribArray = glDisableVertexAttribArray;
+ interface->fDrawArrays = glDrawArrays;
+ interface->fDrawBuffer = NULL;
+ interface->fDrawBuffers = NULL;
+ interface->fDrawElements = glDrawElements;
+ interface->fEnable = glEnable;
+ interface->fEnableVertexAttribArray = glEnableVertexAttribArray;
+ interface->fFinish = glFinish;
+ interface->fFlush = glFlush;
+ interface->fFrontFace = glFrontFace;
+ interface->fGenBuffers = glGenBuffers;
+ interface->fGenerateMipmap = glGenerateMipmap;
+ interface->fGetBufferParameteriv = glGetBufferParameteriv;
+ interface->fGetError = glGetError;
+ interface->fGetIntegerv = glGetIntegerv;
+ interface->fGetProgramInfoLog = glGetProgramInfoLog;
+ interface->fGetProgramiv = glGetProgramiv;
+ interface->fGetShaderInfoLog = glGetShaderInfoLog;
+ interface->fGetShaderiv = glGetShaderiv;
+ interface->fGetString = glGetString;
+ interface->fGenTextures = glGenTextures;
+ interface->fGetUniformLocation = glGetUniformLocation;
+ interface->fLineWidth = glLineWidth;
+ interface->fLinkProgram = glLinkProgram;
+ interface->fPixelStorei = glPixelStorei;
+ interface->fReadBuffer = NULL;
+ interface->fReadPixels = glReadPixels;
+ interface->fScissor = glScissor;
+ interface->fShaderSource = glShaderSource;
+ 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;
+#if GL_ARB_texture_storage
+ interface->fTexStorage2D = glTexStorage2D;
+#elif GL_EXT_texture_storage
+ interface->fTexStorage2D = glTexStorage2DEXT;
+#endif
+#if GL_EXT_discard_framebuffer
+ interface->fDiscardFramebuffer = glDiscardFramebufferEXT;
+#endif
+ interface->fTexParameteri = glTexParameteri;
+ interface->fTexParameteriv = glTexParameteriv;
+ 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->fUseProgram = glUseProgram;
+ interface->fVertexAttrib4fv = glVertexAttrib4fv;
+ interface->fVertexAttribPointer = glVertexAttribPointer;
+ interface->fViewport = glViewport;
+ 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;
+
+#if GL_OES_mapbuffer
+ interface->fMapBuffer = glMapBufferOES;
+ interface->fUnmapBuffer = glUnmapBufferOES;
+#endif
+
+#if GL_APPLE_framebuffer_multisample
+ interface->fRenderbufferStorageMultisample = glRenderbufferStorageMultisampleAPPLE;
+ interface->fResolveMultisampleFramebuffer = glResolveMultisampleFramebufferAPPLE;
+#endif
+
+#if GL_OES_vertex_array_object
+ interface->fBindVertexArray = glBindVertexArrayOES;
+ interface->fDeleteVertexArrays = glDeleteVertexArraysOES;
+ interface->fGenVertexArrays = glGenVertexArraysOES;
+#endif
+
+ interface->fBindingsExported = kES2_GrGLBinding;
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/gpu/gl/iOS/SkNativeGLContext_iOS.mm b/gpu/gl/iOS/SkNativeGLContext_iOS.mm
new file mode 100644
index 00000000..46bd03a2
--- /dev/null
+++ b/gpu/gl/iOS/SkNativeGLContext_iOS.mm
@@ -0,0 +1,62 @@
+
+/*
+ * 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"
+#import <OpenGLES/EAGL.h>
+
+#define EAGLCTX ((EAGLContext*)(fEAGLContext))
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+ fEAGLContext = [EAGLContext currentContext];
+ if (EAGLCTX) {
+ [EAGLCTX retain];
+ }
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+ if (EAGLCTX) {
+ [EAGLContext setCurrentContext:EAGLCTX];
+ [EAGLCTX release];
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkNativeGLContext::SkNativeGLContext()
+ : fEAGLContext(NULL) {
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+ if ([EAGLContext currentContext] == EAGLCTX) {
+ [EAGLContext setCurrentContext:nil];
+ }
+ [EAGLCTX release];
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ [EAGLContext setCurrentContext:EAGLCTX];
+
+ const GrGLInterface* interface = GrGLCreateNativeInterface();
+ if (!interface) {
+ SkDebugf("Failed to create gl interface");
+ this->destroyGLContext();
+ return NULL;
+ }
+ return interface;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+ if (![EAGLContext setCurrentContext:EAGLCTX]) {
+ SkDebugf("Could not set the context.\n");
+ }
+}
diff --git a/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp b/gpu/gl/mac/GrGLCreateNativeInterface_mac.cpp
new file mode 100644
index 00000000..eb0e87e1
--- /dev/null
+++ b/gpu/gl/mac/GrGLCreateNativeInterface_mac.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 "gl/GrGLInterface.h"
+#include "gl/GrGLExtensions.h"
+#include "../GrGLUtil.h"
+
+#include <dlfcn.h>
+
+// We get the proc addresss of all GL functions dynamically because we sometimes link against
+// alternative GL implementations (e.g. MESA) in addition to the native GL implementation.
+class GLLoader {
+public:
+ GLLoader() {
+ fLibrary = dlopen(
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
+ RTLD_LAZY);
+ }
+ ~GLLoader() {
+ if (NULL != fLibrary) {
+ dlclose(fLibrary);
+ }
+ }
+ void* handle() {
+ return NULL == fLibrary ? RTLD_DEFAULT : fLibrary;
+ }
+private:
+ void* fLibrary;
+};
+
+static void* GetProcAddress(const char* name) {
+ static GLLoader gLoader;
+ return dlsym(gLoader.handle(), 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;
+
+ GrGLGetStringProc glGetString = (GrGLGetStringProc) GetProcAddress("glGetString");
+ GrGLGetStringiProc glGetStringi = (GrGLGetStringiProc) GetProcAddress("glGetStringi");
+ GrGLGetIntegervProc glGetIntegerv = (GrGLGetIntegervProc) GetProcAddress("glGetIntegerv");
+
+ glInterface.reset(interface);
+ const char* verStr = (const char*) glGetString(GR_GL_VERSION);
+ GrGLVersion ver = GrGLGetVersionFromString(verStr);
+ GrGLExtensions extensions;
+ if (!extensions.init(kDesktop_GrGLBinding, glGetString, glGetStringi, glGetIntegerv)) {
+ glInterface.reset(NULL);
+ return NULL;
+ }
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+
+ GET_PROC(ActiveTexture);
+ GET_PROC(AttachShader);
+ GET_PROC(BeginQuery);
+ GET_PROC(BindAttribLocation);
+ GET_PROC(BindBuffer);
+ if (ver >= GR_GL_VER(3,0)) {
+ GET_PROC(BindFragDataLocation);
+ }
+ GET_PROC(BindTexture);
+ GET_PROC(BlendFunc);
+
+ if (ver >= GR_GL_VER(1,4) ||
+ extensions.has("GL_ARB_imaging") ||
+ extensions.has("GL_EXT_blend_color")) {
+ GET_PROC(BlendColor);
+ }
+
+ GET_PROC(BufferData);
+ GET_PROC(BufferSubData);
+ GET_PROC(Clear);
+ GET_PROC(ClearColor);
+ GET_PROC(ClearStencil);
+ GET_PROC(ColorMask);
+ GET_PROC(CompileShader);
+ GET_PROC(CompressedTexImage2D);
+ GET_PROC(CopyTexSubImage2D);
+ GET_PROC(CreateProgram);
+ GET_PROC(CreateShader);
+ GET_PROC(CullFace);
+ GET_PROC(DeleteBuffers);
+ GET_PROC(DeleteProgram);
+ GET_PROC(DeleteQueries);
+ GET_PROC(DeleteShader);
+ GET_PROC(DeleteTextures);
+ GET_PROC(DepthMask);
+ GET_PROC(Disable);
+ GET_PROC(DisableVertexAttribArray);
+ GET_PROC(DrawArrays);
+ GET_PROC(DrawBuffer);
+ GET_PROC(DrawBuffers);
+ GET_PROC(DrawElements);
+ GET_PROC(Enable);
+ GET_PROC(EnableVertexAttribArray);
+ GET_PROC(EndQuery);
+ GET_PROC(Finish);
+ GET_PROC(Flush);
+ GET_PROC(FrontFace);
+ GET_PROC(GenBuffers);
+ GET_PROC(GenerateMipmap);
+ GET_PROC(GenQueries);
+ GET_PROC(GetBufferParameteriv);
+ GET_PROC(GetError);
+ GET_PROC(GetIntegerv);
+ GET_PROC(GetProgramInfoLog);
+ GET_PROC(GetProgramiv);
+ GET_PROC(GetQueryiv);
+ GET_PROC(GetQueryObjectiv);
+ GET_PROC(GetQueryObjectuiv);
+ GET_PROC(GetShaderInfoLog);
+ GET_PROC(GetShaderiv);
+ GET_PROC(GetString);
+ GET_PROC(GetStringi);
+ GET_PROC(GetTexLevelParameteriv);
+ GET_PROC(GenTextures);
+ GET_PROC(GetUniformLocation);
+ GET_PROC(LineWidth);
+ GET_PROC(LinkProgram);
+ GET_PROC(MapBuffer);
+ GET_PROC(PixelStorei);
+ GET_PROC(ReadBuffer);
+ GET_PROC(ReadPixels);
+ GET_PROC(Scissor);
+ GET_PROC(ShaderSource);
+ GET_PROC(StencilFunc);
+ GET_PROC(StencilFuncSeparate);
+ GET_PROC(StencilMask);
+ GET_PROC(StencilMaskSeparate);
+ GET_PROC(StencilOp);
+ GET_PROC(StencilOpSeparate);
+ GET_PROC(TexImage2D);
+ GET_PROC(TexParameteri);
+ GET_PROC(TexParameteriv);
+ if (ver >= GR_GL_VER(4,2) || extensions.has("GL_ARB_texture_storage")) {
+ GET_PROC(TexStorage2D);
+ } else if (extensions.has("GL_EXT_texture_storage")) {
+ GET_PROC_SUFFIX(TexStorage2D, EXT);
+ }
+ GET_PROC(TexSubImage2D);
+ GET_PROC(Uniform1f);
+ GET_PROC(Uniform1i);
+ GET_PROC(Uniform1fv);
+ GET_PROC(Uniform1iv);
+ GET_PROC(Uniform2f);
+ GET_PROC(Uniform2i);
+ GET_PROC(Uniform2fv);
+ GET_PROC(Uniform2iv);
+ GET_PROC(Uniform3f);
+ GET_PROC(Uniform3i);
+ GET_PROC(Uniform3fv);
+ GET_PROC(Uniform3iv);
+ GET_PROC(Uniform4f);
+ GET_PROC(Uniform4i);
+ GET_PROC(Uniform4fv);
+ GET_PROC(Uniform4iv);
+ GET_PROC(Uniform4fv);
+ GET_PROC(UniformMatrix2fv);
+ GET_PROC(UniformMatrix3fv);
+ GET_PROC(UniformMatrix4fv);
+ GET_PROC(UnmapBuffer);
+ GET_PROC(UseProgram);
+ GET_PROC(VertexAttrib4fv);
+ GET_PROC(VertexAttribPointer);
+ GET_PROC(Viewport);
+
+ if (ver >= GR_GL_VER(3,0) || extensions.has("GL_ARB_vertex_array_object")) {
+ // no ARB suffix for GL_ARB_vertex_array_object
+ GET_PROC(BindVertexArray);
+ GET_PROC(DeleteVertexArrays);
+ GET_PROC(GenVertexArrays);
+ }
+
+ if (ver >= GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
+ // ARB extension doesn't use the ARB suffix on the function name
+ GET_PROC(QueryCounter);
+ GET_PROC(GetQueryObjecti64v);
+ GET_PROC(GetQueryObjectui64v);
+ } else if (extensions.has("GL_EXT_timer_query")) {
+ GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+ GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+ }
+
+ if (ver >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
+ // ARB extension doesn't use the ARB suffix on the function names
+ GET_PROC(GenFramebuffers);
+ GET_PROC(GetFramebufferAttachmentParameteriv);
+ GET_PROC(GetRenderbufferParameteriv);
+ GET_PROC(BindFramebuffer);
+ GET_PROC(FramebufferTexture2D);
+ GET_PROC(CheckFramebufferStatus);
+ GET_PROC(DeleteFramebuffers);
+ GET_PROC(RenderbufferStorage);
+ GET_PROC(GenRenderbuffers);
+ GET_PROC(DeleteRenderbuffers);
+ GET_PROC(FramebufferRenderbuffer);
+ GET_PROC(BindRenderbuffer);
+ GET_PROC(RenderbufferStorageMultisample);
+ GET_PROC(BlitFramebuffer);
+ } else {
+ if (extensions.has("GL_EXT_framebuffer_object")) {
+ GET_PROC_SUFFIX(GenFramebuffers, EXT);
+ GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+ GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+ GET_PROC_SUFFIX(BindFramebuffer, EXT);
+ GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+ GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+ GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+ GET_PROC_SUFFIX(RenderbufferStorage, EXT);
+ GET_PROC_SUFFIX(GenRenderbuffers, EXT);
+ GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+ GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+ GET_PROC_SUFFIX(BindRenderbuffer, EXT);
+ }
+ if (extensions.has("GL_EXT_framebuffer_multisample")) {
+ GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+ }
+ if (extensions.has("GL_EXT_framebuffer_blit")) {
+ GET_PROC_SUFFIX(BlitFramebuffer, EXT);
+ }
+ }
+ if (ver >= GR_GL_VER(3,3) || extensions.has("GL_ARB_blend_func_extended")) {
+ // ARB extension doesn't use the ARB suffix on the function name
+ GET_PROC(BindFragDataLocationIndexed);
+ }
+ }
+ glInterface.get()->ref();
+ return glInterface.get();
+}
diff --git a/gpu/gl/mac/SkNativeGLContext_mac.cpp b/gpu/gl/mac/SkNativeGLContext_mac.cpp
new file mode 100644
index 00000000..2c43c3d2
--- /dev/null
+++ b/gpu/gl/mac/SkNativeGLContext_mac.cpp
@@ -0,0 +1,76 @@
+
+/*
+ * 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/SkNativeGLContext.h"
+#include "AvailabilityMacros.h"
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldCGLContext = CGLGetCurrentContext();
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+ CGLSetCurrentContext(fOldCGLContext);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkNativeGLContext::SkNativeGLContext()
+ : fContext(NULL) {
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+ if (NULL != fContext) {
+ CGLReleaseContext(fContext);
+ }
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ SkASSERT(NULL == fContext);
+
+ CGLPixelFormatAttribute attributes[] = {
+#if MAC_OS_X_VERSION_10_7
+ kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute) kCGLOGLPVersion_3_2_Core,
+#endif
+ (CGLPixelFormatAttribute)0
+ };
+ CGLPixelFormatObj pixFormat;
+ GLint npix;
+
+ CGLChoosePixelFormat(attributes, &pixFormat, &npix);
+
+ if (NULL == pixFormat) {
+ SkDebugf("CGLChoosePixelFormat failed.");
+ return NULL;
+ }
+
+ CGLCreateContext(pixFormat, NULL, &fContext);
+ CGLReleasePixelFormat(pixFormat);
+
+ if (NULL == fContext) {
+ SkDebugf("CGLCreateContext failed.");
+ return NULL;
+ }
+
+ CGLSetCurrentContext(fContext);
+
+ const GrGLInterface* interface = GrGLCreateNativeInterface();
+ if (NULL == interface) {
+ SkDebugf("Context could not create GL interface.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ return interface;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+ CGLSetCurrentContext(fContext);
+}
diff --git a/gpu/gl/mesa/GrGLCreateMesaInterface.cpp b/gpu/gl/mesa/GrGLCreateMesaInterface.cpp
new file mode 100644
index 00000000..1ffcbcde
--- /dev/null
+++ b/gpu/gl/mesa/GrGLCreateMesaInterface.cpp
@@ -0,0 +1,218 @@
+
+/*
+ * 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/GrGLExtensions.h"
+#include "gl/GrGLInterface.h"
+#include "../GrGLUtil.h"
+
+#define GL_GLEXT_PROTOTYPES
+#include "osmesa_wrapper.h"
+
+#define GR_GL_GET_PROC(F) interface->f ## F = (GrGL ## F ## Proc) \
+ OSMesaGetProcAddress("gl" #F);
+#define GR_GL_GET_PROC_SUFFIX(F, S) interface->f ## F = (GrGL ## F ## Proc) \
+ OSMesaGetProcAddress("gl" #F #S);
+
+// We use OSMesaGetProcAddress for every gl function to avoid accidentally using
+// non-Mesa gl functions.
+
+const GrGLInterface* GrGLCreateMesaInterface() {
+ if (NULL != OSMesaGetCurrentContext()) {
+
+ GrGLGetStringProc getString = (GrGLGetStringProc) OSMesaGetProcAddress("glGetString");
+ GrGLGetStringiProc getStringi = (GrGLGetStringiProc) OSMesaGetProcAddress("glGetStringi");
+ GrGLGetIntegervProc getIntegerv =
+ (GrGLGetIntegervProc) OSMesaGetProcAddress("glGetIntegerv");
+
+ GrGLExtensions extensions;
+ if (!extensions.init(kDesktop_GrGLBinding, getString, getStringi, getIntegerv)) {
+ return NULL;
+ }
+
+ const char* versionString = (const char*) getString(GL_VERSION);
+ GrGLVersion glVer = GrGLGetVersionFromString(versionString);
+
+ if (glVer < GR_GL_VER(1,5)) {
+ // We must have array and element_array buffer objects.
+ return NULL;
+ }
+ GrGLInterface* interface = new GrGLInterface();
+
+ GR_GL_GET_PROC(ActiveTexture);
+ GR_GL_GET_PROC(BeginQuery);
+ GR_GL_GET_PROC(AttachShader);
+ GR_GL_GET_PROC(BindAttribLocation);
+ GR_GL_GET_PROC(BindBuffer);
+ GR_GL_GET_PROC(BindFragDataLocation);
+ GR_GL_GET_PROC(BindTexture);
+ GR_GL_GET_PROC(BlendFunc);
+
+ if (glVer >= GR_GL_VER(1,4) ||
+ extensions.has("GL_ARB_imaging") ||
+ extensions.has("GL_EXT_blend_color")) {
+ GR_GL_GET_PROC(BlendColor);
+ }
+
+ GR_GL_GET_PROC(BufferData);
+ GR_GL_GET_PROC(BufferSubData);
+ GR_GL_GET_PROC(Clear);
+ GR_GL_GET_PROC(ClearColor);
+ GR_GL_GET_PROC(ClearStencil);
+ GR_GL_GET_PROC(ColorMask);
+ GR_GL_GET_PROC(CompileShader);
+ GR_GL_GET_PROC(CompressedTexImage2D);
+ GR_GL_GET_PROC(CopyTexSubImage2D);
+ GR_GL_GET_PROC(CreateProgram);
+ GR_GL_GET_PROC(CreateShader);
+ GR_GL_GET_PROC(CullFace);
+ GR_GL_GET_PROC(DeleteBuffers);
+ GR_GL_GET_PROC(DeleteProgram);
+ GR_GL_GET_PROC(DeleteQueries);
+ GR_GL_GET_PROC(DeleteShader);
+ GR_GL_GET_PROC(DeleteTextures);
+ GR_GL_GET_PROC(DepthMask);
+ GR_GL_GET_PROC(Disable);
+ GR_GL_GET_PROC(DisableVertexAttribArray);
+ GR_GL_GET_PROC(DrawArrays);
+ GR_GL_GET_PROC(DrawBuffer);
+ GR_GL_GET_PROC(DrawBuffers);
+ GR_GL_GET_PROC(DrawElements);
+ GR_GL_GET_PROC(Enable);
+ GR_GL_GET_PROC(EnableVertexAttribArray);
+ GR_GL_GET_PROC(EndQuery);
+ GR_GL_GET_PROC(Finish);
+ GR_GL_GET_PROC(Flush);
+ GR_GL_GET_PROC(FrontFace);
+ GR_GL_GET_PROC(GenBuffers);
+ GR_GL_GET_PROC(GenerateMipmap);
+ GR_GL_GET_PROC(GenQueries);
+ GR_GL_GET_PROC(GetBufferParameteriv);
+ GR_GL_GET_PROC(GetError);
+ GR_GL_GET_PROC(GetIntegerv);
+ GR_GL_GET_PROC(GetProgramInfoLog);
+ GR_GL_GET_PROC(GetProgramiv);
+ if (glVer >= GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
+ GR_GL_GET_PROC(GetQueryObjecti64v);
+ GR_GL_GET_PROC(GetQueryObjectui64v)
+ GR_GL_GET_PROC(QueryCounter);
+ } else if (extensions.has("GL_EXT_timer_query")) {
+ GR_GL_GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+ }
+ GR_GL_GET_PROC(GetQueryObjectiv);
+ GR_GL_GET_PROC(GetQueryObjectuiv);
+ GR_GL_GET_PROC(GetQueryiv);
+ GR_GL_GET_PROC(GetShaderInfoLog);
+ GR_GL_GET_PROC(GetShaderiv);
+ GR_GL_GET_PROC(GetString);
+ GR_GL_GET_PROC(GetStringi);
+ GR_GL_GET_PROC(GetTexLevelParameteriv);
+ GR_GL_GET_PROC(GenTextures);
+ GR_GL_GET_PROC(GetUniformLocation);
+ GR_GL_GET_PROC(LineWidth);
+ GR_GL_GET_PROC(LinkProgram);
+ GR_GL_GET_PROC(MapBuffer);
+ GR_GL_GET_PROC(PixelStorei);
+ GR_GL_GET_PROC(ReadBuffer);
+ GR_GL_GET_PROC(ReadPixels);
+ GR_GL_GET_PROC(Scissor);
+ GR_GL_GET_PROC(ShaderSource);
+ GR_GL_GET_PROC(StencilFunc);
+ GR_GL_GET_PROC(StencilFuncSeparate);
+ GR_GL_GET_PROC(StencilMask);
+ GR_GL_GET_PROC(StencilMaskSeparate);
+ GR_GL_GET_PROC(StencilOp);
+ GR_GL_GET_PROC(StencilOpSeparate);
+ GR_GL_GET_PROC(TexImage2D)
+ GR_GL_GET_PROC(TexParameteri);
+ GR_GL_GET_PROC(TexParameteriv);
+ GR_GL_GET_PROC(TexStorage2D);
+ if (NULL == interface->fTexStorage2D) {
+ GR_GL_GET_PROC_SUFFIX(TexStorage2D, EXT);
+ }
+ GR_GL_GET_PROC(TexSubImage2D);
+ GR_GL_GET_PROC(Uniform1f);
+ GR_GL_GET_PROC(Uniform1i);
+ GR_GL_GET_PROC(Uniform1fv);
+ GR_GL_GET_PROC(Uniform1iv);
+ GR_GL_GET_PROC(Uniform2f);
+ GR_GL_GET_PROC(Uniform2i);
+ GR_GL_GET_PROC(Uniform2fv);
+ GR_GL_GET_PROC(Uniform2iv);
+ GR_GL_GET_PROC(Uniform3f);
+ GR_GL_GET_PROC(Uniform3i);
+ GR_GL_GET_PROC(Uniform3fv);
+ GR_GL_GET_PROC(Uniform3iv);
+ GR_GL_GET_PROC(Uniform4f);
+ GR_GL_GET_PROC(Uniform4i);
+ GR_GL_GET_PROC(Uniform4fv);
+ GR_GL_GET_PROC(Uniform4iv);
+ GR_GL_GET_PROC(UniformMatrix2fv);
+ GR_GL_GET_PROC(UniformMatrix3fv);
+ GR_GL_GET_PROC(UniformMatrix4fv);
+ GR_GL_GET_PROC(UnmapBuffer);
+ GR_GL_GET_PROC(UseProgram);
+ GR_GL_GET_PROC(VertexAttrib4fv);
+ GR_GL_GET_PROC(VertexAttribPointer);
+ GR_GL_GET_PROC(Viewport);
+
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_vertex_array_object")) {
+ // no ARB suffix for GL_ARB_vertex_array_object
+ GR_GL_GET_PROC(BindVertexArray);
+ GR_GL_GET_PROC(DeleteVertexArrays);
+ GR_GL_GET_PROC(GenVertexArrays);
+ }
+
+ // First look for GL3.0 FBO or GL_ARB_framebuffer_object (same since
+ // GL_ARB_framebuffer_object doesn't use ARB suffix.)
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
+ GR_GL_GET_PROC(GenFramebuffers);
+ GR_GL_GET_PROC(GetFramebufferAttachmentParameteriv);
+ GR_GL_GET_PROC(GetRenderbufferParameteriv);
+ GR_GL_GET_PROC(BindFramebuffer);
+ GR_GL_GET_PROC(FramebufferTexture2D);
+ GR_GL_GET_PROC(CheckFramebufferStatus);
+ GR_GL_GET_PROC(DeleteFramebuffers);
+ GR_GL_GET_PROC(RenderbufferStorage);
+ GR_GL_GET_PROC(GenRenderbuffers);
+ GR_GL_GET_PROC(DeleteRenderbuffers);
+ GR_GL_GET_PROC(FramebufferRenderbuffer);
+ GR_GL_GET_PROC(BindRenderbuffer);
+ GR_GL_GET_PROC(RenderbufferStorageMultisample);
+ GR_GL_GET_PROC(BlitFramebuffer);
+ } else if (extensions.has("GL_EXT_framebuffer_object")) {
+ GR_GL_GET_PROC_SUFFIX(GenFramebuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+ GR_GL_GET_PROC_SUFFIX(BindFramebuffer, EXT);
+ GR_GL_GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+ GR_GL_GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+ GR_GL_GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(RenderbufferStorage, EXT);
+ GR_GL_GET_PROC_SUFFIX(GenRenderbuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+ GR_GL_GET_PROC_SUFFIX(BindRenderbuffer, EXT);
+ if (extensions.has("GL_EXT_framebuffer_multisample")) {
+ GR_GL_GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+ }
+ if (extensions.has("GL_EXT_framebuffer_blit")) {
+ GR_GL_GET_PROC_SUFFIX(BlitFramebuffer, EXT);
+ }
+ } else {
+ // we must have FBOs
+ delete interface;
+ return NULL;
+ }
+ GR_GL_GET_PROC(BindFragDataLocationIndexed);
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+ return interface;
+ } else {
+ return NULL;
+ }
+}
diff --git a/gpu/gl/mesa/SkMesaGLContext.cpp b/gpu/gl/mesa/SkMesaGLContext.cpp
new file mode 100644
index 00000000..2be0c130
--- /dev/null
+++ b/gpu/gl/mesa/SkMesaGLContext.cpp
@@ -0,0 +1,106 @@
+
+/*
+ * 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/osmesa.h>
+
+#include "gl/SkMesaGLContext.h"
+#include "gl/GrGLDefines.h"
+
+SkMesaGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldContext = (Context)OSMesaGetCurrentContext();
+ if (NULL != (OSMesaContext)fOldContext) {
+ OSMesaGetColorBuffer((OSMesaContext)fOldContext,
+ &fOldWidth, &fOldHeight,
+ &fOldFormat, &fOldImage);
+ }
+}
+
+SkMesaGLContext::AutoContextRestore::~AutoContextRestore() {
+ if (NULL != (OSMesaContext)fOldContext) {
+ OSMesaMakeCurrent((OSMesaContext)fOldContext, fOldImage,
+ fOldFormat, fOldWidth, fOldHeight);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkMesaGLContext::SkMesaGLContext()
+ : fContext(static_cast<Context>(NULL))
+ , fImage(NULL) {
+ GR_STATIC_ASSERT(sizeof(Context) == sizeof(OSMesaContext));
+}
+
+SkMesaGLContext::~SkMesaGLContext() {
+ this->destroyGLContext();
+}
+
+void SkMesaGLContext::destroyGLContext() {
+ if (fImage) {
+ sk_free(fImage);
+ fImage = NULL;
+ }
+
+ if (fContext) {
+ OSMesaDestroyContext((OSMesaContext)fContext);
+ fContext = static_cast<Context>(NULL);
+ }
+}
+
+static const GrGLint gBOGUS_SIZE = 16;
+
+const GrGLInterface* SkMesaGLContext::createGLContext() {
+ /* Create an RGBA-mode context */
+#if OSMESA_MAJOR_VERSION * 100 + OSMESA_MINOR_VERSION >= 305
+ /* specify Z, stencil, accum sizes */
+ fContext = (Context)OSMesaCreateContextExt(OSMESA_BGRA, 0, 0, 0, NULL);
+#else
+ fContext = (Context)OSMesaCreateContext(OSMESA_BGRA, NULL);
+#endif
+ if (!fContext) {
+ SkDebugf("OSMesaCreateContext failed!\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+ // Allocate the image buffer
+ fImage = (GrGLubyte *) sk_malloc_throw(gBOGUS_SIZE * gBOGUS_SIZE *
+ 4 * sizeof(GrGLubyte));
+ if (!fImage) {
+ SkDebugf("Alloc image buffer failed!\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ // Bind the buffer to the context and make it current
+ if (!OSMesaMakeCurrent((OSMesaContext)fContext,
+ fImage,
+ GR_GL_UNSIGNED_BYTE,
+ gBOGUS_SIZE,
+ gBOGUS_SIZE)) {
+ SkDebugf("OSMesaMakeCurrent failed!\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ const GrGLInterface* interface = GrGLCreateMesaInterface();
+ if (!interface) {
+ SkDebugf("Could not create GL interface!\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+ return interface;
+
+}
+
+void SkMesaGLContext::makeCurrent() const {
+ if (fContext) {
+ if (!OSMesaMakeCurrent((OSMesaContext)fContext, fImage,
+ GR_GL_UNSIGNED_BYTE, gBOGUS_SIZE, gBOGUS_SIZE)) {
+ SkDebugf("Could not make MESA context current.");
+ }
+ }
+}
diff --git a/gpu/gl/mesa/osmesa_wrapper.h b/gpu/gl/mesa/osmesa_wrapper.h
new file mode 100644
index 00000000..70de9937
--- /dev/null
+++ b/gpu/gl/mesa/osmesa_wrapper.h
@@ -0,0 +1,16 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Older versions of XQuartz have a bug where a header included by osmesa.h
+// defines GL_GLEXT_PROTOTYPES. This will cause a redefinition warning if
+// the file that includes osmesa.h already defined it. XCode 3 uses a version
+// of gcc (4.2.1) that does not support the diagnostic pragma to disable a
+// warning (added in 4.2.4). So we use the system_header pragma to shut GCC
+// up about warnings in osmesa.h
+#pragma GCC system_header
+#include <GL/osmesa.h>
diff --git a/gpu/gl/nacl/SkNativeGLContext_nacl.cpp b/gpu/gl/nacl/SkNativeGLContext_nacl.cpp
new file mode 100644
index 00000000..59ed2bf8
--- /dev/null
+++ b/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/gpu/gl/unix/GrGLCreateNativeInterface_unix.cpp b/gpu/gl/unix/GrGLCreateNativeInterface_unix.cpp
new file mode 100644
index 00000000..5820b8e7
--- /dev/null
+++ b/gpu/gl/unix/GrGLCreateNativeInterface_unix.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/GrGLExtensions.h"
+#include "gl/GrGLInterface.h"
+#include "../GrGLUtil.h"
+
+#include <GL/glx.h>
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <GL/glu.h>
+
+#define GR_GL_GET_PROC(F) interface->f ## F = (GrGL ## F ## Proc) \
+ glXGetProcAddress(reinterpret_cast<const GLubyte*>("gl" #F));
+#define GR_GL_GET_PROC_SUFFIX(F, S) interface->f ## F = (GrGL ## F ## Proc) \
+ glXGetProcAddress(reinterpret_cast<const GLubyte*>("gl" #F #S));
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ if (NULL != glXGetCurrentContext()) {
+
+ const char* versionString = (const char*) glGetString(GL_VERSION);
+ GrGLVersion glVer = GrGLGetVersionFromString(versionString);
+
+ // This may or may not succeed depending on the gl version.
+ GrGLGetStringiProc glGetStringi =
+ (GrGLGetStringiProc) glXGetProcAddress(reinterpret_cast<const GLubyte*>("glGetStringi"));
+
+ GrGLExtensions extensions;
+ if (!extensions.init(kDesktop_GrGLBinding, glGetString, glGetStringi, glGetIntegerv)) {
+ return NULL;
+ }
+
+ if (glVer < GR_GL_VER(1,5)) {
+ // We must have array and element_array buffer objects.
+ return NULL;
+ }
+
+ GrGLInterface* interface = new GrGLInterface();
+
+ interface->fActiveTexture = glActiveTexture;
+ GR_GL_GET_PROC(AttachShader);
+ GR_GL_GET_PROC(BindAttribLocation);
+ GR_GL_GET_PROC(BindBuffer);
+ GR_GL_GET_PROC(BindFragDataLocation);
+ GR_GL_GET_PROC(BeginQuery);
+ interface->fBindTexture = glBindTexture;
+ interface->fBlendFunc = glBlendFunc;
+
+ if (glVer >= GR_GL_VER(1,4) ||
+ extensions.has("GL_ARB_imaging") ||
+ extensions.has("GL_EXT_blend_color")) {
+ GR_GL_GET_PROC(BlendColor);
+ }
+
+ GR_GL_GET_PROC(BufferData);
+ GR_GL_GET_PROC(BufferSubData);
+ interface->fClear = glClear;
+ interface->fClearColor = glClearColor;
+ interface->fClearStencil = glClearStencil;
+ interface->fColorMask = glColorMask;
+ GR_GL_GET_PROC(CompileShader);
+ interface->fCompressedTexImage2D = glCompressedTexImage2D;
+ interface->fCopyTexSubImage2D = glCopyTexSubImage2D;
+ GR_GL_GET_PROC(CreateProgram);
+ GR_GL_GET_PROC(CreateShader);
+ interface->fCullFace = glCullFace;
+ GR_GL_GET_PROC(DeleteBuffers);
+ GR_GL_GET_PROC(DeleteProgram);
+ GR_GL_GET_PROC(DeleteQueries);
+ GR_GL_GET_PROC(DeleteShader);
+ interface->fDeleteTextures = glDeleteTextures;
+ interface->fDepthMask = glDepthMask;
+ interface->fDisable = glDisable;
+ GR_GL_GET_PROC(DisableVertexAttribArray);
+ interface->fDrawArrays = glDrawArrays;
+ interface->fDrawBuffer = glDrawBuffer;
+ GR_GL_GET_PROC(DrawBuffers);
+ interface->fDrawElements = glDrawElements;
+ interface->fEnable = glEnable;
+ GR_GL_GET_PROC(EnableVertexAttribArray);
+ GR_GL_GET_PROC(EndQuery);
+ interface->fFinish = glFinish;
+ interface->fFlush = glFlush;
+ interface->fFrontFace = glFrontFace;
+ GR_GL_GET_PROC(GenBuffers);
+ GR_GL_GET_PROC(GenerateMipmap);
+ GR_GL_GET_PROC(GetBufferParameteriv);
+ interface->fGetError = glGetError;
+ interface->fGetIntegerv = glGetIntegerv;
+ GR_GL_GET_PROC(GetQueryObjectiv);
+ GR_GL_GET_PROC(GetQueryObjectuiv);
+ if (glVer >= GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
+ GR_GL_GET_PROC(GetQueryObjecti64v);
+ GR_GL_GET_PROC(GetQueryObjectui64v);
+ GR_GL_GET_PROC(QueryCounter);
+ } else if (extensions.has("GL_EXT_timer_query")) {
+ GR_GL_GET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+ }
+ GR_GL_GET_PROC(GetQueryiv);
+ GR_GL_GET_PROC(GetProgramInfoLog);
+ GR_GL_GET_PROC(GetProgramiv);
+ GR_GL_GET_PROC(GetShaderInfoLog);
+ GR_GL_GET_PROC(GetShaderiv);
+ interface->fGetString = glGetString;
+ GR_GL_GET_PROC(GetStringi);
+ interface->fGetTexLevelParameteriv = glGetTexLevelParameteriv;
+ GR_GL_GET_PROC(GenQueries);
+ interface->fGenTextures = glGenTextures;
+ GR_GL_GET_PROC(GetUniformLocation);
+ interface->fLineWidth = glLineWidth;
+ GR_GL_GET_PROC(LinkProgram);
+ GR_GL_GET_PROC(MapBuffer);
+ interface->fPixelStorei = glPixelStorei;
+ interface->fReadBuffer = glReadBuffer;
+ interface->fReadPixels = glReadPixels;
+ if (extensions.has("GL_NV_framebuffer_multisample_coverage")) {
+ GR_GL_GET_PROC_SUFFIX(RenderbufferStorageMultisampleCoverage, NV);
+ }
+ interface->fScissor = glScissor;
+ GR_GL_GET_PROC(ShaderSource);
+ interface->fStencilFunc = glStencilFunc;
+ GR_GL_GET_PROC(StencilFuncSeparate);
+ interface->fStencilMask = glStencilMask;
+ GR_GL_GET_PROC(StencilMaskSeparate);
+ interface->fStencilOp = glStencilOp;
+ GR_GL_GET_PROC(StencilOpSeparate);
+ interface->fTexImage2D = glTexImage2D;
+ interface->fTexParameteri = glTexParameteri;
+ interface->fTexParameteriv = glTexParameteriv;
+ if (glVer >= GR_GL_VER(4,2) || extensions.has("GL_ARB_texture_storage")) {
+ GR_GL_GET_PROC(TexStorage2D);
+ } else if (extensions.has("GL_EXT_texture_storage")) {
+ GR_GL_GET_PROC_SUFFIX(TexStorage2D, EXT);
+ }
+ interface->fTexSubImage2D = glTexSubImage2D;
+ GR_GL_GET_PROC(Uniform1f);
+ GR_GL_GET_PROC(Uniform1i);
+ GR_GL_GET_PROC(Uniform1fv);
+ GR_GL_GET_PROC(Uniform1iv);
+ GR_GL_GET_PROC(Uniform2f);
+ GR_GL_GET_PROC(Uniform2i);
+ GR_GL_GET_PROC(Uniform2fv);
+ GR_GL_GET_PROC(Uniform2iv);
+ GR_GL_GET_PROC(Uniform3f);
+ GR_GL_GET_PROC(Uniform3i);
+ GR_GL_GET_PROC(Uniform3fv);
+ GR_GL_GET_PROC(Uniform3iv);
+ GR_GL_GET_PROC(Uniform4f);
+ GR_GL_GET_PROC(Uniform4i);
+ GR_GL_GET_PROC(Uniform4fv);
+ GR_GL_GET_PROC(Uniform4iv);
+ GR_GL_GET_PROC(UniformMatrix2fv);
+ GR_GL_GET_PROC(UniformMatrix3fv);
+ GR_GL_GET_PROC(UniformMatrix4fv);
+ GR_GL_GET_PROC(UnmapBuffer);
+ GR_GL_GET_PROC(UseProgram);
+ GR_GL_GET_PROC(VertexAttrib4fv);
+ GR_GL_GET_PROC(VertexAttribPointer);
+ interface->fViewport = glViewport;
+ GR_GL_GET_PROC(BindFragDataLocationIndexed);
+
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_vertex_array_object")) {
+ // no ARB suffix for GL_ARB_vertex_array_object
+ GR_GL_GET_PROC(BindVertexArray);
+ GR_GL_GET_PROC(GenVertexArrays);
+ GR_GL_GET_PROC(DeleteVertexArrays);
+ }
+
+ // First look for GL3.0 FBO or GL_ARB_framebuffer_object (same since
+ // GL_ARB_framebuffer_object doesn't use ARB suffix.)
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
+ GR_GL_GET_PROC(GenFramebuffers);
+ GR_GL_GET_PROC(GetFramebufferAttachmentParameteriv);
+ GR_GL_GET_PROC(GetRenderbufferParameteriv);
+ GR_GL_GET_PROC(BindFramebuffer);
+ GR_GL_GET_PROC(FramebufferTexture2D);
+ GR_GL_GET_PROC(CheckFramebufferStatus);
+ GR_GL_GET_PROC(DeleteFramebuffers);
+ GR_GL_GET_PROC(RenderbufferStorage);
+ GR_GL_GET_PROC(GenRenderbuffers);
+ GR_GL_GET_PROC(DeleteRenderbuffers);
+ GR_GL_GET_PROC(FramebufferRenderbuffer);
+ GR_GL_GET_PROC(BindRenderbuffer);
+ GR_GL_GET_PROC(RenderbufferStorageMultisample);
+ GR_GL_GET_PROC(BlitFramebuffer);
+ } else if (extensions.has("GL_EXT_framebuffer_object")) {
+ GR_GL_GET_PROC_SUFFIX(GenFramebuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+ GR_GL_GET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+ GR_GL_GET_PROC_SUFFIX(BindFramebuffer, EXT);
+ GR_GL_GET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+ GR_GL_GET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+ GR_GL_GET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(RenderbufferStorage, EXT);
+ GR_GL_GET_PROC_SUFFIX(GenRenderbuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+ GR_GL_GET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+ GR_GL_GET_PROC_SUFFIX(BindRenderbuffer, EXT);
+ if (extensions.has("GL_EXT_framebuffer_multisample")) {
+ GR_GL_GET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+ }
+ if (extensions.has("GL_EXT_framebuffer_blit")) {
+ GR_GL_GET_PROC_SUFFIX(BlitFramebuffer, EXT);
+ }
+ } else {
+ // we must have FBOs
+ delete interface;
+ return NULL;
+ }
+
+ GR_GL_GET_PROC(LoadIdentity);
+ GR_GL_GET_PROC(LoadMatrixf);
+ GR_GL_GET_PROC(MatrixMode);
+
+ if (extensions.has("GL_NV_path_rendering")) {
+ GR_GL_GET_PROC_SUFFIX(PathCommands, NV);
+ GR_GL_GET_PROC_SUFFIX(PathCoords, NV);
+ GR_GL_GET_PROC_SUFFIX(PathSubCommands, NV);
+ GR_GL_GET_PROC_SUFFIX(PathSubCoords, NV);
+ GR_GL_GET_PROC_SUFFIX(PathString, NV);
+ GR_GL_GET_PROC_SUFFIX(PathGlyphs, NV);
+ GR_GL_GET_PROC_SUFFIX(PathGlyphRange, NV);
+ GR_GL_GET_PROC_SUFFIX(WeightPaths, NV);
+ GR_GL_GET_PROC_SUFFIX(CopyPath, NV);
+ GR_GL_GET_PROC_SUFFIX(InterpolatePaths, NV);
+ GR_GL_GET_PROC_SUFFIX(TransformPath, NV);
+ GR_GL_GET_PROC_SUFFIX(PathParameteriv, NV);
+ GR_GL_GET_PROC_SUFFIX(PathParameteri, NV);
+ GR_GL_GET_PROC_SUFFIX(PathParameterfv, NV);
+ GR_GL_GET_PROC_SUFFIX(PathParameterf, NV);
+ GR_GL_GET_PROC_SUFFIX(PathDashArray, NV);
+ GR_GL_GET_PROC_SUFFIX(GenPaths, NV);
+ GR_GL_GET_PROC_SUFFIX(DeletePaths, NV);
+ GR_GL_GET_PROC_SUFFIX(IsPath, NV);
+ GR_GL_GET_PROC_SUFFIX(PathStencilFunc, NV);
+ GR_GL_GET_PROC_SUFFIX(PathStencilDepthOffset, NV);
+ GR_GL_GET_PROC_SUFFIX(StencilFillPath, NV);
+ GR_GL_GET_PROC_SUFFIX(StencilStrokePath, NV);
+ GR_GL_GET_PROC_SUFFIX(StencilFillPathInstanced, NV);
+ GR_GL_GET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
+ GR_GL_GET_PROC_SUFFIX(PathCoverDepthFunc, NV);
+ GR_GL_GET_PROC_SUFFIX(PathColorGen, NV);
+ GR_GL_GET_PROC_SUFFIX(PathTexGen, NV);
+ GR_GL_GET_PROC_SUFFIX(PathFogGen, NV);
+ GR_GL_GET_PROC_SUFFIX(CoverFillPath, NV);
+ GR_GL_GET_PROC_SUFFIX(CoverStrokePath, NV);
+ GR_GL_GET_PROC_SUFFIX(CoverFillPathInstanced, NV);
+ GR_GL_GET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathParameteriv, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathParameterfv, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathCommands, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathCoords, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathDashArray, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathMetrics, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathMetricRange, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathSpacing, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathColorGeniv, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathColorGenfv, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathTexGeniv, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathTexGenfv, NV);
+ GR_GL_GET_PROC_SUFFIX(IsPointInFillPath, NV);
+ GR_GL_GET_PROC_SUFFIX(IsPointInStrokePath, NV);
+ GR_GL_GET_PROC_SUFFIX(GetPathLength, NV);
+ GR_GL_GET_PROC_SUFFIX(PointAlongPath, NV);
+ }
+
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+
+ return interface;
+ } else {
+ return NULL;
+ }
+}
diff --git a/gpu/gl/unix/SkNativeGLContext_unix.cpp b/gpu/gl/unix/SkNativeGLContext_unix.cpp
new file mode 100644
index 00000000..e6fae8ed
--- /dev/null
+++ b/gpu/gl/unix/SkNativeGLContext_unix.cpp
@@ -0,0 +1,287 @@
+
+/*
+ * 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/SkNativeGLContext.h"
+
+#include <GL/glu.h>
+
+#define GLX_1_3 1
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldGLXContext = glXGetCurrentContext();
+ fOldDisplay = glXGetCurrentDisplay();
+ fOldDrawable = glXGetCurrentDrawable();
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+ if (NULL != fOldDisplay) {
+ glXMakeCurrent(fOldDisplay, fOldDrawable, fOldGLXContext);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool ctxErrorOccurred = false;
+static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) {
+ ctxErrorOccurred = true;
+ return 0;
+}
+
+SkNativeGLContext::SkNativeGLContext()
+ : fContext(NULL)
+ , fDisplay(NULL)
+ , fPixmap(0)
+ , fGlxPixmap(0) {
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+ if (fDisplay) {
+ glXMakeCurrent(fDisplay, 0, 0);
+
+ if (fContext) {
+ glXDestroyContext(fDisplay, fContext);
+ fContext = NULL;
+ }
+
+ if (fGlxPixmap) {
+ glXDestroyGLXPixmap(fDisplay, fGlxPixmap);
+ fGlxPixmap = 0;
+ }
+
+ if (fPixmap) {
+ XFreePixmap(fDisplay, fPixmap);
+ fPixmap = 0;
+ }
+
+ XCloseDisplay(fDisplay);
+ fDisplay = NULL;
+ }
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ fDisplay = XOpenDisplay(0);
+
+ if (!fDisplay) {
+ SkDebugf("Failed to open X display.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ // Get a matching FB config
+ static int visual_attribs[] = {
+ GLX_X_RENDERABLE , True,
+ GLX_DRAWABLE_TYPE , GLX_PIXMAP_BIT,
+ None
+ };
+
+#ifdef GLX_1_3
+ //SkDebugf("Getting matching framebuffer configs.\n");
+ int fbcount;
+ GLXFBConfig *fbc = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay),
+ visual_attribs, &fbcount);
+ if (!fbc) {
+ SkDebugf("Failed to retrieve a framebuffer config.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+ //SkDebugf("Found %d matching FB configs.\n", fbcount);
+
+ // Pick the FB config/visual with the most samples per pixel
+ //SkDebugf("Getting XVisualInfos.\n");
+ int best_fbc = -1, best_num_samp = -1;
+
+ int i;
+ for (i = 0; i < fbcount; ++i) {
+ XVisualInfo *vi = glXGetVisualFromFBConfig(fDisplay, fbc[i]);
+ if (vi) {
+ int samp_buf, samples;
+ glXGetFBConfigAttrib(fDisplay, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf);
+ glXGetFBConfigAttrib(fDisplay, fbc[i], GLX_SAMPLES, &samples);
+
+ //SkDebugf(" Matching fbconfig %d, visual ID 0x%2x: SAMPLE_BUFFERS = %d,"
+ // " SAMPLES = %d\n",
+ // i, (unsigned int)vi->visualid, samp_buf, samples);
+
+ if (best_fbc < 0 || (samp_buf && samples > best_num_samp))
+ best_fbc = i, best_num_samp = samples;
+ }
+ XFree(vi);
+ }
+
+ GLXFBConfig bestFbc = fbc[best_fbc];
+
+ // Be sure to free the FBConfig list allocated by glXChooseFBConfig()
+ XFree(fbc);
+
+ // Get a visual
+ XVisualInfo *vi = glXGetVisualFromFBConfig(fDisplay, bestFbc);
+ //SkDebugf("Chosen visual ID = 0x%x\n", (unsigned int)vi->visualid);
+#else
+ int numVisuals;
+ XVisualInfo visTemplate, *visReturn;
+
+ visReturn = XGetVisualInfo(fDisplay, VisualNoMask, &visTemplate, &numVisuals);
+ if (NULL == visReturn)
+ {
+ SkDebugf("Failed to get visual information.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ int best = -1, best_num_samp = -1;
+
+ for (int i = 0; i < numVisuals; ++i)
+ {
+ int samp_buf, samples;
+
+ glXGetConfig(fDisplay, &visReturn[i], GLX_SAMPLE_BUFFERS, &samp_buf);
+ glXGetConfig(fDisplay, &visReturn[i], GLX_SAMPLES, &samples);
+
+ if (best < 0 || (samp_buf && samples > best_num_samp))
+ best = i, best_num_samp = samples;
+ }
+
+ XVisualInfo temp = visReturn[best];
+ XVisualInfo *vi = &temp;
+
+ XFree(visReturn);
+#endif
+
+ fPixmap = XCreatePixmap(fDisplay, RootWindow(fDisplay, vi->screen), 10, 10, vi->depth);
+
+ if (!fPixmap) {
+ SkDebugf("Failed to create pixmap.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ fGlxPixmap = glXCreateGLXPixmap(fDisplay, vi, fPixmap);
+
+#ifdef GLX_1_3
+ // Done with the visual info data
+ XFree(vi);
+#endif
+
+ // Create the context
+
+ // Install an X error handler so the application won't exit if GL 3.0
+ // context allocation fails.
+ //
+ // Note this error handler is global.
+ // All display connections in all threads of a process use the same
+ // error handler, so be sure to guard against other threads issuing
+ // X commands while this code is running.
+ ctxErrorOccurred = false;
+ int (*oldHandler)(Display*, XErrorEvent*) =
+ XSetErrorHandler(&ctxErrorHandler);
+
+ // Get the default screen's GLX extension list
+ const char *glxExts = glXQueryExtensionsString(
+ fDisplay, DefaultScreen(fDisplay)
+ );
+ // Check for the GLX_ARB_create_context extension string and the function.
+ // If either is not present, use GLX 1.3 context creation method.
+ if (!gluCheckExtension(
+ reinterpret_cast<const GLubyte*>("GLX_ARB_create_context")
+ , reinterpret_cast<const GLubyte*>(glxExts)))
+ {
+ //SkDebugf("GLX_ARB_create_context not found."
+ // " Using old-style GLX context.\n");
+#ifdef GLX_1_3
+ fContext = glXCreateNewContext(fDisplay, bestFbc, GLX_RGBA_TYPE, 0, True);
+#else
+ fContext = glXCreateContext(fDisplay, vi, 0, True);
+#endif
+
+ }
+#ifdef GLX_1_3
+ else {
+ //SkDebugf("Creating context.\n");
+
+ PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB =
+ (PFNGLXCREATECONTEXTATTRIBSARBPROC) glXGetProcAddressARB((GrGLubyte*)"glXCreateContextAttribsARB");
+ int context_attribs[] = {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
+ GLX_CONTEXT_MINOR_VERSION_ARB, 0,
+ //GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
+ None
+ };
+ fContext = glXCreateContextAttribsARB(
+ fDisplay, bestFbc, 0, True, context_attribs
+ );
+
+ // Sync to ensure any errors generated are processed.
+ XSync(fDisplay, False);
+ if (!ctxErrorOccurred && fContext) {
+ //SkDebugf( "Created GL 3.0 context.\n" );
+ } else {
+ // Couldn't create GL 3.0 context.
+ // Fall back to old-style 2.x context.
+ // When a context version below 3.0 is requested,
+ // implementations will return the newest context version compatible
+ // with OpenGL versions less than version 3.0.
+
+ // GLX_CONTEXT_MAJOR_VERSION_ARB = 1
+ context_attribs[1] = 1;
+ // GLX_CONTEXT_MINOR_VERSION_ARB = 0
+ context_attribs[3] = 0;
+
+ ctxErrorOccurred = false;
+
+ //SkDebugf("Failed to create GL 3.0 context."
+ // " Using old-style GLX context.\n");
+ fContext = glXCreateContextAttribsARB(
+ fDisplay, bestFbc, 0, True, context_attribs
+ );
+ }
+ }
+#endif
+
+ // Sync to ensure any errors generated are processed.
+ XSync(fDisplay, False);
+
+ // Restore the original error handler
+ XSetErrorHandler(oldHandler);
+
+ if (ctxErrorOccurred || !fContext) {
+ SkDebugf("Failed to create an OpenGL context.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ // Verify that context is a direct context
+ if (!glXIsDirect(fDisplay, fContext)) {
+ //SkDebugf("Indirect GLX rendering context obtained.\n");
+ } else {
+ //SkDebugf("Direct GLX rendering context obtained.\n");
+ }
+
+ //SkDebugf("Making context current.\n");
+ if (!glXMakeCurrent(fDisplay, fGlxPixmap, fContext)) {
+ SkDebugf("Could not set the context.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ const GrGLInterface* interface = GrGLCreateNativeInterface();
+ if (!interface) {
+ SkDebugf("Failed to create gl interface");
+ this->destroyGLContext();
+ return NULL;
+ }
+ return interface;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+ if (!glXMakeCurrent(fDisplay, fGlxPixmap, fContext)) {
+ SkDebugf("Could not set the context.\n");
+ }
+}
diff --git a/gpu/gl/win/GrGLCreateNativeInterface_win.cpp b/gpu/gl/win/GrGLCreateNativeInterface_win.cpp
new file mode 100644
index 00000000..53b1eddd
--- /dev/null
+++ b/gpu/gl/win/GrGLCreateNativeInterface_win.cpp
@@ -0,0 +1,316 @@
+
+/*
+ * 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/GrGLExtensions.h"
+#include "gl/GrGLInterface.h"
+#include "gl/GrGLUtil.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+/*
+ * Windows makes the GL funcs all be __stdcall instead of __cdecl :(
+ * This implementation will only work if GR_GL_FUNCTION_TYPE is __stdcall.
+ * Otherwise, a springboard would be needed that hides the calling convention.
+ */
+
+#define SET_PROC(F) interface->f ## F = (GrGL ## F ## Proc) GetProcAddress(alu.get(), "gl" #F);
+#define WGL_SET_PROC(F) interface->f ## F = (GrGL ## F ## Proc) wglGetProcAddress("gl" #F);
+#define WGL_SET_PROC_SUFFIX(F, S) interface->f ## F = \
+ (GrGL ## F ## Proc) wglGetProcAddress("gl" #F #S);
+
+class AutoLibraryUnload {
+public:
+ AutoLibraryUnload(const char* moduleName) {
+ fModule = LoadLibrary(moduleName);
+ }
+ ~AutoLibraryUnload() {
+ if (NULL != fModule) {
+ FreeLibrary(fModule);
+ }
+ }
+ HMODULE get() const { return fModule; }
+
+private:
+ HMODULE fModule;
+};
+
+const GrGLInterface* GrGLCreateNativeInterface() {
+ // wglGetProcAddress requires a context.
+ // GL Function pointers retrieved in one context may not be valid in another
+ // context. For that reason we create a new GrGLInterface each time we're
+ // called.
+ AutoLibraryUnload alu("opengl32.dll");
+ if (NULL == alu.get()) {
+ return NULL;
+ }
+
+ if (NULL != wglGetCurrentContext()) {
+
+ // These should always be present and don't require wglGetProcAddress
+ GrGLGetStringProc glGetString =
+ (GrGLGetStringProc) GetProcAddress(alu.get(), "glGetString");
+ GrGLGetIntegervProc glGetIntegerv =
+ (GrGLGetIntegervProc) GetProcAddress(alu.get(), "glGetIntegerv");
+ if (NULL == glGetString || NULL == glGetIntegerv) {
+ return NULL;
+ }
+
+ // This may or may not succeed depending on the gl version.
+ GrGLGetStringiProc glGetStringi = (GrGLGetStringiProc) wglGetProcAddress("glGetStringi");
+
+ GrGLExtensions extensions;
+ if (!extensions.init(kDesktop_GrGLBinding, glGetString, glGetStringi, glGetIntegerv)) {
+ return NULL;
+ }
+ const char* versionString = (const char*) glGetString(GR_GL_VERSION);
+ GrGLVersion glVer = GrGLGetVersionFromString(versionString);
+
+ if (glVer < GR_GL_VER(1,5)) {
+ // We must have array and element_array buffer objects.
+ return NULL;
+ }
+ GrGLInterface* interface = new GrGLInterface();
+
+ // Functions that are part of GL 1.1 will return NULL in
+ // wglGetProcAddress
+ SET_PROC(BindTexture)
+ SET_PROC(BlendFunc)
+
+ if (glVer >= GR_GL_VER(1,4) ||
+ extensions.has("GL_ARB_imaging") ||
+ extensions.has("GL_EXT_blend_color")) {
+ WGL_SET_PROC(BlendColor);
+ }
+
+ SET_PROC(Clear)
+ SET_PROC(ClearColor)
+ SET_PROC(ClearStencil)
+ SET_PROC(ColorMask)
+ SET_PROC(CopyTexSubImage2D)
+ SET_PROC(CullFace)
+ SET_PROC(DeleteTextures)
+ SET_PROC(DepthMask)
+ SET_PROC(Disable)
+ SET_PROC(DrawArrays)
+ SET_PROC(DrawElements)
+ SET_PROC(DrawBuffer)
+ SET_PROC(Enable)
+ SET_PROC(FrontFace)
+ SET_PROC(Finish)
+ SET_PROC(Flush)
+ SET_PROC(GenTextures)
+ SET_PROC(GetError)
+ SET_PROC(GetIntegerv)
+ SET_PROC(GetString)
+ SET_PROC(GetTexLevelParameteriv)
+ SET_PROC(LineWidth)
+ SET_PROC(LoadIdentity)
+ SET_PROC(LoadMatrixf)
+ SET_PROC(MatrixMode)
+ SET_PROC(PixelStorei)
+ SET_PROC(ReadBuffer)
+ SET_PROC(ReadPixels)
+ SET_PROC(Scissor)
+ SET_PROC(StencilFunc)
+ SET_PROC(StencilMask)
+ SET_PROC(StencilOp)
+ SET_PROC(TexImage2D)
+ SET_PROC(TexParameteri)
+ SET_PROC(TexParameteriv)
+ if (glVer >= GR_GL_VER(4,2) || extensions.has("GL_ARB_texture_storage")) {
+ WGL_SET_PROC(TexStorage2D);
+ } else if (extensions.has("GL_EXT_texture_storage")) {
+ WGL_SET_PROC_SUFFIX(TexStorage2D, EXT);
+ }
+ SET_PROC(TexSubImage2D)
+ SET_PROC(Viewport)
+
+ WGL_SET_PROC(ActiveTexture);
+ WGL_SET_PROC(AttachShader);
+ WGL_SET_PROC(BeginQuery);
+ WGL_SET_PROC(BindAttribLocation);
+ WGL_SET_PROC(BindBuffer);
+ WGL_SET_PROC(BindFragDataLocation);
+ WGL_SET_PROC(BufferData);
+ WGL_SET_PROC(BufferSubData);
+ WGL_SET_PROC(CompileShader);
+ WGL_SET_PROC(CompressedTexImage2D);
+ WGL_SET_PROC(CreateProgram);
+ WGL_SET_PROC(CreateShader);
+ WGL_SET_PROC(DeleteBuffers);
+ WGL_SET_PROC(DeleteQueries);
+ WGL_SET_PROC(DeleteProgram);
+ WGL_SET_PROC(DeleteShader);
+ WGL_SET_PROC(DisableVertexAttribArray);
+ WGL_SET_PROC(DrawBuffers);
+ WGL_SET_PROC(EnableVertexAttribArray);
+ WGL_SET_PROC(EndQuery);
+ WGL_SET_PROC(GenBuffers);
+ WGL_SET_PROC(GenerateMipmap);
+ WGL_SET_PROC(GenQueries);
+ WGL_SET_PROC(GetBufferParameteriv);
+ WGL_SET_PROC(GetQueryiv);
+ WGL_SET_PROC(GetQueryObjectiv);
+ WGL_SET_PROC(GetQueryObjectuiv);
+ if (glVer > GR_GL_VER(3,3) || extensions.has("GL_ARB_timer_query")) {
+ WGL_SET_PROC(GetQueryObjecti64v);
+ WGL_SET_PROC(GetQueryObjectui64v);
+ WGL_SET_PROC(QueryCounter);
+ } else if (extensions.has("GL_EXT_timer_query")) {
+ WGL_SET_PROC_SUFFIX(GetQueryObjecti64v, EXT);
+ WGL_SET_PROC_SUFFIX(GetQueryObjectui64v, EXT);
+ }
+ WGL_SET_PROC(GetProgramInfoLog);
+ WGL_SET_PROC(GetProgramiv);
+ WGL_SET_PROC(GetShaderInfoLog);
+ WGL_SET_PROC(GetShaderiv);
+ WGL_SET_PROC(GetStringi)
+ WGL_SET_PROC(GetUniformLocation);
+ WGL_SET_PROC(LinkProgram);
+ if (extensions.has("GL_NV_framebuffer_multisample_coverage")) {
+ WGL_SET_PROC_SUFFIX(RenderbufferStorageMultisampleCoverage, NV);
+ }
+ WGL_SET_PROC(ShaderSource);
+ WGL_SET_PROC(StencilFuncSeparate);
+ WGL_SET_PROC(StencilMaskSeparate);
+ WGL_SET_PROC(StencilOpSeparate);
+ WGL_SET_PROC(Uniform1f);
+ WGL_SET_PROC(Uniform1i);
+ WGL_SET_PROC(Uniform1fv);
+ WGL_SET_PROC(Uniform1iv);
+ WGL_SET_PROC(Uniform2f);
+ WGL_SET_PROC(Uniform2i);
+ WGL_SET_PROC(Uniform2fv);
+ WGL_SET_PROC(Uniform2iv);
+ WGL_SET_PROC(Uniform3f);
+ WGL_SET_PROC(Uniform3i);
+ WGL_SET_PROC(Uniform3fv);
+ WGL_SET_PROC(Uniform3iv);
+ WGL_SET_PROC(Uniform4f);
+ WGL_SET_PROC(Uniform4i);
+ WGL_SET_PROC(Uniform4fv);
+ WGL_SET_PROC(Uniform4iv);
+ WGL_SET_PROC(UniformMatrix2fv);
+ WGL_SET_PROC(UniformMatrix3fv);
+ WGL_SET_PROC(UniformMatrix4fv);
+ WGL_SET_PROC(UseProgram);
+ WGL_SET_PROC(VertexAttrib4fv);
+ WGL_SET_PROC(VertexAttribPointer);
+ WGL_SET_PROC(BindFragDataLocationIndexed);
+
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_vertex_array_object")) {
+ // no ARB suffix for GL_ARB_vertex_array_object
+ WGL_SET_PROC(BindVertexArray);
+ WGL_SET_PROC(DeleteVertexArrays);
+ WGL_SET_PROC(GenVertexArrays);
+ }
+
+ // First look for GL3.0 FBO or GL_ARB_framebuffer_object (same since
+ // GL_ARB_framebuffer_object doesn't use ARB suffix.)
+ if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) {
+ WGL_SET_PROC(GenFramebuffers);
+ WGL_SET_PROC(GetFramebufferAttachmentParameteriv);
+ WGL_SET_PROC(GetRenderbufferParameteriv);
+ WGL_SET_PROC(BindFramebuffer);
+ WGL_SET_PROC(FramebufferTexture2D);
+ WGL_SET_PROC(CheckFramebufferStatus);
+ WGL_SET_PROC(DeleteFramebuffers);
+ WGL_SET_PROC(RenderbufferStorage);
+ WGL_SET_PROC(GenRenderbuffers);
+ WGL_SET_PROC(DeleteRenderbuffers);
+ WGL_SET_PROC(FramebufferRenderbuffer);
+ WGL_SET_PROC(BindRenderbuffer);
+ WGL_SET_PROC(RenderbufferStorageMultisample);
+ WGL_SET_PROC(BlitFramebuffer);
+ } else if (extensions.has("GL_EXT_framebuffer_object")) {
+ WGL_SET_PROC_SUFFIX(GenFramebuffers, EXT);
+ WGL_SET_PROC_SUFFIX(GetFramebufferAttachmentParameteriv, EXT);
+ WGL_SET_PROC_SUFFIX(GetRenderbufferParameteriv, EXT);
+ WGL_SET_PROC_SUFFIX(BindFramebuffer, EXT);
+ WGL_SET_PROC_SUFFIX(FramebufferTexture2D, EXT);
+ WGL_SET_PROC_SUFFIX(CheckFramebufferStatus, EXT);
+ WGL_SET_PROC_SUFFIX(DeleteFramebuffers, EXT);
+ WGL_SET_PROC_SUFFIX(RenderbufferStorage, EXT);
+ WGL_SET_PROC_SUFFIX(GenRenderbuffers, EXT);
+ WGL_SET_PROC_SUFFIX(DeleteRenderbuffers, EXT);
+ WGL_SET_PROC_SUFFIX(FramebufferRenderbuffer, EXT);
+ WGL_SET_PROC_SUFFIX(BindRenderbuffer, EXT);
+ if (extensions.has("GL_EXT_framebuffer_multisample")) {
+ WGL_SET_PROC_SUFFIX(RenderbufferStorageMultisample, EXT);
+ }
+ if (extensions.has("GL_EXT_framebuffer_blit")) {
+ WGL_SET_PROC_SUFFIX(BlitFramebuffer, EXT);
+ }
+ } else {
+ // we must have FBOs
+ delete interface;
+ return NULL;
+ }
+ WGL_SET_PROC(MapBuffer);
+ WGL_SET_PROC(UnmapBuffer);
+
+ if (extensions.has("GL_NV_path_rendering")) {
+ WGL_SET_PROC_SUFFIX(PathCommands, NV);
+ WGL_SET_PROC_SUFFIX(PathCoords, NV);
+ WGL_SET_PROC_SUFFIX(PathSubCommands, NV);
+ WGL_SET_PROC_SUFFIX(PathSubCoords, NV);
+ WGL_SET_PROC_SUFFIX(PathString, NV);
+ WGL_SET_PROC_SUFFIX(PathGlyphs, NV);
+ WGL_SET_PROC_SUFFIX(PathGlyphRange, NV);
+ WGL_SET_PROC_SUFFIX(WeightPaths, NV);
+ WGL_SET_PROC_SUFFIX(CopyPath, NV);
+ WGL_SET_PROC_SUFFIX(InterpolatePaths, NV);
+ WGL_SET_PROC_SUFFIX(TransformPath, NV);
+ WGL_SET_PROC_SUFFIX(PathParameteriv, NV);
+ WGL_SET_PROC_SUFFIX(PathParameteri, NV);
+ WGL_SET_PROC_SUFFIX(PathParameterfv, NV);
+ WGL_SET_PROC_SUFFIX(PathParameterf, NV);
+ WGL_SET_PROC_SUFFIX(PathDashArray, NV);
+ WGL_SET_PROC_SUFFIX(GenPaths, NV);
+ WGL_SET_PROC_SUFFIX(DeletePaths, NV);
+ WGL_SET_PROC_SUFFIX(IsPath, NV);
+ WGL_SET_PROC_SUFFIX(PathStencilFunc, NV);
+ WGL_SET_PROC_SUFFIX(PathStencilDepthOffset, NV);
+ WGL_SET_PROC_SUFFIX(StencilFillPath, NV);
+ WGL_SET_PROC_SUFFIX(StencilStrokePath, NV);
+ WGL_SET_PROC_SUFFIX(StencilFillPathInstanced, NV);
+ WGL_SET_PROC_SUFFIX(StencilStrokePathInstanced, NV);
+ WGL_SET_PROC_SUFFIX(PathCoverDepthFunc, NV);
+ WGL_SET_PROC_SUFFIX(PathColorGen, NV);
+ WGL_SET_PROC_SUFFIX(PathTexGen, NV);
+ WGL_SET_PROC_SUFFIX(PathFogGen, NV);
+ WGL_SET_PROC_SUFFIX(CoverFillPath, NV);
+ WGL_SET_PROC_SUFFIX(CoverStrokePath, NV);
+ WGL_SET_PROC_SUFFIX(CoverFillPathInstanced, NV);
+ WGL_SET_PROC_SUFFIX(CoverStrokePathInstanced, NV);
+ WGL_SET_PROC_SUFFIX(GetPathParameteriv, NV);
+ WGL_SET_PROC_SUFFIX(GetPathParameterfv, NV);
+ WGL_SET_PROC_SUFFIX(GetPathCommands, NV);
+ WGL_SET_PROC_SUFFIX(GetPathCoords, NV);
+ WGL_SET_PROC_SUFFIX(GetPathDashArray, NV);
+ WGL_SET_PROC_SUFFIX(GetPathMetrics, NV);
+ WGL_SET_PROC_SUFFIX(GetPathMetricRange, NV);
+ WGL_SET_PROC_SUFFIX(GetPathSpacing, NV);
+ WGL_SET_PROC_SUFFIX(GetPathColorGeniv, NV);
+ WGL_SET_PROC_SUFFIX(GetPathColorGenfv, NV);
+ WGL_SET_PROC_SUFFIX(GetPathTexGeniv, NV);
+ WGL_SET_PROC_SUFFIX(GetPathTexGenfv, NV);
+ WGL_SET_PROC_SUFFIX(IsPointInFillPath, NV);
+ WGL_SET_PROC_SUFFIX(IsPointInStrokePath, NV);
+ WGL_SET_PROC_SUFFIX(GetPathLength, NV);
+ WGL_SET_PROC_SUFFIX(PointAlongPath, NV);
+ }
+
+ interface->fBindingsExported = kDesktop_GrGLBinding;
+
+ return interface;
+ } else {
+ return NULL;
+ }
+}
diff --git a/gpu/gl/win/SkNativeGLContext_win.cpp b/gpu/gl/win/SkNativeGLContext_win.cpp
new file mode 100644
index 00000000..4c736994
--- /dev/null
+++ b/gpu/gl/win/SkNativeGLContext_win.cpp
@@ -0,0 +1,114 @@
+
+/*
+ * 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/SkNativeGLContext.h"
+#include "SkWGL.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+SkNativeGLContext::AutoContextRestore::AutoContextRestore() {
+ fOldHGLRC = wglGetCurrentContext();
+ fOldHDC = wglGetCurrentDC();
+}
+
+SkNativeGLContext::AutoContextRestore::~AutoContextRestore() {
+ wglMakeCurrent(fOldHDC, fOldHGLRC);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ATOM SkNativeGLContext::gWC = 0;
+
+SkNativeGLContext::SkNativeGLContext()
+ : fWindow(NULL)
+ , fDeviceContext(NULL)
+ , fGlRenderContext(0) {
+}
+
+SkNativeGLContext::~SkNativeGLContext() {
+ this->destroyGLContext();
+}
+
+void SkNativeGLContext::destroyGLContext() {
+ if (fGlRenderContext) {
+ wglDeleteContext(fGlRenderContext);
+ }
+ if (fWindow && fDeviceContext) {
+ ReleaseDC(fWindow, fDeviceContext);
+ }
+ if (fWindow) {
+ DestroyWindow(fWindow);
+ }
+}
+
+const GrGLInterface* SkNativeGLContext::createGLContext() {
+ HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
+
+ if (!gWC) {
+ WNDCLASS wc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hbrBackground = NULL;
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wc.hInstance = hInstance;
+ wc.lpfnWndProc = (WNDPROC) DefWindowProc;
+ wc.lpszClassName = TEXT("Griffin");
+ wc.lpszMenuName = NULL;
+ wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+
+ gWC = RegisterClass(&wc);
+ if (!gWC) {
+ SkDebugf("Could not register window class.\n");
+ return NULL;
+ }
+ }
+
+ if (!(fWindow = CreateWindow(TEXT("Griffin"),
+ TEXT("The Invisible Man"),
+ WS_OVERLAPPEDWINDOW,
+ 0, 0, 1, 1,
+ NULL, NULL,
+ hInstance, NULL))) {
+ SkDebugf("Could not create window.\n");
+ return NULL;
+ }
+
+ if (!(fDeviceContext = GetDC(fWindow))) {
+ SkDebugf("Could not get device context.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ if (!(fGlRenderContext = SkCreateWGLContext(fDeviceContext, 0, true))) {
+ SkDebugf("Could not create rendering context.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ if (!(wglMakeCurrent(fDeviceContext, fGlRenderContext))) {
+ SkDebugf("Could not set the context.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+ const GrGLInterface* interface = GrGLCreateNativeInterface();
+ if (NULL == interface) {
+ SkDebugf("Could not create GL interface.\n");
+ this->destroyGLContext();
+ return NULL;
+ }
+
+ return interface;
+}
+
+void SkNativeGLContext::makeCurrent() const {
+ if (!wglMakeCurrent(fDeviceContext, fGlRenderContext)) {
+ SkDebugf("Could not create rendering context.\n");
+ }
+}
diff --git a/gpu/gr_unittests.cpp b/gpu/gr_unittests.cpp
new file mode 100644
index 00000000..618d412b
--- /dev/null
+++ b/gpu/gr_unittests.cpp
@@ -0,0 +1,80 @@
+
+/*
+ * 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 "GrBinHashKey.h"
+#include "GrDrawTarget.h"
+#include "SkMatrix.h"
+#include "GrRedBlackTree.h"
+
+// FIXME: needs to be in a header
+void gr_run_unittests();
+
+// If we aren't inheriting these as #defines from elsewhere,
+// clang demands they be declared before we #include the template
+// that relies on them.
+#if GR_DEBUG
+static bool LT(const int& elem, int value) {
+ return elem < value;
+}
+static bool EQ(const int& elem, int value) {
+ return elem == value;
+}
+#include "GrTBSearch.h"
+
+static void test_bsearch() {
+ const int array[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99
+ };
+
+ for (size_t n = 0; n < GR_ARRAY_COUNT(array); n++) {
+ for (size_t i = 0; i < n; i++) {
+ int index = GrTBSearch<int, int>(array, n, array[i]);
+ GrAssert(index == (int) i);
+ index = GrTBSearch<int, int>(array, n, -array[i]);
+ GrAssert(index < 0);
+ }
+ }
+}
+#endif
+
+// bogus empty class for GrBinHashKey
+class BogusEntry {};
+
+static void test_binHashKey()
+{
+ const char* testStringA_ = "abcdABCD";
+ const char* testStringB_ = "abcdBBCD";
+ const uint32_t* testStringA = reinterpret_cast<const uint32_t*>(testStringA_);
+ const uint32_t* testStringB = reinterpret_cast<const uint32_t*>(testStringB_);
+ enum {
+ kDataLenUsedForKey = 8
+ };
+
+ GrTBinHashKey<BogusEntry, kDataLenUsedForKey> keyA;
+ keyA.setKeyData(testStringA);
+ // test copy constructor and comparison
+ GrTBinHashKey<BogusEntry, kDataLenUsedForKey> keyA2(keyA);
+ GrAssert(keyA.compare(keyA2) == 0);
+ GrAssert(keyA.getHash() == keyA2.getHash());
+ // test re-init
+ keyA2.setKeyData(testStringA);
+ GrAssert(keyA.compare(keyA2) == 0);
+ GrAssert(keyA.getHash() == keyA2.getHash());
+ // test sorting
+ GrTBinHashKey<BogusEntry, kDataLenUsedForKey> keyB;
+ keyB.setKeyData(testStringB);
+ GrAssert(keyA.compare(keyB) < 0);
+ GrAssert(keyA.getHash() != keyB.getHash());
+}
+
+
+void gr_run_unittests() {
+ GR_DEBUGCODE(test_bsearch();)
+ test_binHashKey();
+ GrRedBlackTree<int>::UnitTest();
+}
diff --git a/image/SkDataPixelRef.cpp b/image/SkDataPixelRef.cpp
new file mode 100644
index 00000000..980b4f14
--- /dev/null
+++ b/image/SkDataPixelRef.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 "SkDataPixelRef.h"
+#include "SkData.h"
+#include "SkFlattenableBuffers.h"
+
+SkDataPixelRef::SkDataPixelRef(SkData* data) : fData(data) {
+ fData->ref();
+ this->setPreLocked(const_cast<void*>(fData->data()), NULL);
+}
+
+SkDataPixelRef::~SkDataPixelRef() {
+ fData->unref();
+}
+
+void* SkDataPixelRef::onLockPixels(SkColorTable** ct) {
+ *ct = NULL;
+ return const_cast<void*>(fData->data());
+}
+
+void SkDataPixelRef::onUnlockPixels() {
+ // nothing to do
+}
+
+void SkDataPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fData);
+}
+
+SkDataPixelRef::SkDataPixelRef(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer, NULL) {
+ fData = (SkData*)buffer.readFlattenable();
+ this->setPreLocked(const_cast<void*>(fData->data()), NULL);
+}
diff --git a/image/SkDataPixelRef.h b/image/SkDataPixelRef.h
new file mode 100644
index 00000000..6b15802b
--- /dev/null
+++ b/image/SkDataPixelRef.h
@@ -0,0 +1,35 @@
+/*
+ * 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 SkDataPixelRef_DEFINED
+#define SkDataPixelRef_DEFINED
+
+#include "SkPixelRef.h"
+
+class SkData;
+
+class SkDataPixelRef : public SkPixelRef {
+public:
+ SkDataPixelRef(SkData* data);
+ virtual ~SkDataPixelRef();
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDataPixelRef)
+
+protected:
+ virtual void* onLockPixels(SkColorTable**) SK_OVERRIDE;
+ virtual void onUnlockPixels() SK_OVERRIDE;
+
+ SkDataPixelRef(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+private:
+ SkData* fData;
+
+ typedef SkPixelRef INHERITED;
+};
+
+#endif
diff --git a/image/SkImage.cpp b/image/SkImage.cpp
new file mode 100644
index 00000000..39fd93ac
--- /dev/null
+++ b/image/SkImage.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "SkCanvas.h"
+#include "SkImagePriv.h"
+#include "SkImage_Base.h"
+
+SK_DEFINE_INST_COUNT(SkImage)
+
+static SkImage_Base* as_IB(SkImage* image) {
+ return static_cast<SkImage_Base*>(image);
+}
+
+static const SkImage_Base* as_IB(const SkImage* image) {
+ return static_cast<const SkImage_Base*>(image);
+}
+
+uint32_t SkImage::NextUniqueID() {
+ static int32_t gUniqueID;
+
+ // never return 0;
+ uint32_t id;
+ do {
+ id = sk_atomic_inc(&gUniqueID) + 1;
+ } while (0 == id);
+ return id;
+}
+
+void SkImage::draw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ as_IB(this)->onDraw(canvas, x, y, paint);
+}
+
+void SkImage::draw(SkCanvas* canvas, const SkRect* src, const SkRect& dst,
+ const SkPaint* paint) {
+ as_IB(this)->onDrawRectToRect(canvas, src, dst, paint);
+}
+
+GrTexture* SkImage::getTexture() {
+ return as_IB(this)->onGetTexture();
+}
+
+SkData* SkImage::encode(SkImageEncoder::Type type, int quality) const {
+ SkBitmap bm;
+ if (as_IB(this)->getROPixels(&bm)) {
+ return SkImageEncoder::EncodeData(bm, type, quality);
+ }
+ return NULL;
+}
diff --git a/image/SkImagePriv.cpp b/image/SkImagePriv.cpp
new file mode 100644
index 00000000..ca4a12cd
--- /dev/null
+++ b/image/SkImagePriv.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 "SkImagePriv.h"
+#include "SkCanvas.h"
+#include "SkPicture.h"
+
+SkBitmap::Config SkImageInfoToBitmapConfig(const SkImage::Info& info,
+ bool* isOpaque) {
+ switch (info.fColorType) {
+ case SkImage::kAlpha_8_ColorType:
+ switch (info.fAlphaType) {
+ case SkImage::kIgnore_AlphaType:
+ // makes no sense
+ return SkBitmap::kNo_Config;
+
+ case SkImage::kOpaque_AlphaType:
+ *isOpaque = true;
+ return SkBitmap::kA8_Config;
+
+ case SkImage::kPremul_AlphaType:
+ case SkImage::kUnpremul_AlphaType:
+ *isOpaque = false;
+ return SkBitmap::kA8_Config;
+ }
+ break;
+
+ case SkImage::kRGB_565_ColorType:
+ // we ignore fAlpahType, though some would not make sense
+ *isOpaque = true;
+ return SkBitmap::kRGB_565_Config;
+
+ case SkImage::kRGBA_8888_ColorType:
+ case SkImage::kBGRA_8888_ColorType:
+ // not supported yet
+ return SkBitmap::kNo_Config;
+
+ case SkImage::kPMColor_ColorType:
+ switch (info.fAlphaType) {
+ case SkImage::kIgnore_AlphaType:
+ case SkImage::kUnpremul_AlphaType:
+ // not supported yet
+ return SkBitmap::kNo_Config;
+ case SkImage::kOpaque_AlphaType:
+ *isOpaque = true;
+ return SkBitmap::kARGB_8888_Config;
+ case SkImage::kPremul_AlphaType:
+ *isOpaque = false;
+ return SkBitmap::kARGB_8888_Config;
+ }
+ break;
+ }
+ SkASSERT(!"how did we get here");
+ return SkBitmap::kNo_Config;
+}
+
+int SkImageBytesPerPixel(SkImage::ColorType ct) {
+ static const uint8_t gColorTypeBytesPerPixel[] = {
+ 1, // kAlpha_8_ColorType
+ 2, // kRGB_565_ColorType
+ 4, // kRGBA_8888_ColorType
+ 4, // kBGRA_8888_ColorType
+ 4, // kPMColor_ColorType
+ };
+
+ SkASSERT((size_t)ct < SK_ARRAY_COUNT(gColorTypeBytesPerPixel));
+ return gColorTypeBytesPerPixel[ct];
+}
+
+bool SkBitmapToImageInfo(const SkBitmap& bm, SkImage::Info* info) {
+ switch (bm.config()) {
+ case SkBitmap::kA8_Config:
+ info->fColorType = SkImage::kAlpha_8_ColorType;
+ break;
+
+ case SkBitmap::kRGB_565_Config:
+ info->fColorType = SkImage::kRGB_565_ColorType;
+ break;
+
+ case SkBitmap::kARGB_8888_Config:
+ info->fColorType = SkImage::kPMColor_ColorType;
+ break;
+
+ default:
+ return false;
+ }
+
+ info->fWidth = bm.width();
+ info->fHeight = bm.height();
+ info->fAlphaType = bm.isOpaque() ? SkImage::kOpaque_AlphaType :
+ SkImage::kPremul_AlphaType;
+ return true;
+}
+
+SkImage* SkNewImageFromBitmap(const SkBitmap& bm, bool canSharePixelRef) {
+ SkImage::Info info;
+ if (!SkBitmapToImageInfo(bm, &info)) {
+ return NULL;
+ }
+
+ SkImage* image = NULL;
+ if (canSharePixelRef || bm.isImmutable()) {
+ image = SkNewImageFromPixelRef(info, bm.pixelRef(), bm.rowBytes());
+ } else {
+ bm.lockPixels();
+ if (bm.getPixels()) {
+ image = SkImage::NewRasterCopy(info, bm.getPixels(), bm.rowBytes());
+ }
+ bm.unlockPixels();
+ }
+ return image;
+}
+
+static bool needs_layer(const SkPaint& paint) {
+ return 0xFF != paint.getAlpha() ||
+ paint.getColorFilter() ||
+ paint.getImageFilter() ||
+ SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode);
+}
+
+void SkImagePrivDrawPicture(SkCanvas* canvas, SkPicture* picture,
+ SkScalar x, SkScalar y, const SkPaint* paint) {
+ int saveCount = canvas->getSaveCount();
+
+ if (paint && needs_layer(*paint)) {
+ SkRect bounds;
+ bounds.set(x, y,
+ x + SkIntToScalar(picture->width()),
+ y + SkIntToScalar(picture->height()));
+ canvas->saveLayer(&bounds, paint);
+ canvas->translate(x, y);
+ } else if (x || y) {
+ canvas->save();
+ canvas->translate(x, y);
+ }
+
+ canvas->drawPicture(*picture);
+ canvas->restoreToCount(saveCount);
+}
+
+void SkImagePrivDrawPicture(SkCanvas* canvas, SkPicture* picture,
+ const SkRect* src, const SkRect& dst, const SkPaint* paint) {
+ int saveCount = canvas->getSaveCount();
+
+ SkMatrix matrix;
+ SkRect tmpSrc;
+
+ if (NULL != src) {
+ tmpSrc = *src;
+ } else {
+ tmpSrc.set(0, 0,
+ SkIntToScalar(picture->width()),
+ SkIntToScalar(picture->height()));
+ }
+
+ matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
+ if (paint && needs_layer(*paint)) {
+ canvas->saveLayer(&dst, paint);
+ } else {
+ canvas->save();
+ }
+ canvas->concat(matrix);
+ if (!paint || !needs_layer(*paint)) {
+ canvas->clipRect(tmpSrc);
+ }
+
+ canvas->drawPicture(*picture);
+ canvas->restoreToCount(saveCount);
+}
diff --git a/image/SkImagePriv.h b/image/SkImagePriv.h
new file mode 100644
index 00000000..2af8bbea
--- /dev/null
+++ b/image/SkImagePriv.h
@@ -0,0 +1,74 @@
+/*
+ * 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 SkImagePriv_DEFINED
+#define SkImagePriv_DEFINED
+
+#include "SkBitmap.h"
+#include "SkImage.h"
+
+class SkPicture;
+
+extern SkBitmap::Config SkImageInfoToBitmapConfig(const SkImage::Info&,
+ bool* isOpaque);
+
+extern int SkImageBytesPerPixel(SkImage::ColorType);
+
+extern bool SkBitmapToImageInfo(const SkBitmap&, SkImage::Info*);
+
+// Call this if you explicitly want to use/share this pixelRef in the image
+extern SkImage* SkNewImageFromPixelRef(const SkImage::Info&, SkPixelRef*,
+ size_t rowBytes);
+
+/**
+ * Examines the bitmap to decide if it can share the existing pixelRef, or
+ * if it needs to make a deep-copy of the pixels. The bitmap's pixelref will
+ * be shared if either the bitmap is marked as immutable, or canSharePixelRef
+ * is true.
+ *
+ * If the bitmap's config cannot be converted into a corresponding
+ * SkImage::Info, or the bitmap's pixels cannot be accessed, this will return
+ * NULL.
+ */
+extern SkImage* SkNewImageFromBitmap(const SkBitmap&, bool canSharePixelRef);
+
+extern void SkImagePrivDrawPicture(SkCanvas*, SkPicture*,
+ SkScalar x, SkScalar y, const SkPaint*);
+
+extern void SkImagePrivDrawPicture(SkCanvas*, SkPicture*,
+ const SkRect*, const SkRect&, const SkPaint*);
+
+/**
+ * Return an SkImage whose contents are those of the specified picture. Note:
+ * The picture itself is unmodified, and may continue to be used for recording
+ */
+extern SkImage* SkNewImageFromPicture(const SkPicture*);
+
+static inline size_t SkImageMinRowBytes(const SkImage::Info& info) {
+ size_t rb = info.fWidth * SkImageBytesPerPixel(info.fColorType);
+ return SkAlign4(rb);
+}
+
+// Given an image created from SkNewImageFromBitmap, return its pixelref. This
+// may be called to see if the surface and the image share the same pixelref,
+// in which case the surface may need to perform a copy-on-write.
+extern SkPixelRef* SkBitmapImageGetPixelRef(SkImage* rasterImage);
+
+// Given an image created with NewPicture, return its SkPicture.
+extern SkPicture* SkPictureImageGetPicture(SkImage* pictureImage);
+
+// 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* textureImage);
+
+// 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/image/SkImage_Base.h b/image/SkImage_Base.h
new file mode 100644
index 00000000..7bd1f7e6
--- /dev/null
+++ b/image/SkImage_Base.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 SkImage_Base_DEFINED
+#define SkImage_Base_DEFINED
+
+#include "SkImage.h"
+
+class SkImage_Base : public SkImage {
+public:
+ SkImage_Base(int width, int height) : INHERITED(width, height) {}
+
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) = 0;
+ virtual void onDrawRectToRect(SkCanvas*, const SkRect* src,
+ const SkRect& dst, const SkPaint*) = 0;
+ virtual GrTexture* onGetTexture() { return NULL; }
+
+ // return a read-only copy of the pixels. We promise to not modify them,
+ // but only inspect them (or encode them).
+ virtual bool getROPixels(SkBitmap*) const { return false; }
+
+private:
+ typedef SkImage INHERITED;
+};
+
+#endif
diff --git a/image/SkImage_Codec.cpp b/image/SkImage_Codec.cpp
new file mode 100644
index 00000000..64f58a6a
--- /dev/null
+++ b/image/SkImage_Codec.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkImage_Base.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+
+class SkImage_Codec : public SkImage_Base {
+public:
+ static SkImage* NewEmpty();
+
+ SkImage_Codec(SkData* encodedData, int width, int height);
+ virtual ~SkImage_Codec();
+
+ virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
+ virtual void onDrawRectToRect(SkCanvas*, const SkRect*, const SkRect&, const SkPaint*) SK_OVERRIDE;
+
+private:
+ SkData* fEncodedData;
+ SkBitmap fBitmap;
+
+ typedef SkImage_Base INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage_Codec::SkImage_Codec(SkData* data, int width, int height) : INHERITED(width, height) {
+ fEncodedData = data;
+ fEncodedData->ref();
+}
+
+SkImage_Codec::~SkImage_Codec() {
+ fEncodedData->unref();
+}
+
+void SkImage_Codec::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) {
+ if (!fBitmap.pixelRef()) {
+ if (!SkImageDecoder::DecodeMemory(fEncodedData->bytes(), fEncodedData->size(),
+ &fBitmap)) {
+ return;
+ }
+ }
+ canvas->drawBitmap(fBitmap, x, y, paint);
+}
+
+void SkImage_Codec::onDrawRectToRect(SkCanvas* canvas, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ if (!fBitmap.pixelRef()) {
+ if (!SkImageDecoder::DecodeMemory(fEncodedData->bytes(), fEncodedData->size(),
+ &fBitmap)) {
+ return;
+ }
+ }
+ canvas->drawBitmapRectToRect(fBitmap, src, dst, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage* SkImage::NewEncodedData(SkData* data) {
+ if (NULL == data) {
+ return NULL;
+ }
+
+ SkBitmap bitmap;
+ if (!SkImageDecoder::DecodeMemory(data->bytes(), data->size(), &bitmap,
+ SkBitmap::kNo_Config,
+ SkImageDecoder::kDecodeBounds_Mode)) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkImage_Codec, (data, bitmap.width(), bitmap.height()));
+}
diff --git a/image/SkImage_Gpu.cpp b/image/SkImage_Gpu.cpp
new file mode 100644
index 00000000..036e45bb
--- /dev/null
+++ b/image/SkImage_Gpu.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "SkImage_Base.h"
+#include "SkImagePriv.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkGrPixelRef.h"
+
+class SkImage_Gpu : public SkImage_Base {
+public:
+ SK_DECLARE_INST_COUNT(SkImage_Gpu)
+
+ explicit SkImage_Gpu(const SkBitmap&);
+ virtual ~SkImage_Gpu();
+
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) SK_OVERRIDE;
+ virtual void onDrawRectToRect(SkCanvas*, const SkRect* src, const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+ virtual GrTexture* onGetTexture() SK_OVERRIDE;
+ virtual bool getROPixels(SkBitmap*) const SK_OVERRIDE {
+ // TODO
+ return false;
+ }
+
+ GrTexture* getTexture() { return fBitmap.getTexture(); }
+
+private:
+ SkBitmap fBitmap;
+
+ typedef SkImage_Base INHERITED;
+};
+
+SK_DEFINE_INST_COUNT(SkImage_Gpu)
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage_Gpu::SkImage_Gpu(const SkBitmap& bitmap)
+ : INHERITED(bitmap.width(), bitmap.height())
+ , fBitmap(bitmap) {
+ SkASSERT(NULL != fBitmap.getTexture());
+}
+
+SkImage_Gpu::~SkImage_Gpu() {
+}
+
+void SkImage_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ canvas->drawBitmap(fBitmap, x, y, paint);
+}
+
+void SkImage_Gpu::onDrawRectToRect(SkCanvas* canvas, const SkRect* src, const SkRect& dst,
+ const SkPaint* paint) {
+ canvas->drawBitmapRectToRect(fBitmap, src, dst, paint);
+}
+
+GrTexture* SkImage_Gpu::onGetTexture() {
+ return fBitmap.getTexture();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage* SkImage::NewTexture(const SkBitmap& bitmap) {
+ if (NULL == bitmap.getTexture()) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkImage_Gpu, (bitmap));
+}
+
+GrTexture* SkTextureImageGetTexture(SkImage* image) {
+ return ((SkImage_Gpu*)image)->getTexture();
+}
diff --git a/image/SkImage_Picture.cpp b/image/SkImage_Picture.cpp
new file mode 100644
index 00000000..87221de2
--- /dev/null
+++ b/image/SkImage_Picture.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 "SkImage_Base.h"
+#include "SkImagePriv.h"
+#include "SkPicture.h"
+
+class SkImage_Picture : public SkImage_Base {
+public:
+ SkImage_Picture(SkPicture*);
+ virtual ~SkImage_Picture();
+
+ virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
+ virtual void onDrawRectToRect(SkCanvas*, const SkRect*, const SkRect&, const SkPaint*) SK_OVERRIDE;
+
+ SkPicture* getPicture() { return fPicture; }
+
+private:
+ SkPicture* fPicture;
+
+ typedef SkImage_Base INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage_Picture::SkImage_Picture(SkPicture* pict) : INHERITED(pict->width(), pict->height()) {
+ pict->endRecording();
+ pict->ref();
+ fPicture = pict;
+}
+
+SkImage_Picture::~SkImage_Picture() {
+ fPicture->unref();
+}
+
+void SkImage_Picture::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ SkImagePrivDrawPicture(canvas, fPicture, x, y, paint);
+}
+
+void SkImage_Picture::onDrawRectToRect(SkCanvas* canvas, const SkRect* src, const SkRect& dst,
+ const SkPaint* paint) {
+ SkImagePrivDrawPicture(canvas, fPicture, src, dst, paint);
+}
+
+SkImage* SkNewImageFromPicture(const SkPicture* srcPicture) {
+ /**
+ * We want to snapshot the playback status of the picture, w/o affecting
+ * its ability to continue recording (if needed).
+ *
+ * Optimally this will shared as much data/buffers as it can with
+ * srcPicture, and srcPicture will perform a copy-on-write as needed if it
+ * needs to mutate them later on.
+ */
+ SkAutoTUnref<SkPicture> playback(SkNEW_ARGS(SkPicture, (*srcPicture)));
+
+ return SkNEW_ARGS(SkImage_Picture, (playback));
+}
+
+SkPicture* SkPictureImageGetPicture(SkImage* pictureImage) {
+ return static_cast<SkImage_Picture*>(pictureImage)->getPicture();
+}
diff --git a/image/SkImage_Raster.cpp b/image/SkImage_Raster.cpp
new file mode 100644
index 00000000..3e268560
--- /dev/null
+++ b/image/SkImage_Raster.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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 "SkImage_Base.h"
+#include "SkImagePriv.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkDataPixelRef.h"
+
+class SkImage_Raster : public SkImage_Base {
+public:
+ static bool ValidArgs(const Info& info, size_t rowBytes) {
+ const int maxDimension = SK_MaxS32 >> 2;
+ const size_t kMaxPixelByteSize = SK_MaxS32;
+
+ if (info.fWidth < 0 || info.fHeight < 0) {
+ return false;
+ }
+ if (info.fWidth > maxDimension || info.fHeight > maxDimension) {
+ return false;
+ }
+ if ((unsigned)info.fColorType > (unsigned)kLastEnum_ColorType) {
+ return false;
+ }
+ if ((unsigned)info.fAlphaType > (unsigned)kLastEnum_AlphaType) {
+ return false;
+ }
+
+ bool isOpaque;
+ if (SkImageInfoToBitmapConfig(info, &isOpaque) == SkBitmap::kNo_Config) {
+ return false;
+ }
+
+ // TODO: check colorspace
+
+ if (rowBytes < SkImageMinRowBytes(info)) {
+ return false;
+ }
+
+ int64_t size = (int64_t)info.fHeight * rowBytes;
+ if (size > (int64_t)kMaxPixelByteSize) {
+ return false;
+ }
+ return true;
+ }
+
+ static SkImage* NewEmpty();
+
+ SkImage_Raster(const SkImage::Info&, SkData*, size_t rb);
+ virtual ~SkImage_Raster();
+
+ virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
+ virtual void onDrawRectToRect(SkCanvas*, const SkRect*, const SkRect&, const SkPaint*) SK_OVERRIDE;
+ virtual bool getROPixels(SkBitmap*) const SK_OVERRIDE;
+
+ // exposed for SkSurface_Raster via SkNewImageFromPixelRef
+ SkImage_Raster(const SkImage::Info&, SkPixelRef*, size_t rowBytes);
+
+ SkPixelRef* getPixelRef() const { return fBitmap.pixelRef(); }
+
+private:
+ SkImage_Raster() : INHERITED(0, 0) {}
+
+ SkBitmap fBitmap;
+
+ typedef SkImage_Base INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImage* SkImage_Raster::NewEmpty() {
+ // Returns lazily created singleton
+ static SkImage* gEmpty;
+ if (NULL == gEmpty) {
+ gEmpty = SkNEW(SkImage_Raster);
+ }
+ gEmpty->ref();
+ return gEmpty;
+}
+
+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);
+
+ fBitmap.setConfig(config, info.fWidth, info.fHeight, rowBytes);
+ fBitmap.setPixelRef(SkNEW_ARGS(SkDataPixelRef, (data)))->unref();
+ fBitmap.setIsOpaque(isOpaque);
+ fBitmap.setImmutable();
+}
+
+SkImage_Raster::SkImage_Raster(const Info& info, SkPixelRef* pr, size_t rowBytes)
+: INHERITED(info.fWidth, info.fHeight) {
+ bool isOpaque;
+ SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+ fBitmap.setConfig(config, info.fWidth, info.fHeight, rowBytes);
+ fBitmap.setPixelRef(pr);
+ fBitmap.setIsOpaque(isOpaque);
+}
+
+SkImage_Raster::~SkImage_Raster() {}
+
+void SkImage_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) {
+ canvas->drawBitmap(fBitmap, x, y, paint);
+}
+
+void SkImage_Raster::onDrawRectToRect(SkCanvas* canvas, const SkRect* src, const SkRect& dst, const SkPaint* paint) {
+ canvas->drawBitmapRectToRect(fBitmap, src, dst, paint);
+}
+
+bool SkImage_Raster::getROPixels(SkBitmap* dst) const {
+ *dst = fBitmap;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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) {
+ return SkImage_Raster::NewEmpty();
+ }
+ // check this after empty-check
+ if (NULL == pixels) {
+ return NULL;
+ }
+
+ // 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, data, 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) {
+ return SkImage_Raster::NewEmpty();
+ }
+ // check this after empty-check
+ if (NULL == pixelData) {
+ return NULL;
+ }
+
+ // did they give us enough data?
+ size_t size = info.fHeight * rowBytes;
+ if (pixelData->size() < size) {
+ return NULL;
+ }
+
+ SkAutoDataUnref data(pixelData);
+ return SkNEW_ARGS(SkImage_Raster, (info, data, rowBytes));
+}
+
+SkImage* SkNewImageFromPixelRef(const SkImage::Info& info, SkPixelRef* pr,
+ size_t rowBytes) {
+ return SkNEW_ARGS(SkImage_Raster, (info, pr, rowBytes));
+}
+
+SkPixelRef* SkBitmapImageGetPixelRef(SkImage* image) {
+ return ((SkImage_Raster*)image)->getPixelRef();
+}
diff --git a/image/SkSurface.cpp b/image/SkSurface.cpp
new file mode 100644
index 00000000..1dff7ec7
--- /dev/null
+++ b/image/SkSurface.cpp
@@ -0,0 +1,111 @@
+/*
+ * 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 "SkSurface_Base.h"
+#include "SkImagePriv.h"
+#include "SkCanvas.h"
+
+SK_DEFINE_INST_COUNT(SkSurface)
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface_Base::SkSurface_Base(int width, int height) : INHERITED(width, height) {
+ fCachedCanvas = NULL;
+ fCachedImage = NULL;
+}
+
+SkSurface_Base::~SkSurface_Base() {
+ // in case the canvas outsurvives us, we null the callback
+ if (fCachedCanvas) {
+ fCachedCanvas->setSurfaceBase(NULL);
+ }
+
+ SkSafeUnref(fCachedImage);
+ SkSafeUnref(fCachedCanvas);
+}
+
+void SkSurface_Base::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ SkImage* image = this->newImageSnapshot();
+ if (image) {
+ image->draw(canvas, x, y, paint);
+ image->unref();
+ }
+}
+
+void SkSurface_Base::aboutToDraw(ContentChangeMode mode) {
+ this->dirtyGenerationID();
+
+ if (NULL != fCachedCanvas) {
+ SkASSERT(fCachedCanvas->getSurfaceBase() == this || \
+ NULL == fCachedCanvas->getSurfaceBase());
+ fCachedCanvas->setSurfaceBase(NULL);
+ }
+
+ if (NULL != fCachedImage) {
+ // the surface may need to fork its backend, if its sharing it with
+ // the cached image. Note: we only call if there is an outstanding owner
+ // on the image (besides us).
+ if (!fCachedImage->unique()) {
+ this->onCopyOnWrite(mode);
+ }
+
+ // regardless of copy-on-write, we must drop our cached image now, so
+ // that the next request will get our new contents.
+ fCachedImage->unref();
+ fCachedImage = NULL;
+ }
+}
+
+uint32_t SkSurface_Base::newGenerationID() {
+ this->installIntoCanvasForDirtyNotification();
+
+ static int32_t gID;
+ return sk_atomic_inc(&gID) + 1;
+}
+
+static SkSurface_Base* asSB(SkSurface* surface) {
+ return static_cast<SkSurface_Base*>(surface);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface::SkSurface(int width, int height) : fWidth(width), fHeight(height) {
+ SkASSERT(width >= 0);
+ SkASSERT(height >= 0);
+ fGenerationID = 0;
+}
+
+uint32_t SkSurface::generationID() {
+ if (0 == fGenerationID) {
+ fGenerationID = asSB(this)->newGenerationID();
+ }
+ return fGenerationID;
+}
+
+void SkSurface::notifyContentWillChange(ContentChangeMode mode) {
+ asSB(this)->aboutToDraw(mode);
+}
+
+SkCanvas* SkSurface::getCanvas() {
+ return asSB(this)->getCachedCanvas();
+}
+
+SkImage* SkSurface::newImageSnapshot() {
+ SkImage* image = asSB(this)->getCachedImage();
+ SkSafeRef(image); // the caller will call unref() to balance this
+ return image;
+}
+
+SkSurface* SkSurface::newSurface(const SkImage::Info& info) {
+ return asSB(this)->onNewSurface(info);
+}
+
+void SkSurface::draw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ return asSB(this)->onDraw(canvas, x, y, paint);
+}
diff --git a/image/SkSurface_Base.h b/image/SkSurface_Base.h
new file mode 100644
index 00000000..6ea8d60b
--- /dev/null
+++ b/image/SkSurface_Base.h
@@ -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.
+ */
+
+#ifndef SkSurface_Base_DEFINED
+#define SkSurface_Base_DEFINED
+
+#include "SkSurface.h"
+#include "SkCanvas.h"
+
+class SkSurface_Base : public SkSurface {
+public:
+ SkSurface_Base(int width, int height);
+ virtual ~SkSurface_Base();
+
+ /**
+ * Allocate a canvas that will draw into this surface. We will cache this
+ * canvas, to return the same object to the caller multiple times. We
+ * take ownership, and will call unref() on the canvas when we go out of
+ * scope.
+ */
+ virtual SkCanvas* onNewCanvas() = 0;
+
+ virtual SkSurface* onNewSurface(const SkImage::Info&) = 0;
+
+ /**
+ * Allocate an SkImage that represents the current contents of the surface.
+ * This needs to be able to outlive the surface itself (if need be), and
+ * must faithfully represent the current contents, even if the surface
+ * is chaged after this calle (e.g. it is drawn to via its canvas).
+ */
+ virtual SkImage* onNewImageSnapshot() = 0;
+
+ /**
+ * Default implementation:
+ *
+ * image = this->newImageSnapshot();
+ * if (image) {
+ * image->draw(canvas, ...);
+ * image->unref();
+ * }
+ */
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*);
+
+ /**
+ * If the surface is about to change, we call this so that our subclass
+ * can optionally fork their backend (copy-on-write) in case it was
+ * being shared with the cachedImage.
+ */
+ virtual void onCopyOnWrite(ContentChangeMode) = 0;
+
+ inline SkCanvas* getCachedCanvas();
+ inline SkImage* getCachedImage();
+
+ // called by SkSurface to compute a new genID
+ uint32_t newGenerationID();
+
+private:
+ SkCanvas* fCachedCanvas;
+ SkImage* fCachedImage;
+
+ void aboutToDraw(ContentChangeMode mode);
+ friend class SkCanvas;
+ friend class SkSurface;
+
+ inline void installIntoCanvasForDirtyNotification();
+
+ typedef SkSurface INHERITED;
+};
+
+SkCanvas* SkSurface_Base::getCachedCanvas() {
+ if (NULL == fCachedCanvas) {
+ fCachedCanvas = this->onNewCanvas();
+ this->installIntoCanvasForDirtyNotification();
+ }
+ return fCachedCanvas;
+}
+
+SkImage* SkSurface_Base::getCachedImage() {
+ if (NULL == fCachedImage) {
+ fCachedImage = this->onNewImageSnapshot();
+ this->installIntoCanvasForDirtyNotification();
+ }
+ return fCachedImage;
+}
+
+void SkSurface_Base::installIntoCanvasForDirtyNotification() {
+ if (NULL != fCachedCanvas) {
+ fCachedCanvas->setSurfaceBase(this);
+ }
+}
+
+#endif
diff --git a/image/SkSurface_Gpu.cpp b/image/SkSurface_Gpu.cpp
new file mode 100644
index 00000000..e5b7bd44
--- /dev/null
+++ b/image/SkSurface_Gpu.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "SkSurface_Base.h"
+#include "SkImagePriv.h"
+#include "SkCanvas.h"
+#include "SkGpuDevice.h"
+
+class SkSurface_Gpu : public SkSurface_Base {
+public:
+ SK_DECLARE_INST_COUNT(SkSurface_Gpu)
+
+ 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&) SK_OVERRIDE;
+ virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
+
+private:
+ SkGpuDevice* fDevice;
+
+ typedef SkSurface_Base INHERITED;
+};
+
+SK_DEFINE_INST_COUNT(SkSurface_Gpu)
+
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+
+ fDevice = SkNEW_ARGS(SkGpuDevice, (ctx, config, info.fWidth, info.fHeight, sampleCount));
+
+ if (!isOpaque) {
+ fDevice->clear(0x0);
+ }
+}
+
+SkSurface_Gpu::SkSurface_Gpu(GrContext* ctx, GrRenderTarget* renderTarget)
+ : INHERITED(renderTarget->width(), renderTarget->height()) {
+ fDevice = SkNEW_ARGS(SkGpuDevice, (ctx, renderTarget));
+
+ if (kRGB_565_GrPixelConfig != renderTarget->config()) {
+ fDevice->clear(0x0);
+ }
+}
+
+SkSurface_Gpu::~SkSurface_Gpu() {
+ SkSafeUnref(fDevice);
+}
+
+SkCanvas* SkSurface_Gpu::onNewCanvas() {
+ return SkNEW_ARGS(SkCanvas, (fDevice));
+}
+
+SkSurface* SkSurface_Gpu::onNewSurface(const SkImage::Info& info) {
+ GrRenderTarget* rt = fDevice->accessRenderTarget();
+ int sampleCount = rt->numSamples();
+ return SkSurface::NewRenderTarget(fDevice->context(), info, sampleCount);
+}
+
+SkImage* SkSurface_Gpu::onNewImageSnapshot() {
+ return SkImage::NewTexture(fDevice->accessBitmap(false));
+}
+
+void SkSurface_Gpu::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ canvas->drawBitmap(fDevice->accessBitmap(false), x, y, paint);
+}
+
+// Create a new SkGpuDevice and, if necessary, copy the contents of the old
+// device into it. Note that this flushes the SkGpuDevice but
+// doesn't force an OpenGL flush.
+void SkSurface_Gpu::onCopyOnWrite(ContentChangeMode mode) {
+ GrRenderTarget* rt = fDevice->accessRenderTarget();
+ // are we sharing our render target with the image?
+ SkASSERT(NULL != this->getCachedImage());
+ if (rt->asTexture() == SkTextureImageGetTexture(this->getCachedImage())) {
+ SkGpuDevice* newDevice = static_cast<SkGpuDevice*>(
+ fDevice->createCompatibleDevice(fDevice->config(), fDevice->width(),
+ fDevice->height(), fDevice->isOpaque()));
+ SkAutoTUnref<SkGpuDevice> aurd(newDevice);
+ if (kRetain_ContentChangeMode == mode) {
+ fDevice->context()->copyTexture(rt->asTexture(),
+ reinterpret_cast<GrRenderTarget*>(newDevice->accessRenderTarget()));
+ }
+ SkASSERT(NULL != this->getCachedCanvas());
+ SkASSERT(this->getCachedCanvas()->getDevice() == fDevice);
+ this->getCachedCanvas()->setDevice(newDevice);
+ SkRefCnt_SafeAssign(fDevice, newDevice);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface* SkSurface::NewRenderTargetDirect(GrContext* ctx,
+ GrRenderTarget* target) {
+ if (NULL == ctx || NULL == target) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkSurface_Gpu, (ctx, target));
+}
+
+SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImage::Info& info, int sampleCount) {
+ if (NULL == ctx) {
+ return NULL;
+ }
+
+ bool isOpaque;
+ SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kCheckAllocation_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, (ctx, tex->asRenderTarget()));
+}
diff --git a/image/SkSurface_Picture.cpp b/image/SkSurface_Picture.cpp
new file mode 100644
index 00000000..79812c4a
--- /dev/null
+++ b/image/SkSurface_Picture.cpp
@@ -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.
+ */
+
+#include "SkSurface_Base.h"
+#include "SkCanvas.h"
+#include "SkImagePriv.h"
+#include "SkPicture.h"
+
+/**
+ * What does it mean to ask for more than one canvas from a picture?
+ * How do we return an Image and then "continue" recording?
+ */
+class SkSurface_Picture : public SkSurface_Base {
+public:
+ SkSurface_Picture(int width, int height);
+ virtual ~SkSurface_Picture();
+
+ virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
+ virtual SkSurface* onNewSurface(const SkImage::Info&) SK_OVERRIDE;
+ virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
+
+private:
+ SkPicture* fPicture;
+
+ typedef SkSurface_Base INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface_Picture::SkSurface_Picture(int width, int height) : INHERITED(width, height) {
+ fPicture = NULL;
+}
+
+SkSurface_Picture::~SkSurface_Picture() {
+ SkSafeUnref(fPicture);
+}
+
+SkCanvas* SkSurface_Picture::onNewCanvas() {
+ if (!fPicture) {
+ fPicture = SkNEW(SkPicture);
+ }
+ SkCanvas* canvas = fPicture->beginRecording(this->width(), this->height());
+ canvas->ref(); // our caller will call unref()
+ return canvas;
+}
+
+SkSurface* SkSurface_Picture::onNewSurface(const SkImage::Info& info) {
+ return SkSurface::NewPicture(info.fWidth, info.fHeight);
+}
+
+SkImage* SkSurface_Picture::onNewImageSnapshot() {
+ if (fPicture) {
+ return SkNewImageFromPicture(fPicture);
+ } else {
+ SkImage::Info info;
+ info.fWidth = info.fHeight = 0;
+ info.fColorType = SkImage::kPMColor_ColorType;
+ info.fAlphaType = SkImage::kOpaque_AlphaType;
+ return SkImage::NewRasterCopy(info, NULL, 0);
+ }
+}
+
+void SkSurface_Picture::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ if (!fPicture) {
+ return;
+ }
+ SkImagePrivDrawPicture(canvas, fPicture, x, y, paint);
+}
+
+void SkSurface_Picture::onCopyOnWrite(ContentChangeMode /*mode*/) {
+ // We always spawn a copy of the recording picture when we
+ // are asked for a snapshot, so we never need to do anything here.
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+SkSurface* SkSurface::NewPicture(int width, int height) {
+ if ((width | height) < 0) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkSurface_Picture, (width, height));
+}
diff --git a/image/SkSurface_Raster.cpp b/image/SkSurface_Raster.cpp
new file mode 100644
index 00000000..ccfdd27b
--- /dev/null
+++ b/image/SkSurface_Raster.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "SkSurface_Base.h"
+#include "SkImagePriv.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkMallocPixelRef.h"
+
+static const size_t kIgnoreRowBytesValue = (size_t)~0;
+
+class SkSurface_Raster : public SkSurface_Base {
+public:
+ static bool Valid(const SkImage::Info&, size_t rb = kIgnoreRowBytesValue);
+
+ 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&) SK_OVERRIDE;
+ virtual SkImage* onNewImageSnapshot() SK_OVERRIDE;
+ virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void onCopyOnWrite(ContentChangeMode) SK_OVERRIDE;
+
+private:
+ SkBitmap fBitmap;
+ bool fWeOwnThePixels;
+
+ typedef SkSurface_Base INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkSurface_Raster::Valid(const SkImage::Info& info, size_t rowBytes) {
+ static const size_t kMaxTotalSize = SK_MaxS32;
+
+ 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;
+ }
+
+ // TODO: examine colorspace
+
+ if (kIgnoreRowBytesValue == rowBytes) {
+ return true;
+ }
+
+ uint64_t minRB = (uint64_t)info.fWidth << shift;
+ if (minRB > rowBytes) {
+ return false;
+ }
+
+ size_t alignedRowBytes = rowBytes >> shift << shift;
+ if (alignedRowBytes != rowBytes) {
+ return false;
+ }
+
+ uint64_t size = (uint64_t)info.fHeight * rowBytes;
+ if (size > kMaxTotalSize) {
+ return false;
+ }
+
+ return true;
+}
+
+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);
+
+ fBitmap.setConfig(config, info.fWidth, info.fHeight, rb);
+ fBitmap.setPixels(pixels);
+ fBitmap.setIsOpaque(isOpaque);
+ fWeOwnThePixels = false; // We are "Direct"
+}
+
+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);
+
+ fBitmap.setConfig(config, info.fWidth, info.fHeight, rb);
+ fBitmap.setPixelRef(pr);
+ fBitmap.setIsOpaque(isOpaque);
+ fWeOwnThePixels = true;
+
+ if (!isOpaque) {
+ fBitmap.eraseColor(SK_ColorTRANSPARENT);
+ }
+}
+
+SkCanvas* SkSurface_Raster::onNewCanvas() {
+ return SkNEW_ARGS(SkCanvas, (fBitmap));
+}
+
+SkSurface* SkSurface_Raster::onNewSurface(const SkImage::Info& info) {
+ return SkSurface::NewRaster(info);
+}
+
+void SkSurface_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ canvas->drawBitmap(fBitmap, x, y, paint);
+}
+
+SkImage* SkSurface_Raster::onNewImageSnapshot() {
+ return SkNewImageFromBitmap(fBitmap, fWeOwnThePixels);
+}
+
+void SkSurface_Raster::onCopyOnWrite(ContentChangeMode mode) {
+ // are we sharing pixelrefs with the image?
+ SkASSERT(NULL != this->getCachedImage());
+ if (SkBitmapImageGetPixelRef(this->getCachedImage()) == fBitmap.pixelRef()) {
+ SkASSERT(fWeOwnThePixels);
+ if (kDiscard_ContentChangeMode == mode) {
+ fBitmap.setPixelRef(NULL, 0);
+ fBitmap.allocPixels();
+ } else {
+ SkBitmap prev(fBitmap);
+ prev.deepCopyTo(&fBitmap, prev.config());
+ }
+ // Now fBitmap is a deep copy of itself (and therefore different from
+ // what is being used by the image. Next we update the canvas to use
+ // this as its backend, so we can't modify the image's pixels anymore.
+ SkASSERT(NULL != this->getCachedCanvas());
+ this->getCachedCanvas()->getDevice()->replaceBitmapBackendForRasterSurface(fBitmap);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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, pixels, rowBytes));
+}
+
+SkSurface* SkSurface::NewRaster(const SkImage::Info& info) {
+ if (!SkSurface_Raster::Valid(info)) {
+ return NULL;
+ }
+
+ static const size_t kMaxTotalSize = SK_MaxS32;
+ size_t rowBytes = SkImageMinRowBytes(info);
+ uint64_t size64 = (uint64_t)info.fHeight * rowBytes;
+ if (size64 > kMaxTotalSize) {
+ return NULL;
+ }
+
+ size_t size = (size_t)size64;
+ void* pixels = sk_malloc_throw(size);
+ if (NULL == pixels) {
+ return NULL;
+ }
+
+ SkAutoTUnref<SkPixelRef> pr(SkNEW_ARGS(SkMallocPixelRef, (pixels, size, NULL, true)));
+ return SkNEW_ARGS(SkSurface_Raster, (info, pr, rowBytes));
+}
diff --git a/images/SkForceLinking.cpp b/images/SkForceLinking.cpp
new file mode 100644
index 00000000..e4dc60a4
--- /dev/null
+++ b/images/SkForceLinking.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkForceLinking.h"
+#include "SkImageDecoder.h"
+
+// This method is required to fool the linker into not discarding the pre-main
+// initialization and registration of the decoder classes. Passing true will
+// cause memory leaks.
+int SkForceLinking(bool doNotPassTrue) {
+ if (doNotPassTrue) {
+ SkASSERT(false);
+ CreateJPEGImageDecoder();
+ CreateWEBPImageDecoder();
+ CreateBMPImageDecoder();
+ CreateICOImageDecoder();
+ CreateWBMPImageDecoder();
+ // Only link GIF and PNG on platforms that build them. See images.gyp
+#if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_NACL) \
+ && !defined(SK_BUILD_FOR_IOS)
+ CreateGIFImageDecoder();
+#endif
+#if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_IOS)
+ CreatePNGImageDecoder();
+#endif
+ return -1;
+ }
+ return 0;
+}
diff --git a/images/SkImageDecoder.cpp b/images/SkImageDecoder.cpp
new file mode 100644
index 00000000..a671607e
--- /dev/null
+++ b/images/SkImageDecoder.cpp
@@ -0,0 +1,469 @@
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkBitmap.h"
+#include "SkImagePriv.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkCanvas.h"
+
+SK_DEFINE_INST_COUNT(SkImageDecoder::Peeker)
+SK_DEFINE_INST_COUNT(SkImageDecoder::Chooser)
+SK_DEFINE_INST_COUNT(SkImageDecoderFactory)
+
+static SkBitmap::Config gDeviceConfig = SkBitmap::kNo_Config;
+
+SkBitmap::Config SkImageDecoder::GetDeviceConfig()
+{
+ return gDeviceConfig;
+}
+
+void SkImageDecoder::SetDeviceConfig(SkBitmap::Config config)
+{
+ gDeviceConfig = config;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImageDecoder::SkImageDecoder()
+ : fPeeker(NULL)
+ , fChooser(NULL)
+ , fAllocator(NULL)
+ , fSampleSize(1)
+ , fDefaultPref(SkBitmap::kNo_Config)
+ , fDitherImage(true)
+ , fUsePrefTable(false)
+ , fPreferQualityOverSpeed(false)
+ , fRequireUnpremultipliedColors(false) {
+}
+
+SkImageDecoder::~SkImageDecoder() {
+ SkSafeUnref(fPeeker);
+ SkSafeUnref(fChooser);
+ SkSafeUnref(fAllocator);
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* other) {
+ if (NULL == other) {
+ return;
+ }
+ other->setPeeker(fPeeker);
+ other->setChooser(fChooser);
+ other->setAllocator(fAllocator);
+ other->setSampleSize(fSampleSize);
+ if (fUsePrefTable) {
+ other->setPrefConfigTable(fPrefTable);
+ } else {
+ other->fDefaultPref = fDefaultPref;
+ }
+ other->setPreferQualityOverSpeed(fPreferQualityOverSpeed);
+ other->setRequireUnpremultipliedColors(fRequireUnpremultipliedColors);
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+ return kUnknown_Format;
+}
+
+const char* SkImageDecoder::getFormatName() const {
+ return GetFormatName(this->getFormat());
+}
+
+const char* SkImageDecoder::GetFormatName(Format format) {
+ switch (format) {
+ case kUnknown_Format:
+ return "Unknown Format";
+ case kBMP_Format:
+ return "BMP";
+ case kGIF_Format:
+ return "GIF";
+ case kICO_Format:
+ return "ICO";
+ case kJPEG_Format:
+ return "JPEG";
+ case kPNG_Format:
+ return "PNG";
+ case kWBMP_Format:
+ return "WBMP";
+ case kWEBP_Format:
+ return "WEBP";
+ default:
+ SkASSERT(!"Invalid format type!");
+ }
+ return "Unknown Format";
+}
+
+SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker* peeker) {
+ SkRefCnt_SafeAssign(fPeeker, peeker);
+ return peeker;
+}
+
+SkImageDecoder::Chooser* SkImageDecoder::setChooser(Chooser* chooser) {
+ SkRefCnt_SafeAssign(fChooser, chooser);
+ return chooser;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator* alloc) {
+ SkRefCnt_SafeAssign(fAllocator, alloc);
+ return alloc;
+}
+
+void SkImageDecoder::setSampleSize(int size) {
+ if (size < 1) {
+ size = 1;
+ }
+ fSampleSize = size;
+}
+
+bool SkImageDecoder::chooseFromOneChoice(SkBitmap::Config config, int width,
+ int height) const {
+ Chooser* chooser = fChooser;
+
+ if (NULL == chooser) { // no chooser, we just say YES to decoding :)
+ return true;
+ }
+ chooser->begin(1);
+ chooser->inspect(0, config, width, height);
+ return chooser->choose() == 0;
+}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap* bitmap,
+ SkColorTable* ctable) const {
+ return bitmap->allocPixels(fAllocator, ctable);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkImageDecoder::setPrefConfigTable(const SkBitmap::Config pref[6]) {
+ if (NULL == pref) {
+ fUsePrefTable = false;
+ } else {
+ fUsePrefTable = true;
+ fPrefTable.fPrefFor_8Index_NoAlpha_src = pref[0];
+ fPrefTable.fPrefFor_8Index_YesAlpha_src = pref[1];
+ fPrefTable.fPrefFor_8Gray_src = SkBitmap::kNo_Config;
+ fPrefTable.fPrefFor_8bpc_NoAlpha_src = pref[4];
+ fPrefTable.fPrefFor_8bpc_YesAlpha_src = pref[5];
+ }
+}
+
+void SkImageDecoder::setPrefConfigTable(const PrefConfigTable& prefTable) {
+ fUsePrefTable = true;
+ fPrefTable = prefTable;
+}
+
+SkBitmap::Config SkImageDecoder::getPrefConfig(SrcDepth srcDepth,
+ bool srcHasAlpha) const {
+ SkBitmap::Config config = SkBitmap::kNo_Config;
+
+ if (fUsePrefTable) {
+ switch (srcDepth) {
+ case kIndex_SrcDepth:
+ config = srcHasAlpha ? fPrefTable.fPrefFor_8Index_YesAlpha_src
+ : fPrefTable.fPrefFor_8Index_NoAlpha_src;
+ break;
+ case k8BitGray_SrcDepth:
+ config = fPrefTable.fPrefFor_8Gray_src;
+ break;
+ case k32Bit_SrcDepth:
+ config = srcHasAlpha ? fPrefTable.fPrefFor_8bpc_YesAlpha_src
+ : fPrefTable.fPrefFor_8bpc_NoAlpha_src;
+ break;
+ }
+ } else {
+ config = fDefaultPref;
+ }
+
+ if (SkBitmap::kNo_Config == config) {
+ config = SkImageDecoder::GetDeviceConfig();
+ }
+ return config;
+}
+
+bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
+ SkBitmap::Config pref, Mode mode) {
+ // we reset this to false before calling onDecode
+ fShouldCancelDecode = false;
+ // assign this, for use by getPrefConfig(), in case fUsePrefTable is false
+ fDefaultPref = pref;
+
+ // pass a temporary bitmap, so that if we return false, we are assured of
+ // leaving the caller's bitmap untouched.
+ SkBitmap tmp;
+ if (!this->onDecode(stream, &tmp, mode)) {
+ return false;
+ }
+ bm->swap(tmp);
+ return true;
+}
+
+bool SkImageDecoder::decodeSubset(SkBitmap* bm, const SkIRect& rect,
+ SkBitmap::Config pref) {
+ // we reset this to false before calling onDecodeSubset
+ fShouldCancelDecode = false;
+ // assign this, for use by getPrefConfig(), in case fUsePrefTable is false
+ fDefaultPref = pref;
+
+ return this->onDecodeSubset(bm, rect);
+}
+
+bool SkImageDecoder::buildTileIndex(SkStream* stream,
+ int *width, int *height) {
+ // we reset this to false before calling onBuildTileIndex
+ fShouldCancelDecode = false;
+
+ return this->onBuildTileIndex(stream, width, height);
+}
+
+bool SkImageDecoder::cropBitmap(SkBitmap *dst, SkBitmap *src, int sampleSize,
+ int dstX, int dstY, int width, int height,
+ int srcX, int srcY) {
+ int w = width / sampleSize;
+ int h = height / sampleSize;
+ if (src->getConfig() == SkBitmap::kIndex8_Config) {
+ // kIndex8 does not allow drawing via an SkCanvas, as is done below.
+ // Instead, use extractSubset. Note that this shares the SkPixelRef and
+ // SkColorTable.
+ // FIXME: Since src is discarded in practice, this holds on to more
+ // pixels than is strictly necessary. Switch to a copy if memory
+ // savings are more important than speed here. This also means
+ // that the pixels in dst can not be reused (though there is no
+ // allocation, which was already done on src).
+ int x = (dstX - srcX) / sampleSize;
+ int y = (dstY - srcY) / sampleSize;
+ SkIRect subset = SkIRect::MakeXYWH(x, y, w, h);
+ return src->extractSubset(dst, subset);
+ }
+ // if the destination has no pixels then we must allocate them.
+ if (dst->isNull()) {
+ dst->setConfig(src->getConfig(), w, h);
+ dst->setIsOpaque(src->isOpaque());
+
+ if (!this->allocPixelRef(dst, NULL)) {
+ SkDEBUGF(("failed to allocate pixels needed to crop the bitmap"));
+ return false;
+ }
+ }
+ // check to see if the destination is large enough to decode the desired
+ // region. If this assert fails we will just draw as much of the source
+ // into the destination that we can.
+ if (dst->width() < w || dst->height() < h) {
+ SkDEBUGF(("SkImageDecoder::cropBitmap does not have a large enough bitmap.\n"));
+ }
+
+ // Set the Src_Mode for the paint to prevent transparency issue in the
+ // dest in the event that the dest was being re-used.
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+
+ SkCanvas canvas(*dst);
+ canvas.drawSprite(*src, (srcX - dstX) / sampleSize,
+ (srcY - dstY) / sampleSize,
+ &paint);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm,
+ SkBitmap::Config pref, Mode mode, Format* format) {
+ SkASSERT(file);
+ SkASSERT(bm);
+
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(file));
+ if (stream.get()) {
+ if (SkImageDecoder::DecodeStream(stream, bm, pref, mode, format)) {
+ bm->pixelRef()->setURI(file);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void* buffer, size_t size, SkBitmap* bm,
+ SkBitmap::Config pref, Mode mode, Format* format) {
+ if (0 == size) {
+ return false;
+ }
+ SkASSERT(buffer);
+
+ SkMemoryStream stream(buffer, size);
+ return SkImageDecoder::DecodeStream(&stream, bm, pref, mode, format);
+}
+
+/**
+ * Special allocator used by DecodeMemoryToTarget. Uses preallocated memory
+ * provided if the bm is 8888. Otherwise, uses a heap allocator. The same
+ * allocator will be used again for a copy to 8888, when the preallocated
+ * memory will be used.
+ */
+class TargetAllocator : public SkBitmap::HeapAllocator {
+
+public:
+ TargetAllocator(void* target)
+ : fTarget(target) {}
+
+ virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) SK_OVERRIDE {
+ // If the config is not 8888, allocate a pixelref using the heap.
+ // fTarget will be used to store the final pixels when copied to
+ // 8888.
+ if (bm->config() != SkBitmap::kARGB_8888_Config) {
+ return INHERITED::allocPixelRef(bm, ct);
+ }
+ // In kARGB_8888_Config, there is no colortable.
+ SkASSERT(NULL == ct);
+ bm->setPixels(fTarget);
+ return true;
+ }
+
+private:
+ void* fTarget;
+ typedef SkBitmap::HeapAllocator INHERITED;
+};
+
+/**
+ * Helper function for DecodeMemoryToTarget. DecodeMemoryToTarget wants
+ * 8888, so set the config to it. All parameters must not be null.
+ * @param decoder Decoder appropriate for this stream.
+ * @param stream Rewound stream to the encoded data.
+ * @param bitmap On success, will have its bounds set to the bounds of the
+ * encoded data, and its config set to 8888.
+ * @return True if the bounds were decoded and the bitmap is 8888 or can be
+ * copied to 8888.
+ */
+static bool decode_bounds_to_8888(SkImageDecoder* decoder, SkStream* stream,
+ SkBitmap* bitmap) {
+ SkASSERT(decoder != NULL);
+ SkASSERT(stream != NULL);
+ SkASSERT(bitmap != NULL);
+
+ if (!decoder->decode(stream, bitmap, SkImageDecoder::kDecodeBounds_Mode)) {
+ return false;
+ }
+
+ if (bitmap->config() == SkBitmap::kARGB_8888_Config) {
+ return true;
+ }
+
+ if (!bitmap->canCopyTo(SkBitmap::kARGB_8888_Config)) {
+ return false;
+ }
+
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, bitmap->width(), bitmap->height());
+ return true;
+}
+
+/**
+ * Helper function for DecodeMemoryToTarget. Decodes the stream into bitmap, and if
+ * the bitmap is not 8888, then it is copied to 8888. Either way, the end result has
+ * its pixels stored in target. All parameters must not be null.
+ * @param decoder Decoder appropriate for this stream.
+ * @param stream Rewound stream to the encoded data.
+ * @param bitmap On success, will contain the decoded image, with its pixels stored
+ * at target.
+ * @param target Preallocated memory for storing pixels.
+ * @return bool Whether the decode (and copy, if necessary) succeeded.
+ */
+static bool decode_pixels_to_8888(SkImageDecoder* decoder, SkStream* stream,
+ SkBitmap* bitmap, void* target) {
+ SkASSERT(decoder != NULL);
+ SkASSERT(stream != NULL);
+ SkASSERT(bitmap != NULL);
+ SkASSERT(target != NULL);
+
+ TargetAllocator allocator(target);
+ decoder->setAllocator(&allocator);
+
+ bool success = decoder->decode(stream, bitmap, SkImageDecoder::kDecodePixels_Mode);
+ decoder->setAllocator(NULL);
+
+ if (!success) {
+ return false;
+ }
+
+ if (bitmap->config() == SkBitmap::kARGB_8888_Config) {
+ return true;
+ }
+
+ SkBitmap bm8888;
+ if (!bitmap->copyTo(&bm8888, SkBitmap::kARGB_8888_Config, &allocator)) {
+ return false;
+ }
+
+ bitmap->swap(bm8888);
+ return true;
+}
+
+bool SkImageDecoder::DecodeMemoryToTarget(const void* buffer, size_t size,
+ SkImage::Info* info,
+ const SkBitmapFactory::Target* target) {
+ if (NULL == info) {
+ return false;
+ }
+
+ // FIXME: Just to get this working, implement in terms of existing
+ // ImageDecoder calls.
+ SkBitmap bm;
+ SkMemoryStream stream(buffer, size);
+ SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
+ if (NULL == decoder.get()) {
+ return false;
+ }
+
+ if (!decode_bounds_to_8888(decoder.get(), &stream, &bm)) {
+ return false;
+ }
+
+ SkASSERT(bm.config() == SkBitmap::kARGB_8888_Config);
+
+ // Now set info properly.
+ // Since Config is SkBitmap::kARGB_8888_Config, SkBitmapToImageInfo
+ // will always succeed.
+ SkAssertResult(SkBitmapToImageInfo(bm, info));
+
+ if (NULL == target) {
+ return true;
+ }
+
+ if (target->fRowBytes != SkToU32(bm.rowBytes())) {
+ if (target->fRowBytes < SkImageMinRowBytes(*info)) {
+ SkASSERT(!"Desired row bytes is too small");
+ return false;
+ }
+ bm.setConfig(bm.config(), bm.width(), bm.height(), target->fRowBytes);
+ }
+
+ // SkMemoryStream.rewind() will always return true.
+ SkAssertResult(stream.rewind());
+ return decode_pixels_to_8888(decoder.get(), &stream, &bm, target->fAddr);
+}
+
+
+bool SkImageDecoder::DecodeStream(SkStream* stream, SkBitmap* bm,
+ SkBitmap::Config pref, Mode mode, Format* format) {
+ SkASSERT(stream);
+ SkASSERT(bm);
+
+ bool success = false;
+ SkImageDecoder* codec = SkImageDecoder::Factory(stream);
+
+ if (NULL != codec) {
+ success = codec->decode(stream, bm, pref, mode);
+ if (success && format) {
+ *format = codec->getFormat();
+ if (kUnknown_Format == *format) {
+ if (stream->rewind()) {
+ *format = GetStreamFormat(stream);
+ }
+ }
+ }
+ delete codec;
+ }
+ return success;
+}
diff --git a/images/SkImageDecoder_FactoryDefault.cpp b/images/SkImageDecoder_FactoryDefault.cpp
new file mode 100644
index 00000000..565519ac
--- /dev/null
+++ b/images/SkImageDecoder_FactoryDefault.cpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkMovie.h"
+#include "SkStream.h"
+#include "SkTRegistry.h"
+
+extern SkImageDecoder* image_decoder_from_stream(SkStream*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) {
+ return image_decoder_from_stream(stream);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+typedef SkTRegistry<SkMovie*, SkStream*> MovieReg;
+
+SkMovie* SkMovie::DecodeStream(SkStream* stream) {
+ const MovieReg* curr = MovieReg::Head();
+ while (curr) {
+ SkMovie* movie = curr->factory()(stream);
+ if (movie) {
+ return movie;
+ }
+ // we must rewind only if we got NULL, since we gave the stream to the
+ // movie, who may have already started reading from it
+ stream->rewind();
+ curr = curr->next();
+ }
+ return NULL;
+}
diff --git a/images/SkImageDecoder_FactoryRegistrar.cpp b/images/SkImageDecoder_FactoryRegistrar.cpp
new file mode 100644
index 00000000..f1eca3d0
--- /dev/null
+++ b/images/SkImageDecoder_FactoryRegistrar.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 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 "SkErrorInternals.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkTRegistry.h"
+
+// This file is used for registration of SkImageDecoders. It also holds a function
+// for checking all the the registered SkImageDecoders for one that matches an
+// input SkStream.
+
+typedef SkTRegistry<SkImageDecoder*, SkStream*> DecodeReg;
+
+// N.B. You can't use "DecodeReg::gHead here" due to complex C++
+// corner cases.
+template DecodeReg* SkTRegistry<SkImageDecoder*, SkStream*>::gHead;
+
+SkImageDecoder* image_decoder_from_stream(SkStream*);
+
+SkImageDecoder* image_decoder_from_stream(SkStream* stream) {
+ SkImageDecoder* codec = NULL;
+ const DecodeReg* curr = DecodeReg::Head();
+ while (curr) {
+ codec = curr->factory()(stream);
+ // we rewind here, because we promise later when we call "decode", that
+ // the stream will be at its beginning.
+ bool rewindSuceeded = stream->rewind();
+
+ // our image decoder's require that rewind is supported so we fail early
+ // if we are given a stream that does not support rewinding.
+ if (!rewindSuceeded) {
+ SkDEBUGF(("Unable to rewind the image stream."));
+ SkDELETE(codec);
+ return NULL;
+ }
+
+ if (codec) {
+ return codec;
+ }
+ curr = curr->next();
+ }
+ return NULL;
+}
+
+typedef SkTRegistry<SkImageDecoder::Format, SkStream*> FormatReg;
+
+template FormatReg* SkTRegistry<SkImageDecoder::Format, SkStream*>::gHead;
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStream* stream) {
+ const FormatReg* curr = FormatReg::Head();
+ while (curr != NULL) {
+ Format format = curr->factory()(stream);
+ if (!stream->rewind()) {
+ SkErrorInternals::SetError(kInvalidOperation_SkError,
+ "Unable to rewind the image stream\n");
+ return kUnknown_Format;
+ }
+ if (format != kUnknown_Format) {
+ return format;
+ }
+ curr = curr->next();
+ }
+ return kUnknown_Format;
+}
diff --git a/images/SkImageDecoder_libbmp.cpp b/images/SkImageDecoder_libbmp.cpp
new file mode 100644
index 00000000..14b9090f
--- /dev/null
+++ b/images/SkImageDecoder_libbmp.cpp
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright 2007 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 "bmpdecoderhelper.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkColorPriv.h"
+#include "SkTDArray.h"
+#include "SkTRegistry.h"
+
+class SkBMPImageDecoder : public SkImageDecoder {
+public:
+ SkBMPImageDecoder() {}
+
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kBMP_Format;
+ }
+
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(BMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_bmp(SkStream* stream) {
+ static const char kBmpMagic[] = { 'B', 'M' };
+
+
+ char buffer[sizeof(kBmpMagic)];
+
+ return stream->read(buffer, sizeof(kBmpMagic)) == sizeof(kBmpMagic) &&
+ !memcmp(buffer, kBmpMagic, sizeof(kBmpMagic));
+}
+
+static SkImageDecoder* sk_libbmp_dfactory(SkStream* stream) {
+ if (is_bmp(stream)) {
+ return SkNEW(SkBMPImageDecoder);
+ }
+ return NULL;
+}
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gReg(sk_libbmp_dfactory);
+
+static SkImageDecoder::Format get_format_bmp(SkStream* stream) {
+ if (is_bmp(stream)) {
+ return SkImageDecoder::kBMP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_bmp);
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkBmpDecoderCallback : public image_codec::BmpDecoderCallback {
+public:
+ // we don't copy the bitmap, just remember the pointer
+ SkBmpDecoderCallback(bool justBounds) : fJustBounds(justBounds) {}
+
+ // override from BmpDecoderCallback
+ virtual uint8* SetSize(int width, int height) {
+ fWidth = width;
+ fHeight = height;
+ if (fJustBounds) {
+ return NULL;
+ }
+
+ fRGB.setCount(width * height * 3); // 3 == r, g, b
+ return fRGB.begin();
+ }
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+ const uint8_t* rgb() const { return fRGB.begin(); }
+
+private:
+ SkTDArray<uint8_t> fRGB;
+ int fWidth;
+ int fHeight;
+ bool fJustBounds;
+};
+
+bool SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+
+ size_t length = stream->getLength();
+ SkAutoMalloc storage(length);
+
+ if (stream->read(storage.get(), length) != length) {
+ return false;
+ }
+
+ const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode;
+ SkBmpDecoderCallback callback(justBounds);
+
+ // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...]
+ {
+ image_codec::BmpDecoderHelper helper;
+ const int max_pixels = 16383*16383; // max width*height
+ if (!helper.DecodeImage((const char*)storage.get(), length,
+ max_pixels, &callback)) {
+ return false;
+ }
+ }
+
+ // we don't need this anymore, so free it now (before we try to allocate
+ // the bitmap's pixels) rather than waiting for its destructor
+ storage.free();
+
+ int width = callback.width();
+ int height = callback.height();
+ SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
+
+ // only accept prefConfig if it makes sense for us
+ if (SkBitmap::kARGB_4444_Config != config &&
+ SkBitmap::kRGB_565_Config != config) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+
+ SkScaledBitmapSampler sampler(width, height, getSampleSize());
+
+ bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+ bm->setIsOpaque(true);
+
+ if (justBounds) {
+ return true;
+ }
+
+ if (!this->allocPixelRef(bm, NULL)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, getDitherImage())) {
+ return false;
+ }
+
+ const int srcRowBytes = width * 3;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t* srcRow = callback.rgb();
+
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; y++) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+ return true;
+}
diff --git a/images/SkImageDecoder_libgif.cpp b/images/SkImageDecoder_libgif.cpp
new file mode 100644
index 00000000..d368eccd
--- /dev/null
+++ b/images/SkImageDecoder_libgif.cpp
@@ -0,0 +1,389 @@
+
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkPackBits.h"
+
+#include "gif_lib.h"
+
+class SkGIFImageDecoder : public SkImageDecoder {
+public:
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kGIF_Format;
+ }
+
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+static const uint8_t gStartingIterlaceYValue[] = {
+ 0, 4, 2, 1
+};
+static const uint8_t gDeltaIterlaceYValue[] = {
+ 8, 8, 4, 2
+};
+
+/* Implement the GIF interlace algorithm in an iterator.
+ 1) grab every 8th line beginning at 0
+ 2) grab every 8th line beginning at 4
+ 3) grab every 4th line beginning at 2
+ 4) grab every 2nd line beginning at 1
+*/
+class GifInterlaceIter {
+public:
+ GifInterlaceIter(int height) : fHeight(height) {
+ fStartYPtr = gStartingIterlaceYValue;
+ fDeltaYPtr = gDeltaIterlaceYValue;
+
+ fCurrY = *fStartYPtr++;
+ fDeltaY = *fDeltaYPtr++;
+ }
+
+ int currY() const {
+ SkASSERT(fStartYPtr);
+ SkASSERT(fDeltaYPtr);
+ return fCurrY;
+ }
+
+ void next() {
+ SkASSERT(fStartYPtr);
+ SkASSERT(fDeltaYPtr);
+
+ int y = fCurrY + fDeltaY;
+ // We went from an if statement to a while loop so that we iterate
+ // through fStartYPtr until a valid row is found. This is so that images
+ // that are smaller than 5x5 will not trash memory.
+ while (y >= fHeight) {
+ if (gStartingIterlaceYValue +
+ SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
+ // we done
+ SkDEBUGCODE(fStartYPtr = NULL;)
+ SkDEBUGCODE(fDeltaYPtr = NULL;)
+ y = 0;
+ } else {
+ y = *fStartYPtr++;
+ fDeltaY = *fDeltaYPtr++;
+ }
+ }
+ fCurrY = y;
+ }
+
+private:
+ const int fHeight;
+ int fCurrY;
+ int fDeltaY;
+ const uint8_t* fStartYPtr;
+ const uint8_t* fDeltaYPtr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
+ int size) {
+ SkStream* stream = (SkStream*) fileType->UserData;
+ return (int) stream->read(out, size);
+}
+
+void CheckFreeExtension(SavedImage* Image) {
+ if (Image->ExtensionBlocks) {
+#if GIFLIB_MAJOR < 5
+ FreeExtension(Image);
+#else
+ GifFreeExtensions(&Image->ExtensionBlockCount, &Image->ExtensionBlocks);
+#endif
+ }
+}
+
+// return NULL on failure
+static const ColorMapObject* find_colormap(const GifFileType* gif) {
+ const ColorMapObject* cmap = gif->Image.ColorMap;
+ if (NULL == cmap) {
+ cmap = gif->SColorMap;
+ }
+
+ if (NULL == cmap) {
+ // no colormap found
+ return NULL;
+ }
+ // some sanity checks
+ if (cmap && ((unsigned)cmap->ColorCount > 256 ||
+ cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
+ cmap = NULL;
+ }
+ return cmap;
+}
+
+// return -1 if not found (i.e. we're completely opaque)
+static int find_transpIndex(const SavedImage& image, int colorCount) {
+ int transpIndex = -1;
+ for (int i = 0; i < image.ExtensionBlockCount; ++i) {
+ const ExtensionBlock* eb = image.ExtensionBlocks + i;
+ if (eb->Function == 0xF9 && eb->ByteCount == 4) {
+ if (eb->Bytes[0] & 1) {
+ transpIndex = (unsigned char)eb->Bytes[3];
+ // check for valid transpIndex
+ if (transpIndex >= colorCount) {
+ transpIndex = -1;
+ }
+ break;
+ }
+ }
+ }
+ return transpIndex;
+}
+
+static bool error_return(GifFileType* gif, const SkBitmap& bm,
+ const char msg[]) {
+#if 0
+ SkDebugf("libgif error <%s> bitmap [%d %d] pixels %p colortable %p\n",
+ msg, bm.width(), bm.height(), bm.getPixels(), bm.getColorTable());
+#endif
+ return false;
+}
+
+bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
+#if GIFLIB_MAJOR < 5
+ GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
+#else
+ GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, NULL);
+#endif
+ if (NULL == gif) {
+ return error_return(gif, *bm, "DGifOpen");
+ }
+
+ SkAutoTCallIProc<GifFileType, DGifCloseFile> acp(gif);
+
+ SavedImage temp_save;
+ temp_save.ExtensionBlocks=NULL;
+ temp_save.ExtensionBlockCount=0;
+ SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
+
+ int width, height;
+ GifRecordType recType;
+ GifByteType *extData;
+#if GIFLIB_MAJOR >= 5
+ int extFunction;
+#endif
+ int transpIndex = -1; // -1 means we don't have it (yet)
+
+ do {
+ if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
+ return error_return(gif, *bm, "DGifGetRecordType");
+ }
+
+ switch (recType) {
+ case IMAGE_DESC_RECORD_TYPE: {
+ if (DGifGetImageDesc(gif) == GIF_ERROR) {
+ return error_return(gif, *bm, "IMAGE_DESC_RECORD_TYPE");
+ }
+
+ if (gif->ImageCount < 1) { // sanity check
+ return error_return(gif, *bm, "ImageCount < 1");
+ }
+
+ width = gif->SWidth;
+ height = gif->SHeight;
+ if (width <= 0 || height <= 0 ||
+ !this->chooseFromOneChoice(SkBitmap::kIndex8_Config,
+ width, height)) {
+ return error_return(gif, *bm, "chooseFromOneChoice");
+ }
+
+ bm->setConfig(SkBitmap::kIndex8_Config, width, height);
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
+ const GifImageDesc& desc = image->ImageDesc;
+
+ // check for valid descriptor
+ if ( (desc.Top | desc.Left) < 0 ||
+ desc.Left + desc.Width > width ||
+ desc.Top + desc.Height > height) {
+ return error_return(gif, *bm, "TopLeft");
+ }
+
+ // now we decode the colortable
+ int colorCount = 0;
+ {
+ const ColorMapObject* cmap = find_colormap(gif);
+ if (NULL == cmap) {
+ return error_return(gif, *bm, "null cmap");
+ }
+
+ colorCount = cmap->ColorCount;
+ SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount));
+ SkPMColor* colorPtr = ctable->lockColors();
+ for (int index = 0; index < colorCount; index++)
+ colorPtr[index] = SkPackARGB32(0xFF,
+ cmap->Colors[index].Red,
+ cmap->Colors[index].Green,
+ cmap->Colors[index].Blue);
+
+ transpIndex = find_transpIndex(temp_save, colorCount);
+ if (transpIndex < 0)
+ ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
+ else
+ colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor
+ ctable->unlockColors(true);
+
+ SkAutoUnref aurts(ctable);
+ if (!this->allocPixelRef(bm, ctable)) {
+ return error_return(gif, *bm, "allocPixelRef");
+ }
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ // time to decode the scanlines
+ //
+ uint8_t* scanline = bm->getAddr8(0, 0);
+ const int rowBytes = bm->rowBytes();
+ const int innerWidth = desc.Width;
+ const int innerHeight = desc.Height;
+
+ // abort if either inner dimension is <= 0
+ if (innerWidth <= 0 || innerHeight <= 0) {
+ return error_return(gif, *bm, "non-pos inner width/height");
+ }
+
+ // are we only a subset of the total bounds?
+ if ((desc.Top | desc.Left) > 0 ||
+ innerWidth < width || innerHeight < height)
+ {
+ int fill;
+ if (transpIndex >= 0) {
+ fill = transpIndex;
+ } else {
+ fill = gif->SBackGroundColor;
+ }
+ // check for valid fill index/color
+ if (static_cast<unsigned>(fill) >=
+ static_cast<unsigned>(colorCount)) {
+ fill = 0;
+ }
+ memset(scanline, fill, bm->getSize());
+ // bump our starting address
+ scanline += desc.Top * rowBytes + desc.Left;
+ }
+
+ // now decode each scanline
+ if (gif->Image.Interlace)
+ {
+ GifInterlaceIter iter(innerHeight);
+ for (int y = 0; y < innerHeight; y++)
+ {
+ uint8_t* row = scanline + iter.currY() * rowBytes;
+ if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) {
+ return error_return(gif, *bm, "interlace DGifGetLine");
+ }
+ iter.next();
+ }
+ }
+ else
+ {
+ // easy, non-interlace case
+ for (int y = 0; y < innerHeight; y++) {
+ if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
+ return error_return(gif, *bm, "DGifGetLine");
+ }
+ scanline += rowBytes;
+ }
+ }
+ goto DONE;
+ } break;
+
+ case EXTENSION_RECORD_TYPE:
+#if GIFLIB_MAJOR < 5
+ if (DGifGetExtension(gif, &temp_save.Function,
+ &extData) == GIF_ERROR) {
+#else
+ if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) {
+#endif
+ return error_return(gif, *bm, "DGifGetExtension");
+ }
+
+ while (extData != NULL) {
+ /* Create an extension block with our data */
+#if GIFLIB_MAJOR < 5
+ if (AddExtensionBlock(&temp_save, extData[0],
+ &extData[1]) == GIF_ERROR) {
+#else
+ if (GifAddExtensionBlock(&gif->ExtensionBlockCount,
+ &gif->ExtensionBlocks,
+ extFunction,
+ extData[0],
+ &extData[1]) == GIF_ERROR) {
+#endif
+ return error_return(gif, *bm, "AddExtensionBlock");
+ }
+ if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
+ return error_return(gif, *bm, "DGifGetExtensionNext");
+ }
+#if GIFLIB_MAJOR < 5
+ temp_save.Function = 0;
+#endif
+ }
+ break;
+
+ case TERMINATE_RECORD_TYPE:
+ break;
+
+ default: /* Should be trapped by DGifGetRecordType */
+ break;
+ }
+ } while (recType != TERMINATE_RECORD_TYPE);
+
+DONE:
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(GIFImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_gif(SkStream* stream) {
+ char buf[GIF_STAMP_LEN];
+ if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
+ if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#include "SkTRegistry.h"
+
+static SkImageDecoder* sk_libgif_dfactory(SkStream* stream) {
+ if (is_gif(stream)) {
+ return SkNEW(SkGIFImageDecoder);
+ }
+ return NULL;
+}
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gReg(sk_libgif_dfactory);
+
+static SkImageDecoder::Format get_format_gif(SkStream* stream) {
+ if (is_gif(stream)) {
+ return SkImageDecoder::kGIF_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_gif);
diff --git a/images/SkImageDecoder_libico.cpp b/images/SkImageDecoder_libico.cpp
new file mode 100644
index 00000000..b14e1961
--- /dev/null
+++ b/images/SkImageDecoder_libico.cpp
@@ -0,0 +1,419 @@
+
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkColorPriv.h"
+#include "SkTypes.h"
+
+class SkICOImageDecoder : public SkImageDecoder {
+public:
+ SkICOImageDecoder();
+
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kICO_Format;
+ }
+
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+//read bytes starting from the begin-th index in the buffer
+//read in Intel order, and return an integer
+
+#define readByte(buffer,begin) buffer[begin]
+#define read2Bytes(buffer,begin) buffer[begin]+(buffer[begin+1]<<8)
+#define read4Bytes(buffer,begin) buffer[begin]+(buffer[begin+1]<<8)+(buffer[begin+2]<<16)+(buffer[begin+3]<<24)
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkICOImageDecoder::SkICOImageDecoder()
+{
+}
+
+//helpers - my function pointer will call one of these, depending on the bitCount, each time through the inner loop
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+
+
+static int calculateRowBytesFor8888(int w, int bitCount)
+{
+ // Default rowBytes is w << 2 for kARGB_8888
+ // In the case of a 4 bit image with an odd width, we need to add some
+ // so we can go off the end of the drawn bitmap.
+ // Add 4 to ensure that it is still a multiple of 4.
+ if (4 == bitCount && (w & 0x1)) {
+ return (w + 1) << 2;
+ }
+ // Otherwise return 0, which will allow it to be calculated automatically.
+ return 0;
+}
+
+bool SkICOImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode)
+{
+ size_t length = stream->getLength();
+ SkAutoMalloc autoMal(length);
+ unsigned char* buf = (unsigned char*)autoMal.get();
+ if (stream->read((void*)buf, length) != length) {
+ return false;
+ }
+
+ //these should always be the same - should i use for error checking? - what about files that have some
+ //incorrect values, but still decode properly?
+ int reserved = read2Bytes(buf, 0); // 0
+ int type = read2Bytes(buf, 2); // 1
+ if (reserved != 0 || type != 1)
+ return false;
+ int count = read2Bytes(buf, 4);
+
+ //need to at least have enough space to hold the initial table of info
+ if (length < (size_t)(6 + count*16))
+ return false;
+
+ int choice;
+ Chooser* chooser = this->getChooser();
+ //FIXME:if no chooser, consider providing the largest color image
+ //what are the odds that the largest image would be monochrome?
+ if (NULL == chooser) {
+ choice = 0;
+ } else {
+ chooser->begin(count);
+ for (int i = 0; i < count; i++)
+ {
+ //need to find out the config, width, and height from the stream
+ int width = readByte(buf, 6 + i*16);
+ int height = readByte(buf, 7 + i*16);
+ int offset = read4Bytes(buf, 18 + i*16);
+ int bitCount = read2Bytes(buf, offset+14);
+ SkBitmap::Config c;
+ //currently only provide ARGB_8888_, but maybe we want kIndex8_Config for 1 and 4, and possibly 8?
+ //or maybe we'll determine this based on the provided config
+ switch (bitCount)
+ {
+ case 1:
+ case 4:
+ // In reality, at least for the moment, these will be decoded into kARGB_8888 bitmaps.
+ // However, this will be used to distinguish between the lower quality 1bpp and 4 bpp
+ // images and the higher quality images.
+ c = SkBitmap::kIndex8_Config;
+ break;
+ case 8:
+ case 24:
+ case 32:
+ c = SkBitmap::kARGB_8888_Config;
+ break;
+ default:
+ SkDEBUGF(("Image with %ibpp not supported\n", bitCount));
+ continue;
+ }
+ chooser->inspect(i, c, width, height);
+ }
+ choice = chooser->choose();
+ }
+
+ //you never know what the chooser is going to supply
+ if (choice >= count || choice < 0)
+ return false;
+
+ //skip ahead to the correct header
+ //commented out lines are not used, but if i switch to other read method, need to know how many to skip
+ //otherwise, they could be used for error checking
+ int w = readByte(buf, 6 + choice*16);
+ int h = readByte(buf, 7 + choice*16);
+ int colorCount = readByte(buf, 8 + choice*16);
+ //int reservedToo = readByte(buf, 9 + choice*16); //0
+ //int planes = read2Bytes(buf, 10 + choice*16); //1 - but often 0
+ //int fakeBitCount = read2Bytes(buf, 12 + choice*16); //should be real - usually 0
+ int size = read4Bytes(buf, 14 + choice*16); //matters?
+ int offset = read4Bytes(buf, 18 + choice*16);
+ if ((size_t)(offset + size) > length)
+ return false;
+
+ // Check to see if this is a PNG image inside the ICO
+ {
+ SkMemoryStream subStream(buf + offset, size, false);
+ SkAutoTDelete<SkImageDecoder> otherDecoder(SkImageDecoder::Factory(&subStream));
+ if (otherDecoder.get() != NULL) {
+ // Set fields on the other decoder to be the same as this one.
+ this->copyFieldsToOther(otherDecoder.get());
+ if(otherDecoder->decode(&subStream, bm, this->getDefaultPref(), mode)) {
+ return true;
+ }
+ }
+ }
+
+ //int infoSize = read4Bytes(buf, offset); //40
+ //int width = read4Bytes(buf, offset+4); //should == w
+ //int height = read4Bytes(buf, offset+8); //should == 2*h
+ //int planesToo = read2Bytes(buf, offset+12); //should == 1 (does it?)
+ int bitCount = read2Bytes(buf, offset+14);
+
+ void (*placePixel)(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) = NULL;
+ switch (bitCount)
+ {
+ case 1:
+ placePixel = &editPixelBit1;
+ colorCount = 2;
+ break;
+ case 4:
+ placePixel = &editPixelBit4;
+ colorCount = 16;
+ break;
+ case 8:
+ placePixel = &editPixelBit8;
+ colorCount = 256;
+ break;
+ case 24:
+ placePixel = &editPixelBit24;
+ colorCount = 0;
+ break;
+ case 32:
+ placePixel = &editPixelBit32;
+ colorCount = 0;
+ break;
+ default:
+ SkDEBUGF(("Decoding %ibpp is unimplemented\n", bitCount));
+ return false;
+ }
+
+ //these should all be zero, but perhaps are not - need to check
+ //int compression = read4Bytes(buf, offset+16); //0
+ //int imageSize = read4Bytes(buf, offset+20); //0 - sometimes has a value
+ //int xPixels = read4Bytes(buf, offset+24); //0
+ //int yPixels = read4Bytes(buf, offset+28); //0
+ //int colorsUsed = read4Bytes(buf, offset+32) //0 - might have an actual value though
+ //int colorsImportant = read4Bytes(buf, offset+36); //0
+
+ int begin = offset + 40;
+ //this array represents the colortable
+ //if i allow other types of bitmaps, it may actually be used as a part of the bitmap
+ SkPMColor* colors = NULL;
+ int blue, green, red;
+ if (colorCount)
+ {
+ colors = new SkPMColor[colorCount];
+ for (int j = 0; j < colorCount; j++)
+ {
+ //should this be a function - maybe a #define?
+ blue = readByte(buf, begin + 4*j);
+ green = readByte(buf, begin + 4*j + 1);
+ red = readByte(buf, begin + 4*j + 2);
+ colors[j] = SkPackARGB32(0xFF, red & 0xFF, green & 0xFF, blue & 0xFF);
+ }
+ }
+ int bitWidth = w*bitCount;
+ int test = bitWidth & 0x1F;
+ int mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0
+ int lineBitWidth = (bitWidth & 0xFFFFFFE0) + (0x20 & mask);
+ int lineWidth = lineBitWidth/bitCount;
+
+ int xorOffset = begin + colorCount*4; //beginning of the color bitmap
+ //other read method means we will just be here already
+ int andOffset = xorOffset + ((lineWidth*h*bitCount) >> 3);
+
+ /*int */test = w & 0x1F; //the low 5 bits - we are rounding up to the next 32 (2^5)
+ /*int */mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0
+ int andLineWidth = (w & 0xFFFFFFE0) + (0x20 & mask);
+ //if we allow different Configs, everything is the same til here
+ //change the config, and use different address getter, and place index vs color, and add the color table
+ //FIXME: what is the tradeoff in size?
+ //if the andbitmap (mask) is all zeroes, then we can easily do an index bitmap
+ //however, with small images with large colortables, maybe it's better to still do argb_8888
+
+ bm->setConfig(SkBitmap::kARGB_8888_Config, w, h, calculateRowBytesFor8888(w, bitCount));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ delete[] colors;
+ return true;
+ }
+
+ if (!this->allocPixelRef(bm, NULL))
+ {
+ delete[] colors;
+ return false;
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ //U32* address = bm->getAddr32(x, y);
+
+ //check the alpha bit first, but pass it along to the function to figure out how to deal with it
+ int andPixelNo = andLineWidth*(h-y-1)+x;
+ //only need to get a new alphaByte when x %8 == 0
+ //but that introduces an if and a mod - probably much slower
+ //that's ok, it's just a read of an array, not a stream
+ int alphaByte = readByte(buf, andOffset + (andPixelNo >> 3));
+ int shift = 7 - (andPixelNo & 0x7);
+ int m = 1 << shift;
+
+ int pixelNo = lineWidth*(h-y-1)+x;
+ placePixel(pixelNo, buf, xorOffset, x, y, w, bm, alphaByte, m, shift, colors);
+
+ }
+ }
+
+ delete [] colors;
+ //ensure we haven't read off the end?
+ //of course this doesn't help us if the andOffset was a lie...
+ //return andOffset + (andLineWidth >> 3) <= length;
+ return true;
+} //onDecode
+
+//function to place the pixel, determined by the bitCount
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ // note that this should be the same as/similar to the AND bitmap
+ SkPMColor* address = bm->getAddr32(x,y);
+ int byte = readByte(buf, xorOffset + (pixelNo >> 3));
+ int colorBit;
+ int alphaBit;
+ // Read all of the bits in this byte.
+ int i = x + 8;
+ // Pin to the width so we do not write outside the bounds of
+ // our color table.
+ i = i > w ? w : i;
+ // While loop to check all 8 bits individually.
+ while (x < i)
+ {
+
+ colorBit = (byte & m) >> shift;
+ alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[colorBit]);
+ x++;
+ // setup for the next pixel
+ address = address + 1;
+ m = m >> 1;
+ shift -= 1;
+ }
+ x--;
+}
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int byte = readByte(buf, xorOffset + (pixelNo >> 1));
+ int pixel = (byte >> 4) & 0xF;
+ int alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[pixel]);
+ x++;
+ //if w is odd, x may be the same as w, which means we are writing to an unused portion of the bitmap
+ //but that's okay, since i've added an extra rowByte for just this purpose
+ address = address + 1;
+ pixel = byte & 0xF;
+ m = m >> 1;
+ alphaBit = (alphaByte & m) >> (shift-1);
+ //speed up trick here
+ *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int pixel = readByte(buf, xorOffset + pixelNo);
+ int alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int blue = readByte(buf, xorOffset + 3*pixelNo);
+ int green = readByte(buf, xorOffset + 3*pixelNo + 1);
+ int red = readByte(buf, xorOffset + 3*pixelNo + 2);
+ int alphaBit = (alphaByte & m) >> shift;
+ //alphaBit == 1 => alpha = 0
+ int alpha = (alphaBit-1) & 0xFF;
+ *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int blue = readByte(buf, xorOffset + 4*pixelNo);
+ int green = readByte(buf, xorOffset + 4*pixelNo + 1);
+ int red = readByte(buf, xorOffset + 4*pixelNo + 2);
+ int alphaBit = (alphaByte & m) >> shift;
+#if 1 // don't trust the alphaBit for 32bit images <mrr>
+ alphaBit = 0;
+#endif
+ int alpha = readByte(buf, xorOffset + 4*pixelNo + 3) & ((alphaBit-1)&0xFF);
+ *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(ICOImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_ico(SkStream* stream) {
+ // Check to see if the first four bytes are 0,0,1,0
+ // FIXME: Is that required and sufficient?
+ SkAutoMalloc autoMal(4);
+ unsigned char* buf = (unsigned char*)autoMal.get();
+ stream->read((void*)buf, 4);
+ int reserved = read2Bytes(buf, 0);
+ int type = read2Bytes(buf, 2);
+ if (reserved != 0 || type != 1) {
+ // This stream does not represent an ICO image.
+ return false;
+ }
+ return true;
+}
+
+#include "SkTRegistry.h"
+
+static SkImageDecoder* sk_libico_dfactory(SkStream* stream) {
+ if (is_ico(stream)) {
+ return SkNEW(SkICOImageDecoder);
+ }
+ return NULL;
+}
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gReg(sk_libico_dfactory);
+
+static SkImageDecoder::Format get_format_ico(SkStream* stream) {
+ if (is_ico(stream)) {
+ return SkImageDecoder::kICO_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_ico);
diff --git a/images/SkImageDecoder_libjpeg.cpp b/images/SkImageDecoder_libjpeg.cpp
new file mode 100644
index 00000000..788e3526
--- /dev/null
+++ b/images/SkImageDecoder_libjpeg.cpp
@@ -0,0 +1,1162 @@
+/*
+ * Copyright 2007 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 "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkJpegUtility.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkTime.h"
+#include "SkUtils.h"
+#include "SkRect.h"
+#include "SkCanvas.h"
+
+#include <stdio.h>
+extern "C" {
+ #include "jpeglib.h"
+ #include "jerror.h"
+}
+
+// These enable timing code that report milliseconds for an encoding/decoding
+//#define TIME_ENCODE
+//#define TIME_DECODE
+
+// this enables our rgb->yuv code, which is faster than libjpeg on ARM
+#define WE_CONVERT_TO_YUV
+
+// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
+// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static void overwrite_mem_buffer_size(jpeg_decompress_struct* cinfo) {
+#ifdef SK_BUILD_FOR_ANDROID
+ /* Check if the device indicates that it has a large amount of system memory
+ * if so, increase the memory allocation to 30MB instead of the default 5MB.
+ */
+#ifdef ANDROID_LARGE_MEMORY_DEVICE
+ cinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
+#else
+ cinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
+#endif
+#endif // SK_BUILD_FOR_ANDROID
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) {
+ SkASSERT(cinfo != NULL);
+ SkASSERT(src_mgr != NULL);
+ jpeg_create_decompress(cinfo);
+ overwrite_mem_buffer_size(cinfo);
+ cinfo->src = src_mgr;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+class SkJPEGImageIndex {
+public:
+ SkJPEGImageIndex(SkStream* stream, SkImageDecoder* decoder)
+ : fSrcMgr(stream, decoder)
+ , fInfoInitialized(false)
+ , fHuffmanCreated(false)
+ , fDecompressStarted(false)
+ {
+ SkDEBUGCODE(fReadHeaderSucceeded = false;)
+ }
+
+ ~SkJPEGImageIndex() {
+ if (fHuffmanCreated) {
+ // Set to false before calling the libjpeg function, in case
+ // the libjpeg function calls longjmp. Our setjmp handler may
+ // attempt to delete this SkJPEGImageIndex, thus entering this
+ // destructor again. Setting fHuffmanCreated to false first
+ // prevents an infinite loop.
+ fHuffmanCreated = false;
+ jpeg_destroy_huffman_index(&fHuffmanIndex);
+ }
+ if (fDecompressStarted) {
+ // Like fHuffmanCreated, set to false before calling libjpeg
+ // function to prevent potential infinite loop.
+ fDecompressStarted = false;
+ jpeg_finish_decompress(&fCInfo);
+ }
+ if (fInfoInitialized) {
+ this->destroyInfo();
+ }
+ }
+
+ /**
+ * Destroy the cinfo struct.
+ * After this call, if a huffman index was already built, it
+ * can be used after calling initializeInfoAndReadHeader
+ * again. Must not be called after startTileDecompress except
+ * in the destructor.
+ */
+ void destroyInfo() {
+ SkASSERT(fInfoInitialized);
+ SkASSERT(!fDecompressStarted);
+ // Like fHuffmanCreated, set to false before calling libjpeg
+ // function to prevent potential infinite loop.
+ fInfoInitialized = false;
+ jpeg_destroy_decompress(&fCInfo);
+ SkDEBUGCODE(fReadHeaderSucceeded = false;)
+ }
+
+ /**
+ * Initialize the cinfo struct.
+ * Calls jpeg_create_decompress, makes customizations, and
+ * finally calls jpeg_read_header. Returns true if jpeg_read_header
+ * returns JPEG_HEADER_OK.
+ * If cinfo was already initialized, destroyInfo must be called to
+ * destroy the old one. Must not be called after startTileDecompress.
+ */
+ bool initializeInfoAndReadHeader() {
+ SkASSERT(!fInfoInitialized && !fDecompressStarted);
+ initialize_info(&fCInfo, &fSrcMgr);
+ fInfoInitialized = true;
+ const bool success = (JPEG_HEADER_OK == jpeg_read_header(&fCInfo, true));
+ SkDEBUGCODE(fReadHeaderSucceeded = success;)
+ return success;
+ }
+
+ jpeg_decompress_struct* cinfo() { return &fCInfo; }
+
+ huffman_index* huffmanIndex() { return &fHuffmanIndex; }
+
+ /**
+ * Build the index to be used for tile based decoding.
+ * Must only be called after a successful call to
+ * initializeInfoAndReadHeader and must not be called more
+ * than once.
+ */
+ bool buildHuffmanIndex() {
+ SkASSERT(fReadHeaderSucceeded);
+ SkASSERT(!fHuffmanCreated);
+ jpeg_create_huffman_index(&fCInfo, &fHuffmanIndex);
+ fHuffmanCreated = true;
+ SkASSERT(1 == fCInfo.scale_num && 1 == fCInfo.scale_denom);
+ return jpeg_build_huffman_index(&fCInfo, &fHuffmanIndex);
+ }
+
+ /**
+ * Start tile based decoding. Must only be called after a
+ * successful call to buildHuffmanIndex, and must only be
+ * called once.
+ */
+ bool startTileDecompress() {
+ SkASSERT(fHuffmanCreated);
+ SkASSERT(fReadHeaderSucceeded);
+ SkASSERT(!fDecompressStarted);
+ if (jpeg_start_tile_decompress(&fCInfo)) {
+ fDecompressStarted = true;
+ return true;
+ }
+ return false;
+ }
+
+private:
+ skjpeg_source_mgr fSrcMgr;
+ jpeg_decompress_struct fCInfo;
+ huffman_index fHuffmanIndex;
+ bool fInfoInitialized;
+ bool fHuffmanCreated;
+ bool fDecompressStarted;
+ SkDEBUGCODE(bool fReadHeaderSucceeded;)
+};
+#endif
+
+class SkJPEGImageDecoder : public SkImageDecoder {
+public:
+#ifdef SK_BUILD_FOR_ANDROID
+ SkJPEGImageDecoder() {
+ fImageIndex = NULL;
+ fImageWidth = 0;
+ fImageHeight = 0;
+ }
+
+ virtual ~SkJPEGImageDecoder() {
+ SkDELETE(fImageIndex);
+ }
+#endif
+
+ virtual Format getFormat() const {
+ return kJPEG_Format;
+ }
+
+protected:
+#ifdef SK_BUILD_FOR_ANDROID
+ virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
+ virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
+#endif
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+#ifdef SK_BUILD_FOR_ANDROID
+ SkJPEGImageIndex* fImageIndex;
+ int fImageWidth;
+ int fImageHeight;
+#endif
+
+ /**
+ * Determine the appropriate bitmap config and out_color_space based on
+ * both the preference of the caller and the jpeg_color_space on the
+ * jpeg_decompress_struct passed in.
+ * Must be called after jpeg_read_header.
+ */
+ SkBitmap::Config getBitmapConfig(jpeg_decompress_struct*);
+
+ typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+/* Automatically clean up after throwing an exception */
+class JPEGAutoClean {
+public:
+ JPEGAutoClean(): cinfo_ptr(NULL) {}
+ ~JPEGAutoClean() {
+ if (cinfo_ptr) {
+ jpeg_destroy_decompress(cinfo_ptr);
+ }
+ }
+ void set(jpeg_decompress_struct* info) {
+ cinfo_ptr = info;
+ }
+private:
+ jpeg_decompress_struct* cinfo_ptr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* If we need to better match the request, we might examine the image and
+ output dimensions, and determine if the downsampling jpeg provided is
+ not sufficient. If so, we can recompute a modified sampleSize value to
+ make up the difference.
+
+ To skip this additional scaling, just set sampleSize = 1; below.
+ */
+static int recompute_sampleSize(int sampleSize,
+ const jpeg_decompress_struct& cinfo) {
+ return sampleSize * cinfo.output_width / cinfo.image_width;
+}
+
+static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
+ /* These are initialized to 0, so if they have non-zero values, we assume
+ they are "valid" (i.e. have been computed by libjpeg)
+ */
+ return 0 != cinfo.output_width && 0 != cinfo.output_height;
+}
+
+static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
+ for (int i = 0; i < count; i++) {
+ JSAMPLE* rowptr = (JSAMPLE*)buffer;
+ int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
+ if (1 != row_count) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
+ huffman_index *index, void* buffer, int count) {
+ for (int i = 0; i < count; i++) {
+ JSAMPLE* rowptr = (JSAMPLE*)buffer;
+ int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr);
+ if (1 != row_count) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+// This guy exists just to aid in debugging, as it allows debuggers to just
+// set a break-point in one place to see all error exists.
+static bool return_false(const jpeg_decompress_struct& cinfo,
+ const SkBitmap& bm, const char msg[]) {
+#ifdef SK_DEBUG
+ SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n", cinfo.err->msg_code,
+ cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg,
+ bm.width(), bm.height());
+#endif
+ return false; // must always return false
+}
+
+// Convert a scanline of CMYK samples to RGBX in place. Note that this
+// method moves the "scanline" pointer in its processing
+static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) {
+ // At this point we've received CMYK pixels from libjpeg. We
+ // perform a crude conversion to RGB (based on the formulae
+ // from easyrgb.com):
+ // CMYK -> CMY
+ // C = ( C * (1 - K) + K ) // for each CMY component
+ // CMY -> RGB
+ // R = ( 1 - C ) * 255 // for each RGB component
+ // Unfortunately we are seeing inverted CMYK so all the original terms
+ // are 1-. This yields:
+ // CMYK -> CMY
+ // C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K
+ // The conversion from CMY->RGB remains the same
+ for (unsigned int x = 0; x < width; ++x, scanline += 4) {
+ scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]);
+ scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]);
+ scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]);
+ scanline[3] = 255;
+ }
+}
+
+/**
+ * Common code for setting the error manager.
+ */
+static void set_error_mgr(jpeg_decompress_struct* cinfo, skjpeg_error_mgr* errorManager) {
+ SkASSERT(cinfo != NULL);
+ SkASSERT(errorManager != NULL);
+ cinfo->err = jpeg_std_error(errorManager);
+ errorManager->error_exit = skjpeg_error_exit;
+}
+
+/**
+ * Common code for turning off upsampling and smoothing. Turning these
+ * off helps performance without showing noticable differences in the
+ * resulting bitmap.
+ */
+static void turn_off_visual_optimizations(jpeg_decompress_struct* cinfo) {
+ SkASSERT(cinfo != NULL);
+ /* this gives about 30% performance improvement. In theory it may
+ reduce the visual quality, in practice I'm not seeing a difference
+ */
+ cinfo->do_fancy_upsampling = 0;
+
+ /* this gives another few percents */
+ cinfo->do_block_smoothing = 0;
+}
+
+/**
+ * Common code for setting the dct method.
+ */
+static void set_dct_method(const SkImageDecoder& decoder, jpeg_decompress_struct* cinfo) {
+ SkASSERT(cinfo != NULL);
+#ifdef DCT_IFAST_SUPPORTED
+ if (decoder.getPreferQualityOverSpeed()) {
+ cinfo->dct_method = JDCT_ISLOW;
+ } else {
+ cinfo->dct_method = JDCT_IFAST;
+ }
+#else
+ cinfo->dct_method = JDCT_ISLOW;
+#endif
+}
+
+SkBitmap::Config SkJPEGImageDecoder::getBitmapConfig(jpeg_decompress_struct* cinfo) {
+ SkASSERT(cinfo != NULL);
+
+ SrcDepth srcDepth = k32Bit_SrcDepth;
+ if (JCS_GRAYSCALE == cinfo->jpeg_color_space) {
+ srcDepth = k8BitGray_SrcDepth;
+ }
+
+ SkBitmap::Config config = this->getPrefConfig(srcDepth, /*hasAlpha*/ false);
+ switch (config) {
+ case SkBitmap::kA8_Config:
+ // Only respect A8 config if the original is grayscale,
+ // in which case we will treat the grayscale as alpha
+ // values.
+ if (cinfo->jpeg_color_space != JCS_GRAYSCALE) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ // Fall through.
+ case SkBitmap::kARGB_4444_Config:
+ // Fall through.
+ case SkBitmap::kRGB_565_Config:
+ // These are acceptable destination configs.
+ break;
+ default:
+ // Force all other configs to 8888.
+ config = SkBitmap::kARGB_8888_Config;
+ break;
+ }
+
+ switch (cinfo->jpeg_color_space) {
+ case JCS_CMYK:
+ // Fall through.
+ case JCS_YCCK:
+ // libjpeg cannot convert from CMYK or YCCK to RGB - here we set up
+ // so libjpeg will give us CMYK samples back and we will later
+ // manually convert them to RGB
+ cinfo->out_color_space = JCS_CMYK;
+ break;
+ case JCS_GRAYSCALE:
+ if (SkBitmap::kA8_Config == config) {
+ cinfo->out_color_space = JCS_GRAYSCALE;
+ break;
+ }
+ // The data is JCS_GRAYSCALE, but the caller wants some sort of RGB
+ // config. Fall through to set to the default.
+ default:
+ cinfo->out_color_space = JCS_RGB;
+ break;
+ }
+ return config;
+}
+
+#ifdef ANDROID_RGB
+/**
+ * Based on the config and dither mode, adjust out_color_space and
+ * dither_mode of cinfo.
+ */
+static void adjust_out_color_space_and_dither(jpeg_decompress_struct* cinfo,
+ SkBitmap::Config config,
+ const SkImageDecoder& decoder) {
+ SkASSERT(cinfo != NULL);
+ cinfo->dither_mode = JDITHER_NONE;
+ if (JCS_CMYK == cinfo->out_color_space) {
+ return;
+ }
+ switch(config) {
+ case SkBitmap::kARGB_8888_Config:
+ cinfo->out_color_space = JCS_RGBA_8888;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ cinfo->out_color_space = JCS_RGB_565;
+ if (decoder.getDitherImage()) {
+ cinfo->dither_mode = JDITHER_ORDERED;
+ }
+ break;
+ default:
+ break;
+ }
+}
+#endif
+
+bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+#ifdef TIME_DECODE
+ SkAutoTime atm("JPEG Decode");
+#endif
+
+ JPEGAutoClean autoClean;
+
+ jpeg_decompress_struct cinfo;
+ skjpeg_source_mgr srcManager(stream, this);
+
+ skjpeg_error_mgr errorManager;
+ set_error_mgr(&cinfo, &errorManager);
+
+ // All objects need to be instantiated before this setjmp call so that
+ // they will be cleaned up properly if an error occurs.
+ if (setjmp(errorManager.fJmpBuf)) {
+ return return_false(cinfo, *bm, "setjmp");
+ }
+
+ initialize_info(&cinfo, &srcManager);
+ autoClean.set(&cinfo);
+
+ int status = jpeg_read_header(&cinfo, true);
+ if (status != JPEG_HEADER_OK) {
+ return return_false(cinfo, *bm, "read_header");
+ }
+
+ /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
+ can) much faster that we, just use their num/denom api to approximate
+ the size.
+ */
+ int sampleSize = this->getSampleSize();
+
+ set_dct_method(*this, &cinfo);
+
+ SkASSERT(1 == cinfo.scale_num);
+ cinfo.scale_denom = sampleSize;
+
+ turn_off_visual_optimizations(&cinfo);
+
+ const SkBitmap::Config config = this->getBitmapConfig(&cinfo);
+
+#ifdef ANDROID_RGB
+ adjust_out_color_space_and_dither(&cinfo, config, *this);
+#endif
+
+ if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
+ bm->setConfig(config, cinfo.image_width, cinfo.image_height);
+ bm->setIsOpaque(config != SkBitmap::kA8_Config);
+ return true;
+ }
+
+ /* image_width and image_height are the original dimensions, available
+ after jpeg_read_header(). To see the scaled dimensions, we have to call
+ jpeg_start_decompress(), and then read output_width and output_height.
+ */
+ if (!jpeg_start_decompress(&cinfo)) {
+ /* If we failed here, we may still have enough information to return
+ to the caller if they just wanted (subsampled bounds). If sampleSize
+ was 1, then we would have already returned. Thus we just check if
+ we're in kDecodeBounds_Mode, and that we have valid output sizes.
+
+ One reason to fail here is that we have insufficient stream data
+ to complete the setup. However, output dimensions seem to get
+ computed very early, which is why this special check can pay off.
+ */
+ if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
+ SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
+ recompute_sampleSize(sampleSize, cinfo));
+ bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
+ bm->setIsOpaque(config != SkBitmap::kA8_Config);
+ return true;
+ } else {
+ return return_false(cinfo, *bm, "start_decompress");
+ }
+ }
+ sampleSize = recompute_sampleSize(sampleSize, cinfo);
+
+ // should we allow the Chooser (if present) to pick a config for us???
+ if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) {
+ return return_false(cinfo, *bm, "chooseFromOneChoice");
+ }
+
+ SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
+ bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+ bm->setIsOpaque(config != SkBitmap::kA8_Config);
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+ if (!this->allocPixelRef(bm, NULL)) {
+ return return_false(cinfo, *bm, "allocPixelRef");
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+#ifdef ANDROID_RGB
+ /* short-circuit the SkScaledBitmapSampler when possible, as this gives
+ a significant performance boost.
+ */
+ if (sampleSize == 1 &&
+ ((config == SkBitmap::kARGB_8888_Config &&
+ cinfo.out_color_space == JCS_RGBA_8888) ||
+ (config == SkBitmap::kRGB_565_Config &&
+ cinfo.out_color_space == JCS_RGB_565)))
+ {
+ JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
+ INT32 const bpr = bm->rowBytes();
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+ // if row_count == 0, then we didn't get a scanline, so abort.
+ // if we supported partial images, we might return true in this case
+ if (0 == row_count) {
+ return return_false(cinfo, *bm, "read_scanlines");
+ }
+ if (this->shouldCancelDecode()) {
+ return return_false(cinfo, *bm, "shouldCancelDecode");
+ }
+ rowptr += bpr;
+ }
+ jpeg_finish_decompress(&cinfo);
+ return true;
+ }
+#endif
+
+ // check for supported formats
+ SkScaledBitmapSampler::SrcConfig sc;
+ if (JCS_CMYK == cinfo.out_color_space) {
+ // In this case we will manually convert the CMYK values to RGB
+ sc = SkScaledBitmapSampler::kRGBX;
+ } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
+ sc = SkScaledBitmapSampler::kRGB;
+#ifdef ANDROID_RGB
+ } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
+ sc = SkScaledBitmapSampler::kRGBX;
+ } else if (JCS_RGB_565 == cinfo.out_color_space) {
+ sc = SkScaledBitmapSampler::kRGB_565;
+#endif
+ } else if (1 == cinfo.out_color_components &&
+ JCS_GRAYSCALE == cinfo.out_color_space) {
+ sc = SkScaledBitmapSampler::kGray;
+ } else {
+ return return_false(cinfo, *bm, "jpeg colorspace");
+ }
+
+ if (!sampler.begin(bm, sc, this->getDitherImage())) {
+ return return_false(cinfo, *bm, "sampler.begin");
+ }
+
+ // The CMYK work-around relies on 4 components per pixel here
+ SkAutoMalloc srcStorage(cinfo.output_width * 4);
+ uint8_t* srcRow = (uint8_t*)srcStorage.get();
+
+ // Possibly skip initial rows [sampler.srcY0]
+ if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
+ return return_false(cinfo, *bm, "skip rows");
+ }
+
+ // now loop through scanlines until y == bm->height() - 1
+ for (int y = 0;; y++) {
+ JSAMPLE* rowptr = (JSAMPLE*)srcRow;
+ int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+ if (0 == row_count) {
+ return return_false(cinfo, *bm, "read_scanlines");
+ }
+ if (this->shouldCancelDecode()) {
+ return return_false(cinfo, *bm, "shouldCancelDecode");
+ }
+
+ if (JCS_CMYK == cinfo.out_color_space) {
+ convert_CMYK_to_RGB(srcRow, cinfo.output_width);
+ }
+
+ sampler.next(srcRow);
+ if (bm->height() - 1 == y) {
+ // we're done
+ break;
+ }
+
+ if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
+ return return_false(cinfo, *bm, "skip rows");
+ }
+ }
+
+ // we formally skip the rest, so we don't get a complaint from libjpeg
+ if (!skip_src_rows(&cinfo, srcRow,
+ cinfo.output_height - cinfo.output_scanline)) {
+ return return_false(cinfo, *bm, "skip rows");
+ }
+ jpeg_finish_decompress(&cinfo);
+
+ return true;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *height) {
+
+ SkAutoTDelete<SkJPEGImageIndex> imageIndex(SkNEW_ARGS(SkJPEGImageIndex, (stream, this)));
+ jpeg_decompress_struct* cinfo = imageIndex->cinfo();
+
+ skjpeg_error_mgr sk_err;
+ set_error_mgr(cinfo, &sk_err);
+
+ // All objects need to be instantiated before this setjmp call so that
+ // they will be cleaned up properly if an error occurs.
+ if (setjmp(sk_err.fJmpBuf)) {
+ return false;
+ }
+
+ // create the cinfo used to create/build the huffmanIndex
+ if (!imageIndex->initializeInfoAndReadHeader()) {
+ return false;
+ }
+
+ if (!imageIndex->buildHuffmanIndex()) {
+ return false;
+ }
+
+ // destroy the cinfo used to create/build the huffman index
+ imageIndex->destroyInfo();
+
+ // Init decoder to image decode mode
+ if (!imageIndex->initializeInfoAndReadHeader()) {
+ return false;
+ }
+
+ // FIXME: This sets cinfo->out_color_space, which we may change later
+ // based on the config in onDecodeSubset. This should be fine, since
+ // jpeg_init_read_tile_scanline will check out_color_space again after
+ // that change (when it calls jinit_color_deconverter).
+ (void) this->getBitmapConfig(cinfo);
+
+ turn_off_visual_optimizations(cinfo);
+
+ // instead of jpeg_start_decompress() we start a tiled decompress
+ if (!imageIndex->startTileDecompress()) {
+ return false;
+ }
+
+ SkASSERT(1 == cinfo->scale_num);
+ fImageWidth = cinfo->output_width;
+ fImageHeight = cinfo->output_height;
+
+ if (width) {
+ *width = fImageWidth;
+ }
+ if (height) {
+ *height = fImageHeight;
+ }
+
+ SkDELETE(fImageIndex);
+ fImageIndex = imageIndex.detach();
+
+ return true;
+}
+
+bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
+ if (NULL == fImageIndex) {
+ return false;
+ }
+ jpeg_decompress_struct* cinfo = fImageIndex->cinfo();
+
+ SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight);
+ if (!rect.intersect(region)) {
+ // If the requested region is entirely outside the image return false
+ return false;
+ }
+
+
+ skjpeg_error_mgr errorManager;
+ set_error_mgr(cinfo, &errorManager);
+
+ if (setjmp(errorManager.fJmpBuf)) {
+ return false;
+ }
+
+ int requestedSampleSize = this->getSampleSize();
+ cinfo->scale_denom = requestedSampleSize;
+
+ set_dct_method(*this, cinfo);
+
+ const SkBitmap::Config config = this->getBitmapConfig(cinfo);
+#ifdef ANDROID_RGB
+ adjust_out_color_space_and_dither(cinfo, config, *this);
+#endif
+
+ int startX = rect.fLeft;
+ int startY = rect.fTop;
+ int width = rect.width();
+ int height = rect.height();
+
+ jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(),
+ &startX, &startY, &width, &height);
+ int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
+ int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size);
+
+ SkScaledBitmapSampler sampler(width, height, skiaSampleSize);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+ bitmap.setIsOpaque(true);
+
+ // Check ahead of time if the swap(dest, src) is possible or not.
+ // If yes, then we will stick to AllocPixelRef since it's cheaper with the
+ // swap happening. If no, then we will use alloc to allocate pixels to
+ // prevent garbage collection.
+ int w = rect.width() / actualSampleSize;
+ int h = rect.height() / actualSampleSize;
+ bool swapOnly = (rect == region) && bm->isNull() &&
+ (w == bitmap.width()) && (h == bitmap.height()) &&
+ ((startX - rect.x()) / actualSampleSize == 0) &&
+ ((startY - rect.y()) / actualSampleSize == 0);
+ if (swapOnly) {
+ if (!this->allocPixelRef(&bitmap, NULL)) {
+ return return_false(*cinfo, bitmap, "allocPixelRef");
+ }
+ } else {
+ if (!bitmap.allocPixels()) {
+ return return_false(*cinfo, bitmap, "allocPixels");
+ }
+ }
+
+ SkAutoLockPixels alp(bitmap);
+
+#ifdef ANDROID_RGB
+ /* short-circuit the SkScaledBitmapSampler when possible, as this gives
+ a significant performance boost.
+ */
+ if (skiaSampleSize == 1 &&
+ ((config == SkBitmap::kARGB_8888_Config &&
+ cinfo->out_color_space == JCS_RGBA_8888) ||
+ (config == SkBitmap::kRGB_565_Config &&
+ cinfo->out_color_space == JCS_RGB_565)))
+ {
+ JSAMPLE* rowptr = (JSAMPLE*)bitmap.getPixels();
+ INT32 const bpr = bitmap.rowBytes();
+ int rowTotalCount = 0;
+
+ while (rowTotalCount < height) {
+ int rowCount = jpeg_read_tile_scanline(cinfo,
+ fImageIndex->huffmanIndex(),
+ &rowptr);
+ // if row_count == 0, then we didn't get a scanline, so abort.
+ // if we supported partial images, we might return true in this case
+ if (0 == rowCount) {
+ return return_false(*cinfo, bitmap, "read_scanlines");
+ }
+ if (this->shouldCancelDecode()) {
+ return return_false(*cinfo, bitmap, "shouldCancelDecode");
+ }
+ rowTotalCount += rowCount;
+ rowptr += bpr;
+ }
+
+ if (swapOnly) {
+ bm->swap(bitmap);
+ } else {
+ cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
+ region.width(), region.height(), startX, startY);
+ }
+ return true;
+ }
+#endif
+
+ // check for supported formats
+ SkScaledBitmapSampler::SrcConfig sc;
+ if (JCS_CMYK == cinfo->out_color_space) {
+ // In this case we will manually convert the CMYK values to RGB
+ sc = SkScaledBitmapSampler::kRGBX;
+ } else if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) {
+ sc = SkScaledBitmapSampler::kRGB;
+#ifdef ANDROID_RGB
+ } else if (JCS_RGBA_8888 == cinfo->out_color_space) {
+ sc = SkScaledBitmapSampler::kRGBX;
+ } else if (JCS_RGB_565 == cinfo->out_color_space) {
+ sc = SkScaledBitmapSampler::kRGB_565;
+#endif
+ } else if (1 == cinfo->out_color_components &&
+ JCS_GRAYSCALE == cinfo->out_color_space) {
+ sc = SkScaledBitmapSampler::kGray;
+ } else {
+ return return_false(*cinfo, *bm, "jpeg colorspace");
+ }
+
+ if (!sampler.begin(&bitmap, sc, this->getDitherImage())) {
+ return return_false(*cinfo, bitmap, "sampler.begin");
+ }
+
+ // The CMYK work-around relies on 4 components per pixel here
+ SkAutoMalloc srcStorage(width * 4);
+ uint8_t* srcRow = (uint8_t*)srcStorage.get();
+
+ // Possibly skip initial rows [sampler.srcY0]
+ if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) {
+ return return_false(*cinfo, bitmap, "skip rows");
+ }
+
+ // now loop through scanlines until y == bitmap->height() - 1
+ for (int y = 0;; y++) {
+ JSAMPLE* rowptr = (JSAMPLE*)srcRow;
+ int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
+ if (0 == row_count) {
+ return return_false(*cinfo, bitmap, "read_scanlines");
+ }
+ if (this->shouldCancelDecode()) {
+ return return_false(*cinfo, bitmap, "shouldCancelDecode");
+ }
+
+ if (JCS_CMYK == cinfo->out_color_space) {
+ convert_CMYK_to_RGB(srcRow, width);
+ }
+
+ sampler.next(srcRow);
+ if (bitmap.height() - 1 == y) {
+ // we're done
+ break;
+ }
+
+ if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow,
+ sampler.srcDY() - 1)) {
+ return return_false(*cinfo, bitmap, "skip rows");
+ }
+ }
+ if (swapOnly) {
+ bm->swap(bitmap);
+ } else {
+ cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
+ region.width(), region.height(), startX, startY);
+ }
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorPriv.h"
+
+// taken from jcolor.c in libjpeg
+#if 0 // 16bit - precise but slow
+ #define CYR 19595 // 0.299
+ #define CYG 38470 // 0.587
+ #define CYB 7471 // 0.114
+
+ #define CUR -11059 // -0.16874
+ #define CUG -21709 // -0.33126
+ #define CUB 32768 // 0.5
+
+ #define CVR 32768 // 0.5
+ #define CVG -27439 // -0.41869
+ #define CVB -5329 // -0.08131
+
+ #define CSHIFT 16
+#else // 8bit - fast, slightly less precise
+ #define CYR 77 // 0.299
+ #define CYG 150 // 0.587
+ #define CYB 29 // 0.114
+
+ #define CUR -43 // -0.16874
+ #define CUG -85 // -0.33126
+ #define CUB 128 // 0.5
+
+ #define CVR 128 // 0.5
+ #define CVG -107 // -0.41869
+ #define CVB -21 // -0.08131
+
+ #define CSHIFT 8
+#endif
+
+static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
+ int r = SkGetPackedR32(c);
+ int g = SkGetPackedG32(c);
+ int b = SkGetPackedB32(c);
+
+ int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
+ int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
+ int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
+
+ dst[0] = SkToU8(y);
+ dst[1] = SkToU8(u + 128);
+ dst[2] = SkToU8(v + 128);
+}
+
+static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
+ int r = SkGetPackedR4444(c);
+ int g = SkGetPackedG4444(c);
+ int b = SkGetPackedB4444(c);
+
+ int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
+ int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
+ int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
+
+ dst[0] = SkToU8(y);
+ dst[1] = SkToU8(u + 128);
+ dst[2] = SkToU8(v + 128);
+}
+
+static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
+ int r = SkGetPackedR16(c);
+ int g = SkGetPackedG16(c);
+ int b = SkGetPackedB16(c);
+
+ int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
+ int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
+ int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
+
+ dst[0] = SkToU8(y);
+ dst[1] = SkToU8(u + 128);
+ dst[2] = SkToU8(v + 128);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
+ const void* SK_RESTRICT src, int width,
+ const SkPMColor* SK_RESTRICT ctable);
+
+static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
+ const void* SK_RESTRICT srcRow, int width,
+ const SkPMColor*) {
+ const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
+ while (--width >= 0) {
+#ifdef WE_CONVERT_TO_YUV
+ rgb2yuv_32(dst, *src++);
+#else
+ uint32_t c = *src++;
+ dst[0] = SkGetPackedR32(c);
+ dst[1] = SkGetPackedG32(c);
+ dst[2] = SkGetPackedB32(c);
+#endif
+ dst += 3;
+ }
+}
+
+static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
+ const void* SK_RESTRICT srcRow, int width,
+ const SkPMColor*) {
+ const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
+ while (--width >= 0) {
+#ifdef WE_CONVERT_TO_YUV
+ rgb2yuv_4444(dst, *src++);
+#else
+ SkPMColor16 c = *src++;
+ dst[0] = SkPacked4444ToR32(c);
+ dst[1] = SkPacked4444ToG32(c);
+ dst[2] = SkPacked4444ToB32(c);
+#endif
+ dst += 3;
+ }
+}
+
+static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
+ const void* SK_RESTRICT srcRow, int width,
+ const SkPMColor*) {
+ const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
+ while (--width >= 0) {
+#ifdef WE_CONVERT_TO_YUV
+ rgb2yuv_16(dst, *src++);
+#else
+ uint16_t c = *src++;
+ dst[0] = SkPacked16ToR32(c);
+ dst[1] = SkPacked16ToG32(c);
+ dst[2] = SkPacked16ToB32(c);
+#endif
+ dst += 3;
+ }
+}
+
+static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
+ const void* SK_RESTRICT srcRow, int width,
+ const SkPMColor* SK_RESTRICT ctable) {
+ const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
+ while (--width >= 0) {
+#ifdef WE_CONVERT_TO_YUV
+ rgb2yuv_32(dst, ctable[*src++]);
+#else
+ uint32_t c = ctable[*src++];
+ dst[0] = SkGetPackedR32(c);
+ dst[1] = SkGetPackedG32(c);
+ dst[2] = SkGetPackedB32(c);
+#endif
+ dst += 3;
+ }
+}
+
+static WriteScanline ChooseWriter(const SkBitmap& bm) {
+ switch (bm.config()) {
+ case SkBitmap::kARGB_8888_Config:
+ return Write_32_YUV;
+ case SkBitmap::kRGB_565_Config:
+ return Write_16_YUV;
+ case SkBitmap::kARGB_4444_Config:
+ return Write_4444_YUV;
+ case SkBitmap::kIndex8_Config:
+ return Write_Index_YUV;
+ default:
+ return NULL;
+ }
+}
+
+class SkJPEGImageEncoder : public SkImageEncoder {
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
+#ifdef TIME_ENCODE
+ SkAutoTime atm("JPEG Encode");
+#endif
+
+ SkAutoLockPixels alp(bm);
+ if (NULL == bm.getPixels()) {
+ return false;
+ }
+
+ jpeg_compress_struct cinfo;
+ skjpeg_error_mgr sk_err;
+ skjpeg_destination_mgr sk_wstream(stream);
+
+ // allocate these before set call setjmp
+ SkAutoMalloc oneRow;
+ SkAutoLockColors ctLocker;
+
+ cinfo.err = jpeg_std_error(&sk_err);
+ sk_err.error_exit = skjpeg_error_exit;
+ if (setjmp(sk_err.fJmpBuf)) {
+ return false;
+ }
+
+ // Keep after setjmp or mark volatile.
+ const WriteScanline writer = ChooseWriter(bm);
+ if (NULL == writer) {
+ return false;
+ }
+
+ jpeg_create_compress(&cinfo);
+ cinfo.dest = &sk_wstream;
+ cinfo.image_width = bm.width();
+ cinfo.image_height = bm.height();
+ cinfo.input_components = 3;
+#ifdef WE_CONVERT_TO_YUV
+ cinfo.in_color_space = JCS_YCbCr;
+#else
+ cinfo.in_color_space = JCS_RGB;
+#endif
+ cinfo.input_gamma = 1;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
+#ifdef DCT_IFAST_SUPPORTED
+ cinfo.dct_method = JDCT_IFAST;
+#endif
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ const int width = bm.width();
+ uint8_t* oneRowP = (uint8_t*)oneRow.reset(width * 3);
+
+ const SkPMColor* colors = ctLocker.lockColors(bm);
+ const void* srcRow = bm.getPixels();
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
+
+ writer(oneRowP, srcRow, width, colors);
+ row_pointer[0] = oneRowP;
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
+ }
+
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ return true;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(JPEGImageDecoder);
+DEFINE_ENCODER_CREATOR(JPEGImageEncoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_jpeg(SkStream* stream) {
+ static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF };
+ static const size_t HEADER_SIZE = sizeof(gHeader);
+
+ char buffer[HEADER_SIZE];
+ size_t len = stream->read(buffer, HEADER_SIZE);
+
+ if (len != HEADER_SIZE) {
+ return false; // can't read enough
+ }
+ if (memcmp(buffer, gHeader, HEADER_SIZE)) {
+ return false;
+ }
+ return true;
+}
+
+#include "SkTRegistry.h"
+
+static SkImageDecoder* sk_libjpeg_dfactory(SkStream* stream) {
+ if (is_jpeg(stream)) {
+ return SkNEW(SkJPEGImageDecoder);
+ }
+ return NULL;
+}
+
+static SkImageDecoder::Format get_format_jpeg(SkStream* stream) {
+ if (is_jpeg(stream)) {
+ return SkImageDecoder::kJPEG_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) {
+ return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
+}
+
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libjpeg_dfactory);
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_jpeg);
+static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libjpeg_efactory);
diff --git a/images/SkImageDecoder_libpng.cpp b/images/SkImageDecoder_libpng.cpp
new file mode 100644
index 00000000..85e803b4
--- /dev/null
+++ b/images/SkImageDecoder_libpng.cpp
@@ -0,0 +1,1206 @@
+
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkMath.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+#include "transform_scanline.h"
+
+extern "C" {
+#include "png.h"
+}
+
+/* These were dropped in libpng >= 1.4 */
+#ifndef png_infopp_NULL
+#define png_infopp_NULL NULL
+#endif
+
+#ifndef png_bytepp_NULL
+#define png_bytepp_NULL NULL
+#endif
+
+#ifndef int_p_NULL
+#define int_p_NULL NULL
+#endif
+
+#ifndef png_flush_ptr_NULL
+#define png_flush_ptr_NULL NULL
+#endif
+
+class SkPNGImageIndex {
+public:
+ SkPNGImageIndex(SkStream* stream, png_structp png_ptr, png_infop info_ptr)
+ : fStream(stream)
+ , fPng_ptr(png_ptr)
+ , fInfo_ptr(info_ptr)
+ , fConfig(SkBitmap::kNo_Config) {
+ SkASSERT(stream != NULL);
+ stream->ref();
+ }
+ ~SkPNGImageIndex() {
+ if (NULL != fPng_ptr) {
+ png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
+ }
+ }
+
+ SkAutoTUnref<SkStream> fStream;
+ png_structp fPng_ptr;
+ png_infop fInfo_ptr;
+ SkBitmap::Config fConfig;
+};
+
+class SkPNGImageDecoder : public SkImageDecoder {
+public:
+ SkPNGImageDecoder() {
+ fImageIndex = NULL;
+ }
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kPNG_Format;
+ }
+
+ virtual ~SkPNGImageDecoder() {
+ SkDELETE(fImageIndex);
+ }
+
+protected:
+#ifdef SK_BUILD_FOR_ANDROID
+ virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
+ virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
+#endif
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+ SkPNGImageIndex* fImageIndex;
+
+ bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
+ bool decodePalette(png_structp png_ptr, png_infop info_ptr,
+ bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
+ SkColorTable **colorTablep);
+ bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
+ SkBitmap::Config *config, bool *hasAlpha,
+ bool *doDither, SkPMColor *theTranspColor);
+
+ typedef SkImageDecoder INHERITED;
+};
+
+#ifndef png_jmpbuf
+# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
+#endif
+
+#define PNG_BYTES_TO_CHECK 4
+
+/* Automatically clean up after throwing an exception */
+struct PNGAutoClean {
+ PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
+ ~PNGAutoClean() {
+ png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+ }
+private:
+ png_structp png_ptr;
+ png_infop info_ptr;
+};
+
+static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
+ SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
+ size_t bytes = sk_stream->read(data, length);
+ if (bytes != length) {
+ png_error(png_ptr, "Read Error!");
+ }
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
+ SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
+ sk_stream->rewind();
+ (void)sk_stream->skip(offset);
+}
+#endif
+
+static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
+ SkImageDecoder::Peeker* peeker =
+ (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
+ // peek() returning true means continue decoding
+ return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
+ 1 : -1;
+}
+
+static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
+ SkDEBUGF(("------ png error %s\n", msg));
+ longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
+ for (int i = 0; i < count; i++) {
+ uint8_t* tmp = storage;
+ png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+ }
+}
+
+static bool pos_le(int value, int max) {
+ return value > 0 && value <= max;
+}
+
+static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
+ SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
+
+ bool reallyHasAlpha = false;
+
+ for (int y = bm->height() - 1; y >= 0; --y) {
+ SkPMColor* p = bm->getAddr32(0, y);
+ for (int x = bm->width() - 1; x >= 0; --x) {
+ if (match == *p) {
+ *p = 0;
+ reallyHasAlpha = true;
+ }
+ p += 1;
+ }
+ }
+ return reallyHasAlpha;
+}
+
+static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
+ bool srcHasAlpha) {
+ switch (dstConfig) {
+ case SkBitmap::kARGB_8888_Config:
+ case SkBitmap::kARGB_4444_Config:
+ return true;
+ case SkBitmap::kRGB_565_Config:
+ // only return true if the src is opaque (since 565 is opaque)
+ return !srcHasAlpha;
+ default:
+ return false;
+ }
+}
+
+// call only if color_type is PALETTE. Returns true if the ctable has alpha
+static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
+ png_bytep trans;
+ int num_trans;
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
+ return num_trans > 0;
+ }
+ return false;
+}
+
+bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
+ png_infop *info_ptrp) {
+ /* Create and initialize the png_struct with the desired error handler
+ * functions. If you want to use the default stderr and longjump method,
+ * you can supply NULL for the last three parameters. We also supply the
+ * the compiler header file version, so that we know if the application
+ * was compiled with a compatible version of the library. */
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ NULL, sk_error_fn, NULL);
+ // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
+ if (png_ptr == NULL) {
+ return false;
+ }
+ *png_ptrp = png_ptr;
+
+ /* Allocate/initialize the memory for image information. */
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ return false;
+ }
+ *info_ptrp = info_ptr;
+
+ /* Set error handling if you are using the setjmp/longjmp method (this is
+ * the normal method of doing things with libpng). REQUIRED unless you
+ * set up your own error handlers in the png_create_read_struct() earlier.
+ */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
+
+ /* If you are using replacement read functions, instead of calling
+ * png_init_io() here you would call:
+ */
+ png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
+#ifdef SK_BUILD_FOR_ANDROID
+ png_set_seek_fn(png_ptr, sk_seek_fn);
+#endif
+ /* where user_io_ptr is a structure you want available to the callbacks */
+ /* If we have already read some of the signature */
+// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
+
+ // hookup our peeker so we can see any user-chunks the caller may be interested in
+ png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
+ if (this->getPeeker()) {
+ png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
+ }
+
+ /* The call to png_read_info() gives us all of the information from the
+ * PNG file before the first IDAT (image data chunk). */
+ png_read_info(png_ptr, info_ptr);
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+ /* tell libpng to strip 16 bit/color files down to 8 bits/color */
+ if (bitDepth == 16) {
+ png_set_strip_16(png_ptr);
+ }
+ /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
+ * byte into separate bytes (useful for paletted and grayscale images). */
+ if (bitDepth < 8) {
+ png_set_packing(png_ptr);
+ }
+ /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ }
+
+ return true;
+}
+
+bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
+ Mode mode) {
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
+
+ PNGAutoClean autoClean(png_ptr, info_ptr);
+
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType, interlaceType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, &interlaceType, int_p_NULL, int_p_NULL);
+
+ SkBitmap::Config config;
+ bool hasAlpha = false;
+ bool doDither = this->getDitherImage();
+ SkPMColor theTranspColor = 0; // 0 tells us not to try to match
+
+ if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
+ return false;
+ }
+
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+ decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ // from here down we are concerned with colortables and pixels
+
+ // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
+ // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
+ // draw lots faster if we can flag the bitmap has being opaque
+ bool reallyHasAlpha = false;
+ SkColorTable* colorTable = NULL;
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
+ }
+
+ SkAutoUnref aur(colorTable);
+
+ if (!this->allocPixelRef(decodedBitmap,
+ SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ /* Turn on interlace handling. REQUIRED if you are not using
+ * png_read_image(). To see how to handle interlacing passes,
+ * see the png_read_row() method below:
+ */
+ const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
+ png_set_interlace_handling(png_ptr) : 1;
+
+ /* Optional call to gamma correct and add the background to the palette
+ * and update info structure. REQUIRED if you are expecting libpng to
+ * update the palette for you (ie you selected such a transform above).
+ */
+ png_read_update_info(png_ptr, info_ptr);
+
+ if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
+ && 1 == sampleSize) {
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(config != SkBitmap::kA8_Config
+ || PNG_COLOR_TYPE_GRAY == colorType);
+ for (int i = 0; i < number_passes; i++) {
+ for (png_uint_32 y = 0; y < origHeight; y++) {
+ uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ }
+ } else {
+ SkScaledBitmapSampler::SrcConfig sc;
+ int srcBytesPerPixel = 4;
+
+ if (colorTable != NULL) {
+ sc = SkScaledBitmapSampler::kIndex;
+ srcBytesPerPixel = 1;
+ } else if (SkBitmap::kA8_Config == config) {
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
+ sc = SkScaledBitmapSampler::kGray;
+ srcBytesPerPixel = 1;
+ } else if (hasAlpha) {
+ sc = SkScaledBitmapSampler::kRGBA;
+ } else {
+ sc = SkScaledBitmapSampler::kRGBX;
+ }
+
+ /* We have to pass the colortable explicitly, since we may have one
+ even if our decodedBitmap doesn't, due to the request that we
+ upscale png's palette to a direct model
+ */
+ SkAutoLockColors ctLock(colorTable);
+ if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
+ this->getRequireUnpremultipliedColors())) {
+ return false;
+ }
+ const int height = decodedBitmap->height();
+
+ if (number_passes > 1) {
+ SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
+ uint8_t* base = (uint8_t*)storage.get();
+ size_t rowBytes = origWidth * srcBytesPerPixel;
+
+ for (int i = 0; i < number_passes; i++) {
+ uint8_t* row = base;
+ for (png_uint_32 y = 0; y < origHeight; y++) {
+ uint8_t* bmRow = row;
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ row += rowBytes;
+ }
+ }
+ // now sample it
+ base += sampler.srcY0() * rowBytes;
+ for (int y = 0; y < height; y++) {
+ reallyHasAlpha |= sampler.next(base);
+ base += sampler.srcDY() * rowBytes;
+ }
+ } else {
+ SkAutoMalloc storage(origWidth * srcBytesPerPixel);
+ uint8_t* srcRow = (uint8_t*)storage.get();
+ skip_src_rows(png_ptr, srcRow, sampler.srcY0());
+
+ for (int y = 0; y < height; y++) {
+ uint8_t* tmp = srcRow;
+ png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+ reallyHasAlpha |= sampler.next(srcRow);
+ if (y < height - 1) {
+ skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
+ }
+ }
+
+ // skip the rest of the rows (if any)
+ png_uint_32 read = (height - 1) * sampler.srcDY() +
+ sampler.srcY0() + 1;
+ SkASSERT(read <= origHeight);
+ skip_src_rows(png_ptr, srcRow, origHeight - read);
+ }
+ }
+
+ /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
+ png_read_end(png_ptr, info_ptr);
+
+ if (0 != theTranspColor) {
+ reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
+ }
+ if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
+ SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
+ // If the caller wants an unpremultiplied bitmap, and we let them get
+ // away with a config other than 8888, and it has alpha after all,
+ // return false, since the result will have premultiplied colors.
+ return false;
+ }
+ decodedBitmap->setIsOpaque(!reallyHasAlpha);
+ return true;
+}
+
+
+
+bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
+ SkBitmap::Config* SK_RESTRICT configp,
+ bool* SK_RESTRICT hasAlphap,
+ bool* SK_RESTRICT doDitherp,
+ SkPMColor* SK_RESTRICT theTranspColorp) {
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+ // check for sBIT chunk data, in case we should disable dithering because
+ // our data is not truely 8bits per component
+ png_color_8p sig_bit;
+ if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
+#if 0
+ SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
+ sig_bit->blue, sig_bit->alpha);
+#endif
+ // 0 seems to indicate no information available
+ if (pos_le(sig_bit->red, SK_R16_BITS) &&
+ pos_le(sig_bit->green, SK_G16_BITS) &&
+ pos_le(sig_bit->blue, SK_B16_BITS)) {
+ *doDitherp = false;
+ }
+ }
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
+ *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
+ // now see if we can upscale to their requested config
+ if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
+ *configp = SkBitmap::kIndex8_Config;
+ }
+ } else {
+ png_color_16p transpColor = NULL;
+ int numTransp = 0;
+
+ png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
+
+ bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
+
+ if (valid && numTransp == 1 && transpColor != NULL) {
+ /* Compute our transparent color, which we'll match against later.
+ We don't really handle 16bit components properly here, since we
+ do our compare *after* the values have been knocked down to 8bit
+ which means we will find more matches than we should. The real
+ fix seems to be to see the actual 16bit components, do the
+ compare, and then knock it down to 8bits ourselves.
+ */
+ if (colorType & PNG_COLOR_MASK_COLOR) {
+ if (16 == bitDepth) {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
+ transpColor->green >> 8,
+ transpColor->blue >> 8);
+ } else {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
+ transpColor->green,
+ transpColor->blue);
+ }
+ } else { // gray
+ if (16 == bitDepth) {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
+ transpColor->gray >> 8,
+ transpColor->gray >> 8);
+ } else {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
+ transpColor->gray,
+ transpColor->gray);
+ }
+ }
+ }
+
+ if (valid ||
+ PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
+ PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
+ *hasAlphap = true;
+ }
+
+ SrcDepth srcDepth = k32Bit_SrcDepth;
+ if (PNG_COLOR_TYPE_GRAY == colorType) {
+ srcDepth = k8BitGray_SrcDepth;
+ // Remove this assert, which fails on desk_pokemonwiki.skp
+ //SkASSERT(!*hasAlphap);
+ }
+
+ *configp = this->getPrefConfig(srcDepth, *hasAlphap);
+ // now match the request against our capabilities
+ if (*hasAlphap) {
+ if (*configp != SkBitmap::kARGB_4444_Config) {
+ *configp = SkBitmap::kARGB_8888_Config;
+ }
+ } else {
+ if (*configp != SkBitmap::kRGB_565_Config &&
+ *configp != SkBitmap::kARGB_4444_Config &&
+ *configp != SkBitmap::kA8_Config) {
+ *configp = SkBitmap::kARGB_8888_Config;
+ }
+ }
+ }
+
+ // sanity check for size
+ {
+ Sk64 size;
+ size.setMul(origWidth, origHeight);
+ if (size.isNeg() || !size.is32()) {
+ return false;
+ }
+ // now check that if we are 4-bytes per pixel, we also don't overflow
+ if (size.get32() > (0x7FFFFFFF >> 2)) {
+ return false;
+ }
+ }
+
+ if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
+ return false;
+ }
+
+ // If the image has alpha and the decoder wants unpremultiplied
+ // colors, the only supported config is 8888.
+ if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
+ *configp = SkBitmap::kARGB_8888_Config;
+ }
+
+ if (fImageIndex != NULL) {
+ if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
+ // This is the first time for this subset decode. From now on,
+ // all decodes must be in the same config.
+ fImageIndex->fConfig = *configp;
+ } else if (fImageIndex->fConfig != *configp) {
+ // Requesting a different config for a subsequent decode is not
+ // supported. Report failure before we make changes to png_ptr.
+ return false;
+ }
+ }
+
+ bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
+ && *configp != SkBitmap::kA8_Config;
+
+ // Unless the user is requesting A8, convert a grayscale image into RGB.
+ // GRAY_ALPHA will always be converted to RGB
+ if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(png_ptr);
+ }
+
+ // Add filler (or alpha) byte (after each RGB triplet) if necessary.
+ if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
+ png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
+ }
+
+ return true;
+}
+
+typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
+
+bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
+ bool *hasAlphap, bool *reallyHasAlphap,
+ SkColorTable **colorTablep) {
+ int numPalette;
+ png_colorp palette;
+ png_bytep trans;
+ int numTrans;
+ bool reallyHasAlpha = false;
+ SkColorTable* colorTable = NULL;
+
+ png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
+
+ /* BUGGY IMAGE WORKAROUND
+
+ We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
+ which is a problem since we use the byte as an index. To work around this we grow
+ the colortable by 1 (if its < 256) and duplicate the last color into that slot.
+ */
+ int colorCount = numPalette + (numPalette < 256);
+
+ colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
+
+ SkPMColor* colorPtr = colorTable->lockColors();
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
+ *hasAlphap = (numTrans > 0);
+ } else {
+ numTrans = 0;
+ colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
+ }
+ // check for bad images that might make us crash
+ if (numTrans > numPalette) {
+ numTrans = numPalette;
+ }
+
+ int index = 0;
+ int transLessThanFF = 0;
+
+ // Choose which function to use to create the color table. If the final destination's
+ // config is unpremultiplied, the color table will store unpremultiplied colors.
+ PackColorProc proc;
+ if (this->getRequireUnpremultipliedColors()) {
+ proc = &SkPackARGB32NoCheck;
+ } else {
+ proc = &SkPreMultiplyARGB;
+ }
+ for (; index < numTrans; index++) {
+ transLessThanFF |= (int)*trans - 0xFF;
+ *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
+ palette++;
+ }
+ reallyHasAlpha |= (transLessThanFF < 0);
+
+ for (; index < numPalette; index++) {
+ *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
+ palette++;
+ }
+
+ // see BUGGY IMAGE WORKAROUND comment above
+ if (numPalette < 256) {
+ *colorPtr = colorPtr[-1];
+ }
+ colorTable->unlockColors(true);
+ *colorTablep = colorTable;
+ *reallyHasAlphap = reallyHasAlpha;
+ return true;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+
+bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr)) != 0) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+ return false;
+ }
+
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+ *width = origWidth;
+ *height = origHeight;
+
+ png_build_index(png_ptr);
+
+ if (fImageIndex) {
+ SkDELETE(fImageIndex);
+ }
+ fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
+
+ return true;
+}
+
+bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
+ if (NULL == fImageIndex) {
+ return false;
+ }
+
+ png_structp png_ptr = fImageIndex->fPng_ptr;
+ png_infop info_ptr = fImageIndex->fInfo_ptr;
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
+
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType, interlaceType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, &interlaceType, int_p_NULL, int_p_NULL);
+
+ SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
+
+ if (!rect.intersect(region)) {
+ // If the requested region is entirely outside the image, just
+ // returns false
+ return false;
+ }
+
+ SkBitmap::Config config;
+ bool hasAlpha = false;
+ bool doDither = this->getDitherImage();
+ SkPMColor theTranspColor = 0; // 0 tells us not to try to match
+
+ if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
+ return false;
+ }
+
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
+
+ SkBitmap decodedBitmap;
+ decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+
+ // from here down we are concerned with colortables and pixels
+
+ // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
+ // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
+ // draw lots faster if we can flag the bitmap has being opaque
+ bool reallyHasAlpha = false;
+ SkColorTable* colorTable = NULL;
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
+ }
+
+ SkAutoUnref aur(colorTable);
+
+ // Check ahead of time if the swap(dest, src) is possible.
+ // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
+ // If no, then we will use alloc to allocate pixels to prevent garbage collection.
+ int w = rect.width() / sampleSize;
+ int h = rect.height() / sampleSize;
+ const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
+ (h == decodedBitmap.height()) && bm->isNull();
+ const bool needColorTable = SkBitmap::kIndex8_Config == config;
+ if (swapOnly) {
+ if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
+ return false;
+ }
+ } else {
+ if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
+ return false;
+ }
+ }
+ SkAutoLockPixels alp(decodedBitmap);
+
+ /* Turn on interlace handling. REQUIRED if you are not using
+ * png_read_image(). To see how to handle interlacing passes,
+ * see the png_read_row() method below:
+ */
+ const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
+ png_set_interlace_handling(png_ptr) : 1;
+
+ /* Optional call to gamma correct and add the background to the palette
+ * and update info structure. REQUIRED if you are expecting libpng to
+ * update the palette for you (ie you selected such a transform above).
+ */
+
+ // Direct access to png_ptr fields is deprecated in libpng > 1.2.
+#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
+ png_ptr->pass = 0;
+#else
+ // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
+ png_set_interlaced_pass(png_ptr, 0);
+#endif
+ png_read_update_info(png_ptr, info_ptr);
+
+ int actualTop = rect.fTop;
+
+ if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
+ && 1 == sampleSize) {
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(config != SkBitmap::kA8_Config
+ || PNG_COLOR_TYPE_GRAY == colorType);
+
+ for (int i = 0; i < number_passes; i++) {
+ png_configure_decoder(png_ptr, &actualTop, i);
+ for (int j = 0; j < rect.fTop - actualTop; j++) {
+ uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
+ for (png_uint_32 y = 0; y < bitmapHeight; y++) {
+ uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ }
+ } else {
+ SkScaledBitmapSampler::SrcConfig sc;
+ int srcBytesPerPixel = 4;
+
+ if (colorTable != NULL) {
+ sc = SkScaledBitmapSampler::kIndex;
+ srcBytesPerPixel = 1;
+ } else if (SkBitmap::kA8_Config == config) {
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
+ sc = SkScaledBitmapSampler::kGray;
+ srcBytesPerPixel = 1;
+ } else if (hasAlpha) {
+ sc = SkScaledBitmapSampler::kRGBA;
+ } else {
+ sc = SkScaledBitmapSampler::kRGBX;
+ }
+
+ /* We have to pass the colortable explicitly, since we may have one
+ even if our decodedBitmap doesn't, due to the request that we
+ upscale png's palette to a direct model
+ */
+ SkAutoLockColors ctLock(colorTable);
+ if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
+ this->getRequireUnpremultipliedColors())) {
+ return false;
+ }
+ const int height = decodedBitmap.height();
+
+ if (number_passes > 1) {
+ SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
+ uint8_t* base = (uint8_t*)storage.get();
+ size_t rb = origWidth * srcBytesPerPixel;
+
+ for (int i = 0; i < number_passes; i++) {
+ png_configure_decoder(png_ptr, &actualTop, i);
+ for (int j = 0; j < rect.fTop - actualTop; j++) {
+ uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ uint8_t* row = base;
+ for (int32_t y = 0; y < rect.height(); y++) {
+ uint8_t* bmRow = row;
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ row += rb;
+ }
+ }
+ // now sample it
+ base += sampler.srcY0() * rb;
+ for (int y = 0; y < height; y++) {
+ reallyHasAlpha |= sampler.next(base);
+ base += sampler.srcDY() * rb;
+ }
+ } else {
+ SkAutoMalloc storage(origWidth * srcBytesPerPixel);
+ uint8_t* srcRow = (uint8_t*)storage.get();
+
+ png_configure_decoder(png_ptr, &actualTop, 0);
+ skip_src_rows(png_ptr, srcRow, sampler.srcY0());
+
+ for (int i = 0; i < rect.fTop - actualTop; i++) {
+ uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ for (int y = 0; y < height; y++) {
+ uint8_t* tmp = srcRow;
+ png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+ reallyHasAlpha |= sampler.next(srcRow);
+ if (y < height - 1) {
+ skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
+ }
+ }
+ }
+ }
+
+ if (0 != theTranspColor) {
+ reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
+ }
+ decodedBitmap.setIsOpaque(!reallyHasAlpha);
+
+ if (swapOnly) {
+ bm->swap(decodedBitmap);
+ return true;
+ }
+ return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
+ region.width(), region.height(), 0, rect.y());
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorPriv.h"
+#include "SkUnPreMultiply.h"
+
+static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
+ SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
+ if (!sk_stream->write(data, len)) {
+ png_error(png_ptr, "sk_write_fn Error!");
+ }
+}
+
+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
+ // colortable packing cares about that distinction, not the pixels
+ if (SkBitmap::kIndex8_Config == config) {
+ hasAlpha = false; // we store false in the table entries for kIndex8
+ }
+
+ static const struct {
+ SkBitmap::Config fConfig;
+ bool fHasAlpha;
+ transform_scanline_proc fProc;
+ } gMap[] = {
+ { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
+ { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
+ { 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_memcpy },
+ };
+
+ for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
+ if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
+ return gMap[i].fProc;
+ }
+ }
+ sk_throw();
+ return NULL;
+}
+
+// return the minimum legal bitdepth (by png standards) for this many colortable
+// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
+// we can use fewer bits per in png
+static int computeBitDepth(int colorCount) {
+#if 0
+ int bits = SkNextLog2(colorCount);
+ SkASSERT(bits >= 1 && bits <= 8);
+ // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
+ return SkNextPow2(bits);
+#else
+ // for the moment, we don't know how to pack bitdepth < 8
+ return 8;
+#endif
+}
+
+/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
+ pack trans[] and return the number of trans[] entries written. If hasAlpha
+ is false, the return value will always be 0.
+
+ Note: this routine takes care of unpremultiplying the RGB values when we
+ have alpha in the colortable, since png doesn't support premul colors
+*/
+static inline int pack_palette(SkColorTable* ctable,
+ png_color* SK_RESTRICT palette,
+ png_byte* SK_RESTRICT trans, bool hasAlpha) {
+ SkAutoLockColors alc(ctable);
+ const SkPMColor* SK_RESTRICT colors = alc.colors();
+ const int ctCount = ctable->count();
+ int i, num_trans = 0;
+
+ if (hasAlpha) {
+ /* first see if we have some number of fully opaque at the end of the
+ ctable. PNG allows num_trans < num_palette, but all of the trans
+ entries must come first in the palette. If I was smarter, I'd
+ reorder the indices and ctable so that all non-opaque colors came
+ first in the palette. But, since that would slow down the encode,
+ I'm leaving the indices and ctable order as is, and just looking
+ at the tail of the ctable for opaqueness.
+ */
+ num_trans = ctCount;
+ for (i = ctCount - 1; i >= 0; --i) {
+ if (SkGetPackedA32(colors[i]) != 0xFF) {
+ break;
+ }
+ num_trans -= 1;
+ }
+
+ const SkUnPreMultiply::Scale* SK_RESTRICT table =
+ SkUnPreMultiply::GetScaleTable();
+
+ for (i = 0; i < num_trans; i++) {
+ const SkPMColor c = *colors++;
+ const unsigned a = SkGetPackedA32(c);
+ const SkUnPreMultiply::Scale s = table[a];
+ trans[i] = a;
+ palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
+ palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
+ palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
+ }
+ // now fall out of this if-block to use common code for the trailing
+ // opaque entries
+ }
+
+ // these (remaining) entries are opaque
+ for (i = num_trans; i < ctCount; i++) {
+ SkPMColor c = *colors++;
+ palette[i].red = SkGetPackedR32(c);
+ palette[i].green = SkGetPackedG32(c);
+ palette[i].blue = SkGetPackedB32(c);
+ }
+ return num_trans;
+}
+
+class SkPNGImageEncoder : public SkImageEncoder {
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
+private:
+ bool doEncode(SkWStream* stream, const SkBitmap& bm,
+ const bool& hasAlpha, int colorType,
+ int bitDepth, SkBitmap::Config config,
+ png_color_8& sig_bit);
+
+ typedef SkImageEncoder INHERITED;
+};
+
+bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
+ int /*quality*/) {
+ SkBitmap::Config config = bitmap.getConfig();
+
+ const bool hasAlpha = !bitmap.isOpaque();
+ int colorType = PNG_COLOR_MASK_COLOR;
+ int bitDepth = 8; // default for color
+ png_color_8 sig_bit;
+
+ switch (config) {
+ case SkBitmap::kIndex8_Config:
+ colorType |= PNG_COLOR_MASK_PALETTE;
+ // fall through to the ARGB_8888 case
+ case SkBitmap::kARGB_8888_Config:
+ sig_bit.red = 8;
+ sig_bit.green = 8;
+ sig_bit.blue = 8;
+ sig_bit.alpha = 8;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ sig_bit.red = 4;
+ sig_bit.green = 4;
+ sig_bit.blue = 4;
+ sig_bit.alpha = 4;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ sig_bit.red = 5;
+ sig_bit.green = 6;
+ sig_bit.blue = 5;
+ sig_bit.alpha = 0;
+ break;
+ default:
+ return false;
+ }
+
+ if (hasAlpha) {
+ // don't specify alpha if we're a palette, even if our ctable has alpha
+ if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
+ colorType |= PNG_COLOR_MASK_ALPHA;
+ }
+ } else {
+ sig_bit.alpha = 0;
+ }
+
+ SkAutoLockPixels alp(bitmap);
+ // readyToDraw checks for pixels (and colortable if that is required)
+ if (!bitmap.readyToDraw()) {
+ return false;
+ }
+
+ // we must do this after we have locked the pixels
+ SkColorTable* ctable = bitmap.getColorTable();
+ if (NULL != ctable) {
+ if (ctable->count() == 0) {
+ return false;
+ }
+ // check if we can store in fewer than 8 bits
+ bitDepth = computeBitDepth(ctable->count());
+ }
+
+ return doEncode(stream, bitmap, hasAlpha, colorType,
+ bitDepth, config, sig_bit);
+}
+
+bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
+ const bool& hasAlpha, int colorType,
+ int bitDepth, SkBitmap::Config config,
+ png_color_8& sig_bit) {
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
+ NULL);
+ if (NULL == png_ptr) {
+ return false;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (NULL == info_ptr) {
+ png_destroy_write_struct(&png_ptr, png_infopp_NULL);
+ return false;
+ }
+
+ /* Set error handling. REQUIRED if you aren't supplying your own
+ * error handling functions in the png_create_write_struct() call.
+ */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return false;
+ }
+
+ png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
+
+ /* Set the image information here. Width and height are up to 2^31,
+ * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
+ * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
+ * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
+ * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
+ * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
+ * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
+ */
+
+ png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
+ bitDepth, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+
+ // set our colortable/trans arrays if needed
+ png_color paletteColors[256];
+ png_byte trans[256];
+ if (SkBitmap::kIndex8_Config == config) {
+ SkColorTable* ct = bitmap.getColorTable();
+ int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
+ png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
+ if (numTrans > 0) {
+ png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
+ }
+ }
+
+ png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+ png_write_info(png_ptr, info_ptr);
+
+ const char* srcImage = (const char*)bitmap.getPixels();
+ SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
+ char* storage = (char*)rowStorage.get();
+ transform_scanline_proc proc = choose_proc(config, hasAlpha);
+
+ for (int y = 0; y < bitmap.height(); y++) {
+ png_bytep row_ptr = (png_bytep)storage;
+ proc(srcImage, bitmap.width(), storage);
+ png_write_rows(png_ptr, &row_ptr, 1);
+ srcImage += bitmap.rowBytes();
+ }
+
+ png_write_end(png_ptr, info_ptr);
+
+ /* clean up after the write, and free any memory allocated */
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(PNGImageDecoder);
+DEFINE_ENCODER_CREATOR(PNGImageEncoder);
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+static bool is_png(SkStream* stream) {
+ char buf[PNG_BYTES_TO_CHECK];
+ if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
+ !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
+ return true;
+ }
+ return false;
+}
+
+SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
+ if (is_png(stream)) {
+ return SkNEW(SkPNGImageDecoder);
+ }
+ return NULL;
+}
+
+static SkImageDecoder::Format get_format_png(SkStream* stream) {
+ if (is_png(stream)) {
+ return SkImageDecoder::kPNG_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
+ return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
+}
+
+static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
+static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);
diff --git a/images/SkImageDecoder_libwebp.cpp b/images/SkImageDecoder_libwebp.cpp
new file mode 100644
index 00000000..4691d0d1
--- /dev/null
+++ b/images/SkImageDecoder_libwebp.cpp
@@ -0,0 +1,597 @@
+/*
+ * Copyright 2010, 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.
+ */
+
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkColorPriv.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+// A WebP decoder only, on top of (subset of) libwebp
+// For more information on WebP image format, and libwebp library, see:
+// http://code.google.com/speed/webp/
+// http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
+// http://review.webmproject.org/gitweb?p=libwebp.git
+
+#include <stdio.h>
+extern "C" {
+// If moving libwebp out of skia source tree, path for webp headers must be
+// updated accordingly. Here, we enforce using local copy in webp sub-directory.
+#include "webp/decode.h"
+#include "webp/encode.h"
+}
+
+#ifdef ANDROID
+#include <cutils/properties.h>
+
+// Key to lookup the size of memory buffer set in system property
+static const char KEY_MEM_CAP[] = "ro.media.dec.webp.memcap";
+#endif
+
+// this enables timing code to report milliseconds for a decode
+//#define TIME_DECODE
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+// Define VP8 I/O on top of Skia stream
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static const size_t WEBP_VP8_HEADER_SIZE = 64;
+static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16);
+
+// Parse headers of RIFF container, and check for valid Webp (VP8) content.
+static bool webp_parse_header(SkStream* stream, int* width, int* height, int* alpha) {
+ unsigned char buffer[WEBP_VP8_HEADER_SIZE];
+ const uint32_t contentSize = stream->getLength();
+ const size_t len = stream->read(buffer, WEBP_VP8_HEADER_SIZE);
+ const uint32_t read_bytes =
+ (contentSize < WEBP_VP8_HEADER_SIZE) ? contentSize : WEBP_VP8_HEADER_SIZE;
+ if (len != read_bytes) {
+ return false; // can't read enough
+ }
+
+ WebPBitstreamFeatures features;
+ VP8StatusCode status = WebPGetFeatures(buffer, read_bytes, &features);
+ if (VP8_STATUS_OK != status) {
+ return false; // Invalid WebP file.
+ }
+ *width = features.width;
+ *height = features.height;
+ *alpha = features.has_alpha;
+
+ // sanity check for image size that's about to be decoded.
+ {
+ Sk64 size;
+ size.setMul(*width, *height);
+ if (size.isNeg() || !size.is32()) {
+ return false;
+ }
+ // now check that if we are 4-bytes per pixel, we also don't overflow
+ if (size.get32() > (0x7FFFFFFF >> 2)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class SkWEBPImageDecoder: public SkImageDecoder {
+public:
+ SkWEBPImageDecoder() {
+ fInputStream = NULL;
+ fOrigWidth = 0;
+ fOrigHeight = 0;
+ fHasAlpha = 0;
+ }
+ virtual ~SkWEBPImageDecoder() {
+ SkSafeUnref(fInputStream);
+ }
+
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kWEBP_Format;
+ }
+
+protected:
+ virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
+ virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+ /**
+ * Called when determining the output config to request to webp.
+ * If the image does not have alpha, there is no need to premultiply.
+ * If the caller wants unpremultiplied colors, that is respected.
+ */
+ bool shouldPremultiply() const {
+ return SkToBool(fHasAlpha) && !this->getRequireUnpremultipliedColors();
+ }
+
+ bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height);
+
+ SkStream* fInputStream;
+ int fOrigWidth;
+ int fOrigHeight;
+ int fHasAlpha;
+
+ typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef TIME_DECODE
+
+#include "SkTime.h"
+
+class AutoTimeMillis {
+public:
+ AutoTimeMillis(const char label[]) :
+ fLabel(label) {
+ if (NULL == fLabel) {
+ fLabel = "";
+ }
+ fNow = SkTime::GetMSecs();
+ }
+ ~AutoTimeMillis() {
+ SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
+ }
+private:
+ const char* fLabel;
+ SkMSec fNow;
+};
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This guy exists just to aid in debugging, as it allows debuggers to just
+// set a break-point in one place to see all error exists.
+static bool return_false(const SkBitmap& bm, const char msg[]) {
+ SkDEBUGF(("libwebp error %s [%d %d]", msg, bm.width(), bm.height()));
+ return false; // must always return false
+}
+
+static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, bool premultiply) {
+ WEBP_CSP_MODE mode = MODE_LAST;
+ SkBitmap::Config config = decodedBitmap->config();
+
+ if (config == SkBitmap::kARGB_8888_Config) {
+ mode = premultiply ? MODE_rgbA : MODE_RGBA;
+ } else if (config == SkBitmap::kARGB_4444_Config) {
+ mode = premultiply ? MODE_rgbA_4444 : MODE_RGBA_4444;
+ } else if (config == SkBitmap::kRGB_565_Config) {
+ mode = MODE_RGB_565;
+ }
+ SkASSERT(MODE_LAST != mode);
+ return mode;
+}
+
+// Incremental WebP image decoding. Reads input buffer of 64K size iteratively
+// and decodes this block to appropriate color-space as per config object.
+static bool webp_idecode(SkStream* stream, WebPDecoderConfig* config) {
+ WebPIDecoder* idec = WebPIDecode(NULL, 0, config);
+ if (NULL == idec) {
+ WebPFreeDecBuffer(&config->output);
+ return false;
+ }
+
+ stream->rewind();
+ const uint32_t contentSize = stream->getLength();
+ const uint32_t readBufferSize = (contentSize < WEBP_IDECODE_BUFFER_SZ) ?
+ contentSize : WEBP_IDECODE_BUFFER_SZ;
+ SkAutoMalloc srcStorage(readBufferSize);
+ unsigned char* input = (uint8_t*)srcStorage.get();
+ if (NULL == input) {
+ WebPIDelete(idec);
+ WebPFreeDecBuffer(&config->output);
+ return false;
+ }
+
+ bool success = true;
+ VP8StatusCode status = VP8_STATUS_SUSPENDED;
+ do {
+ const size_t bytesRead = stream->read(input, readBufferSize);
+ if (0 == bytesRead) {
+ success = false;
+ break;
+ }
+
+ status = WebPIAppend(idec, input, bytesRead);
+ if (VP8_STATUS_OK != status && VP8_STATUS_SUSPENDED != status) {
+ success = false;
+ break;
+ }
+ } while (VP8_STATUS_OK != status);
+ srcStorage.free();
+ WebPIDelete(idec);
+ WebPFreeDecBuffer(&config->output);
+
+ return success;
+}
+
+static bool webp_get_config_resize(WebPDecoderConfig* config,
+ SkBitmap* decodedBitmap,
+ int width, int height, bool premultiply) {
+ WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, premultiply);
+ if (MODE_LAST == mode) {
+ return false;
+ }
+
+ if (0 == WebPInitDecoderConfig(config)) {
+ return false;
+ }
+
+ config->output.colorspace = mode;
+ config->output.u.RGBA.rgba = (uint8_t*)decodedBitmap->getPixels();
+ config->output.u.RGBA.stride = decodedBitmap->rowBytes();
+ config->output.u.RGBA.size = decodedBitmap->getSize();
+ config->output.is_external_memory = 1;
+
+ if (width != decodedBitmap->width() || height != decodedBitmap->height()) {
+ config->options.use_scaling = 1;
+ config->options.scaled_width = decodedBitmap->width();
+ config->options.scaled_height = decodedBitmap->height();
+ }
+
+ return true;
+}
+
+static bool webp_get_config_resize_crop(WebPDecoderConfig* config,
+ SkBitmap* decodedBitmap,
+ const SkIRect& region, bool premultiply) {
+
+ if (!webp_get_config_resize(config, decodedBitmap, region.width(),
+ region.height(), premultiply)) {
+ return false;
+ }
+
+ config->options.use_cropping = 1;
+ config->options.crop_left = region.fLeft;
+ config->options.crop_top = region.fTop;
+ config->options.crop_width = region.width();
+ config->options.crop_height = region.height();
+
+ return true;
+}
+
+bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap,
+ int width, int height) {
+ SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, SkToBool(fHasAlpha));
+
+ // YUV converter supports output in RGB565, RGBA4444 and RGBA8888 formats.
+ if (fHasAlpha) {
+ if (config != SkBitmap::kARGB_4444_Config) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+ } else {
+ if (config != SkBitmap::kRGB_565_Config &&
+ config != SkBitmap::kARGB_4444_Config) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+ }
+
+ if (!this->chooseFromOneChoice(config, width, height)) {
+ return false;
+ }
+
+ decodedBitmap->setConfig(config, width, height, 0);
+
+ decodedBitmap->setIsOpaque(!fHasAlpha);
+
+ return true;
+}
+
+bool SkWEBPImageDecoder::onBuildTileIndex(SkStream* stream,
+ int *width, int *height) {
+ int origWidth, origHeight, hasAlpha;
+ if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
+ return false;
+ }
+
+ stream->rewind();
+ *width = origWidth;
+ *height = origHeight;
+
+ SkRefCnt_SafeAssign(this->fInputStream, stream);
+ this->fOrigWidth = origWidth;
+ this->fOrigHeight = origHeight;
+ this->fHasAlpha = hasAlpha;
+
+ return true;
+}
+
+static bool is_config_compatible(const SkBitmap& bitmap) {
+ SkBitmap::Config config = bitmap.config();
+ return config == SkBitmap::kARGB_4444_Config ||
+ config == SkBitmap::kRGB_565_Config ||
+ config == SkBitmap::kARGB_8888_Config;
+}
+
+bool SkWEBPImageDecoder::onDecodeSubset(SkBitmap* decodedBitmap,
+ const SkIRect& region) {
+ SkIRect rect = SkIRect::MakeWH(fOrigWidth, fOrigHeight);
+
+ if (!rect.intersect(region)) {
+ // If the requested region is entirely outsides the image, return false
+ return false;
+ }
+
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(rect.width(), rect.height(), sampleSize);
+ const int width = sampler.scaledWidth();
+ const int height = sampler.scaledHeight();
+
+ // The image can be decoded directly to decodedBitmap if
+ // 1. the region is within the image range
+ // 2. bitmap's config is compatible
+ // 3. bitmap's size is same as the required region (after sampled)
+ bool directDecode = (rect == region) &&
+ (decodedBitmap->isNull() ||
+ (is_config_compatible(*decodedBitmap) &&
+ (decodedBitmap->width() == width) &&
+ (decodedBitmap->height() == height)));
+
+ SkBitmap tmpBitmap;
+ SkBitmap *bitmap = decodedBitmap;
+
+ if (!directDecode) {
+ bitmap = &tmpBitmap;
+ }
+
+ if (bitmap->isNull()) {
+ if (!setDecodeConfig(bitmap, width, height)) {
+ return false;
+ }
+ // alloc from native heap if it is a temp bitmap. (prevent GC)
+ bool allocResult = (bitmap == decodedBitmap)
+ ? allocPixelRef(bitmap, NULL)
+ : bitmap->allocPixels();
+ if (!allocResult) {
+ return return_false(*decodedBitmap, "allocPixelRef");
+ }
+ } else {
+ // This is also called in setDecodeConfig in above block.
+ // i.e., when bitmap->isNull() is true.
+ if (!chooseFromOneChoice(bitmap->config(), width, height)) {
+ return false;
+ }
+ }
+
+ SkAutoLockPixels alp(*bitmap);
+ WebPDecoderConfig config;
+ if (!webp_get_config_resize_crop(&config, bitmap, rect,
+ this->shouldPremultiply())) {
+ return false;
+ }
+
+ // Decode the WebP image data stream using WebP incremental decoding for
+ // the specified cropped image-region.
+ if (!webp_idecode(this->fInputStream, &config)) {
+ return false;
+ }
+
+ if (!directDecode) {
+ cropBitmap(decodedBitmap, bitmap, sampleSize, region.x(), region.y(),
+ region.width(), region.height(), rect.x(), rect.y());
+ }
+ return true;
+}
+
+bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+ Mode mode) {
+#ifdef TIME_DECODE
+ AutoTimeMillis atm("WEBP Decode");
+#endif
+
+ int origWidth, origHeight, hasAlpha;
+ if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
+ return false;
+ }
+ this->fHasAlpha = hasAlpha;
+
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+ if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(),
+ sampler.scaledHeight())) {
+ return false;
+ }
+
+ // If only bounds are requested, done
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ if (!this->allocPixelRef(decodedBitmap, NULL)) {
+ return return_false(*decodedBitmap, "allocPixelRef");
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ WebPDecoderConfig config;
+ if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight,
+ this->shouldPremultiply())) {
+ return false;
+ }
+
+ // Decode the WebP image data stream using WebP incremental decoding.
+ return webp_idecode(stream, &config);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* out, int width,
+ const SkPMColor* SK_RESTRICT ctable);
+
+static void ARGB_8888_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
+ const SkPMColor*) {
+ const uint32_t* SK_RESTRICT src = (const uint32_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint32_t c = *src++;
+ rgb[0] = SkGetPackedR32(c);
+ rgb[1] = SkGetPackedG32(c);
+ rgb[2] = SkGetPackedB32(c);
+ rgb += 3;
+ }
+}
+
+static void RGB_565_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
+ const SkPMColor*) {
+ const uint16_t* SK_RESTRICT src = (const uint16_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint16_t c = *src++;
+ rgb[0] = SkPacked16ToR32(c);
+ rgb[1] = SkPacked16ToG32(c);
+ rgb[2] = SkPacked16ToB32(c);
+ rgb += 3;
+ }
+}
+
+static void ARGB_4444_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
+ const SkPMColor*) {
+ const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)in;
+ for (int i = 0; i < width; ++i) {
+ const SkPMColor16 c = *src++;
+ rgb[0] = SkPacked4444ToR32(c);
+ rgb[1] = SkPacked4444ToG32(c);
+ rgb[2] = SkPacked4444ToB32(c);
+ rgb += 3;
+ }
+}
+
+static void Index8_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
+ const SkPMColor* SK_RESTRICT ctable) {
+ const uint8_t* SK_RESTRICT src = (const uint8_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint32_t c = ctable[*src++];
+ rgb[0] = SkGetPackedR32(c);
+ rgb[1] = SkGetPackedG32(c);
+ rgb[2] = SkGetPackedB32(c);
+ rgb += 3;
+ }
+}
+
+static ScanlineImporter ChooseImporter(const SkBitmap::Config& config) {
+ switch (config) {
+ case SkBitmap::kARGB_8888_Config:
+ return ARGB_8888_To_RGB;
+ case SkBitmap::kRGB_565_Config:
+ return RGB_565_To_RGB;
+ case SkBitmap::kARGB_4444_Config:
+ return ARGB_4444_To_RGB;
+ case SkBitmap::kIndex8_Config:
+ return Index8_To_RGB;
+ default:
+ return NULL;
+ }
+}
+
+static int stream_writer(const uint8_t* data, size_t data_size,
+ const WebPPicture* const picture) {
+ SkWStream* const stream = (SkWStream*)picture->custom_ptr;
+ return stream->write(data, data_size) ? 1 : 0;
+}
+
+class SkWEBPImageEncoder : public SkImageEncoder {
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
+
+private:
+ typedef SkImageEncoder INHERITED;
+};
+
+bool SkWEBPImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bm,
+ int quality) {
+ const SkBitmap::Config config = bm.getConfig();
+ const ScanlineImporter scanline_import = ChooseImporter(config);
+ if (NULL == scanline_import) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(bm);
+ SkAutoLockColors ctLocker;
+ if (NULL == bm.getPixels()) {
+ return false;
+ }
+
+ WebPConfig webp_config;
+ if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, (float) quality)) {
+ return false;
+ }
+
+ WebPPicture pic;
+ WebPPictureInit(&pic);
+ pic.width = bm.width();
+ pic.height = bm.height();
+ pic.writer = stream_writer;
+ pic.custom_ptr = (void*)stream;
+
+ const SkPMColor* colors = ctLocker.lockColors(bm);
+ const uint8_t* src = (uint8_t*)bm.getPixels();
+ const int rgbStride = pic.width * 3;
+
+ // Import (for each scanline) the bit-map image (in appropriate color-space)
+ // to RGB color space.
+ uint8_t* rgb = new uint8_t[rgbStride * pic.height];
+ for (int y = 0; y < pic.height; ++y) {
+ scanline_import(src + y * bm.rowBytes(), rgb + y * rgbStride,
+ pic.width, colors);
+ }
+
+ bool ok = SkToBool(WebPPictureImportRGB(&pic, rgb, rgbStride));
+ delete[] rgb;
+
+ ok = ok && WebPEncode(&webp_config, &pic);
+ WebPPictureFree(&pic);
+
+ return ok;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WEBPImageDecoder);
+DEFINE_ENCODER_CREATOR(WEBPImageEncoder);
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+static SkImageDecoder* sk_libwebp_dfactory(SkStream* stream) {
+ int width, height, hasAlpha;
+ if (!webp_parse_header(stream, &width, &height, &hasAlpha)) {
+ return NULL;
+ }
+
+ // Magic matches, call decoder
+ return SkNEW(SkWEBPImageDecoder);
+}
+
+static SkImageDecoder::Format get_format_webp(SkStream* stream) {
+ int width, height, hasAlpha;
+ if (webp_parse_header(stream, &width, &height, &hasAlpha)) {
+ return SkImageDecoder::kWEBP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) {
+ return (SkImageEncoder::kWEBP_Type == t) ? SkNEW(SkWEBPImageEncoder) : NULL;
+}
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libwebp_dfactory);
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_webp);
+static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libwebp_efactory);
diff --git a/images/SkImageDecoder_wbmp.cpp b/images/SkImageDecoder_wbmp.cpp
new file mode 100644
index 00000000..8b1659ba
--- /dev/null
+++ b/images/SkImageDecoder_wbmp.cpp
@@ -0,0 +1,175 @@
+
+/*
+ * 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 "SkImageDecoder.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkMath.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+class SkWBMPImageDecoder : public SkImageDecoder {
+public:
+ virtual Format getFormat() const SK_OVERRIDE {
+ return kWBMP_Format;
+ }
+
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+static bool read_byte(SkStream* stream, uint8_t* data)
+{
+ return stream->read(data, 1) == 1;
+}
+
+static bool read_mbf(SkStream* stream, int* value)
+{
+ int n = 0;
+ uint8_t data;
+ do {
+ if (!read_byte(stream, &data)) {
+ return false;
+ }
+ n = (n << 7) | (data & 0x7F);
+ } while (data & 0x80);
+
+ *value = n;
+ return true;
+}
+
+struct wbmp_head {
+ int fWidth;
+ int fHeight;
+
+ bool init(SkStream* stream)
+ {
+ uint8_t data;
+
+ if (!read_byte(stream, &data) || data != 0) { // unknown type
+ return false;
+ }
+ if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header
+ return false;
+ }
+ if (!read_mbf(stream, &fWidth) || (unsigned)fWidth > 0xFFFF) {
+ return false;
+ }
+ if (!read_mbf(stream, &fHeight) || (unsigned)fHeight > 0xFFFF) {
+ return false;
+ }
+ return fWidth != 0 && fHeight != 0;
+ }
+};
+
+static void expand_bits_to_bytes(uint8_t dst[], const uint8_t src[], int bits)
+{
+ int bytes = bits >> 3;
+
+ for (int i = 0; i < bytes; i++) {
+ unsigned mask = *src++;
+ dst[0] = (mask >> 7) & 1;
+ dst[1] = (mask >> 6) & 1;
+ dst[2] = (mask >> 5) & 1;
+ dst[3] = (mask >> 4) & 1;
+ dst[4] = (mask >> 3) & 1;
+ dst[5] = (mask >> 2) & 1;
+ dst[6] = (mask >> 1) & 1;
+ dst[7] = (mask >> 0) & 1;
+ dst += 8;
+ }
+
+ bits &= 7;
+ if (bits > 0) {
+ unsigned mask = *src;
+ do {
+ *dst++ = (mask >> 7) & 1;;
+ mask <<= 1;
+ } while (--bits != 0);
+ }
+}
+
+bool SkWBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+ Mode mode)
+{
+ wbmp_head head;
+
+ if (!head.init(stream)) {
+ return false;
+ }
+
+ int width = head.fWidth;
+ int height = head.fHeight;
+
+ decodedBitmap->setConfig(SkBitmap::kIndex8_Config, width, height);
+ decodedBitmap->setIsOpaque(true);
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ const SkPMColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
+ SkColorTable* ct = SkNEW_ARGS(SkColorTable, (colors, 2));
+ SkAutoUnref aur(ct);
+
+ if (!this->allocPixelRef(decodedBitmap, ct)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ uint8_t* dst = decodedBitmap->getAddr8(0, 0);
+ // store the 1-bit valuess at the end of our pixels, so we won't stomp
+ // on them before we're read them. Just trying to avoid a temp allocation
+ size_t srcRB = SkAlign8(width) >> 3;
+ size_t srcSize = height * srcRB;
+ uint8_t* src = dst + decodedBitmap->getSize() - srcSize;
+ if (stream->read(src, srcSize) != srcSize) {
+ return false;
+ }
+
+ for (int y = 0; y < height; y++)
+ {
+ expand_bits_to_bytes(dst, src, width);
+ dst += decodedBitmap->rowBytes();
+ src += srcRB;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WBMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+static SkImageDecoder* sk_wbmp_dfactory(SkStream* stream) {
+ wbmp_head head;
+
+ if (head.init(stream)) {
+ return SkNEW(SkWBMPImageDecoder);
+ }
+ return NULL;
+}
+
+static SkImageDecoder::Format get_format_wbmp(SkStream* stream) {
+ wbmp_head head;
+ if (head.init(stream)) {
+ return SkImageDecoder::kWBMP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkTRegistry<SkImageDecoder*, SkStream*> gReg(sk_wbmp_dfactory);
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_wbmp);
diff --git a/images/SkImageEncoder.cpp b/images/SkImageEncoder.cpp
new file mode 100644
index 00000000..31fdcf96
--- /dev/null
+++ b/images/SkImageEncoder.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2009 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 "SkImageEncoder.h"
+#include "SkBitmap.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+SkImageEncoder::~SkImageEncoder() {}
+
+bool SkImageEncoder::encodeStream(SkWStream* stream, const SkBitmap& bm,
+ int quality) {
+ quality = SkMin32(100, SkMax32(0, quality));
+ return this->onEncode(stream, bm, quality);
+}
+
+bool SkImageEncoder::encodeFile(const char file[], const SkBitmap& bm,
+ int quality) {
+ quality = SkMin32(100, SkMax32(0, quality));
+ SkFILEWStream stream(file);
+ return this->onEncode(&stream, bm, quality);
+}
+
+SkData* SkImageEncoder::encodeData(const SkBitmap& bm, int quality) {
+ SkDynamicMemoryWStream stream;
+ quality = SkMin32(100, SkMax32(0, quality));
+ if (this->onEncode(&stream, bm, quality)) {
+ return stream.copyToData();
+ }
+ return NULL;
+}
+
+bool SkImageEncoder::EncodeFile(const char file[], const SkBitmap& bm, Type t,
+ int quality) {
+ SkAutoTDelete<SkImageEncoder> enc(SkImageEncoder::Create(t));
+ return enc.get() && enc.get()->encodeFile(file, bm, quality);
+}
+
+bool SkImageEncoder::EncodeStream(SkWStream* stream, const SkBitmap& bm, Type t,
+ int quality) {
+ SkAutoTDelete<SkImageEncoder> enc(SkImageEncoder::Create(t));
+ return enc.get() && enc.get()->encodeStream(stream, bm, quality);
+}
+
+SkData* SkImageEncoder::EncodeData(const SkBitmap& bm, Type t, int quality) {
+ SkAutoTDelete<SkImageEncoder> enc(SkImageEncoder::Create(t));
+ return enc.get() ? enc.get()->encodeData(bm, quality) : NULL;
+}
diff --git a/images/SkImageEncoder_Factory.cpp b/images/SkImageEncoder_Factory.cpp
new file mode 100644
index 00000000..10f8d64d
--- /dev/null
+++ b/images/SkImageEncoder_Factory.cpp
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright 2009 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 "SkImageEncoder.h"
+#include "SkTRegistry.h"
+
+typedef SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> EncodeReg;
+
+// Can't use the typedef here because of complex C++ corner cases
+template EncodeReg* SkTRegistry<SkImageEncoder*, SkImageEncoder::Type>::gHead;
+
+SkImageEncoder* SkImageEncoder::Create(Type t) {
+ SkImageEncoder* codec = NULL;
+ const EncodeReg* curr = EncodeReg::Head();
+ while (curr) {
+ if ((codec = curr->factory()(t)) != NULL) {
+ return codec;
+ }
+ curr = curr->next();
+ }
+ return NULL;
+}
diff --git a/images/SkImageEncoder_argb.cpp b/images/SkImageEncoder_argb.cpp
new file mode 100644
index 00000000..5abc23ce
--- /dev/null
+++ b/images/SkImageEncoder_argb.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkImageEncoder.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+class SkARGBImageEncoder : public SkImageEncoder {
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
+
+private:
+ typedef SkImageEncoder INHERITED;
+};
+
+typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* argb, int width,
+ const SkPMColor* SK_RESTRICT ctable);
+
+static void ARGB_8888_To_ARGB(const uint8_t* in, uint8_t* argb, int width, const SkPMColor*) {
+ const uint32_t* SK_RESTRICT src = (const uint32_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint32_t c = *src++;
+ argb[0] = SkGetPackedA32(c);
+ argb[1] = SkGetPackedR32(c);
+ argb[2] = SkGetPackedG32(c);
+ argb[3] = SkGetPackedB32(c);
+ argb += 4;
+ }
+}
+
+static void RGB_565_To_ARGB(const uint8_t* in, uint8_t* argb, int width, const SkPMColor*) {
+ const uint16_t* SK_RESTRICT src = (const uint16_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint16_t c = *src++;
+ argb[0] = 0xFF;
+ argb[1] = SkPacked16ToR32(c);
+ argb[2] = SkPacked16ToG32(c);
+ argb[3] = SkPacked16ToB32(c);
+ argb += 4;
+ }
+}
+
+static void ARGB_4444_To_ARGB(const uint8_t* in, uint8_t* argb, int width, const SkPMColor*) {
+ const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)in;
+ for (int i = 0; i < width; ++i) {
+ const SkPMColor16 c = *src++;
+ argb[0] = SkPacked4444ToA32(c);
+ argb[1] = SkPacked4444ToR32(c);
+ argb[2] = SkPacked4444ToG32(c);
+ argb[3] = SkPacked4444ToB32(c);
+ argb += 4;
+ }
+}
+
+static void Index8_To_ARGB(const uint8_t* in, uint8_t* argb, int width,
+ const SkPMColor* SK_RESTRICT ctable) {
+ const uint8_t* SK_RESTRICT src = (const uint8_t*)in;
+ for (int i = 0; i < width; ++i) {
+ const uint32_t c = ctable[*src++];
+ argb[0] = SkGetPackedA32(c);
+ argb[1] = SkGetPackedR32(c);
+ argb[2] = SkGetPackedG32(c);
+ argb[3] = SkGetPackedB32(c);
+ argb += 4;
+ }
+}
+
+static ScanlineImporter ChooseImporter(const SkBitmap::Config& config) {
+ switch (config) {
+ case SkBitmap::kARGB_8888_Config:
+ return ARGB_8888_To_ARGB;
+ case SkBitmap::kRGB_565_Config:
+ return RGB_565_To_ARGB;
+ case SkBitmap::kARGB_4444_Config:
+ return ARGB_4444_To_ARGB;
+ case SkBitmap::kIndex8_Config:
+ return Index8_To_ARGB;
+ default:
+ return NULL;
+ }
+}
+
+bool SkARGBImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int) {
+ const SkBitmap::Config config = bitmap.getConfig();
+ const ScanlineImporter scanline_import = ChooseImporter(config);
+ if (NULL == scanline_import) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(bitmap);
+ const uint8_t* src = (uint8_t*)bitmap.getPixels();
+ if (NULL == bitmap.getPixels()) {
+ return false;
+ }
+
+ SkAutoLockColors ctLocker;
+ const SkPMColor* colors = ctLocker.lockColors(bitmap);
+
+ const int argbStride = bitmap.width() * 4;
+ SkAutoTDeleteArray<uint8_t> ada(new uint8_t[argbStride]);
+ uint8_t* argb = ada.get();
+ for (int y = 0; y < bitmap.height(); ++y) {
+ scanline_import(src + y * bitmap.rowBytes(), argb, bitmap.width(), colors);
+ stream->write(argb, argbStride);
+ }
+
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_ENCODER_CREATOR(ARGBImageEncoder);
+///////////////////////////////////////////////////////////////////////////////
diff --git a/images/SkImageRef.cpp b/images/SkImageRef.cpp
new file mode 100644
index 00000000..299166c3
--- /dev/null
+++ b/images/SkImageRef.cpp
@@ -0,0 +1,189 @@
+
+/*
+ * 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 "SkImageRef.h"
+#include "SkBitmap.h"
+#include "SkFlattenableBuffers.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+
+//#define DUMP_IMAGEREF_LIFECYCLE
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImageRef::SkImageRef(SkStream* stream, SkBitmap::Config config,
+ int sampleSize, SkBaseMutex* mutex)
+ : SkPixelRef(mutex), fErrorInDecoding(false) {
+ SkASSERT(stream);
+ stream->ref();
+ fStream = stream;
+ fConfig = config;
+ fSampleSize = sampleSize;
+ fDoDither = true;
+ fPrev = fNext = NULL;
+ fFactory = NULL;
+
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ SkDebugf("add ImageRef %p [%d] data=%d\n",
+ this, config, (int)stream->getLength());
+#endif
+}
+
+SkImageRef::~SkImageRef() {
+
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ SkDebugf("delete ImageRef %p [%d] data=%d\n",
+ this, fConfig, (int)fStream->getLength());
+#endif
+
+ fStream->unref();
+ SkSafeUnref(fFactory);
+}
+
+bool SkImageRef::getInfo(SkBitmap* bitmap) {
+ SkAutoMutexAcquire ac(this->mutex());
+
+ if (!this->prepareBitmap(SkImageDecoder::kDecodeBounds_Mode)) {
+ return false;
+ }
+
+ SkASSERT(SkBitmap::kNo_Config != fBitmap.config());
+ if (bitmap) {
+ bitmap->setConfig(fBitmap.config(), fBitmap.width(), fBitmap.height());
+ }
+ return true;
+}
+
+bool SkImageRef::isOpaque(SkBitmap* bitmap) {
+ if (bitmap && bitmap->pixelRef() == this) {
+ bitmap->lockPixels();
+ bitmap->setIsOpaque(fBitmap.isOpaque());
+ bitmap->unlockPixels();
+ return true;
+ }
+ return false;
+}
+
+SkImageDecoderFactory* SkImageRef::setDecoderFactory(
+ SkImageDecoderFactory* fact) {
+ SkRefCnt_SafeAssign(fFactory, fact);
+ return fact;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkImageRef::onDecode(SkImageDecoder* codec, SkStream* stream,
+ SkBitmap* bitmap, SkBitmap::Config config,
+ SkImageDecoder::Mode mode) {
+ return codec->decode(stream, bitmap, config, mode);
+}
+
+bool SkImageRef::prepareBitmap(SkImageDecoder::Mode mode) {
+
+ if (fErrorInDecoding) {
+ return false;
+ }
+
+ /* As soon as we really know our config, we record it, so that on
+ subsequent calls to the codec, we are sure we will always get the same
+ result.
+ */
+ if (SkBitmap::kNo_Config != fBitmap.config()) {
+ fConfig = fBitmap.config();
+ }
+
+ if (NULL != fBitmap.getPixels() ||
+ (SkBitmap::kNo_Config != fBitmap.config() &&
+ SkImageDecoder::kDecodeBounds_Mode == mode)) {
+ return true;
+ }
+
+ SkASSERT(fBitmap.getPixels() == NULL);
+
+ fStream->rewind();
+
+ SkImageDecoder* codec;
+ if (fFactory) {
+ codec = fFactory->newDecoder(fStream);
+ } else {
+ codec = SkImageDecoder::Factory(fStream);
+ }
+
+ if (codec) {
+ SkAutoTDelete<SkImageDecoder> ad(codec);
+
+ codec->setSampleSize(fSampleSize);
+ codec->setDitherImage(fDoDither);
+ if (this->onDecode(codec, fStream, &fBitmap, fConfig, mode)) {
+ return true;
+ }
+ }
+
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ if (NULL == codec) {
+ SkDebugf("--- ImageRef: <%s> failed to find codec\n", this->getURI());
+ } else {
+ SkDebugf("--- ImageRef: <%s> failed in codec for %d mode\n",
+ this->getURI(), mode);
+ }
+#endif
+ fErrorInDecoding = true;
+ fBitmap.reset();
+ return false;
+}
+
+void* SkImageRef::onLockPixels(SkColorTable** ct) {
+ if (NULL == fBitmap.getPixels()) {
+ (void)this->prepareBitmap(SkImageDecoder::kDecodePixels_Mode);
+ }
+
+ if (ct) {
+ *ct = fBitmap.getColorTable();
+ }
+ return fBitmap.getPixels();
+}
+
+size_t SkImageRef::ramUsed() const {
+ size_t size = 0;
+
+ if (fBitmap.getPixels()) {
+ size = fBitmap.getSize();
+ if (fBitmap.getColorTable()) {
+ size += fBitmap.getColorTable()->count() * sizeof(SkPMColor);
+ }
+ }
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkImageRef::SkImageRef(SkFlattenableReadBuffer& buffer, SkBaseMutex* mutex)
+ : INHERITED(buffer, mutex), fErrorInDecoding(false) {
+ fConfig = (SkBitmap::Config)buffer.readUInt();
+ fSampleSize = buffer.readInt();
+ fDoDither = buffer.readBool();
+
+ size_t length = buffer.getArrayCount();
+ fStream = SkNEW_ARGS(SkMemoryStream, (length));
+ buffer.readByteArray((void*)fStream->getMemoryBase());
+
+ fPrev = fNext = NULL;
+ fFactory = NULL;
+}
+
+void SkImageRef::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeUInt(fConfig);
+ buffer.writeInt(fSampleSize);
+ buffer.writeBool(fDoDither);
+ fStream->rewind();
+ buffer.writeStream(fStream, fStream->getLength());
+}
diff --git a/images/SkImageRefPool.cpp b/images/SkImageRefPool.cpp
new file mode 100644
index 00000000..0a3d7bf8
--- /dev/null
+++ b/images/SkImageRefPool.cpp
@@ -0,0 +1,192 @@
+
+/*
+ * 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 "SkImageRefPool.h"
+#include "SkImageRef.h"
+#include "SkThread.h"
+
+SkImageRefPool::SkImageRefPool() {
+ fRAMBudget = 0; // means no explicit limit
+ fRAMUsed = 0;
+ fCount = 0;
+ fHead = fTail = NULL;
+}
+
+SkImageRefPool::~SkImageRefPool() {
+ // SkASSERT(NULL == fHead);
+}
+
+void SkImageRefPool::setRAMBudget(size_t size) {
+ if (fRAMBudget != size) {
+ fRAMBudget = size;
+ this->purgeIfNeeded();
+ }
+}
+
+void SkImageRefPool::justAddedPixels(SkImageRef* ref) {
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ SkDebugf("=== ImagePool: add pixels %s [%d %d %d] bytes=%d heap=%d\n",
+ ref->getURI(),
+ ref->fBitmap.width(), ref->fBitmap.height(),
+ ref->fBitmap.bytesPerPixel(),
+ ref->fBitmap.getSize(), (int)fRAMUsed);
+#endif
+ fRAMUsed += ref->ramUsed();
+ this->purgeIfNeeded();
+}
+
+void SkImageRefPool::canLosePixels(SkImageRef* ref) {
+ // the refs near fHead have recently been released (used)
+ // if we purge, we purge from the tail
+ this->detach(ref);
+ this->addToHead(ref);
+ this->purgeIfNeeded();
+}
+
+void SkImageRefPool::purgeIfNeeded() {
+ // do nothing if we have a zero-budget (i.e. unlimited)
+ if (fRAMBudget != 0) {
+ this->setRAMUsed(fRAMBudget);
+ }
+}
+
+void SkImageRefPool::setRAMUsed(size_t limit) {
+ SkImageRef* ref = fTail;
+
+ while (NULL != ref && fRAMUsed > limit) {
+ // only purge it if its pixels are unlocked
+ if (!ref->isLocked() && ref->fBitmap.getPixels()) {
+ size_t size = ref->ramUsed();
+ SkASSERT(size <= fRAMUsed);
+ fRAMUsed -= size;
+
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ SkDebugf("=== ImagePool: purge %s [%d %d %d] bytes=%d heap=%d\n",
+ ref->getURI(),
+ ref->fBitmap.width(), ref->fBitmap.height(),
+ ref->fBitmap.bytesPerPixel(),
+ (int)size, (int)fRAMUsed);
+#endif
+
+ // remember the bitmap config (don't call reset),
+ // just clear the pixel memory
+ ref->fBitmap.setPixels(NULL);
+ SkASSERT(NULL == ref->fBitmap.getPixels());
+ }
+ ref = ref->fPrev;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkImageRefPool::addToHead(SkImageRef* ref) {
+ ref->fNext = fHead;
+ ref->fPrev = NULL;
+
+ if (fHead) {
+ SkASSERT(NULL == fHead->fPrev);
+ fHead->fPrev = ref;
+ }
+ fHead = ref;
+
+ if (NULL == fTail) {
+ fTail = ref;
+ }
+ fCount += 1;
+ SkASSERT(computeCount() == fCount);
+
+ fRAMUsed += ref->ramUsed();
+}
+
+void SkImageRefPool::addToTail(SkImageRef* ref) {
+ ref->fNext = NULL;
+ ref->fPrev = fTail;
+
+ if (fTail) {
+ SkASSERT(NULL == fTail->fNext);
+ fTail->fNext = ref;
+ }
+ fTail = ref;
+
+ if (NULL == fHead) {
+ fHead = ref;
+ }
+ fCount += 1;
+ SkASSERT(computeCount() == fCount);
+
+ fRAMUsed += ref->ramUsed();
+}
+
+void SkImageRefPool::detach(SkImageRef* ref) {
+ SkASSERT(fCount > 0);
+
+ if (fHead == ref) {
+ fHead = ref->fNext;
+ }
+ if (fTail == ref) {
+ fTail = ref->fPrev;
+ }
+ if (ref->fPrev) {
+ ref->fPrev->fNext = ref->fNext;
+ }
+ if (ref->fNext) {
+ ref->fNext->fPrev = ref->fPrev;
+ }
+
+ ref->fNext = ref->fPrev = NULL;
+
+ fCount -= 1;
+ SkASSERT(computeCount() == fCount);
+
+ SkASSERT(fRAMUsed >= ref->ramUsed());
+ fRAMUsed -= ref->ramUsed();
+}
+
+int SkImageRefPool::computeCount() const {
+ SkImageRef* ref = fHead;
+ int count = 0;
+
+ while (ref != NULL) {
+ count += 1;
+ ref = ref->fNext;
+ }
+
+#ifdef SK_DEBUG
+ ref = fTail;
+ int count2 = 0;
+
+ while (ref != NULL) {
+ count2 += 1;
+ ref = ref->fPrev;
+ }
+ SkASSERT(count2 == count);
+#endif
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkStream.h"
+
+void SkImageRefPool::dump() const {
+#if defined(SK_DEBUG) || defined(DUMP_IMAGEREF_LIFECYCLE)
+ SkDebugf("ImagePool dump: bugdet: %d used: %d count: %d\n",
+ (int)fRAMBudget, (int)fRAMUsed, fCount);
+
+ SkImageRef* ref = fHead;
+
+ while (ref != NULL) {
+ SkDebugf(" [%3d %3d %d] ram=%d data=%d locked=%d %s\n", ref->fBitmap.width(),
+ ref->fBitmap.height(), ref->fBitmap.config(),
+ ref->ramUsed(), (int)ref->fStream->getLength(),
+ ref->isLocked(), ref->getURI());
+
+ ref = ref->fNext;
+ }
+#endif
+}
diff --git a/images/SkImageRefPool.h b/images/SkImageRefPool.h
new file mode 100644
index 00000000..1e74a6d0
--- /dev/null
+++ b/images/SkImageRefPool.h
@@ -0,0 +1,49 @@
+
+/*
+ * 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 SkImageRefPool_DEFINED
+#define SkImageRefPool_DEFINED
+
+#include "SkTypes.h"
+
+class SkImageRef;
+class SkImageRef_GlobalPool;
+
+class SkImageRefPool {
+public:
+ SkImageRefPool();
+ ~SkImageRefPool();
+
+ size_t getRAMBudget() const { return fRAMBudget; }
+ void setRAMBudget(size_t);
+
+ size_t getRAMUsed() const { return fRAMUsed; }
+ void setRAMUsed(size_t limit);
+
+ void addToHead(SkImageRef*);
+ void addToTail(SkImageRef*);
+ void detach(SkImageRef*);
+
+ void dump() const;
+
+private:
+ size_t fRAMBudget;
+ size_t fRAMUsed;
+
+ int fCount;
+ SkImageRef* fHead, *fTail;
+
+ int computeCount() const;
+
+ friend class SkImageRef_GlobalPool;
+
+ void justAddedPixels(SkImageRef*);
+ void canLosePixels(SkImageRef*);
+ void purgeIfNeeded();
+};
+
+#endif
diff --git a/images/SkImageRef_GlobalPool.cpp b/images/SkImageRef_GlobalPool.cpp
new file mode 100644
index 00000000..6af86532
--- /dev/null
+++ b/images/SkImageRef_GlobalPool.cpp
@@ -0,0 +1,100 @@
+
+/*
+ * 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 "SkImageRef_GlobalPool.h"
+#include "SkImageRefPool.h"
+#include "SkThread.h"
+
+SK_DECLARE_STATIC_MUTEX(gGlobalPoolMutex);
+
+/*
+ * This returns the lazily-allocated global pool. It must be called
+ * from inside the guard mutex, so we safely only ever allocate 1.
+ */
+static SkImageRefPool* GetGlobalPool() {
+ static SkImageRefPool* gPool;
+ if (NULL == gPool) {
+ gPool = SkNEW(SkImageRefPool);
+ // call sk_atexit(...) when we have that, to free the global pool
+ }
+ return gPool;
+}
+
+SkImageRef_GlobalPool::SkImageRef_GlobalPool(SkStream* stream,
+ SkBitmap::Config config,
+ int sampleSize)
+ : SkImageRef(stream, config, sampleSize, &gGlobalPoolMutex) {
+ SkASSERT(&gGlobalPoolMutex == this->mutex());
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->addToHead(this);
+}
+
+SkImageRef_GlobalPool::~SkImageRef_GlobalPool() {
+ SkASSERT(&gGlobalPoolMutex == this->mutex());
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->detach(this);
+}
+
+/* By design, onUnlockPixels() already is inside the mutex-lock,
+ * and it is the (indirect) caller of onDecode(), therefore we can assume
+ * that we also are already inside the mutex. Hence, we can reference
+ * the global-pool directly.
+ */
+bool SkImageRef_GlobalPool::onDecode(SkImageDecoder* codec, SkStream* stream,
+ SkBitmap* bitmap, SkBitmap::Config config,
+ SkImageDecoder::Mode mode) {
+ if (!this->INHERITED::onDecode(codec, stream, bitmap, config, mode)) {
+ return false;
+ }
+ if (mode == SkImageDecoder::kDecodePixels_Mode) {
+ // no need to grab the mutex here, it has already been acquired.
+ GetGlobalPool()->justAddedPixels(this);
+ }
+ return true;
+}
+
+void SkImageRef_GlobalPool::onUnlockPixels() {
+ this->INHERITED::onUnlockPixels();
+
+ // by design, onUnlockPixels() already is inside the mutex-lock
+ GetGlobalPool()->canLosePixels(this);
+}
+
+SkImageRef_GlobalPool::SkImageRef_GlobalPool(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer, &gGlobalPoolMutex) {
+ SkASSERT(&gGlobalPoolMutex == this->mutex());
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->addToHead(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// global imagerefpool wrappers
+
+size_t SkImageRef_GlobalPool::GetRAMBudget() {
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ return GetGlobalPool()->getRAMBudget();
+}
+
+void SkImageRef_GlobalPool::SetRAMBudget(size_t size) {
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->setRAMBudget(size);
+}
+
+size_t SkImageRef_GlobalPool::GetRAMUsed() {
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ return GetGlobalPool()->getRAMUsed();
+}
+
+void SkImageRef_GlobalPool::SetRAMUsed(size_t usage) {
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->setRAMUsed(usage);
+}
+
+void SkImageRef_GlobalPool::DumpPool() {
+ SkAutoMutexAcquire ac(gGlobalPoolMutex);
+ GetGlobalPool()->dump();
+}
diff --git a/images/SkImageRef_ashmem.cpp b/images/SkImageRef_ashmem.cpp
new file mode 100644
index 00000000..a4058ff6
--- /dev/null
+++ b/images/SkImageRef_ashmem.cpp
@@ -0,0 +1,230 @@
+
+/*
+ * 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 "SkImageRef_ashmem.h"
+#include "SkImageDecoder.h"
+#include "SkFlattenableBuffers.h"
+#include "SkThread.h"
+
+#include "android/ashmem.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+//#define TRACE_ASH_PURGE // just trace purges
+
+#ifdef DUMP_IMAGEREF_LIFECYCLE
+ #define DUMP_ASHMEM_LIFECYCLE
+#else
+// #define DUMP_ASHMEM_LIFECYCLE
+#endif
+
+// ashmem likes lengths on page boundaries
+static size_t roundToPageSize(size_t size) {
+ const size_t mask = getpagesize() - 1;
+ size_t newsize = (size + mask) & ~mask;
+// SkDebugf("---- oldsize %d newsize %d\n", size, newsize);
+ return newsize;
+}
+
+SkImageRef_ashmem::SkImageRef_ashmem(SkStream* stream,
+ SkBitmap::Config config,
+ int sampleSize)
+ : SkImageRef(stream, config, sampleSize) {
+
+ fRec.fFD = -1;
+ fRec.fAddr = NULL;
+ fRec.fSize = 0;
+ fRec.fPinned = false;
+
+ fCT = NULL;
+}
+
+SkImageRef_ashmem::~SkImageRef_ashmem() {
+ SkSafeUnref(fCT);
+ this->closeFD();
+}
+
+void SkImageRef_ashmem::closeFD() {
+ if (-1 != fRec.fFD) {
+#ifdef DUMP_ASHMEM_LIFECYCLE
+ SkDebugf("=== ashmem close %d\n", fRec.fFD);
+#endif
+ SkASSERT(fRec.fAddr);
+ SkASSERT(fRec.fSize);
+ munmap(fRec.fAddr, fRec.fSize);
+ close(fRec.fFD);
+ fRec.fFD = -1;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class AshmemAllocator : public SkBitmap::Allocator {
+public:
+ AshmemAllocator(SkAshmemRec* rec, const char name[])
+ : fRec(rec), fName(name) {}
+
+ virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) {
+ const size_t size = roundToPageSize(bm->getSize());
+ int fd = fRec->fFD;
+ void* addr = fRec->fAddr;
+
+ SkASSERT(!fRec->fPinned);
+
+ if (-1 == fd) {
+ SkASSERT(NULL == addr);
+ SkASSERT(0 == fRec->fSize);
+
+ fd = ashmem_create_region(fName, size);
+#ifdef DUMP_ASHMEM_LIFECYCLE
+ SkDebugf("=== ashmem_create_region %s size=%d fd=%d\n", fName, size, fd);
+#endif
+ if (-1 == fd) {
+ SkDebugf("------- imageref_ashmem create failed <%s> %d\n",
+ fName, size);
+ return false;
+ }
+
+ int err = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+ if (err) {
+ SkDebugf("------ ashmem_set_prot_region(%d) failed %d\n",
+ fd, err);
+ close(fd);
+ return false;
+ }
+
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (-1 == (long)addr) {
+ SkDebugf("---------- mmap failed for imageref_ashmem size=%d\n",
+ size);
+ close(fd);
+ return false;
+ }
+
+ fRec->fFD = fd;
+ fRec->fAddr = addr;
+ fRec->fSize = size;
+ } else {
+ SkASSERT(addr);
+ SkASSERT(size == fRec->fSize);
+ (void)ashmem_pin_region(fd, 0, 0);
+ }
+
+ bm->setPixels(addr, ct);
+ fRec->fPinned = true;
+ return true;
+ }
+
+private:
+ // we just point to our caller's memory, these are not copies
+ SkAshmemRec* fRec;
+ const char* fName;
+};
+
+bool SkImageRef_ashmem::onDecode(SkImageDecoder* codec, SkStream* stream,
+ SkBitmap* bitmap, SkBitmap::Config config,
+ SkImageDecoder::Mode mode) {
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return this->INHERITED::onDecode(codec, stream, bitmap, config, mode);
+ }
+
+ AshmemAllocator alloc(&fRec, this->getURI());
+
+ codec->setAllocator(&alloc);
+ bool success = this->INHERITED::onDecode(codec, stream, bitmap, config,
+ mode);
+ // remove the allocator, since its on the stack
+ codec->setAllocator(NULL);
+
+ if (success) {
+ // remember the colortable (if any)
+ SkRefCnt_SafeAssign(fCT, bitmap->getColorTable());
+ return true;
+ } else {
+ if (fRec.fPinned) {
+ ashmem_unpin_region(fRec.fFD, 0, 0);
+ fRec.fPinned = false;
+ }
+ this->closeFD();
+ return false;
+ }
+}
+
+void* SkImageRef_ashmem::onLockPixels(SkColorTable** ct) {
+ SkASSERT(fBitmap.getPixels() == NULL);
+ SkASSERT(fBitmap.getColorTable() == NULL);
+
+ // fast case: check if we can just pin and get the cached data
+ if (-1 != fRec.fFD) {
+ SkASSERT(fRec.fAddr);
+ SkASSERT(!fRec.fPinned);
+ int pin = ashmem_pin_region(fRec.fFD, 0, 0);
+
+ if (ASHMEM_NOT_PURGED == pin) { // yea, fast case!
+ fBitmap.setPixels(fRec.fAddr, fCT);
+ fRec.fPinned = true;
+ } else if (ASHMEM_WAS_PURGED == pin) {
+ ashmem_unpin_region(fRec.fFD, 0, 0);
+ // let go of our colortable if we lost the pixels. Well get it back
+ // again when we re-decode
+ if (fCT) {
+ fCT->unref();
+ fCT = NULL;
+ }
+#if defined(DUMP_ASHMEM_LIFECYCLE) || defined(TRACE_ASH_PURGE)
+ SkDebugf("===== ashmem purged %d\n", fBitmap.getSize());
+#endif
+ } else {
+ SkDebugf("===== ashmem pin_region(%d) returned %d\n", fRec.fFD, pin);
+ // return null result for failure
+ if (ct) {
+ *ct = NULL;
+ }
+ return NULL;
+ }
+ } else {
+ // no FD, will create an ashmem region in allocator
+ }
+
+ return this->INHERITED::onLockPixels(ct);
+}
+
+void SkImageRef_ashmem::onUnlockPixels() {
+ this->INHERITED::onUnlockPixels();
+
+ if (-1 != fRec.fFD) {
+ SkASSERT(fRec.fAddr);
+ SkASSERT(fRec.fPinned);
+
+ ashmem_unpin_region(fRec.fFD, 0, 0);
+ fRec.fPinned = false;
+ }
+
+ // we clear this with or without an error, since we've either closed or
+ // unpinned the region
+ fBitmap.setPixels(NULL, NULL);
+}
+
+void SkImageRef_ashmem::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeString(getURI());
+}
+
+SkImageRef_ashmem::SkImageRef_ashmem(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fRec.fFD = -1;
+ fRec.fAddr = NULL;
+ fRec.fSize = 0;
+ fRec.fPinned = false;
+ fCT = NULL;
+
+ SkString uri;
+ buffer.readString(&uri);
+ this->setURI(uri);
+}
diff --git a/images/SkImageRef_ashmem.h b/images/SkImageRef_ashmem.h
new file mode 100644
index 00000000..f98507aa
--- /dev/null
+++ b/images/SkImageRef_ashmem.h
@@ -0,0 +1,47 @@
+
+/*
+ * 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 SkImageRef_ashmem_DEFINED
+#define SkImageRef_ashmem_DEFINED
+
+#include "SkImageRef.h"
+
+struct SkAshmemRec {
+ int fFD;
+ void* fAddr;
+ size_t fSize;
+ bool fPinned;
+};
+
+class SkImageRef_ashmem : public SkImageRef {
+public:
+ SkImageRef_ashmem(SkStream*, SkBitmap::Config, int sampleSize = 1);
+ virtual ~SkImageRef_ashmem();
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkImageRef_ashmem)
+
+protected:
+ SkImageRef_ashmem(SkFlattenableReadBuffer&);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+ virtual bool onDecode(SkImageDecoder* codec, SkStream* stream,
+ SkBitmap* bitmap, SkBitmap::Config config,
+ SkImageDecoder::Mode mode);
+
+ virtual void* onLockPixels(SkColorTable**);
+ virtual void onUnlockPixels();
+
+private:
+ void closeFD();
+
+ SkColorTable* fCT;
+ SkAshmemRec fRec;
+
+ typedef SkImageRef INHERITED;
+};
+
+#endif
diff --git a/images/SkImages.cpp b/images/SkImages.cpp
new file mode 100644
index 00000000..5b6bf6b7
--- /dev/null
+++ b/images/SkImages.cpp
@@ -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.
+ */
+
+#include "SkFlattenable.h"
+#include "SkImageRef_GlobalPool.h"
+#include "SkImages.h"
+
+#ifdef SK_BUILD_FOR_ANDROID
+#include "SkImageRef_ashmem.h"
+#endif
+
+void SkImages::InitializeFlattenables() {
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageRef_GlobalPool)
+#ifdef SK_BUILD_FOR_ANDROID
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageRef_ashmem)
+#endif
+}
diff --git a/images/SkJpegUtility.cpp b/images/SkJpegUtility.cpp
new file mode 100644
index 00000000..8d8f62f5
--- /dev/null
+++ b/images/SkJpegUtility.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2010 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 "SkJpegUtility.h"
+
+/////////////////////////////////////////////////////////////////////
+static void sk_init_source(j_decompress_ptr cinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+#ifdef SK_BUILD_FOR_ANDROID
+ src->current_offset = 0;
+#endif
+ src->fStream->rewind();
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+static boolean sk_seek_input_data(j_decompress_ptr cinfo, long byte_offset) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ size_t bo = (size_t) byte_offset;
+
+ if (bo > src->current_offset) {
+ (void)src->fStream->skip(bo - src->current_offset);
+ } else {
+ src->fStream->rewind();
+ (void)src->fStream->skip(bo);
+ }
+
+ src->current_offset = bo;
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+ return true;
+}
+#endif
+
+static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ if (src->fDecoder != NULL && src->fDecoder->shouldCancelDecode()) {
+ return FALSE;
+ }
+ size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize);
+ // note that JPEG is happy with less than the full read,
+ // as long as the result is non-zero
+ if (bytes == 0) {
+ return FALSE;
+ }
+
+#ifdef SK_BUILD_FOR_ANDROID
+ src->current_offset += bytes;
+#endif
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = bytes;
+ return TRUE;
+}
+
+static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+
+ if (num_bytes > (long)src->bytes_in_buffer) {
+ long bytesToSkip = num_bytes - src->bytes_in_buffer;
+ while (bytesToSkip > 0) {
+ long bytes = (long)src->fStream->skip(bytesToSkip);
+ if (bytes <= 0 || bytes > bytesToSkip) {
+// SkDebugf("xxxxxxxxxxxxxx failure to skip request %d returned %d\n", bytesToSkip, bytes);
+ cinfo->err->error_exit((j_common_ptr)cinfo);
+ return;
+ }
+#ifdef SK_BUILD_FOR_ANDROID
+ src->current_offset += bytes;
+#endif
+ bytesToSkip -= bytes;
+ }
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+ } else {
+ src->next_input_byte += num_bytes;
+ src->bytes_in_buffer -= num_bytes;
+ }
+}
+
+static boolean sk_resync_to_restart(j_decompress_ptr cinfo, int desired) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+
+ // what is the desired param for???
+
+ if (!src->fStream->rewind()) {
+ SkDebugf("xxxxxxxxxxxxxx failure to rewind\n");
+ cinfo->err->error_exit((j_common_ptr)cinfo);
+ return FALSE;
+ }
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+ return TRUE;
+}
+
+static void sk_term_source(j_decompress_ptr /*cinfo*/) {}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder)
+ : fStream(SkRef(stream))
+ , fDecoder(decoder) {
+
+ init_source = sk_init_source;
+ fill_input_buffer = sk_fill_input_buffer;
+ skip_input_data = sk_skip_input_data;
+ resync_to_restart = sk_resync_to_restart;
+ term_source = sk_term_source;
+#ifdef SK_BUILD_FOR_ANDROID
+ seek_input_data = sk_seek_input_data;
+#endif
+// SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
+}
+
+skjpeg_source_mgr::~skjpeg_source_mgr() {
+ SkSafeUnref(fStream);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void sk_init_destination(j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize;
+}
+
+static boolean sk_empty_output_buffer(j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+// if (!dest->fStream->write(dest->fBuffer, skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer))
+ if (!dest->fStream->write(dest->fBuffer,
+ skjpeg_destination_mgr::kBufferSize)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return false;
+ }
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize;
+ return TRUE;
+}
+
+static void sk_term_destination (j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+ size_t size = skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer;
+ if (size > 0) {
+ if (!dest->fStream->write(dest->fBuffer, size)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return;
+ }
+ }
+ dest->fStream->flush();
+}
+
+skjpeg_destination_mgr::skjpeg_destination_mgr(SkWStream* stream)
+ : fStream(stream) {
+ this->init_destination = sk_init_destination;
+ this->empty_output_buffer = sk_empty_output_buffer;
+ this->term_destination = sk_term_destination;
+}
+
+void skjpeg_error_exit(j_common_ptr cinfo) {
+ skjpeg_error_mgr* error = (skjpeg_error_mgr*)cinfo->err;
+
+ (*error->output_message) (cinfo);
+
+ /* Let the memory manager delete any temp files before we die */
+ jpeg_destroy(cinfo);
+
+ longjmp(error->fJmpBuf, -1);
+}
diff --git a/images/SkJpegUtility.h b/images/SkJpegUtility.h
new file mode 100644
index 00000000..69092ef1
--- /dev/null
+++ b/images/SkJpegUtility.h
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright 2010 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 SkJpegUtility_DEFINED
+#define SkJpegUtility_DEFINED
+
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+
+extern "C" {
+ #include "jpeglib.h"
+ #include "jerror.h"
+}
+
+#include <setjmp.h>
+
+/* Our error-handling struct.
+ *
+*/
+struct skjpeg_error_mgr : jpeg_error_mgr {
+ jmp_buf fJmpBuf;
+};
+
+
+void skjpeg_error_exit(j_common_ptr cinfo);
+
+///////////////////////////////////////////////////////////////////////////
+/* Our source struct for directing jpeg to our stream object.
+*/
+struct skjpeg_source_mgr : jpeg_source_mgr {
+ skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder);
+ ~skjpeg_source_mgr();
+
+ // fStream is ref'ed and unref'ed
+ SkStream* fStream;
+ // Unowned pointer to the decoder, used to check if the decoding process
+ // has been cancelled.
+ SkImageDecoder* fDecoder;
+ enum {
+ kBufferSize = 1024
+ };
+ char fBuffer[kBufferSize];
+};
+
+/////////////////////////////////////////////////////////////////////////////
+/* Our destination struct for directing decompressed pixels to our stream
+ * object.
+ */
+struct skjpeg_destination_mgr : jpeg_destination_mgr {
+ skjpeg_destination_mgr(SkWStream* stream);
+
+ SkWStream* fStream;
+
+ enum {
+ kBufferSize = 1024
+ };
+ uint8_t fBuffer[kBufferSize];
+};
+
+#endif
diff --git a/images/SkMovie.cpp b/images/SkMovie.cpp
new file mode 100644
index 00000000..25600149
--- /dev/null
+++ b/images/SkMovie.cpp
@@ -0,0 +1,97 @@
+
+/*
+ * 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 "SkMovie.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+SK_DEFINE_INST_COUNT(SkMovie)
+
+// We should never see this in normal operation since our time values are
+// 0-based. So we use it as a sentinal.
+#define UNINITIALIZED_MSEC ((SkMSec)-1)
+
+SkMovie::SkMovie()
+{
+ fInfo.fDuration = UNINITIALIZED_MSEC; // uninitialized
+ fCurrTime = UNINITIALIZED_MSEC; // uninitialized
+ fNeedBitmap = true;
+}
+
+void SkMovie::ensureInfo()
+{
+ if (fInfo.fDuration == UNINITIALIZED_MSEC && !this->onGetInfo(&fInfo))
+ memset(&fInfo, 0, sizeof(fInfo)); // failure
+}
+
+SkMSec SkMovie::duration()
+{
+ this->ensureInfo();
+ return fInfo.fDuration;
+}
+
+int SkMovie::width()
+{
+ this->ensureInfo();
+ return fInfo.fWidth;
+}
+
+int SkMovie::height()
+{
+ this->ensureInfo();
+ return fInfo.fHeight;
+}
+
+int SkMovie::isOpaque()
+{
+ this->ensureInfo();
+ return fInfo.fIsOpaque;
+}
+
+bool SkMovie::setTime(SkMSec time)
+{
+ SkMSec dur = this->duration();
+ if (time > dur)
+ time = dur;
+
+ bool changed = false;
+ if (time != fCurrTime)
+ {
+ fCurrTime = time;
+ changed = this->onSetTime(time);
+ fNeedBitmap |= changed;
+ }
+ return changed;
+}
+
+const SkBitmap& SkMovie::bitmap()
+{
+ if (fCurrTime == UNINITIALIZED_MSEC) // uninitialized
+ this->setTime(0);
+
+ if (fNeedBitmap)
+ {
+ if (!this->onGetBitmap(&fBitmap)) // failure
+ fBitmap.reset();
+ fNeedBitmap = false;
+ }
+ return fBitmap;
+}
+
+////////////////////////////////////////////////////////////////////
+
+#include "SkStream.h"
+
+SkMovie* SkMovie::DecodeMemory(const void* data, size_t length) {
+ SkMemoryStream stream(data, length, false);
+ return SkMovie::DecodeStream(&stream);
+}
+
+SkMovie* SkMovie::DecodeFile(const char path[]) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return stream.get() ? SkMovie::DecodeStream(stream) : NULL;
+}
diff --git a/images/SkMovie_gif.cpp b/images/SkMovie_gif.cpp
new file mode 100644
index 00000000..31581681
--- /dev/null
+++ b/images/SkMovie_gif.cpp
@@ -0,0 +1,449 @@
+
+/*
+ * 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 "SkMovie.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+#include "gif_lib.h"
+
+class SkGIFMovie : public SkMovie {
+public:
+ SkGIFMovie(SkStream* stream);
+ virtual ~SkGIFMovie();
+
+protected:
+ virtual bool onGetInfo(Info*);
+ virtual bool onSetTime(SkMSec);
+ virtual bool onGetBitmap(SkBitmap*);
+
+private:
+ GifFileType* fGIF;
+ int fCurrIndex;
+ int fLastDrawIndex;
+ SkBitmap fBackup;
+};
+
+static int Decode(GifFileType* fileType, GifByteType* out, int size) {
+ SkStream* stream = (SkStream*) fileType->UserData;
+ return (int) stream->read(out, size);
+}
+
+SkGIFMovie::SkGIFMovie(SkStream* stream)
+{
+#if GIFLIB_MAJOR < 5
+ fGIF = DGifOpen( stream, Decode );
+#else
+ fGIF = DGifOpen( stream, Decode, NULL );
+#endif
+ if (NULL == fGIF)
+ return;
+
+ if (DGifSlurp(fGIF) != GIF_OK)
+ {
+ DGifCloseFile(fGIF);
+ fGIF = NULL;
+ }
+ fCurrIndex = -1;
+ fLastDrawIndex = -1;
+}
+
+SkGIFMovie::~SkGIFMovie()
+{
+ if (fGIF)
+ DGifCloseFile(fGIF);
+}
+
+static SkMSec savedimage_duration(const SavedImage* image)
+{
+ for (int j = 0; j < image->ExtensionBlockCount; j++)
+ {
+ if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
+ {
+ SkASSERT(image->ExtensionBlocks[j].ByteCount >= 4);
+ const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
+ return ((b[2] << 8) | b[1]) * 10;
+ }
+ }
+ return 0;
+}
+
+bool SkGIFMovie::onGetInfo(Info* info)
+{
+ if (NULL == fGIF)
+ return false;
+
+ SkMSec dur = 0;
+ for (int i = 0; i < fGIF->ImageCount; i++)
+ dur += savedimage_duration(&fGIF->SavedImages[i]);
+
+ info->fDuration = dur;
+ info->fWidth = fGIF->SWidth;
+ info->fHeight = fGIF->SHeight;
+ info->fIsOpaque = false; // how to compute?
+ return true;
+}
+
+bool SkGIFMovie::onSetTime(SkMSec time)
+{
+ if (NULL == fGIF)
+ return false;
+
+ SkMSec dur = 0;
+ for (int i = 0; i < fGIF->ImageCount; i++)
+ {
+ dur += savedimage_duration(&fGIF->SavedImages[i]);
+ if (dur >= time)
+ {
+ fCurrIndex = i;
+ return fLastDrawIndex != fCurrIndex;
+ }
+ }
+ fCurrIndex = fGIF->ImageCount - 1;
+ return true;
+}
+
+static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap,
+ int transparent, int width)
+{
+ for (; width > 0; width--, src++, dst++) {
+ if (*src != transparent) {
+ const GifColorType& col = cmap->Colors[*src];
+ *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
+ }
+ }
+}
+
+#if GIFLIB_MAJOR < 5
+static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src,
+ const ColorMapObject* cmap, int transparent, int copyWidth,
+ int copyHeight, const GifImageDesc& imageDesc, int rowStep,
+ int startRow)
+{
+ int row;
+ // every 'rowStep'th row, starting with row 'startRow'
+ for (row = startRow; row < copyHeight; row += rowStep) {
+ uint32_t* dst = bm->getAddr32(imageDesc.Left, imageDesc.Top + row);
+ copyLine(dst, src, cmap, transparent, copyWidth);
+ src += imageDesc.Width;
+ }
+
+ // pad for rest height
+ src += imageDesc.Width * ((imageDesc.Height - row + rowStep - 1) / rowStep);
+}
+
+static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
+ int transparent)
+{
+ int width = bm->width();
+ int height = bm->height();
+ GifWord copyWidth = frame->ImageDesc.Width;
+ if (frame->ImageDesc.Left + copyWidth > width) {
+ copyWidth = width - frame->ImageDesc.Left;
+ }
+
+ GifWord copyHeight = frame->ImageDesc.Height;
+ if (frame->ImageDesc.Top + copyHeight > height) {
+ copyHeight = height - frame->ImageDesc.Top;
+ }
+
+ // deinterlace
+ const unsigned char* src = (unsigned char*)frame->RasterBits;
+
+ // group 1 - every 8th row, starting with row 0
+ copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 0);
+
+ // group 2 - every 8th row, starting with row 4
+ copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 4);
+
+ // group 3 - every 4th row, starting with row 2
+ copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 4, 2);
+
+ copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 2, 1);
+}
+#endif
+
+static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
+ int transparent)
+{
+ int width = bm->width();
+ int height = bm->height();
+ const unsigned char* src = (unsigned char*)frame->RasterBits;
+ uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top);
+ GifWord copyWidth = frame->ImageDesc.Width;
+ if (frame->ImageDesc.Left + copyWidth > width) {
+ copyWidth = width - frame->ImageDesc.Left;
+ }
+
+ GifWord copyHeight = frame->ImageDesc.Height;
+ if (frame->ImageDesc.Top + copyHeight > height) {
+ copyHeight = height - frame->ImageDesc.Top;
+ }
+
+ for (; copyHeight > 0; copyHeight--) {
+ copyLine(dst, src, cmap, transparent, copyWidth);
+ src += frame->ImageDesc.Width;
+ dst += width;
+ }
+}
+
+static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height,
+ uint32_t col)
+{
+ int bmWidth = bm->width();
+ int bmHeight = bm->height();
+ uint32_t* dst = bm->getAddr32(left, top);
+ GifWord copyWidth = width;
+ if (left + copyWidth > bmWidth) {
+ copyWidth = bmWidth - left;
+ }
+
+ GifWord copyHeight = height;
+ if (top + copyHeight > bmHeight) {
+ copyHeight = bmHeight - top;
+ }
+
+ for (; copyHeight > 0; copyHeight--) {
+ sk_memset32(dst, col, copyWidth);
+ dst += bmWidth;
+ }
+}
+
+static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap)
+{
+ int transparent = -1;
+
+ for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
+ ExtensionBlock* eb = frame->ExtensionBlocks + i;
+ if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
+ eb->ByteCount == 4) {
+ bool has_transparency = ((eb->Bytes[0] & 1) == 1);
+ if (has_transparency) {
+ transparent = (unsigned char)eb->Bytes[3];
+ }
+ }
+ }
+
+ if (frame->ImageDesc.ColorMap != NULL) {
+ // use local color table
+ cmap = frame->ImageDesc.ColorMap;
+ }
+
+ if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
+ SkDEBUGFAIL("bad colortable setup");
+ return;
+ }
+
+#if GIFLIB_MAJOR < 5
+ // before GIFLIB 5, de-interlacing wasn't done by library at load time
+ if (frame->ImageDesc.Interlace) {
+ blitInterlace(bm, frame, cmap, transparent);
+ return;
+ }
+#endif
+
+ blitNormal(bm, frame, cmap, transparent);
+}
+
+static bool checkIfWillBeCleared(const SavedImage* frame)
+{
+ for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
+ ExtensionBlock* eb = frame->ExtensionBlocks + i;
+ if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
+ eb->ByteCount == 4) {
+ // check disposal method
+ int disposal = ((eb->Bytes[0] >> 2) & 7);
+ if (disposal == 2 || disposal == 3) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal)
+{
+ *trans = false;
+ *disposal = 0;
+ for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
+ ExtensionBlock* eb = frame->ExtensionBlocks + i;
+ if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
+ eb->ByteCount == 4) {
+ *trans = ((eb->Bytes[0] & 1) == 1);
+ *disposal = ((eb->Bytes[0] >> 2) & 7);
+ }
+ }
+}
+
+// return true if area of 'target' is completely covers area of 'covered'
+static bool checkIfCover(const SavedImage* target, const SavedImage* covered)
+{
+ if (target->ImageDesc.Left <= covered->ImageDesc.Left
+ && covered->ImageDesc.Left + covered->ImageDesc.Width <=
+ target->ImageDesc.Left + target->ImageDesc.Width
+ && target->ImageDesc.Top <= covered->ImageDesc.Top
+ && covered->ImageDesc.Top + covered->ImageDesc.Height <=
+ target->ImageDesc.Top + target->ImageDesc.Height) {
+ return true;
+ }
+ return false;
+}
+
+static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next,
+ SkBitmap* backup, SkColor color)
+{
+ // We can skip disposal process if next frame is not transparent
+ // and completely covers current area
+ bool curTrans;
+ int curDisposal;
+ getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal);
+ bool nextTrans;
+ int nextDisposal;
+ getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal);
+ if ((curDisposal == 2 || curDisposal == 3)
+ && (nextTrans || !checkIfCover(next, cur))) {
+ switch (curDisposal) {
+ // restore to background color
+ // -> 'background' means background under this image.
+ case 2:
+ fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top,
+ cur->ImageDesc.Width, cur->ImageDesc.Height,
+ color);
+ break;
+
+ // restore to previous
+ case 3:
+ bm->swap(*backup);
+ break;
+ }
+ }
+
+ // Save current image if next frame's disposal method == 3
+ if (nextDisposal == 3) {
+ const uint32_t* src = bm->getAddr32(0, 0);
+ uint32_t* dst = backup->getAddr32(0, 0);
+ int cnt = bm->width() * bm->height();
+ memcpy(dst, src, cnt*sizeof(uint32_t));
+ }
+}
+
+bool SkGIFMovie::onGetBitmap(SkBitmap* bm)
+{
+ const GifFileType* gif = fGIF;
+ if (NULL == gif)
+ return false;
+
+ if (gif->ImageCount < 1) {
+ return false;
+ }
+
+ const int width = gif->SWidth;
+ const int height = gif->SHeight;
+ if (width <= 0 || height <= 0) {
+ return false;
+ }
+
+ // no need to draw
+ if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) {
+ return true;
+ }
+
+ int startIndex = fLastDrawIndex + 1;
+ if (fLastDrawIndex < 0 || !bm->readyToDraw()) {
+ // first time
+
+ startIndex = 0;
+
+ // create bitmap
+ bm->setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
+ if (!bm->allocPixels(NULL)) {
+ return false;
+ }
+ // create bitmap for backup
+ fBackup.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
+ if (!fBackup.allocPixels(NULL)) {
+ return false;
+ }
+ } else if (startIndex > fCurrIndex) {
+ // rewind to 1st frame for repeat
+ startIndex = 0;
+ }
+
+ int lastIndex = fCurrIndex;
+ if (lastIndex < 0) {
+ // first time
+ lastIndex = 0;
+ } else if (lastIndex > fGIF->ImageCount - 1) {
+ // this block must not be reached.
+ lastIndex = fGIF->ImageCount - 1;
+ }
+
+ SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
+ if (gif->SColorMap != NULL) {
+ const GifColorType& col = gif->SColorMap->Colors[fGIF->SBackGroundColor];
+ bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
+ }
+
+ static SkColor paintingColor = SkPackARGB32(0, 0, 0, 0);
+ // draw each frames - not intelligent way
+ for (int i = startIndex; i <= lastIndex; i++) {
+ const SavedImage* cur = &fGIF->SavedImages[i];
+ if (i == 0) {
+ bool trans;
+ int disposal;
+ getTransparencyAndDisposalMethod(cur, &trans, &disposal);
+ if (!trans && gif->SColorMap != NULL) {
+ paintingColor = bgColor;
+ } else {
+ paintingColor = SkColorSetARGB(0, 0, 0, 0);
+ }
+
+ bm->eraseColor(paintingColor);
+ fBackup.eraseColor(paintingColor);
+ } else {
+ // Dispose previous frame before move to next frame.
+ const SavedImage* prev = &fGIF->SavedImages[i-1];
+ disposeFrameIfNeeded(bm, prev, cur, &fBackup, paintingColor);
+ }
+
+ // Draw frame
+ // We can skip this process if this index is not last and disposal
+ // method == 2 or method == 3
+ if (i == lastIndex || !checkIfWillBeCleared(cur)) {
+ drawFrame(bm, cur, gif->SColorMap);
+ }
+ }
+
+ // save index
+ fLastDrawIndex = lastIndex;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+SkMovie* Factory(SkStream* stream) {
+ char buf[GIF_STAMP_LEN];
+ if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
+ if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
+ // must rewind here, since our construct wants to re-read the data
+ stream->rewind();
+ return SkNEW_ARGS(SkGIFMovie, (stream));
+ }
+ }
+ return NULL;
+}
+
+static SkTRegistry<SkMovie*, SkStream*> gReg(Factory);
diff --git a/images/SkPageFlipper.cpp b/images/SkPageFlipper.cpp
new file mode 100644
index 00000000..a53f47bc
--- /dev/null
+++ b/images/SkPageFlipper.cpp
@@ -0,0 +1,76 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkPageFlipper.h"
+
+SkPageFlipper::SkPageFlipper() {
+ fWidth = 0;
+ fHeight = 0;
+ fDirty0 = &fDirty0Storage;
+ fDirty1 = &fDirty1Storage;
+
+ fDirty0->setEmpty();
+ fDirty1->setEmpty();
+}
+
+SkPageFlipper::SkPageFlipper(int width, int height) {
+ fWidth = width;
+ fHeight = height;
+ fDirty0 = &fDirty0Storage;
+ fDirty1 = &fDirty1Storage;
+
+ fDirty0->setRect(0, 0, width, height);
+ fDirty1->setEmpty();
+}
+
+void SkPageFlipper::resize(int width, int height) {
+ fWidth = width;
+ fHeight = height;
+
+ // this is the opposite of the constructors
+ fDirty1->setRect(0, 0, width, height);
+ fDirty0->setEmpty();
+}
+
+void SkPageFlipper::inval() {
+ fDirty1->setRect(0, 0, fWidth, fHeight);
+}
+
+void SkPageFlipper::inval(const SkIRect& rect) {
+ SkIRect r;
+ r.set(0, 0, fWidth, fHeight);
+ if (r.intersect(rect)) {
+ fDirty1->op(r, SkRegion::kUnion_Op);
+ }
+}
+
+void SkPageFlipper::inval(const SkRegion& rgn) {
+ SkRegion r;
+ r.setRect(0, 0, fWidth, fHeight);
+ if (r.op(rgn, SkRegion::kIntersect_Op)) {
+ fDirty1->op(r, SkRegion::kUnion_Op);
+ }
+}
+
+void SkPageFlipper::inval(const SkRect& rect, bool antialias) {
+ SkIRect r;
+ rect.round(&r);
+ if (antialias) {
+ r.inset(-1, -1);
+ }
+ this->inval(r);
+}
+
+const SkRegion& SkPageFlipper::update(SkRegion* copyBits) {
+ // Copy over anything new from page0 that isn't dirty in page1
+ copyBits->op(*fDirty0, *fDirty1, SkRegion::kDifference_Op);
+ SkTSwap<SkRegion*>(fDirty0, fDirty1);
+ fDirty1->setEmpty();
+ return *fDirty0;
+}
diff --git a/images/SkScaledBitmapSampler.cpp b/images/SkScaledBitmapSampler.cpp
new file mode 100644
index 00000000..021f86ba
--- /dev/null
+++ b/images/SkScaledBitmapSampler.cpp
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2007 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 "SkScaledBitmapSampler.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkTypes.h"
+
+// 8888
+
+static bool Sample_Gray_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32(0xFF, src[0], src[0], src[0]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32(0xFF, src[0], src[1], src[2]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBA_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ unsigned alphaMask = 0xFF;
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+// 565
+
+static bool Sample_Gray_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Gray_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherRGBTo565(src[0], src[0], src[0], DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_D565_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ uint16_t* SK_RESTRICT castedSrc = (uint16_t*) src;
+ for (int x = 0; x < width; x++) {
+ dst[x] = castedSrc[0];
+ castedSrc += deltaSrc >> 1;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+// 4444
+
+static bool Sample_Gray_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ for (int x = 0; x < width; x++) {
+ unsigned gray = src[0] >> 4;
+ dst[x] = SkPackARGB4444(0xF, gray, gray, gray);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Gray_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ DITHER_4444_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[0], src[0],
+ DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB4444(0xF, src[0] >> 4, src[1] >> 4, src[2] >> 4);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ SkPMColor16* dst = (SkPMColor16*)dstRow;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[1], src[2],
+ DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBA_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkPixel32ToPixel4444(c);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+// Index
+
+#define A32_MASK_IN_PLACE (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT)
+
+static bool Sample_Index_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = c;
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPixel32ToPixel16(ctable[*src]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Index_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ dst[x] = SkDitherRGBTo565(SkGetPackedR32(c), SkGetPackedG32(c),
+ SkGetPackedB32(c), DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Index_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = SkPixel32ToPixel4444(c);
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_DI(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ if (1 == deltaSrc) {
+ memcpy(dstRow, src, width);
+ } else {
+ uint8_t* SK_RESTRICT dst = (uint8_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = src[0];
+ src += deltaSrc;
+ }
+ }
+ return false;
+}
+
+// A8
+static bool Sample_Gray_DA8(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ memcpy(dstRow, src, width);
+ return true;
+}
+
+// 8888 Unpremul
+
+static bool Sample_Gray_D8888_Unpremul(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32NoCheck(0xFF, src[0], src[0], src[0]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+// Sample_RGBx_D8888_Unpremul is no different from Sample_RGBx_D8888, since alpha
+// is 0xFF
+
+static bool Sample_RGBA_D8888_Unpremul(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
+ unsigned alphaMask = 0xFF;
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ dst[x] = SkPackARGB32NoCheck(alpha, src[0], src[1], src[2]);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+// Sample_Index_D8888_Unpremul is the same as Sample_Index_D8888, since the
+// color table has its colors inserted unpremultiplied.
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkScaledBitmapSampler.h"
+
+SkScaledBitmapSampler::SkScaledBitmapSampler(int width, int height,
+ int sampleSize) {
+ fCTable = NULL;
+ fDstRow = NULL;
+ fRowProc = NULL;
+
+ if (width <= 0 || height <= 0) {
+ sk_throw();
+ }
+
+ if (sampleSize <= 1) {
+ fScaledWidth = width;
+ fScaledHeight = height;
+ fX0 = fY0 = 0;
+ fDX = fDY = 1;
+ return;
+ }
+
+ int dx = SkMin32(sampleSize, width);
+ int dy = SkMin32(sampleSize, height);
+
+ fScaledWidth = width / dx;
+ fScaledHeight = height / dy;
+
+ SkASSERT(fScaledWidth > 0);
+ SkASSERT(fScaledHeight > 0);
+
+ fX0 = dx >> 1;
+ fY0 = dy >> 1;
+
+ SkASSERT(fX0 >= 0 && fX0 < width);
+ SkASSERT(fY0 >= 0 && fY0 < height);
+
+ fDX = dx;
+ fDY = dy;
+
+ SkASSERT(fDX > 0 && (fX0 + fDX * (fScaledWidth - 1)) < width);
+ SkASSERT(fDY > 0 && (fY0 + fDY * (fScaledHeight - 1)) < height);
+}
+
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc, bool dither,
+ const SkPMColor ctable[],
+ bool requireUnpremul) {
+ static const RowProc gProcs[] = {
+ // 8888 (no dither distinction)
+ Sample_Gray_D8888, Sample_Gray_D8888,
+ Sample_RGBx_D8888, Sample_RGBx_D8888,
+ Sample_RGBA_D8888, Sample_RGBA_D8888,
+ Sample_Index_D8888, Sample_Index_D8888,
+ NULL, NULL,
+ // 565 (no alpha distinction)
+ Sample_Gray_D565, Sample_Gray_D565_D,
+ Sample_RGBx_D565, Sample_RGBx_D565_D,
+ Sample_RGBx_D565, Sample_RGBx_D565_D,
+ Sample_Index_D565, Sample_Index_D565_D,
+ Sample_D565_D565, Sample_D565_D565,
+ // 4444
+ Sample_Gray_D4444, Sample_Gray_D4444_D,
+ Sample_RGBx_D4444, Sample_RGBx_D4444_D,
+ Sample_RGBA_D4444, Sample_RGBA_D4444_D,
+ Sample_Index_D4444, Sample_Index_D4444_D,
+ NULL, NULL,
+ // Index8
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ Sample_Index_DI, Sample_Index_DI,
+ NULL, NULL,
+ // A8
+ Sample_Gray_DA8, Sample_Gray_DA8,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ // 8888 Unpremul (no dither distinction)
+ Sample_Gray_D8888_Unpremul, Sample_Gray_D8888_Unpremul,
+ Sample_RGBx_D8888, Sample_RGBx_D8888,
+ Sample_RGBA_D8888_Unpremul, Sample_RGBA_D8888_Unpremul,
+ Sample_Index_D8888, Sample_Index_D8888,
+ NULL, NULL,
+ };
+ // The jump between dst configs in the table
+ static const int gProcDstConfigSpan = 10;
+ SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gProcs) == 6 * gProcDstConfigSpan,
+ gProcs_has_the_wrong_number_of_entries);
+
+ fCTable = ctable;
+
+ int index = 0;
+ if (dither) {
+ index += 1;
+ }
+ switch (sc) {
+ case SkScaledBitmapSampler::kGray:
+ fSrcPixelSize = 1;
+ index += 0;
+ break;
+ case SkScaledBitmapSampler::kRGB:
+ fSrcPixelSize = 3;
+ index += 2;
+ break;
+ case SkScaledBitmapSampler::kRGBX:
+ fSrcPixelSize = 4;
+ index += 2;
+ break;
+ case SkScaledBitmapSampler::kRGBA:
+ fSrcPixelSize = 4;
+ index += 4;
+ break;
+ case SkScaledBitmapSampler::kIndex:
+ fSrcPixelSize = 1;
+ index += 6;
+ break;
+ case SkScaledBitmapSampler::kRGB_565:
+ fSrcPixelSize = 2;
+ index += 8;
+ break;
+ default:
+ return false;
+ }
+
+ switch (dst->config()) {
+ case SkBitmap::kARGB_8888_Config:
+ index += 0 * gProcDstConfigSpan;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ index += 1 * gProcDstConfigSpan;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ index += 2 * gProcDstConfigSpan;
+ break;
+ case SkBitmap::kIndex8_Config:
+ index += 3 * gProcDstConfigSpan;
+ break;
+ case SkBitmap::kA8_Config:
+ index += 4 * gProcDstConfigSpan;
+ break;
+ default:
+ return false;
+ }
+
+ if (requireUnpremul) {
+ if (dst->config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+ index += 5 * gProcDstConfigSpan;
+ }
+
+ fRowProc = gProcs[index];
+ fDstRow = (char*)dst->getPixels();
+ fDstRowBytes = dst->rowBytes();
+ fCurrY = 0;
+ return fRowProc != NULL;
+}
+
+bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) {
+ SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight);
+
+ bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+ fDX * fSrcPixelSize, fCurrY, fCTable);
+ fDstRow += fDstRowBytes;
+ fCurrY += 1;
+ return hadAlpha;
+}
diff --git a/images/SkScaledBitmapSampler.h b/images/SkScaledBitmapSampler.h
new file mode 100644
index 00000000..6477db21
--- /dev/null
+++ b/images/SkScaledBitmapSampler.h
@@ -0,0 +1,69 @@
+
+/*
+ * 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 SkScaledBitmapSampler_DEFINED
+#define SkScaledBitmapSampler_DEFINED
+
+#include "SkTypes.h"
+#include "SkColor.h"
+
+class SkBitmap;
+
+class SkScaledBitmapSampler {
+public:
+ SkScaledBitmapSampler(int origWidth, int origHeight, int cellSize);
+
+ int scaledWidth() const { return fScaledWidth; }
+ int scaledHeight() const { return fScaledHeight; }
+
+ int srcY0() const { return fY0; }
+ int srcDY() const { return fDY; }
+
+ enum SrcConfig {
+ kGray, // 1 byte per pixel
+ kIndex, // 1 byte per pixel
+ kRGB, // 3 bytes per pixel
+ kRGBX, // 4 byes per pixel (ignore 4th)
+ kRGBA, // 4 bytes per pixel
+ kRGB_565 // 2 bytes per pixel
+ };
+
+ // Given a dst bitmap (with pixels already allocated) and a src-config,
+ // prepares iterator to process the src colors and write them into dst.
+ // Returns false if the request cannot be fulfulled.
+ bool begin(SkBitmap* dst, SrcConfig sc, bool doDither,
+ const SkPMColor* = NULL, bool requireUnPremul = false);
+ // call with row of src pixels, for y = 0...scaledHeight-1.
+ // returns true if the row had non-opaque alpha in it
+ bool next(const uint8_t* SK_RESTRICT src);
+
+private:
+ int fScaledWidth;
+ int fScaledHeight;
+
+ int fX0; // first X coord to sample
+ int fY0; // first Y coord (scanline) to sample
+ int fDX; // step between X samples
+ int fDY; // step between Y samples
+
+ typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y,
+ const SkPMColor[]);
+
+ // setup state
+ char* fDstRow; // points into bitmap's pixels
+ size_t fDstRowBytes;
+ int fCurrY; // used for dithering
+ int fSrcPixelSize; // 1, 3, 4
+ RowProc fRowProc;
+
+ // optional reference to the src colors if the src is a palette model
+ const SkPMColor* fCTable;
+};
+
+#endif
diff --git a/images/bmpdecoderhelper.cpp b/images/bmpdecoderhelper.cpp
new file mode 100644
index 00000000..77496649
--- /dev/null
+++ b/images/bmpdecoderhelper.cpp
@@ -0,0 +1,369 @@
+
+/*
+ * Copyright 2007 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.
+ */
+
+// Author: cevans@google.com (Chris Evans)
+
+#include "bmpdecoderhelper.h"
+
+namespace image_codec {
+
+static const int kBmpHeaderSize = 14;
+static const int kBmpInfoSize = 40;
+static const int kBmpOS2InfoSize = 12;
+static const int kMaxDim = SHRT_MAX / 2;
+
+bool BmpDecoderHelper::DecodeImage(const char* p,
+ int len,
+ int max_pixels,
+ BmpDecoderCallback* callback) {
+ data_ = reinterpret_cast<const uint8*>(p);
+ pos_ = 0;
+ len_ = len;
+ inverted_ = true;
+ // Parse the header structure.
+ if (len < kBmpHeaderSize + 4) {
+ return false;
+ }
+ GetShort(); // Signature.
+ GetInt(); // Size.
+ GetInt(); // Reserved.
+ int offset = GetInt();
+ // Parse the info structure.
+ int infoSize = GetInt();
+ if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) {
+ return false;
+ }
+ int cols = 0;
+ int comp = 0;
+ int colLen = 4;
+ if (infoSize >= kBmpInfoSize) {
+ if (len < kBmpHeaderSize + kBmpInfoSize) {
+ return false;
+ }
+ width_ = GetInt();
+ height_ = GetInt();
+ GetShort(); // Planes.
+ bpp_ = GetShort();
+ comp = GetInt();
+ GetInt(); // Size.
+ GetInt(); // XPPM.
+ GetInt(); // YPPM.
+ cols = GetInt();
+ GetInt(); // Important colours.
+ } else {
+ if (len < kBmpHeaderSize + kBmpOS2InfoSize) {
+ return false;
+ }
+ colLen = 3;
+ width_ = GetShort();
+ height_ = GetShort();
+ GetShort(); // Planes.
+ bpp_ = GetShort();
+ }
+ if (height_ < 0) {
+ height_ = -height_;
+ inverted_ = false;
+ }
+ if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) {
+ return false;
+ }
+ if (width_ * height_ > max_pixels) {
+ return false;
+ }
+ if (cols < 0 || cols > 256) {
+ return false;
+ }
+ // Allocate then read in the colour map.
+ if (cols == 0 && bpp_ <= 8) {
+ cols = 1 << bpp_;
+ }
+ if (bpp_ <= 8 || cols > 0) {
+ uint8* colBuf = new uint8[256 * 3];
+ memset(colBuf, '\0', 256 * 3);
+ colTab_.reset(colBuf);
+ }
+ if (cols > 0) {
+ if (pos_ + (cols * colLen) > len_) {
+ return false;
+ }
+ for (int i = 0; i < cols; ++i) {
+ int base = i * 3;
+ colTab_[base + 2] = GetByte();
+ colTab_[base + 1] = GetByte();
+ colTab_[base] = GetByte();
+ if (colLen == 4) {
+ GetByte();
+ }
+ }
+ }
+ // Read in the compression data if necessary.
+ redBits_ = 0x7c00;
+ greenBits_ = 0x03e0;
+ blueBits_ = 0x001f;
+ bool rle = false;
+ if (comp == 1 || comp == 2) {
+ rle = true;
+ } else if (comp == 3) {
+ if (pos_ + 12 > len_) {
+ return false;
+ }
+ redBits_ = GetInt() & 0xffff;
+ greenBits_ = GetInt() & 0xffff;
+ blueBits_ = GetInt() & 0xffff;
+ }
+ redShiftRight_ = CalcShiftRight(redBits_);
+ greenShiftRight_ = CalcShiftRight(greenBits_);
+ blueShiftRight_ = CalcShiftRight(blueBits_);
+ redShiftLeft_ = CalcShiftLeft(redBits_);
+ greenShiftLeft_ = CalcShiftLeft(greenBits_);
+ blueShiftLeft_ = CalcShiftLeft(blueBits_);
+ rowPad_ = 0;
+ pixelPad_ = 0;
+ int rowLen;
+ if (bpp_ == 32) {
+ rowLen = width_ * 4;
+ pixelPad_ = 1;
+ } else if (bpp_ == 24) {
+ rowLen = width_ * 3;
+ } else if (bpp_ == 16) {
+ rowLen = width_ * 2;
+ } else if (bpp_ == 8) {
+ rowLen = width_;
+ } else if (bpp_ == 4) {
+ rowLen = width_ / 2;
+ if (width_ & 1) {
+ rowLen++;
+ }
+ } else if (bpp_ == 1) {
+ rowLen = width_ / 8;
+ if (width_ & 7) {
+ rowLen++;
+ }
+ } else {
+ return false;
+ }
+ // Round the rowLen up to a multiple of 4.
+ if (rowLen % 4 != 0) {
+ rowPad_ = 4 - (rowLen % 4);
+ rowLen += rowPad_;
+ }
+
+ if (offset > 0 && offset > pos_ && offset < len_) {
+ pos_ = offset;
+ }
+ // Deliberately off-by-one; a load of BMPs seem to have their last byte
+ // missing.
+ if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) {
+ return false;
+ }
+
+ output_ = callback->SetSize(width_, height_);
+ if (NULL == output_) {
+ return true; // meaning we succeeded, but they want us to stop now
+ }
+
+ if (rle && (bpp_ == 4 || bpp_ == 8)) {
+ DoRLEDecode();
+ } else {
+ DoStandardDecode();
+ }
+ return true;
+}
+
+void BmpDecoderHelper::DoRLEDecode() {
+ static const uint8 RLE_ESCAPE = 0;
+ static const uint8 RLE_EOL = 0;
+ static const uint8 RLE_EOF = 1;
+ static const uint8 RLE_DELTA = 2;
+ int x = 0;
+ int y = height_ - 1;
+ while (pos_ < len_ - 1) {
+ uint8 cmd = GetByte();
+ if (cmd != RLE_ESCAPE) {
+ uint8 pixels = GetByte();
+ int num = 0;
+ uint8 col = pixels;
+ while (cmd-- && x < width_) {
+ if (bpp_ == 4) {
+ if (num & 1) {
+ col = pixels & 0xf;
+ } else {
+ col = pixels >> 4;
+ }
+ }
+ PutPixel(x++, y, col);
+ num++;
+ }
+ } else {
+ cmd = GetByte();
+ if (cmd == RLE_EOF) {
+ return;
+ } else if (cmd == RLE_EOL) {
+ x = 0;
+ y--;
+ if (y < 0) {
+ return;
+ }
+ } else if (cmd == RLE_DELTA) {
+ if (pos_ < len_ - 1) {
+ uint8 dx = GetByte();
+ uint8 dy = GetByte();
+ x += dx;
+ if (x > width_) {
+ x = width_;
+ }
+ y -= dy;
+ if (y < 0) {
+ return;
+ }
+ }
+ } else {
+ int num = 0;
+ int bytesRead = 0;
+ uint8 val = 0;
+ while (cmd-- && pos_ < len_) {
+ if (bpp_ == 8 || !(num & 1)) {
+ val = GetByte();
+ bytesRead++;
+ }
+ uint8 col = val;
+ if (bpp_ == 4) {
+ if (num & 1) {
+ col = col & 0xf;
+ } else {
+ col >>= 4;
+ }
+ }
+ if (x < width_) {
+ PutPixel(x++, y, col);
+ }
+ num++;
+ }
+ // All pixel runs must be an even number of bytes - skip a byte if we
+ // read an odd number.
+ if ((bytesRead & 1) && pos_ < len_) {
+ GetByte();
+ }
+ }
+ }
+ }
+}
+
+void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) {
+ CHECK(x >= 0 && x < width_);
+ CHECK(y >= 0 && y < height_);
+ if (!inverted_) {
+ y = height_ - (y + 1);
+ }
+
+ int base = ((y * width_) + x) * 3;
+ int colBase = col * 3;
+ output_[base] = colTab_[colBase];
+ output_[base + 1] = colTab_[colBase + 1];
+ output_[base + 2] = colTab_[colBase + 2];
+}
+
+void BmpDecoderHelper::DoStandardDecode() {
+ int row = 0;
+ uint8 currVal = 0;
+ for (int h = height_ - 1; h >= 0; h--, row++) {
+ int realH = h;
+ if (!inverted_) {
+ realH = height_ - (h + 1);
+ }
+ uint8* line = output_ + (3 * width_ * realH);
+ for (int w = 0; w < width_; w++) {
+ if (bpp_ >= 24) {
+ line[2] = GetByte();
+ line[1] = GetByte();
+ line[0] = GetByte();
+ } else if (bpp_ == 16) {
+ uint32 val = GetShort();
+ line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_;
+ line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_;
+ line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_;
+ } else if (bpp_ <= 8) {
+ uint8 col;
+ if (bpp_ == 8) {
+ col = GetByte();
+ } else if (bpp_ == 4) {
+ if ((w % 2) == 0) {
+ currVal = GetByte();
+ col = currVal >> 4;
+ } else {
+ col = currVal & 0xf;
+ }
+ } else {
+ if ((w % 8) == 0) {
+ currVal = GetByte();
+ }
+ int bit = w & 7;
+ col = ((currVal >> (7 - bit)) & 1);
+ }
+ int base = col * 3;
+ line[0] = colTab_[base];
+ line[1] = colTab_[base + 1];
+ line[2] = colTab_[base + 2];
+ }
+ line += 3;
+ for (int i = 0; i < pixelPad_; ++i) {
+ GetByte();
+ }
+ }
+ for (int i = 0; i < rowPad_; ++i) {
+ GetByte();
+ }
+ }
+}
+
+int BmpDecoderHelper::GetInt() {
+ uint8 b1 = GetByte();
+ uint8 b2 = GetByte();
+ uint8 b3 = GetByte();
+ uint8 b4 = GetByte();
+ return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
+}
+
+int BmpDecoderHelper::GetShort() {
+ uint8 b1 = GetByte();
+ uint8 b2 = GetByte();
+ return b1 | (b2 << 8);
+}
+
+uint8 BmpDecoderHelper::GetByte() {
+ CHECK(pos_ >= 0 && pos_ <= len_);
+ // We deliberately allow this off-by-one access to cater for BMPs with their
+ // last byte missing.
+ if (pos_ == len_) {
+ return 0;
+ }
+ return data_[pos_++];
+}
+
+int BmpDecoderHelper::CalcShiftRight(uint32 mask) {
+ int ret = 0;
+ while (mask != 0 && !(mask & 1)) {
+ mask >>= 1;
+ ret++;
+ }
+ return ret;
+}
+
+int BmpDecoderHelper::CalcShiftLeft(uint32 mask) {
+ int ret = 0;
+ while (mask != 0 && !(mask & 1)) {
+ mask >>= 1;
+ }
+ while (mask != 0 && !(mask & 0x80)) {
+ mask <<= 1;
+ ret++;
+ }
+ return ret;
+}
+
+} // namespace image_codec
diff --git a/images/bmpdecoderhelper.h b/images/bmpdecoderhelper.h
new file mode 100644
index 00000000..f2f41094
--- /dev/null
+++ b/images/bmpdecoderhelper.h
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright 2007 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 IMAGE_CODEC_BMPDECODERHELPER_H__
+#define IMAGE_CODEC_BMPDECODERHELPER_H__
+
+///////////////////////////////////////////////////////////////////////////////
+// this section is my current "glue" between google3 code and android.
+// will be fixed soon
+
+#include "SkTypes.h"
+#include <limits.h>
+#define DISALLOW_EVIL_CONSTRUCTORS(name)
+#define CHECK(predicate) SkASSERT(predicate)
+typedef uint8_t uint8;
+typedef uint32_t uint32;
+
+template <typename T> class scoped_array {
+private:
+ T* ptr_;
+ scoped_array(scoped_array const&);
+ scoped_array& operator=(const scoped_array&);
+
+public:
+ explicit scoped_array(T* p = 0) : ptr_(p) {}
+ ~scoped_array() {
+ delete[] ptr_;
+ }
+
+ void reset(T* p = 0) {
+ if (p != ptr_) {
+ delete[] ptr_;
+ ptr_ = p;
+ }
+ }
+
+ T& operator[](int i) const {
+ return ptr_[i];
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace image_codec {
+
+class BmpDecoderCallback {
+ public:
+ BmpDecoderCallback() { }
+ virtual ~BmpDecoderCallback() {}
+
+ /**
+ * This is called once for an image. It is passed the width and height and
+ * should return the address of a buffer that is large enough to store
+ * all of the resulting pixels (widht * height * 3 bytes). If it returns NULL,
+ * then the decoder will abort, but return true, as the caller has received
+ * valid dimensions.
+ */
+ virtual uint8* SetSize(int width, int height) = 0;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderCallback);
+};
+
+class BmpDecoderHelper {
+ public:
+ BmpDecoderHelper() { }
+ ~BmpDecoderHelper() { }
+ bool DecodeImage(const char* data,
+ int len,
+ int max_pixels,
+ BmpDecoderCallback* callback);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderHelper);
+
+ void DoRLEDecode();
+ void DoStandardDecode();
+ void PutPixel(int x, int y, uint8 col);
+
+ int GetInt();
+ int GetShort();
+ uint8 GetByte();
+ int CalcShiftRight(uint32 mask);
+ int CalcShiftLeft(uint32 mask);
+
+ const uint8* data_;
+ int pos_;
+ int len_;
+ int width_;
+ int height_;
+ int bpp_;
+ int pixelPad_;
+ int rowPad_;
+ scoped_array<uint8> colTab_;
+ uint32 redBits_;
+ uint32 greenBits_;
+ uint32 blueBits_;
+ int redShiftRight_;
+ int greenShiftRight_;
+ int blueShiftRight_;
+ int redShiftLeft_;
+ int greenShiftLeft_;
+ int blueShiftLeft_;
+ uint8* output_;
+ bool inverted_;
+};
+
+} // namespace
+
+#endif
diff --git a/images/transform_scanline.h b/images/transform_scanline.h
new file mode 100644
index 00000000..36efdd84
--- /dev/null
+++ b/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/lazy/SkBitmapFactory.cpp b/lazy/SkBitmapFactory.cpp
new file mode 100644
index 00000000..0ff4ee2d
--- /dev/null
+++ b/lazy/SkBitmapFactory.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 "SkImageCache.h"
+#include "SkImagePriv.h"
+#include "SkLazyPixelRef.h"
+
+SK_DEFINE_INST_COUNT(SkBitmapFactory::CacheSelector)
+
+SkBitmapFactory::SkBitmapFactory(SkBitmapFactory::DecodeProc proc)
+ : fDecodeProc(proc)
+ , fImageCache(NULL)
+ , fCacheSelector(NULL) {
+ SkASSERT(fDecodeProc != NULL);
+}
+
+SkBitmapFactory::~SkBitmapFactory() {
+ SkSafeUnref(fImageCache);
+ SkSafeUnref(fCacheSelector);
+}
+
+void SkBitmapFactory::setImageCache(SkImageCache *cache) {
+ SkRefCnt_SafeAssign(fImageCache, cache);
+ if (cache != NULL) {
+ SkSafeUnref(fCacheSelector);
+ fCacheSelector = NULL;
+ }
+}
+
+void SkBitmapFactory::setCacheSelector(CacheSelector* selector) {
+ SkRefCnt_SafeAssign(fCacheSelector, selector);
+ if (selector != NULL) {
+ SkSafeUnref(fImageCache);
+ fImageCache = NULL;
+ }
+}
+
+bool SkBitmapFactory::installPixelRef(SkData* data, SkBitmap* dst) {
+ if (NULL == data || 0 == data->size() || dst == NULL) {
+ return false;
+ }
+
+ SkImage::Info info;
+ if (!fDecodeProc(data->data(), data->size(), &info, NULL)) {
+ return false;
+ }
+
+ bool isOpaque = false;
+ SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+ Target target;
+ // FIMXE: There will be a problem if this rowbytes is calculated differently from
+ // in SkLazyPixelRef.
+ target.fRowBytes = SkImageMinRowBytes(info);
+
+ dst->setConfig(config, info.fWidth, info.fHeight, target.fRowBytes);
+ dst->setIsOpaque(isOpaque);
+
+ // fImageCache and fCacheSelector are mutually exclusive.
+ SkASSERT(NULL == fImageCache || NULL == fCacheSelector);
+
+ SkImageCache* cache = NULL == fCacheSelector ? fImageCache : fCacheSelector->selectCache(info);
+
+ if (cache != NULL) {
+ // Now set a new LazyPixelRef on dst.
+ SkAutoTUnref<SkLazyPixelRef> lazyRef(SkNEW_ARGS(SkLazyPixelRef,
+ (data, fDecodeProc, cache)));
+ dst->setPixelRef(lazyRef);
+ return true;
+ } else {
+ dst->allocPixels();
+ target.fAddr = dst->getPixels();
+ return fDecodeProc(data->data(), data->size(), &info, &target);
+ }
+}
diff --git a/lazy/SkLazyPixelRef.cpp b/lazy/SkLazyPixelRef.cpp
new file mode 100644
index 00000000..dc9aef9f
--- /dev/null
+++ b/lazy/SkLazyPixelRef.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 "Sk64.h"
+#include "SkLazyPixelRef.h"
+#include "SkColorTable.h"
+#include "SkData.h"
+#include "SkImageCache.h"
+#include "SkImagePriv.h"
+
+#if LAZY_CACHE_STATS
+#include "SkThread.h"
+
+int32_t SkLazyPixelRef::gCacheHits;
+int32_t SkLazyPixelRef::gCacheMisses;
+#endif
+
+SkLazyPixelRef::SkLazyPixelRef(SkData* data, SkBitmapFactory::DecodeProc proc, SkImageCache* cache)
+ // Pass NULL for the Mutex so that the default (ring buffer) will be used.
+ : INHERITED(NULL)
+ , fDecodeProc(proc)
+ , fImageCache(cache)
+ , fCacheId(SkImageCache::UNINITIALIZED_ID)
+ , fRowBytes(0) {
+ SkASSERT(fDecodeProc != NULL);
+ if (NULL == data) {
+ fData = SkData::NewEmpty();
+ fErrorInDecoding = true;
+ } else {
+ fData = data;
+ fData->ref();
+ fErrorInDecoding = data->size() == 0;
+ }
+ SkASSERT(cache != NULL);
+ cache->ref();
+ // Since this pixel ref bases its data on encoded data, it should never change.
+ this->setImmutable();
+}
+
+SkLazyPixelRef::~SkLazyPixelRef() {
+ SkASSERT(fData != NULL);
+ fData->unref();
+ SkASSERT(fImageCache);
+ if (fCacheId != SkImageCache::UNINITIALIZED_ID) {
+ fImageCache->throwAwayCache(fCacheId);
+ }
+ fImageCache->unref();
+}
+
+static size_t ComputeMinRowBytesAndSize(const SkImage::Info& info, size_t* rowBytes) {
+ *rowBytes = SkImageMinRowBytes(info);
+
+ Sk64 safeSize;
+ safeSize.setZero();
+ if (info.fHeight > 0) {
+ safeSize.setMul(info.fHeight, SkToS32(*rowBytes));
+ }
+ SkASSERT(!safeSize.isNeg());
+ return safeSize.is32() ? safeSize.get32() : 0;
+}
+
+void* SkLazyPixelRef::onLockPixels(SkColorTable**) {
+ if (fErrorInDecoding) {
+ return NULL;
+ }
+ SkBitmapFactory::Target target;
+ // Check to see if the pixels still exist in the cache.
+ if (SkImageCache::UNINITIALIZED_ID == fCacheId) {
+ target.fAddr = NULL;
+ } else {
+ SkImageCache::DataStatus status;
+ target.fAddr = fImageCache->pinCache(fCacheId, &status);
+ if (target.fAddr == NULL) {
+ fCacheId = SkImageCache::UNINITIALIZED_ID;
+ } else {
+ if (SkImageCache::kRetained_DataStatus == status) {
+#if LAZY_CACHE_STATS
+ sk_atomic_inc(&gCacheHits);
+#endif
+ return target.fAddr;
+ }
+ SkASSERT(SkImageCache::kUninitialized_DataStatus == status);
+ }
+ // Cache miss. Either pinCache returned NULL or it returned a memory address without the old
+ // data
+#if LAZY_CACHE_STATS
+ sk_atomic_inc(&gCacheMisses);
+#endif
+ }
+ SkImage::Info info;
+ SkASSERT(fData != NULL && fData->size() > 0);
+ if (NULL == target.fAddr) {
+ // Determine the size of the image in order to determine how much memory to allocate.
+ // FIXME: As an optimization, only do this part once.
+ fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
+ if (fErrorInDecoding) {
+ // We can only reach here if fCacheId was already set to UNINITIALIZED_ID, or if
+ // pinCache returned NULL, in which case it was reset to UNINITIALIZED_ID.
+ SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
+ return NULL;
+ }
+
+ size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
+ target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
+ if (NULL == target.fAddr) {
+ // Space could not be allocated.
+ // Just like the last assert, fCacheId must be UNINITIALIZED_ID.
+ SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
+ return NULL;
+ }
+ } else {
+ // pinCache returned purged memory to which target.fAddr already points. Set
+ // target.fRowBytes properly.
+ target.fRowBytes = fRowBytes;
+ // Assume that the size is correct, since it was determined by this same function
+ // previously.
+ }
+ SkASSERT(target.fAddr != NULL);
+ SkASSERT(SkImageCache::UNINITIALIZED_ID != fCacheId);
+ fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, &target);
+ if (fErrorInDecoding) {
+ fImageCache->throwAwayCache(fCacheId);
+ fCacheId = SkImageCache::UNINITIALIZED_ID;
+ return NULL;
+ }
+ // Upon success, store fRowBytes so it can be used in case pinCache later returns purged memory.
+ fRowBytes = target.fRowBytes;
+ return target.fAddr;
+}
+
+void SkLazyPixelRef::onUnlockPixels() {
+ if (fErrorInDecoding) {
+ return;
+ }
+ if (fCacheId != SkImageCache::UNINITIALIZED_ID) {
+ fImageCache->releaseCache(fCacheId);
+ }
+}
+
+SkData* SkLazyPixelRef::onRefEncodedData() {
+ fData->ref();
+ return fData;
+}
diff --git a/lazy/SkLazyPixelRef.h b/lazy/SkLazyPixelRef.h
new file mode 100644
index 00000000..fd41dd46
--- /dev/null
+++ b/lazy/SkLazyPixelRef.h
@@ -0,0 +1,81 @@
+/*
+ * 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 SkLazyPixelRef_DEFINED
+#define SkLazyPixelRef_DEFINED
+
+#include "SkBitmapFactory.h"
+#include "SkImage.h"
+#include "SkPixelRef.h"
+#include "SkFlattenable.h"
+
+class SkColorTable;
+class SkData;
+class SkImageCache;
+
+#ifdef SK_DEBUG
+ #define LAZY_CACHE_STATS 1
+#elif !defined(LAZY_CACHE_STATS)
+ #define LAZY_CACHE_STATS 0
+#endif
+
+/**
+ * PixelRef which defers decoding until SkBitmap::lockPixels() is called.
+ */
+class SkLazyPixelRef : public SkPixelRef {
+
+public:
+ /**
+ * Create a new SkLazyPixelRef.
+ * @param SkData Encoded data representing the pixels.
+ * @param DecodeProc Called to decode the pixels when needed. Must be non-NULL.
+ * @param SkImageCache Object that handles allocating and freeing the pixel memory, as needed.
+ * Must not be NULL.
+ */
+ SkLazyPixelRef(SkData*, SkBitmapFactory::DecodeProc, SkImageCache*);
+
+ virtual ~SkLazyPixelRef();
+
+#ifdef SK_DEBUG
+ intptr_t getCacheId() const { return fCacheId; }
+#endif
+
+#if LAZY_CACHE_STATS
+ static int32_t GetCacheHits() { return gCacheHits; }
+ static int32_t GetCacheMisses() { return gCacheMisses; }
+ static void ResetCacheStats() { gCacheHits = gCacheMisses = 0; }
+#endif
+
+ // No need to flatten this object. When flattening an SkBitmap, SkOrderedWriteBuffer will check
+ // the encoded data and write that instead.
+ // Future implementations of SkFlattenableWriteBuffer will need to special case for
+ // onRefEncodedData as well.
+ SK_DECLARE_UNFLATTENABLE_OBJECT()
+
+protected:
+ virtual void* onLockPixels(SkColorTable**) SK_OVERRIDE;
+ virtual void onUnlockPixels() SK_OVERRIDE;
+ virtual bool onLockPixelsAreWritable() const SK_OVERRIDE { return false; }
+ virtual SkData* onRefEncodedData() SK_OVERRIDE;
+
+private:
+ bool fErrorInDecoding;
+ SkData* fData;
+ SkBitmapFactory::DecodeProc fDecodeProc;
+ SkImageCache* fImageCache;
+ intptr_t fCacheId;
+ size_t fRowBytes;
+
+#if LAZY_CACHE_STATS
+ static int32_t gCacheHits;
+ static int32_t gCacheMisses;
+#endif
+
+ typedef SkPixelRef INHERITED;
+};
+
+#endif // SkLazyPixelRef_DEFINED
diff --git a/lazy/SkLruImageCache.cpp b/lazy/SkLruImageCache.cpp
new file mode 100644
index 00000000..40cfefa2
--- /dev/null
+++ b/lazy/SkLruImageCache.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLruImageCache.h"
+
+SK_DEFINE_INST_COUNT(SkImageCache)
+SK_DEFINE_INST_COUNT(SkLruImageCache)
+
+static intptr_t NextGenerationID() {
+ static intptr_t gNextID;
+ do {
+ gNextID++;
+ } while (SkImageCache::UNINITIALIZED_ID == gNextID);
+ return gNextID;
+}
+
+class CachedPixels : public SkNoncopyable {
+
+public:
+ CachedPixels(size_t length)
+ : fLength(length)
+ , fID(NextGenerationID())
+ , fLocked(false) {
+ fAddr = sk_malloc_throw(length);
+ }
+
+ ~CachedPixels() {
+ sk_free(fAddr);
+ }
+
+ void* getData() { return fAddr; }
+
+ intptr_t getID() const { return fID; }
+
+ size_t getLength() const { return fLength; }
+
+ void lock() { SkASSERT(!fLocked); fLocked = true; }
+
+ void unlock() { SkASSERT(fLocked); fLocked = false; }
+
+ bool isLocked() const { return fLocked; }
+
+private:
+ void* fAddr;
+ size_t fLength;
+ const intptr_t fID;
+ bool fLocked;
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(CachedPixels);
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+
+SkLruImageCache::SkLruImageCache(size_t budget)
+ : fRamBudget(budget)
+ , fRamUsed(0) {}
+
+SkLruImageCache::~SkLruImageCache() {
+ // Don't worry about updating pointers. All will be deleted.
+ Iter iter;
+ CachedPixels* pixels = iter.init(fLRU, Iter::kTail_IterStart);
+ while (pixels != NULL) {
+ CachedPixels* prev = iter.prev();
+ SkASSERT(!pixels->isLocked());
+#ifdef SK_DEBUG
+ fRamUsed -= pixels->getLength();
+#endif
+ SkDELETE(pixels);
+ pixels = prev;
+ }
+#ifdef SK_DEBUG
+ SkASSERT(fRamUsed == 0);
+#endif
+}
+
+#ifdef SK_DEBUG
+SkImageCache::MemoryStatus SkLruImageCache::getMemoryStatus(intptr_t ID) const {
+ if (SkImageCache::UNINITIALIZED_ID == ID) {
+ return SkImageCache::kFreed_MemoryStatus;
+ }
+ SkAutoMutexAcquire ac(&fMutex);
+ CachedPixels* pixels = this->findByID(ID);
+ if (NULL == pixels) {
+ return SkImageCache::kFreed_MemoryStatus;
+ }
+ if (pixels->isLocked()) {
+ return SkImageCache::kPinned_MemoryStatus;
+ }
+ return SkImageCache::kUnpinned_MemoryStatus;
+}
+
+void SkLruImageCache::purgeAllUnpinnedCaches() {
+ SkAutoMutexAcquire ac(&fMutex);
+ this->purgeTilAtOrBelow(0);
+}
+#endif
+
+size_t SkLruImageCache::setImageCacheLimit(size_t newLimit) {
+ size_t oldLimit = fRamBudget;
+ SkAutoMutexAcquire ac(&fMutex);
+ fRamBudget = newLimit;
+ this->purgeIfNeeded();
+ return oldLimit;
+}
+
+void* SkLruImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
+ SkAutoMutexAcquire ac(&fMutex);
+ CachedPixels* pixels = SkNEW_ARGS(CachedPixels, (bytes));
+ if (ID != NULL) {
+ *ID = pixels->getID();
+ }
+ pixels->lock();
+ fRamUsed += bytes;
+ fLRU.addToHead(pixels);
+ this->purgeIfNeeded();
+ return pixels->getData();
+}
+
+void* SkLruImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&fMutex);
+ CachedPixels* pixels = this->findByID(ID);
+ if (NULL == pixels) {
+ return NULL;
+ }
+ if (pixels != fLRU.head()) {
+ fLRU.remove(pixels);
+ fLRU.addToHead(pixels);
+ }
+ SkASSERT(status != NULL);
+ // This cache will never return pinned memory whose data has been overwritten.
+ *status = SkImageCache::kRetained_DataStatus;
+ pixels->lock();
+ return pixels->getData();
+}
+
+void SkLruImageCache::releaseCache(intptr_t ID) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&fMutex);
+ CachedPixels* pixels = this->findByID(ID);
+ SkASSERT(pixels != NULL);
+ pixels->unlock();
+ this->purgeIfNeeded();
+}
+
+void SkLruImageCache::throwAwayCache(intptr_t ID) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&fMutex);
+ CachedPixels* pixels = this->findByID(ID);
+ if (pixels != NULL) {
+ if (pixels->isLocked()) {
+ pixels->unlock();
+ }
+ this->removePixels(pixels);
+ }
+}
+
+void SkLruImageCache::removePixels(CachedPixels* pixels) {
+ // Mutex is already locked.
+ SkASSERT(!pixels->isLocked());
+ const size_t size = pixels->getLength();
+ SkASSERT(size <= fRamUsed);
+ fLRU.remove(pixels);
+ SkDELETE(pixels);
+ fRamUsed -= size;
+}
+
+CachedPixels* SkLruImageCache::findByID(intptr_t ID) const {
+ // Mutex is already locked.
+ Iter iter;
+ // Start from the head, most recently used.
+ CachedPixels* pixels = iter.init(fLRU, Iter::kHead_IterStart);
+ while (pixels != NULL) {
+ if (pixels->getID() == ID) {
+ return pixels;
+ }
+ pixels = iter.next();
+ }
+ return NULL;
+}
+
+void SkLruImageCache::purgeIfNeeded() {
+ // Mutex is already locked.
+ if (fRamBudget > 0) {
+ this->purgeTilAtOrBelow(fRamBudget);
+ }
+}
+
+void SkLruImageCache::purgeTilAtOrBelow(size_t limit) {
+ // Mutex is already locked.
+ if (fRamUsed > limit) {
+ Iter iter;
+ // Start from the tail, least recently used.
+ CachedPixels* pixels = iter.init(fLRU, Iter::kTail_IterStart);
+ while (pixels != NULL && fRamUsed > limit) {
+ CachedPixels* prev = iter.prev();
+ if (!pixels->isLocked()) {
+ this->removePixels(pixels);
+ }
+ pixels = prev;
+ }
+ }
+}
diff --git a/lazy/SkPurgeableImageCache.cpp b/lazy/SkPurgeableImageCache.cpp
new file mode 100644
index 00000000..0d36e4a9
--- /dev/null
+++ b/lazy/SkPurgeableImageCache.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkThread.h"
+#include "SkPurgeableImageCache.h"
+#include "SkPurgeableMemoryBlock.h"
+
+#ifdef SK_DEBUG
+ #include "SkTSearch.h"
+#endif
+
+SK_DEFINE_INST_COUNT(SkPurgeableImageCache)
+SK_DECLARE_STATIC_MUTEX(gPurgeableImageMutex);
+
+SkImageCache* SkPurgeableImageCache::Create() {
+ if (!SkPurgeableMemoryBlock::IsSupported()) {
+ return NULL;
+ }
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+ static SkPurgeableImageCache gCache;
+ gCache.ref();
+ return &gCache;
+}
+
+SkPurgeableImageCache::SkPurgeableImageCache() {}
+
+#ifdef SK_DEBUG
+SkPurgeableImageCache::~SkPurgeableImageCache() {
+ SkASSERT(fRecs.count() == 0);
+}
+#endif
+
+
+void* SkPurgeableImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+ SkPurgeableMemoryBlock* block = SkPurgeableMemoryBlock::Create(bytes);
+ if (NULL == block) {
+ return NULL;
+ }
+
+ SkPurgeableMemoryBlock::PinResult pinResult;
+ void* data = block->pin(&pinResult);
+ if (NULL == data) {
+ SkDELETE(block);
+ return NULL;
+ }
+
+ SkASSERT(ID != NULL);
+ *ID = reinterpret_cast<intptr_t>(block);
+#ifdef SK_DEBUG
+ // Insert into the array of all recs:
+ int index = this->findRec(*ID);
+ SkASSERT(index < 0);
+ fRecs.insert(~index, 1, ID);
+#endif
+ return data;
+}
+
+void* SkPurgeableImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+ SkASSERT(this->findRec(ID) >= 0);
+ SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+ SkPurgeableMemoryBlock::PinResult pinResult;
+ void* data = block->pin(&pinResult);
+ if (NULL == data) {
+ this->removeRec(ID);
+ return NULL;
+ }
+
+ switch (pinResult) {
+ case SkPurgeableMemoryBlock::kRetained_PinResult:
+ *status = SkImageCache::kRetained_DataStatus;
+ break;
+
+ case SkPurgeableMemoryBlock::kUninitialized_PinResult:
+ *status = SkImageCache::kUninitialized_DataStatus;
+ break;
+
+ default:
+ // Invalid value. Treat as a failure to pin.
+ SkASSERT(false);
+ this->removeRec(ID);
+ return NULL;
+ }
+
+ return data;
+}
+
+void SkPurgeableImageCache::releaseCache(intptr_t ID) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+ SkASSERT(this->findRec(ID) >= 0);
+ SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+ block->unpin();
+}
+
+void SkPurgeableImageCache::throwAwayCache(intptr_t ID) {
+ SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+
+ this->removeRec(ID);
+}
+
+#ifdef SK_DEBUG
+SkImageCache::MemoryStatus SkPurgeableImageCache::getMemoryStatus(intptr_t ID) const {
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+ if (SkImageCache::UNINITIALIZED_ID == ID || this->findRec(ID) < 0) {
+ return SkImageCache::kFreed_MemoryStatus;
+ }
+
+ SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+ if (block->isPinned()) {
+ return SkImageCache::kPinned_MemoryStatus;
+ }
+ return SkImageCache::kUnpinned_MemoryStatus;
+}
+
+void SkPurgeableImageCache::purgeAllUnpinnedCaches() {
+ SkAutoMutexAcquire ac(&gPurgeableImageMutex);
+ if (SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks()) {
+ SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks();
+ } else {
+ // Go through the blocks, and purge them individually.
+ // Rather than deleting the blocks, which would interfere with further calls, purge them
+ // and keep them around.
+ for (int i = 0; i < fRecs.count(); i++) {
+ SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(fRecs[i]);
+ if (!block->isPinned()) {
+ if (!block->purge()) {
+ // FIXME: This should be more meaningful (which one, etc...)
+ SkDebugf("Failed to purge\n");
+ }
+ }
+ }
+ }
+}
+
+int SkPurgeableImageCache::findRec(intptr_t rec) const {
+ return SkTSearch(fRecs.begin(), fRecs.count(), rec, sizeof(intptr_t));
+}
+#endif
+
+void SkPurgeableImageCache::removeRec(intptr_t ID) {
+#ifdef SK_DEBUG
+ int index = this->findRec(ID);
+ SkASSERT(index >= 0);
+ fRecs.remove(index);
+#endif
+ SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
+ SkASSERT(!block->isPinned());
+ SkDELETE(block);
+}
diff --git a/lazy/SkPurgeableMemoryBlock.h b/lazy/SkPurgeableMemoryBlock.h
new file mode 100644
index 00000000..1750ad9d
--- /dev/null
+++ b/lazy/SkPurgeableMemoryBlock.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPurgeableMemoryBlock_DEFINED
+#define SkPurgeableMemoryBlock_DEFINED
+
+#include "SkTypes.h"
+
+class SkPurgeableMemoryBlock : public SkNoncopyable {
+
+public:
+ /**
+ * Whether or not this platform has an implementation for purgeable memory.
+ */
+ static bool IsSupported();
+
+ /**
+ * Create a new purgeable memory block of 'size' bytes. Returns NULL if not supported on this
+ * platform or on failure.
+ * @param size Number of bytes requested.
+ * @return A new block, or NULL on failure.
+ */
+ static SkPurgeableMemoryBlock* Create(size_t size);
+
+#ifdef SK_DEBUG
+ /**
+ * Whether the platform supports one shot purge of all unpinned blocks. If so,
+ * PurgeAllUnpinnedBlocks will be used to test a purge. Otherwise, purge will be called on
+ * individual blocks.
+ */
+ static bool PlatformSupportsPurgingAllUnpinnedBlocks();
+
+ /**
+ * Purge all unpinned blocks at once, if the platform supports it.
+ */
+ static bool PurgeAllUnpinnedBlocks();
+
+ // If PlatformSupportsPurgingAllUnpinnedBlocks returns true, this will not be called, so it can
+ // simply return false.
+ bool purge();
+
+ bool isPinned() const { return fPinned; }
+#endif
+
+ ~SkPurgeableMemoryBlock();
+
+ /**
+ * Output parameter for pin(), stating whether the data has been retained.
+ */
+ enum PinResult {
+ /**
+ * The data has been purged, or this is the first call to pin.
+ */
+ kUninitialized_PinResult,
+
+ /**
+ * The data has been retained. The memory contains the same data it held when unpin() was
+ * called.
+ */
+ kRetained_PinResult,
+ };
+
+ /**
+ * Pin the memory for use. Must not be called while already pinned.
+ * @param PinResult Whether the data was retained. Ignored on failure.
+ * @return Pointer to the pinned data on success. NULL on failure.
+ */
+ void* pin(PinResult*);
+
+ /**
+ * Unpin the data so it can be purged if necessary.
+ */
+ void unpin();
+
+private:
+ void* fAddr;
+ size_t fSize;
+ bool fPinned;
+#ifdef SK_BUILD_FOR_ANDROID
+ int fFD;
+#endif
+
+ // Unimplemented default constructor is private, to prevent manual creation.
+ SkPurgeableMemoryBlock();
+
+ // The correct way to create a new one is from the static Create.
+ SkPurgeableMemoryBlock(size_t);
+};
+
+#endif // SkPurgeableMemoryBlock_DEFINED
diff --git a/lazy/SkPurgeableMemoryBlock_common.cpp b/lazy/SkPurgeableMemoryBlock_common.cpp
new file mode 100644
index 00000000..a549c46e
--- /dev/null
+++ b/lazy/SkPurgeableMemoryBlock_common.cpp
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPurgeableMemoryBlock.h"
+
+SkPurgeableMemoryBlock* SkPurgeableMemoryBlock::Create(size_t size) {
+ SkASSERT(IsSupported());
+ if (!IsSupported()) {
+ return NULL;
+ }
+ return SkNEW_ARGS(SkPurgeableMemoryBlock, (size));
+}
diff --git a/opts/SkBitmapFilter_opts_SSE2.cpp b/opts/SkBitmapFilter_opts_SSE2.cpp
new file mode 100644
index 00000000..95492c59
--- /dev/null
+++ b/opts/SkBitmapFilter_opts_SSE2.cpp
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapProcState.h"
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkUnPreMultiply.h"
+#include "SkShader.h"
+#include "SkConvolver.h"
+
+#include "SkBitmapFilter_opts_SSE2.h"
+
+#include <emmintrin.h>
+
+#if 0
+static inline void print128i(__m128i value) {
+ int *v = (int*) &value;
+ printf("% .11d % .11d % .11d % .11d\n", v[0], v[1], v[2], v[3]);
+}
+
+static inline void print128i_16(__m128i value) {
+ short *v = (short*) &value;
+ printf("% .5d % .5d % .5d % .5d % .5d % .5d % .5d % .5d\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+}
+
+static inline void print128i_8(__m128i value) {
+ unsigned char *v = (unsigned char*) &value;
+ printf("%.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u %.3u\n",
+ v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7],
+ v[8], v[9], v[10], v[11], v[12], v[13], v[14], v[15]
+ );
+}
+
+static inline void print128f(__m128 value) {
+ float *f = (float*) &value;
+ printf("%3.4f %3.4f %3.4f %3.4f\n", f[0], f[1], f[2], f[3]);
+}
+#endif
+
+// because the border is handled specially, this is guaranteed to have all 16 pixels
+// available to it without running off the bitmap's edge.
+
+int debug_x = 20;
+int debug_y = 255;
+
+void highQualityFilter_SSE2(const SkBitmapProcState& s, int x, int y,
+ SkPMColor* SK_RESTRICT colors, int count) {
+
+ const int maxX = s.fBitmap->width() - 1;
+ const int maxY = s.fBitmap->height() - 1;
+
+ while (count-- > 0) {
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x),
+ SkIntToScalar(y), &srcPt);
+ srcPt.fX -= SK_ScalarHalf;
+ srcPt.fY -= SK_ScalarHalf;
+
+ int sx = SkScalarFloorToInt(srcPt.fX);
+ int sy = SkScalarFloorToInt(srcPt.fY);
+
+ __m128 weight = _mm_setzero_ps();
+ __m128 accum = _mm_setzero_ps();
+
+ int y0 = SkTMax(0, int(ceil(sy-s.getBitmapFilter()->width() + 0.5f)));
+ int y1 = SkTMin(maxY, int(floor(sy+s.getBitmapFilter()->width() + 0.5f)));
+ int x0 = SkTMax(0, int(ceil(sx-s.getBitmapFilter()->width() + 0.5f)));
+ int x1 = SkTMin(maxX, int(floor(sx+s.getBitmapFilter()->width() + 0.5f)));
+
+ for (int src_y = y0; src_y <= y1; src_y++) {
+ float yweight = SkScalarToFloat(s.getBitmapFilter()->lookupScalar(srcPt.fY - src_y));
+
+ for (int src_x = x0; src_x <= x1 ; src_x++) {
+ float xweight = SkScalarToFloat(s.getBitmapFilter()->lookupScalar(srcPt.fX - src_x));
+
+ float combined_weight = xweight * yweight;
+
+ SkPMColor color = *s.fBitmap->getAddr32(src_x, src_y);
+
+ __m128i c = _mm_cvtsi32_si128( color );
+ c = _mm_unpacklo_epi8(c, _mm_setzero_si128());
+ c = _mm_unpacklo_epi16(c, _mm_setzero_si128());
+
+ __m128 cfloat = _mm_cvtepi32_ps( c );
+
+ __m128 weightVector = _mm_set1_ps(combined_weight);
+
+ accum = _mm_add_ps(accum, _mm_mul_ps(cfloat, weightVector));
+ weight = _mm_add_ps( weight, weightVector );
+ }
+ }
+
+ accum = _mm_div_ps(accum, weight);
+ accum = _mm_add_ps(accum, _mm_set1_ps(0.5f));
+
+ __m128i accumInt = _mm_cvtps_epi32( accum );
+
+ int localResult[4];
+ _mm_storeu_si128((__m128i *) (localResult), accumInt);
+ int a = SkClampMax(localResult[0], 255);
+ int r = SkClampMax(localResult[1], a);
+ int g = SkClampMax(localResult[2], a);
+ int b = SkClampMax(localResult[3], a);
+
+ *colors++ = SkPackARGB32(a, r, g, b);
+
+ x++;
+ }
+}
+
+void highQualityFilter_ScaleOnly_SSE2(const SkBitmapProcState &s, int x, int y,
+ SkPMColor *SK_RESTRICT colors, int count) {
+ const int maxX = s.fBitmap->width() - 1;
+ const int maxY = s.fBitmap->height() - 1;
+
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x),
+ SkIntToScalar(y), &srcPt);
+ srcPt.fY -= SK_ScalarHalf;
+ int sy = SkScalarFloorToInt(srcPt.fY);
+
+ int y0 = SkTMax(0, int(ceil(sy-s.getBitmapFilter()->width() + 0.5f)));
+ int y1 = SkTMin(maxY, int(floor(sy+s.getBitmapFilter()->width() + 0.5f)));
+
+ while (count-- > 0) {
+ srcPt.fX -= SK_ScalarHalf;
+ srcPt.fY -= SK_ScalarHalf;
+
+ int sx = SkScalarFloorToInt(srcPt.fX);
+
+ float weight = 0;
+ __m128 accum = _mm_setzero_ps();
+
+ int x0 = SkTMax(0, int(ceil(sx-s.getBitmapFilter()->width() + 0.5f)));
+ int x1 = SkTMin(maxX, int(floor(sx+s.getBitmapFilter()->width() + 0.5f)));
+
+ for (int src_y = y0; src_y <= y1; src_y++) {
+ float yweight = SkScalarToFloat(s.getBitmapFilter()->lookupScalar(srcPt.fY - src_y));
+
+ for (int src_x = x0; src_x <= x1 ; src_x++) {
+ float xweight = SkScalarToFloat(s.getBitmapFilter()->lookupScalar(srcPt.fX - src_x));
+
+ float combined_weight = xweight * yweight;
+
+ SkPMColor color = *s.fBitmap->getAddr32(src_x, src_y);
+
+ __m128 c = _mm_set_ps((float)SkGetPackedB32(color),
+ (float)SkGetPackedG32(color),
+ (float)SkGetPackedR32(color),
+ (float)SkGetPackedA32(color));
+
+ __m128 weightVector = _mm_set1_ps(combined_weight);
+
+ accum = _mm_add_ps(accum, _mm_mul_ps(c, weightVector));
+ weight += combined_weight;
+ }
+ }
+
+ __m128 totalWeightVector = _mm_set1_ps(weight);
+ accum = _mm_div_ps(accum, totalWeightVector);
+ accum = _mm_add_ps(accum, _mm_set1_ps(0.5f));
+
+ float localResult[4];
+ _mm_storeu_ps(localResult, accum);
+ int a = SkClampMax(int(localResult[0]), 255);
+ int r = SkClampMax(int(localResult[1]), a);
+ int g = SkClampMax(int(localResult[2]), a);
+ int b = SkClampMax(int(localResult[3]), a);
+
+ *colors++ = SkPackARGB32(a, r, g, b);
+
+ x++;
+
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x),
+ SkIntToScalar(y), &srcPt);
+
+ }
+}
+
+// Convolves horizontally along a single row. The row data is given in
+// |src_data| and continues for the num_values() of the filter.
+void convolveHorizontally_SSE2(const unsigned char* src_data,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* out_row,
+ bool /*has_alpha*/) {
+ int num_values = filter.numValues();
+
+ int filter_offset, filter_length;
+ __m128i zero = _mm_setzero_si128();
+ __m128i mask[4];
+ // |mask| will be used to decimate all extra filter coefficients that are
+ // loaded by SIMD when |filter_length| is not divisible by 4.
+ // mask[0] is not used in following algorithm.
+ mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
+ mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
+ mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
+
+ // Output one pixel each iteration, calculating all channels (RGBA) together.
+ for (int out_x = 0; out_x < num_values; out_x++) {
+ const SkConvolutionFilter1D::ConvolutionFixed* filter_values =
+ filter.FilterForValue(out_x, &filter_offset, &filter_length);
+
+ __m128i accum = _mm_setzero_si128();
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filter_length| pixels (4 bytes each) after this.
+ const __m128i* row_to_filter =
+ reinterpret_cast<const __m128i*>(&src_data[filter_offset << 2]);
+
+ // We will load and accumulate with four coefficients per iteration.
+ for (int filter_x = 0; filter_x < filter_length >> 2; filter_x++) {
+
+ // Load 4 coefficients => duplicate 1st and 2nd of them for all channels.
+ __m128i coeff, coeff16;
+ // [16] xx xx xx xx c3 c2 c1 c0
+ coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
+ // [16] xx xx xx xx c1 c1 c0 c0
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
+ // [16] c1 c1 c1 c1 c0 c0 c0 c0
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+
+ // Load four pixels => unpack the first two pixels to 16 bits =>
+ // multiply with coefficients => accumulate the convolution result.
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src8 = _mm_loadu_si128(row_to_filter);
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0*c0 b0*c0 g0*c0 r0*c0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ // [32] a1*c1 b1*c1 g1*c1 r1*c1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+
+ // Duplicate 3rd and 4th coefficients for all channels =>
+ // unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients
+ // => accumulate the convolution results.
+ // [16] xx xx xx xx c3 c3 c2 c2
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
+ // [16] c3 c3 c3 c3 c2 c2 c2 c2
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+ // [16] a3 g3 b3 r3 a2 g2 b2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2*c2 b2*c2 g2*c2 r2*c2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ // [32] a3*c3 b3*c3 g3*c3 r3*c3
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+
+ // Advance the pixel and coefficients pointers.
+ row_to_filter += 1;
+ filter_values += 4;
+ }
+
+ // When |filter_length| is not divisible by 4, we need to decimate some of
+ // the filter coefficient that was loaded incorrectly to zero; Other than
+ // that the algorithm is same with above, exceot that the 4th pixel will be
+ // always absent.
+ int r = filter_length&3;
+ if (r) {
+ // Note: filter_values must be padded to align_up(filter_offset, 8).
+ __m128i coeff, coeff16;
+ coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
+ // Mask out extra filter taps.
+ coeff = _mm_and_si128(coeff, mask[r]);
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+
+ // Note: line buffer must be padded to align_up(filter_offset, 16).
+ // We resolve this by use C-version for the last horizontal line.
+ __m128i src8 = _mm_loadu_si128(row_to_filter);
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
+ coeff16 = _mm_unpacklo_epi16(coeff16, coeff16);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum = _mm_add_epi32(accum, t);
+ }
+
+ // Shift right for fixed point implementation.
+ accum = _mm_srai_epi32(accum, SkConvolutionFilter1D::kShiftBits);
+
+ // Packing 32 bits |accum| to 16 bits per channel (signed saturation).
+ accum = _mm_packs_epi32(accum, zero);
+ // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
+ accum = _mm_packus_epi16(accum, zero);
+
+ // Store the pixel value of 32 bits.
+ *(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum);
+ out_row += 4;
+ }
+}
+
+// Convolves horizontally along four rows. The row data is given in
+// |src_data| and continues for the num_values() of the filter.
+// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please
+// refer to that function for detailed comments.
+void convolve4RowsHorizontally_SSE2(const unsigned char* src_data[4],
+ const SkConvolutionFilter1D& filter,
+ unsigned char* out_row[4]) {
+ int num_values = filter.numValues();
+
+ int filter_offset, filter_length;
+ __m128i zero = _mm_setzero_si128();
+ __m128i mask[4];
+ // |mask| will be used to decimate all extra filter coefficients that are
+ // loaded by SIMD when |filter_length| is not divisible by 4.
+ // mask[0] is not used in following algorithm.
+ mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1);
+ mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1);
+ mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1);
+
+ // Output one pixel each iteration, calculating all channels (RGBA) together.
+ for (int out_x = 0; out_x < num_values; out_x++) {
+ const SkConvolutionFilter1D::ConvolutionFixed* filter_values =
+ filter.FilterForValue(out_x, &filter_offset, &filter_length);
+
+ // four pixels in a column per iteration.
+ __m128i accum0 = _mm_setzero_si128();
+ __m128i accum1 = _mm_setzero_si128();
+ __m128i accum2 = _mm_setzero_si128();
+ __m128i accum3 = _mm_setzero_si128();
+ int start = (filter_offset<<2);
+ // We will load and accumulate with four coefficients per iteration.
+ for (int filter_x = 0; filter_x < (filter_length >> 2); filter_x++) {
+ __m128i coeff, coeff16lo, coeff16hi;
+ // [16] xx xx xx xx c3 c2 c1 c0
+ coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
+ // [16] xx xx xx xx c1 c1 c0 c0
+ coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
+ // [16] c1 c1 c1 c1 c0 c0 c0 c0
+ coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo);
+ // [16] xx xx xx xx c3 c3 c2 c2
+ coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
+ // [16] c3 c3 c3 c3 c2 c2 c2 c2
+ coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi);
+
+ __m128i src8, src16, mul_hi, mul_lo, t;
+
+#define ITERATION(src, accum) \
+ src8 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src)); \
+ src16 = _mm_unpacklo_epi8(src8, zero); \
+ mul_hi = _mm_mulhi_epi16(src16, coeff16lo); \
+ mul_lo = _mm_mullo_epi16(src16, coeff16lo); \
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi); \
+ accum = _mm_add_epi32(accum, t); \
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi); \
+ accum = _mm_add_epi32(accum, t); \
+ src16 = _mm_unpackhi_epi8(src8, zero); \
+ mul_hi = _mm_mulhi_epi16(src16, coeff16hi); \
+ mul_lo = _mm_mullo_epi16(src16, coeff16hi); \
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi); \
+ accum = _mm_add_epi32(accum, t); \
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi); \
+ accum = _mm_add_epi32(accum, t)
+
+ ITERATION(src_data[0] + start, accum0);
+ ITERATION(src_data[1] + start, accum1);
+ ITERATION(src_data[2] + start, accum2);
+ ITERATION(src_data[3] + start, accum3);
+
+ start += 16;
+ filter_values += 4;
+ }
+
+ int r = filter_length & 3;
+ if (r) {
+ // Note: filter_values must be padded to align_up(filter_offset, 8);
+ __m128i coeff;
+ coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values));
+ // Mask out extra filter taps.
+ coeff = _mm_and_si128(coeff, mask[r]);
+
+ __m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0));
+ /* c1 c1 c1 c1 c0 c0 c0 c0 */
+ coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo);
+ __m128i coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2));
+ coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi);
+
+ __m128i src8, src16, mul_hi, mul_lo, t;
+
+ ITERATION(src_data[0] + start, accum0);
+ ITERATION(src_data[1] + start, accum1);
+ ITERATION(src_data[2] + start, accum2);
+ ITERATION(src_data[3] + start, accum3);
+ }
+
+ accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum0 = _mm_packs_epi32(accum0, zero);
+ accum0 = _mm_packus_epi16(accum0, zero);
+ accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum1 = _mm_packs_epi32(accum1, zero);
+ accum1 = _mm_packus_epi16(accum1, zero);
+ accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits);
+ accum2 = _mm_packs_epi32(accum2, zero);
+ accum2 = _mm_packus_epi16(accum2, zero);
+ accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits);
+ accum3 = _mm_packs_epi32(accum3, zero);
+ accum3 = _mm_packus_epi16(accum3, zero);
+
+ *(reinterpret_cast<int*>(out_row[0])) = _mm_cvtsi128_si32(accum0);
+ *(reinterpret_cast<int*>(out_row[1])) = _mm_cvtsi128_si32(accum1);
+ *(reinterpret_cast<int*>(out_row[2])) = _mm_cvtsi128_si32(accum2);
+ *(reinterpret_cast<int*>(out_row[3])) = _mm_cvtsi128_si32(accum3);
+
+ out_row[0] += 4;
+ out_row[1] += 4;
+ out_row[2] += 4;
+ out_row[3] += 4;
+ }
+}
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |source_data_rows| array, with each row
+// being |pixel_width| wide.
+//
+// The output must have room for |pixel_width * 4| bytes.
+template<bool has_alpha>
+void convolveVertically_SSE2(const SkConvolutionFilter1D::ConvolutionFixed* filter_values,
+ int filter_length,
+ unsigned char* const* source_data_rows,
+ int pixel_width,
+ unsigned char* out_row) {
+ int width = pixel_width & ~3;
+
+ __m128i zero = _mm_setzero_si128();
+ __m128i accum0, accum1, accum2, accum3, coeff16;
+ const __m128i* src;
+ // Output four pixels per iteration (16 bytes).
+ for (int out_x = 0; out_x < width; out_x += 4) {
+
+ // Accumulated result for each pixel. 32 bits per RGBA channel.
+ accum0 = _mm_setzero_si128();
+ accum1 = _mm_setzero_si128();
+ accum2 = _mm_setzero_si128();
+ accum3 = _mm_setzero_si128();
+
+ // Convolve with one filter coefficient per iteration.
+ for (int filter_y = 0; filter_y < filter_length; filter_y++) {
+
+ // Duplicate the filter coefficient 8 times.
+ // [16] cj cj cj cj cj cj cj cj
+ coeff16 = _mm_set1_epi16(filter_values[filter_y]);
+
+ // Load four pixels (16 bytes) together.
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ src = reinterpret_cast<const __m128i*>(
+ &source_data_rows[filter_y][out_x << 2]);
+ __m128i src8 = _mm_loadu_si128(src);
+
+ // Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels =>
+ // multiply with current coefficient => accumulate the result.
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0 b0 g0 r0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum0 = _mm_add_epi32(accum0, t);
+ // [32] a1 b1 g1 r1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum1 = _mm_add_epi32(accum1, t);
+
+ // Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels =>
+ // multiply with current coefficient => accumulate the result.
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2 b2 g2 r2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum2 = _mm_add_epi32(accum2, t);
+ // [32] a3 b3 g3 r3
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum3 = _mm_add_epi32(accum3, t);
+ }
+
+ // Shift right for fixed point implementation.
+ accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits);
+ accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits);
+
+ // Packing 32 bits |accum| to 16 bits per channel (signed saturation).
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packs_epi32(accum0, accum1);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ accum2 = _mm_packs_epi32(accum2, accum3);
+
+ // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation).
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packus_epi16(accum0, accum2);
+
+ if (has_alpha) {
+ // Compute the max(ri, gi, bi) for each pixel.
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ __m128i a = _mm_srli_epi32(accum0, 8);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ __m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = _mm_srli_epi32(accum0, 16);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = _mm_max_epu8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = _mm_slli_epi32(b, 24);
+
+ // Make sure the value of alpha channel is always larger than maximum
+ // value of color channels.
+ accum0 = _mm_max_epu8(b, accum0);
+ } else {
+ // Set value of alpha channels to 0xFF.
+ __m128i mask = _mm_set1_epi32(0xff000000);
+ accum0 = _mm_or_si128(accum0, mask);
+ }
+
+ // Store the convolution result (16 bytes) and advance the pixel pointers.
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(out_row), accum0);
+ out_row += 16;
+ }
+
+ // When the width of the output is not divisible by 4, We need to save one
+ // pixel (4 bytes) each time. And also the fourth pixel is always absent.
+ if (pixel_width & 3) {
+ accum0 = _mm_setzero_si128();
+ accum1 = _mm_setzero_si128();
+ accum2 = _mm_setzero_si128();
+ for (int filter_y = 0; filter_y < filter_length; ++filter_y) {
+ coeff16 = _mm_set1_epi16(filter_values[filter_y]);
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ src = reinterpret_cast<const __m128i*>(
+ &source_data_rows[filter_y][width<<2]);
+ __m128i src8 = _mm_loadu_si128(src);
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ __m128i src16 = _mm_unpacklo_epi8(src8, zero);
+ __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ __m128i mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a0 b0 g0 r0
+ __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum0 = _mm_add_epi32(accum0, t);
+ // [32] a1 b1 g1 r1
+ t = _mm_unpackhi_epi16(mul_lo, mul_hi);
+ accum1 = _mm_add_epi32(accum1, t);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ src16 = _mm_unpackhi_epi8(src8, zero);
+ mul_hi = _mm_mulhi_epi16(src16, coeff16);
+ mul_lo = _mm_mullo_epi16(src16, coeff16);
+ // [32] a2 b2 g2 r2
+ t = _mm_unpacklo_epi16(mul_lo, mul_hi);
+ accum2 = _mm_add_epi32(accum2, t);
+ }
+
+ accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits);
+ accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits);
+ accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits);
+ // [16] a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packs_epi32(accum0, accum1);
+ // [16] a3 b3 g3 r3 a2 b2 g2 r2
+ accum2 = _mm_packs_epi32(accum2, zero);
+ // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0
+ accum0 = _mm_packus_epi16(accum0, accum2);
+ if (has_alpha) {
+ // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0
+ __m128i a = _mm_srli_epi32(accum0, 8);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ __m128i b = _mm_max_epu8(a, accum0); // Max of r and g.
+ // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0
+ a = _mm_srli_epi32(accum0, 16);
+ // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0
+ b = _mm_max_epu8(a, b); // Max of r and g and b.
+ // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00
+ b = _mm_slli_epi32(b, 24);
+ accum0 = _mm_max_epu8(b, accum0);
+ } else {
+ __m128i mask = _mm_set1_epi32(0xff000000);
+ accum0 = _mm_or_si128(accum0, mask);
+ }
+
+ for (int out_x = width; out_x < pixel_width; out_x++) {
+ *(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum0);
+ accum0 = _mm_srli_si128(accum0, 4);
+ out_row += 4;
+ }
+ }
+}
+
+void convolveVertically_SSE2(const SkConvolutionFilter1D::ConvolutionFixed* filter_values,
+ int filter_length,
+ unsigned char* const* source_data_rows,
+ int pixel_width,
+ unsigned char* out_row,
+ bool has_alpha) {
+ if (has_alpha) {
+ convolveVertically_SSE2<true>(filter_values,
+ filter_length,
+ source_data_rows,
+ pixel_width,
+ out_row);
+ } else {
+ convolveVertically_SSE2<false>(filter_values,
+ filter_length,
+ source_data_rows,
+ pixel_width,
+ out_row);
+ }
+}
+
+void applySIMDPadding_SSE2(SkConvolutionFilter1D *filter) {
+ // Padding |paddingCount| of more dummy coefficients after the coefficients
+ // of last filter to prevent SIMD instructions which load 8 or 16 bytes
+ // together to access invalid memory areas. We are not trying to align the
+ // coefficients right now due to the opaqueness of <vector> implementation.
+ // This has to be done after all |AddFilter| calls.
+ for (int i = 0; i < 8; ++i) {
+ filter->addFilterValue(static_cast<SkConvolutionFilter1D::ConvolutionFixed>(0));
+ }
+}
diff --git a/opts/SkBitmapFilter_opts_SSE2.h b/opts/SkBitmapFilter_opts_SSE2.h
new file mode 100644
index 00000000..588f4ef1
--- /dev/null
+++ b/opts/SkBitmapFilter_opts_SSE2.h
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkBitmapFilter_opts_sse2_DEFINED
+#define SkBitmapFilter_opts_sse2_DEFINED
+
+#include "SkBitmapProcState.h"
+#include "SkConvolver.h"
+
+void highQualityFilter_ScaleOnly_SSE2(const SkBitmapProcState &s, int x, int y,
+ SkPMColor *SK_RESTRICT colors, int count);
+void highQualityFilter_SSE2(const SkBitmapProcState &s, int x, int y,
+ SkPMColor *SK_RESTRICT colors, int count);
+
+
+void convolveVertically_SSE2(const SkConvolutionFilter1D::ConvolutionFixed* filter_values,
+ int filter_length,
+ unsigned char* const* source_data_rows,
+ int pixel_width,
+ unsigned char* out_row,
+ bool has_alpha);
+void convolve4RowsHorizontally_SSE2(const unsigned char* src_data[4],
+ const SkConvolutionFilter1D& filter,
+ unsigned char* out_row[4]);
+void convolveHorizontally_SSE2(const unsigned char* src_data,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* out_row,
+ bool has_alpha);
+void applySIMDPadding_SSE2(SkConvolutionFilter1D* filter);
+
+#endif
diff --git a/opts/SkBitmapProcState_arm_neon.cpp b/opts/SkBitmapProcState_arm_neon.cpp
new file mode 100644
index 00000000..d50707dc
--- /dev/null
+++ b/opts/SkBitmapProcState_arm_neon.cpp
@@ -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.
+ */
+#include "SkBitmapProcState.h"
+#include "SkBitmapProcState_filter.h"
+#include "SkColorPriv.h"
+#include "SkFilterProc.h"
+#include "SkPaint.h"
+#include "SkShader.h" // for tilemodes
+#include "SkUtilsArm.h"
+
+// Required to ensure the table is part of the final binary.
+extern const SkBitmapProcState::SampleProc32 gSkBitmapProcStateSample32_neon[];
+extern const SkBitmapProcState::SampleProc16 gSkBitmapProcStateSample16_neon[];
+
+#define NAME_WRAP(x) x ## _neon
+#include "SkBitmapProcState_filter_neon.h"
+#include "SkBitmapProcState_procs.h"
+
+const SkBitmapProcState::SampleProc32 gSkBitmapProcStateSample32_neon[] = {
+ S32_opaque_D32_nofilter_DXDY_neon,
+ S32_alpha_D32_nofilter_DXDY_neon,
+ S32_opaque_D32_nofilter_DX_neon,
+ S32_alpha_D32_nofilter_DX_neon,
+ S32_opaque_D32_filter_DXDY_neon,
+ S32_alpha_D32_filter_DXDY_neon,
+ S32_opaque_D32_filter_DX_neon,
+ S32_alpha_D32_filter_DX_neon,
+
+ S16_opaque_D32_nofilter_DXDY_neon,
+ S16_alpha_D32_nofilter_DXDY_neon,
+ S16_opaque_D32_nofilter_DX_neon,
+ S16_alpha_D32_nofilter_DX_neon,
+ S16_opaque_D32_filter_DXDY_neon,
+ S16_alpha_D32_filter_DXDY_neon,
+ S16_opaque_D32_filter_DX_neon,
+ S16_alpha_D32_filter_DX_neon,
+
+ SI8_opaque_D32_nofilter_DXDY_neon,
+ SI8_alpha_D32_nofilter_DXDY_neon,
+ SI8_opaque_D32_nofilter_DX_neon,
+ SI8_alpha_D32_nofilter_DX_neon,
+ SI8_opaque_D32_filter_DXDY_neon,
+ SI8_alpha_D32_filter_DXDY_neon,
+ SI8_opaque_D32_filter_DX_neon,
+ SI8_alpha_D32_filter_DX_neon,
+
+ S4444_opaque_D32_nofilter_DXDY_neon,
+ S4444_alpha_D32_nofilter_DXDY_neon,
+ S4444_opaque_D32_nofilter_DX_neon,
+ S4444_alpha_D32_nofilter_DX_neon,
+ S4444_opaque_D32_filter_DXDY_neon,
+ S4444_alpha_D32_filter_DXDY_neon,
+ S4444_opaque_D32_filter_DX_neon,
+ S4444_alpha_D32_filter_DX_neon,
+
+ // A8 treats alpha/opauqe the same (equally efficient)
+ SA8_alpha_D32_nofilter_DXDY_neon,
+ SA8_alpha_D32_nofilter_DXDY_neon,
+ SA8_alpha_D32_nofilter_DX_neon,
+ SA8_alpha_D32_nofilter_DX_neon,
+ SA8_alpha_D32_filter_DXDY_neon,
+ SA8_alpha_D32_filter_DXDY_neon,
+ SA8_alpha_D32_filter_DX_neon,
+ SA8_alpha_D32_filter_DX_neon
+};
+
+const SkBitmapProcState::SampleProc16 gSkBitmapProcStateSample16_neon[] = {
+ S32_D16_nofilter_DXDY_neon,
+ S32_D16_nofilter_DX_neon,
+ S32_D16_filter_DXDY_neon,
+ S32_D16_filter_DX_neon,
+
+ S16_D16_nofilter_DXDY_neon,
+ S16_D16_nofilter_DX_neon,
+ S16_D16_filter_DXDY_neon,
+ S16_D16_filter_DX_neon,
+
+ SI8_D16_nofilter_DXDY_neon,
+ SI8_D16_nofilter_DX_neon,
+ SI8_D16_filter_DXDY_neon,
+ SI8_D16_filter_DX_neon,
+
+ // Don't support 4444 -> 565
+ NULL, NULL, NULL, NULL,
+ // Don't support A8 -> 565
+ NULL, NULL, NULL, NULL
+};
diff --git a/opts/SkBitmapProcState_filter_neon.h b/opts/SkBitmapProcState_filter_neon.h
new file mode 100644
index 00000000..86c1dcf5
--- /dev/null
+++ b/opts/SkBitmapProcState_filter_neon.h
@@ -0,0 +1,88 @@
+
+/*
+ * 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 "SkColorPriv.h"
+
+/*
+ Filter_32_opaque
+
+ There is no hard-n-fast rule that the filtering must produce
+ exact results for the color components, but if the 4 incoming colors are
+ all opaque, then the output color must also be opaque. Subsequent parts of
+ the drawing pipeline may rely on this (e.g. which blitrow proc to use).
+ */
+
+static inline void Filter_32_opaque_neon(unsigned x, unsigned y,
+ SkPMColor a00, SkPMColor a01,
+ SkPMColor a10, SkPMColor a11,
+ SkPMColor *dst) {
+ asm volatile(
+ "vdup.8 d0, %[y] \n\t" // duplicate y into d0
+ "vmov.u8 d16, #16 \n\t" // set up constant in d16
+ "vsub.u8 d1, d16, d0 \n\t" // d1 = 16-y
+
+ "vdup.32 d4, %[a00] \n\t" // duplicate a00 into d4
+ "vdup.32 d5, %[a10] \n\t" // duplicate a10 into d5
+ "vmov.32 d4[1], %[a01] \n\t" // set top of d4 to a01
+ "vmov.32 d5[1], %[a11] \n\t" // set top of d5 to a11
+
+ "vmull.u8 q3, d4, d1 \n\t" // q3 = [a01|a00] * (16-y)
+ "vmull.u8 q0, d5, d0 \n\t" // q0 = [a11|a10] * y
+
+ "vdup.16 d5, %[x] \n\t" // duplicate x into d5
+ "vmov.u16 d16, #16 \n\t" // set up constant in d16
+ "vsub.u16 d3, d16, d5 \n\t" // d3 = 16-x
+
+ "vmul.i16 d4, d7, d5 \n\t" // d4 = a01 * x
+ "vmla.i16 d4, d1, d5 \n\t" // d4 += a11 * x
+ "vmla.i16 d4, d6, d3 \n\t" // d4 += a00 * (16-x)
+ "vmla.i16 d4, d0, d3 \n\t" // d4 += a10 * (16-x)
+ "vshrn.i16 d0, q2, #8 \n\t" // shift down result by 8
+ "vst1.32 {d0[0]}, [%[dst]] \n\t" // store result
+ :
+ : [x] "r" (x), [y] "r" (y), [a00] "r" (a00), [a01] "r" (a01), [a10] "r" (a10), [a11] "r" (a11), [dst] "r" (dst)
+ : "cc", "memory", "d0", "d1", "d3", "d4", "d5", "d6", "d7", "d16"
+ );
+}
+
+static inline void Filter_32_alpha_neon(unsigned x, unsigned y,
+ SkPMColor a00, SkPMColor a01,
+ SkPMColor a10, SkPMColor a11,
+ SkPMColor *dst, uint16_t scale) {
+ asm volatile(
+ "vdup.8 d0, %[y] \n\t" // duplicate y into d0
+ "vmov.u8 d16, #16 \n\t" // set up constant in d16
+ "vsub.u8 d1, d16, d0 \n\t" // d1 = 16-y
+
+ "vdup.32 d4, %[a00] \n\t" // duplicate a00 into d4
+ "vdup.32 d5, %[a10] \n\t" // duplicate a10 into d5
+ "vmov.32 d4[1], %[a01] \n\t" // set top of d4 to a01
+ "vmov.32 d5[1], %[a11] \n\t" // set top of d5 to a11
+
+ "vmull.u8 q3, d4, d1 \n\t" // q3 = [a01|a00] * (16-y)
+ "vmull.u8 q0, d5, d0 \n\t" // q0 = [a11|a10] * y
+
+ "vdup.16 d5, %[x] \n\t" // duplicate x into d5
+ "vmov.u16 d16, #16 \n\t" // set up constant in d16
+ "vsub.u16 d3, d16, d5 \n\t" // d3 = 16-x
+
+ "vmul.i16 d4, d7, d5 \n\t" // d4 = a01 * x
+ "vmla.i16 d4, d1, d5 \n\t" // d4 += a11 * x
+ "vmla.i16 d4, d6, d3 \n\t" // d4 += a00 * (16-x)
+ "vmla.i16 d4, d0, d3 \n\t" // d4 += a10 * (16-x)
+ "vdup.16 d3, %[scale] \n\t" // duplicate scale into d3
+ "vshr.u16 d4, d4, #8 \n\t" // shift down result by 8
+ "vmul.i16 d4, d4, d3 \n\t" // multiply result by scale
+ "vshrn.i16 d0, q2, #8 \n\t" // shift down result by 8
+ "vst1.32 {d0[0]}, [%[dst]] \n\t" // store result
+ :
+ : [x] "r" (x), [y] "r" (y), [a00] "r" (a00), [a01] "r" (a01), [a10] "r" (a10), [a11] "r" (a11), [dst] "r" (dst), [scale] "r" (scale)
+ : "cc", "memory", "d0", "d1", "d3", "d4", "d5", "d6", "d7", "d16"
+ );
+}
diff --git a/opts/SkBitmapProcState_matrixProcs_neon.cpp b/opts/SkBitmapProcState_matrixProcs_neon.cpp
new file mode 100644
index 00000000..7d75143e
--- /dev/null
+++ b/opts/SkBitmapProcState_matrixProcs_neon.cpp
@@ -0,0 +1,144 @@
+/* NEON optimized code (C) COPYRIGHT 2009 Motorola
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapProcState.h"
+#include "SkPerspIter.h"
+#include "SkShader.h"
+#include "SkUtilsArm.h"
+
+extern const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs_neon[];
+extern const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs_neon[];
+
+static void decal_nofilter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count);
+static void decal_filter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count);
+
+static unsigned SK_USHIFT16(unsigned x) {
+ return x >> 16;
+}
+
+#define MAKENAME(suffix) ClampX_ClampY ## suffix ## _neon
+#define TILEX_PROCF(fx, max) SkClampMax((fx) >> 16, max)
+#define TILEY_PROCF(fy, max) SkClampMax((fy) >> 16, max)
+#define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF)
+#define CHECK_FOR_DECAL
+#include "SkBitmapProcState_matrix_clamp_neon.h"
+
+#define MAKENAME(suffix) RepeatX_RepeatY ## suffix ## _neon
+#define TILEX_PROCF(fx, max) SK_USHIFT16(((fx) & 0xFFFF) * ((max) + 1))
+#define TILEY_PROCF(fy, max) SK_USHIFT16(((fy) & 0xFFFF) * ((max) + 1))
+#define TILEX_LOW_BITS(fx, max) ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+#define TILEY_LOW_BITS(fy, max) ((((fy) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+#include "SkBitmapProcState_matrix_repeat_neon.h"
+
+
+void decal_nofilter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count)
+{
+ int i;
+
+ if (count >= 8) {
+ /* SkFixed is 16.16 fixed point */
+ SkFixed dx2 = dx+dx;
+ SkFixed dx4 = dx2+dx2;
+ SkFixed dx8 = dx4+dx4;
+
+ /* now build fx/fx+dx/fx+2dx/fx+3dx */
+ SkFixed fx1, fx2, fx3;
+ int32x4_t lbase, hbase;
+ uint16_t *dst16 = (uint16_t *)dst;
+
+ fx1 = fx+dx;
+ fx2 = fx1+dx;
+ fx3 = fx2+dx;
+
+ /* avoid an 'lbase unitialized' warning */
+ lbase = vdupq_n_s32(fx);
+ lbase = vsetq_lane_s32(fx1, lbase, 1);
+ lbase = vsetq_lane_s32(fx2, lbase, 2);
+ lbase = vsetq_lane_s32(fx3, lbase, 3);
+ hbase = vaddq_s32(lbase, vdupq_n_s32(dx4));
+
+ /* take upper 16 of each, store, and bump everything */
+ do {
+ int32x4_t lout, hout;
+ uint16x8_t hi16;
+
+ lout = lbase;
+ hout = hbase;
+ /* gets hi's of all louts then hi's of all houts */
+ asm ("vuzpq.16 %q0, %q1" : "+w" (lout), "+w" (hout));
+ hi16 = vreinterpretq_u16_s32(hout);
+ vst1q_u16(dst16, hi16);
+
+ /* on to the next */
+ lbase = vaddq_s32 (lbase, vdupq_n_s32(dx8));
+ hbase = vaddq_s32 (hbase, vdupq_n_s32(dx8));
+ dst16 += 8;
+ count -= 8;
+ fx += dx8;
+ } while (count >= 8);
+ dst = (uint32_t *) dst16;
+ }
+
+ uint16_t* xx = (uint16_t*)dst;
+ for (i = count; i > 0; --i) {
+ *xx++ = SkToU16(fx >> 16); fx += dx;
+ }
+}
+
+void decal_filter_scale_neon(uint32_t dst[], SkFixed fx, SkFixed dx, int count)
+{
+ if (count >= 8) {
+ int32x4_t wide_fx;
+ int32x4_t wide_fx2;
+ int32x4_t wide_dx8 = vdupq_n_s32(dx*8);
+
+ wide_fx = vdupq_n_s32(fx);
+ wide_fx = vsetq_lane_s32(fx+dx, wide_fx, 1);
+ wide_fx = vsetq_lane_s32(fx+dx+dx, wide_fx, 2);
+ wide_fx = vsetq_lane_s32(fx+dx+dx+dx, wide_fx, 3);
+
+ wide_fx2 = vaddq_s32(wide_fx, vdupq_n_s32(dx+dx+dx+dx));
+
+ while (count >= 8) {
+ int32x4_t wide_out;
+ int32x4_t wide_out2;
+
+ wide_out = vshlq_n_s32(vshrq_n_s32(wide_fx, 12), 14);
+ wide_out = vorrq_s32(wide_out,
+ vaddq_s32(vshrq_n_s32(wide_fx,16), vdupq_n_s32(1)));
+
+ wide_out2 = vshlq_n_s32(vshrq_n_s32(wide_fx2, 12), 14);
+ wide_out2 = vorrq_s32(wide_out2,
+ vaddq_s32(vshrq_n_s32(wide_fx2,16), vdupq_n_s32(1)));
+
+ vst1q_u32(dst, vreinterpretq_u32_s32(wide_out));
+ vst1q_u32(dst+4, vreinterpretq_u32_s32(wide_out2));
+
+ dst += 8;
+ fx += dx*8;
+ wide_fx = vaddq_s32(wide_fx, wide_dx8);
+ wide_fx2 = vaddq_s32(wide_fx2, wide_dx8);
+ count -= 8;
+ }
+ }
+
+ if (count & 1)
+ {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ }
+ while ((count -= 2) >= 0)
+ {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+
+ *dst++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ }
+}
diff --git a/opts/SkBitmapProcState_matrix_clamp_neon.h b/opts/SkBitmapProcState_matrix_clamp_neon.h
new file mode 100644
index 00000000..a615e26b
--- /dev/null
+++ b/opts/SkBitmapProcState_matrix_clamp_neon.h
@@ -0,0 +1,911 @@
+/* NEON optimized code (C) COPYRIGHT 2009 Motorola
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Modifications done in-house at Motorola
+ *
+ * this is a clone of SkBitmapProcState_matrix.h
+ * and has been tuned to work with the NEON unit.
+ *
+ * Still going back and forth between whether this approach
+ * (clone the entire SkBitmapProcState_matrix.h file or
+ * if I should put just the modified routines in here and
+ * then use a construct like #define DONT_DO_THIS_FUNCTION or
+ * something like that...
+ *
+ * This is for the ClampX_ClampY instance
+ *
+ */
+
+
+#include <arm_neon.h>
+
+/*
+ * This has been modified on the knowledge that (at the time)
+ * we had the following macro definitions in the parent file
+ *
+ * #define MAKENAME(suffix) ClampX_ClampY ## suffix
+ * #define TILEX_PROCF(fx, max) SkClampMax((fx) >> 16, max)
+ * #define TILEY_PROCF(fy, max) SkClampMax((fy) >> 16, max)
+ * #define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF)
+ * #define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF)
+ * #define CHECK_FOR_DECAL
+ */
+
+/* SkClampMax(val,max) -- bound to 0..max */
+
+#define SCALE_NOFILTER_NAME MAKENAME(_nofilter_scale)
+#define SCALE_FILTER_NAME MAKENAME(_filter_scale)
+#define AFFINE_NOFILTER_NAME MAKENAME(_nofilter_affine)
+#define AFFINE_FILTER_NAME MAKENAME(_filter_affine)
+#define PERSP_NOFILTER_NAME MAKENAME(_nofilter_persp)
+#define PERSP_FILTER_NAME MAKENAME(_filter_persp)
+
+#define PACK_FILTER_X_NAME MAKENAME(_pack_filter_x)
+#define PACK_FILTER_Y_NAME MAKENAME(_pack_filter_y)
+
+#ifndef PREAMBLE
+ #define PREAMBLE(state)
+ #define PREAMBLE_PARAM_X
+ #define PREAMBLE_PARAM_Y
+ #define PREAMBLE_ARG_X
+ #define PREAMBLE_ARG_Y
+#endif
+
+static void SCALE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+
+ PREAMBLE(s);
+ // we store y, x, x, x, x, x
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ SkFixed fx;
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ fx = SkScalarToFixed(pt.fY);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ *xy++ = TILEY_PROCF(fx, maxY);
+ fx = SkScalarToFixed(pt.fX);
+ }
+
+ if (0 == maxX) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ const SkFixed dx = s.fInvSx;
+
+#ifdef CHECK_FOR_DECAL
+ // test if we don't need to apply the tile proc
+ if ((unsigned)(fx >> 16) <= maxX &&
+ (unsigned)((fx + dx * (count - 1)) >> 16) <= maxX) {
+ decal_nofilter_scale_neon(xy, fx, dx, count);
+ return;
+ }
+#endif
+
+ int i;
+
+ /* very much like done in decal_nofilter, but with
+ * an extra clamping function applied.
+ * TILEX_PROCF(fx,max) SkClampMax((fx)>>16, max)
+ */
+ if (count >= 8) {
+ /* SkFixed is 16.16 fixed point */
+ SkFixed dx2 = dx+dx;
+ SkFixed dx4 = dx2+dx2;
+ SkFixed dx8 = dx4+dx4;
+
+ /* now build fx/fx+dx/fx+2dx/fx+3dx */
+ SkFixed fx1, fx2, fx3;
+ int32x4_t lbase, hbase;
+ int16_t *dst16 = (int16_t *)xy;
+
+ fx1 = fx+dx;
+ fx2 = fx1+dx;
+ fx3 = fx2+dx;
+
+ /* build my template(s) */
+ /* avoid the 'lbase unitialized' warning */
+ lbase = vdupq_n_s32(fx);
+ lbase = vsetq_lane_s32(fx1, lbase, 1);
+ lbase = vsetq_lane_s32(fx2, lbase, 2);
+ lbase = vsetq_lane_s32(fx3, lbase, 3);
+
+ hbase = vaddq_s32(lbase, vdupq_n_s32(dx4));
+
+ /* store & bump */
+ do {
+ int32x4_t lout;
+ int32x4_t hout;
+ int16x8_t hi16;
+
+ /* get the hi 16s of all those 32s */
+ lout = lbase;
+ hout = hbase;
+ /* this sets up all lout's then all hout's in hout */
+ asm ("vuzpq.16 %q0, %q1" : "+w" (lout), "+w" (hout));
+ hi16 = vreinterpretq_s16_s32(hout);
+
+ /* clamp & output */
+ hi16 = vmaxq_s16(hi16, vdupq_n_s16(0));
+ hi16 = vminq_s16(hi16, vdupq_n_s16(maxX));
+ vst1q_s16(dst16, hi16);
+
+ /* but preserving base & on to the next */
+ lbase = vaddq_s32 (lbase, vdupq_n_s32(dx8));
+ hbase = vaddq_s32 (hbase, vdupq_n_s32(dx8));
+ dst16 += 8;
+ count -= 8;
+ fx += dx8;
+ } while (count >= 8);
+ xy = (uint32_t *) dst16;
+ }
+
+ uint16_t* xx = (uint16_t*)xy;
+ for (i = count; i > 0; --i) {
+ *xx++ = TILEX_PROCF(fx, maxX); fx += dx;
+ }
+}
+
+// note: we could special-case on a matrix which is skewed in X but not Y.
+// this would require a more general setup thatn SCALE does, but could use
+// SCALE's inner loop that only looks at dx
+
+static void AFFINE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed fx = SkScalarToFixed(srcPt.fX);
+ SkFixed fy = SkScalarToFixed(srcPt.fY);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ /* NEON lets us do an 8x unrolling */
+ if (count >= 8) {
+ /* SkFixed is 16.16 fixed point */
+ SkFixed dx4 = dx * 4;
+ SkFixed dy4 = dy * 4;
+ SkFixed dx8 = dx * 8;
+ SkFixed dy8 = dy * 8;
+
+ int32x4_t xbase, ybase;
+ int32x4_t x2base, y2base;
+ int16_t *dst16 = (int16_t *) xy;
+
+ /* my sets of maxx/maxy for clamping */
+ int32_t maxpair = (maxX&0xffff) | ((maxY&0xffff)<<16);
+ int16x8_t maxXY = vreinterpretq_s16_s32(vdupq_n_s32(maxpair));
+
+ /* now build fx/fx+dx/fx+2dx/fx+3dx */
+ /* avoid the 'xbase unitialized' warning...*/
+ xbase = vdupq_n_s32(fx);
+ xbase = vsetq_lane_s32(fx+dx, xbase, 1);
+ xbase = vsetq_lane_s32(fx+dx+dx, xbase, 2);
+ xbase = vsetq_lane_s32(fx+dx+dx+dx, xbase, 3);
+
+ /* same for fy */
+ /* avoid the 'ybase unitialized' warning...*/
+ ybase = vdupq_n_s32(fy);
+ ybase = vsetq_lane_s32(fy+dy, ybase, 1);
+ ybase = vsetq_lane_s32(fy+dy+dy, ybase, 2);
+ ybase = vsetq_lane_s32(fy+dy+dy+dy, ybase, 3);
+
+ x2base = vaddq_s32(xbase, vdupq_n_s32(dx4));
+ y2base = vaddq_s32(ybase, vdupq_n_s32(dy4));
+
+ /* store & bump */
+ do {
+ int32x4_t xout, yout;
+ int32x4_t x2out, y2out;
+ int16x8_t hi16, hi16_2;
+
+ xout = xbase;
+ yout = ybase;
+
+ /* overlay y's low16 with hi16 from x */
+ /* so we properly shifted xyxyxyxy */
+ yout = vsriq_n_s32(yout, xout, 16);
+ hi16 = vreinterpretq_s16_s32 (yout);
+
+ /* do the clamping; both guys get 0's */
+ hi16 = vmaxq_s16 (hi16, vdupq_n_s16(0));
+ hi16 = vminq_s16 (hi16, maxXY);
+
+ vst1q_s16 (dst16, hi16);
+
+ /* and for the other 4 pieces of this iteration */
+ x2out = x2base;
+ y2out = y2base;
+
+ /* overlay y's low16 with hi16 from x */
+ /* so we properly shifted xyxyxyxy */
+ y2out = vsriq_n_s32(y2out, x2out, 16);
+ hi16_2 = vreinterpretq_s16_s32 (y2out);
+
+ /* do the clamping; both guys get 0's */
+ hi16_2 = vmaxq_s16 (hi16_2, vdupq_n_s16(0));
+ hi16_2 = vminq_s16 (hi16_2, maxXY);
+
+ /* RBE: gcc regenerates dst16+8 all the time instead
+ * of folding it into an addressing mode. *sigh* */
+ vst1q_s16 (dst16+8, hi16_2);
+
+ /* moving base and on to the next */
+ xbase = vaddq_s32 (xbase, vdupq_n_s32 (dx8));
+ ybase = vaddq_s32 (ybase, vdupq_n_s32 (dy8));
+ x2base = vaddq_s32 (x2base, vdupq_n_s32 (dx8));
+ y2base = vaddq_s32 (y2base, vdupq_n_s32 (dy8));
+
+ dst16 += 16; /* 8x32 aka 16x16 */
+ count -= 8;
+ fx += dx8;
+ fy += dy8;
+ } while (count >= 8);
+ xy = (uint32_t *) dst16;
+ }
+
+ for (int i = count; i > 0; --i) {
+ *xy++ = (TILEY_PROCF(fy, maxY) << 16) | TILEX_PROCF(fx, maxX);
+ fx += dx; fy += dy;
+ }
+}
+
+#undef DEBUG_PERSP_NOFILTER
+
+static void PERSP_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy,
+ int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ PREAMBLE(s);
+ /* max{X,Y} are int here, but later shown/assumed to fit in 16 bits */
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+
+#if defined(DEBUG_PERSP_NOFILTER)
+ /* debugging stuff */
+ const SkFixed *end_srcXY = srcXY + (count*2);
+ uint32_t *end_xy = xy + (count);
+ const SkFixed *base_srcXY = srcXY;
+ uint32_t *base_xy = xy;
+ int base_count = count;
+#endif
+
+#if 1
+ // 2009/9/30: crashes in ApiDemos - Views - Animation - 3D Transition
+ // 2009/10/9: reworked to avoid illegal (but allowed by gas) insn
+
+ /* srcXY is a batch of 32 bit numbers X0,Y0,X1,Y1...
+ * but we immediately discard the low 16 bits...
+ * so what we're going to do is vld4, which will give us
+ * xlo,xhi,ylo,yhi distribution and we can ignore the 'lo'
+ * parts....
+ */
+ if (count >= 8) {
+ int16_t *mysrc = (int16_t *) srcXY;
+ int16_t *mydst = (int16_t *) xy;
+ int16x4_t maxX4 = vdup_n_s16((int16_t)maxX);
+ int16x4_t maxY4 = vdup_n_s16((int16_t)maxY);
+ int16x4_t zero4 = vdup_n_s16(0);
+
+ /* The constructs with local blocks for register assignments
+ * and asm() instructions is to make keep any hard register
+ * assignments to as small a scope as possible. and to avoid
+ * burning call-preserved hard registers on the vld/vst
+ * instructions.
+ */
+
+ do {
+ int16x4_t xhi, yhi;
+ int16x4_t x2hi, y2hi;
+
+ /* vld4 does the de-interleaving for us */
+ {
+ register int16x4_t t_xlo asm("d0");
+ register int16x4_t t_xhi asm("d1");
+ register int16x4_t t_ylo asm("d2");
+ register int16x4_t t_yhi asm("d3");
+
+ asm ("vld4.16 {d0-d3},[%4] /* xlo=%P0 xhi=%P1 ylo=%P2 yhi=%P3 */"
+ : "=w" (t_xlo), "=w" (t_xhi), "=w" (t_ylo), "=w" (t_yhi)
+ : "r" (mysrc)
+ );
+ xhi = t_xhi;
+ yhi = t_yhi;
+ }
+
+ /* clamp X>>16 (aka xhi) to 0..maxX */
+ xhi = vmax_s16(xhi, zero4); /* now 0.. */
+ xhi = vmin_s16(xhi, maxX4); /* now 0..maxX */
+
+ /* clamp Y>>16 (aka yhi) to 0..maxY */
+ yhi = vmax_s16(yhi, zero4); /* now 0.. */
+ yhi = vmin_s16(yhi, maxY4); /* now 0..maxY */
+
+ /* deal with the second set of numbers */
+ {
+ register int16x4_t t_xlo asm("d4");
+ register int16x4_t t_xhi asm("d5");
+ register int16x4_t t_ylo asm("d6");
+ register int16x4_t t_yhi asm("d7");
+
+ /* offset == 256 bits == 32 bytes == 8 longs == 16 shorts */
+ asm ("vld4.16 {d4-d7},[%4] /* xlo=%P0 xhi=%P1 ylo=%P2 yhi=%P3 */"
+ : "=w" (t_xlo), "=w" (t_xhi), "=w" (t_ylo), "=w" (t_yhi)
+ : "r" (mysrc+16)
+ );
+ x2hi = t_xhi;
+ y2hi = t_yhi;
+ }
+
+ /* clamp the second 4 here */
+
+ if (0) { extern void rbe(void); rbe(); }
+
+ /* clamp X>>16 (aka xhi) to 0..maxX */
+ x2hi = vmax_s16(x2hi, zero4); /* now 0.. */
+ x2hi = vmin_s16(x2hi, maxX4); /* now 0..maxX */
+
+ /* clamp Y>>16 (aka yhi) to 0..maxY */
+ y2hi = vmax_s16(y2hi, zero4); /* now 0.. */
+ y2hi = vmin_s16(y2hi, maxY4); /* now 0..maxY */
+
+ /* we're storing as {x,y}s: x is [0], y is [1] */
+ /* we'll use vst2 to make this happen */
+
+ {
+ register int16x4_t out_x asm("d16") = xhi;
+ register int16x4_t out_y asm("d17") = yhi;
+
+ asm ("vst2.16 {d16-d17},[%2] /* xlo=%P0 xhi=%P1 */"
+ :
+ : "w" (out_x), "w" (out_y), "r" (mydst)
+ );
+ }
+ {
+ register int16x4_t out_x asm("d18") = x2hi;
+ register int16x4_t out_y asm("d19") = y2hi;
+
+ asm ("vst2.16 {d18-d19},[%2] /* xlo=%P0 xhi=%P1 */"
+ :
+ : "w" (out_x), "w" (out_y), "r" (mydst+8)
+ );
+ }
+
+ /* XXX: gcc isn't interleaving these with the NEON ops
+ * but i think that all the scoreboarding works out */
+ count -= 8; /* 8 iterations */
+ mysrc += 32; /* 16 longs, aka 32 shorts */
+ mydst += 16; /* 16 shorts, aka 8 longs */
+ } while (count >= 8);
+ /* get xy and srcXY fixed up */
+ srcXY = (const SkFixed *) mysrc;
+ xy = (uint32_t *) mydst;
+ }
+#endif
+
+ while (--count >= 0) {
+ *xy++ = (TILEY_PROCF(srcXY[1], maxY) << 16) |
+ TILEX_PROCF(srcXY[0], maxX);
+ srcXY += 2;
+ }
+
+#if defined(DEBUG_PERSP_NOFILTER)
+ /* for checking our NEON-produced results against vanilla code */
+ {
+ int bad = (-1);
+ for (int i = 0; i < base_count; i++) {
+ uint32_t val;
+ val = (TILEY_PROCF (base_srcXY[i * 2 + 1], maxY) << 16) |
+ TILEX_PROCF (base_srcXY[i * 2 + 0], maxX);
+
+ if (val != base_xy[i]) {
+ bad = i;
+ break;
+ }
+ }
+ if (bad >= 0) {
+ SkDebugf("clamp-nofilter-persp failed piece %d\n", bad);
+ SkDebugf(" maxX %08x maxY %08x\n", maxX, maxY);
+ bad -= (bad & 0x7); /* align */
+ for (int i = bad; i < bad + 8; i++) {
+ uint32_t val;
+ val = (TILEY_PROCF (base_srcXY[i * 2 + 1], maxY) << 16) |
+ TILEX_PROCF (base_srcXY[i * 2 + 0], maxX);
+
+ SkDebugf("%d: got %08x want %08x srcXY[0] %08x srcXY[1] %08x\n",
+ i, base_xy[i], val, base_srcXY[i * 2 + 0],
+ base_srcXY[i * 2 + 1]);
+ }
+ SkDebugf ("---\n");
+ }
+
+ if (end_xy != xy) {
+ SkDebugf("xy ended at %08x, should be %08x\n", xy, end_xy);
+ }
+ if (end_srcXY != srcXY) {
+ SkDebugf("srcXY ended at %08x, should be %08x\n", srcXY,
+ end_srcXY);
+ }
+ }
+#endif
+ }
+}
+
+#undef DEBUG_PERSP_NOFILTER
+
+//////////////////////////////////////////////////////////////////////////////
+
+static inline uint32_t PACK_FILTER_Y_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_Y) {
+ unsigned i = TILEY_PROCF(f, max);
+ i = (i << 4) | TILEY_LOW_BITS(f, max);
+ return (i << 14) | (TILEY_PROCF((f + one), max));
+}
+
+static inline uint32_t PACK_FILTER_X_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_X) {
+ unsigned i = TILEX_PROCF(f, max);
+ i = (i << 4) | TILEX_LOW_BITS(f, max);
+ return (i << 14) | (TILEX_PROCF((f + one), max));
+}
+
+static void SCALE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+
+ PREAMBLE(s);
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ const SkFixed one = s.fFilterOneX;
+ const SkFixed dx = s.fInvSx;
+ SkFixed fx;
+
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ const SkFixed fy = SkScalarToFixed(pt.fY) - (s.fFilterOneY >> 1);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ // compute our two Y values up front
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, s.fFilterOneY PREAMBLE_ARG_Y);
+ // now initialize fx
+ fx = SkScalarToFixed(pt.fX) - (one >> 1);
+ }
+
+#ifdef CHECK_FOR_DECAL
+ // test if we don't need to apply the tile proc
+ if (dx > 0 &&
+ (unsigned)(fx >> 16) <= maxX &&
+ (unsigned)((fx + dx * (count - 1)) >> 16) < maxX) {
+ decal_filter_scale_neon(xy, fx, dx, count);
+ } else
+#endif
+
+ if (count >= 4) {
+ int32x4_t wide_one, wide_fx, wide_fx1, wide_i, wide_lo;
+ #if 0
+ /* verification hooks -- see below */
+ SkFixed debug_fx = fx;
+ int count_done = 0;
+ #endif
+
+ wide_fx = vdupq_n_s32(fx);
+ wide_fx = vsetq_lane_s32(fx+dx, wide_fx, 1);
+ wide_fx = vsetq_lane_s32(fx+dx+dx, wide_fx, 2);
+ wide_fx = vsetq_lane_s32(fx+dx+dx+dx, wide_fx, 3);
+
+ wide_one = vdupq_n_s32(one);
+
+ while (count >= 4) {
+ /* original expands to:
+ * unsigned i = SkClampMax((f) >> 16, max);
+ * i = (i << 4) | (((f) >> 12) & 0xF);
+ * return (i << 14) | (SkClampMax(((f + one)) >> 16, max));
+ */
+
+ /* i = SkClampMax(f>>16, maxX) */
+ wide_i = vmaxq_s32(vshrq_n_s32(wide_fx,16), vdupq_n_s32(0));
+ wide_i = vminq_s32(wide_i, vdupq_n_s32(maxX));
+
+ /* i<<4 | TILEX_LOW_BITS(fx) */
+ wide_lo = vshrq_n_s32(wide_fx, 12);
+ wide_i = vsliq_n_s32(wide_lo, wide_i, 4);
+
+ /* i<<14 */
+ wide_i = vshlq_n_s32(wide_i, 14);
+
+ /* SkClampMax(((f + one)) >> 16, max) */
+ wide_fx1 = vaddq_s32(wide_fx, wide_one);
+ wide_fx1 = vmaxq_s32(vshrq_n_s32(wide_fx1,16), vdupq_n_s32(0));
+ wide_fx1 = vminq_s32(wide_fx1, vdupq_n_s32(maxX));
+
+ /* final combination */
+ wide_i = vorrq_s32(wide_i, wide_fx1);
+
+ vst1q_u32(xy, vreinterpretq_u32_s32(wide_i));
+
+ #if 0
+ /* having a verification hook is a good idea */
+ /* use debug_fx, debug_fx+dx, etc. */
+
+ for (int i=0;i<4;i++) {
+ uint32_t want = PACK_FILTER_X_NAME(debug_fx, maxX, one PREAMBLE_ARG_X);
+ if (xy[i] != want)
+ {
+ /* print a nastygram */
+ SkDebugf("clamp-filter-scale fails\n");
+ SkDebugf("got %08x want %08x\n", xy[i], want);
+ SkDebugf("fx %08x debug_fx %08x dx %08x done %d\n",
+ fx, debug_fx, dx, count_done);
+ SkDebugf(" maxX %08x one %08x\n", maxX, one);
+
+ }
+ debug_fx += dx;
+ count_done++;
+ }
+ #endif
+ wide_fx += vdupq_n_s32(dx+dx+dx+dx);
+ fx += dx+dx+dx+dx;
+ xy += 4;
+ count -= 4;
+ }
+ }
+
+ while (--count >= 0) {
+ *xy++ = PACK_FILTER_X_NAME(fx, maxX, one PREAMBLE_ARG_X);
+ fx += dx;
+ }
+}
+
+static void AFFINE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+ SkFixed fx = SkScalarToFixed(srcPt.fX) - (oneX >> 1);
+ SkFixed fy = SkScalarToFixed(srcPt.fY) - (oneY >> 1);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+
+ if (count >= 4) {
+ int32x4_t wide_i, wide_lo;
+ int32x4_t wide_fx, wide_onex, wide_fx1;
+ int32x4_t wide_fy, wide_oney, wide_fy1;
+
+ #undef AFFINE_DEBUG
+ #if defined(AFFINE_DEBUG)
+ SkFixed fyp = fy;
+ SkFixed fxp = fx;
+ uint32_t *xyp = xy;
+ int count_done = 0;
+ #endif
+
+ wide_fx = vdupq_n_s32(fx);
+ wide_fx = vsetq_lane_s32(fx+dx, wide_fx, 1);
+ wide_fx = vsetq_lane_s32(fx+dx+dx, wide_fx, 2);
+ wide_fx = vsetq_lane_s32(fx+dx+dx+dx, wide_fx, 3);
+
+ wide_fy = vdupq_n_s32(fy);
+ wide_fy = vsetq_lane_s32(fy+dy, wide_fy, 1);
+ wide_fy = vsetq_lane_s32(fy+dy+dy, wide_fy, 2);
+ wide_fy = vsetq_lane_s32(fy+dy+dy+dy, wide_fy, 3);
+
+ wide_onex = vdupq_n_s32(oneX);
+ wide_oney = vdupq_n_s32(oneY);
+
+ while (count >= 4) {
+ int32x4_t wide_x;
+ int32x4_t wide_y;
+
+ /* do the X side, then the Y side, then interleave them */
+
+ /* original expands to:
+ * unsigned i = SkClampMax((f) >> 16, max);
+ * i = (i << 4) | (((f) >> 12) & 0xF);
+ * return (i << 14) | (SkClampMax(((f + one)) >> 16, max));
+ */
+
+ /* i = SkClampMax(f>>16, maxX) */
+ wide_i = vmaxq_s32(vshrq_n_s32(wide_fx,16), vdupq_n_s32(0));
+ wide_i = vminq_s32(wide_i, vdupq_n_s32(maxX));
+
+ /* i<<4 | TILEX_LOW_BITS(fx) */
+ wide_lo = vshrq_n_s32(wide_fx, 12);
+ wide_i = vsliq_n_s32(wide_lo, wide_i, 4);
+
+ /* i<<14 */
+ wide_i = vshlq_n_s32(wide_i, 14);
+
+ /* SkClampMax(((f + one)) >> 16, max) */
+ wide_fx1 = vaddq_s32(wide_fx, wide_onex);
+ wide_fx1 = vmaxq_s32(vshrq_n_s32(wide_fx1,16), vdupq_n_s32(0));
+ wide_fx1 = vminq_s32(wide_fx1, vdupq_n_s32(maxX));
+
+ /* final combination */
+ wide_x = vorrq_s32(wide_i, wide_fx1);
+
+ /* And now the Y side */
+
+ /* i = SkClampMax(f>>16, maxX) */
+ wide_i = vmaxq_s32(vshrq_n_s32(wide_fy,16), vdupq_n_s32(0));
+ wide_i = vminq_s32(wide_i, vdupq_n_s32(maxY));
+
+ /* i<<4 | TILEX_LOW_BITS(fx) */
+ wide_lo = vshrq_n_s32(wide_fy, 12);
+ wide_i = vsliq_n_s32(wide_lo, wide_i, 4);
+
+ /* i<<14 */
+ wide_i = vshlq_n_s32(wide_i, 14);
+
+ /* SkClampMax(((f + one)) >> 16, max) */
+ wide_fy1 = vaddq_s32(wide_fy, wide_oney);
+ wide_fy1 = vmaxq_s32(vshrq_n_s32(wide_fy1,16), vdupq_n_s32(0));
+ wide_fy1 = vminq_s32(wide_fy1, vdupq_n_s32(maxY));
+
+ /* final combination */
+ wide_y = vorrq_s32(wide_i, wide_fy1);
+
+ /* interleave as YXYXYXYX as part of the storing */
+ {
+ /* vst2.32 needs side-by-side registers */
+ register int32x4_t t_x asm("q1");
+ register int32x4_t t_y asm("q0");
+
+ t_x = wide_x; t_y = wide_y;
+ asm ("vst2.32 {q0-q1},[%2] /* y=%q0 x=%q1 */"
+ :
+ : "w" (t_y), "w" (t_x), "r" (xy)
+ );
+ }
+
+ #if defined(AFFINE_DEBUG)
+ /* make sure we're good here -- check the 4 we just output */
+ for (int i = 0; i<4;i++) {
+ uint32_t val;
+ val = PACK_FILTER_Y_NAME(fyp, maxY, oneY PREAMBLE_ARG_Y);
+ if (val != xy[i*2+0]) {
+ /* print a nastygram */
+ SkDebugf("clamp-filter-affine fails\n");
+ SkDebugf("[bad-y] got %08x want %08x\n", xy[i*2+0], val);
+ SkDebugf("fy %08x fxp %08x fyp %08x dx %08x dy %08x done %d\n",
+ fy, fxp, fyp, dx, dy, count_done);
+ SkDebugf(" maxY %08x oneY %08x\n", maxY, oneY);
+ }
+ val = PACK_FILTER_X_NAME(fxp, maxX, oneX PREAMBLE_ARG_X);
+ if (val != xy[i*2+1]) {
+ /* print a nastygram */
+ SkDebugf("clamp-filter-affine fails\n");
+ SkDebugf("[bad-x] got %08x want %08x\n", xy[i*2+1], val);
+ SkDebugf("fx %08x fxp %08x fyp %08x dx %08x dy %08x done %d\n",
+ fx, fxp, fyp, dx, dy, count_done);
+ SkDebugf(" maxX %08x one %08x\n", maxX, oneX);
+ }
+ fyp += dy;
+ fxp += dx;
+ count_done++;
+ }
+ #endif
+
+ wide_fx += vdupq_n_s32(dx+dx+dx+dx);
+ fx += dx+dx+dx+dx;
+ wide_fy += vdupq_n_s32(dy+dy+dy+dy);
+ fy += dy+dy+dy+dy;
+ xy += 8; /* 4 x's, 4 y's */
+ count -= 4;
+ }
+ }
+
+ while (--count >= 0) {
+ /* NB: writing Y/X */
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, oneY PREAMBLE_ARG_Y);
+ fy += dy;
+ *xy++ = PACK_FILTER_X_NAME(fx, maxX, oneX PREAMBLE_ARG_X);
+ fx += dx;
+ }
+}
+
+static void PERSP_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy, int count,
+ int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ PREAMBLE(s);
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+
+ if (count >= 4) {
+ int32x4_t wide_i, wide_lo;
+ int32x4_t wide_fx1;
+ int32x4_t wide_fy1;
+ int32x4_t wide_x, wide_y;
+
+ while (count >= 4) {
+ /* RBE: it's good, but:
+ * -- we spill a constant that could be easily regnerated
+ * [perhaps tweak gcc's NEON constant costs?]
+ */
+
+ /* load src: x-y-x-y-x-y-x-y */
+ {
+ register int32x4_t q0 asm ("q0");
+ register int32x4_t q1 asm ("q1");
+ asm ("vld2.32 {q0-q1},[%2] /* x=%q0 y=%q1 */"
+ : "=w" (q0), "=w" (q1)
+ : "r" (srcXY));
+ wide_x = q0; wide_y = q1;
+ }
+
+ /* do the X side, then the Y side, then interleave them */
+
+ wide_x = vsubq_s32(wide_x, vdupq_n_s32 (oneX>>1));
+
+ /* original expands to:
+ * unsigned i = SkClampMax((f) >> 16, max);
+ * i = (i << 4) | (((f) >> 12) & 0xF);
+ * return (i << 14) | (SkClampMax(((f + one)) >> 16, max));
+ */
+
+ /* i = SkClampMax(f>>16, maxX) */
+ wide_i = vmaxq_s32 (vshrq_n_s32 (wide_x, 16), vdupq_n_s32 (0));
+ wide_i = vminq_s32 (wide_i, vdupq_n_s32 (maxX));
+
+ /* i<<4 | TILEX_LOW_BITS(fx) */
+ wide_lo = vshrq_n_s32 (wide_x, 12);
+ wide_i = vsliq_n_s32 (wide_lo, wide_i, 4);
+
+ /* i<<14 */
+ wide_i = vshlq_n_s32 (wide_i, 14);
+
+ /* SkClampMax(((f + one)) >> 16, max) */
+ wide_fx1 = vaddq_s32 (wide_x, vdupq_n_s32(oneX));
+ wide_fx1 = vmaxq_s32 (vshrq_n_s32 (wide_fx1, 16), vdupq_n_s32 (0));
+ wide_fx1 = vminq_s32 (wide_fx1, vdupq_n_s32 (maxX));
+
+ /* final combination */
+ wide_x = vorrq_s32 (wide_i, wide_fx1);
+
+
+ /* And now the Y side */
+
+ wide_y = vsubq_s32(wide_y, vdupq_n_s32 (oneY>>1));
+
+ /* i = SkClampMax(f>>16, maxX) */
+ wide_i = vmaxq_s32 (vshrq_n_s32 (wide_y, 16), vdupq_n_s32 (0));
+ wide_i = vminq_s32 (wide_i, vdupq_n_s32 (maxY));
+
+ /* i<<4 | TILEX_LOW_BITS(fx) */
+ wide_lo = vshrq_n_s32 (wide_y, 12);
+ wide_i = vsliq_n_s32 (wide_lo, wide_i, 4);
+
+ /* i<<14 */
+ wide_i = vshlq_n_s32 (wide_i, 14);
+
+ /* SkClampMax(((f + one)) >> 16, max) */
+
+ /* wide_fy1_1 and wide_fy1_2 are just temporary variables to
+ * work-around an ICE in debug */
+ int32x4_t wide_fy1_1 = vaddq_s32 (wide_y, vdupq_n_s32(oneY));
+ int32x4_t wide_fy1_2 = vmaxq_s32 (vshrq_n_s32 (wide_fy1_1, 16),
+ vdupq_n_s32 (0));
+ wide_fy1 = vminq_s32 (wide_fy1_2, vdupq_n_s32 (maxY));
+
+ /* final combination */
+ wide_y = vorrq_s32 (wide_i, wide_fy1);
+
+ /* switch them around; have to do it this way to get them
+ * in the proper registers to match our instruction */
+
+ /* iteration bookkeeping, ahead of the asm() for scheduling */
+ srcXY += 2*4;
+ count -= 4;
+
+ /* store interleaved as y-x-y-x-y-x-y-x (NB != read order) */
+ {
+ register int32x4_t q0 asm ("q0") = wide_y;
+ register int32x4_t q1 asm ("q1") = wide_x;
+
+ asm ("vst2.32 {q0-q1},[%2] /* y=%q0 x=%q1 */"
+ :
+ : "w" (q0), "w" (q1), "r" (xy));
+ }
+
+ /* on to the next iteration */
+ /* count, srcXY are handled above */
+ xy += 2*4;
+ }
+ }
+
+ /* was do-while; NEON code invalidates original count>0 assumption */
+ while (--count >= 0) {
+ /* NB: we read x/y, we write y/x */
+ *xy++ = PACK_FILTER_Y_NAME(srcXY[1] - (oneY >> 1), maxY,
+ oneY PREAMBLE_ARG_Y);
+ *xy++ = PACK_FILTER_X_NAME(srcXY[0] - (oneX >> 1), maxX,
+ oneX PREAMBLE_ARG_X);
+ srcXY += 2;
+ }
+ }
+}
+
+const SkBitmapProcState::MatrixProc MAKENAME(_Procs)[] = {
+ SCALE_NOFILTER_NAME,
+ SCALE_FILTER_NAME,
+ AFFINE_NOFILTER_NAME,
+ AFFINE_FILTER_NAME,
+ PERSP_NOFILTER_NAME,
+ PERSP_FILTER_NAME
+};
+
+#undef MAKENAME
+#undef TILEX_PROCF
+#undef TILEY_PROCF
+#ifdef CHECK_FOR_DECAL
+ #undef CHECK_FOR_DECAL
+#endif
+
+#undef SCALE_NOFILTER_NAME
+#undef SCALE_FILTER_NAME
+#undef AFFINE_NOFILTER_NAME
+#undef AFFINE_FILTER_NAME
+#undef PERSP_NOFILTER_NAME
+#undef PERSP_FILTER_NAME
+
+#undef PREAMBLE
+#undef PREAMBLE_PARAM_X
+#undef PREAMBLE_PARAM_Y
+#undef PREAMBLE_ARG_X
+#undef PREAMBLE_ARG_Y
+
+#undef TILEX_LOW_BITS
+#undef TILEY_LOW_BITS
diff --git a/opts/SkBitmapProcState_matrix_repeat_neon.h b/opts/SkBitmapProcState_matrix_repeat_neon.h
new file mode 100644
index 00000000..55e2997a
--- /dev/null
+++ b/opts/SkBitmapProcState_matrix_repeat_neon.h
@@ -0,0 +1,542 @@
+/* NEON optimized code (C) COPYRIGHT 2009 Motorola
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Modifications done in-house at Motorola
+ *
+ * this is a clone of SkBitmapProcState_matrix.h
+ * and has been tuned to work with the NEON unit.
+ *
+ * Still going back and forth between whether this approach
+ * (clone the entire SkBitmapProcState_matrix.h file or
+ * if I should put just the modified routines in here and
+ * then use a construct like #define DONT_DO_THIS_FUNCTION or
+ * something like that...
+ *
+ * This is for the RepeatX_RepeatY part of the world
+ */
+
+
+#include <arm_neon.h>
+
+/*
+ * This has been modified on the knowledge that (at the time)
+ * we had the following macro definitions in the parent file
+ *
+ * #define MAKENAME(suffix) RepeatX_RepeatY ## suffix
+ * #define TILEX_PROCF(fx, max) (((fx) & 0xFFFF) * ((max) + 1) >> 16)
+ * #define TILEY_PROCF(fy, max) (((fy) & 0xFFFF) * ((max) + 1) >> 16)
+ * #define TILEX_LOW_BITS(fx, max) ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+ * #define TILEY_LOW_BITS(fy, max) ((((fy) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
+ */
+
+/* SkClampMax(val,max) -- bound to 0..max */
+
+#define SCALE_NOFILTER_NAME MAKENAME(_nofilter_scale)
+#define SCALE_FILTER_NAME MAKENAME(_filter_scale)
+#define AFFINE_NOFILTER_NAME MAKENAME(_nofilter_affine)
+#define AFFINE_FILTER_NAME MAKENAME(_filter_affine)
+#define PERSP_NOFILTER_NAME MAKENAME(_nofilter_persp)
+#define PERSP_FILTER_NAME MAKENAME(_filter_persp)
+
+#define PACK_FILTER_X_NAME MAKENAME(_pack_filter_x)
+#define PACK_FILTER_Y_NAME MAKENAME(_pack_filter_y)
+
+#ifndef PREAMBLE
+ #define PREAMBLE(state)
+ #define PREAMBLE_PARAM_X
+ #define PREAMBLE_PARAM_Y
+ #define PREAMBLE_ARG_X
+ #define PREAMBLE_ARG_Y
+#endif
+
+static void SCALE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+
+ PREAMBLE(s);
+ // we store y, x, x, x, x, x
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ SkFixed fx;
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ fx = SkScalarToFixed(pt.fY);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ *xy++ = TILEY_PROCF(fx, maxY);
+ fx = SkScalarToFixed(pt.fX);
+ }
+
+ if (0 == maxX) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ const SkFixed dx = s.fInvSx;
+
+#ifdef CHECK_FOR_DECAL
+ // test if we don't need to apply the tile proc
+ if ((unsigned)(fx >> 16) <= maxX &&
+ (unsigned)((fx + dx * (count - 1)) >> 16) <= maxX) {
+ decal_nofilter_scale_neon(xy, fx, dx, count);
+ } else
+#endif
+ {
+ int i;
+
+ /* RBE: very much like done in decal_nofilter ,
+ * but some processing of the 'fx' information
+ * TILEX_PROCF(fx, max) (((fx) & 0xFFFF) * ((max) + 1) >> 16)
+ */
+ if (count >= 8) {
+ /* SkFixed is 16.16 fixed point */
+ SkFixed dx2 = dx+dx;
+ SkFixed dx4 = dx2+dx2;
+ SkFixed dx8 = dx4+dx4;
+
+ /* now build fx/fx+dx/fx+2dx/fx+3dx */
+ SkFixed fx1, fx2, fx3;
+ int32x4_t lbase, hbase;
+ int16_t *dst16 = (int16_t *)xy;
+
+ fx1 = fx+dx;
+ fx2 = fx1+dx;
+ fx3 = fx2+dx;
+
+ lbase = vdupq_n_s32(fx);
+ lbase = vsetq_lane_s32(fx1, lbase, 1);
+ lbase = vsetq_lane_s32(fx2, lbase, 2);
+ lbase = vsetq_lane_s32(fx3, lbase, 3);
+ hbase = vaddq_s32(lbase, vdupq_n_s32(dx4));
+
+ /* store & bump */
+ do
+ {
+ int32x4_t lout;
+ int32x4_t hout;
+ int16x8_t hi16;
+
+ /* TILEX_PROCF(fx, max) (((fx)&0xFFFF)*((max)+1)>> 16) */
+ /* mask to low 16 [would like to use uzp tricks) */
+ lout = vandq_s32(lbase, vdupq_n_s32(0xffff));
+ hout = vandq_s32(hbase, vdupq_n_s32(0xffff));
+ /* bare multiplication, not SkFixedMul */
+ lout = vmulq_s32(lout, vdupq_n_s32(maxX+1));
+ hout = vmulq_s32(hout, vdupq_n_s32(maxX+1));
+
+ /* extraction, using uzp */
+ /* this is ok -- we want all hi(lout)s then all hi(hout)s */
+ asm ("vuzpq.16 %q0, %q1" : "+w" (lout), "+w" (hout));
+ hi16 = vreinterpretq_s16_s32(hout);
+ vst1q_s16(dst16, hi16);
+
+ /* bump our base on to the next */
+ lbase = vaddq_s32 (lbase, vdupq_n_s32(dx8));
+ hbase = vaddq_s32 (hbase, vdupq_n_s32(dx8));
+ dst16 += 8;
+ count -= 8;
+ fx += dx8;
+ } while (count >= 8);
+ xy = (uint32_t *) dst16;
+ }
+ uint16_t* xx = (uint16_t*)xy;
+ for (i = count; i > 0; --i) {
+ *xx++ = TILEX_PROCF(fx, maxX); fx += dx;
+ }
+ }
+}
+
+// note: we could special-case on a matrix which is skewed in X but not Y.
+// this would require a more general setup thatn SCALE does, but could use
+// SCALE's inner loop that only looks at dx
+
+
+static void AFFINE_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed fx = SkScalarToFixed(srcPt.fX);
+ SkFixed fy = SkScalarToFixed(srcPt.fY);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+#if 0
+ int ocount = count;
+ uint32_t *oxy = xy;
+ SkFixed bfx = fx, bfy=fy, bdx=dx, bdy=dy;
+#endif
+
+
+ if (0) { extern void rbe(void); rbe(); }
+
+ /* RBE: benchmarks show this eats up time; can we neonize it? */
+ /* RBE: very much like done in decal_nofilter ,
+ * but some processing of the 'fx' information
+ * TILEX_PROCF(fx, max) (((fx) & 0xFFFF) * ((max) + 1) >> 16)
+ */
+ if (count >= 4) {
+ /* SkFixed is 16.16 fixed point */
+ SkFixed dx4 = dx*4;
+ SkFixed dy4 = dy*4;
+
+ /* now build fx/fx+dx/fx+2dx/fx+3dx */
+ int32x4_t xbase, ybase;
+ int16_t *dst16 = (int16_t *)xy;
+
+ /* synthesize 4x for both X and Y */
+ xbase = vdupq_n_s32(fx);
+ xbase = vsetq_lane_s32(fx+dx, xbase, 1);
+ xbase = vsetq_lane_s32(fx+dx+dx, xbase, 2);
+ xbase = vsetq_lane_s32(fx+dx+dx+dx, xbase, 3);
+
+ ybase = vdupq_n_s32(fy);
+ ybase = vsetq_lane_s32(fy+dy, ybase, 1);
+ ybase = vsetq_lane_s32(fy+dy+dy, ybase, 2);
+ ybase = vsetq_lane_s32(fy+dy+dy+dy, ybase, 3);
+
+ /* store & bump */
+ do {
+ int32x4_t xout;
+ int32x4_t yout;
+ int16x8_t hi16;
+
+ /* TILEX_PROCF(fx, max) (((fx)&0xFFFF)*((max)+1)>> 16) */
+ /* mask to low 16 [would like to use uzp tricks) */
+ xout = vandq_s32(xbase, vdupq_n_s32(0xffff));
+ yout = vandq_s32(ybase, vdupq_n_s32(0xffff));
+ /* bare multiplication, not SkFixedMul */
+ xout = vmulq_s32(xout, vdupq_n_s32(maxX+1));
+ yout = vmulq_s32(yout, vdupq_n_s32(maxY+1));
+
+ /* put hi16 from xout over low16 from yout */
+ yout = vsriq_n_s32(yout, xout, 16);
+
+ /* and then yout has the interleaved upper 16's */
+ hi16 = vreinterpretq_s16_s32(yout);
+ vst1q_s16(dst16, hi16);
+
+ /* bump preserved base & on to the next */
+ xbase = vaddq_s32 (xbase, vdupq_n_s32(dx4));
+ ybase = vaddq_s32 (ybase, vdupq_n_s32(dy4));
+ dst16 += 8; /* 8 x16 aka 4x32 */
+ count -= 4;
+ fx += dx4;
+ fy += dy4;
+ } while (count >= 4);
+ xy = (uint32_t *) dst16;
+ }
+
+#if 0
+ /* diagnostics... see whether we agree with the NEON code */
+ int bad = 0;
+ uint32_t *myxy = oxy;
+ int myi = (-1);
+ SkFixed ofx = bfx, ofy= bfy, odx= bdx, ody= bdy;
+ for (myi = ocount; myi > 0; --myi) {
+ uint32_t val = (TILEY_PROCF(ofy, maxY) << 16) | TILEX_PROCF(ofx, maxX);
+ if (val != *myxy++) {
+ bad++;
+ break;
+ }
+ ofx += odx; ofy += ody;
+ }
+ if (bad) {
+ SkDebugf("repeat-nofilter-affine fails\n");
+ SkDebugf("count %d myi %d\n", ocount, myi);
+ SkDebugf(" bfx %08x, bdx %08x, bfy %08x bdy %08x\n",
+ bfx, bdx, bfy, bdy);
+ SkDebugf("maxX %08x maxY %08x\n", maxX, maxY);
+ }
+#endif
+
+ for (int i = count; i > 0; --i) {
+ /* fx, fy, dx, dy are all 32 bit 16.16 fixed point */
+ /* (((fx) & 0xFFFF) * ((max) + 1) >> 16) */
+ *xy++ = (TILEY_PROCF(fy, maxY) << 16) | TILEX_PROCF(fx, maxX);
+ fx += dx; fy += dy;
+ }
+}
+
+static void PERSP_NOFILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy,
+ int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ PREAMBLE(s);
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+
+ /* RBE: */
+ /* TILEX_PROCF(fx, max) (((fx) & 0xFFFF) * ((max) + 1) >> 16) */
+ /* it's a little more complicated than what I did for the
+ * clamp case -- where I could immediately snip to the top
+ * 16 bits and do my min/max games there.
+ * ... might only be able to get 4x unrolling here
+ */
+
+ /* vld2 to get a set of 32x4's ... */
+ /* do the tile[xy]_procf operations */
+ /* which includes doing vuzp to get hi16's */
+ /* store it */
+ /* -- inner loop (other than vld2) can be had from above */
+
+ /* srcXY is a batch of 32 bit numbers X0,Y0,X1,Y1...
+ * but we immediately discard the low 16 bits...
+ * so what we're going to do is vld4, which will give us
+ * xlo,xhi,ylo,yhi distribution and we can ignore the 'lo'
+ * parts....
+ */
+ if (0) { extern void rbe(void); rbe(); }
+ if (count >= 8) {
+ int32_t *mysrc = (int32_t *) srcXY;
+ int16_t *mydst = (int16_t *) xy;
+ do {
+ int32x4_t x, y, x2, y2;
+ int16x8_t hi, hi2;
+
+ /* read array of x,y,x,y,x,y */
+ /* vld2 does the de-interleaving for us */
+ /* isolate reg-bound scopes; gcc will minimize register
+ * motion if possible; this ensures that we don't lose
+ * a register across a debugging call because it happens
+ * to be bound into a call-clobbered register
+ */
+ {
+ register int32x4_t q0 asm("q0");
+ register int32x4_t q1 asm("q1");
+ asm ("vld2.32 {q0-q1},[%2] /* x=%q0 y=%q1 */"
+ : "=w" (q0), "=w" (q1)
+ : "r" (mysrc)
+ );
+ x = q0; y = q1;
+ }
+
+ /* offset == 256 bits == 32 bytes == 8 longs */
+ {
+ register int32x4_t q2 asm("q2");
+ register int32x4_t q3 asm("q3");
+ asm ("vld2.32 {q2-q3},[%2] /* x=%q0 y=%q1 */"
+ : "=w" (q2), "=w" (q3)
+ : "r" (mysrc+8)
+ );
+ x2 = q2; y2 = q3;
+ }
+
+ /* TILEX_PROCF(fx, max) (((fx)&0xFFFF)*((max)+1)>> 16) */
+ /* mask to low 16 [would like to use uzp tricks) */
+ /* bare multiplication, not SkFixedMul */
+ x = vandq_s32(x, vdupq_n_s32(0xffff));
+ x = vmulq_s32(x, vdupq_n_s32(maxX+1));
+ y = vandq_s32(y, vdupq_n_s32(0xffff));
+ y = vmulq_s32(y, vdupq_n_s32(maxY+1));
+
+ x2 = vandq_s32(x2, vdupq_n_s32(0xffff));
+ x2 = vmulq_s32(x2, vdupq_n_s32(maxX+1));
+ y2 = vandq_s32(y2, vdupq_n_s32(0xffff));
+ y2 = vmulq_s32(y2, vdupq_n_s32(maxY+1));
+
+ /* now collect interleaved high 16's */
+ /* (hi-x, hi-y)4 (hi-x2; hi-y2)4 */
+
+ /* extraction, using uzp, leaves hi16's in y */
+ y = vsriq_n_s32(y, x, 16);
+ hi = vreinterpretq_s16_s32(y);
+ vst1q_s16(mydst, hi);
+
+ /* and likewise for the second 8 entries */
+ y2 = vsriq_n_s32(y2, x2, 16);
+ hi2 = vreinterpretq_s16_s32(y2);
+ vst1q_s16(mydst+8, hi2);
+
+ /* XXX: gcc isn't interleaving these with the NEON ops
+ * but i think that all the scoreboarding works out */
+ count -= 8; /* 8 iterations */
+ mysrc += 16; /* 16 longs */
+ mydst += 16; /* 16 shorts, aka 8 longs */
+ } while (count >= 8);
+ /* get xy and srcXY fixed up */
+ srcXY = (const SkFixed *) mysrc;
+ xy = (uint32_t *) mydst;
+ }
+ while (--count >= 0) {
+ *xy++ = (TILEY_PROCF(srcXY[1], maxY) << 16) |
+ TILEX_PROCF(srcXY[0], maxX);
+ srcXY += 2;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static inline uint32_t PACK_FILTER_Y_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_Y) {
+ unsigned i = TILEY_PROCF(f, max);
+ i = (i << 4) | TILEY_LOW_BITS(f, max);
+ return (i << 14) | (TILEY_PROCF((f + one), max));
+}
+
+static inline uint32_t PACK_FILTER_X_NAME(SkFixed f, unsigned max,
+ SkFixed one PREAMBLE_PARAM_X) {
+ unsigned i = TILEX_PROCF(f, max);
+ i = (i << 4) | TILEX_LOW_BITS(f, max);
+ return (i << 14) | (TILEX_PROCF((f + one), max));
+}
+
+static void SCALE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+
+ PREAMBLE(s);
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ const SkFixed one = s.fFilterOneX;
+ const SkFractionalInt dx = s.fInvSxFractionalInt;
+ SkFractionalInt fx;
+
+ {
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ const SkFixed fy = SkScalarToFixed(pt.fY) - (s.fFilterOneY >> 1);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ // compute our two Y values up front
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, s.fFilterOneY PREAMBLE_ARG_Y);
+ // now initialize fx
+ fx = SkScalarToFractionalInt(pt.fX) - (SkFixedToFractionalInt(one) >> 1);
+ }
+
+#ifdef CHECK_FOR_DECAL
+ // test if we don't need to apply the tile proc
+ if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
+ decal_filter_scale_neon(xy, SkFractionalIntToFixed(fx),
+ SkFractionalIntToFixed(dx), count);
+ } else
+#endif
+ {
+ do {
+ SkFixed fixedFx = SkFractionalIntToFixed(fx);
+ *xy++ = PACK_FILTER_X_NAME(fixedFx, maxX, one PREAMBLE_ARG_X);
+ fx += dx;
+ } while (--count != 0);
+ }
+}
+
+static void AFFINE_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ PREAMBLE(s);
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+ SkFixed fx = SkScalarToFixed(srcPt.fX) - (oneX >> 1);
+ SkFixed fy = SkScalarToFixed(srcPt.fY) - (oneY >> 1);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+
+ do {
+ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, oneY PREAMBLE_ARG_Y);
+ fy += dy;
+ *xy++ = PACK_FILTER_X_NAME(fx, maxX, oneX PREAMBLE_ARG_X);
+ fx += dx;
+ } while (--count != 0);
+}
+
+static void PERSP_FILTER_NAME(const SkBitmapProcState& s,
+ uint32_t* SK_RESTRICT xy, int count,
+ int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kPerspective_Mask);
+
+ extern void rbe(void);
+
+ PREAMBLE(s);
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+
+
+
+ SkPerspIter iter(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, count);
+
+ while ((count = iter.next()) != 0) {
+ const SkFixed* SK_RESTRICT srcXY = iter.getXY();
+ do {
+ *xy++ = PACK_FILTER_Y_NAME(srcXY[1] - (oneY >> 1), maxY,
+ oneY PREAMBLE_ARG_Y);
+ *xy++ = PACK_FILTER_X_NAME(srcXY[0] - (oneX >> 1), maxX,
+ oneX PREAMBLE_ARG_X);
+ srcXY += 2;
+ } while (--count != 0);
+ }
+}
+
+const SkBitmapProcState::MatrixProc MAKENAME(_Procs)[] = {
+ SCALE_NOFILTER_NAME,
+ SCALE_FILTER_NAME,
+ AFFINE_NOFILTER_NAME,
+ AFFINE_FILTER_NAME,
+ PERSP_NOFILTER_NAME,
+ PERSP_FILTER_NAME
+};
+
+#undef MAKENAME
+#undef TILEX_PROCF
+#undef TILEY_PROCF
+#ifdef CHECK_FOR_DECAL
+ #undef CHECK_FOR_DECAL
+#endif
+
+#undef SCALE_NOFILTER_NAME
+#undef SCALE_FILTER_NAME
+#undef AFFINE_NOFILTER_NAME
+#undef AFFINE_FILTER_NAME
+#undef PERSP_NOFILTER_NAME
+#undef PERSP_FILTER_NAME
+
+#undef PREAMBLE
+#undef PREAMBLE_PARAM_X
+#undef PREAMBLE_PARAM_Y
+#undef PREAMBLE_ARG_X
+#undef PREAMBLE_ARG_Y
+
+#undef TILEX_LOW_BITS
+#undef TILEY_LOW_BITS
diff --git a/opts/SkBitmapProcState_opts_SSE2.cpp b/opts/SkBitmapProcState_opts_SSE2.cpp
new file mode 100644
index 00000000..0b079977
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_SSE2.cpp
@@ -0,0 +1,766 @@
+
+/*
+ * Copyright 2009 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 <emmintrin.h>
+#include "SkBitmapProcState_opts_SSE2.h"
+#include "SkPaint.h"
+#include "SkUtils.h"
+
+void S32_opaque_D32_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkASSERT(s.fBitmap->config() == SkBitmap::kARGB_8888_Config);
+ SkASSERT(s.fAlphaScale == 256);
+
+ const char* srcAddr = static_cast<const char*>(s.fBitmap->getPixels());
+ size_t rb = s.fBitmap->rowBytes();
+ uint32_t XY = *xy++;
+ unsigned y0 = XY >> 14;
+ const uint32_t* row0 = reinterpret_cast<const uint32_t*>(srcAddr + (y0 >> 4) * rb);
+ const uint32_t* row1 = reinterpret_cast<const uint32_t*>(srcAddr + (XY & 0x3FFF) * rb);
+ unsigned subY = y0 & 0xF;
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 16)
+ __m128i sixteen = _mm_cvtsi32_si128(16);
+
+ // ( 0, 0, 0, 0, 16, 16, 16, 16)
+ sixteen = _mm_shufflelo_epi16(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, y)
+ __m128i allY = _mm_cvtsi32_si128(subY);
+
+ // ( 0, 0, 0, 0, y, y, y, y)
+ allY = _mm_shufflelo_epi16(allY, 0);
+
+ // ( 0, 0, 0, 0, 16-y, 16-y, 16-y, 16-y)
+ __m128i negY = _mm_sub_epi16(sixteen, allY);
+
+ // (16-y, 16-y, 16-y, 16-y, y, y, y, y)
+ allY = _mm_unpacklo_epi64(allY, negY);
+
+ // (16, 16, 16, 16, 16, 16, 16, 16 )
+ sixteen = _mm_shuffle_epi32(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 0)
+ __m128i zero = _mm_setzero_si128();
+ do {
+ uint32_t XX = *xy++; // x0:14 | 4 | x1:14
+ unsigned x0 = XX >> 18;
+ unsigned x1 = XX & 0x3FFF;
+
+ // (0, 0, 0, 0, 0, 0, 0, x)
+ __m128i allX = _mm_cvtsi32_si128((XX >> 14) & 0x0F);
+
+ // (0, 0, 0, 0, x, x, x, x)
+ allX = _mm_shufflelo_epi16(allX, 0);
+
+ // (x, x, x, x, x, x, x, x)
+ allX = _mm_shuffle_epi32(allX, 0);
+
+ // (16-x, 16-x, 16-x, 16-x, 16-x, 16-x, 16-x)
+ __m128i negX = _mm_sub_epi16(sixteen, allX);
+
+ // Load 4 samples (pixels).
+ __m128i a00 = _mm_cvtsi32_si128(row0[x0]);
+ __m128i a01 = _mm_cvtsi32_si128(row0[x1]);
+ __m128i a10 = _mm_cvtsi32_si128(row1[x0]);
+ __m128i a11 = _mm_cvtsi32_si128(row1[x1]);
+
+ // (0, 0, a00, a10)
+ __m128i a00a10 = _mm_unpacklo_epi32(a10, a00);
+
+ // Expand to 16 bits per component.
+ a00a10 = _mm_unpacklo_epi8(a00a10, zero);
+
+ // ((a00 * (16-y)), (a10 * y)).
+ a00a10 = _mm_mullo_epi16(a00a10, allY);
+
+ // (a00 * (16-y) * (16-x), a10 * y * (16-x)).
+ a00a10 = _mm_mullo_epi16(a00a10, negX);
+
+ // (0, 0, a01, a10)
+ __m128i a01a11 = _mm_unpacklo_epi32(a11, a01);
+
+ // Expand to 16 bits per component.
+ a01a11 = _mm_unpacklo_epi8(a01a11, zero);
+
+ // (a01 * (16-y)), (a11 * y)
+ a01a11 = _mm_mullo_epi16(a01a11, allY);
+
+ // (a01 * (16-y) * x), (a11 * y * x)
+ a01a11 = _mm_mullo_epi16(a01a11, allX);
+
+ // (a00*w00 + a01*w01, a10*w10 + a11*w11)
+ __m128i sum = _mm_add_epi16(a00a10, a01a11);
+
+ // (DC, a00*w00 + a01*w01)
+ __m128i shifted = _mm_shuffle_epi32(sum, 0xEE);
+
+ // (DC, a00*w00 + a01*w01 + a10*w10 + a11*w11)
+ sum = _mm_add_epi16(sum, shifted);
+
+ // Divide each 16 bit component by 256.
+ sum = _mm_srli_epi16(sum, 8);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum = _mm_packus_epi16(sum, zero);
+
+ // Extract low int and store.
+ *colors++ = _mm_cvtsi128_si32(sum);
+ } while (--count > 0);
+}
+
+void S32_alpha_D32_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkASSERT(s.fBitmap->config() == SkBitmap::kARGB_8888_Config);
+ SkASSERT(s.fAlphaScale < 256);
+
+ const char* srcAddr = static_cast<const char*>(s.fBitmap->getPixels());
+ size_t rb = s.fBitmap->rowBytes();
+ uint32_t XY = *xy++;
+ unsigned y0 = XY >> 14;
+ const uint32_t* row0 = reinterpret_cast<const uint32_t*>(srcAddr + (y0 >> 4) * rb);
+ const uint32_t* row1 = reinterpret_cast<const uint32_t*>(srcAddr + (XY & 0x3FFF) * rb);
+ unsigned subY = y0 & 0xF;
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 16)
+ __m128i sixteen = _mm_cvtsi32_si128(16);
+
+ // ( 0, 0, 0, 0, 16, 16, 16, 16)
+ sixteen = _mm_shufflelo_epi16(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, y)
+ __m128i allY = _mm_cvtsi32_si128(subY);
+
+ // ( 0, 0, 0, 0, y, y, y, y)
+ allY = _mm_shufflelo_epi16(allY, 0);
+
+ // ( 0, 0, 0, 0, 16-y, 16-y, 16-y, 16-y)
+ __m128i negY = _mm_sub_epi16(sixteen, allY);
+
+ // (16-y, 16-y, 16-y, 16-y, y, y, y, y)
+ allY = _mm_unpacklo_epi64(allY, negY);
+
+ // (16, 16, 16, 16, 16, 16, 16, 16 )
+ sixteen = _mm_shuffle_epi32(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 0)
+ __m128i zero = _mm_setzero_si128();
+
+ // ( alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha )
+ __m128i alpha = _mm_set1_epi16(s.fAlphaScale);
+
+ do {
+ uint32_t XX = *xy++; // x0:14 | 4 | x1:14
+ unsigned x0 = XX >> 18;
+ unsigned x1 = XX & 0x3FFF;
+
+ // (0, 0, 0, 0, 0, 0, 0, x)
+ __m128i allX = _mm_cvtsi32_si128((XX >> 14) & 0x0F);
+
+ // (0, 0, 0, 0, x, x, x, x)
+ allX = _mm_shufflelo_epi16(allX, 0);
+
+ // (x, x, x, x, x, x, x, x)
+ allX = _mm_shuffle_epi32(allX, 0);
+
+ // (16-x, 16-x, 16-x, 16-x, 16-x, 16-x, 16-x)
+ __m128i negX = _mm_sub_epi16(sixteen, allX);
+
+ // Load 4 samples (pixels).
+ __m128i a00 = _mm_cvtsi32_si128(row0[x0]);
+ __m128i a01 = _mm_cvtsi32_si128(row0[x1]);
+ __m128i a10 = _mm_cvtsi32_si128(row1[x0]);
+ __m128i a11 = _mm_cvtsi32_si128(row1[x1]);
+
+ // (0, 0, a00, a10)
+ __m128i a00a10 = _mm_unpacklo_epi32(a10, a00);
+
+ // Expand to 16 bits per component.
+ a00a10 = _mm_unpacklo_epi8(a00a10, zero);
+
+ // ((a00 * (16-y)), (a10 * y)).
+ a00a10 = _mm_mullo_epi16(a00a10, allY);
+
+ // (a00 * (16-y) * (16-x), a10 * y * (16-x)).
+ a00a10 = _mm_mullo_epi16(a00a10, negX);
+
+ // (0, 0, a01, a10)
+ __m128i a01a11 = _mm_unpacklo_epi32(a11, a01);
+
+ // Expand to 16 bits per component.
+ a01a11 = _mm_unpacklo_epi8(a01a11, zero);
+
+ // (a01 * (16-y)), (a11 * y)
+ a01a11 = _mm_mullo_epi16(a01a11, allY);
+
+ // (a01 * (16-y) * x), (a11 * y * x)
+ a01a11 = _mm_mullo_epi16(a01a11, allX);
+
+ // (a00*w00 + a01*w01, a10*w10 + a11*w11)
+ __m128i sum = _mm_add_epi16(a00a10, a01a11);
+
+ // (DC, a00*w00 + a01*w01)
+ __m128i shifted = _mm_shuffle_epi32(sum, 0xEE);
+
+ // (DC, a00*w00 + a01*w01 + a10*w10 + a11*w11)
+ sum = _mm_add_epi16(sum, shifted);
+
+ // Divide each 16 bit component by 256.
+ sum = _mm_srli_epi16(sum, 8);
+
+ // Multiply by alpha.
+ sum = _mm_mullo_epi16(sum, alpha);
+
+ // Divide each 16 bit component by 256.
+ sum = _mm_srli_epi16(sum, 8);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum = _mm_packus_epi16(sum, zero);
+
+ // Extract low int and store.
+ *colors++ = _mm_cvtsi128_si32(sum);
+ } while (--count > 0);
+}
+
+static inline uint32_t ClampX_ClampY_pack_filter(SkFixed f, unsigned max,
+ SkFixed one) {
+ unsigned i = SkClampMax(f >> 16, max);
+ i = (i << 4) | ((f >> 12) & 0xF);
+ return (i << 14) | SkClampMax((f + one) >> 16, max);
+}
+
+/* SSE version of ClampX_ClampY_filter_scale()
+ * portable version is in core/SkBitmapProcState_matrix.h
+ */
+void ClampX_ClampY_filter_scale_SSE2(const SkBitmapProcState& s, uint32_t xy[],
+ int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+ SkASSERT(s.fInvKy == 0);
+
+ const unsigned maxX = s.fBitmap->width() - 1;
+ const SkFixed one = s.fFilterOneX;
+ const SkFixed dx = s.fInvSx;
+ SkFixed fx;
+
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ const SkFixed fy = SkScalarToFixed(pt.fY) - (s.fFilterOneY >> 1);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ // compute our two Y values up front
+ *xy++ = ClampX_ClampY_pack_filter(fy, maxY, s.fFilterOneY);
+ // now initialize fx
+ fx = SkScalarToFixed(pt.fX) - (one >> 1);
+
+ // test if we don't need to apply the tile proc
+ if (dx > 0 && (unsigned)(fx >> 16) <= maxX &&
+ (unsigned)((fx + dx * (count - 1)) >> 16) < maxX) {
+ if (count >= 4) {
+ // SSE version of decal_filter_scale
+ while ((size_t(xy) & 0x0F) != 0) {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *xy++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ count--;
+ }
+
+ __m128i wide_1 = _mm_set1_epi32(1);
+ __m128i wide_dx4 = _mm_set1_epi32(dx * 4);
+ __m128i wide_fx = _mm_set_epi32(fx + dx * 3, fx + dx * 2,
+ fx + dx, fx);
+
+ while (count >= 4) {
+ __m128i wide_out;
+
+ wide_out = _mm_slli_epi32(_mm_srai_epi32(wide_fx, 12), 14);
+ wide_out = _mm_or_si128(wide_out, _mm_add_epi32(
+ _mm_srai_epi32(wide_fx, 16), wide_1));
+
+ _mm_store_si128(reinterpret_cast<__m128i*>(xy), wide_out);
+
+ xy += 4;
+ fx += dx * 4;
+ wide_fx = _mm_add_epi32(wide_fx, wide_dx4);
+ count -= 4;
+ } // while count >= 4
+ } // if count >= 4
+
+ while (count-- > 0) {
+ SkASSERT((fx >> (16 + 14)) == 0);
+ *xy++ = (fx >> 12 << 14) | ((fx >> 16) + 1);
+ fx += dx;
+ }
+ } else {
+ // SSE2 only support 16bit interger max & min, so only process the case
+ // maxX less than the max 16bit interger. Actually maxX is the bitmap's
+ // height, there should be rare bitmap whose height will be greater
+ // than max 16bit interger in the real world.
+ if ((count >= 4) && (maxX <= 0xFFFF)) {
+ while (((size_t)xy & 0x0F) != 0) {
+ *xy++ = ClampX_ClampY_pack_filter(fx, maxX, one);
+ fx += dx;
+ count--;
+ }
+
+ __m128i wide_fx = _mm_set_epi32(fx + dx * 3, fx + dx * 2,
+ fx + dx, fx);
+ __m128i wide_dx4 = _mm_set1_epi32(dx * 4);
+ __m128i wide_one = _mm_set1_epi32(one);
+ __m128i wide_maxX = _mm_set1_epi32(maxX);
+ __m128i wide_mask = _mm_set1_epi32(0xF);
+
+ while (count >= 4) {
+ __m128i wide_i;
+ __m128i wide_lo;
+ __m128i wide_fx1;
+
+ // i = SkClampMax(f>>16,maxX)
+ wide_i = _mm_max_epi16(_mm_srli_epi32(wide_fx, 16),
+ _mm_setzero_si128());
+ wide_i = _mm_min_epi16(wide_i, wide_maxX);
+
+ // i<<4 | TILEX_LOW_BITS(fx)
+ wide_lo = _mm_srli_epi32(wide_fx, 12);
+ wide_lo = _mm_and_si128(wide_lo, wide_mask);
+ wide_i = _mm_slli_epi32(wide_i, 4);
+ wide_i = _mm_or_si128(wide_i, wide_lo);
+
+ // i<<14
+ wide_i = _mm_slli_epi32(wide_i, 14);
+
+ // SkClampMax(((f+one))>>16,max)
+ wide_fx1 = _mm_add_epi32(wide_fx, wide_one);
+ wide_fx1 = _mm_max_epi16(_mm_srli_epi32(wide_fx1, 16),
+ _mm_setzero_si128());
+ wide_fx1 = _mm_min_epi16(wide_fx1, wide_maxX);
+
+ // final combination
+ wide_i = _mm_or_si128(wide_i, wide_fx1);
+ _mm_store_si128(reinterpret_cast<__m128i*>(xy), wide_i);
+
+ wide_fx = _mm_add_epi32(wide_fx, wide_dx4);
+ fx += dx * 4;
+ xy += 4;
+ count -= 4;
+ } // while count >= 4
+ } // if count >= 4
+
+ while (count-- > 0) {
+ *xy++ = ClampX_ClampY_pack_filter(fx, maxX, one);
+ fx += dx;
+ }
+ }
+}
+
+/* SSE version of ClampX_ClampY_nofilter_scale()
+ * portable version is in core/SkBitmapProcState_matrix.h
+ */
+void ClampX_ClampY_nofilter_scale_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask)) == 0);
+
+ // we store y, x, x, x, x, x
+ const unsigned maxX = s.fBitmap->width() - 1;
+ SkFixed fx;
+ SkPoint pt;
+ s.fInvProc(s.fInvMatrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &pt);
+ fx = SkScalarToFixed(pt.fY);
+ const unsigned maxY = s.fBitmap->height() - 1;
+ *xy++ = SkClampMax(fx >> 16, maxY);
+ fx = SkScalarToFixed(pt.fX);
+
+ if (0 == maxX) {
+ // all of the following X values must be 0
+ memset(xy, 0, count * sizeof(uint16_t));
+ return;
+ }
+
+ const SkFixed dx = s.fInvSx;
+
+ // test if we don't need to apply the tile proc
+ if ((unsigned)(fx >> 16) <= maxX &&
+ (unsigned)((fx + dx * (count - 1)) >> 16) <= maxX) {
+ // SSE version of decal_nofilter_scale
+ if (count >= 8) {
+ while (((size_t)xy & 0x0F) != 0) {
+ *xy++ = pack_two_shorts(fx >> 16, (fx + dx) >> 16);
+ fx += 2 * dx;
+ count -= 2;
+ }
+
+ __m128i wide_dx4 = _mm_set1_epi32(dx * 4);
+ __m128i wide_dx8 = _mm_add_epi32(wide_dx4, wide_dx4);
+
+ __m128i wide_low = _mm_set_epi32(fx + dx * 3, fx + dx * 2,
+ fx + dx, fx);
+ __m128i wide_high = _mm_add_epi32(wide_low, wide_dx4);
+
+ while (count >= 8) {
+ __m128i wide_out_low = _mm_srli_epi32(wide_low, 16);
+ __m128i wide_out_high = _mm_srli_epi32(wide_high, 16);
+
+ __m128i wide_result = _mm_packs_epi32(wide_out_low,
+ wide_out_high);
+ _mm_store_si128(reinterpret_cast<__m128i*>(xy), wide_result);
+
+ wide_low = _mm_add_epi32(wide_low, wide_dx8);
+ wide_high = _mm_add_epi32(wide_high, wide_dx8);
+
+ xy += 4;
+ fx += dx * 8;
+ count -= 8;
+ }
+ } // if count >= 8
+
+ uint16_t* xx = reinterpret_cast<uint16_t*>(xy);
+ while (count-- > 0) {
+ *xx++ = SkToU16(fx >> 16);
+ fx += dx;
+ }
+ } else {
+ // SSE2 only support 16bit interger max & min, so only process the case
+ // maxX less than the max 16bit interger. Actually maxX is the bitmap's
+ // height, there should be rare bitmap whose height will be greater
+ // than max 16bit interger in the real world.
+ if ((count >= 8) && (maxX <= 0xFFFF)) {
+ while (((size_t)xy & 0x0F) != 0) {
+ *xy++ = pack_two_shorts(SkClampMax((fx + dx) >> 16, maxX),
+ SkClampMax(fx >> 16, maxX));
+ fx += 2 * dx;
+ count -= 2;
+ }
+
+ __m128i wide_dx4 = _mm_set1_epi32(dx * 4);
+ __m128i wide_dx8 = _mm_add_epi32(wide_dx4, wide_dx4);
+
+ __m128i wide_low = _mm_set_epi32(fx + dx * 3, fx + dx * 2,
+ fx + dx, fx);
+ __m128i wide_high = _mm_add_epi32(wide_low, wide_dx4);
+ __m128i wide_maxX = _mm_set1_epi32(maxX);
+
+ while (count >= 8) {
+ __m128i wide_out_low = _mm_srli_epi32(wide_low, 16);
+ __m128i wide_out_high = _mm_srli_epi32(wide_high, 16);
+
+ wide_out_low = _mm_max_epi16(wide_out_low,
+ _mm_setzero_si128());
+ wide_out_low = _mm_min_epi16(wide_out_low, wide_maxX);
+ wide_out_high = _mm_max_epi16(wide_out_high,
+ _mm_setzero_si128());
+ wide_out_high = _mm_min_epi16(wide_out_high, wide_maxX);
+
+ __m128i wide_result = _mm_packs_epi32(wide_out_low,
+ wide_out_high);
+ _mm_store_si128(reinterpret_cast<__m128i*>(xy), wide_result);
+
+ wide_low = _mm_add_epi32(wide_low, wide_dx8);
+ wide_high = _mm_add_epi32(wide_high, wide_dx8);
+
+ xy += 4;
+ fx += dx * 8;
+ count -= 8;
+ }
+ } // if count >= 8
+
+ uint16_t* xx = reinterpret_cast<uint16_t*>(xy);
+ while (count-- > 0) {
+ *xx++ = SkClampMax(fx >> 16, maxX);
+ fx += dx;
+ }
+ }
+}
+
+/* SSE version of ClampX_ClampY_filter_affine()
+ * portable version is in core/SkBitmapProcState_matrix.h
+ */
+void ClampX_ClampY_filter_affine_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed oneX = s.fFilterOneX;
+ SkFixed oneY = s.fFilterOneY;
+ SkFixed fx = SkScalarToFixed(srcPt.fX) - (oneX >> 1);
+ SkFixed fy = SkScalarToFixed(srcPt.fY) - (oneY >> 1);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ unsigned maxX = s.fBitmap->width() - 1;
+ unsigned maxY = s.fBitmap->height() - 1;
+
+ if (count >= 2 && (maxX <= 0xFFFF)) {
+ SkFixed dx2 = dx + dx;
+ SkFixed dy2 = dy + dy;
+
+ __m128i wide_f = _mm_set_epi32(fx + dx, fy + dy, fx, fy);
+ __m128i wide_d2 = _mm_set_epi32(dx2, dy2, dx2, dy2);
+ __m128i wide_one = _mm_set_epi32(oneX, oneY, oneX, oneY);
+ __m128i wide_max = _mm_set_epi32(maxX, maxY, maxX, maxY);
+ __m128i wide_mask = _mm_set1_epi32(0xF);
+
+ while (count >= 2) {
+ // i = SkClampMax(f>>16,maxX)
+ __m128i wide_i = _mm_max_epi16(_mm_srli_epi32(wide_f, 16),
+ _mm_setzero_si128());
+ wide_i = _mm_min_epi16(wide_i, wide_max);
+
+ // i<<4 | TILEX_LOW_BITS(f)
+ __m128i wide_lo = _mm_srli_epi32(wide_f, 12);
+ wide_lo = _mm_and_si128(wide_lo, wide_mask);
+ wide_i = _mm_slli_epi32(wide_i, 4);
+ wide_i = _mm_or_si128(wide_i, wide_lo);
+
+ // i<<14
+ wide_i = _mm_slli_epi32(wide_i, 14);
+
+ // SkClampMax(((f+one))>>16,max)
+ __m128i wide_f1 = _mm_add_epi32(wide_f, wide_one);
+ wide_f1 = _mm_max_epi16(_mm_srli_epi32(wide_f1, 16),
+ _mm_setzero_si128());
+ wide_f1 = _mm_min_epi16(wide_f1, wide_max);
+
+ // final combination
+ wide_i = _mm_or_si128(wide_i, wide_f1);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(xy), wide_i);
+
+ wide_f = _mm_add_epi32(wide_f, wide_d2);
+
+ fx += dx2;
+ fy += dy2;
+ xy += 4;
+ count -= 2;
+ } // while count >= 2
+ } // if count >= 2
+
+ while (count-- > 0) {
+ *xy++ = ClampX_ClampY_pack_filter(fy, maxY, oneY);
+ fy += dy;
+ *xy++ = ClampX_ClampY_pack_filter(fx, maxX, oneX);
+ fx += dx;
+ }
+}
+
+/* SSE version of ClampX_ClampY_nofilter_affine()
+ * portable version is in core/SkBitmapProcState_matrix.h
+ */
+void ClampX_ClampY_nofilter_affine_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y) {
+ SkASSERT(s.fInvType & SkMatrix::kAffine_Mask);
+ SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+ SkMatrix::kScale_Mask |
+ SkMatrix::kAffine_Mask)) == 0);
+
+ SkPoint srcPt;
+ s.fInvProc(s.fInvMatrix,
+ SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkFixed fx = SkScalarToFixed(srcPt.fX);
+ SkFixed fy = SkScalarToFixed(srcPt.fY);
+ SkFixed dx = s.fInvSx;
+ SkFixed dy = s.fInvKy;
+ int maxX = s.fBitmap->width() - 1;
+ int maxY = s.fBitmap->height() - 1;
+
+ if (count >= 4 && (maxX <= 0xFFFF)) {
+ while (((size_t)xy & 0x0F) != 0) {
+ *xy++ = (SkClampMax(fy >> 16, maxY) << 16) |
+ SkClampMax(fx >> 16, maxX);
+ fx += dx;
+ fy += dy;
+ count--;
+ }
+
+ SkFixed dx4 = dx * 4;
+ SkFixed dy4 = dy * 4;
+
+ __m128i wide_fx = _mm_set_epi32(fx + dx * 3, fx + dx * 2,
+ fx + dx, fx);
+ __m128i wide_fy = _mm_set_epi32(fy + dy * 3, fy + dy * 2,
+ fy + dy, fy);
+ __m128i wide_dx4 = _mm_set1_epi32(dx4);
+ __m128i wide_dy4 = _mm_set1_epi32(dy4);
+
+ __m128i wide_maxX = _mm_set1_epi32(maxX);
+ __m128i wide_maxY = _mm_set1_epi32(maxY);
+
+ while (count >= 4) {
+ // SkClampMax(fx>>16,maxX)
+ __m128i wide_lo = _mm_max_epi16(_mm_srli_epi32(wide_fx, 16),
+ _mm_setzero_si128());
+ wide_lo = _mm_min_epi16(wide_lo, wide_maxX);
+
+ // SkClampMax(fy>>16,maxY)
+ __m128i wide_hi = _mm_max_epi16(_mm_srli_epi32(wide_fy, 16),
+ _mm_setzero_si128());
+ wide_hi = _mm_min_epi16(wide_hi, wide_maxY);
+
+ // final combination
+ __m128i wide_i = _mm_or_si128(_mm_slli_epi32(wide_hi, 16),
+ wide_lo);
+ _mm_store_si128(reinterpret_cast<__m128i*>(xy), wide_i);
+
+ wide_fx = _mm_add_epi32(wide_fx, wide_dx4);
+ wide_fy = _mm_add_epi32(wide_fy, wide_dy4);
+
+ fx += dx4;
+ fy += dy4;
+ xy += 4;
+ count -= 4;
+ } // while count >= 4
+ } // if count >= 4
+
+ while (count-- > 0) {
+ *xy++ = (SkClampMax(fy >> 16, maxY) << 16) |
+ SkClampMax(fx >> 16, maxX);
+ fx += dx;
+ fy += dy;
+ }
+}
+
+/* SSE version of S32_D16_filter_DX_SSE2
+ * Definition is in section of "D16 functions for SRC == 8888" in SkBitmapProcState.cpp
+ * It combines S32_opaque_D32_filter_DX_SSE2 and SkPixel32ToPixel16
+ */
+void S32_D16_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint16_t* colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkASSERT(s.fBitmap->config() == SkBitmap::kARGB_8888_Config);
+ SkASSERT(s.fBitmap->isOpaque());
+
+ SkPMColor dstColor;
+ const char* srcAddr = static_cast<const char*>(s.fBitmap->getPixels());
+ size_t rb = s.fBitmap->rowBytes();
+ uint32_t XY = *xy++;
+ unsigned y0 = XY >> 14;
+ const uint32_t* row0 = reinterpret_cast<const uint32_t*>(srcAddr + (y0 >> 4) * rb);
+ const uint32_t* row1 = reinterpret_cast<const uint32_t*>(srcAddr + (XY & 0x3FFF) * rb);
+ unsigned subY = y0 & 0xF;
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 16)
+ __m128i sixteen = _mm_cvtsi32_si128(16);
+
+ // ( 0, 0, 0, 0, 16, 16, 16, 16)
+ sixteen = _mm_shufflelo_epi16(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, y)
+ __m128i allY = _mm_cvtsi32_si128(subY);
+
+ // ( 0, 0, 0, 0, y, y, y, y)
+ allY = _mm_shufflelo_epi16(allY, 0);
+
+ // ( 0, 0, 0, 0, 16-y, 16-y, 16-y, 16-y)
+ __m128i negY = _mm_sub_epi16(sixteen, allY);
+
+ // (16-y, 16-y, 16-y, 16-y, y, y, y, y)
+ allY = _mm_unpacklo_epi64(allY, negY);
+
+ // (16, 16, 16, 16, 16, 16, 16, 16 )
+ sixteen = _mm_shuffle_epi32(sixteen, 0);
+
+ // ( 0, 0, 0, 0, 0, 0, 0, 0)
+ __m128i zero = _mm_setzero_si128();
+
+ do {
+ uint32_t XX = *xy++; // x0:14 | 4 | x1:14
+ unsigned x0 = XX >> 18;
+ unsigned x1 = XX & 0x3FFF;
+
+ // (0, 0, 0, 0, 0, 0, 0, x)
+ __m128i allX = _mm_cvtsi32_si128((XX >> 14) & 0x0F);
+
+ // (0, 0, 0, 0, x, x, x, x)
+ allX = _mm_shufflelo_epi16(allX, 0);
+
+ // (x, x, x, x, x, x, x, x)
+ allX = _mm_shuffle_epi32(allX, 0);
+
+ // (16-x, 16-x, 16-x, 16-x, 16-x, 16-x, 16-x)
+ __m128i negX = _mm_sub_epi16(sixteen, allX);
+
+ // Load 4 samples (pixels).
+ __m128i a00 = _mm_cvtsi32_si128(row0[x0]);
+ __m128i a01 = _mm_cvtsi32_si128(row0[x1]);
+ __m128i a10 = _mm_cvtsi32_si128(row1[x0]);
+ __m128i a11 = _mm_cvtsi32_si128(row1[x1]);
+
+ // (0, 0, a00, a10)
+ __m128i a00a10 = _mm_unpacklo_epi32(a10, a00);
+
+ // Expand to 16 bits per component.
+ a00a10 = _mm_unpacklo_epi8(a00a10, zero);
+
+ // ((a00 * (16-y)), (a10 * y)).
+ a00a10 = _mm_mullo_epi16(a00a10, allY);
+
+ // (a00 * (16-y) * (16-x), a10 * y * (16-x)).
+ a00a10 = _mm_mullo_epi16(a00a10, negX);
+
+ // (0, 0, a01, a10)
+ __m128i a01a11 = _mm_unpacklo_epi32(a11, a01);
+
+ // Expand to 16 bits per component.
+ a01a11 = _mm_unpacklo_epi8(a01a11, zero);
+
+ // (a01 * (16-y)), (a11 * y)
+ a01a11 = _mm_mullo_epi16(a01a11, allY);
+
+ // (a01 * (16-y) * x), (a11 * y * x)
+ a01a11 = _mm_mullo_epi16(a01a11, allX);
+
+ // (a00*w00 + a01*w01, a10*w10 + a11*w11)
+ __m128i sum = _mm_add_epi16(a00a10, a01a11);
+
+ // (DC, a00*w00 + a01*w01)
+ __m128i shifted = _mm_shuffle_epi32(sum, 0xEE);
+
+ // (DC, a00*w00 + a01*w01 + a10*w10 + a11*w11)
+ sum = _mm_add_epi16(sum, shifted);
+
+ // Divide each 16 bit component by 256.
+ sum = _mm_srli_epi16(sum, 8);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum = _mm_packus_epi16(sum, zero);
+
+ // Extract low int and store.
+ dstColor = _mm_cvtsi128_si32(sum);
+
+ //*colors++ = SkPixel32ToPixel16(dstColor);
+ // below is much faster than the above. It's tested for Android benchmark--Softweg
+ __m128i _m_temp1 = _mm_set1_epi32(dstColor);
+ __m128i _m_temp2 = _mm_srli_epi32(_m_temp1, 3);
+
+ unsigned int r32 = _mm_cvtsi128_si32(_m_temp2);
+ unsigned r = (r32 & ((1<<5) -1)) << 11;
+
+ _m_temp2 = _mm_srli_epi32(_m_temp2, 7);
+ unsigned int g32 = _mm_cvtsi128_si32(_m_temp2);
+ unsigned g = (g32 & ((1<<6) -1)) << 5;
+
+ _m_temp2 = _mm_srli_epi32(_m_temp2, 9);
+ unsigned int b32 = _mm_cvtsi128_si32(_m_temp2);
+ unsigned b = (b32 & ((1<<5) -1));
+
+ *colors++ = r | g | b;
+
+ } while (--count > 0);
+}
diff --git a/opts/SkBitmapProcState_opts_SSE2.h b/opts/SkBitmapProcState_opts_SSE2.h
new file mode 100644
index 00000000..46e35a0f
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_SSE2.h
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright 2009 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 "SkBitmapProcState.h"
+
+void S32_opaque_D32_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
+void S32_alpha_D32_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
+void Color32_SSE2(SkPMColor dst[], const SkPMColor src[], int count,
+ SkPMColor color);
+void ClampX_ClampY_filter_scale_SSE2(const SkBitmapProcState& s, uint32_t xy[],
+ int count, int x, int y);
+void ClampX_ClampY_nofilter_scale_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void ClampX_ClampY_filter_affine_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void ClampX_ClampY_nofilter_affine_SSE2(const SkBitmapProcState& s,
+ uint32_t xy[], int count, int x, int y);
+void S32_D16_filter_DX_SSE2(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint16_t* colors);
diff --git a/opts/SkBitmapProcState_opts_SSSE3.cpp b/opts/SkBitmapProcState_opts_SSSE3.cpp
new file mode 100644
index 00000000..f8342eca
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_SSSE3.cpp
@@ -0,0 +1,724 @@
+/*
+ * 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 <tmmintrin.h> // SSSE3
+#include "SkBitmapProcState_opts_SSSE3.h"
+#include "SkPaint.h"
+#include "SkUtils.h"
+
+// adding anonymous namespace seemed to force gcc to inline directly the
+// instantiation, instead of creating the functions
+// S32_generic_D32_filter_DX_SSSE3<true> and
+// S32_generic_D32_filter_DX_SSSE3<false> which were then called by the
+// external functions.
+namespace {
+// In this file, variations for alpha and non alpha versions are implemented
+// with a template, as it makes the code more compact and a bit easier to
+// maintain, while making the compiler generate the same exact code as with
+// two functions that only differ by a few lines.
+
+
+// Prepare all necessary constants for a round of processing for two pixel
+// pairs.
+// @param xy is the location where the xy parameters for four pixels should be
+// read from. It is identical in concept with argument two of
+// S32_{opaque}_D32_filter_DX methods.
+// @param mask_3FFF vector of 32 bit constants containing 3FFF,
+// suitable to mask the bottom 14 bits of a XY value.
+// @param mask_000F vector of 32 bit constants containing 000F,
+// suitable to mask the bottom 4 bits of a XY value.
+// @param sixteen_8bit vector of 8 bit components containing the value 16.
+// @param mask_dist_select vector of 8 bit components containing the shuffling
+// parameters to reorder x[0-3] parameters.
+// @param all_x_result vector of 8 bit components that will contain the
+// (4x(x3), 4x(x2), 4x(x1), 4x(x0)) upon return.
+// @param sixteen_minus_x vector of 8 bit components, containing
+// (4x(16 - x3), 4x(16 - x2), 4x(16 - x1), 4x(16 - x0))
+inline void PrepareConstantsTwoPixelPairs(const uint32_t* xy,
+ const __m128i& mask_3FFF,
+ const __m128i& mask_000F,
+ const __m128i& sixteen_8bit,
+ const __m128i& mask_dist_select,
+ __m128i* all_x_result,
+ __m128i* sixteen_minus_x,
+ int* x0,
+ int* x1) {
+ const __m128i xx = _mm_loadu_si128(reinterpret_cast<const __m128i *>(xy));
+
+ // 4 delta X
+ // (x03, x02, x01, x00)
+ const __m128i x0_wide = _mm_srli_epi32(xx, 18);
+ // (x13, x12, x11, x10)
+ const __m128i x1_wide = _mm_and_si128(xx, mask_3FFF);
+
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(x0), x0_wide);
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(x1), x1_wide);
+
+ __m128i all_x = _mm_and_si128(_mm_srli_epi32(xx, 14), mask_000F);
+
+ // (4x(x3), 4x(x2), 4x(x1), 4x(x0))
+ all_x = _mm_shuffle_epi8(all_x, mask_dist_select);
+
+ *all_x_result = all_x;
+ // (4x(16-x3), 4x(16-x2), 4x(16-x1), 4x(16-x0))
+ *sixteen_minus_x = _mm_sub_epi8(sixteen_8bit, all_x);
+}
+
+// Prepare all necessary constants for a round of processing for two pixel
+// pairs.
+// @param xy is the location where the xy parameters for four pixels should be
+// read from. It is identical in concept with argument two of
+// S32_{opaque}_D32_filter_DXDY methods.
+// @param mask_3FFF vector of 32 bit constants containing 3FFF,
+// suitable to mask the bottom 14 bits of a XY value.
+// @param mask_000F vector of 32 bit constants containing 000F,
+// suitable to mask the bottom 4 bits of a XY value.
+// @param sixteen_8bit vector of 8 bit components containing the value 16.
+// @param mask_dist_select vector of 8 bit components containing the shuffling
+// parameters to reorder x[0-3] parameters.
+// @param all_xy_result vector of 8 bit components that will contain the
+// (4x(y1), 4x(y0), 4x(x1), 4x(x0)) upon return.
+// @param sixteen_minus_x vector of 8 bit components, containing
+// (4x(16-y1), 4x(16-y0), 4x(16-x1), 4x(16-x0)).
+inline void PrepareConstantsTwoPixelPairsDXDY(const uint32_t* xy,
+ const __m128i& mask_3FFF,
+ const __m128i& mask_000F,
+ const __m128i& sixteen_8bit,
+ const __m128i& mask_dist_select,
+ __m128i* all_xy_result,
+ __m128i* sixteen_minus_xy,
+ int* xy0, int* xy1) {
+ const __m128i xy_wide =
+ _mm_loadu_si128(reinterpret_cast<const __m128i *>(xy));
+
+ // (x10, y10, x00, y00)
+ __m128i xy0_wide = _mm_srli_epi32(xy_wide, 18);
+ // (y10, y00, x10, x00)
+ xy0_wide = _mm_shuffle_epi32(xy0_wide, _MM_SHUFFLE(2, 0, 3, 1));
+ // (x11, y11, x01, y01)
+ __m128i xy1_wide = _mm_and_si128(xy_wide, mask_3FFF);
+ // (y11, y01, x11, x01)
+ xy1_wide = _mm_shuffle_epi32(xy1_wide, _MM_SHUFFLE(2, 0, 3, 1));
+
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(xy0), xy0_wide);
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(xy1), xy1_wide);
+
+ // (x1, y1, x0, y0)
+ __m128i all_xy = _mm_and_si128(_mm_srli_epi32(xy_wide, 14), mask_000F);
+ // (y1, y0, x1, x0)
+ all_xy = _mm_shuffle_epi32(all_xy, _MM_SHUFFLE(2, 0, 3, 1));
+ // (4x(y1), 4x(y0), 4x(x1), 4x(x0))
+ all_xy = _mm_shuffle_epi8(all_xy, mask_dist_select);
+
+ *all_xy_result = all_xy;
+ // (4x(16-y1), 4x(16-y0), 4x(16-x1), 4x(16-x0))
+ *sixteen_minus_xy = _mm_sub_epi8(sixteen_8bit, all_xy);
+}
+
+// Helper function used when processing one pixel pair.
+// @param pixel0..3 are the four input pixels
+// @param scale_x vector of 8 bit components to multiply the pixel[0:3]. This
+// will contain (4x(x1, 16-x1), 4x(x0, 16-x0))
+// or (4x(x3, 16-x3), 4x(x2, 16-x2))
+// @return a vector of 16 bit components containing:
+// (Aa2 * (16 - x1) + Aa3 * x1, ... , Ra0 * (16 - x0) + Ra1 * x0)
+inline __m128i ProcessPixelPairHelper(uint32_t pixel0,
+ uint32_t pixel1,
+ uint32_t pixel2,
+ uint32_t pixel3,
+ const __m128i& scale_x) {
+ __m128i a0, a1, a2, a3;
+ // Load 2 pairs of pixels
+ a0 = _mm_cvtsi32_si128(pixel0);
+ a1 = _mm_cvtsi32_si128(pixel1);
+
+ // Interleave pixels.
+ // (0, 0, 0, 0, 0, 0, 0, 0, Aa1, Aa0, Ba1, Ba0, Ga1, Ga0, Ra1, Ra0)
+ a0 = _mm_unpacklo_epi8(a0, a1);
+
+ a2 = _mm_cvtsi32_si128(pixel2);
+ a3 = _mm_cvtsi32_si128(pixel3);
+ // (0, 0, 0, 0, 0, 0, 0, 0, Aa3, Aa2, Ba3, Ba2, Ga3, Ga2, Ra3, Ra2)
+ a2 = _mm_unpacklo_epi8(a2, a3);
+
+ // two pairs of pixel pairs, interleaved.
+ // (Aa3, Aa2, Ba3, Ba2, Ga3, Ga2, Ra3, Ra2,
+ // Aa1, Aa0, Ba1, Ba0, Ga1, Ga0, Ra1, Ra0)
+ a0 = _mm_unpacklo_epi64(a0, a2);
+
+ // multiply and sum to 16 bit components.
+ // (Aa2 * (16 - x1) + Aa3 * x1, ... , Ra0 * (16 - x0) + Ra1 * x0)
+ // At that point, we use up a bit less than 12 bits for each 16 bit
+ // component:
+ // All components are less than 255. So,
+ // C0 * (16 - x) + C1 * x <= 255 * (16 - x) + 255 * x = 255 * 16.
+ return _mm_maddubs_epi16(a0, scale_x);
+}
+
+// Scale back the results after multiplications to the [0:255] range, and scale
+// by alpha when has_alpha is true.
+// Depending on whether one set or two sets of multiplications had been applied,
+// the results have to be shifted by four places (dividing by 16), or shifted
+// by eight places (dividing by 256), since each multiplication is by a quantity
+// in the range [0:16].
+template<bool has_alpha, int scale>
+inline __m128i ScaleFourPixels(__m128i* pixels,
+ const __m128i& alpha) {
+ // Divide each 16 bit component by 16 (or 256 depending on scale).
+ *pixels = _mm_srli_epi16(*pixels, scale);
+
+ if (has_alpha) {
+ // Multiply by alpha.
+ *pixels = _mm_mullo_epi16(*pixels, alpha);
+
+ // Divide each 16 bit component by 256.
+ *pixels = _mm_srli_epi16(*pixels, 8);
+ }
+ return *pixels;
+}
+
+// Wrapper to calculate two output pixels from four input pixels. The
+// arguments are the same as ProcessPixelPairHelper. Technically, there are
+// eight input pixels, but since sub_y == 0, the factors applied to half of the
+// pixels is zero (sub_y), and are therefore omitted here to save on some
+// processing.
+// @param alpha when has_alpha is true, scale all resulting components by this
+// value.
+// @return a vector of 16 bit components containing:
+// ((Aa2 * (16 - x1) + Aa3 * x1) * alpha, ...,
+// (Ra0 * (16 - x0) + Ra1 * x0) * alpha) (when has_alpha is true)
+// otherwise
+// (Aa2 * (16 - x1) + Aa3 * x1, ... , Ra0 * (16 - x0) + Ra1 * x0)
+// In both cases, the results are renormalized (divided by 16) to match the
+// expected formats when storing back the results into memory.
+template<bool has_alpha>
+inline __m128i ProcessPixelPairZeroSubY(uint32_t pixel0,
+ uint32_t pixel1,
+ uint32_t pixel2,
+ uint32_t pixel3,
+ const __m128i& scale_x,
+ const __m128i& alpha) {
+ __m128i sum = ProcessPixelPairHelper(pixel0, pixel1, pixel2, pixel3,
+ scale_x);
+ return ScaleFourPixels<has_alpha, 4>(&sum, alpha);
+}
+
+// Same as ProcessPixelPairZeroSubY, expect processing one output pixel at a
+// time instead of two. As in the above function, only two pixels are needed
+// to generate a single pixel since sub_y == 0.
+// @return same as ProcessPixelPairZeroSubY, except that only the bottom 4
+// 16 bit components are set.
+template<bool has_alpha>
+inline __m128i ProcessOnePixelZeroSubY(uint32_t pixel0,
+ uint32_t pixel1,
+ __m128i scale_x,
+ __m128i alpha) {
+ __m128i a0 = _mm_cvtsi32_si128(pixel0);
+ __m128i a1 = _mm_cvtsi32_si128(pixel1);
+
+ // Interleave
+ a0 = _mm_unpacklo_epi8(a0, a1);
+
+ // (a0 * (16-x) + a1 * x)
+ __m128i sum = _mm_maddubs_epi16(a0, scale_x);
+
+ return ScaleFourPixels<has_alpha, 4>(&sum, alpha);
+}
+
+// Methods when sub_y != 0
+
+
+// Same as ProcessPixelPairHelper, except that the values are scaled by y.
+// @param y vector of 16 bit components containing 'y' values. There are two
+// cases in practice, where y will contain the sub_y constant, or will
+// contain the 16 - sub_y constant.
+// @return vector of 16 bit components containing:
+// (y * (Aa2 * (16 - x1) + Aa3 * x1), ... , y * (Ra0 * (16 - x0) + Ra1 * x0))
+inline __m128i ProcessPixelPair(uint32_t pixel0,
+ uint32_t pixel1,
+ uint32_t pixel2,
+ uint32_t pixel3,
+ const __m128i& scale_x,
+ const __m128i& y) {
+ __m128i sum = ProcessPixelPairHelper(pixel0, pixel1, pixel2, pixel3,
+ scale_x);
+
+ // first row times 16-y or y depending on whether 'y' represents one or
+ // the other.
+ // Values will be up to 255 * 16 * 16 = 65280.
+ // (y * (Aa2 * (16 - x1) + Aa3 * x1), ... ,
+ // y * (Ra0 * (16 - x0) + Ra1 * x0))
+ sum = _mm_mullo_epi16(sum, y);
+
+ return sum;
+}
+
+// Process two pixel pairs out of eight input pixels.
+// In other methods, the distinct pixels are passed one by one, but in this
+// case, the rows, and index offsets to the pixels into the row are passed
+// to generate the 8 pixels.
+// @param row0..1 top and bottom row where to find input pixels.
+// @param x0..1 offsets into the row for all eight input pixels.
+// @param all_y vector of 16 bit components containing the constant sub_y
+// @param neg_y vector of 16 bit components containing the constant 16 - sub_y
+// @param alpha vector of 16 bit components containing the alpha value to scale
+// the results by, when has_alpha is true.
+// @return
+// (alpha * ((16-y) * (Aa2 * (16-x1) + Aa3 * x1) +
+// y * (Aa2' * (16-x1) + Aa3' * x1)),
+// ...
+// alpha * ((16-y) * (Ra0 * (16-x0) + Ra1 * x0) +
+// y * (Ra0' * (16-x0) + Ra1' * x0))
+// With the factor alpha removed when has_alpha is false.
+// The values are scaled back to 16 bit components, but with only the bottom
+// 8 bits being set.
+template<bool has_alpha>
+inline __m128i ProcessTwoPixelPairs(const uint32_t* row0,
+ const uint32_t* row1,
+ const int* x0,
+ const int* x1,
+ const __m128i& scale_x,
+ const __m128i& all_y,
+ const __m128i& neg_y,
+ const __m128i& alpha) {
+ __m128i sum0 = ProcessPixelPair(
+ row0[x0[0]], row0[x1[0]], row0[x0[1]], row0[x1[1]],
+ scale_x, neg_y);
+ __m128i sum1 = ProcessPixelPair(
+ row1[x0[0]], row1[x1[0]], row1[x0[1]], row1[x1[1]],
+ scale_x, all_y);
+
+ // 2 samples fully summed.
+ // ((16-y) * (Aa2 * (16-x1) + Aa3 * x1) +
+ // y * (Aa2' * (16-x1) + Aa3' * x1),
+ // ...
+ // (16-y) * (Ra0 * (16 - x0) + Ra1 * x0)) +
+ // y * (Ra0' * (16-x0) + Ra1' * x0))
+ // Each component, again can be at most 256 * 255 = 65280, so no overflow.
+ sum0 = _mm_add_epi16(sum0, sum1);
+
+ return ScaleFourPixels<has_alpha, 8>(&sum0, alpha);
+}
+
+// Similar to ProcessTwoPixelPairs except the pixel indexes.
+template<bool has_alpha>
+inline __m128i ProcessTwoPixelPairsDXDY(const uint32_t* row00,
+ const uint32_t* row01,
+ const uint32_t* row10,
+ const uint32_t* row11,
+ const int* xy0,
+ const int* xy1,
+ const __m128i& scale_x,
+ const __m128i& all_y,
+ const __m128i& neg_y,
+ const __m128i& alpha) {
+ // first row
+ __m128i sum0 = ProcessPixelPair(
+ row00[xy0[0]], row00[xy1[0]], row10[xy0[1]], row10[xy1[1]],
+ scale_x, neg_y);
+ // second row
+ __m128i sum1 = ProcessPixelPair(
+ row01[xy0[0]], row01[xy1[0]], row11[xy0[1]], row11[xy1[1]],
+ scale_x, all_y);
+
+ // 2 samples fully summed.
+ // ((16-y1) * (Aa2 * (16-x1) + Aa3 * x1) +
+ // y0 * (Aa2' * (16-x1) + Aa3' * x1),
+ // ...
+ // (16-y0) * (Ra0 * (16 - x0) + Ra1 * x0)) +
+ // y0 * (Ra0' * (16-x0) + Ra1' * x0))
+ // Each component, again can be at most 256 * 255 = 65280, so no overflow.
+ sum0 = _mm_add_epi16(sum0, sum1);
+
+ return ScaleFourPixels<has_alpha, 8>(&sum0, alpha);
+}
+
+
+// Same as ProcessPixelPair, except that performing the math one output pixel
+// at a time. This means that only the bottom four 16 bit components are set.
+inline __m128i ProcessOnePixel(uint32_t pixel0, uint32_t pixel1,
+ const __m128i& scale_x, const __m128i& y) {
+ __m128i a0 = _mm_cvtsi32_si128(pixel0);
+ __m128i a1 = _mm_cvtsi32_si128(pixel1);
+
+ // Interleave
+ // (0, 0, 0, 0, 0, 0, 0, 0, Aa1, Aa0, Ba1, Ba0, Ga1, Ga0, Ra1, Ra0)
+ a0 = _mm_unpacklo_epi8(a0, a1);
+
+ // (a0 * (16-x) + a1 * x)
+ a0 = _mm_maddubs_epi16(a0, scale_x);
+
+ // scale row by y
+ return _mm_mullo_epi16(a0, y);
+}
+
+// Notes about the various tricks that are used in this implementation:
+// - specialization for sub_y == 0.
+// Statistically, 1/16th of the samples will have sub_y == 0. When this
+// happens, the math goes from:
+// (16 - x)*(16 - y)*a00 + x*(16 - y)*a01 + (16 - x)*y*a10 + x*y*a11
+// to:
+// (16 - x)*a00 + 16*x*a01
+// much simpler. The simplification makes for an easy boost in performance.
+// - calculating 4 output pixels at a time.
+// This allows loading the coefficients x0 and x1 and shuffling them to the
+// optimum location only once per loop, instead of twice per loop.
+// This also allows us to store the four pixels with a single store.
+// - Use of 2 special SSSE3 instructions (comparatively to the SSE2 instruction
+// version):
+// _mm_shuffle_epi8 : this allows us to spread the coefficients x[0-3] loaded
+// in 32 bit values to 8 bit values repeated four times.
+// _mm_maddubs_epi16 : this allows us to perform multiplications and additions
+// in one swoop of 8bit values storing the results in 16 bit values. This
+// instruction is actually crucial for the speed of the implementation since
+// as one can see in the SSE2 implementation, all inputs have to be used as
+// 16 bits because the results are 16 bits. This basically allows us to process
+// twice as many pixel components per iteration.
+//
+// As a result, this method behaves faster than the traditional SSE2. The actual
+// boost varies greatly on the underlying architecture.
+template<bool has_alpha>
+void S32_generic_D32_filter_DX_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkASSERT(s.fBitmap->config() == SkBitmap::kARGB_8888_Config);
+ if (has_alpha) {
+ SkASSERT(s.fAlphaScale < 256);
+ } else {
+ SkASSERT(s.fAlphaScale == 256);
+ }
+
+ const uint8_t* src_addr =
+ static_cast<const uint8_t*>(s.fBitmap->getPixels());
+ const size_t rb = s.fBitmap->rowBytes();
+ const uint32_t XY = *xy++;
+ const unsigned y0 = XY >> 14;
+ const uint32_t* row0 =
+ reinterpret_cast<const uint32_t*>(src_addr + (y0 >> 4) * rb);
+ const uint32_t* row1 =
+ reinterpret_cast<const uint32_t*>(src_addr + (XY & 0x3FFF) * rb);
+ const unsigned sub_y = y0 & 0xF;
+
+ // vector constants
+ const __m128i mask_dist_select = _mm_set_epi8(12, 12, 12, 12,
+ 8, 8, 8, 8,
+ 4, 4, 4, 4,
+ 0, 0, 0, 0);
+ const __m128i mask_3FFF = _mm_set1_epi32(0x3FFF);
+ const __m128i mask_000F = _mm_set1_epi32(0x000F);
+ const __m128i sixteen_8bit = _mm_set1_epi8(16);
+ // (0, 0, 0, 0, 0, 0, 0, 0)
+ const __m128i zero = _mm_setzero_si128();
+
+ __m128i alpha = _mm_setzero_si128();
+ if (has_alpha)
+ // 8x(alpha)
+ alpha = _mm_set1_epi16(s.fAlphaScale);
+
+ if (sub_y == 0) {
+ // Unroll 4x, interleave bytes, use pmaddubsw (all_x is small)
+ while (count > 3) {
+ count -= 4;
+
+ int x0[4];
+ int x1[4];
+ __m128i all_x, sixteen_minus_x;
+ PrepareConstantsTwoPixelPairs(xy, mask_3FFF, mask_000F,
+ sixteen_8bit, mask_dist_select,
+ &all_x, &sixteen_minus_x, x0, x1);
+ xy += 4;
+
+ // First pair of pixel pairs.
+ // (4x(x1, 16-x1), 4x(x0, 16-x0))
+ __m128i scale_x;
+ scale_x = _mm_unpacklo_epi8(sixteen_minus_x, all_x);
+
+ __m128i sum0 = ProcessPixelPairZeroSubY<has_alpha>(
+ row0[x0[0]], row0[x1[0]], row0[x0[1]], row0[x1[1]],
+ scale_x, alpha);
+
+ // second pair of pixel pairs
+ // (4x (x3, 16-x3), 4x (16-x2, x2))
+ scale_x = _mm_unpackhi_epi8(sixteen_minus_x, all_x);
+
+ __m128i sum1 = ProcessPixelPairZeroSubY<has_alpha>(
+ row0[x0[2]], row0[x1[2]], row0[x0[3]], row0[x1[3]],
+ scale_x, alpha);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum0 = _mm_packus_epi16(sum0, sum1);
+
+ // Extract low int and store.
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(colors), sum0);
+
+ colors += 4;
+ }
+
+ // handle remainder
+ while (count-- > 0) {
+ uint32_t xx = *xy++; // x0:14 | 4 | x1:14
+ unsigned x0 = xx >> 18;
+ unsigned x1 = xx & 0x3FFF;
+
+ // 16x(x)
+ const __m128i all_x = _mm_set1_epi8((xx >> 14) & 0x0F);
+
+ // (16x(16-x))
+ __m128i scale_x = _mm_sub_epi8(sixteen_8bit, all_x);
+
+ scale_x = _mm_unpacklo_epi8(scale_x, all_x);
+
+ __m128i sum = ProcessOnePixelZeroSubY<has_alpha>(
+ row0[x0], row0[x1],
+ scale_x, alpha);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum = _mm_packus_epi16(sum, zero);
+
+ // Extract low int and store.
+ *colors++ = _mm_cvtsi128_si32(sum);
+ }
+ } else { // more general case, y != 0
+ // 8x(16)
+ const __m128i sixteen_16bit = _mm_set1_epi16(16);
+
+ // 8x (y)
+ const __m128i all_y = _mm_set1_epi16(sub_y);
+
+ // 8x (16-y)
+ const __m128i neg_y = _mm_sub_epi16(sixteen_16bit, all_y);
+
+ // Unroll 4x, interleave bytes, use pmaddubsw (all_x is small)
+ while (count > 3) {
+ count -= 4;
+
+ int x0[4];
+ int x1[4];
+ __m128i all_x, sixteen_minus_x;
+ PrepareConstantsTwoPixelPairs(xy, mask_3FFF, mask_000F,
+ sixteen_8bit, mask_dist_select,
+ &all_x, &sixteen_minus_x, x0, x1);
+ xy += 4;
+
+ // First pair of pixel pairs
+ // (4x(x1, 16-x1), 4x(x0, 16-x0))
+ __m128i scale_x;
+ scale_x = _mm_unpacklo_epi8(sixteen_minus_x, all_x);
+
+ __m128i sum0 = ProcessTwoPixelPairs<has_alpha>(
+ row0, row1, x0, x1,
+ scale_x, all_y, neg_y, alpha);
+
+ // second pair of pixel pairs
+ // (4x (x3, 16-x3), 4x (16-x2, x2))
+ scale_x = _mm_unpackhi_epi8(sixteen_minus_x, all_x);
+
+ __m128i sum1 = ProcessTwoPixelPairs<has_alpha>(
+ row0, row1, x0 + 2, x1 + 2,
+ scale_x, all_y, neg_y, alpha);
+
+ // Do the final packing of the two results
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum0 = _mm_packus_epi16(sum0, sum1);
+
+ // Extract low int and store.
+ _mm_storeu_si128(reinterpret_cast<__m128i *>(colors), sum0);
+
+ colors += 4;
+ }
+
+ // Left over.
+ while (count-- > 0) {
+ const uint32_t xx = *xy++; // x0:14 | 4 | x1:14
+ const unsigned x0 = xx >> 18;
+ const unsigned x1 = xx & 0x3FFF;
+
+ // 16x(x)
+ const __m128i all_x = _mm_set1_epi8((xx >> 14) & 0x0F);
+
+ // 16x (16-x)
+ __m128i scale_x = _mm_sub_epi8(sixteen_8bit, all_x);
+
+ // (8x (x, 16-x))
+ scale_x = _mm_unpacklo_epi8(scale_x, all_x);
+
+ // first row.
+ __m128i sum0 = ProcessOnePixel(row0[x0], row0[x1], scale_x, neg_y);
+ // second row.
+ __m128i sum1 = ProcessOnePixel(row1[x0], row1[x1], scale_x, all_y);
+
+ // Add both rows for full sample
+ sum0 = _mm_add_epi16(sum0, sum1);
+
+ sum0 = ScaleFourPixels<has_alpha, 8>(&sum0, alpha);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum0 = _mm_packus_epi16(sum0, zero);
+
+ // Extract low int and store.
+ *colors++ = _mm_cvtsi128_si32(sum0);
+ }
+ }
+}
+
+/*
+ * Similar to S32_generic_D32_filter_DX_SSSE3, we do not need to handle the
+ * special case suby == 0 as suby is changing in every loop.
+ */
+template<bool has_alpha>
+void S32_generic_D32_filter_DXDY_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fFilterLevel != SkPaint::kNone_FilterLevel);
+ SkASSERT(s.fBitmap->config() == SkBitmap::kARGB_8888_Config);
+ if (has_alpha) {
+ SkASSERT(s.fAlphaScale < 256);
+ } else {
+ SkASSERT(s.fAlphaScale == 256);
+ }
+
+ const uint8_t* src_addr =
+ static_cast<const uint8_t*>(s.fBitmap->getPixels());
+ const size_t rb = s.fBitmap->rowBytes();
+
+ // vector constants
+ const __m128i mask_dist_select = _mm_set_epi8(12, 12, 12, 12,
+ 8, 8, 8, 8,
+ 4, 4, 4, 4,
+ 0, 0, 0, 0);
+ const __m128i mask_3FFF = _mm_set1_epi32(0x3FFF);
+ const __m128i mask_000F = _mm_set1_epi32(0x000F);
+ const __m128i sixteen_8bit = _mm_set1_epi8(16);
+
+ __m128i alpha;
+ if (has_alpha) {
+ // 8x(alpha)
+ alpha = _mm_set1_epi16(s.fAlphaScale);
+ }
+
+ // Unroll 2x, interleave bytes, use pmaddubsw (all_x is small)
+ while (count >= 2) {
+ int xy0[4];
+ int xy1[4];
+ __m128i all_xy, sixteen_minus_xy;
+ PrepareConstantsTwoPixelPairsDXDY(xy, mask_3FFF, mask_000F,
+ sixteen_8bit, mask_dist_select,
+ &all_xy, &sixteen_minus_xy, xy0, xy1);
+
+ // (4x(x1, 16-x1), 4x(x0, 16-x0))
+ __m128i scale_x = _mm_unpacklo_epi8(sixteen_minus_xy, all_xy);
+ // (4x(0, y1), 4x(0, y0))
+ __m128i all_y = _mm_unpackhi_epi8(all_xy, _mm_setzero_si128());
+ __m128i neg_y = _mm_sub_epi16(_mm_set1_epi16(16), all_y);
+
+ const uint32_t* row00 =
+ reinterpret_cast<const uint32_t*>(src_addr + xy0[2] * rb);
+ const uint32_t* row01 =
+ reinterpret_cast<const uint32_t*>(src_addr + xy1[2] * rb);
+ const uint32_t* row10 =
+ reinterpret_cast<const uint32_t*>(src_addr + xy0[3] * rb);
+ const uint32_t* row11 =
+ reinterpret_cast<const uint32_t*>(src_addr + xy1[3] * rb);
+
+ __m128i sum0 = ProcessTwoPixelPairsDXDY<has_alpha>(
+ row00, row01, row10, row11, xy0, xy1,
+ scale_x, all_y, neg_y, alpha);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum0 = _mm_packus_epi16(sum0, _mm_setzero_si128());
+
+ // Extract low int and store.
+ _mm_storel_epi64(reinterpret_cast<__m128i *>(colors), sum0);
+
+ xy += 4;
+ colors += 2;
+ count -= 2;
+ }
+
+ // Handle the remainder
+ while (count-- > 0) {
+ uint32_t data = *xy++;
+ unsigned y0 = data >> 14;
+ unsigned y1 = data & 0x3FFF;
+ unsigned subY = y0 & 0xF;
+ y0 >>= 4;
+
+ data = *xy++;
+ unsigned x0 = data >> 14;
+ unsigned x1 = data & 0x3FFF;
+ unsigned subX = x0 & 0xF;
+ x0 >>= 4;
+
+ const uint32_t* row0 =
+ reinterpret_cast<const uint32_t*>(src_addr + y0 * rb);
+ const uint32_t* row1 =
+ reinterpret_cast<const uint32_t*>(src_addr + y1 * rb);
+
+ // 16x(x)
+ const __m128i all_x = _mm_set1_epi8(subX);
+
+ // 16x (16-x)
+ __m128i scale_x = _mm_sub_epi8(sixteen_8bit, all_x);
+
+ // (8x (x, 16-x))
+ scale_x = _mm_unpacklo_epi8(scale_x, all_x);
+
+ // 8x(16)
+ const __m128i sixteen_16bit = _mm_set1_epi16(16);
+
+ // 8x (y)
+ const __m128i all_y = _mm_set1_epi16(subY);
+
+ // 8x (16-y)
+ const __m128i neg_y = _mm_sub_epi16(sixteen_16bit, all_y);
+
+ // first row.
+ __m128i sum0 = ProcessOnePixel(row0[x0], row0[x1], scale_x, neg_y);
+ // second row.
+ __m128i sum1 = ProcessOnePixel(row1[x0], row1[x1], scale_x, all_y);
+
+ // Add both rows for full sample
+ sum0 = _mm_add_epi16(sum0, sum1);
+
+ sum0 = ScaleFourPixels<has_alpha, 8>(&sum0, alpha);
+
+ // Pack lower 4 16 bit values of sum into lower 4 bytes.
+ sum0 = _mm_packus_epi16(sum0, _mm_setzero_si128());
+
+ // Extract low int and store.
+ *colors++ = _mm_cvtsi128_si32(sum0);
+ }
+}
+} // namepace
+
+void S32_opaque_D32_filter_DX_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ S32_generic_D32_filter_DX_SSSE3<false>(s, xy, count, colors);
+}
+
+void S32_alpha_D32_filter_DX_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ S32_generic_D32_filter_DX_SSSE3<true>(s, xy, count, colors);
+}
+
+void S32_opaque_D32_filter_DXDY_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ S32_generic_D32_filter_DXDY_SSSE3<false>(s, xy, count, colors);
+}
+
+void S32_alpha_D32_filter_DXDY_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors) {
+ S32_generic_D32_filter_DXDY_SSSE3<true>(s, xy, count, colors);
+}
diff --git a/opts/SkBitmapProcState_opts_SSSE3.h b/opts/SkBitmapProcState_opts_SSSE3.h
new file mode 100644
index 00000000..176f2bfb
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_SSSE3.h
@@ -0,0 +1,21 @@
+/*
+ * 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 "SkBitmapProcState.h"
+
+void S32_opaque_D32_filter_DX_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
+void S32_alpha_D32_filter_DX_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
+void S32_opaque_D32_filter_DXDY_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
+void S32_alpha_D32_filter_DXDY_SSSE3(const SkBitmapProcState& s,
+ const uint32_t* xy,
+ int count, uint32_t* colors);
diff --git a/opts/SkBitmapProcState_opts_arm.cpp b/opts/SkBitmapProcState_opts_arm.cpp
new file mode 100644
index 00000000..a9b44daa
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_arm.cpp
@@ -0,0 +1,422 @@
+/*
+ * Copyright 2009 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 "SkBitmapProcState.h"
+#include "SkColorPriv.h"
+#include "SkPaint.h"
+#include "SkTypes.h"
+#include "SkUtils.h"
+#include "SkUtilsArm.h"
+
+#include "SkConvolver.h"
+
+#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN)
+void SI8_D16_nofilter_DX_arm(
+ const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count,
+ uint16_t* SK_RESTRICT colors) SK_ATTRIBUTE_OPTIMIZE_O1;
+
+void SI8_D16_nofilter_DX_arm(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, uint16_t* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask));
+ SkASSERT(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+
+ const uint16_t* SK_RESTRICT table = s.fBitmap->getColorTable()->lock16BitCache();
+ const uint8_t* SK_RESTRICT srcAddr = (const uint8_t*)s.fBitmap->getPixels();
+
+ // buffer is y32, x16, x16, x16, x16, x16
+ // bump srcAddr to the proper row, since we're told Y never changes
+ SkASSERT((unsigned)xy[0] < (unsigned)s.fBitmap->height());
+ srcAddr = (const uint8_t*)((const char*)srcAddr +
+ xy[0] * s.fBitmap->rowBytes());
+
+ uint8_t src;
+
+ if (1 == s.fBitmap->width()) {
+ src = srcAddr[0];
+ uint16_t dstValue = table[src];
+ sk_memset16(colors, dstValue, count);
+ } else {
+ int i;
+ int count8 = count >> 3;
+ const uint16_t* SK_RESTRICT xx = (const uint16_t*)(xy + 1);
+
+ asm volatile (
+ "cmp %[count8], #0 \n\t" // compare loop counter with 0
+ "beq 2f \n\t" // if loop counter == 0, exit
+ "1: \n\t"
+ "ldmia %[xx]!, {r5, r7, r9, r11} \n\t" // load ptrs to pixels 0-7
+ "subs %[count8], %[count8], #1 \n\t" // decrement loop counter
+ "uxth r4, r5 \n\t" // extract ptr 0
+ "mov r5, r5, lsr #16 \n\t" // extract ptr 1
+ "uxth r6, r7 \n\t" // extract ptr 2
+ "mov r7, r7, lsr #16 \n\t" // extract ptr 3
+ "ldrb r4, [%[srcAddr], r4] \n\t" // load pixel 0 from image
+ "uxth r8, r9 \n\t" // extract ptr 4
+ "ldrb r5, [%[srcAddr], r5] \n\t" // load pixel 1 from image
+ "mov r9, r9, lsr #16 \n\t" // extract ptr 5
+ "ldrb r6, [%[srcAddr], r6] \n\t" // load pixel 2 from image
+ "uxth r10, r11 \n\t" // extract ptr 6
+ "ldrb r7, [%[srcAddr], r7] \n\t" // load pixel 3 from image
+ "mov r11, r11, lsr #16 \n\t" // extract ptr 7
+ "ldrb r8, [%[srcAddr], r8] \n\t" // load pixel 4 from image
+ "add r4, r4, r4 \n\t" // double pixel 0 for RGB565 lookup
+ "ldrb r9, [%[srcAddr], r9] \n\t" // load pixel 5 from image
+ "add r5, r5, r5 \n\t" // double pixel 1 for RGB565 lookup
+ "ldrb r10, [%[srcAddr], r10] \n\t" // load pixel 6 from image
+ "add r6, r6, r6 \n\t" // double pixel 2 for RGB565 lookup
+ "ldrb r11, [%[srcAddr], r11] \n\t" // load pixel 7 from image
+ "add r7, r7, r7 \n\t" // double pixel 3 for RGB565 lookup
+ "ldrh r4, [%[table], r4] \n\t" // load pixel 0 RGB565 from colmap
+ "add r8, r8, r8 \n\t" // double pixel 4 for RGB565 lookup
+ "ldrh r5, [%[table], r5] \n\t" // load pixel 1 RGB565 from colmap
+ "add r9, r9, r9 \n\t" // double pixel 5 for RGB565 lookup
+ "ldrh r6, [%[table], r6] \n\t" // load pixel 2 RGB565 from colmap
+ "add r10, r10, r10 \n\t" // double pixel 6 for RGB565 lookup
+ "ldrh r7, [%[table], r7] \n\t" // load pixel 3 RGB565 from colmap
+ "add r11, r11, r11 \n\t" // double pixel 7 for RGB565 lookup
+ "ldrh r8, [%[table], r8] \n\t" // load pixel 4 RGB565 from colmap
+ "ldrh r9, [%[table], r9] \n\t" // load pixel 5 RGB565 from colmap
+ "ldrh r10, [%[table], r10] \n\t" // load pixel 6 RGB565 from colmap
+ "ldrh r11, [%[table], r11] \n\t" // load pixel 7 RGB565 from colmap
+ "pkhbt r5, r4, r5, lsl #16 \n\t" // pack pixels 0 and 1
+ "pkhbt r6, r6, r7, lsl #16 \n\t" // pack pixels 2 and 3
+ "pkhbt r8, r8, r9, lsl #16 \n\t" // pack pixels 4 and 5
+ "pkhbt r10, r10, r11, lsl #16 \n\t" // pack pixels 6 and 7
+ "stmia %[colors]!, {r5, r6, r8, r10} \n\t" // store last 8 pixels
+ "bgt 1b \n\t" // loop if counter > 0
+ "2: \n\t"
+ : [xx] "+r" (xx), [count8] "+r" (count8), [colors] "+r" (colors)
+ : [table] "r" (table), [srcAddr] "r" (srcAddr)
+ : "memory", "cc", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"
+ );
+
+ for (i = (count & 7); i > 0; --i) {
+ src = srcAddr[*xx++]; *colors++ = table[src];
+ }
+ }
+
+ s.fBitmap->getColorTable()->unlock16BitCache();
+}
+
+void SI8_opaque_D32_nofilter_DX_arm(
+ const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count,
+ SkPMColor* SK_RESTRICT colors) SK_ATTRIBUTE_OPTIMIZE_O1;
+
+void SI8_opaque_D32_nofilter_DX_arm(const SkBitmapProcState& s,
+ const uint32_t* SK_RESTRICT xy,
+ int count, SkPMColor* SK_RESTRICT colors) {
+ SkASSERT(count > 0 && colors != NULL);
+ SkASSERT(s.fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask));
+ SkASSERT(SkPaint::kNone_FilterLevel == s.fFilterLevel);
+
+ const SkPMColor* SK_RESTRICT table = s.fBitmap->getColorTable()->lockColors();
+ const uint8_t* SK_RESTRICT srcAddr = (const uint8_t*)s.fBitmap->getPixels();
+
+ // buffer is y32, x16, x16, x16, x16, x16
+ // bump srcAddr to the proper row, since we're told Y never changes
+ SkASSERT((unsigned)xy[0] < (unsigned)s.fBitmap->height());
+ srcAddr = (const uint8_t*)((const char*)srcAddr + xy[0] * s.fBitmap->rowBytes());
+
+ if (1 == s.fBitmap->width()) {
+ uint8_t src = srcAddr[0];
+ SkPMColor dstValue = table[src];
+ sk_memset32(colors, dstValue, count);
+ } else {
+ const uint16_t* xx = (const uint16_t*)(xy + 1);
+
+ asm volatile (
+ "subs %[count], %[count], #8 \n\t" // decrement count by 8, set flags
+ "blt 2f \n\t" // if count < 0, branch to singles
+ "1: \n\t" // eights loop
+ "ldmia %[xx]!, {r5, r7, r9, r11} \n\t" // load ptrs to pixels 0-7
+ "uxth r4, r5 \n\t" // extract ptr 0
+ "mov r5, r5, lsr #16 \n\t" // extract ptr 1
+ "uxth r6, r7 \n\t" // extract ptr 2
+ "mov r7, r7, lsr #16 \n\t" // extract ptr 3
+ "ldrb r4, [%[srcAddr], r4] \n\t" // load pixel 0 from image
+ "uxth r8, r9 \n\t" // extract ptr 4
+ "ldrb r5, [%[srcAddr], r5] \n\t" // load pixel 1 from image
+ "mov r9, r9, lsr #16 \n\t" // extract ptr 5
+ "ldrb r6, [%[srcAddr], r6] \n\t" // load pixel 2 from image
+ "uxth r10, r11 \n\t" // extract ptr 6
+ "ldrb r7, [%[srcAddr], r7] \n\t" // load pixel 3 from image
+ "mov r11, r11, lsr #16 \n\t" // extract ptr 7
+ "ldrb r8, [%[srcAddr], r8] \n\t" // load pixel 4 from image
+ "ldrb r9, [%[srcAddr], r9] \n\t" // load pixel 5 from image
+ "ldrb r10, [%[srcAddr], r10] \n\t" // load pixel 6 from image
+ "ldrb r11, [%[srcAddr], r11] \n\t" // load pixel 7 from image
+ "ldr r4, [%[table], r4, lsl #2] \n\t" // load pixel 0 SkPMColor from colmap
+ "ldr r5, [%[table], r5, lsl #2] \n\t" // load pixel 1 SkPMColor from colmap
+ "ldr r6, [%[table], r6, lsl #2] \n\t" // load pixel 2 SkPMColor from colmap
+ "ldr r7, [%[table], r7, lsl #2] \n\t" // load pixel 3 SkPMColor from colmap
+ "ldr r8, [%[table], r8, lsl #2] \n\t" // load pixel 4 SkPMColor from colmap
+ "ldr r9, [%[table], r9, lsl #2] \n\t" // load pixel 5 SkPMColor from colmap
+ "ldr r10, [%[table], r10, lsl #2] \n\t" // load pixel 6 SkPMColor from colmap
+ "ldr r11, [%[table], r11, lsl #2] \n\t" // load pixel 7 SkPMColor from colmap
+ "subs %[count], %[count], #8 \n\t" // decrement loop counter
+ "stmia %[colors]!, {r4-r11} \n\t" // store 8 pixels
+ "bge 1b \n\t" // loop if counter >= 0
+ "2: \n\t"
+ "adds %[count], %[count], #8 \n\t" // fix up counter, set flags
+ "beq 4f \n\t" // if count == 0, branch to exit
+ "3: \n\t" // singles loop
+ "ldrh r4, [%[xx]], #2 \n\t" // load pixel ptr
+ "subs %[count], %[count], #1 \n\t" // decrement loop counter
+ "ldrb r5, [%[srcAddr], r4] \n\t" // load pixel from image
+ "ldr r6, [%[table], r5, lsl #2] \n\t" // load SkPMColor from colmap
+ "str r6, [%[colors]], #4 \n\t" // store pixel, update ptr
+ "bne 3b \n\t" // loop if counter != 0
+ "4: \n\t" // exit
+ : [xx] "+r" (xx), [count] "+r" (count), [colors] "+r" (colors)
+ : [table] "r" (table), [srcAddr] "r" (srcAddr)
+ : "memory", "cc", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"
+ );
+ }
+
+ s.fBitmap->getColorTable()->unlockColors(false);
+}
+#endif // SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN)
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* If we replace a sampleproc, then we null-out the associated shaderproc,
+ otherwise the shader won't even look at the matrix/sampler
+ */
+void SkBitmapProcState::platformProcs() {
+ bool isOpaque = 256 == fAlphaScale;
+ bool justDx = false;
+
+ if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) {
+ justDx = true;
+ }
+
+ switch (fBitmap->config()) {
+ case SkBitmap::kIndex8_Config:
+#if SK_ARM_ARCH >= 6 && !defined(SK_CPU_BENDIAN)
+ if (justDx && SkPaint::kNone_FilterLevel == fFilterLevel) {
+#if 0 /* crashing on android device */
+ fSampleProc16 = SI8_D16_nofilter_DX_arm;
+ fShaderProc16 = NULL;
+#endif
+ if (isOpaque) {
+ // this one is only very slighty faster than the C version
+ fSampleProc32 = SI8_opaque_D32_nofilter_DX_arm;
+ fShaderProc32 = NULL;
+ }
+ }
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+/////////////////////////////////////
+
+/* FUNCTIONS BELOW ARE SCALAR STUBS INTENDED FOR ARM DEVELOPERS TO REPLACE */
+
+/////////////////////////////////////
+
+
+static inline unsigned char ClampTo8(int a) {
+ if (static_cast<unsigned>(a) < 256) {
+ return a; // Avoid the extra check in the common case.
+ }
+ if (a < 0) {
+ return 0;
+ }
+ return 255;
+}
+
+// Convolves horizontally along a single row. The row data is given in
+// |srcData| and continues for the numValues() of the filter.
+void convolveHorizontally_arm(const unsigned char* srcData,
+ const SkConvolutionFilter1D& filter,
+ unsigned char* outRow,
+ bool hasAlpha) {
+ // Loop over each pixel on this row in the output image.
+ int numValues = filter.numValues();
+ for (int outX = 0; outX < numValues; outX++) {
+ // Get the filter that determines the current output pixel.
+ int filterOffset, filterLength;
+ const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
+ filter.FilterForValue(outX, &filterOffset, &filterLength);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filterLength| pixels (4 bytes each) after this.
+ const unsigned char* rowToFilter = &srcData[filterOffset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int accum[4] = {0};
+ for (int filterX = 0; filterX < filterLength; filterX++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX];
+ accum[0] += curFilter * rowToFilter[filterX * 4 + 0];
+ accum[1] += curFilter * rowToFilter[filterX * 4 + 1];
+ accum[2] += curFilter * rowToFilter[filterX * 4 + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * rowToFilter[filterX * 4 + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[outX * 4 + 0] = ClampTo8(accum[0]);
+ outRow[outX * 4 + 1] = ClampTo8(accum[1]);
+ outRow[outX * 4 + 2] = ClampTo8(accum[2]);
+ if (hasAlpha) {
+ outRow[outX * 4 + 3] = ClampTo8(accum[3]);
+ }
+ }
+}
+
+// Does vertical convolution to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |sourceDataRows| array, with each row
+// being |pixelWidth| wide.
+//
+// The output must have room for |pixelWidth * 4| bytes.
+template<bool hasAlpha>
+ void convolveVertically_arm(const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength,
+ unsigned char* const* sourceDataRows,
+ int pixelWidth,
+ unsigned char* outRow) {
+ // We go through each column in the output and do a vertical convolution,
+ // generating one output pixel each time.
+ for (int outX = 0; outX < pixelWidth; outX++) {
+ // Compute the number of bytes over in each row that the current column
+ // we're convolving starts at. The pixel will cover the next 4 bytes.
+ int byteOffset = outX * 4;
+
+ // Apply the filter to one column of pixels.
+ int accum[4] = {0};
+ for (int filterY = 0; filterY < filterLength; filterY++) {
+ SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY];
+ accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0];
+ accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1];
+ accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2];
+ if (hasAlpha) {
+ accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3];
+ }
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of precision.
+ accum[0] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[1] >>= SkConvolutionFilter1D::kShiftBits;
+ accum[2] >>= SkConvolutionFilter1D::kShiftBits;
+ if (hasAlpha) {
+ accum[3] >>= SkConvolutionFilter1D::kShiftBits;
+ }
+
+ // Store the new pixel.
+ outRow[byteOffset + 0] = ClampTo8(accum[0]);
+ outRow[byteOffset + 1] = ClampTo8(accum[1]);
+ outRow[byteOffset + 2] = ClampTo8(accum[2]);
+ if (hasAlpha) {
+ unsigned char alpha = ClampTo8(accum[3]);
+
+ // Make sure the alpha channel doesn't come out smaller than any of the
+ // color channels. We use premultipled alpha channels, so this should
+ // never happen, but rounding errors will cause this from time to time.
+ // These "impossible" colors will cause overflows (and hence random pixel
+ // values) when the resulting bitmap is drawn to the screen.
+ //
+ // We only need to do this when generating the final output row (here).
+ int maxColorChannel = SkTMax(outRow[byteOffset + 0],
+ SkTMax(outRow[byteOffset + 1],
+ outRow[byteOffset + 2]));
+ if (alpha < maxColorChannel) {
+ outRow[byteOffset + 3] = maxColorChannel;
+ } else {
+ outRow[byteOffset + 3] = alpha;
+ }
+ } else {
+ // No alpha channel, the image is opaque.
+ outRow[byteOffset + 3] = 0xff;
+ }
+ }
+ }
+
+void convolveVertically_arm(const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
+ int filterLength,
+ unsigned char* const* sourceDataRows,
+ int pixelWidth,
+ unsigned char* outRow,
+ bool sourceHasAlpha) {
+ if (sourceHasAlpha) {
+ convolveVertically_arm<true>(filterValues, filterLength,
+ sourceDataRows, pixelWidth,
+ outRow);
+ } else {
+ convolveVertically_arm<false>(filterValues, filterLength,
+ sourceDataRows, pixelWidth,
+ outRow);
+ }
+}
+
+// Convolves horizontally along four rows. The row data is given in
+// |src_data| and continues for the num_values() of the filter.
+// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please
+// refer to that function for detailed comments.
+void convolve4RowsHorizontally_arm(const unsigned char* src_data[4],
+ const SkConvolutionFilter1D& filter,
+ unsigned char* out_row[4]) {
+}
+
+///////////////////////////
+
+/* STOP REWRITING FUNCTIONS HERE, BUT DON'T FORGET TO EDIT THE
+ PLATFORM CONVOLUTION PROCS BELOW */
+
+///////////////////////////
+
+void applySIMDPadding_arm(SkConvolutionFilter1D *filter) {
+ // Padding |paddingCount| of more dummy coefficients after the coefficients
+ // of last filter to prevent SIMD instructions which load 8 or 16 bytes
+ // together to access invalid memory areas. We are not trying to align the
+ // coefficients right now due to the opaqueness of <vector> implementation.
+ // This has to be done after all |AddFilter| calls.
+ for (int i = 0; i < 8; ++i) {
+ filter->addFilterValue(static_cast<SkConvolutionFilter1D::ConvolutionFixed>(0));
+ }
+}
+
+void SkBitmapProcState::platformConvolutionProcs() {
+ if (sk_cpu_arm_has_neon()) {
+ fConvolutionProcs->fExtraHorizontalReads = 3;
+ fConvolutionProcs->fConvolveVertically = &convolveVertically_arm;
+
+ // next line is commented out because the four-row convolution function above is
+ // just a no-op. Please see the comment above its definition, and the SSE implementation
+ // in SkBitmapProcState_opts_SSE2.cpp for guidance on its semantics.
+ // leaving it as NULL will just cause the convolution system to not attempt
+ // to operate on four rows at once, which is correct but not performance-optimal.
+
+ // fConvolutionProcs->fConvolve4RowsHorizontally = &convolve4RowsHorizontally_arm;
+
+ fConvolutionProcs->fConvolve4RowsHorizontally = NULL;
+
+ fConvolutionProcs->fConvolveHorizontally = &convolveHorizontally_arm;
+ fConvolutionProcs->fApplySIMDPadding = &applySIMDPadding_arm;
+ }
+}
diff --git a/opts/SkBitmapProcState_opts_none.cpp b/opts/SkBitmapProcState_opts_none.cpp
new file mode 100644
index 00000000..57af7296
--- /dev/null
+++ b/opts/SkBitmapProcState_opts_none.cpp
@@ -0,0 +1,26 @@
+
+/*
+ * 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 "SkBitmapProcState.h"
+
+/* A platform may optionally overwrite any of these with accelerated
+ versions. On input, these will already have valid function pointers,
+ so a platform need only overwrite the ones it chooses, based on the
+ current state (e.g. fBitmap, fInvMatrix, etc.)
+
+ fShaderProc32
+ fShaderProc16
+ fMatrixProc
+ fSampleProc32
+ fSampleProc32
+ */
+
+// empty implementation just uses default supplied function pointers
+void SkBitmapProcState::platformProcs() {}
+
+// empty implementation just uses default supplied function pointers
+void SkBitmapProcState::platformConvolutionProcs() {}
diff --git a/opts/SkBlitRect_opts_SSE2.cpp b/opts/SkBlitRect_opts_SSE2.cpp
new file mode 100644
index 00000000..3cb2b9c6
--- /dev/null
+++ b/opts/SkBlitRect_opts_SSE2.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 "SkBlitRect_opts_SSE2.h"
+#include "SkBlitRow.h"
+#include "SkColorPriv.h"
+
+#include <emmintrin.h>
+
+/** Simple blitting of opaque rectangles less than 31 pixels wide:
+ inlines and merges sections of Color32_SSE2 and sk_memset32_SSE2.
+*/
+static void BlitRect32_OpaqueNarrow_SSE2(SkPMColor* SK_RESTRICT destination,
+ int width, int height,
+ size_t rowBytes, uint32_t color) {
+ SkASSERT(255 == SkGetPackedA32(color));
+ SkASSERT(width > 0);
+ SkASSERT(width < 31);
+
+ while (--height >= 0) {
+ SkPMColor* dst = destination;
+ int count = width;
+
+ while (count > 4) {
+ *dst++ = color;
+ *dst++ = color;
+ *dst++ = color;
+ *dst++ = color;
+ count -= 4;
+ }
+
+ while (count > 0) {
+ *dst++ = color;
+ --count;
+ }
+
+ destination = (uint32_t*)((char*)destination + rowBytes);
+ }
+}
+
+/**
+ Fast blitting of opaque rectangles at least 31 pixels wide:
+ inlines and merges sections of Color32_SSE2 and sk_memset32_SSE2.
+ A 31 pixel rectangle is guaranteed to have at least one
+ 16-pixel aligned span that can take advantage of mm_store.
+*/
+static void BlitRect32_OpaqueWide_SSE2(SkPMColor* SK_RESTRICT destination,
+ int width, int height,
+ size_t rowBytes, uint32_t color) {
+ SkASSERT(255 == SkGetPackedA32(color));
+ SkASSERT(width >= 31);
+
+ __m128i color_wide = _mm_set1_epi32(color);
+ while (--height >= 0) {
+ // Prefetching one row ahead to L1 cache can equal hardware
+ // performance for large/tall rects, but never *beats*
+ // hardware performance.
+ SkPMColor* dst = destination;
+ int count = width;
+
+ while (((size_t)dst) & 0x0F) {
+ *dst++ = color;
+ --count;
+ }
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+
+ // Googling suggests _mm_stream is only going to beat _mm_store
+ // for things that wouldn't fit in L2 cache anyway, typically
+ // >500kB, and precisely fill cache lines. For us, with
+ // arrays > 100k elements _mm_stream is still 100%+ slower than
+ // mm_store.
+
+ // Unrolling to count >= 64 is a break-even for most
+ // input patterns; we seem to be saturating the bus and having
+ // low enough overhead at 32.
+
+ while (count >= 32) {
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ count -= 32;
+ }
+ if (count >= 16) {
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ _mm_store_si128(d++, color_wide);
+ count -= 16;
+ }
+ dst = reinterpret_cast<uint32_t*>(d);
+
+ // Unrolling the loop in the Narrow code is a significant performance
+ // gain, but unrolling this loop appears to make no difference in
+ // benchmarks with either mm_store_si128 or individual sets.
+
+ while (count > 0) {
+ *dst++ = color;
+ --count;
+ }
+
+ destination = (uint32_t*)((char*)destination + rowBytes);
+ }
+}
+
+void ColorRect32_SSE2(SkPMColor* destination,
+ int width, int height,
+ size_t rowBytes, uint32_t color) {
+ if (0 == height || 0 == width || 0 == color) {
+ return;
+ }
+ unsigned colorA = SkGetPackedA32(color);
+ colorA = 0; // skip below if () for now...(has been disabled since this was added in r3423).
+ if (255 == colorA) {
+ if (width < 31) {
+ BlitRect32_OpaqueNarrow_SSE2(destination, width, height,
+ rowBytes, color);
+ } else {
+ BlitRect32_OpaqueWide_SSE2(destination, width, height,
+ rowBytes, color);
+ }
+ } else {
+ SkBlitRow::ColorRect32(destination, width, height, rowBytes, color);
+ }
+}
diff --git a/opts/SkBlitRect_opts_SSE2.h b/opts/SkBlitRect_opts_SSE2.h
new file mode 100644
index 00000000..4d2f74a4
--- /dev/null
+++ b/opts/SkBlitRect_opts_SSE2.h
@@ -0,0 +1,23 @@
+/*
+ * 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 SkBlitRect_opts_SSE2_DEFINED
+#define SkBlitRect_opts_SSE2_DEFINED
+
+/*
+ These functions' implementations copy sections of both
+ SkBlitRow_opts_SSE2 and SkUtils_opts_SSE2.
+*/
+
+#include "SkColor.h"
+
+void ColorRect32_SSE2(SkPMColor* SK_RESTRICT dst,
+ int width, int height,
+ size_t rowBytes, uint32_t color);
+
+
+#endif
diff --git a/opts/SkBlitRow_opts_SSE2.cpp b/opts/SkBlitRow_opts_SSE2.cpp
new file mode 100644
index 00000000..f3d010e3
--- /dev/null
+++ b/opts/SkBlitRow_opts_SSE2.cpp
@@ -0,0 +1,853 @@
+/*
+ * 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 "SkBlitRow_opts_SSE2.h"
+#include "SkBitmapProcState_opts_SSE2.h"
+#include "SkColorPriv.h"
+#include "SkUtils.h"
+
+#include <emmintrin.h>
+
+/* SSE2 version of S32_Blend_BlitRow32()
+ * portable version is in core/SkBlitRow_D32.cpp
+ */
+void S32_Blend_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha <= 255);
+ if (count <= 0) {
+ return;
+ }
+
+ uint32_t src_scale = SkAlpha255To256(alpha);
+ uint32_t dst_scale = 256 - src_scale;
+
+ if (count >= 4) {
+ SkASSERT(((size_t)dst & 0x03) == 0);
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = SkAlphaMulQ(*src, src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ src++;
+ dst++;
+ count--;
+ }
+
+ const __m128i *s = reinterpret_cast<const __m128i*>(src);
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i ag_mask = _mm_set1_epi32(0xFF00FF00);
+
+ // Move scale factors to upper byte of word
+ __m128i src_scale_wide = _mm_set1_epi16(src_scale << 8);
+ __m128i dst_scale_wide = _mm_set1_epi16(dst_scale << 8);
+ while (count >= 4) {
+ // Load 4 pixels each of src and dest.
+ __m128i src_pixel = _mm_loadu_si128(s);
+ __m128i dst_pixel = _mm_load_si128(d);
+
+ // Interleave Atom port 0/1 operations based on the execution port
+ // constraints that multiply can only be executed on port 0 (while
+ // boolean operations can be executed on either port 0 or port 1)
+ // because GCC currently doesn't do a good job scheduling
+ // instructions based on these constraints.
+
+ // Get red and blue pixels into lower byte of each word.
+ // (0, r, 0, b, 0, r, 0, b, 0, r, 0, b, 0, r, 0, b)
+ __m128i src_rb = _mm_and_si128(rb_mask, src_pixel);
+
+ // Multiply by scale.
+ // (4 x (0, rs.h, 0, bs.h))
+ // where rs.h stands for the higher byte of r * scale, and
+ // bs.h the higher byte of b * scale.
+ src_rb = _mm_mulhi_epu16(src_rb, src_scale_wide);
+
+ // Get alpha and green pixels into higher byte of each word.
+ // (a, 0, g, 0, a, 0, g, 0, a, 0, g, 0, a, 0, g, 0)
+ __m128i src_ag = _mm_and_si128(ag_mask, src_pixel);
+
+ // Multiply by scale.
+ // (4 x (as.h, as.l, gs.h, gs.l))
+ src_ag = _mm_mulhi_epu16(src_ag, src_scale_wide);
+
+ // Clear the lower byte of the a*scale and g*scale results
+ // (4 x (as.h, 0, gs.h, 0))
+ src_ag = _mm_and_si128(src_ag, ag_mask);
+
+ // Operations the destination pixels are the same as on the
+ // source pixels. See the comments above.
+ __m128i dst_rb = _mm_and_si128(rb_mask, dst_pixel);
+ dst_rb = _mm_mulhi_epu16(dst_rb, dst_scale_wide);
+ __m128i dst_ag = _mm_and_si128(ag_mask, dst_pixel);
+ dst_ag = _mm_mulhi_epu16(dst_ag, dst_scale_wide);
+ dst_ag = _mm_and_si128(dst_ag, ag_mask);
+
+ // Combine back into RGBA.
+ // (4 x (as.h, rs.h, gs.h, bs.h))
+ src_pixel = _mm_or_si128(src_rb, src_ag);
+ dst_pixel = _mm_or_si128(dst_rb, dst_ag);
+
+ // Add result
+ __m128i result = _mm_add_epi8(src_pixel, dst_pixel);
+ _mm_store_si128(d, result);
+ s++;
+ d++;
+ count -= 4;
+ }
+ src = reinterpret_cast<const SkPMColor*>(s);
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (count > 0) {
+ *dst = SkAlphaMulQ(*src, src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ src++;
+ dst++;
+ count--;
+ }
+}
+
+void S32A_Opaque_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha == 255);
+ if (count <= 0) {
+ return;
+ }
+
+ if (count >= 4) {
+ SkASSERT(((size_t)dst & 0x03) == 0);
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = SkPMSrcOver(*src, *dst);
+ src++;
+ dst++;
+ count--;
+ }
+
+ const __m128i *s = reinterpret_cast<const __m128i*>(src);
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+#ifdef SK_USE_ACCURATE_BLENDING
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i c_128 = _mm_set1_epi16(128); // 8 copies of 128 (16-bit)
+ __m128i c_255 = _mm_set1_epi16(255); // 8 copies of 255 (16-bit)
+ while (count >= 4) {
+ // Load 4 pixels
+ __m128i src_pixel = _mm_loadu_si128(s);
+ __m128i dst_pixel = _mm_load_si128(d);
+
+ __m128i dst_rb = _mm_and_si128(rb_mask, dst_pixel);
+ __m128i dst_ag = _mm_srli_epi16(dst_pixel, 8);
+ // Shift alphas down to lower 8 bits of each quad.
+ __m128i alpha = _mm_srli_epi32(src_pixel, 24);
+
+ // Copy alpha to upper 3rd byte of each quad
+ alpha = _mm_or_si128(alpha, _mm_slli_epi32(alpha, 16));
+
+ // Subtract alphas from 255, to get 0..255
+ alpha = _mm_sub_epi16(c_255, alpha);
+
+ // Multiply by red and blue by src alpha.
+ dst_rb = _mm_mullo_epi16(dst_rb, alpha);
+ // Multiply by alpha and green by src alpha.
+ dst_ag = _mm_mullo_epi16(dst_ag, alpha);
+
+ // dst_rb_low = (dst_rb >> 8)
+ __m128i dst_rb_low = _mm_srli_epi16(dst_rb, 8);
+ __m128i dst_ag_low = _mm_srli_epi16(dst_ag, 8);
+
+ // dst_rb = (dst_rb + dst_rb_low + 128) >> 8
+ dst_rb = _mm_add_epi16(dst_rb, dst_rb_low);
+ dst_rb = _mm_add_epi16(dst_rb, c_128);
+ dst_rb = _mm_srli_epi16(dst_rb, 8);
+
+ // dst_ag = (dst_ag + dst_ag_low + 128) & ag_mask
+ dst_ag = _mm_add_epi16(dst_ag, dst_ag_low);
+ dst_ag = _mm_add_epi16(dst_ag, c_128);
+ dst_ag = _mm_andnot_si128(rb_mask, dst_ag);
+
+ // Combine back into RGBA.
+ dst_pixel = _mm_or_si128(dst_rb, dst_ag);
+
+ // Add result
+ __m128i result = _mm_add_epi8(src_pixel, dst_pixel);
+ _mm_store_si128(d, result);
+ s++;
+ d++;
+ count -= 4;
+ }
+ #else
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i c_256 = _mm_set1_epi16(0x0100); // 8 copies of 256 (16-bit)
+ while (count >= 4) {
+ // Load 4 pixels
+ __m128i src_pixel = _mm_loadu_si128(s);
+ __m128i dst_pixel = _mm_load_si128(d);
+
+ __m128i dst_rb = _mm_and_si128(rb_mask, dst_pixel);
+ __m128i dst_ag = _mm_srli_epi16(dst_pixel, 8);
+
+ // (a0, g0, a1, g1, a2, g2, a3, g3) (low byte of each word)
+ __m128i alpha = _mm_srli_epi16(src_pixel, 8);
+
+ // (a0, a0, a1, a1, a2, g2, a3, g3)
+ alpha = _mm_shufflehi_epi16(alpha, 0xF5);
+
+ // (a0, a0, a1, a1, a2, a2, a3, a3)
+ alpha = _mm_shufflelo_epi16(alpha, 0xF5);
+
+ // Subtract alphas from 256, to get 1..256
+ alpha = _mm_sub_epi16(c_256, alpha);
+
+ // Multiply by red and blue by src alpha.
+ dst_rb = _mm_mullo_epi16(dst_rb, alpha);
+ // Multiply by alpha and green by src alpha.
+ dst_ag = _mm_mullo_epi16(dst_ag, alpha);
+
+ // Divide by 256.
+ dst_rb = _mm_srli_epi16(dst_rb, 8);
+
+ // Mask out high bits (already in the right place)
+ dst_ag = _mm_andnot_si128(rb_mask, dst_ag);
+
+ // Combine back into RGBA.
+ dst_pixel = _mm_or_si128(dst_rb, dst_ag);
+
+ // Add result
+ __m128i result = _mm_add_epi8(src_pixel, dst_pixel);
+ _mm_store_si128(d, result);
+ s++;
+ d++;
+ count -= 4;
+ }
+#endif
+ src = reinterpret_cast<const SkPMColor*>(s);
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (count > 0) {
+ *dst = SkPMSrcOver(*src, *dst);
+ src++;
+ dst++;
+ count--;
+ }
+}
+
+void S32A_Blend_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha <= 255);
+ if (count <= 0) {
+ return;
+ }
+
+ if (count >= 4) {
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = SkBlendARGB32(*src, *dst, alpha);
+ src++;
+ dst++;
+ count--;
+ }
+
+ uint32_t src_scale = SkAlpha255To256(alpha);
+
+ const __m128i *s = reinterpret_cast<const __m128i*>(src);
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i src_scale_wide = _mm_set1_epi16(src_scale << 8);
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i c_256 = _mm_set1_epi16(256); // 8 copies of 256 (16-bit)
+ while (count >= 4) {
+ // Load 4 pixels each of src and dest.
+ __m128i src_pixel = _mm_loadu_si128(s);
+ __m128i dst_pixel = _mm_load_si128(d);
+
+ // Get red and blue pixels into lower byte of each word.
+ __m128i dst_rb = _mm_and_si128(rb_mask, dst_pixel);
+ __m128i src_rb = _mm_and_si128(rb_mask, src_pixel);
+
+ // Get alpha and green into lower byte of each word.
+ __m128i dst_ag = _mm_srli_epi16(dst_pixel, 8);
+ __m128i src_ag = _mm_srli_epi16(src_pixel, 8);
+
+ // Put per-pixel alpha in low byte of each word.
+ // After the following two statements, the dst_alpha looks like
+ // (0, a0, 0, a0, 0, a1, 0, a1, 0, a2, 0, a2, 0, a3, 0, a3)
+ __m128i dst_alpha = _mm_shufflehi_epi16(src_ag, 0xF5);
+ dst_alpha = _mm_shufflelo_epi16(dst_alpha, 0xF5);
+
+ // dst_alpha = dst_alpha * src_scale
+ // Because src_scales are in the higher byte of each word and
+ // we use mulhi here, the resulting alpha values are already
+ // in the right place and don't need to be divided by 256.
+ // (0, sa0, 0, sa0, 0, sa1, 0, sa1, 0, sa2, 0, sa2, 0, sa3, 0, sa3)
+ dst_alpha = _mm_mulhi_epu16(dst_alpha, src_scale_wide);
+
+ // Subtract alphas from 256, to get 1..256
+ dst_alpha = _mm_sub_epi16(c_256, dst_alpha);
+
+ // Multiply red and blue by dst pixel alpha.
+ dst_rb = _mm_mullo_epi16(dst_rb, dst_alpha);
+ // Multiply alpha and green by dst pixel alpha.
+ dst_ag = _mm_mullo_epi16(dst_ag, dst_alpha);
+
+ // Multiply red and blue by global alpha.
+ // (4 x (0, rs.h, 0, bs.h))
+ // where rs.h stands for the higher byte of r * src_scale,
+ // and bs.h the higher byte of b * src_scale.
+ // Again, because we use mulhi, the resuling red and blue
+ // values are already in the right place and don't need to
+ // be divided by 256.
+ src_rb = _mm_mulhi_epu16(src_rb, src_scale_wide);
+ // Multiply alpha and green by global alpha.
+ // (4 x (0, as.h, 0, gs.h))
+ src_ag = _mm_mulhi_epu16(src_ag, src_scale_wide);
+
+ // Divide by 256.
+ dst_rb = _mm_srli_epi16(dst_rb, 8);
+
+ // Mask out low bits (goodies already in the right place; no need to divide)
+ dst_ag = _mm_andnot_si128(rb_mask, dst_ag);
+ // Shift alpha and green to higher byte of each word.
+ // (4 x (as.h, 0, gs.h, 0))
+ src_ag = _mm_slli_epi16(src_ag, 8);
+
+ // Combine back into RGBA.
+ dst_pixel = _mm_or_si128(dst_rb, dst_ag);
+ src_pixel = _mm_or_si128(src_rb, src_ag);
+
+ // Add two pixels into result.
+ __m128i result = _mm_add_epi8(src_pixel, dst_pixel);
+ _mm_store_si128(d, result);
+ s++;
+ d++;
+ count -= 4;
+ }
+ src = reinterpret_cast<const SkPMColor*>(s);
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (count > 0) {
+ *dst = SkBlendARGB32(*src, *dst, alpha);
+ src++;
+ dst++;
+ count--;
+ }
+}
+
+/* SSE2 version of Color32()
+ * portable version is in core/SkBlitRow_D32.cpp
+ */
+void Color32_SSE2(SkPMColor dst[], const SkPMColor src[], int count,
+ SkPMColor color) {
+
+ if (count <= 0) {
+ return;
+ }
+
+ if (0 == color) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(SkPMColor));
+ }
+ return;
+ }
+
+ unsigned colorA = SkGetPackedA32(color);
+ if (255 == colorA) {
+ sk_memset32(dst, color, count);
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(colorA);
+
+ if (count >= 4) {
+ SkASSERT(((size_t)dst & 0x03) == 0);
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = color + SkAlphaMulQ(*src, scale);
+ src++;
+ dst++;
+ count--;
+ }
+
+ const __m128i *s = reinterpret_cast<const __m128i*>(src);
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i src_scale_wide = _mm_set1_epi16(scale);
+ __m128i color_wide = _mm_set1_epi32(color);
+ while (count >= 4) {
+ // Load 4 pixels each of src and dest.
+ __m128i src_pixel = _mm_loadu_si128(s);
+
+ // Get red and blue pixels into lower byte of each word.
+ __m128i src_rb = _mm_and_si128(rb_mask, src_pixel);
+
+ // Get alpha and green into lower byte of each word.
+ __m128i src_ag = _mm_srli_epi16(src_pixel, 8);
+
+ // Multiply by scale.
+ src_rb = _mm_mullo_epi16(src_rb, src_scale_wide);
+ src_ag = _mm_mullo_epi16(src_ag, src_scale_wide);
+
+ // Divide by 256.
+ src_rb = _mm_srli_epi16(src_rb, 8);
+ src_ag = _mm_andnot_si128(rb_mask, src_ag);
+
+ // Combine back into RGBA.
+ src_pixel = _mm_or_si128(src_rb, src_ag);
+
+ // Add color to result.
+ __m128i result = _mm_add_epi8(color_wide, src_pixel);
+
+ // Store result.
+ _mm_store_si128(d, result);
+ s++;
+ d++;
+ count -= 4;
+ }
+ src = reinterpret_cast<const SkPMColor*>(s);
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (count > 0) {
+ *dst = color + SkAlphaMulQ(*src, scale);
+ src += 1;
+ dst += 1;
+ count--;
+ }
+ }
+}
+
+void SkARGB32_A8_BlitMask_SSE2(void* device, size_t dstRB, const void* maskPtr,
+ size_t maskRB, SkColor origColor,
+ int width, int height) {
+ SkPMColor color = SkPreMultiplyColor(origColor);
+ size_t dstOffset = dstRB - (width << 2);
+ size_t maskOffset = maskRB - width;
+ SkPMColor* dst = (SkPMColor *)device;
+ const uint8_t* mask = (const uint8_t*)maskPtr;
+ do {
+ int count = width;
+ if (count >= 4) {
+ while (((size_t)dst & 0x0F) != 0 && (count > 0)) {
+ *dst = SkBlendARGB32(color, *dst, *mask);
+ mask++;
+ dst++;
+ count--;
+ }
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i rb_mask = _mm_set1_epi32(0x00FF00FF);
+ __m128i c_256 = _mm_set1_epi16(256);
+ __m128i c_1 = _mm_set1_epi16(1);
+ __m128i src_pixel = _mm_set1_epi32(color);
+ while (count >= 4) {
+ // Load 4 pixels each of src and dest.
+ __m128i dst_pixel = _mm_load_si128(d);
+
+ //set the aphla value
+ __m128i src_scale_wide = _mm_set_epi8(0, *(mask+3),\
+ 0, *(mask+3),0, \
+ *(mask+2),0, *(mask+2),\
+ 0,*(mask+1), 0,*(mask+1),\
+ 0, *mask,0,*mask);
+
+ //call SkAlpha255To256()
+ src_scale_wide = _mm_add_epi16(src_scale_wide, c_1);
+
+ // Get red and blue pixels into lower byte of each word.
+ __m128i dst_rb = _mm_and_si128(rb_mask, dst_pixel);
+ __m128i src_rb = _mm_and_si128(rb_mask, src_pixel);
+
+ // Get alpha and green into lower byte of each word.
+ __m128i dst_ag = _mm_srli_epi16(dst_pixel, 8);
+ __m128i src_ag = _mm_srli_epi16(src_pixel, 8);
+
+ // Put per-pixel alpha in low byte of each word.
+ __m128i dst_alpha = _mm_shufflehi_epi16(src_ag, 0xF5);
+ dst_alpha = _mm_shufflelo_epi16(dst_alpha, 0xF5);
+
+ // dst_alpha = dst_alpha * src_scale
+ dst_alpha = _mm_mullo_epi16(dst_alpha, src_scale_wide);
+
+ // Divide by 256.
+ dst_alpha = _mm_srli_epi16(dst_alpha, 8);
+
+ // Subtract alphas from 256, to get 1..256
+ dst_alpha = _mm_sub_epi16(c_256, dst_alpha);
+ // Multiply red and blue by dst pixel alpha.
+ dst_rb = _mm_mullo_epi16(dst_rb, dst_alpha);
+ // Multiply alpha and green by dst pixel alpha.
+ dst_ag = _mm_mullo_epi16(dst_ag, dst_alpha);
+
+ // Multiply red and blue by global alpha.
+ src_rb = _mm_mullo_epi16(src_rb, src_scale_wide);
+ // Multiply alpha and green by global alpha.
+ src_ag = _mm_mullo_epi16(src_ag, src_scale_wide);
+ // Divide by 256.
+ dst_rb = _mm_srli_epi16(dst_rb, 8);
+ src_rb = _mm_srli_epi16(src_rb, 8);
+
+ // Mask out low bits (goodies already in the right place; no need to divide)
+ dst_ag = _mm_andnot_si128(rb_mask, dst_ag);
+ src_ag = _mm_andnot_si128(rb_mask, src_ag);
+
+ // Combine back into RGBA.
+ dst_pixel = _mm_or_si128(dst_rb, dst_ag);
+ __m128i tmp_src_pixel = _mm_or_si128(src_rb, src_ag);
+
+ // Add two pixels into result.
+ __m128i result = _mm_add_epi8(tmp_src_pixel, dst_pixel);
+ _mm_store_si128(d, result);
+ // load the next 4 pixel
+ mask = mask + 4;
+ d++;
+ count -= 4;
+ }
+ dst = reinterpret_cast<SkPMColor *>(d);
+ }
+ while(count > 0) {
+ *dst= SkBlendARGB32(color, *dst, *mask);
+ dst += 1;
+ mask++;
+ count --;
+ }
+ dst = (SkPMColor *)((char*)dst + dstOffset);
+ mask += maskOffset;
+ } while (--height != 0);
+}
+
+// The following (left) shifts cause the top 5 bits of the mask components to
+// line up with the corresponding components in an SkPMColor.
+// Note that the mask's RGB16 order may differ from the SkPMColor order.
+#define SK_R16x5_R32x5_SHIFT (SK_R32_SHIFT - SK_R16_SHIFT - SK_R16_BITS + 5)
+#define SK_G16x5_G32x5_SHIFT (SK_G32_SHIFT - SK_G16_SHIFT - SK_G16_BITS + 5)
+#define SK_B16x5_B32x5_SHIFT (SK_B32_SHIFT - SK_B16_SHIFT - SK_B16_BITS + 5)
+
+#if SK_R16x5_R32x5_SHIFT == 0
+ #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (x)
+#elif SK_R16x5_R32x5_SHIFT > 0
+ #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (_mm_slli_epi32(x, SK_R16x5_R32x5_SHIFT))
+#else
+ #define SkPackedR16x5ToUnmaskedR32x5_SSE2(x) (_mm_srli_epi32(x, -SK_R16x5_R32x5_SHIFT))
+#endif
+
+#if SK_G16x5_G32x5_SHIFT == 0
+ #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (x)
+#elif SK_G16x5_G32x5_SHIFT > 0
+ #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (_mm_slli_epi32(x, SK_G16x5_G32x5_SHIFT))
+#else
+ #define SkPackedG16x5ToUnmaskedG32x5_SSE2(x) (_mm_srli_epi32(x, -SK_G16x5_G32x5_SHIFT))
+#endif
+
+#if SK_B16x5_B32x5_SHIFT == 0
+ #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (x)
+#elif SK_B16x5_B32x5_SHIFT > 0
+ #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (_mm_slli_epi32(x, SK_B16x5_B32x5_SHIFT))
+#else
+ #define SkPackedB16x5ToUnmaskedB32x5_SSE2(x) (_mm_srli_epi32(x, -SK_B16x5_B32x5_SHIFT))
+#endif
+
+static __m128i SkBlendLCD16_SSE2(__m128i &src, __m128i &dst,
+ __m128i &mask, __m128i &srcA) {
+ // In the following comments, the components of src, dst and mask are
+ // abbreviated as (s)rc, (d)st, and (m)ask. Color components are marked
+ // by an R, G, B, or A suffix. Components of one of the four pixels that
+ // are processed in parallel are marked with 0, 1, 2, and 3. "d1B", for
+ // example is the blue channel of the second destination pixel. Memory
+ // layout is shown for an ARGB byte order in a color value.
+
+ // src and srcA store 8-bit values interleaved with zeros.
+ // src = (0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0)
+ // srcA = (srcA, 0, srcA, 0, srcA, 0, srcA, 0,
+ // srcA, 0, srcA, 0, srcA, 0, srcA, 0)
+ // mask stores 16-bit values (compressed three channels) interleaved with zeros.
+ // Lo and Hi denote the low and high bytes of a 16-bit value, respectively.
+ // mask = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0,
+ // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0)
+
+ // Get the R,G,B of each 16bit mask pixel, we want all of them in 5 bits.
+ // r = (0, m0R, 0, 0, 0, m1R, 0, 0, 0, m2R, 0, 0, 0, m3R, 0, 0)
+ __m128i r = _mm_and_si128(SkPackedR16x5ToUnmaskedR32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_R32_SHIFT));
+
+ // g = (0, 0, m0G, 0, 0, 0, m1G, 0, 0, 0, m2G, 0, 0, 0, m3G, 0)
+ __m128i g = _mm_and_si128(SkPackedG16x5ToUnmaskedG32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_G32_SHIFT));
+
+ // b = (0, 0, 0, m0B, 0, 0, 0, m1B, 0, 0, 0, m2B, 0, 0, 0, m3B)
+ __m128i b = _mm_and_si128(SkPackedB16x5ToUnmaskedB32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_B32_SHIFT));
+
+ // Pack the 4 16bit mask pixels into 4 32bit pixels, (p0, p1, p2, p3)
+ // Each component (m0R, m0G, etc.) is then a 5-bit value aligned to an
+ // 8-bit position
+ // mask = (0, m0R, m0G, m0B, 0, m1R, m1G, m1B,
+ // 0, m2R, m2G, m2B, 0, m3R, m3G, m3B)
+ mask = _mm_or_si128(_mm_or_si128(r, g), b);
+
+ // Interleave R,G,B into the lower byte of word.
+ // i.e. split the sixteen 8-bit values from mask into two sets of eight
+ // 16-bit values, padded by zero.
+ __m128i maskLo, maskHi;
+ // maskLo = (0, 0, m0R, 0, m0G, 0, m0B, 0, 0, 0, m1R, 0, m1G, 0, m1B, 0)
+ maskLo = _mm_unpacklo_epi8(mask, _mm_setzero_si128());
+ // maskHi = (0, 0, m2R, 0, m2G, 0, m2B, 0, 0, 0, m3R, 0, m3G, 0, m3B, 0)
+ maskHi = _mm_unpackhi_epi8(mask, _mm_setzero_si128());
+
+ // Upscale from 0..31 to 0..32
+ // (allows to replace division by left-shift further down)
+ // Left-shift each component by 4 and add the result back to that component,
+ // mapping numbers in the range 0..15 to 0..15, and 16..31 to 17..32
+ maskLo = _mm_add_epi16(maskLo, _mm_srli_epi16(maskLo, 4));
+ maskHi = _mm_add_epi16(maskHi, _mm_srli_epi16(maskHi, 4));
+
+ // Multiply each component of maskLo and maskHi by srcA
+ maskLo = _mm_mullo_epi16(maskLo, srcA);
+ maskHi = _mm_mullo_epi16(maskHi, srcA);
+
+ // Left shift mask components by 8 (divide by 256)
+ maskLo = _mm_srli_epi16(maskLo, 8);
+ maskHi = _mm_srli_epi16(maskHi, 8);
+
+ // Interleave R,G,B into the lower byte of the word
+ // dstLo = (0, 0, d0R, 0, d0G, 0, d0B, 0, 0, 0, d1R, 0, d1G, 0, d1B, 0)
+ __m128i dstLo = _mm_unpacklo_epi8(dst, _mm_setzero_si128());
+ // dstLo = (0, 0, d2R, 0, d2G, 0, d2B, 0, 0, 0, d3R, 0, d3G, 0, d3B, 0)
+ __m128i dstHi = _mm_unpackhi_epi8(dst, _mm_setzero_si128());
+
+ // mask = (src - dst) * mask
+ maskLo = _mm_mullo_epi16(maskLo, _mm_sub_epi16(src, dstLo));
+ maskHi = _mm_mullo_epi16(maskHi, _mm_sub_epi16(src, dstHi));
+
+ // mask = (src - dst) * mask >> 5
+ maskLo = _mm_srai_epi16(maskLo, 5);
+ maskHi = _mm_srai_epi16(maskHi, 5);
+
+ // Add two pixels into result.
+ // result = dst + ((src - dst) * mask >> 5)
+ __m128i resultLo = _mm_add_epi16(dstLo, maskLo);
+ __m128i resultHi = _mm_add_epi16(dstHi, maskHi);
+
+ // Pack into 4 32bit dst pixels.
+ // resultLo and resultHi contain eight 16-bit components (two pixels) each.
+ // Merge into one SSE regsiter with sixteen 8-bit values (four pixels),
+ // clamping to 255 if necessary.
+ return _mm_packus_epi16(resultLo, resultHi);
+}
+
+static __m128i SkBlendLCD16Opaque_SSE2(__m128i &src, __m128i &dst,
+ __m128i &mask) {
+ // In the following comments, the components of src, dst and mask are
+ // abbreviated as (s)rc, (d)st, and (m)ask. Color components are marked
+ // by an R, G, B, or A suffix. Components of one of the four pixels that
+ // are processed in parallel are marked with 0, 1, 2, and 3. "d1B", for
+ // example is the blue channel of the second destination pixel. Memory
+ // layout is shown for an ARGB byte order in a color value.
+
+ // src and srcA store 8-bit values interleaved with zeros.
+ // src = (0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0)
+ // mask stores 16-bit values (shown as high and low bytes) interleaved with
+ // zeros
+ // mask = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0,
+ // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0)
+
+ // Get the R,G,B of each 16bit mask pixel, we want all of them in 5 bits.
+ // r = (0, m0R, 0, 0, 0, m1R, 0, 0, 0, m2R, 0, 0, 0, m3R, 0, 0)
+ __m128i r = _mm_and_si128(SkPackedR16x5ToUnmaskedR32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_R32_SHIFT));
+
+ // g = (0, 0, m0G, 0, 0, 0, m1G, 0, 0, 0, m2G, 0, 0, 0, m3G, 0)
+ __m128i g = _mm_and_si128(SkPackedG16x5ToUnmaskedG32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_G32_SHIFT));
+
+ // b = (0, 0, 0, m0B, 0, 0, 0, m1B, 0, 0, 0, m2B, 0, 0, 0, m3B)
+ __m128i b = _mm_and_si128(SkPackedB16x5ToUnmaskedB32x5_SSE2(mask),
+ _mm_set1_epi32(0x1F << SK_B32_SHIFT));
+
+ // Pack the 4 16bit mask pixels into 4 32bit pixels, (p0, p1, p2, p3)
+ // Each component (m0R, m0G, etc.) is then a 5-bit value aligned to an
+ // 8-bit position
+ // mask = (0, m0R, m0G, m0B, 0, m1R, m1G, m1B,
+ // 0, m2R, m2G, m2B, 0, m3R, m3G, m3B)
+ mask = _mm_or_si128(_mm_or_si128(r, g), b);
+
+ // Interleave R,G,B into the lower byte of word.
+ // i.e. split the sixteen 8-bit values from mask into two sets of eight
+ // 16-bit values, padded by zero.
+ __m128i maskLo, maskHi;
+ // maskLo = (0, 0, m0R, 0, m0G, 0, m0B, 0, 0, 0, m1R, 0, m1G, 0, m1B, 0)
+ maskLo = _mm_unpacklo_epi8(mask, _mm_setzero_si128());
+ // maskHi = (0, 0, m2R, 0, m2G, 0, m2B, 0, 0, 0, m3R, 0, m3G, 0, m3B, 0)
+ maskHi = _mm_unpackhi_epi8(mask, _mm_setzero_si128());
+
+ // Upscale from 0..31 to 0..32
+ // (allows to replace division by left-shift further down)
+ // Left-shift each component by 4 and add the result back to that component,
+ // mapping numbers in the range 0..15 to 0..15, and 16..31 to 17..32
+ maskLo = _mm_add_epi16(maskLo, _mm_srli_epi16(maskLo, 4));
+ maskHi = _mm_add_epi16(maskHi, _mm_srli_epi16(maskHi, 4));
+
+ // Interleave R,G,B into the lower byte of the word
+ // dstLo = (0, 0, d0R, 0, d0G, 0, d0B, 0, 0, 0, d1R, 0, d1G, 0, d1B, 0)
+ __m128i dstLo = _mm_unpacklo_epi8(dst, _mm_setzero_si128());
+ // dstLo = (0, 0, d2R, 0, d2G, 0, d2B, 0, 0, 0, d3R, 0, d3G, 0, d3B, 0)
+ __m128i dstHi = _mm_unpackhi_epi8(dst, _mm_setzero_si128());
+
+ // mask = (src - dst) * mask
+ maskLo = _mm_mullo_epi16(maskLo, _mm_sub_epi16(src, dstLo));
+ maskHi = _mm_mullo_epi16(maskHi, _mm_sub_epi16(src, dstHi));
+
+ // mask = (src - dst) * mask >> 5
+ maskLo = _mm_srai_epi16(maskLo, 5);
+ maskHi = _mm_srai_epi16(maskHi, 5);
+
+ // Add two pixels into result.
+ // result = dst + ((src - dst) * mask >> 5)
+ __m128i resultLo = _mm_add_epi16(dstLo, maskLo);
+ __m128i resultHi = _mm_add_epi16(dstHi, maskHi);
+
+ // Pack into 4 32bit dst pixels and force opaque.
+ // resultLo and resultHi contain eight 16-bit components (two pixels) each.
+ // Merge into one SSE regsiter with sixteen 8-bit values (four pixels),
+ // clamping to 255 if necessary. Set alpha components to 0xFF.
+ return _mm_or_si128(_mm_packus_epi16(resultLo, resultHi),
+ _mm_set1_epi32(SK_A32_MASK << SK_A32_SHIFT));
+}
+
+void SkBlitLCD16Row_SSE2(SkPMColor dst[], const uint16_t mask[],
+ SkColor src, int width, SkPMColor) {
+ if (width <= 0) {
+ return;
+ }
+
+ int srcA = SkColorGetA(src);
+ int srcR = SkColorGetR(src);
+ int srcG = SkColorGetG(src);
+ int srcB = SkColorGetB(src);
+
+ srcA = SkAlpha255To256(srcA);
+
+ if (width >= 4) {
+ SkASSERT(((size_t)dst & 0x03) == 0);
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = SkBlendLCD16(srcA, srcR, srcG, srcB, *dst, *mask);
+ mask++;
+ dst++;
+ width--;
+ }
+
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ // Set alpha to 0xFF and replicate source four times in SSE register.
+ __m128i src_sse = _mm_set1_epi32(SkPackARGB32(0xFF, srcR, srcG, srcB));
+ // Interleave with zeros to get two sets of four 16-bit values.
+ src_sse = _mm_unpacklo_epi8(src_sse, _mm_setzero_si128());
+ // Set srcA_sse to contain eight copies of srcA, padded with zero.
+ // src_sse=(0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0)
+ __m128i srcA_sse = _mm_set1_epi16(srcA);
+ while (width >= 4) {
+ // Load four destination pixels into dst_sse.
+ __m128i dst_sse = _mm_load_si128(d);
+ // Load four 16-bit masks into lower half of mask_sse.
+ __m128i mask_sse = _mm_loadl_epi64(
+ reinterpret_cast<const __m128i*>(mask));
+
+ // Check whether masks are equal to 0 and get the highest bit
+ // of each byte of result, if masks are all zero, we will get
+ // pack_cmp to 0xFFFF
+ int pack_cmp = _mm_movemask_epi8(_mm_cmpeq_epi16(mask_sse,
+ _mm_setzero_si128()));
+
+ // if mask pixels are not all zero, we will blend the dst pixels
+ if (pack_cmp != 0xFFFF) {
+ // Unpack 4 16bit mask pixels to
+ // mask_sse = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0,
+ // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0)
+ mask_sse = _mm_unpacklo_epi16(mask_sse,
+ _mm_setzero_si128());
+
+ // Process 4 32bit dst pixels
+ __m128i result = SkBlendLCD16_SSE2(src_sse, dst_sse,
+ mask_sse, srcA_sse);
+ _mm_store_si128(d, result);
+ }
+
+ d++;
+ mask += 4;
+ width -= 4;
+ }
+
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (width > 0) {
+ *dst = SkBlendLCD16(srcA, srcR, srcG, srcB, *dst, *mask);
+ mask++;
+ dst++;
+ width--;
+ }
+}
+
+void SkBlitLCD16OpaqueRow_SSE2(SkPMColor dst[], const uint16_t mask[],
+ SkColor src, int width, SkPMColor opaqueDst) {
+ if (width <= 0) {
+ return;
+ }
+
+ int srcR = SkColorGetR(src);
+ int srcG = SkColorGetG(src);
+ int srcB = SkColorGetB(src);
+
+ if (width >= 4) {
+ SkASSERT(((size_t)dst & 0x03) == 0);
+ while (((size_t)dst & 0x0F) != 0) {
+ *dst = SkBlendLCD16Opaque(srcR, srcG, srcB, *dst, *mask, opaqueDst);
+ mask++;
+ dst++;
+ width--;
+ }
+
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ // Set alpha to 0xFF and replicate source four times in SSE register.
+ __m128i src_sse = _mm_set1_epi32(SkPackARGB32(0xFF, srcR, srcG, srcB));
+ // Set srcA_sse to contain eight copies of srcA, padded with zero.
+ // src_sse=(0xFF, 0, sR, 0, sG, 0, sB, 0, 0xFF, 0, sR, 0, sG, 0, sB, 0)
+ src_sse = _mm_unpacklo_epi8(src_sse, _mm_setzero_si128());
+ while (width >= 4) {
+ // Load four destination pixels into dst_sse.
+ __m128i dst_sse = _mm_load_si128(d);
+ // Load four 16-bit masks into lower half of mask_sse.
+ __m128i mask_sse = _mm_loadl_epi64(
+ reinterpret_cast<const __m128i*>(mask));
+
+ // Check whether masks are equal to 0 and get the highest bit
+ // of each byte of result, if masks are all zero, we will get
+ // pack_cmp to 0xFFFF
+ int pack_cmp = _mm_movemask_epi8(_mm_cmpeq_epi16(mask_sse,
+ _mm_setzero_si128()));
+
+ // if mask pixels are not all zero, we will blend the dst pixels
+ if (pack_cmp != 0xFFFF) {
+ // Unpack 4 16bit mask pixels to
+ // mask_sse = (m0RGBLo, m0RGBHi, 0, 0, m1RGBLo, m1RGBHi, 0, 0,
+ // m2RGBLo, m2RGBHi, 0, 0, m3RGBLo, m3RGBHi, 0, 0)
+ mask_sse = _mm_unpacklo_epi16(mask_sse,
+ _mm_setzero_si128());
+
+ // Process 4 32bit dst pixels
+ __m128i result = SkBlendLCD16Opaque_SSE2(src_sse, dst_sse,
+ mask_sse);
+ _mm_store_si128(d, result);
+ }
+
+ d++;
+ mask += 4;
+ width -= 4;
+ }
+
+ dst = reinterpret_cast<SkPMColor*>(d);
+ }
+
+ while (width > 0) {
+ *dst = SkBlendLCD16Opaque(srcR, srcG, srcB, *dst, *mask, opaqueDst);
+ mask++;
+ dst++;
+ width--;
+ }
+}
diff --git a/opts/SkBlitRow_opts_SSE2.h b/opts/SkBlitRow_opts_SSE2.h
new file mode 100644
index 00000000..b443ec7f
--- /dev/null
+++ b/opts/SkBlitRow_opts_SSE2.h
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright 2009 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 "SkBlitRow.h"
+
+void S32_Blend_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha);
+
+void S32A_Opaque_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha);
+
+void S32A_Blend_BlitRow32_SSE2(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha);
+void SkARGB32_A8_BlitMask_SSE2(void* device, size_t dstRB, const void* mask,
+ size_t maskRB, SkColor color,
+ int width, int height);
+
+void SkBlitLCD16Row_SSE2(SkPMColor dst[], const uint16_t src[],
+ SkColor color, int width, SkPMColor);
+void SkBlitLCD16OpaqueRow_SSE2(SkPMColor dst[], const uint16_t src[],
+ SkColor color, int width, SkPMColor opaqueDst);
diff --git a/opts/SkBlitRow_opts_arm.cpp b/opts/SkBlitRow_opts_arm.cpp
new file mode 100644
index 00000000..8eb7b1cf
--- /dev/null
+++ b/opts/SkBlitRow_opts_arm.cpp
@@ -0,0 +1,395 @@
+/*
+ * 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 "SkBlitMask.h"
+#include "SkBlitRow.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkMathPriv.h"
+#include "SkUtils.h"
+#include "SkUtilsArm.h"
+
+#include "SkCachePreload_arm.h"
+
+// Define USE_NEON_CODE to indicate that we need to build NEON routines
+#define USE_NEON_CODE (!SK_ARM_NEON_IS_NONE)
+
+// Define USE_ARM_CODE to indicate that we need to build ARM routines
+#define USE_ARM_CODE (!SK_ARM_NEON_IS_ALWAYS)
+
+#if USE_NEON_CODE
+ #include "SkBlitRow_opts_arm_neon.h"
+#endif
+
+#if USE_ARM_CODE
+
+static void S32A_D565_Opaque(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 == alpha);
+
+ asm volatile (
+ "1: \n\t"
+ "ldr r3, [%[src]], #4 \n\t"
+ "cmp r3, #0xff000000 \n\t"
+ "blo 2f \n\t"
+ "and r4, r3, #0x0000f8 \n\t"
+ "and r5, r3, #0x00fc00 \n\t"
+ "and r6, r3, #0xf80000 \n\t"
+ "pld [r1, #32] \n\t"
+ "lsl r3, r4, #8 \n\t"
+ "orr r3, r3, r5, lsr #5 \n\t"
+ "orr r3, r3, r6, lsr #19 \n\t"
+ "subs %[count], %[count], #1 \n\t"
+ "strh r3, [%[dst]], #2 \n\t"
+ "bne 1b \n\t"
+ "b 4f \n\t"
+ "2: \n\t"
+ "lsrs r7, r3, #24 \n\t"
+ "beq 3f \n\t"
+ "ldrh r4, [%[dst]] \n\t"
+ "rsb r7, r7, #255 \n\t"
+ "and r6, r4, #0x001f \n\t"
+#if SK_ARM_ARCH == 6
+ "lsl r5, r4, #21 \n\t"
+ "lsr r5, r5, #26 \n\t"
+#else
+ "ubfx r5, r4, #5, #6 \n\t"
+#endif
+ "pld [r0, #16] \n\t"
+ "lsr r4, r4, #11 \n\t"
+#ifdef SK_ARM_HAS_EDSP
+ "smulbb r6, r6, r7 \n\t"
+ "smulbb r5, r5, r7 \n\t"
+ "smulbb r4, r4, r7 \n\t"
+#else
+ "mul r6, r6, r7 \n\t"
+ "mul r5, r5, r7 \n\t"
+ "mul r4, r4, r7 \n\t"
+#endif
+ "uxtb r7, r3, ROR #16 \n\t"
+ "uxtb ip, r3, ROR #8 \n\t"
+ "and r3, r3, #0xff \n\t"
+ "add r6, r6, #16 \n\t"
+ "add r5, r5, #32 \n\t"
+ "add r4, r4, #16 \n\t"
+ "add r6, r6, r6, lsr #5 \n\t"
+ "add r5, r5, r5, lsr #6 \n\t"
+ "add r4, r4, r4, lsr #5 \n\t"
+ "add r6, r7, r6, lsr #5 \n\t"
+ "add r5, ip, r5, lsr #6 \n\t"
+ "add r4, r3, r4, lsr #5 \n\t"
+ "lsr r6, r6, #3 \n\t"
+ "and r5, r5, #0xfc \n\t"
+ "and r4, r4, #0xf8 \n\t"
+ "orr r6, r6, r5, lsl #3 \n\t"
+ "orr r4, r6, r4, lsl #8 \n\t"
+ "strh r4, [%[dst]], #2 \n\t"
+ "pld [r1, #32] \n\t"
+ "subs %[count], %[count], #1 \n\t"
+ "bne 1b \n\t"
+ "b 4f \n\t"
+ "3: \n\t"
+ "subs %[count], %[count], #1 \n\t"
+ "add %[dst], %[dst], #2 \n\t"
+ "bne 1b \n\t"
+ "4: \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src), [count] "+r" (count)
+ :
+ : "memory", "cc", "r3", "r4", "r5", "r6", "r7", "ip"
+ );
+}
+
+static void S32A_Opaque_BlitRow32_arm(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+
+ SkASSERT(255 == alpha);
+
+ asm volatile (
+ "cmp %[count], #0 \n\t" /* comparing count with 0 */
+ "beq 3f \n\t" /* if zero exit */
+
+ "mov ip, #0xff \n\t" /* load the 0xff mask in ip */
+ "orr ip, ip, ip, lsl #16 \n\t" /* convert it to 0xff00ff in ip */
+
+ "cmp %[count], #2 \n\t" /* compare count with 2 */
+ "blt 2f \n\t" /* if less than 2 -> single loop */
+
+ /* Double Loop */
+ "1: \n\t" /* <double loop> */
+ "ldm %[src]!, {r5,r6} \n\t" /* load the src(s) at r5-r6 */
+ "ldm %[dst], {r7,r8} \n\t" /* loading dst(s) into r7-r8 */
+ "lsr r4, r5, #24 \n\t" /* extracting the alpha from source and storing it to r4 */
+
+ /* ----------- */
+ "and r9, ip, r7 \n\t" /* r9 = br masked by ip */
+ "rsb r4, r4, #256 \n\t" /* subtracting the alpha from 256 -> r4=scale */
+ "and r10, ip, r7, lsr #8 \n\t" /* r10 = ag masked by ip */
+
+ "mul r9, r9, r4 \n\t" /* br = br * scale */
+ "mul r10, r10, r4 \n\t" /* ag = ag * scale */
+ "and r9, ip, r9, lsr #8 \n\t" /* lsr br by 8 and mask it */
+
+ "and r10, r10, ip, lsl #8 \n\t" /* mask ag with reverse mask */
+ "lsr r4, r6, #24 \n\t" /* extracting the alpha from source and storing it to r4 */
+ "orr r7, r9, r10 \n\t" /* br | ag*/
+
+ "add r7, r5, r7 \n\t" /* dst = src + calc dest(r7) */
+ "rsb r4, r4, #256 \n\t" /* subtracting the alpha from 255 -> r4=scale */
+
+ /* ----------- */
+ "and r9, ip, r8 \n\t" /* r9 = br masked by ip */
+
+ "and r10, ip, r8, lsr #8 \n\t" /* r10 = ag masked by ip */
+ "mul r9, r9, r4 \n\t" /* br = br * scale */
+ "sub %[count], %[count], #2 \n\t"
+ "mul r10, r10, r4 \n\t" /* ag = ag * scale */
+
+ "and r9, ip, r9, lsr #8 \n\t" /* lsr br by 8 and mask it */
+ "and r10, r10, ip, lsl #8 \n\t" /* mask ag with reverse mask */
+ "cmp %[count], #1 \n\t" /* comparing count with 1 */
+ "orr r8, r9, r10 \n\t" /* br | ag */
+
+ "add r8, r6, r8 \n\t" /* dst = src + calc dest(r8) */
+
+ /* ----------------- */
+ "stm %[dst]!, {r7,r8} \n\t" /* *dst = r7, increment dst by two (each times 4) */
+ /* ----------------- */
+
+ "bgt 1b \n\t" /* if greater than 1 -> reloop */
+ "blt 3f \n\t" /* if less than 1 -> exit */
+
+ /* Single Loop */
+ "2: \n\t" /* <single loop> */
+ "ldr r5, [%[src]], #4 \n\t" /* load the src pointer into r5 r5=src */
+ "ldr r7, [%[dst]] \n\t" /* loading dst into r7 */
+ "lsr r4, r5, #24 \n\t" /* extracting the alpha from source and storing it to r4 */
+
+ /* ----------- */
+ "and r9, ip, r7 \n\t" /* r9 = br masked by ip */
+ "rsb r4, r4, #256 \n\t" /* subtracting the alpha from 256 -> r4=scale */
+
+ "and r10, ip, r7, lsr #8 \n\t" /* r10 = ag masked by ip */
+ "mul r9, r9, r4 \n\t" /* br = br * scale */
+ "mul r10, r10, r4 \n\t" /* ag = ag * scale */
+ "and r9, ip, r9, lsr #8 \n\t" /* lsr br by 8 and mask it */
+
+ "and r10, r10, ip, lsl #8 \n\t" /* mask ag */
+ "orr r7, r9, r10 \n\t" /* br | ag */
+
+ "add r7, r5, r7 \n\t" /* *dst = src + calc dest(r7) */
+
+ /* ----------------- */
+ "str r7, [%[dst]], #4 \n\t" /* *dst = r7, increment dst by one (times 4) */
+ /* ----------------- */
+
+ "3: \n\t" /* <exit> */
+ : [dst] "+r" (dst), [src] "+r" (src), [count] "+r" (count)
+ :
+ : "cc", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "ip", "memory"
+ );
+}
+
+/*
+ * ARM asm version of S32A_Blend_BlitRow32
+ */
+void S32A_Blend_BlitRow32_arm(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ asm volatile (
+ "cmp %[count], #0 \n\t" /* comparing count with 0 */
+ "beq 3f \n\t" /* if zero exit */
+
+ "mov r12, #0xff \n\t" /* load the 0xff mask in r12 */
+ "orr r12, r12, r12, lsl #16 \n\t" /* convert it to 0xff00ff in r12 */
+
+ /* src1,2_scale */
+ "add %[alpha], %[alpha], #1 \n\t" /* loading %[alpha]=src_scale=alpha+1 */
+
+ "cmp %[count], #2 \n\t" /* comparing count with 2 */
+ "blt 2f \n\t" /* if less than 2 -> single loop */
+
+ /* Double Loop */
+ "1: \n\t" /* <double loop> */
+ "ldm %[src]!, {r5, r6} \n\t" /* loading src pointers into r5 and r6 */
+ "ldm %[dst], {r7, r8} \n\t" /* loading dst pointers into r7 and r8 */
+
+ /* dst1_scale and dst2_scale*/
+ "lsr r9, r5, #24 \n\t" /* src >> 24 */
+ "lsr r10, r6, #24 \n\t" /* src >> 24 */
+#ifdef SK_ARM_HAS_EDSP
+ "smulbb r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */
+ "smulbb r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */
+#else
+ "mul r9, r9, %[alpha] \n\t" /* r9 = SkMulS16 r9 with src_scale */
+ "mul r10, r10, %[alpha] \n\t" /* r10 = SkMulS16 r10 with src_scale */
+#endif
+ "lsr r9, r9, #8 \n\t" /* r9 >> 8 */
+ "lsr r10, r10, #8 \n\t" /* r10 >> 8 */
+ "rsb r9, r9, #256 \n\t" /* dst1_scale = r9 = 255 - r9 + 1 */
+ "rsb r10, r10, #256 \n\t" /* dst2_scale = r10 = 255 - r10 + 1 */
+
+ /* ---------------------- */
+
+ /* src1, src1_scale */
+ "and r11, r12, r5, lsr #8 \n\t" /* ag = r11 = r5 masked by r12 lsr by #8 */
+ "and r4, r12, r5 \n\t" /* rb = r4 = r5 masked by r12 */
+ "mul r11, r11, %[alpha] \n\t" /* ag = r11 times src_scale */
+ "mul r4, r4, %[alpha] \n\t" /* rb = r4 times src_scale */
+ "and r11, r11, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r4, r12, r4, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r5, r11, r4 \n\t" /* r5 = (src1, src_scale) */
+
+ /* dst1, dst1_scale */
+ "and r11, r12, r7, lsr #8 \n\t" /* ag = r11 = r7 masked by r12 lsr by #8 */
+ "and r4, r12, r7 \n\t" /* rb = r4 = r7 masked by r12 */
+ "mul r11, r11, r9 \n\t" /* ag = r11 times dst_scale (r9) */
+ "mul r4, r4, r9 \n\t" /* rb = r4 times dst_scale (r9) */
+ "and r11, r11, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r4, r12, r4, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r9, r11, r4 \n\t" /* r9 = (dst1, dst_scale) */
+
+ /* ---------------------- */
+ "add r9, r5, r9 \n\t" /* *dst = src plus dst both scaled */
+ /* ---------------------- */
+
+ /* ====================== */
+
+ /* src2, src2_scale */
+ "and r11, r12, r6, lsr #8 \n\t" /* ag = r11 = r6 masked by r12 lsr by #8 */
+ "and r4, r12, r6 \n\t" /* rb = r4 = r6 masked by r12 */
+ "mul r11, r11, %[alpha] \n\t" /* ag = r11 times src_scale */
+ "mul r4, r4, %[alpha] \n\t" /* rb = r4 times src_scale */
+ "and r11, r11, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r4, r12, r4, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r6, r11, r4 \n\t" /* r6 = (src2, src_scale) */
+
+ /* dst2, dst2_scale */
+ "and r11, r12, r8, lsr #8 \n\t" /* ag = r11 = r8 masked by r12 lsr by #8 */
+ "and r4, r12, r8 \n\t" /* rb = r4 = r8 masked by r12 */
+ "mul r11, r11, r10 \n\t" /* ag = r11 times dst_scale (r10) */
+ "mul r4, r4, r10 \n\t" /* rb = r4 times dst_scale (r6) */
+ "and r11, r11, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r4, r12, r4, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r10, r11, r4 \n\t" /* r10 = (dst2, dst_scale) */
+
+ "sub %[count], %[count], #2 \n\t" /* decrease count by 2 */
+ /* ---------------------- */
+ "add r10, r6, r10 \n\t" /* *dst = src plus dst both scaled */
+ /* ---------------------- */
+ "cmp %[count], #1 \n\t" /* compare count with 1 */
+ /* ----------------- */
+ "stm %[dst]!, {r9, r10} \n\t" /* copy r9 and r10 to r7 and r8 respectively */
+ /* ----------------- */
+
+ "bgt 1b \n\t" /* if %[count] greater than 1 reloop */
+ "blt 3f \n\t" /* if %[count] less than 1 exit */
+ /* else get into the single loop */
+ /* Single Loop */
+ "2: \n\t" /* <single loop> */
+ "ldr r5, [%[src]], #4 \n\t" /* loading src pointer into r5: r5=src */
+ "ldr r7, [%[dst]] \n\t" /* loading dst pointer into r7: r7=dst */
+
+ "lsr r6, r5, #24 \n\t" /* src >> 24 */
+ "and r8, r12, r5, lsr #8 \n\t" /* ag = r8 = r5 masked by r12 lsr by #8 */
+#ifdef SK_ARM_HAS_EDSP
+ "smulbb r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */
+#else
+ "mul r6, r6, %[alpha] \n\t" /* r6 = SkMulS16 with src_scale */
+#endif
+ "and r9, r12, r5 \n\t" /* rb = r9 = r5 masked by r12 */
+ "lsr r6, r6, #8 \n\t" /* r6 >> 8 */
+ "mul r8, r8, %[alpha] \n\t" /* ag = r8 times scale */
+ "rsb r6, r6, #256 \n\t" /* r6 = 255 - r6 + 1 */
+
+ /* src, src_scale */
+ "mul r9, r9, %[alpha] \n\t" /* rb = r9 times scale */
+ "and r8, r8, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r9, r12, r9, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r10, r8, r9 \n\t" /* r10 = (scr, src_scale) */
+
+ /* dst, dst_scale */
+ "and r8, r12, r7, lsr #8 \n\t" /* ag = r8 = r7 masked by r12 lsr by #8 */
+ "and r9, r12, r7 \n\t" /* rb = r9 = r7 masked by r12 */
+ "mul r8, r8, r6 \n\t" /* ag = r8 times scale (r6) */
+ "mul r9, r9, r6 \n\t" /* rb = r9 times scale (r6) */
+ "and r8, r8, r12, lsl #8 \n\t" /* ag masked by reverse mask (r12) */
+ "and r9, r12, r9, lsr #8 \n\t" /* rb masked by mask (r12) */
+ "orr r7, r8, r9 \n\t" /* r7 = (dst, dst_scale) */
+
+ "add r10, r7, r10 \n\t" /* *dst = src plus dst both scaled */
+
+ /* ----------------- */
+ "str r10, [%[dst]], #4 \n\t" /* *dst = r10, postincrement dst by one (times 4) */
+ /* ----------------- */
+
+ "3: \n\t" /* <exit> */
+ : [dst] "+r" (dst), [src] "+r" (src), [count] "+r" (count), [alpha] "+r" (alpha)
+ :
+ : "cc", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "memory"
+ );
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkBlitRow::Proc sk_blitrow_platform_565_procs_arm[] = {
+ // no dither
+ // NOTE: For the functions below, we don't have a special version
+ // that assumes that each source pixel is opaque. But our S32A is
+ // still faster than the default, so use it.
+ S32A_D565_Opaque, // S32_D565_Opaque
+ NULL, // S32_D565_Blend
+ S32A_D565_Opaque, // S32A_D565_Opaque
+ NULL, // S32A_D565_Blend
+
+ // dither
+ NULL, // S32_D565_Opaque_Dither
+ NULL, // S32_D565_Blend_Dither
+ NULL, // S32A_D565_Opaque_Dither
+ NULL, // S32A_D565_Blend_Dither
+};
+
+static const SkBlitRow::Proc32 sk_blitrow_platform_32_procs_arm[] = {
+ NULL, // S32_Opaque,
+ NULL, // S32_Blend,
+ S32A_Opaque_BlitRow32_arm, // S32A_Opaque,
+ S32A_Blend_BlitRow32_arm // S32A_Blend
+};
+
+#endif // USE_ARM_CODE
+
+SkBlitRow::Proc SkBlitRow::PlatformProcs565(unsigned flags) {
+ return SK_ARM_NEON_WRAP(sk_blitrow_platform_565_procs_arm)[flags];
+}
+
+SkBlitRow::Proc32 SkBlitRow::PlatformProcs32(unsigned flags) {
+ return SK_ARM_NEON_WRAP(sk_blitrow_platform_32_procs_arm)[flags];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define Color32_arm NULL
+SkBlitRow::ColorProc SkBlitRow::PlatformColorProc() {
+ return SK_ARM_NEON_WRAP(Color32_arm);
+}
+
+SkBlitMask::ColorProc SkBlitMask::PlatformColorProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ SkColor color) {
+ return NULL;
+}
+
+SkBlitMask::BlitLCD16RowProc SkBlitMask::PlatformBlitRowProcs16(bool isOpaque) {
+ return NULL;
+}
+
+SkBlitMask::RowProc SkBlitMask::PlatformRowProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ RowFlags flags) {
+ return NULL;
+}
diff --git a/opts/SkBlitRow_opts_arm_neon.cpp b/opts/SkBlitRow_opts_arm_neon.cpp
new file mode 100644
index 00000000..1bc0ea10
--- /dev/null
+++ b/opts/SkBlitRow_opts_arm_neon.cpp
@@ -0,0 +1,1369 @@
+/*
+ * 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 "SkBlitRow_opts_arm_neon.h"
+
+#include "SkBlitMask.h"
+#include "SkBlitRow.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkMathPriv.h"
+#include "SkUtils.h"
+
+#include "SkCachePreload_arm.h"
+
+#include <arm_neon.h>
+
+void S32A_D565_Opaque_neon(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+ SkASSERT(255 == alpha);
+
+ if (count >= 8) {
+ uint16_t* SK_RESTRICT keep_dst = 0;
+
+ asm volatile (
+ "ands ip, %[count], #7 \n\t"
+ "vmov.u8 d31, #1<<7 \n\t"
+ "vld1.16 {q12}, [%[dst]] \n\t"
+ "vld4.8 {d0-d3}, [%[src]] \n\t"
+ // Thumb does not support the standard ARM conditional
+ // instructions but instead requires the 'it' instruction
+ // to signal conditional execution
+ "it eq \n\t"
+ "moveq ip, #8 \n\t"
+ "mov %[keep_dst], %[dst] \n\t"
+
+ "add %[src], %[src], ip, LSL#2 \n\t"
+ "add %[dst], %[dst], ip, LSL#1 \n\t"
+ "subs %[count], %[count], ip \n\t"
+ "b 9f \n\t"
+ // LOOP
+ "2: \n\t"
+
+ "vld1.16 {q12}, [%[dst]]! \n\t"
+ "vld4.8 {d0-d3}, [%[src]]! \n\t"
+ "vst1.16 {q10}, [%[keep_dst]] \n\t"
+ "sub %[keep_dst], %[dst], #8*2 \n\t"
+ "subs %[count], %[count], #8 \n\t"
+ "9: \n\t"
+ "pld [%[dst],#32] \n\t"
+ // expand 0565 q12 to 8888 {d4-d7}
+ "vmovn.u16 d4, q12 \n\t"
+ "vshr.u16 q11, q12, #5 \n\t"
+ "vshr.u16 q10, q12, #6+5 \n\t"
+ "vmovn.u16 d5, q11 \n\t"
+ "vmovn.u16 d6, q10 \n\t"
+ "vshl.u8 d4, d4, #3 \n\t"
+ "vshl.u8 d5, d5, #2 \n\t"
+ "vshl.u8 d6, d6, #3 \n\t"
+
+ "vmovl.u8 q14, d31 \n\t"
+ "vmovl.u8 q13, d31 \n\t"
+ "vmovl.u8 q12, d31 \n\t"
+
+ // duplicate in 4/2/1 & 8pix vsns
+ "vmvn.8 d30, d3 \n\t"
+ "vmlal.u8 q14, d30, d6 \n\t"
+ "vmlal.u8 q13, d30, d5 \n\t"
+ "vmlal.u8 q12, d30, d4 \n\t"
+ "vshr.u16 q8, q14, #5 \n\t"
+ "vshr.u16 q9, q13, #6 \n\t"
+ "vaddhn.u16 d6, q14, q8 \n\t"
+ "vshr.u16 q8, q12, #5 \n\t"
+ "vaddhn.u16 d5, q13, q9 \n\t"
+ "vqadd.u8 d6, d6, d0 \n\t" // moved up
+ "vaddhn.u16 d4, q12, q8 \n\t"
+ // intentionally don't calculate alpha
+ // result in d4-d6
+
+ "vqadd.u8 d5, d5, d1 \n\t"
+ "vqadd.u8 d4, d4, d2 \n\t"
+
+ // pack 8888 {d4-d6} to 0565 q10
+ "vshll.u8 q10, d6, #8 \n\t"
+ "vshll.u8 q3, d5, #8 \n\t"
+ "vshll.u8 q2, d4, #8 \n\t"
+ "vsri.u16 q10, q3, #5 \n\t"
+ "vsri.u16 q10, q2, #11 \n\t"
+
+ "bne 2b \n\t"
+
+ "1: \n\t"
+ "vst1.16 {q10}, [%[keep_dst]] \n\t"
+ : [count] "+r" (count)
+ : [dst] "r" (dst), [keep_dst] "r" (keep_dst), [src] "r" (src)
+ : "ip", "cc", "memory", "d0","d1","d2","d3","d4","d5","d6","d7",
+ "d16","d17","d18","d19","d20","d21","d22","d23","d24","d25","d26","d27","d28","d29",
+ "d30","d31"
+ );
+ }
+ else
+ { // handle count < 8
+ uint16_t* SK_RESTRICT keep_dst = 0;
+
+ asm volatile (
+ "vmov.u8 d31, #1<<7 \n\t"
+ "mov %[keep_dst], %[dst] \n\t"
+
+ "tst %[count], #4 \n\t"
+ "beq 14f \n\t"
+ "vld1.16 {d25}, [%[dst]]! \n\t"
+ "vld1.32 {q1}, [%[src]]! \n\t"
+
+ "14: \n\t"
+ "tst %[count], #2 \n\t"
+ "beq 12f \n\t"
+ "vld1.32 {d24[1]}, [%[dst]]! \n\t"
+ "vld1.32 {d1}, [%[src]]! \n\t"
+
+ "12: \n\t"
+ "tst %[count], #1 \n\t"
+ "beq 11f \n\t"
+ "vld1.16 {d24[1]}, [%[dst]]! \n\t"
+ "vld1.32 {d0[1]}, [%[src]]! \n\t"
+
+ "11: \n\t"
+ // unzips achieve the same as a vld4 operation
+ "vuzpq.u16 q0, q1 \n\t"
+ "vuzp.u8 d0, d1 \n\t"
+ "vuzp.u8 d2, d3 \n\t"
+ // expand 0565 q12 to 8888 {d4-d7}
+ "vmovn.u16 d4, q12 \n\t"
+ "vshr.u16 q11, q12, #5 \n\t"
+ "vshr.u16 q10, q12, #6+5 \n\t"
+ "vmovn.u16 d5, q11 \n\t"
+ "vmovn.u16 d6, q10 \n\t"
+ "vshl.u8 d4, d4, #3 \n\t"
+ "vshl.u8 d5, d5, #2 \n\t"
+ "vshl.u8 d6, d6, #3 \n\t"
+
+ "vmovl.u8 q14, d31 \n\t"
+ "vmovl.u8 q13, d31 \n\t"
+ "vmovl.u8 q12, d31 \n\t"
+
+ // duplicate in 4/2/1 & 8pix vsns
+ "vmvn.8 d30, d3 \n\t"
+ "vmlal.u8 q14, d30, d6 \n\t"
+ "vmlal.u8 q13, d30, d5 \n\t"
+ "vmlal.u8 q12, d30, d4 \n\t"
+ "vshr.u16 q8, q14, #5 \n\t"
+ "vshr.u16 q9, q13, #6 \n\t"
+ "vaddhn.u16 d6, q14, q8 \n\t"
+ "vshr.u16 q8, q12, #5 \n\t"
+ "vaddhn.u16 d5, q13, q9 \n\t"
+ "vqadd.u8 d6, d6, d0 \n\t" // moved up
+ "vaddhn.u16 d4, q12, q8 \n\t"
+ // intentionally don't calculate alpha
+ // result in d4-d6
+
+ "vqadd.u8 d5, d5, d1 \n\t"
+ "vqadd.u8 d4, d4, d2 \n\t"
+
+ // pack 8888 {d4-d6} to 0565 q10
+ "vshll.u8 q10, d6, #8 \n\t"
+ "vshll.u8 q3, d5, #8 \n\t"
+ "vshll.u8 q2, d4, #8 \n\t"
+ "vsri.u16 q10, q3, #5 \n\t"
+ "vsri.u16 q10, q2, #11 \n\t"
+
+ // store
+ "tst %[count], #4 \n\t"
+ "beq 24f \n\t"
+ "vst1.16 {d21}, [%[keep_dst]]! \n\t"
+
+ "24: \n\t"
+ "tst %[count], #2 \n\t"
+ "beq 22f \n\t"
+ "vst1.32 {d20[1]}, [%[keep_dst]]! \n\t"
+
+ "22: \n\t"
+ "tst %[count], #1 \n\t"
+ "beq 21f \n\t"
+ "vst1.16 {d20[1]}, [%[keep_dst]]! \n\t"
+
+ "21: \n\t"
+ : [count] "+r" (count)
+ : [dst] "r" (dst), [keep_dst] "r" (keep_dst), [src] "r" (src)
+ : "ip", "cc", "memory", "d0","d1","d2","d3","d4","d5","d6","d7",
+ "d16","d17","d18","d19","d20","d21","d22","d23","d24","d25","d26","d27","d28","d29",
+ "d30","d31"
+ );
+ }
+}
+
+void S32A_D565_Blend_neon(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src, int count,
+ U8CPU alpha, int /*x*/, int /*y*/) {
+
+ U8CPU alpha_for_asm = alpha;
+
+ asm volatile (
+ /* This code implements a Neon version of S32A_D565_Blend. The output differs from
+ * the original in two respects:
+ * 1. The results have a few mismatches compared to the original code. These mismatches
+ * never exceed 1. It's possible to improve accuracy vs. a floating point
+ * implementation by introducing rounding right shifts (vrshr) for the final stage.
+ * Rounding is not present in the code below, because although results would be closer
+ * to a floating point implementation, the number of mismatches compared to the
+ * original code would be far greater.
+ * 2. On certain inputs, the original code can overflow, causing colour channels to
+ * mix. Although the Neon code can also overflow, it doesn't allow one colour channel
+ * to affect another.
+ */
+
+#if 1
+ /* reflects SkAlpha255To256()'s change from a+a>>7 to a+1 */
+ "add %[alpha], %[alpha], #1 \n\t" // adjust range of alpha 0-256
+#else
+ "add %[alpha], %[alpha], %[alpha], lsr #7 \n\t" // adjust range of alpha 0-256
+#endif
+ "vmov.u16 q3, #255 \n\t" // set up constant
+ "movs r4, %[count], lsr #3 \n\t" // calc. count>>3
+ "vmov.u16 d2[0], %[alpha] \n\t" // move alpha to Neon
+ "beq 2f \n\t" // if count8 == 0, exit
+ "vmov.u16 q15, #0x1f \n\t" // set up blue mask
+
+ "1: \n\t"
+ "vld1.u16 {d0, d1}, [%[dst]] \n\t" // load eight dst RGB565 pixels
+ "subs r4, r4, #1 \n\t" // decrement loop counter
+ "vld4.u8 {d24, d25, d26, d27}, [%[src]]! \n\t" // load eight src ABGR32 pixels
+ // and deinterleave
+
+ "vshl.u16 q9, q0, #5 \n\t" // shift green to top of lanes
+ "vand q10, q0, q15 \n\t" // extract blue
+ "vshr.u16 q8, q0, #11 \n\t" // extract red
+ "vshr.u16 q9, q9, #10 \n\t" // extract green
+ // dstrgb = {q8, q9, q10}
+
+ "vshr.u8 d24, d24, #3 \n\t" // shift red to 565 range
+ "vshr.u8 d25, d25, #2 \n\t" // shift green to 565 range
+ "vshr.u8 d26, d26, #3 \n\t" // shift blue to 565 range
+
+ "vmovl.u8 q11, d24 \n\t" // widen red to 16 bits
+ "vmovl.u8 q12, d25 \n\t" // widen green to 16 bits
+ "vmovl.u8 q14, d27 \n\t" // widen alpha to 16 bits
+ "vmovl.u8 q13, d26 \n\t" // widen blue to 16 bits
+ // srcrgba = {q11, q12, q13, q14}
+
+ "vmul.u16 q2, q14, d2[0] \n\t" // sa * src_scale
+ "vmul.u16 q11, q11, d2[0] \n\t" // red result = src_red * src_scale
+ "vmul.u16 q12, q12, d2[0] \n\t" // grn result = src_grn * src_scale
+ "vmul.u16 q13, q13, d2[0] \n\t" // blu result = src_blu * src_scale
+
+ "vshr.u16 q2, q2, #8 \n\t" // sa * src_scale >> 8
+ "vsub.u16 q2, q3, q2 \n\t" // 255 - (sa * src_scale >> 8)
+ // dst_scale = q2
+
+ "vmla.u16 q11, q8, q2 \n\t" // red result += dst_red * dst_scale
+ "vmla.u16 q12, q9, q2 \n\t" // grn result += dst_grn * dst_scale
+ "vmla.u16 q13, q10, q2 \n\t" // blu result += dst_blu * dst_scale
+
+#if 1
+ // trying for a better match with SkDiv255Round(a)
+ // C alg is: a+=128; (a+a>>8)>>8
+ // we'll use just a rounding shift [q2 is available for scratch]
+ "vrshr.u16 q11, q11, #8 \n\t" // shift down red
+ "vrshr.u16 q12, q12, #8 \n\t" // shift down green
+ "vrshr.u16 q13, q13, #8 \n\t" // shift down blue
+#else
+ // arm's original "truncating divide by 256"
+ "vshr.u16 q11, q11, #8 \n\t" // shift down red
+ "vshr.u16 q12, q12, #8 \n\t" // shift down green
+ "vshr.u16 q13, q13, #8 \n\t" // shift down blue
+#endif
+
+ "vsli.u16 q13, q12, #5 \n\t" // insert green into blue
+ "vsli.u16 q13, q11, #11 \n\t" // insert red into green/blue
+ "vst1.16 {d26, d27}, [%[dst]]! \n\t" // write pixel back to dst, update ptr
+
+ "bne 1b \n\t" // if counter != 0, loop
+ "2: \n\t" // exit
+
+ : [src] "+r" (src), [dst] "+r" (dst), [count] "+r" (count), [alpha] "+r" (alpha_for_asm)
+ :
+ : "cc", "memory", "r4", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31"
+ );
+
+ count &= 7;
+ if (count > 0) {
+ do {
+ SkPMColor sc = *src++;
+ if (sc) {
+ uint16_t dc = *dst;
+ unsigned dst_scale = 255 - SkMulDiv255Round(SkGetPackedA32(sc), alpha);
+ unsigned dr = SkMulS16(SkPacked32ToR16(sc), alpha) + SkMulS16(SkGetPackedR16(dc), dst_scale);
+ unsigned dg = SkMulS16(SkPacked32ToG16(sc), alpha) + SkMulS16(SkGetPackedG16(dc), dst_scale);
+ unsigned db = SkMulS16(SkPacked32ToB16(sc), alpha) + SkMulS16(SkGetPackedB16(dc), dst_scale);
+ *dst = SkPackRGB16(SkDiv255Round(dr), SkDiv255Round(dg), SkDiv255Round(db));
+ }
+ dst += 1;
+ } while (--count != 0);
+ }
+}
+
+/* dither matrix for Neon, derived from gDitherMatrix_3Bit_16.
+ * each dither value is spaced out into byte lanes, and repeated
+ * to allow an 8-byte load from offsets 0, 1, 2 or 3 from the
+ * start of each row.
+ */
+static const uint8_t gDitherMatrix_Neon[48] = {
+ 0, 4, 1, 5, 0, 4, 1, 5, 0, 4, 1, 5,
+ 6, 2, 7, 3, 6, 2, 7, 3, 6, 2, 7, 3,
+ 1, 5, 0, 4, 1, 5, 0, 4, 1, 5, 0, 4,
+ 7, 3, 6, 2, 7, 3, 6, 2, 7, 3, 6, 2,
+
+};
+
+void S32_D565_Blend_Dither_neon(uint16_t *dst, const SkPMColor *src,
+ int count, U8CPU alpha, int x, int y)
+{
+ /* select row and offset for dither array */
+ const uint8_t *dstart = &gDitherMatrix_Neon[(y&3)*12 + (x&3)];
+
+ /* rescale alpha to range 0 - 256 */
+ int scale = SkAlpha255To256(alpha);
+
+ asm volatile (
+ "vld1.8 {d31}, [%[dstart]] \n\t" // load dither values
+ "vshr.u8 d30, d31, #1 \n\t" // calc. green dither values
+ "vdup.16 d6, %[scale] \n\t" // duplicate scale into neon reg
+ "vmov.i8 d29, #0x3f \n\t" // set up green mask
+ "vmov.i8 d28, #0x1f \n\t" // set up blue mask
+ "1: \n\t"
+ "vld4.8 {d0, d1, d2, d3}, [%[src]]! \n\t" // load 8 pixels and split into argb
+ "vshr.u8 d22, d0, #5 \n\t" // calc. red >> 5
+ "vshr.u8 d23, d1, #6 \n\t" // calc. green >> 6
+ "vshr.u8 d24, d2, #5 \n\t" // calc. blue >> 5
+ "vaddl.u8 q8, d0, d31 \n\t" // add in dither to red and widen
+ "vaddl.u8 q9, d1, d30 \n\t" // add in dither to green and widen
+ "vaddl.u8 q10, d2, d31 \n\t" // add in dither to blue and widen
+ "vsubw.u8 q8, q8, d22 \n\t" // sub shifted red from result
+ "vsubw.u8 q9, q9, d23 \n\t" // sub shifted green from result
+ "vsubw.u8 q10, q10, d24 \n\t" // sub shifted blue from result
+ "vshrn.i16 d22, q8, #3 \n\t" // shift right and narrow to 5 bits
+ "vshrn.i16 d23, q9, #2 \n\t" // shift right and narrow to 6 bits
+ "vshrn.i16 d24, q10, #3 \n\t" // shift right and narrow to 5 bits
+ // load 8 pixels from dst, extract rgb
+ "vld1.16 {d0, d1}, [%[dst]] \n\t" // load 8 pixels
+ "vshrn.i16 d17, q0, #5 \n\t" // shift green down to bottom 6 bits
+ "vmovn.i16 d18, q0 \n\t" // narrow to get blue as bytes
+ "vshr.u16 q0, q0, #11 \n\t" // shift down to extract red
+ "vand d17, d17, d29 \n\t" // and green with green mask
+ "vand d18, d18, d28 \n\t" // and blue with blue mask
+ "vmovn.i16 d16, q0 \n\t" // narrow to get red as bytes
+ // src = {d22 (r), d23 (g), d24 (b)}
+ // dst = {d16 (r), d17 (g), d18 (b)}
+ // subtract dst from src and widen
+ "vsubl.s8 q0, d22, d16 \n\t" // subtract red src from dst
+ "vsubl.s8 q1, d23, d17 \n\t" // subtract green src from dst
+ "vsubl.s8 q2, d24, d18 \n\t" // subtract blue src from dst
+ // multiply diffs by scale and shift
+ "vmul.i16 q0, q0, d6[0] \n\t" // multiply red by scale
+ "vmul.i16 q1, q1, d6[0] \n\t" // multiply blue by scale
+ "vmul.i16 q2, q2, d6[0] \n\t" // multiply green by scale
+ "subs %[count], %[count], #8 \n\t" // decrement loop counter
+ "vshrn.i16 d0, q0, #8 \n\t" // shift down red by 8 and narrow
+ "vshrn.i16 d2, q1, #8 \n\t" // shift down green by 8 and narrow
+ "vshrn.i16 d4, q2, #8 \n\t" // shift down blue by 8 and narrow
+ // add dst to result
+ "vaddl.s8 q0, d0, d16 \n\t" // add dst to red
+ "vaddl.s8 q1, d2, d17 \n\t" // add dst to green
+ "vaddl.s8 q2, d4, d18 \n\t" // add dst to blue
+ // put result into 565 format
+ "vsli.i16 q2, q1, #5 \n\t" // shift up green and insert into blue
+ "vsli.i16 q2, q0, #11 \n\t" // shift up red and insert into blue
+ "vst1.16 {d4, d5}, [%[dst]]! \n\t" // store result
+ "bgt 1b \n\t" // loop if count > 0
+ : [src] "+r" (src), [dst] "+r" (dst), [count] "+r" (count)
+ : [dstart] "r" (dstart), [scale] "r" (scale)
+ : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", "d24", "d28", "d29", "d30", "d31"
+ );
+
+ DITHER_565_SCAN(y);
+
+ while((count & 7) > 0)
+ {
+ SkPMColor c = *src++;
+
+ int dither = DITHER_VALUE(x);
+ int sr = SkGetPackedR32(c);
+ int sg = SkGetPackedG32(c);
+ int sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32To565(sr, dither);
+ sg = SkDITHER_G32To565(sg, dither);
+ sb = SkDITHER_B32To565(sb, dither);
+
+ uint16_t d = *dst;
+ *dst++ = SkPackRGB16(SkAlphaBlend(sr, SkGetPackedR16(d), scale),
+ SkAlphaBlend(sg, SkGetPackedG16(d), scale),
+ SkAlphaBlend(sb, SkGetPackedB16(d), scale));
+ DITHER_INC_X(x);
+ count--;
+ }
+}
+
+void S32A_Opaque_BlitRow32_neon(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+
+ SkASSERT(255 == alpha);
+ if (count > 0) {
+
+
+ uint8x8_t alpha_mask;
+
+ static const uint8_t alpha_mask_setup[] = {3,3,3,3,7,7,7,7};
+ alpha_mask = vld1_u8(alpha_mask_setup);
+
+ /* do the NEON unrolled code */
+#define UNROLL 4
+ while (count >= UNROLL) {
+ uint8x8_t src_raw, dst_raw, dst_final;
+ uint8x8_t src_raw_2, dst_raw_2, dst_final_2;
+
+ /* The two prefetches below may make the code slighlty
+ * slower for small values of count but are worth having
+ * in the general case.
+ */
+ __builtin_prefetch(src+32);
+ __builtin_prefetch(dst+32);
+
+ /* get the source */
+ src_raw = vreinterpret_u8_u32(vld1_u32(src));
+#if UNROLL > 2
+ src_raw_2 = vreinterpret_u8_u32(vld1_u32(src+2));
+#endif
+
+ /* get and hold the dst too */
+ dst_raw = vreinterpret_u8_u32(vld1_u32(dst));
+#if UNROLL > 2
+ dst_raw_2 = vreinterpret_u8_u32(vld1_u32(dst+2));
+#endif
+
+ /* 1st and 2nd bits of the unrolling */
+ {
+ uint8x8_t dst_cooked;
+ uint16x8_t dst_wide;
+ uint8x8_t alpha_narrow;
+ uint16x8_t alpha_wide;
+
+ /* get the alphas spread out properly */
+ alpha_narrow = vtbl1_u8(src_raw, alpha_mask);
+ alpha_wide = vsubw_u8(vdupq_n_u16(256), alpha_narrow);
+
+ /* spread the dest */
+ dst_wide = vmovl_u8(dst_raw);
+
+ /* alpha mul the dest */
+ dst_wide = vmulq_u16 (dst_wide, alpha_wide);
+ dst_cooked = vshrn_n_u16(dst_wide, 8);
+
+ /* sum -- ignoring any byte lane overflows */
+ dst_final = vadd_u8(src_raw, dst_cooked);
+ }
+
+#if UNROLL > 2
+ /* the 3rd and 4th bits of our unrolling */
+ {
+ uint8x8_t dst_cooked;
+ uint16x8_t dst_wide;
+ uint8x8_t alpha_narrow;
+ uint16x8_t alpha_wide;
+
+ alpha_narrow = vtbl1_u8(src_raw_2, alpha_mask);
+ alpha_wide = vsubw_u8(vdupq_n_u16(256), alpha_narrow);
+
+ /* spread the dest */
+ dst_wide = vmovl_u8(dst_raw_2);
+
+ /* alpha mul the dest */
+ dst_wide = vmulq_u16 (dst_wide, alpha_wide);
+ dst_cooked = vshrn_n_u16(dst_wide, 8);
+
+ /* sum -- ignoring any byte lane overflows */
+ dst_final_2 = vadd_u8(src_raw_2, dst_cooked);
+ }
+#endif
+
+ vst1_u32(dst, vreinterpret_u32_u8(dst_final));
+#if UNROLL > 2
+ vst1_u32(dst+2, vreinterpret_u32_u8(dst_final_2));
+#endif
+
+ src += UNROLL;
+ dst += UNROLL;
+ count -= UNROLL;
+ }
+#undef UNROLL
+
+ /* do any residual iterations */
+ while (--count >= 0) {
+ *dst = SkPMSrcOver(*src, *dst);
+ src += 1;
+ dst += 1;
+ }
+ }
+}
+
+void S32A_Opaque_BlitRow32_neon_src_alpha(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(255 == alpha);
+
+ if (count <= 0)
+ return;
+
+ /* Use these to check if src is transparent or opaque */
+ const unsigned int ALPHA_OPAQ = 0xFF000000;
+ const unsigned int ALPHA_TRANS = 0x00FFFFFF;
+
+#define UNROLL 4
+ const SkPMColor* SK_RESTRICT src_end = src + count - (UNROLL + 1);
+ const SkPMColor* SK_RESTRICT src_temp = src;
+
+ /* set up the NEON variables */
+ uint8x8_t alpha_mask;
+ static const uint8_t alpha_mask_setup[] = {3,3,3,3,7,7,7,7};
+ alpha_mask = vld1_u8(alpha_mask_setup);
+
+ uint8x8_t src_raw, dst_raw, dst_final;
+ uint8x8_t src_raw_2, dst_raw_2, dst_final_2;
+ uint8x8_t dst_cooked;
+ uint16x8_t dst_wide;
+ uint8x8_t alpha_narrow;
+ uint16x8_t alpha_wide;
+
+ /* choose the first processing type */
+ if( src >= src_end)
+ goto TAIL;
+ if(*src <= ALPHA_TRANS)
+ goto ALPHA_0;
+ if(*src >= ALPHA_OPAQ)
+ goto ALPHA_255;
+ /* fall-thru */
+
+ALPHA_1_TO_254:
+ do {
+
+ /* get the source */
+ src_raw = vreinterpret_u8_u32(vld1_u32(src));
+ src_raw_2 = vreinterpret_u8_u32(vld1_u32(src+2));
+
+ /* get and hold the dst too */
+ dst_raw = vreinterpret_u8_u32(vld1_u32(dst));
+ dst_raw_2 = vreinterpret_u8_u32(vld1_u32(dst+2));
+
+
+ /* get the alphas spread out properly */
+ alpha_narrow = vtbl1_u8(src_raw, alpha_mask);
+ /* reflect SkAlpha255To256() semantics a+1 vs a+a>>7 */
+ /* we collapsed (255-a)+1 ... */
+ alpha_wide = vsubw_u8(vdupq_n_u16(256), alpha_narrow);
+
+ /* spread the dest */
+ dst_wide = vmovl_u8(dst_raw);
+
+ /* alpha mul the dest */
+ dst_wide = vmulq_u16 (dst_wide, alpha_wide);
+ dst_cooked = vshrn_n_u16(dst_wide, 8);
+
+ /* sum -- ignoring any byte lane overflows */
+ dst_final = vadd_u8(src_raw, dst_cooked);
+
+ alpha_narrow = vtbl1_u8(src_raw_2, alpha_mask);
+ /* reflect SkAlpha255To256() semantics a+1 vs a+a>>7 */
+ /* we collapsed (255-a)+1 ... */
+ alpha_wide = vsubw_u8(vdupq_n_u16(256), alpha_narrow);
+
+ /* spread the dest */
+ dst_wide = vmovl_u8(dst_raw_2);
+
+ /* alpha mul the dest */
+ dst_wide = vmulq_u16 (dst_wide, alpha_wide);
+ dst_cooked = vshrn_n_u16(dst_wide, 8);
+
+ /* sum -- ignoring any byte lane overflows */
+ dst_final_2 = vadd_u8(src_raw_2, dst_cooked);
+
+ vst1_u32(dst, vreinterpret_u32_u8(dst_final));
+ vst1_u32(dst+2, vreinterpret_u32_u8(dst_final_2));
+
+ src += UNROLL;
+ dst += UNROLL;
+
+ /* if 2 of the next pixels aren't between 1 and 254
+ it might make sense to go to the optimized loops */
+ if((src[0] <= ALPHA_TRANS && src[1] <= ALPHA_TRANS) || (src[0] >= ALPHA_OPAQ && src[1] >= ALPHA_OPAQ))
+ break;
+
+ } while(src < src_end);
+
+ if (src >= src_end)
+ goto TAIL;
+
+ if(src[0] >= ALPHA_OPAQ && src[1] >= ALPHA_OPAQ)
+ goto ALPHA_255;
+
+ /*fall-thru*/
+
+ALPHA_0:
+
+ /*In this state, we know the current alpha is 0 and
+ we optimize for the next alpha also being zero. */
+ src_temp = src; //so we don't have to increment dst every time
+ do {
+ if(*(++src) > ALPHA_TRANS)
+ break;
+ if(*(++src) > ALPHA_TRANS)
+ break;
+ if(*(++src) > ALPHA_TRANS)
+ break;
+ if(*(++src) > ALPHA_TRANS)
+ break;
+ } while(src < src_end);
+
+ dst += (src - src_temp);
+
+ /* no longer alpha 0, so determine where to go next. */
+ if( src >= src_end)
+ goto TAIL;
+ if(*src >= ALPHA_OPAQ)
+ goto ALPHA_255;
+ else
+ goto ALPHA_1_TO_254;
+
+ALPHA_255:
+ while((src[0] & src[1] & src[2] & src[3]) >= ALPHA_OPAQ) {
+ dst[0]=src[0];
+ dst[1]=src[1];
+ dst[2]=src[2];
+ dst[3]=src[3];
+ src+=UNROLL;
+ dst+=UNROLL;
+ if(src >= src_end)
+ goto TAIL;
+ }
+
+ //Handle remainder.
+ if(*src >= ALPHA_OPAQ) { *dst++ = *src++;
+ if(*src >= ALPHA_OPAQ) { *dst++ = *src++;
+ if(*src >= ALPHA_OPAQ) { *dst++ = *src++; }
+ }
+ }
+
+ if( src >= src_end)
+ goto TAIL;
+ if(*src <= ALPHA_TRANS)
+ goto ALPHA_0;
+ else
+ goto ALPHA_1_TO_254;
+
+TAIL:
+ /* do any residual iterations */
+ src_end += UNROLL + 1; //goto the real end
+ while(src != src_end) {
+ if( *src != 0 ) {
+ if( *src >= ALPHA_OPAQ ) {
+ *dst = *src;
+ }
+ else {
+ *dst = SkPMSrcOver(*src, *dst);
+ }
+ }
+ src++;
+ dst++;
+ }
+
+#undef UNROLL
+ return;
+}
+
+/* Neon version of S32_Blend_BlitRow32()
+ * portable version is in src/core/SkBlitRow_D32.cpp
+ */
+void S32_Blend_BlitRow32_neon(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+ SkASSERT(alpha <= 255);
+ if (count > 0) {
+ uint16_t src_scale = SkAlpha255To256(alpha);
+ uint16_t dst_scale = 256 - src_scale;
+
+ /* run them N at a time through the NEON unit */
+ /* note that each 1 is 4 bytes, each treated exactly the same,
+ * so we can work under that guise. We *do* know that the src&dst
+ * will be 32-bit aligned quantities, so we can specify that on
+ * the load/store ops and do a neon 'reinterpret' to get us to
+ * byte-sized (pun intended) pieces that we widen/multiply/shift
+ * we're limited at 128 bits in the wide ops, which is 8x16bits
+ * or a pair of 32 bit src/dsts.
+ */
+ /* we *could* manually unroll this loop so that we load 128 bits
+ * (as a pair of 64s) from each of src and dst, processing them
+ * in pieces. This might give us a little better management of
+ * the memory latency, but my initial attempts here did not
+ * produce an instruction stream that looked all that nice.
+ */
+#define UNROLL 2
+ while (count >= UNROLL) {
+ uint8x8_t src_raw, dst_raw, dst_final;
+ uint16x8_t src_wide, dst_wide;
+
+ /* get 64 bits of src, widen it, multiply by src_scale */
+ src_raw = vreinterpret_u8_u32(vld1_u32(src));
+ src_wide = vmovl_u8(src_raw);
+ /* gcc hoists vdupq_n_u16(), better than using vmulq_n_u16() */
+ src_wide = vmulq_u16 (src_wide, vdupq_n_u16(src_scale));
+
+ /* ditto with dst */
+ dst_raw = vreinterpret_u8_u32(vld1_u32(dst));
+ dst_wide = vmovl_u8(dst_raw);
+
+ /* combine add with dst multiply into mul-accumulate */
+ dst_wide = vmlaq_u16(src_wide, dst_wide, vdupq_n_u16(dst_scale));
+
+ dst_final = vshrn_n_u16(dst_wide, 8);
+ vst1_u32(dst, vreinterpret_u32_u8(dst_final));
+
+ src += UNROLL;
+ dst += UNROLL;
+ count -= UNROLL;
+ }
+ /* RBE: well, i don't like how gcc manages src/dst across the above
+ * loop it's constantly calculating src+bias, dst+bias and it only
+ * adjusts the real ones when we leave the loop. Not sure why
+ * it's "hoisting down" (hoisting implies above in my lexicon ;))
+ * the adjustments to src/dst/count, but it does...
+ * (might be SSA-style internal logic...
+ */
+
+#if UNROLL == 2
+ if (count == 1) {
+ *dst = SkAlphaMulQ(*src, src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ }
+#else
+ if (count > 0) {
+ do {
+ *dst = SkAlphaMulQ(*src, src_scale) + SkAlphaMulQ(*dst, dst_scale);
+ src += 1;
+ dst += 1;
+ } while (--count > 0);
+ }
+#endif
+
+#undef UNROLL
+ }
+}
+
+void S32A_Blend_BlitRow32_neon(SkPMColor* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha) {
+
+ SkASSERT(255 >= alpha);
+
+ if (count <= 0) {
+ return;
+ }
+
+ unsigned alpha256 = SkAlpha255To256(alpha);
+
+ // First deal with odd counts
+ if (count & 1) {
+ uint8x8_t vsrc = vdup_n_u8(0), vdst = vdup_n_u8(0), vres;
+ uint16x8_t vdst_wide, vsrc_wide;
+ unsigned dst_scale;
+
+ // Load
+ vsrc = vreinterpret_u8_u32(vld1_lane_u32(src, vreinterpret_u32_u8(vsrc), 0));
+ vdst = vreinterpret_u8_u32(vld1_lane_u32(dst, vreinterpret_u32_u8(vdst), 0));
+
+ // Calc dst_scale
+ dst_scale = vget_lane_u8(vsrc, 3);
+ dst_scale *= alpha256;
+ dst_scale >>= 8;
+ dst_scale = 256 - dst_scale;
+
+ // Process src
+ vsrc_wide = vmovl_u8(vsrc);
+ vsrc_wide = vmulq_n_u16(vsrc_wide, alpha256);
+
+ // Process dst
+ vdst_wide = vmovl_u8(vdst);
+ vdst_wide = vmulq_n_u16(vdst_wide, dst_scale);
+
+ // Combine
+ vres = vshrn_n_u16(vdst_wide, 8) + vshrn_n_u16(vsrc_wide, 8);
+
+ vst1_lane_u32(dst, vreinterpret_u32_u8(vres), 0);
+ dst++;
+ src++;
+ count--;
+ }
+
+ if (count) {
+ uint8x8_t alpha_mask;
+ static const uint8_t alpha_mask_setup[] = {3,3,3,3,7,7,7,7};
+ alpha_mask = vld1_u8(alpha_mask_setup);
+
+ do {
+
+ uint8x8_t vsrc, vdst, vres, vsrc_alphas;
+ uint16x8_t vdst_wide, vsrc_wide, vsrc_scale, vdst_scale;
+
+ __builtin_prefetch(src+32);
+ __builtin_prefetch(dst+32);
+
+ // Load
+ vsrc = vreinterpret_u8_u32(vld1_u32(src));
+ vdst = vreinterpret_u8_u32(vld1_u32(dst));
+
+ // Prepare src_scale
+ vsrc_scale = vdupq_n_u16(alpha256);
+
+ // Calc dst_scale
+ vsrc_alphas = vtbl1_u8(vsrc, alpha_mask);
+ vdst_scale = vmovl_u8(vsrc_alphas);
+ vdst_scale *= vsrc_scale;
+ vdst_scale = vshrq_n_u16(vdst_scale, 8);
+ vdst_scale = vsubq_u16(vdupq_n_u16(256), vdst_scale);
+
+ // Process src
+ vsrc_wide = vmovl_u8(vsrc);
+ vsrc_wide *= vsrc_scale;
+
+ // Process dst
+ vdst_wide = vmovl_u8(vdst);
+ vdst_wide *= vdst_scale;
+
+ // Combine
+ vres = vshrn_n_u16(vdst_wide, 8) + vshrn_n_u16(vsrc_wide, 8);
+
+ vst1_u32(dst, vreinterpret_u32_u8(vres));
+
+ src += 2;
+ dst += 2;
+ count -= 2;
+ } while(count);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef DEBUG_OPAQUE_DITHER
+
+#if defined(DEBUG_OPAQUE_DITHER)
+static void showme8(char *str, void *p, int len)
+{
+ static char buf[256];
+ char tbuf[32];
+ int i;
+ char *pc = (char*) p;
+ sprintf(buf,"%8s:", str);
+ for(i=0;i<len;i++) {
+ sprintf(tbuf, " %02x", pc[i]);
+ strcat(buf, tbuf);
+ }
+ SkDebugf("%s\n", buf);
+}
+static void showme16(char *str, void *p, int len)
+{
+ static char buf[256];
+ char tbuf[32];
+ int i;
+ uint16_t *pc = (uint16_t*) p;
+ sprintf(buf,"%8s:", str);
+ len = (len / sizeof(uint16_t)); /* passed as bytes */
+ for(i=0;i<len;i++) {
+ sprintf(tbuf, " %04x", pc[i]);
+ strcat(buf, tbuf);
+ }
+ SkDebugf("%s\n", buf);
+}
+#endif
+
+void S32A_D565_Opaque_Dither_neon (uint16_t * SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 == alpha);
+
+#define UNROLL 8
+
+ if (count >= UNROLL) {
+ uint8x8_t dbase;
+
+#if defined(DEBUG_OPAQUE_DITHER)
+ uint16_t tmpbuf[UNROLL];
+ int td[UNROLL];
+ int tdv[UNROLL];
+ int ta[UNROLL];
+ int tap[UNROLL];
+ uint16_t in_dst[UNROLL];
+ int offset = 0;
+ int noisy = 0;
+#endif
+
+ const uint8_t *dstart = &gDitherMatrix_Neon[(y&3)*12 + (x&3)];
+ dbase = vld1_u8(dstart);
+
+ do {
+ uint8x8_t sr, sg, sb, sa, d;
+ uint16x8_t dst8, scale8, alpha8;
+ uint16x8_t dst_r, dst_g, dst_b;
+
+#if defined(DEBUG_OPAQUE_DITHER)
+ /* calculate 8 elements worth into a temp buffer */
+ {
+ int my_y = y;
+ int my_x = x;
+ SkPMColor* my_src = (SkPMColor*)src;
+ uint16_t* my_dst = dst;
+ int i;
+
+ DITHER_565_SCAN(my_y);
+ for(i=0;i<UNROLL;i++) {
+ SkPMColor c = *my_src++;
+ SkPMColorAssert(c);
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+
+ int d = SkAlphaMul(DITHER_VALUE(my_x), SkAlpha255To256(a));
+ tdv[i] = DITHER_VALUE(my_x);
+ ta[i] = a;
+ tap[i] = SkAlpha255To256(a);
+ td[i] = d;
+
+ unsigned sr = SkGetPackedR32(c);
+ unsigned sg = SkGetPackedG32(c);
+ unsigned sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32_FOR_565(sr, d);
+ sg = SkDITHER_G32_FOR_565(sg, d);
+ sb = SkDITHER_B32_FOR_565(sb, d);
+
+ uint32_t src_expanded = (sg << 24) | (sr << 13) | (sb << 2);
+ uint32_t dst_expanded = SkExpand_rgb_16(*my_dst);
+ dst_expanded = dst_expanded * (SkAlpha255To256(255 - a) >> 3);
+ // now src and dst expanded are in g:11 r:10 x:1 b:10
+ tmpbuf[i] = SkCompact_rgb_16((src_expanded + dst_expanded) >> 5);
+ td[i] = d;
+
+ } else {
+ tmpbuf[i] = *my_dst;
+ ta[i] = tdv[i] = td[i] = 0xbeef;
+ }
+ in_dst[i] = *my_dst;
+ my_dst += 1;
+ DITHER_INC_X(my_x);
+ }
+ }
+#endif
+
+ /* source is in ABGR */
+ {
+ register uint8x8_t d0 asm("d0");
+ register uint8x8_t d1 asm("d1");
+ register uint8x8_t d2 asm("d2");
+ register uint8x8_t d3 asm("d3");
+
+ asm ("vld4.8 {d0-d3},[%4] /* r=%P0 g=%P1 b=%P2 a=%P3 */"
+ : "=w" (d0), "=w" (d1), "=w" (d2), "=w" (d3)
+ : "r" (src)
+ );
+ sr = d0; sg = d1; sb = d2; sa = d3;
+ }
+
+ /* calculate 'd', which will be 0..7 */
+ /* dbase[] is 0..7; alpha is 0..256; 16 bits suffice */
+#if defined(SK_BUILD_FOR_ANDROID)
+ /* SkAlpha255To256() semantic a+1 vs a+a>>7 */
+ alpha8 = vaddw_u8(vmovl_u8(sa), vdup_n_u8(1));
+#else
+ alpha8 = vaddw_u8(vmovl_u8(sa), vshr_n_u8(sa, 7));
+#endif
+ alpha8 = vmulq_u16(alpha8, vmovl_u8(dbase));
+ d = vshrn_n_u16(alpha8, 8); /* narrowing too */
+
+ /* sr = sr - (sr>>5) + d */
+ /* watching for 8-bit overflow. d is 0..7; risky range of
+ * sr is >248; and then (sr>>5) is 7 so it offsets 'd';
+ * safe as long as we do ((sr-sr>>5) + d) */
+ sr = vsub_u8(sr, vshr_n_u8(sr, 5));
+ sr = vadd_u8(sr, d);
+
+ /* sb = sb - (sb>>5) + d */
+ sb = vsub_u8(sb, vshr_n_u8(sb, 5));
+ sb = vadd_u8(sb, d);
+
+ /* sg = sg - (sg>>6) + d>>1; similar logic for overflows */
+ sg = vsub_u8(sg, vshr_n_u8(sg, 6));
+ sg = vadd_u8(sg, vshr_n_u8(d,1));
+
+ /* need to pick up 8 dst's -- at 16 bits each, 128 bits */
+ dst8 = vld1q_u16(dst);
+ dst_b = vandq_u16(dst8, vdupq_n_u16(0x001F));
+ dst_g = vandq_u16(vshrq_n_u16(dst8,5), vdupq_n_u16(0x003F));
+ dst_r = vshrq_n_u16(dst8,11); /* clearing hi bits */
+
+ /* blend */
+#if 1
+ /* SkAlpha255To256() semantic a+1 vs a+a>>7 */
+ /* originally 255-sa + 1 */
+ scale8 = vsubw_u8(vdupq_n_u16(256), sa);
+#else
+ scale8 = vsubw_u8(vdupq_n_u16(255), sa);
+ scale8 = vaddq_u16(scale8, vshrq_n_u16(scale8, 7));
+#endif
+
+#if 1
+ /* combine the addq and mul, save 3 insns */
+ scale8 = vshrq_n_u16(scale8, 3);
+ dst_b = vmlaq_u16(vshll_n_u8(sb,2), dst_b, scale8);
+ dst_g = vmlaq_u16(vshll_n_u8(sg,3), dst_g, scale8);
+ dst_r = vmlaq_u16(vshll_n_u8(sr,2), dst_r, scale8);
+#else
+ /* known correct, but +3 insns over above */
+ scale8 = vshrq_n_u16(scale8, 3);
+ dst_b = vmulq_u16(dst_b, scale8);
+ dst_g = vmulq_u16(dst_g, scale8);
+ dst_r = vmulq_u16(dst_r, scale8);
+
+ /* combine */
+ /* NB: vshll widens, need to preserve those bits */
+ dst_b = vaddq_u16(dst_b, vshll_n_u8(sb,2));
+ dst_g = vaddq_u16(dst_g, vshll_n_u8(sg,3));
+ dst_r = vaddq_u16(dst_r, vshll_n_u8(sr,2));
+#endif
+
+ /* repack to store */
+ dst8 = vandq_u16(vshrq_n_u16(dst_b, 5), vdupq_n_u16(0x001F));
+ dst8 = vsliq_n_u16(dst8, vshrq_n_u16(dst_g, 5), 5);
+ dst8 = vsliq_n_u16(dst8, vshrq_n_u16(dst_r,5), 11);
+
+ vst1q_u16(dst, dst8);
+
+#if defined(DEBUG_OPAQUE_DITHER)
+ /* verify my 8 elements match the temp buffer */
+ {
+ int i, bad=0;
+ static int invocation;
+
+ for (i=0;i<UNROLL;i++)
+ if (tmpbuf[i] != dst[i]) bad=1;
+ if (bad) {
+ SkDebugf("BAD S32A_D565_Opaque_Dither_neon(); invocation %d offset %d\n",
+ invocation, offset);
+ SkDebugf(" alpha 0x%x\n", alpha);
+ for (i=0;i<UNROLL;i++)
+ SkDebugf("%2d: %s %04x w %04x id %04x s %08x d %04x %04x %04x %04x\n",
+ i, ((tmpbuf[i] != dst[i])?"BAD":"got"),
+ dst[i], tmpbuf[i], in_dst[i], src[i], td[i], tdv[i], tap[i], ta[i]);
+
+ showme16("alpha8", &alpha8, sizeof(alpha8));
+ showme16("scale8", &scale8, sizeof(scale8));
+ showme8("d", &d, sizeof(d));
+ showme16("dst8", &dst8, sizeof(dst8));
+ showme16("dst_b", &dst_b, sizeof(dst_b));
+ showme16("dst_g", &dst_g, sizeof(dst_g));
+ showme16("dst_r", &dst_r, sizeof(dst_r));
+ showme8("sb", &sb, sizeof(sb));
+ showme8("sg", &sg, sizeof(sg));
+ showme8("sr", &sr, sizeof(sr));
+
+ /* cop out */
+ return;
+ }
+ offset += UNROLL;
+ invocation++;
+ }
+#endif
+
+ dst += UNROLL;
+ src += UNROLL;
+ count -= UNROLL;
+ /* skip x += UNROLL, since it's unchanged mod-4 */
+ } while (count >= UNROLL);
+ }
+#undef UNROLL
+
+ /* residuals */
+ if (count > 0) {
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+
+ // dither and alpha are just temporary variables to work-around
+ // an ICE in debug.
+ unsigned dither = DITHER_VALUE(x);
+ unsigned alpha = SkAlpha255To256(a);
+ int d = SkAlphaMul(dither, alpha);
+
+ unsigned sr = SkGetPackedR32(c);
+ unsigned sg = SkGetPackedG32(c);
+ unsigned sb = SkGetPackedB32(c);
+ sr = SkDITHER_R32_FOR_565(sr, d);
+ sg = SkDITHER_G32_FOR_565(sg, d);
+ sb = SkDITHER_B32_FOR_565(sb, d);
+
+ uint32_t src_expanded = (sg << 24) | (sr << 13) | (sb << 2);
+ uint32_t dst_expanded = SkExpand_rgb_16(*dst);
+ dst_expanded = dst_expanded * (SkAlpha255To256(255 - a) >> 3);
+ // now src and dst expanded are in g:11 r:10 x:1 b:10
+ *dst = SkCompact_rgb_16((src_expanded + dst_expanded) >> 5);
+ }
+ dst += 1;
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* 2009/10/27: RBE says "a work in progress"; debugging says ok;
+ * speedup untested, but ARM version is 26 insns/iteration and
+ * this NEON version is 21 insns/iteration-of-8 (2.62insns/element)
+ * which is 10x the native version; that's pure instruction counts,
+ * not accounting for any instruction or memory latencies.
+ */
+
+#undef DEBUG_S32_OPAQUE_DITHER
+
+void S32_D565_Opaque_Dither_neon(uint16_t* SK_RESTRICT dst,
+ const SkPMColor* SK_RESTRICT src,
+ int count, U8CPU alpha, int x, int y) {
+ SkASSERT(255 == alpha);
+
+#define UNROLL 8
+ if (count >= UNROLL) {
+ uint8x8_t d;
+ const uint8_t *dstart = &gDitherMatrix_Neon[(y&3)*12 + (x&3)];
+ d = vld1_u8(dstart);
+
+ while (count >= UNROLL) {
+ uint8x8_t sr, sg, sb;
+ uint16x8_t dr, dg, db;
+ uint16x8_t dst8;
+
+ /* source is in ABGR ordering (R == lsb) */
+ {
+ register uint8x8_t d0 asm("d0");
+ register uint8x8_t d1 asm("d1");
+ register uint8x8_t d2 asm("d2");
+ register uint8x8_t d3 asm("d3");
+
+ asm ("vld4.8 {d0-d3},[%4] /* r=%P0 g=%P1 b=%P2 a=%P3 */"
+ : "=w" (d0), "=w" (d1), "=w" (d2), "=w" (d3)
+ : "r" (src)
+ );
+ sr = d0; sg = d1; sb = d2;
+ }
+ /* XXX: if we want to prefetch, hide it in the above asm()
+ * using the gcc __builtin_prefetch(), the prefetch will
+ * fall to the bottom of the loop -- it won't stick up
+ * at the top of the loop, just after the vld4.
+ */
+
+ /* sr = sr - (sr>>5) + d */
+ sr = vsub_u8(sr, vshr_n_u8(sr, 5));
+ dr = vaddl_u8(sr, d);
+
+ /* sb = sb - (sb>>5) + d */
+ sb = vsub_u8(sb, vshr_n_u8(sb, 5));
+ db = vaddl_u8(sb, d);
+
+ /* sg = sg - (sg>>6) + d>>1; similar logic for overflows */
+ sg = vsub_u8(sg, vshr_n_u8(sg, 6));
+ dg = vaddl_u8(sg, vshr_n_u8(d,1));
+ /* XXX: check that the "d>>1" here is hoisted */
+
+ /* pack high bits of each into 565 format (rgb, b is lsb) */
+ dst8 = vshrq_n_u16(db, 3);
+ dst8 = vsliq_n_u16(dst8, vshrq_n_u16(dg, 2), 5);
+ dst8 = vsliq_n_u16(dst8, vshrq_n_u16(dr,3), 11);
+
+ /* store it */
+ vst1q_u16(dst, dst8);
+
+#if defined(DEBUG_S32_OPAQUE_DITHER)
+ /* always good to know if we generated good results */
+ {
+ int i, myx = x, myy = y;
+ DITHER_565_SCAN(myy);
+ for (i=0;i<UNROLL;i++) {
+ SkPMColor c = src[i];
+ unsigned dither = DITHER_VALUE(myx);
+ uint16_t val = SkDitherRGB32To565(c, dither);
+ if (val != dst[i]) {
+ SkDebugf("RBE: src %08x dither %02x, want %04x got %04x dbas[i] %02x\n",
+ c, dither, val, dst[i], dstart[i]);
+ }
+ DITHER_INC_X(myx);
+ }
+ }
+#endif
+
+ dst += UNROLL;
+ src += UNROLL;
+ count -= UNROLL;
+ x += UNROLL; /* probably superfluous */
+ }
+ }
+#undef UNROLL
+
+ /* residuals */
+ if (count > 0) {
+ DITHER_565_SCAN(y);
+ do {
+ SkPMColor c = *src++;
+ SkPMColorAssert(c);
+ SkASSERT(SkGetPackedA32(c) == 255);
+
+ unsigned dither = DITHER_VALUE(x);
+ *dst++ = SkDitherRGB32To565(c, dither);
+ DITHER_INC_X(x);
+ } while (--count != 0);
+ }
+}
+
+void Color32_arm_neon(SkPMColor* dst, const SkPMColor* src, int count,
+ SkPMColor color) {
+ if (count <= 0) {
+ return;
+ }
+
+ if (0 == color) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(SkPMColor));
+ }
+ return;
+ }
+
+ unsigned colorA = SkGetPackedA32(color);
+ if (255 == colorA) {
+ sk_memset32(dst, color, count);
+ } else {
+ unsigned scale = 256 - SkAlpha255To256(colorA);
+
+ if (count >= 8) {
+ // at the end of this assembly, count will have been decremented
+ // to a negative value. That is, if count mod 8 = x, it will be
+ // -8 +x coming out.
+ asm volatile (
+ PLD128(src, 0)
+
+ "vdup.32 q0, %[color] \n\t"
+
+ PLD128(src, 128)
+
+ // scale numerical interval [0-255], so load as 8 bits
+ "vdup.8 d2, %[scale] \n\t"
+
+ PLD128(src, 256)
+
+ "subs %[count], %[count], #8 \n\t"
+
+ PLD128(src, 384)
+
+ "Loop_Color32: \n\t"
+
+ // load src color, 8 pixels, 4 64 bit registers
+ // (and increment src).
+ "vld1.32 {d4-d7}, [%[src]]! \n\t"
+
+ PLD128(src, 384)
+
+ // multiply long by scale, 64 bits at a time,
+ // destination into a 128 bit register.
+ "vmull.u8 q4, d4, d2 \n\t"
+ "vmull.u8 q5, d5, d2 \n\t"
+ "vmull.u8 q6, d6, d2 \n\t"
+ "vmull.u8 q7, d7, d2 \n\t"
+
+ // shift the 128 bit registers, containing the 16
+ // bit scaled values back to 8 bits, narrowing the
+ // results to 64 bit registers.
+ "vshrn.i16 d8, q4, #8 \n\t"
+ "vshrn.i16 d9, q5, #8 \n\t"
+ "vshrn.i16 d10, q6, #8 \n\t"
+ "vshrn.i16 d11, q7, #8 \n\t"
+
+ // adding back the color, using 128 bit registers.
+ "vadd.i8 q6, q4, q0 \n\t"
+ "vadd.i8 q7, q5, q0 \n\t"
+
+ // store back the 8 calculated pixels (2 128 bit
+ // registers), and increment dst.
+ "vst1.32 {d12-d15}, [%[dst]]! \n\t"
+
+ "subs %[count], %[count], #8 \n\t"
+ "bge Loop_Color32 \n\t"
+ : [src] "+r" (src), [dst] "+r" (dst), [count] "+r" (count)
+ : [color] "r" (color), [scale] "r" (scale)
+ : "cc", "memory",
+ "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
+ "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15"
+ );
+ // At this point, if we went through the inline assembly, count is
+ // a negative value:
+ // if the value is -8, there is no pixel left to process.
+ // if the value is -7, there is one pixel left to process
+ // ...
+ // And'ing it with 7 will give us the number of pixels
+ // left to process.
+ count = count & 0x7;
+ }
+
+ while (count > 0) {
+ *dst = color + SkAlphaMulQ(*src, scale);
+ src += 1;
+ dst += 1;
+ count--;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkBlitRow::Proc sk_blitrow_platform_565_procs_arm_neon[] = {
+ // no dither
+ // NOTE: For the two functions below, we don't have a special version
+ // that assumes that each source pixel is opaque. But our S32A is
+ // still faster than the default, so use it.
+ S32A_D565_Opaque_neon, // really S32_D565_Opaque
+ S32A_D565_Blend_neon, // really S32_D565_Blend
+ S32A_D565_Opaque_neon,
+ S32A_D565_Blend_neon,
+
+ // dither
+ S32_D565_Opaque_Dither_neon,
+ S32_D565_Blend_Dither_neon,
+ S32A_D565_Opaque_Dither_neon,
+ NULL, // S32A_D565_Blend_Dither
+};
+
+const SkBlitRow::Proc32 sk_blitrow_platform_32_procs_arm_neon[] = {
+ NULL, // S32_Opaque,
+ S32_Blend_BlitRow32_neon, // S32_Blend,
+ /*
+ * We have two choices for S32A_Opaque procs. The one reads the src alpha
+ * value and attempts to optimize accordingly. The optimization is
+ * sensitive to the source content and is not a win in all cases. For
+ * example, if there are a lot of transitions between the alpha states,
+ * the performance will almost certainly be worse. However, for many
+ * common cases the performance is equivalent or better than the standard
+ * case where we do not inspect the src alpha.
+ */
+#if SK_A32_SHIFT == 24
+ // This proc assumes the alpha value occupies bits 24-32 of each SkPMColor
+ S32A_Opaque_BlitRow32_neon_src_alpha, // S32A_Opaque,
+#else
+ S32A_Opaque_BlitRow32_neon, // S32A_Opaque,
+#endif
+ S32A_Blend_BlitRow32_neon // S32A_Blend
+};
diff --git a/opts/SkBlitRow_opts_arm_neon.h b/opts/SkBlitRow_opts_arm_neon.h
new file mode 100644
index 00000000..80b87541
--- /dev/null
+++ b/opts/SkBlitRow_opts_arm_neon.h
@@ -0,0 +1,18 @@
+/*
+ * 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 SkBlitRow_opts_arm_neon_DEFINED
+#define SkBlitRow_opts_arm_neon_DEFINED
+
+#include "SkBlitRow.h"
+
+extern const SkBlitRow::Proc sk_blitrow_platform_565_procs_arm_neon[];
+extern const SkBlitRow::Proc32 sk_blitrow_platform_32_procs_arm_neon[];
+
+extern void Color32_arm_neon(SkPMColor* dst, const SkPMColor* src, int count,
+ SkPMColor color);
+
+#endif
diff --git a/opts/SkBlitRow_opts_none.cpp b/opts/SkBlitRow_opts_none.cpp
new file mode 100644
index 00000000..0abcf591
--- /dev/null
+++ b/opts/SkBlitRow_opts_none.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 "SkBlitRow.h"
+#include "SkBlitMask.h"
+
+// Platform impl of Platform_procs with no overrides
+
+SkBlitRow::Proc SkBlitRow::PlatformProcs565(unsigned flags) {
+ return NULL;
+}
+
+SkBlitRow::Proc32 SkBlitRow::PlatformProcs32(unsigned flags) {
+ return NULL;
+}
+
+SkBlitRow::ColorProc SkBlitRow::PlatformColorProc() {
+ return NULL;
+}
+
+SkBlitRow::ColorRectProc PlatformColorRectProcFactory() {
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlitMask::ColorProc SkBlitMask::PlatformColorProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ SkColor color) {
+ return NULL;
+}
+
+SkBlitMask::BlitLCD16RowProc SkBlitMask::PlatformBlitRowProcs16(bool isOpaque) {
+ return NULL;
+}
+
+SkBlitMask::RowProc SkBlitMask::PlatformRowProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ RowFlags flags) {
+ return NULL;
+}
diff --git a/opts/SkCachePreload_arm.h b/opts/SkCachePreload_arm.h
new file mode 100644
index 00000000..cff8c2a9
--- /dev/null
+++ b/opts/SkCachePreload_arm.h
@@ -0,0 +1,34 @@
+/*
+ * 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 SkCachePreload_arm_DEFINED
+#define SkCachePreload_arm_DEFINED
+
+// This file defines macros for preload instructions for ARM. These macros
+// are designed to be embedded inside GNU inline assembly.
+// For the use of these macros, __ARM_USE_PLD needs to be enabled. The cache
+// line size also needs to be known (and needs to be contained inside
+// __ARM_CACHE_LINE_SIZE).
+#if defined(__ARM_USE_PLD)
+
+#define PLD(x, n) "pld [%["#x"], #("#n")]\n\t"
+
+#if __ARM_CACHE_LINE_SIZE == 32
+ #define PLD64(x, n) PLD(x, n) PLD(x, (n) + 32)
+#elif __ARM_CACHE_LINE_SIZE == 64
+ #define PLD64(x, n) PLD(x, n)
+#else
+ #error "unknown __ARM_CACHE_LINE_SIZE."
+#endif
+#else
+ // PLD is disabled, all macros become empty.
+ #define PLD(x, n)
+ #define PLD64(x, n)
+#endif
+
+#define PLD128(x, n) PLD64(x, n) PLD64(x, (n) + 64)
+
+#endif // SkCachePreload_arm_DEFINED
diff --git a/opts/SkUtils_opts_SSE2.cpp b/opts/SkUtils_opts_SSE2.cpp
new file mode 100644
index 00000000..e22044d3
--- /dev/null
+++ b/opts/SkUtils_opts_SSE2.cpp
@@ -0,0 +1,71 @@
+
+/*
+ * Copyright 2009 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 <emmintrin.h>
+#include "SkUtils_opts_SSE2.h"
+
+void sk_memset16_SSE2(uint16_t *dst, uint16_t value, int count)
+{
+ SkASSERT(dst != NULL && count >= 0);
+
+ // dst must be 2-byte aligned.
+ SkASSERT((((size_t) dst) & 0x01) == 0);
+
+ if (count >= 32) {
+ while (((size_t)dst) & 0x0F) {
+ *dst++ = value;
+ --count;
+ }
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i value_wide = _mm_set1_epi16(value);
+ while (count >= 32) {
+ _mm_store_si128(d , value_wide);
+ _mm_store_si128(d + 1, value_wide);
+ _mm_store_si128(d + 2, value_wide);
+ _mm_store_si128(d + 3, value_wide);
+ d += 4;
+ count -= 32;
+ }
+ dst = reinterpret_cast<uint16_t*>(d);
+ }
+ while (count > 0) {
+ *dst++ = value;
+ --count;
+ }
+}
+
+void sk_memset32_SSE2(uint32_t *dst, uint32_t value, int count)
+{
+ SkASSERT(dst != NULL && count >= 0);
+
+ // dst must be 4-byte aligned.
+ SkASSERT((((size_t) dst) & 0x03) == 0);
+
+ if (count >= 16) {
+ while (((size_t)dst) & 0x0F) {
+ *dst++ = value;
+ --count;
+ }
+ __m128i *d = reinterpret_cast<__m128i*>(dst);
+ __m128i value_wide = _mm_set1_epi32(value);
+ while (count >= 16) {
+ _mm_store_si128(d , value_wide);
+ _mm_store_si128(d + 1, value_wide);
+ _mm_store_si128(d + 2, value_wide);
+ _mm_store_si128(d + 3, value_wide);
+ d += 4;
+ count -= 16;
+ }
+ dst = reinterpret_cast<uint32_t*>(d);
+ }
+ while (count > 0) {
+ *dst++ = value;
+ --count;
+ }
+}
diff --git a/opts/SkUtils_opts_SSE2.h b/opts/SkUtils_opts_SSE2.h
new file mode 100644
index 00000000..ed24c1ff
--- /dev/null
+++ b/opts/SkUtils_opts_SSE2.h
@@ -0,0 +1,13 @@
+
+/*
+ * Copyright 2009 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 "SkTypes.h"
+
+void sk_memset16_SSE2(uint16_t *dst, uint16_t value, int count);
+void sk_memset32_SSE2(uint32_t *dst, uint32_t value, int count);
diff --git a/opts/SkUtils_opts_none.cpp b/opts/SkUtils_opts_none.cpp
new file mode 100644
index 00000000..286f10d7
--- /dev/null
+++ b/opts/SkUtils_opts_none.cpp
@@ -0,0 +1,18 @@
+
+/*
+ * Copyright 2009 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 "SkUtils.h"
+
+SkMemset16Proc SkMemset16GetPlatformProc() {
+ return NULL;
+}
+
+SkMemset32Proc SkMemset32GetPlatformProc() {
+ return NULL;
+}
diff --git a/opts/memset.arm.S b/opts/memset.arm.S
new file mode 100644
index 00000000..44b75e3d
--- /dev/null
+++ b/opts/memset.arm.S
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/* Changes:
+ * 2010-08-11 Steve McIntyre <steve.mcintyre@arm.com>
+ * Added small changes to the two functions to make them work on the
+ * specified number of 16- or 32-bit values rather than the original
+ * code which was specified as a count of bytes. More verbose comments
+ * to aid future maintenance.
+ */
+
+ .text
+ .align 4
+ .syntax unified
+
+ .global arm_memset32
+ .type arm_memset32, %function
+ .global arm_memset16
+ .type arm_memset16, %function
+
+/*
+ * Optimized memset functions for ARM.
+ *
+ * void arm_memset16(uint16_t* dst, uint16_t value, int count);
+ * void arm_memset32(uint32_t* dst, uint32_t value, int count);
+ *
+ */
+arm_memset16:
+ .fnstart
+ push {lr}
+
+ /* if count is equal to zero then abort */
+ teq r2, #0
+ ble .Lfinish
+
+ /* Multiply count by 2 - go from the number of 16-bit shorts
+ * to the number of bytes desired. */
+ mov r2, r2, lsl #1
+
+ /* expand the data to 32 bits */
+ orr r1, r1, r1, lsl #16
+
+ /* align to 32 bits */
+ tst r0, #2
+ strhne r1, [r0], #2
+ subne r2, r2, #2
+
+ /* Now jump into the main loop below. */
+ b .Lwork_32
+ .fnend
+
+arm_memset32:
+ .fnstart
+ push {lr}
+
+ /* if count is equal to zero then abort */
+ teq r2, #0
+ ble .Lfinish
+
+ /* Multiply count by 4 - go from the number of 32-bit words to
+ * the number of bytes desired. */
+ mov r2, r2, lsl #2
+
+.Lwork_32:
+ /* Set up registers ready for writing them out. */
+ mov ip, r1
+ mov lr, r1
+
+ /* Try to align the destination to a cache line. Assume 32
+ * byte (8 word) cache lines, it's the common case. */
+ rsb r3, r0, #0
+ ands r3, r3, #0x1C
+ beq .Laligned32
+ cmp r3, r2
+ andhi r3, r2, #0x1C
+ sub r2, r2, r3
+
+ /* (Optionally) write any unaligned leading bytes.
+ * (0-28 bytes, length in r3) */
+ movs r3, r3, lsl #28
+ stmiacs r0!, {r1, lr}
+ stmiacs r0!, {r1, lr}
+ stmiami r0!, {r1, lr}
+ movs r3, r3, lsl #2
+ strcs r1, [r0], #4
+
+ /* Now quickly loop through the cache-aligned data. */
+.Laligned32:
+ mov r3, r1
+1: subs r2, r2, #32
+ stmiahs r0!, {r1,r3,ip,lr}
+ stmiahs r0!, {r1,r3,ip,lr}
+ bhs 1b
+ add r2, r2, #32
+
+ /* (Optionally) store any remaining trailing bytes.
+ * (0-30 bytes, length in r2) */
+ movs r2, r2, lsl #28
+ stmiacs r0!, {r1,r3,ip,lr}
+ stmiami r0!, {r1,lr}
+ movs r2, r2, lsl #2
+ strcs r1, [r0], #4
+ strhmi lr, [r0], #2
+
+.Lfinish:
+ pop {pc}
+ .fnend
diff --git a/opts/memset16_neon.S b/opts/memset16_neon.S
new file mode 100644
index 00000000..b1719fa1
--- /dev/null
+++ b/opts/memset16_neon.S
@@ -0,0 +1,143 @@
+/***************************************************************************
+ * Copyright (c) 2009,2010, Code Aurora Forum. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ ***************************************************************************/
+
+/***************************************************************************
+ Neon memset: Attempts to do a memset with Neon registers if possible,
+ Inputs:
+ s: The buffer to write to
+ c: The integer data to write to the buffer
+ n: The size_t count.
+ Outputs:
+
+***************************************************************************/
+
+ .code 32
+ .fpu neon
+ .align 4
+ .globl memset16_neon
+ .func
+
+memset16_neon:
+ cmp r2, #0
+ bxeq lr
+
+ /* Keep in mind that r2 -- the count argument -- is for the
+ * number of 16-bit items to copy.
+ */
+ lsl r2, r2, #1
+
+ push {r0}
+
+ /* If we have < 8 bytes, just do a quick loop to handle that */
+ cmp r2, #8
+ bgt memset_gt4
+memset_smallcopy_loop:
+ strh r1, [r0], #2
+ subs r2, r2, #2
+ bne memset_smallcopy_loop
+memset_smallcopy_done:
+ pop {r0}
+ bx lr
+
+memset_gt4:
+ /*
+ * Duplicate the r1 lowest 16-bits across r1. The idea is to have
+ * a register with two 16-bit-values we can copy. We do this by
+ * duplicating lowest 16-bits of r1 to upper 16-bits.
+ */
+ orr r1, r1, r1, lsl #16
+ /*
+ * If we're copying > 64 bytes, then we may want to get
+ * onto a 16-byte boundary to improve speed even more.
+ */
+ cmp r2, #64
+ blt memset_route
+ ands r12, r0, #0xf
+ beq memset_route
+ /*
+ * Determine the number of bytes to move forward to get to the 16-byte
+ * boundary. Note that this will be a multiple of 4, since we
+ * already are word-aligned.
+ */
+ rsb r12, r12, #16
+ sub r2, r2, r12
+ lsls r12, r12, #29
+ strmi r1, [r0], #4
+ strcs r1, [r0], #4
+ strcs r1, [r0], #4
+ lsls r12, r12, #2
+ strcsh r1, [r0], #2
+memset_route:
+ /*
+ * Decide where to route for the maximum copy sizes. Note that we
+ * build q0 and q1 depending on if we'll need it, so that's
+ * interwoven here as well.
+ */
+ vdup.u32 d0, r1
+ cmp r2, #16
+ blt memset_8
+ vmov d1, d0
+ cmp r2, #64
+ blt memset_16
+ vmov q1, q0
+ cmp r2, #128
+ blt memset_32
+memset_128:
+ mov r12, r2, lsr #7
+memset_128_loop:
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ subs r12, r12, #1
+ bne memset_128_loop
+ ands r2, r2, #0x7f
+ beq memset_end
+memset_32:
+ movs r12, r2, lsr #5
+ beq memset_16
+memset_32_loop:
+ subs r12, r12, #1
+ vst1.64 {q0, q1}, [r0]!
+ bne memset_32_loop
+ ands r2, r2, #0x1f
+ beq memset_end
+memset_16:
+ movs r12, r2, lsr #4
+ beq memset_8
+memset_16_loop:
+ subs r12, r12, #1
+ vst1.32 {q0}, [r0]!
+ bne memset_16_loop
+ ands r2, r2, #0xf
+ beq memset_end
+ /*
+ * memset_8 isn't a loop, since we try to do our loops at 16
+ * bytes and above. We should loop there, then drop down here
+ * to finish the <16-byte versions. Same for memset_4 and
+ * memset_1.
+ */
+memset_8:
+ cmp r2, #8
+ blt memset_4
+ subs r2, r2, #8
+ vst1.32 {d0}, [r0]!
+memset_4:
+ cmp r2, #4
+ blt memset_2
+ subs r2, r2, #4
+ str r1, [r0], #4
+memset_2:
+ cmp r2, #0
+ ble memset_end
+ strh r1, [r0], #2
+memset_end:
+ pop {r0}
+ bx lr
+
+ .endfunc
+ .end
diff --git a/opts/memset32_neon.S b/opts/memset32_neon.S
new file mode 100644
index 00000000..a9eaa0e8
--- /dev/null
+++ b/opts/memset32_neon.S
@@ -0,0 +1,113 @@
+/***************************************************************************
+ * Copyright (c) 2009,2010, Code Aurora Forum. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ ***************************************************************************/
+
+ .code 32
+ .fpu neon
+ .align 4
+ .globl memset32_neon
+ .func
+
+ /* r0 = buffer, r1 = value, r2 = times to write */
+memset32_neon:
+ cmp r2, #1
+ streq r1, [r0], #4
+ bxeq lr
+
+ cmp r2, #4
+ bgt memset32_neon_start
+ cmp r2, #0
+ bxeq lr
+memset32_neon_small:
+ str r1, [r0], #4
+ subs r2, r2, #1
+ bne memset32_neon_small
+ bx lr
+memset32_neon_start:
+ cmp r2, #16
+ blt memset32_dropthru
+ vdup.32 q0, r1
+ vmov q1, q0
+ cmp r2, #32
+ blt memset32_16
+ cmp r2, #64
+ blt memset32_32
+ cmp r2, #128
+ blt memset32_64
+memset32_128:
+ movs r12, r2, lsr #7
+memset32_loop128:
+ subs r12, r12, #1
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ bne memset32_loop128
+ ands r2, r2, #0x7f
+ bxeq lr
+memset32_64:
+ movs r12, r2, lsr #6
+ beq memset32_32
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ ands r2, r2, #0x3f
+ bxeq lr
+memset32_32:
+ movs r12, r2, lsr #5
+ beq memset32_16
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+ ands r2, r2, #0x1f
+ bxeq lr
+memset32_16:
+ movs r12, r2, lsr #4
+ beq memset32_dropthru
+ and r2, r2, #0xf
+ vst1.64 {q0, q1}, [r0]!
+ vst1.64 {q0, q1}, [r0]!
+memset32_dropthru:
+ rsb r2, r2, #15
+ add pc, pc, r2, lsl #2
+ nop
+ str r1, [r0, #56]
+ str r1, [r0, #52]
+ str r1, [r0, #48]
+ str r1, [r0, #44]
+ str r1, [r0, #40]
+ str r1, [r0, #36]
+ str r1, [r0, #32]
+ str r1, [r0, #28]
+ str r1, [r0, #24]
+ str r1, [r0, #20]
+ str r1, [r0, #16]
+ str r1, [r0, #12]
+ str r1, [r0, #8]
+ str r1, [r0, #4]
+ str r1, [r0, #0]
+ bx lr
+
+ .endfunc
+ .end
diff --git a/opts/opts_check_SSE2.cpp b/opts/opts_check_SSE2.cpp
new file mode 100644
index 00000000..9b6f775d
--- /dev/null
+++ b/opts/opts_check_SSE2.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2009 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 "SkBitmapProcState_opts_SSE2.h"
+#include "SkBitmapProcState_opts_SSSE3.h"
+#include "SkBitmapFilter_opts_SSE2.h"
+#include "SkBlitMask.h"
+#include "SkBlitRow.h"
+#include "SkBlitRect_opts_SSE2.h"
+#include "SkBlitRow_opts_SSE2.h"
+#include "SkUtils_opts_SSE2.h"
+#include "SkUtils.h"
+
+#include "SkRTConf.h"
+
+#if defined(_MSC_VER) && defined(_WIN64)
+#include <intrin.h>
+#endif
+
+/* This file must *not* be compiled with -msse or -msse2, otherwise
+ gcc may generate sse2 even for scalar ops (and thus give an invalid
+ instruction on Pentium3 on the code below). Only files named *_SSE2.cpp
+ in this directory should be compiled with -msse2. */
+
+
+#ifdef _MSC_VER
+static inline void getcpuid(int info_type, int info[4]) {
+#if defined(_WIN64)
+ __cpuid(info, info_type);
+#else
+ __asm {
+ mov eax, [info_type]
+ cpuid
+ mov edi, [info]
+ mov [edi], eax
+ mov [edi+4], ebx
+ mov [edi+8], ecx
+ mov [edi+12], edx
+ }
+#endif
+}
+#else
+#if defined(__x86_64__)
+static inline void getcpuid(int info_type, int info[4]) {
+ asm volatile (
+ "cpuid \n\t"
+ : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3])
+ : "a"(info_type)
+ );
+}
+#else
+static inline void getcpuid(int info_type, int info[4]) {
+ // We save and restore ebx, so this code can be compatible with -fPIC
+ asm volatile (
+ "pushl %%ebx \n\t"
+ "cpuid \n\t"
+ "movl %%ebx, %1 \n\t"
+ "popl %%ebx \n\t"
+ : "=a"(info[0]), "=r"(info[1]), "=c"(info[2]), "=d"(info[3])
+ : "a"(info_type)
+ );
+}
+#endif
+#endif
+
+#if defined(__x86_64__) || defined(_WIN64) || SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
+/* All x86_64 machines have SSE2, or we know it's supported at compile time, so don't even bother checking. */
+static inline bool hasSSE2() {
+ return true;
+}
+#else
+
+static inline bool hasSSE2() {
+ int cpu_info[4] = { 0 };
+ getcpuid(1, cpu_info);
+ return (cpu_info[3] & (1<<26)) != 0;
+}
+#endif
+
+#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3
+/* If we know SSSE3 is supported at compile time, don't even bother checking. */
+static inline bool hasSSSE3() {
+ return true;
+}
+#else
+
+static inline bool hasSSSE3() {
+ int cpu_info[4] = { 0 };
+ getcpuid(1, cpu_info);
+ return (cpu_info[2] & 0x200) != 0;
+}
+#endif
+
+static bool cachedHasSSE2() {
+ static bool gHasSSE2 = hasSSE2();
+ return gHasSSE2;
+}
+
+static bool cachedHasSSSE3() {
+ static bool gHasSSSE3 = hasSSSE3();
+ return gHasSSSE3;
+}
+
+SK_CONF_DECLARE( bool, c_hqfilter_sse, "bitmap.filter.highQualitySSE", false, "Use SSE optimized version of high quality image filters");
+
+void SkBitmapProcState::platformConvolutionProcs() {
+ if (cachedHasSSE2()) {
+ fConvolutionProcs->fExtraHorizontalReads = 3;
+ fConvolutionProcs->fConvolveVertically = &convolveVertically_SSE2;
+ fConvolutionProcs->fConvolve4RowsHorizontally = &convolve4RowsHorizontally_SSE2;
+ fConvolutionProcs->fConvolveHorizontally = &convolveHorizontally_SSE2;
+ fConvolutionProcs->fApplySIMDPadding = &applySIMDPadding_SSE2;
+ }
+}
+
+void SkBitmapProcState::platformProcs() {
+ if (cachedHasSSSE3()) {
+ if (fSampleProc32 == S32_opaque_D32_filter_DX) {
+ fSampleProc32 = S32_opaque_D32_filter_DX_SSSE3;
+ } else if (fSampleProc32 == S32_alpha_D32_filter_DX) {
+ fSampleProc32 = S32_alpha_D32_filter_DX_SSSE3;
+ }
+
+ if (fSampleProc32 == S32_opaque_D32_filter_DXDY) {
+ fSampleProc32 = S32_opaque_D32_filter_DXDY_SSSE3;
+ } else if (fSampleProc32 == S32_alpha_D32_filter_DXDY) {
+ fSampleProc32 = S32_alpha_D32_filter_DXDY_SSSE3;
+ }
+ } else if (cachedHasSSE2()) {
+ if (fSampleProc32 == S32_opaque_D32_filter_DX) {
+ fSampleProc32 = S32_opaque_D32_filter_DX_SSE2;
+ } else if (fSampleProc32 == S32_alpha_D32_filter_DX) {
+ fSampleProc32 = S32_alpha_D32_filter_DX_SSE2;
+ }
+
+ if (fSampleProc16 == S32_D16_filter_DX) {
+ fSampleProc16 = S32_D16_filter_DX_SSE2;
+ }
+ }
+
+ if (cachedHasSSSE3() || cachedHasSSE2()) {
+ if (fMatrixProc == ClampX_ClampY_filter_scale) {
+ fMatrixProc = ClampX_ClampY_filter_scale_SSE2;
+ } else if (fMatrixProc == ClampX_ClampY_nofilter_scale) {
+ fMatrixProc = ClampX_ClampY_nofilter_scale_SSE2;
+ }
+
+ if (fMatrixProc == ClampX_ClampY_filter_affine) {
+ fMatrixProc = ClampX_ClampY_filter_affine_SSE2;
+ } else if (fMatrixProc == ClampX_ClampY_nofilter_affine) {
+ fMatrixProc = ClampX_ClampY_nofilter_affine_SSE2;
+ }
+ if (c_hqfilter_sse) {
+ if (fShaderProc32 == highQualityFilter) {
+ fShaderProc32 = highQualityFilter_SSE2;
+ }
+ }
+ }
+}
+
+static SkBlitRow::Proc32 platform_32_procs[] = {
+ NULL, // S32_Opaque,
+ S32_Blend_BlitRow32_SSE2, // S32_Blend,
+ S32A_Opaque_BlitRow32_SSE2, // S32A_Opaque
+ S32A_Blend_BlitRow32_SSE2, // S32A_Blend,
+};
+
+SkBlitRow::Proc SkBlitRow::PlatformProcs565(unsigned flags) {
+ return NULL;
+}
+
+SkBlitRow::ColorProc SkBlitRow::PlatformColorProc() {
+ if (cachedHasSSE2()) {
+ return Color32_SSE2;
+ } else {
+ return NULL;
+ }
+}
+
+SkBlitRow::Proc32 SkBlitRow::PlatformProcs32(unsigned flags) {
+ if (cachedHasSSE2()) {
+ return platform_32_procs[flags];
+ } else {
+ return NULL;
+ }
+}
+
+
+SkBlitMask::ColorProc SkBlitMask::PlatformColorProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ SkColor color) {
+ if (SkMask::kA8_Format != maskFormat) {
+ return NULL;
+ }
+
+ ColorProc proc = NULL;
+ if (cachedHasSSE2()) {
+ switch (dstConfig) {
+ case SkBitmap::kARGB_8888_Config:
+ // The SSE2 version is not (yet) faster for black, so we check
+ // for that.
+ if (SK_ColorBLACK != color) {
+ proc = SkARGB32_A8_BlitMask_SSE2;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return proc;
+}
+
+SkBlitMask::BlitLCD16RowProc SkBlitMask::PlatformBlitRowProcs16(bool isOpaque) {
+ if (cachedHasSSE2()) {
+ if (isOpaque) {
+ return SkBlitLCD16OpaqueRow_SSE2;
+ } else {
+ return SkBlitLCD16Row_SSE2;
+ }
+ } else {
+ return NULL;
+ }
+
+}
+SkBlitMask::RowProc SkBlitMask::PlatformRowProcs(SkBitmap::Config dstConfig,
+ SkMask::Format maskFormat,
+ RowFlags flags) {
+ return NULL;
+}
+
+SkMemset16Proc SkMemset16GetPlatformProc() {
+ if (cachedHasSSE2()) {
+ return sk_memset16_SSE2;
+ } else {
+ return NULL;
+ }
+}
+
+SkMemset32Proc SkMemset32GetPlatformProc() {
+ if (cachedHasSSE2()) {
+ return sk_memset32_SSE2;
+ } else {
+ return NULL;
+ }
+}
+
+SkBlitRow::ColorRectProc PlatformColorRectProcFactory(); // suppress warning
+
+SkBlitRow::ColorRectProc PlatformColorRectProcFactory() {
+ if (cachedHasSSE2()) {
+ return ColorRect32_SSE2;
+ } else {
+ return NULL;
+ }
+}
diff --git a/opts/opts_check_arm.cpp b/opts/opts_check_arm.cpp
new file mode 100644
index 00000000..ba407d71
--- /dev/null
+++ b/opts/opts_check_arm.cpp
@@ -0,0 +1,67 @@
+/***************************************************************************
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ * Copyright 2006-2010, 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.
+ ***************************************************************************/
+
+/* Changes:
+ * 2011-04-01 ARM
+ * Merged the functions from src/opts/opts_check_arm_neon.cpp
+ * Modified to return ARM version of memset16 and memset32 if no neon
+ * available in the core
+ */
+
+#include "SkBlitRow.h"
+#include "SkUtils.h"
+
+#include "SkUtilsArm.h"
+
+#if defined(SK_CPU_LENDIAN) && !SK_ARM_NEON_IS_NONE
+extern "C" void memset16_neon(uint16_t dst[], uint16_t value, int count);
+extern "C" void memset32_neon(uint32_t dst[], uint32_t value, int count);
+#endif
+
+#if defined(SK_CPU_LENDIAN)
+extern "C" void arm_memset16(uint16_t* dst, uint16_t value, int count);
+extern "C" void arm_memset32(uint32_t* dst, uint32_t value, int count);
+#endif
+
+SkMemset16Proc SkMemset16GetPlatformProc() {
+ // FIXME: memset.arm.S is using syntax incompatible with XCode
+#if !defined(SK_CPU_LENDIAN) || defined(SK_BUILD_FOR_IOS)
+ return NULL;
+#elif SK_ARM_NEON_IS_DYNAMIC
+ if (sk_cpu_arm_has_neon()) {
+ return memset16_neon;
+ } else {
+ return arm_memset16;
+ }
+#elif SK_ARM_NEON_IS_ALWAYS
+ return memset16_neon;
+#else
+ return arm_memset16;
+#endif
+}
+
+SkMemset32Proc SkMemset32GetPlatformProc() {
+ // FIXME: memset.arm.S is using syntax incompatible with XCode
+#if !defined(SK_CPU_LENDIAN) || defined(SK_BUILD_FOR_IOS)
+ return NULL;
+#elif SK_ARM_NEON_IS_DYNAMIC
+ if (sk_cpu_arm_has_neon()) {
+ return memset32_neon;
+ } else {
+ return arm_memset32;
+ }
+#elif SK_ARM_NEON_IS_ALWAYS
+ return memset32_neon;
+#else
+ return arm_memset32;
+#endif
+}
+
+SkBlitRow::ColorRectProc PlatformColorRectProcFactory() {
+ return NULL;
+}
diff --git a/pathops/SkAddIntersections.cpp b/pathops/SkAddIntersections.cpp
new file mode 100644
index 00000000..0d654467
--- /dev/null
+++ b/pathops/SkAddIntersections.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 "SkAddIntersections.h"
+#include "SkPathOpsBounds.h"
+
+#if DEBUG_ADD_INTERSECTING_TS
+
+static void debugShowLineIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn, const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " LINE_DEBUG_STR " " LINE_DEBUG_STR "\n",
+ __FUNCTION__, LINE_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " LINE_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], LINE_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ if (pts == 2) {
+ SkDebugf(" " T_DEBUG_STR(wtTs, 1) " " PT_DEBUG_STR, i[0][1], PT_DEBUG_DATA(i, 1));
+ }
+ SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts()));
+ if (pts == 2) {
+ SkDebugf(" " T_DEBUG_STR(wnTs, 1), i[1][1]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowQuadLineIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn,
+ const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " QUAD_DEBUG_STR " " LINE_DEBUG_STR "\n",
+ __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n));
+ }
+ SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts()));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowQuadIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn, const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " QUAD_DEBUG_STR " " QUAD_DEBUG_STR "\n",
+ __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n));
+ }
+ SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts()));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowCubicLineIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn, const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " LINE_DEBUG_STR "\n",
+ __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n));
+ }
+ SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts()));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowCubicQuadIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn, const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " QUAD_DEBUG_STR "\n",
+ __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n));
+ }
+ SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts()));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersectionHelper& wn, const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CUBIC_DEBUG_STR "\n",
+ __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CUBIC_DEBUG_DATA(wn.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n));
+ }
+ SkDebugf(" wnTs[0]=%g " CUBIC_DEBUG_STR, i[1][0], CUBIC_DEBUG_DATA(wn.pts()));
+ for (int n = 1; n < pts; ++n) {
+ SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]);
+ }
+ SkDebugf("\n");
+}
+
+static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no self intersect " CUBIC_DEBUG_STR "\n", __FUNCTION__,
+ CUBIC_DEBUG_DATA(wt.pts()));
+ return;
+ }
+ SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__,
+ i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0));
+ SkDebugf(" " T_DEBUG_STR(wtTs, 1), i[1][0]);
+ SkDebugf("\n");
+}
+
+#else
+static void debugShowLineIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowQuadLineIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowQuadIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicLineIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersections& ) {
+}
+#endif
+
+bool AddIntersectTs(SkOpContour* test, SkOpContour* next) {
+ if (test != next) {
+ if (test->bounds().fBottom < next->bounds().fTop) {
+ return false;
+ }
+ if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) {
+ return true;
+ }
+ }
+ SkIntersectionHelper wt;
+ wt.init(test);
+ bool foundCommonContour = test == next;
+ do {
+ SkIntersectionHelper wn;
+ wn.init(next);
+ if (test == next && !wn.startAfter(wt)) {
+ continue;
+ }
+ do {
+ if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) {
+ continue;
+ }
+ int pts = 0;
+ SkIntersections ts;
+ bool swap = false;
+ switch (wt.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ swap = true;
+ switch (wn.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ case SkIntersectionHelper::kLine_Segment: {
+ pts = ts.lineHorizontal(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped());
+ debugShowLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ case SkIntersectionHelper::kQuad_Segment: {
+ pts = ts.quadHorizontal(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped());
+ debugShowQuadLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ pts = ts.cubicHorizontal(wn.pts(), wt.left(),
+ wt.right(), wt.y(), wt.xFlipped());
+ debugShowCubicLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ swap = true;
+ switch (wn.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ case SkIntersectionHelper::kLine_Segment: {
+ pts = ts.lineVertical(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped());
+ debugShowLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ case SkIntersectionHelper::kQuad_Segment: {
+ pts = ts.quadVertical(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped());
+ debugShowQuadLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ pts = ts.cubicVertical(wn.pts(), wt.top(),
+ wt.bottom(), wt.x(), wt.yFlipped());
+ debugShowCubicLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkIntersectionHelper::kLine_Segment:
+ switch (wn.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ pts = ts.lineHorizontal(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped());
+ debugShowLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ pts = ts.lineVertical(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped());
+ debugShowLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kLine_Segment: {
+ pts = ts.lineLine(wt.pts(), wn.pts());
+ debugShowLineIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kQuad_Segment: {
+ swap = true;
+ pts = ts.quadLine(wn.pts(), wt.pts());
+ debugShowQuadLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ swap = true;
+ pts = ts.cubicLine(wn.pts(), wt.pts());
+ debugShowCubicLineIntersection(pts, wn, wt, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkIntersectionHelper::kQuad_Segment:
+ switch (wn.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ pts = ts.quadHorizontal(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped());
+ debugShowQuadLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ pts = ts.quadVertical(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped());
+ debugShowQuadLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kLine_Segment: {
+ pts = ts.quadLine(wt.pts(), wn.pts());
+ debugShowQuadLineIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kQuad_Segment: {
+ pts = ts.quadQuad(wt.pts(), wn.pts());
+ debugShowQuadIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ swap = true;
+ pts = ts.cubicQuad(wn.pts(), wt.pts());
+ debugShowCubicQuadIntersection(pts, wn, wt, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ case SkIntersectionHelper::kCubic_Segment:
+ switch (wn.segmentType()) {
+ case SkIntersectionHelper::kHorizontalLine_Segment:
+ pts = ts.cubicHorizontal(wt.pts(), wn.left(),
+ wn.right(), wn.y(), wn.xFlipped());
+ debugShowCubicLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kVerticalLine_Segment:
+ pts = ts.cubicVertical(wt.pts(), wn.top(),
+ wn.bottom(), wn.x(), wn.yFlipped());
+ debugShowCubicLineIntersection(pts, wt, wn, ts);
+ break;
+ case SkIntersectionHelper::kLine_Segment: {
+ pts = ts.cubicLine(wt.pts(), wn.pts());
+ debugShowCubicLineIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kQuad_Segment: {
+ pts = ts.cubicQuad(wt.pts(), wn.pts());
+ debugShowCubicQuadIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ pts = ts.cubicCubic(wt.pts(), wn.pts());
+ debugShowCubicIntersection(pts, wt, wn, 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() <= SkIntersectionHelper::kLine_Segment
+ && wt.segmentType() <= SkIntersectionHelper::kLine_Segment) {
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+ if (wn.segmentType() >= SkIntersectionHelper::kQuad_Segment
+ && wt.segmentType() >= SkIntersectionHelper::kQuad_Segment
+ && ts.isCoincident(0)) {
+ SkASSERT(ts.coincidentUsed() == 2);
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+ }
+ for (int pt = 0; pt < pts; ++pt) {
+ SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1);
+ SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1);
+ SkPoint point = ts.pt(pt).asSkPoint();
+ int testTAt = wt.addT(wn, point, ts[swap][pt]);
+ int nextTAt = wn.addT(wt, point, ts[!swap][pt]);
+ wt.addOtherT(testTAt, ts[!swap][pt], nextTAt);
+ wn.addOtherT(nextTAt, ts[swap][pt], testTAt);
+ }
+ } while (wn.advance());
+ } while (wt.advance());
+ return true;
+}
+
+void AddSelfIntersectTs(SkOpContour* test) {
+ SkIntersectionHelper wt;
+ wt.init(test);
+ do {
+ if (wt.segmentType() != SkIntersectionHelper::kCubic_Segment) {
+ continue;
+ }
+ SkIntersections ts;
+ int pts = ts.cubic(wt.pts());
+ debugShowCubicIntersection(pts, wt, ts);
+ if (!pts) {
+ continue;
+ }
+ SkASSERT(pts == 1);
+ SkASSERT(ts[0][0] >= 0 && ts[0][0] <= 1);
+ SkASSERT(ts[1][0] >= 0 && ts[1][0] <= 1);
+ SkPoint point = ts.pt(0).asSkPoint();
+ int testTAt = wt.addSelfT(wt, point, ts[0][0]);
+ int nextTAt = wt.addT(wt, point, ts[1][0]);
+ wt.addOtherT(testTAt, ts[1][0], nextTAt);
+ wt.addOtherT(nextTAt, ts[0][0], testTAt);
+ } while (wt.advance());
+}
+
+// resolve any coincident pairs found while intersecting, and
+// see if coincidence is formed by clipping non-concident segments
+void CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total) {
+ int contourCount = (*contourList).count();
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->addCoincidentPoints();
+ }
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->calcCoincidentWinding();
+ }
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->findTooCloseToCall();
+ }
+}
diff --git a/pathops/SkAddIntersections.h b/pathops/SkAddIntersections.h
new file mode 100644
index 00000000..94ea436b
--- /dev/null
+++ b/pathops/SkAddIntersections.h
@@ -0,0 +1,18 @@
+/*
+ * 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 SkAddIntersections_DEFINED
+#define SkAddIntersections_DEFINED
+
+#include "SkIntersectionHelper.h"
+#include "SkIntersections.h"
+#include "SkTArray.h"
+
+bool AddIntersectTs(SkOpContour* test, SkOpContour* next);
+void AddSelfIntersectTs(SkOpContour* test);
+void CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total);
+
+#endif
diff --git a/pathops/SkDCubicIntersection.cpp b/pathops/SkDCubicIntersection.cpp
new file mode 100644
index 00000000..19e7340c
--- /dev/null
+++ b/pathops/SkDCubicIntersection.cpp
@@ -0,0 +1,548 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsPoint.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+#include "SkReduceOrder.h"
+#include "SkTSort.h"
+
+#if ONE_OFF_DEBUG
+static const double tLimits1[2][2] = {{0.388600450, 0.388600452}, {0.245852802, 0.245852804}};
+static const double tLimits2[2][2] = {{-0.865211397, -0.865215212}, {-0.865207696, -0.865208078}};
+#endif
+
+#define DEBUG_QUAD_PART ONE_OFF_DEBUG && 1
+#define DEBUG_QUAD_PART_SHOW_SIMPLE DEBUG_QUAD_PART && 0
+#define SWAP_TOP_DEBUG 0
+
+static const int kCubicToQuadSubdivisionDepth = 8; // slots reserved for cubic to quads subdivision
+
+static int quadPart(const SkDCubic& cubic, double tStart, double tEnd, SkReduceOrder* reducer) {
+ SkDCubic part = cubic.subDivide(tStart, tEnd);
+ SkDQuad quad = part.toQuad();
+ // FIXME: should reduceOrder be looser in this use case if quartic is going to blow up on an
+ // extremely shallow quadratic?
+ int order = reducer->reduce(quad, SkReduceOrder::kFill_Style);
+#if DEBUG_QUAD_PART
+ SkDebugf("%s cubic=(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)"
+ " t=(%1.9g,%1.9g)\n", __FUNCTION__, cubic[0].fX, cubic[0].fY,
+ cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
+ cubic[3].fX, cubic[3].fY, tStart, tEnd);
+ SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n"
+ " {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n",
+ part[0].fX, part[0].fY, part[1].fX, part[1].fY, part[2].fX, part[2].fY,
+ part[3].fX, part[3].fY, quad[0].fX, quad[0].fY,
+ quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY);
+#if DEBUG_QUAD_PART_SHOW_SIMPLE
+ SkDebugf("%s simple=(%1.9g,%1.9g", __FUNCTION__, reducer->fQuad[0].fX, reducer->fQuad[0].fY);
+ if (order > 1) {
+ SkDebugf(" %1.9g,%1.9g", reducer->fQuad[1].fX, reducer->fQuad[1].fY);
+ }
+ if (order > 2) {
+ SkDebugf(" %1.9g,%1.9g", reducer->fQuad[2].fX, reducer->fQuad[2].fY);
+ }
+ SkDebugf(")\n");
+ SkASSERT(order < 4 && order > 0);
+#endif
+#endif
+ return order;
+}
+
+static void intersectWithOrder(const SkDQuad& simple1, int order1, const SkDQuad& simple2,
+ int order2, SkIntersections& i) {
+ if (order1 == 3 && order2 == 3) {
+ i.intersect(simple1, simple2);
+ } else if (order1 <= 2 && order2 <= 2) {
+ i.intersect((const SkDLine&) simple1, (const SkDLine&) simple2);
+ } else if (order1 == 3 && order2 <= 2) {
+ i.intersect(simple1, (const SkDLine&) simple2);
+ } else {
+ SkASSERT(order1 <= 2 && order2 == 3);
+ i.intersect(simple2, (const SkDLine&) simple1);
+ i.swapPts();
+ }
+}
+
+// this flavor centers potential intersections recursively. In contrast, '2' may inadvertently
+// chase intersections near quadratic ends, requiring odd hacks to find them.
+static void intersect(const SkDCubic& cubic1, double t1s, double t1e, const SkDCubic& cubic2,
+ double t2s, double t2e, double precisionScale, SkIntersections& i) {
+ i.upDepth();
+ SkDCubic c1 = cubic1.subDivide(t1s, t1e);
+ SkDCubic c2 = cubic2.subDivide(t2s, t2e);
+ SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts1;
+ // OPTIMIZE: if c1 == c2, call once (happens when detecting self-intersection)
+ c1.toQuadraticTs(c1.calcPrecision() * precisionScale, &ts1);
+ SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts2;
+ c2.toQuadraticTs(c2.calcPrecision() * precisionScale, &ts2);
+ double t1Start = t1s;
+ int ts1Count = ts1.count();
+ for (int i1 = 0; i1 <= ts1Count; ++i1) {
+ const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1;
+ const double t1 = t1s + (t1e - t1s) * tEnd1;
+ SkReduceOrder s1;
+ int o1 = quadPart(cubic1, t1Start, t1, &s1);
+ double t2Start = t2s;
+ int ts2Count = ts2.count();
+ for (int i2 = 0; i2 <= ts2Count; ++i2) {
+ const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1;
+ const double t2 = t2s + (t2e - t2s) * tEnd2;
+ if (&cubic1 == &cubic2 && t1Start >= t2Start) {
+ t2Start = t2;
+ continue;
+ }
+ SkReduceOrder s2;
+ int o2 = quadPart(cubic2, t2Start, t2, &s2);
+ #if ONE_OFF_DEBUG
+ char tab[] = " ";
+ if (tLimits1[0][0] >= t1Start && tLimits1[0][1] <= t1
+ && tLimits1[1][0] >= t2Start && tLimits1[1][1] <= t2) {
+ SkDebugf("%.*s %s t1=(%1.9g,%1.9g) t2=(%1.9g,%1.9g)", i.depth()*2, tab,
+ __FUNCTION__, t1Start, t1, t2Start, t2);
+ SkIntersections xlocals;
+ intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, xlocals);
+ SkDebugf(" xlocals.fUsed=%d\n", xlocals.used());
+ }
+ #endif
+ SkIntersections locals;
+ intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, locals);
+ int tCount = locals.used();
+ for (int tIdx = 0; tIdx < tCount; ++tIdx) {
+ double to1 = t1Start + (t1 - t1Start) * locals[0][tIdx];
+ double to2 = t2Start + (t2 - t2Start) * locals[1][tIdx];
+ // if the computed t is not sufficiently precise, iterate
+ SkDPoint p1 = cubic1.ptAtT(to1);
+ SkDPoint p2 = cubic2.ptAtT(to2);
+ if (p1.approximatelyEqual(p2)) {
+ SkASSERT(!locals.isCoincident(tIdx));
+ if (&cubic1 != &cubic2 || !approximately_equal(to1, to2)) {
+ if (i.swapped()) { // FIXME: insert should respect swap
+ i.insert(to2, to1, p1);
+ } else {
+ i.insert(to1, to2, p1);
+ }
+ }
+ } else {
+ double offset = precisionScale / 16; // FIME: const is arbitrary: test, refine
+ double c1Bottom = tIdx == 0 ? 0 :
+ (t1Start + (t1 - t1Start) * locals[0][tIdx - 1] + to1) / 2;
+ double c1Min = SkTMax(c1Bottom, to1 - offset);
+ double c1Top = tIdx == tCount - 1 ? 1 :
+ (t1Start + (t1 - t1Start) * locals[0][tIdx + 1] + to1) / 2;
+ double c1Max = SkTMin(c1Top, to1 + offset);
+ double c2Min = SkTMax(0., to2 - offset);
+ double c2Max = SkTMin(1., to2 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 1 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 1 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, c1Bottom, c1Top, 0., 1.,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 1 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 1 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ if (tCount > 1) {
+ c1Min = SkTMax(0., to1 - offset);
+ c1Max = SkTMin(1., to1 + offset);
+ double c2Bottom = tIdx == 0 ? to2 :
+ (t2Start + (t2 - t2Start) * locals[1][tIdx - 1] + to2) / 2;
+ double c2Top = tIdx == tCount - 1 ? to2 :
+ (t2Start + (t2 - t2Start) * locals[1][tIdx + 1] + to2) / 2;
+ if (c2Bottom > c2Top) {
+ SkTSwap(c2Bottom, c2Top);
+ }
+ if (c2Bottom == to2) {
+ c2Bottom = 0;
+ }
+ if (c2Top == to2) {
+ c2Top = 1;
+ }
+ c2Min = SkTMax(c2Bottom, to2 - offset);
+ c2Max = SkTMin(c2Top, to2 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 2 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 2 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 2 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 2 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ c1Min = SkTMax(c1Bottom, to1 - offset);
+ c1Max = SkTMin(c1Top, to1 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 3 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 3 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 3 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 3 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ }
+ // intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ // FIXME: if no intersection is found, either quadratics intersected where
+ // cubics did not, or the intersection was missed. In the former case, expect
+ // the quadratics to be nearly parallel at the point of intersection, and check
+ // for that.
+ }
+ }
+ t2Start = t2;
+ }
+ t1Start = t1;
+ }
+ i.downDepth();
+}
+
+#define LINE_FRACTION 0.1
+
+// intersect the end of the cubic with the other. Try lines from the end to control and opposite
+// end to determine range of t on opposite cubic.
+static void intersectEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2,
+ const SkDRect& bounds2, bool selfIntersect, SkIntersections& i) {
+ SkDLine line;
+ int t1Index = start ? 0 : 3;
+ bool swap = i.swapped();
+ double testT = (double) !start;
+ // quad/quad at this point checks to see if exact matches have already been found
+ // cubic/cubic can't reject so easily since cubics can intersect same point more than once
+ if (!selfIntersect) {
+ SkDLine tmpLine;
+ tmpLine[0] = tmpLine[1] = cubic2[t1Index];
+ tmpLine[1].fX += cubic2[2 - start].fY - cubic2[t1Index].fY;
+ tmpLine[1].fY -= cubic2[2 - start].fX - cubic2[t1Index].fX;
+ SkIntersections impTs;
+ impTs.intersectRay(cubic1, tmpLine);
+ for (int index = 0; index < impTs.used(); ++index) {
+ SkDPoint realPt = impTs.pt(index);
+ if (!tmpLine[0].approximatelyEqualHalf(realPt)) {
+ continue;
+ }
+ if (swap) {
+ i.insert(testT, impTs[0][index], tmpLine[0]);
+ } else {
+ i.insert(impTs[0][index], testT, tmpLine[0]);
+ }
+ return;
+ }
+ }
+ // don't bother if the two cubics are connnected
+ static const int kPointsInCubic = 4; // FIXME: move to DCubic, replace '4' with this
+ static const int kMaxLineCubicIntersections = 3;
+ SkSTArray<(kMaxLineCubicIntersections - 1) * kMaxLineCubicIntersections, double, true> tVals;
+ line[0] = cubic1[t1Index];
+ // this variant looks for intersections with the end point and lines parallel to other points
+ for (int index = 0; index < kPointsInCubic; ++index) {
+ if (index == t1Index) {
+ continue;
+ }
+ SkDVector dxy1 = cubic1[index] - line[0];
+ dxy1 /= SkDCubic::gPrecisionUnit;
+ line[1] = line[0] + dxy1;
+ SkDRect lineBounds;
+ lineBounds.setBounds(line);
+ if (!bounds2.intersects(&lineBounds)) {
+ continue;
+ }
+ SkIntersections local;
+ if (!local.intersect(cubic2, line)) {
+ continue;
+ }
+ for (int idx2 = 0; idx2 < local.used(); ++idx2) {
+ double foundT = local[0][idx2];
+ if (approximately_less_than_zero(foundT)
+ || approximately_greater_than_one(foundT)) {
+ continue;
+ }
+ if (local.pt(idx2).approximatelyEqual(line[0])) {
+ if (i.swapped()) { // FIXME: insert should respect swap
+ i.insert(foundT, testT, line[0]);
+ } else {
+ i.insert(testT, foundT, line[0]);
+ }
+ } else {
+ tVals.push_back(foundT);
+ }
+ }
+ }
+ if (tVals.count() == 0) {
+ return;
+ }
+ SkTQSort<double>(tVals.begin(), tVals.end() - 1);
+ double tMin1 = start ? 0 : 1 - LINE_FRACTION;
+ double tMax1 = start ? LINE_FRACTION : 1;
+ int tIdx = 0;
+ do {
+ int tLast = tIdx;
+ while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) {
+ ++tLast;
+ }
+ double tMin2 = SkTMax(tVals[tIdx] - LINE_FRACTION, 0.0);
+ double tMax2 = SkTMin(tVals[tLast] + LINE_FRACTION, 1.0);
+ int lastUsed = i.used();
+ intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i);
+ if (lastUsed == i.used()) {
+ tMin2 = SkTMax(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0);
+ tMax2 = SkTMin(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0);
+ intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i);
+ }
+ tIdx = tLast + 1;
+ } while (tIdx < tVals.count());
+ return;
+}
+
+const double CLOSE_ENOUGH = 0.001;
+
+static bool closeStart(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) {
+ if (i[cubicIndex][0] != 0 || i[cubicIndex][1] > CLOSE_ENOUGH) {
+ return false;
+ }
+ pt = cubic.ptAtT((i[cubicIndex][0] + i[cubicIndex][1]) / 2);
+ return true;
+}
+
+static bool closeEnd(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) {
+ int last = i.used() - 1;
+ if (i[cubicIndex][last] != 1 || i[cubicIndex][last - 1] < 1 - CLOSE_ENOUGH) {
+ return false;
+ }
+ pt = cubic.ptAtT((i[cubicIndex][last] + i[cubicIndex][last - 1]) / 2);
+ return true;
+}
+
+static bool only_end_pts_in_common(const SkDCubic& c1, const SkDCubic& c2) {
+// 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 < 4; ++oddMan) {
+ const SkDPoint* endPt[3];
+ for (int opp = 1; opp < 4; ++opp) {
+ int end = oddMan ^ opp; // choose a value not equal to oddMan
+ endPt[opp - 1] = &c1[end];
+ }
+ for (int triTest = 0; triTest < 3; ++triTest) {
+ double origX = endPt[triTest]->fX;
+ double origY = endPt[triTest]->fY;
+ int oppTest = triTest + 1;
+ if (3 == oppTest) {
+ oppTest = 0;
+ }
+ double adj = endPt[oppTest]->fX - origX;
+ double opp = endPt[oppTest]->fY - origY;
+ double sign = (c1[oddMan].fY - origY) * adj - (c1[oddMan].fX - origX) * opp;
+ if (approximately_zero(sign)) {
+ goto tryNextHalfPlane;
+ }
+ for (int n = 0; n < 4; ++n) {
+ double test = (c2[n].fY - origY) * adj - (c2[n].fX - origX) * opp;
+ if (test * sign > 0 && !precisely_zero(test)) {
+ goto tryNextHalfPlane;
+ }
+ }
+ }
+ return true;
+tryNextHalfPlane:
+ ;
+ }
+ return false;
+}
+
+int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) {
+ bool selfIntersect = &c1 == &c2;
+ if (selfIntersect) {
+ if (c1[0].approximatelyEqualHalf(c1[3])) {
+ insert(0, 1, c1[0]);
+ }
+ } else {
+ for (int i1 = 0; i1 < 4; i1 += 3) {
+ for (int i2 = 0; i2 < 4; i2 += 3) {
+ if (c1[i1].approximatelyEqualHalf(c2[i2])) {
+ insert(i1 >> 1, i2 >> 1, c1[i1]);
+ }
+ }
+ }
+ }
+ SkASSERT(fUsed < 4);
+ if (!selfIntersect) {
+ if (only_end_pts_in_common(c1, c2)) {
+ return fUsed;
+ }
+ if (only_end_pts_in_common(c2, c1)) {
+ return fUsed;
+ }
+ }
+ // quad/quad does linear test here -- cubic does not
+ // cubics which are really lines should have been detected in reduce step earlier
+ SkDRect c1Bounds, c2Bounds;
+ // FIXME: pass in cached bounds from caller
+ c1Bounds.setBounds(c1); // OPTIMIZE use setRawBounds ?
+ c2Bounds.setBounds(c2);
+ intersectEnd(c1, false, c2, c2Bounds, selfIntersect, *this);
+ intersectEnd(c1, true, c2, c2Bounds, selfIntersect, *this);
+ if (selfIntersect) {
+ if (fUsed) {
+ return fUsed;
+ }
+ } else {
+ swap();
+ intersectEnd(c2, false, c1, c1Bounds, false, *this);
+ intersectEnd(c2, true, c1, c1Bounds, false, *this);
+ swap();
+ }
+ // if two ends intersect, check middle for coincidence
+ if (fUsed >= 2) {
+ SkASSERT(!selfIntersect);
+ int last = fUsed - 1;
+ double tRange1 = fT[0][last] - fT[0][0];
+ double tRange2 = fT[1][last] - fT[1][0];
+ for (int index = 1; index < 5; ++index) {
+ double testT1 = fT[0][0] + tRange1 * index / 5;
+ double testT2 = fT[1][0] + tRange2 * index / 5;
+ SkDPoint testPt1 = c1.ptAtT(testT1);
+ SkDPoint testPt2 = c2.ptAtT(testT2);
+ if (!testPt1.approximatelyEqual(testPt2)) {
+ goto skipCoincidence;
+ }
+ }
+ if (fUsed > 2) {
+ fPt[1] = fPt[last];
+ fT[0][1] = fT[0][last];
+ fT[1][1] = fT[1][last];
+ fUsed = 2;
+ }
+ fIsCoincident[0] = fIsCoincident[1] = 0x03;
+ return fUsed;
+ }
+skipCoincidence:
+ ::intersect(c1, 0, 1, c2, 0, 1, 1, *this);
+ // If an end point and a second point very close to the end is returned, the second
+ // point may have been detected because the approximate quads
+ // intersected at the end and close to it. Verify that the second point is valid.
+ if (fUsed <= 1) {
+ return fUsed;
+ }
+ SkDPoint pt[2];
+ if (closeStart(c1, 0, *this, pt[0]) && closeStart(c2, 1, *this, pt[1])
+ && pt[0].approximatelyEqual(pt[1])) {
+ removeOne(1);
+ }
+ if (closeEnd(c1, 0, *this, pt[0]) && closeEnd(c2, 1, *this, pt[1])
+ && pt[0].approximatelyEqual(pt[1])) {
+ removeOne(used() - 2);
+ }
+ // vet the pairs of t values to see if the mid value is also on the curve. If so, mark
+ // the span as coincident
+ if (fUsed >= 2 && !coincidentUsed()) {
+ int last = fUsed - 1;
+ int match = 0;
+ for (int index = 0; index < last; ++index) {
+ double mid1 = (fT[0][index] + fT[0][index + 1]) / 2;
+ double mid2 = (fT[1][index] + fT[1][index + 1]) / 2;
+ pt[0] = c1.ptAtT(mid1);
+ pt[1] = c2.ptAtT(mid2);
+ if (pt[0].approximatelyEqual(pt[1])) {
+ match |= 1 << index;
+ }
+ }
+ if (match) {
+ if (((match + 1) & match) != 0) {
+ SkDebugf("%s coincident hole\n", __FUNCTION__);
+ }
+ // for now, assume that everything from start to finish is coincident
+ if (fUsed > 2) {
+ fPt[1] = fPt[last];
+ fT[0][1] = fT[0][last];
+ fT[1][1] = fT[1][last];
+ fIsCoincident[0] = 0x03;
+ fIsCoincident[1] = 0x03;
+ fUsed = 2;
+ }
+ }
+ }
+ return fUsed;
+}
+
+// Up promote the quad to a cubic.
+// OPTIMIZATION If this is a common use case, optimize by duplicating
+// the intersect 3 loop to avoid the promotion / demotion code
+int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) {
+ SkDCubic up = quad.toCubic();
+ (void) intersect(cubic, up);
+ return used();
+}
+
+/* http://www.ag.jku.at/compass/compasssample.pdf
+( Self-Intersection Problems and Approximate Implicitization by Jan B. Thomassen
+Centre of Mathematics for Applications, University of Oslo http://www.cma.uio.no janbth@math.uio.no
+SINTEF Applied Mathematics http://www.sintef.no )
+describes a method to find the self intersection of a cubic by taking the gradient of the implicit
+form dotted with the normal, and solving for the roots. My math foo is too poor to implement this.*/
+
+int SkIntersections::intersect(const SkDCubic& c) {
+ // check to see if x or y end points are the extrema. Are other quick rejects possible?
+ if (c.endsAreExtremaInXOrY()) {
+ return false;
+ }
+ (void) intersect(c, c);
+ if (used() > 0) {
+ SkASSERT(used() == 1);
+ if (fT[0][0] > fT[1][0]) {
+ swapPts();
+ }
+ }
+ return used();
+}
diff --git a/pathops/SkDCubicLineIntersection.cpp b/pathops/SkDCubicLineIntersection.cpp
new file mode 100644
index 00000000..a891abec
--- /dev/null
+++ b/pathops/SkDCubicLineIntersection.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+
+/*
+Find the interection of a line and cubic by solving for valid t values.
+
+Analogous to line-quadratic intersection, solve line-cubic intersection by
+representing the cubic as:
+ x = a(1-t)^3 + 2b(1-t)^2t + c(1-t)t^2 + dt^3
+ y = e(1-t)^3 + 2f(1-t)^2t + g(1-t)t^2 + ht^3
+and the line as:
+ y = i*x + j (if the line is more horizontal)
+or:
+ x = i*y + j (if the line is more vertical)
+
+Then using Mathematica, solve for the values of t where the cubic intersects the
+line:
+
+ (in) Resultant[
+ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - x,
+ e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - i*x - j, x]
+ (out) -e + j +
+ 3 e t - 3 f t -
+ 3 e t^2 + 6 f t^2 - 3 g t^2 +
+ e t^3 - 3 f t^3 + 3 g t^3 - h t^3 +
+ i ( a -
+ 3 a t + 3 b t +
+ 3 a t^2 - 6 b t^2 + 3 c t^2 -
+ a t^3 + 3 b t^3 - 3 c t^3 + d t^3 )
+
+if i goes to infinity, we can rewrite the line in terms of x. Mathematica:
+
+ (in) Resultant[
+ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - i*y - j,
+ e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y]
+ (out) a - j -
+ 3 a t + 3 b t +
+ 3 a t^2 - 6 b t^2 + 3 c t^2 -
+ a t^3 + 3 b t^3 - 3 c t^3 + d t^3 -
+ i ( e -
+ 3 e t + 3 f t +
+ 3 e t^2 - 6 f t^2 + 3 g t^2 -
+ e t^3 + 3 f t^3 - 3 g t^3 + h t^3 )
+
+Solving this with Mathematica produces an expression with hundreds of terms;
+instead, use Numeric Solutions recipe to solve the cubic.
+
+The near-horizontal case, in terms of: Ax^3 + Bx^2 + Cx + D == 0
+ A = (-(-e + 3*f - 3*g + h) + i*(-a + 3*b - 3*c + d) )
+ B = 3*(-( e - 2*f + g ) + i*( a - 2*b + c ) )
+ C = 3*(-(-e + f ) + i*(-a + b ) )
+ D = (-( e ) + i*( a ) + j )
+
+The near-vertical case, in terms of: Ax^3 + Bx^2 + Cx + D == 0
+ A = ( (-a + 3*b - 3*c + d) - i*(-e + 3*f - 3*g + h) )
+ B = 3*( ( a - 2*b + c ) - i*( e - 2*f + g ) )
+ C = 3*( (-a + b ) - i*(-e + f ) )
+ D = ( ( a ) - i*( e ) - j )
+
+For horizontal lines:
+(in) Resultant[
+ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - j,
+ e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y]
+(out) e - j -
+ 3 e t + 3 f t +
+ 3 e t^2 - 6 f t^2 + 3 g t^2 -
+ e t^3 + 3 f t^3 - 3 g t^3 + h t^3
+ */
+
+class LineCubicIntersections {
+public:
+ enum PinTPoint {
+ kPointUninitialized,
+ kPointInitialized
+ };
+
+ LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections* i)
+ : fCubic(c)
+ , fLine(l)
+ , fIntersections(i)
+ , fAllowNear(true) {
+ }
+
+ void allowNear(bool allow) {
+ fAllowNear = allow;
+ }
+
+ // see parallel routine in line quadratic intersections
+ int intersectRay(double roots[3]) {
+ double adj = fLine[1].fX - fLine[0].fX;
+ double opp = fLine[1].fY - fLine[0].fY;
+ SkDCubic r;
+ for (int n = 0; n < 4; ++n) {
+ r[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp;
+ }
+ double A, B, C, D;
+ SkDCubic::Coefficients(&r[0].fX, &A, &B, &C, &D);
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+ }
+
+ int intersect() {
+ addExactEndPoints();
+ double rootVals[3];
+ int roots = intersectRay(rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ double lineT = findLineT(cubicT);
+ SkDPoint pt;
+ if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized)) {
+ #if ONE_OFF_DEBUG
+ SkDPoint cPt = fCubic.ptAtT(cubicT);
+ SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY,
+ cPt.fX, cPt.fY);
+ #endif
+ fIntersections->insert(cubicT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearEndPoints();
+ }
+ return fIntersections->used();
+ }
+
+ int horizontalIntersect(double axisIntercept, double roots[3]) {
+ double A, B, C, D;
+ SkDCubic::Coefficients(&fCubic[0].fY, &A, &B, &C, &D);
+ D -= axisIntercept;
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+ }
+
+ int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) {
+ addExactHorizontalEndPoints(left, right, axisIntercept);
+ double rootVals[3];
+ int roots = horizontalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ SkDPoint pt = fCubic.ptAtT(cubicT);
+ double lineT = (pt.fX - left) / (right - left);
+ if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) {
+ fIntersections->insert(cubicT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearHorizontalEndPoints(left, right, axisIntercept);
+ }
+ if (flipped) {
+ fIntersections->flip();
+ }
+ return fIntersections->used();
+ }
+
+ int verticalIntersect(double axisIntercept, double roots[3]) {
+ double A, B, C, D;
+ SkDCubic::Coefficients(&fCubic[0].fX, &A, &B, &C, &D);
+ D -= axisIntercept;
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+ }
+
+ int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) {
+ addExactVerticalEndPoints(top, bottom, axisIntercept);
+ double rootVals[3];
+ int roots = verticalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ SkDPoint pt = fCubic.ptAtT(cubicT);
+ double lineT = (pt.fY - top) / (bottom - top);
+ if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) {
+ fIntersections->insert(cubicT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearVerticalEndPoints(top, bottom, axisIntercept);
+ }
+ if (flipped) {
+ fIntersections->flip();
+ }
+ return fIntersections->used();
+ }
+
+ protected:
+
+ void addExactEndPoints() {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double lineT = fLine.exactPoint(fCubic[cIndex]);
+ if (lineT < 0) {
+ continue;
+ }
+ double cubicT = (double) (cIndex >> 1);
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ }
+
+ void addNearEndPoints() {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double cubicT = (double) (cIndex >> 1);
+ if (fIntersections->hasT(cubicT)) {
+ continue;
+ }
+ double lineT = fLine.nearPoint(fCubic[cIndex]);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ }
+
+ void addExactHorizontalEndPoints(double left, double right, double y) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double lineT = SkDLine::ExactPointH(fCubic[cIndex], left, right, y);
+ if (lineT < 0) {
+ continue;
+ }
+ double cubicT = (double) (cIndex >> 1);
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ }
+
+ void addNearHorizontalEndPoints(double left, double right, double y) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double cubicT = (double) (cIndex >> 1);
+ if (fIntersections->hasT(cubicT)) {
+ continue;
+ }
+ double lineT = SkDLine::NearPointH(fCubic[cIndex], left, right, y);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ // FIXME: see if line end is nearly on cubic
+ }
+
+ void addExactVerticalEndPoints(double top, double bottom, double x) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double lineT = SkDLine::ExactPointV(fCubic[cIndex], top, bottom, x);
+ if (lineT < 0) {
+ continue;
+ }
+ double cubicT = (double) (cIndex >> 1);
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ }
+
+ void addNearVerticalEndPoints(double top, double bottom, double x) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ double cubicT = (double) (cIndex >> 1);
+ if (fIntersections->hasT(cubicT)) {
+ continue;
+ }
+ double lineT = SkDLine::NearPointV(fCubic[cIndex], top, bottom, x);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(cubicT, lineT, fCubic[cIndex]);
+ }
+ // FIXME: see if line end is nearly on cubic
+ }
+
+ double findLineT(double t) {
+ SkDPoint xy = fCubic.ptAtT(t);
+ double dx = fLine[1].fX - fLine[0].fX;
+ double dy = fLine[1].fY - fLine[0].fY;
+ if (fabs(dx) > fabs(dy)) {
+ return (xy.fX - fLine[0].fX) / dx;
+ }
+ return (xy.fY - fLine[0].fY) / dy;
+ }
+
+ bool pinTs(double* cubicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) {
+ if (!approximately_one_or_less(*lineT)) {
+ return false;
+ }
+ if (!approximately_zero_or_more(*lineT)) {
+ return false;
+ }
+ double cT = *cubicT = SkPinT(*cubicT);
+ double lT = *lineT = SkPinT(*lineT);
+ if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && cT != 0 && cT != 1)) {
+ *pt = fLine.ptAtT(lT);
+ } else if (ptSet == kPointUninitialized) {
+ *pt = fCubic.ptAtT(cT);
+ }
+ return true;
+ }
+
+private:
+ const SkDCubic& fCubic;
+ const SkDLine& fLine;
+ SkIntersections* fIntersections;
+ bool fAllowNear;
+};
+
+int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y,
+ bool flipped) {
+ SkDLine line = {{{ left, y }, { right, y }}};
+ LineCubicIntersections c(cubic, line, this);
+ return c.horizontalIntersect(y, left, right, flipped);
+}
+
+int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x,
+ bool flipped) {
+ SkDLine line = {{{ x, top }, { x, bottom }}};
+ LineCubicIntersections c(cubic, line, this);
+ return c.verticalIntersect(x, top, bottom, flipped);
+}
+
+int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) {
+ LineCubicIntersections c(cubic, line, this);
+ c.allowNear(fAllowNear);
+ return c.intersect();
+}
+
+int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) {
+ LineCubicIntersections c(cubic, line, this);
+ fUsed = c.intersectRay(fT[0]);
+ for (int index = 0; index < fUsed; ++index) {
+ fPt[index] = cubic.ptAtT(fT[0][index]);
+ }
+ return fUsed;
+}
diff --git a/pathops/SkDCubicToQuads.cpp b/pathops/SkDCubicToQuads.cpp
new file mode 100644
index 00000000..571f1d94
--- /dev/null
+++ b/pathops/SkDCubicToQuads.cpp
@@ -0,0 +1,190 @@
+/*
+http://stackoverflow.com/questions/2009160/how-do-i-convert-the-2-control-points-of-a-cubic-curve-to-the-single-control-poi
+*/
+
+/*
+Let's call the control points of the cubic Q0..Q3 and the control points of the quadratic P0..P2.
+Then for degree elevation, the equations are:
+
+Q0 = P0
+Q1 = 1/3 P0 + 2/3 P1
+Q2 = 2/3 P1 + 1/3 P2
+Q3 = P2
+In your case you have Q0..Q3 and you're solving for P0..P2. There are two ways to compute P1 from
+ the equations above:
+
+P1 = 3/2 Q1 - 1/2 Q0
+P1 = 3/2 Q2 - 1/2 Q3
+If this is a degree-elevated cubic, then both equations will give the same answer for P1. Since
+ it's likely not, your best bet is to average them. So,
+
+P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3
+
+
+SkDCubic defined by: P1/2 - anchor points, C1/C2 control points
+|x| is the euclidean norm of x
+mid-point approx of cubic: a quad that shares the same anchors with the cubic and has the
+ control point at C = (3·C2 - P2 + 3·C1 - P1)/4
+
+Algorithm
+
+pick an absolute precision (prec)
+Compute the Tdiv as the root of (cubic) equation
+sqrt(3)/18 · |P2 - 3·C2 + 3·C1 - P1|/2 · Tdiv ^ 3 = prec
+if Tdiv < 0.5 divide the cubic at Tdiv. First segment [0..Tdiv] can be approximated with by a
+ quadratic, with a defect less than prec, by the mid-point approximation.
+ Repeat from step 2 with the second resulted segment (corresponding to 1-Tdiv)
+0.5<=Tdiv<1 - simply divide the cubic in two. The two halves can be approximated by the mid-point
+ approximation
+Tdiv>=1 - the entire cubic can be approximated by the mid-point approximation
+
+confirmed by (maybe stolen from)
+http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
+// maybe in turn derived from http://www.cccg.ca/proceedings/2004/36.pdf
+// also stored at http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/bezier%20cccg04%20paper.pdf
+
+*/
+
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkReduceOrder.h"
+#include "SkTArray.h"
+#include "SkTSort.h"
+
+#define USE_CUBIC_END_POINTS 1
+
+static double calc_t_div(const SkDCubic& cubic, double precision, double start) {
+ const double adjust = sqrt(3.) / 36;
+ SkDCubic sub;
+ const SkDCubic* cPtr;
+ if (start == 0) {
+ cPtr = &cubic;
+ } else {
+ // OPTIMIZE: special-case half-split ?
+ sub = cubic.subDivide(start, 1);
+ cPtr = &sub;
+ }
+ const SkDCubic& c = *cPtr;
+ double dx = c[3].fX - 3 * (c[2].fX - c[1].fX) - c[0].fX;
+ double dy = c[3].fY - 3 * (c[2].fY - c[1].fY) - c[0].fY;
+ double dist = sqrt(dx * dx + dy * dy);
+ double tDiv3 = precision / (adjust * dist);
+ double t = SkDCubeRoot(tDiv3);
+ if (start > 0) {
+ t = start + (1 - start) * t;
+ }
+ return t;
+}
+
+SkDQuad SkDCubic::toQuad() const {
+ SkDQuad quad;
+ quad[0] = fPts[0];
+ const SkDPoint fromC1 = {(3 * fPts[1].fX - fPts[0].fX) / 2, (3 * fPts[1].fY - fPts[0].fY) / 2};
+ const SkDPoint fromC2 = {(3 * fPts[2].fX - fPts[3].fX) / 2, (3 * fPts[2].fY - fPts[3].fY) / 2};
+ quad[1].fX = (fromC1.fX + fromC2.fX) / 2;
+ quad[1].fY = (fromC1.fY + fromC2.fY) / 2;
+ quad[2] = fPts[3];
+ return quad;
+}
+
+static bool add_simple_ts(const SkDCubic& cubic, double precision, SkTArray<double, true>* ts) {
+ double tDiv = calc_t_div(cubic, precision, 0);
+ if (tDiv >= 1) {
+ return true;
+ }
+ if (tDiv >= 0.5) {
+ ts->push_back(0.5);
+ return true;
+ }
+ return false;
+}
+
+static void addTs(const SkDCubic& cubic, double precision, double start, double end,
+ SkTArray<double, true>* ts) {
+ double tDiv = calc_t_div(cubic, precision, 0);
+ double parts = ceil(1.0 / tDiv);
+ for (double index = 0; index < parts; ++index) {
+ double newT = start + (index / parts) * (end - start);
+ if (newT > 0 && newT < 1) {
+ ts->push_back(newT);
+ }
+ }
+}
+
+// flavor that returns T values only, deferring computing the quads until they are needed
+// FIXME: when called from recursive intersect 2, this could take the original cubic
+// and do a more precise job when calling chop at and sub divide by computing the fractional ts.
+// it would still take the prechopped cubic for reduce order and find cubic inflections
+void SkDCubic::toQuadraticTs(double precision, SkTArray<double, true>* ts) const {
+ SkReduceOrder reducer;
+ int order = reducer.reduce(*this, SkReduceOrder::kAllow_Quadratics, SkReduceOrder::kFill_Style);
+ if (order < 3) {
+ return;
+ }
+ double inflectT[5];
+ int inflections = findInflections(inflectT);
+ SkASSERT(inflections <= 2);
+ if (!endsAreExtremaInXOrY()) {
+ inflections += findMaxCurvature(&inflectT[inflections]);
+ SkASSERT(inflections <= 5);
+ }
+ SkTQSort<double>(inflectT, &inflectT[inflections - 1]);
+ // OPTIMIZATION: is this filtering common enough that it needs to be pulled out into its
+ // own subroutine?
+ while (inflections && approximately_less_than_zero(inflectT[0])) {
+ memmove(inflectT, &inflectT[1], sizeof(inflectT[0]) * --inflections);
+ }
+ int start = 0;
+ do {
+ int next = start + 1;
+ if (next >= inflections) {
+ break;
+ }
+ if (!approximately_equal(inflectT[start], inflectT[next])) {
+ ++start;
+ continue;
+ }
+ memmove(&inflectT[start], &inflectT[next], sizeof(inflectT[0]) * (--inflections - start));
+ } while (true);
+ while (inflections && approximately_greater_than_one(inflectT[inflections - 1])) {
+ --inflections;
+ }
+ SkDCubicPair pair;
+ if (inflections == 1) {
+ pair = chopAt(inflectT[0]);
+ int orderP1 = reducer.reduce(pair.first(), SkReduceOrder::kNo_Quadratics,
+ SkReduceOrder::kFill_Style);
+ if (orderP1 < 2) {
+ --inflections;
+ } else {
+ int orderP2 = reducer.reduce(pair.second(), SkReduceOrder::kNo_Quadratics,
+ SkReduceOrder::kFill_Style);
+ if (orderP2 < 2) {
+ --inflections;
+ }
+ }
+ }
+ if (inflections == 0 && add_simple_ts(*this, precision, ts)) {
+ return;
+ }
+ if (inflections == 1) {
+ pair = chopAt(inflectT[0]);
+ addTs(pair.first(), precision, 0, inflectT[0], ts);
+ addTs(pair.second(), precision, inflectT[0], 1, ts);
+ return;
+ }
+ if (inflections > 1) {
+ SkDCubic part = subDivide(0, inflectT[0]);
+ addTs(part, precision, 0, inflectT[0], ts);
+ int last = inflections - 1;
+ for (int idx = 0; idx < last; ++idx) {
+ part = subDivide(inflectT[idx], inflectT[idx + 1]);
+ addTs(part, precision, inflectT[idx], inflectT[idx + 1], ts);
+ }
+ part = subDivide(inflectT[last], 1);
+ addTs(part, precision, inflectT[last], 1, ts);
+ return;
+ }
+ addTs(*this, precision, 0, 1, ts);
+}
diff --git a/pathops/SkDLineIntersection.cpp b/pathops/SkDLineIntersection.cpp
new file mode 100644
index 00000000..46118429
--- /dev/null
+++ b/pathops/SkDLineIntersection.cpp
@@ -0,0 +1,315 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkPathOpsLine.h"
+
+/* Determine the intersection point of two lines. This assumes the lines are not parallel,
+ and that that the lines are infinite.
+ From http://en.wikipedia.org/wiki/Line-line_intersection
+ */
+SkDPoint SkIntersections::Line(const SkDLine& a, const SkDLine& b) {
+ double axLen = a[1].fX - a[0].fX;
+ double ayLen = a[1].fY - a[0].fY;
+ double bxLen = b[1].fX - b[0].fX;
+ double byLen = b[1].fY - b[0].fY;
+ double denom = byLen * axLen - ayLen * bxLen;
+ SkASSERT(denom);
+ double term1 = a[1].fX * a[0].fY - a[1].fY * a[0].fX;
+ double term2 = b[1].fX * b[0].fY - b[1].fY * b[0].fX;
+ SkDPoint p;
+ p.fX = (term1 * bxLen - axLen * term2) / denom;
+ p.fY = (term1 * byLen - ayLen * term2) / denom;
+ return p;
+}
+
+int SkIntersections::computePoints(const SkDLine& line, int used) {
+ fPt[0] = line.ptAtT(fT[0][0]);
+ if ((fUsed = used) == 2) {
+ fPt[1] = line.ptAtT(fT[0][1]);
+ }
+ return fUsed;
+}
+
+int SkIntersections::intersectRay(const SkDLine& a, const SkDLine& b) {
+ double axLen = a[1].fX - a[0].fX;
+ double ayLen = a[1].fY - a[0].fY;
+ double bxLen = b[1].fX - b[0].fX;
+ double byLen = b[1].fY - b[0].fY;
+ /* 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;
+ double ab0y = a[0].fY - b[0].fY;
+ double ab0x = a[0].fX - b[0].fX;
+ double numerA = ab0y * bxLen - byLen * ab0x;
+ double numerB = ab0y * axLen - ayLen * ab0x;
+ numerA /= denom;
+ numerB /= denom;
+ int used;
+ if (!approximately_zero(denom)) {
+ fT[0][0] = numerA;
+ fT[1][0] = numerB;
+ used = 1;
+ } else {
+ /* 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 (!AlmostEqualUlps(axLen * a[0].fY - ayLen * a[0].fX,
+ axLen * b[0].fY - ayLen * b[0].fX)) {
+ return fUsed = 0;
+ }
+ // there's no great answer for intersection points for coincident rays, but return something
+ fT[0][0] = fT[1][0] = 0;
+ fT[1][0] = fT[1][1] = 1;
+ used = 2;
+ }
+ return computePoints(a, used);
+}
+
+// note that this only works if both lines are neither horizontal nor vertical
+int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) {
+ // see if end points intersect the opposite line
+ double t;
+ for (int iA = 0; iA < 2; ++iA) {
+ if ((t = b.exactPoint(a[iA])) >= 0) {
+ insert(iA, t, a[iA]);
+ }
+ }
+ for (int iB = 0; iB < 2; ++iB) {
+ if ((t = a.exactPoint(b[iB])) >= 0) {
+ insert(t, iB, b[iB]);
+ }
+ }
+ /* Determine the intersection point of two line segments
+ Return FALSE if the lines don't intersect
+ from: http://paulbourke.net/geometry/lineline2d/ */
+ double axLen = a[1].fX - a[0].fX;
+ double ayLen = a[1].fY - a[0].fY;
+ double bxLen = b[1].fX - b[0].fX;
+ double byLen = b[1].fY - b[0].fY;
+ /* 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 axByLen = axLen * byLen;
+ double ayBxLen = ayLen * bxLen;
+ // detect parallel lines the same way here and in SkOpAngle operator <
+ // so that non-parallel means they are also sortable
+ bool parallel = AlmostEqualUlps(axByLen, ayBxLen);
+ if (!parallel) {
+ double ab0y = a[0].fY - b[0].fY;
+ double ab0x = a[0].fX - b[0].fX;
+ double numerA = ab0y * bxLen - byLen * ab0x;
+ double numerB = ab0y * axLen - ayLen * ab0x;
+ double denom = axByLen - ayBxLen;
+ if (between(0, numerA, denom) && between(0, numerB, denom)) {
+ fT[0][0] = numerA / denom;
+ fT[1][0] = numerB / denom;
+ computePoints(a, 1);
+ }
+ }
+ if (fAllowNear || parallel) {
+ for (int iA = 0; iA < 2; ++iA) {
+ if ((t = b.nearPoint(a[iA])) >= 0) {
+ insert(iA, t, a[iA]);
+ }
+ }
+ for (int iB = 0; iB < 2; ++iB) {
+ if ((t = a.nearPoint(b[iB])) >= 0) {
+ insert(t, iB, b[iB]);
+ }
+ }
+ }
+ return fUsed;
+}
+
+static int horizontal_coincident(const SkDLine& line, double y) {
+ double min = line[0].fY;
+ double max = line[1].fY;
+ if (min > max) {
+ SkTSwap(min, max);
+ }
+ if (min > y || max < y) {
+ return 0;
+ }
+ if (AlmostEqualUlps(min, max) && max - min < fabs(line[0].fX - line[1].fX)) {
+ return 2;
+ }
+ return 1;
+}
+
+static double horizontal_intercept(const SkDLine& line, double y) {
+ return (y - line[0].fY) / (line[1].fY - line[0].fY);
+}
+
+int SkIntersections::horizontal(const SkDLine& line, double y) {
+ int horizontalType = horizontal_coincident(line, y);
+ if (horizontalType == 1) {
+ fT[0][0] = horizontal_intercept(line, y);
+ } else if (horizontalType == 2) {
+ fT[0][0] = 0;
+ fT[0][1] = 1;
+ }
+ return fUsed = horizontalType;
+}
+
+int SkIntersections::horizontal(const SkDLine& line, double left, double right,
+ double y, bool flipped) {
+ // see if end points intersect the opposite line
+ double t;
+ const SkDPoint leftPt = { left, y };
+ if ((t = line.exactPoint(leftPt)) >= 0) {
+ insert(t, (double) flipped, leftPt);
+ }
+ if (left != right) {
+ const SkDPoint rightPt = { right, y };
+ if ((t = line.exactPoint(rightPt)) >= 0) {
+ insert(t, (double) !flipped, rightPt);
+ }
+ for (int index = 0; index < 2; ++index) {
+ if ((t = SkDLine::ExactPointH(line[index], left, right, y)) >= 0) {
+ insert((double) index, flipped ? 1 - t : t, line[index]);
+ }
+ }
+ }
+ int result = horizontal_coincident(line, y);
+ if (result == 1 && fUsed == 0) {
+ fT[0][0] = horizontal_intercept(line, y);
+ double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX);
+ if (between(left, xIntercept, right)) {
+ fT[1][0] = (xIntercept - left) / (right - left);
+ if (flipped) {
+ // OPTIMIZATION: ? instead of swapping, pass original line, use [1].fX - [0].fX
+ for (int index = 0; index < result; ++index) {
+ fT[1][index] = 1 - fT[1][index];
+ }
+ }
+ return computePoints(line, result);
+ }
+ }
+ if (!fAllowNear && result != 2) {
+ return fUsed;
+ }
+ if ((t = line.nearPoint(leftPt)) >= 0) {
+ insert(t, (double) flipped, leftPt);
+ }
+ if (left != right) {
+ const SkDPoint rightPt = { right, y };
+ if ((t = line.nearPoint(rightPt)) >= 0) {
+ insert(t, (double) !flipped, rightPt);
+ }
+ for (int index = 0; index < 2; ++index) {
+ if ((t = SkDLine::NearPointH(line[index], left, right, y)) >= 0) {
+ insert((double) index, flipped ? 1 - t : t, line[index]);
+ }
+ }
+ }
+ return fUsed;
+}
+
+static int vertical_coincident(const SkDLine& line, double x) {
+ double min = line[0].fX;
+ double max = line[1].fX;
+ if (min > max) {
+ SkTSwap(min, max);
+ }
+ if (!precisely_between(min, x, max)) {
+ return 0;
+ }
+ if (AlmostEqualUlps(min, max)) {
+ return 2;
+ }
+ return 1;
+}
+
+static double vertical_intercept(const SkDLine& line, double x) {
+ return (x - line[0].fX) / (line[1].fX - line[0].fX);
+}
+
+int SkIntersections::vertical(const SkDLine& line, double x) {
+ int verticalType = vertical_coincident(line, x);
+ if (verticalType == 1) {
+ fT[0][0] = vertical_intercept(line, x);
+ } else if (verticalType == 2) {
+ fT[0][0] = 0;
+ fT[0][1] = 1;
+ }
+ return fUsed = verticalType;
+}
+
+int SkIntersections::vertical(const SkDLine& line, double top, double bottom,
+ double x, bool flipped) {
+ // see if end points intersect the opposite line
+ double t;
+ SkDPoint topPt = { x, top };
+ if ((t = line.exactPoint(topPt)) >= 0) {
+ insert(t, (double) flipped, topPt);
+ }
+ if (top != bottom) {
+ SkDPoint bottomPt = { x, bottom };
+ if ((t = line.exactPoint(bottomPt)) >= 0) {
+ insert(t, (double) !flipped, bottomPt);
+ }
+ for (int index = 0; index < 2; ++index) {
+ if ((t = SkDLine::ExactPointV(line[index], top, bottom, x)) >= 0) {
+ insert((double) index, flipped ? 1 - t : t, line[index]);
+ }
+ }
+ }
+ int result = vertical_coincident(line, x);
+ if (result == 1 && fUsed == 0) {
+ fT[0][0] = vertical_intercept(line, x);
+ double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY);
+ if (between(top, yIntercept, bottom)) {
+ fT[1][0] = (yIntercept - top) / (bottom - top);
+ if (flipped) {
+ // OPTIMIZATION: instead of swapping, pass original line, use [1].fY - [0].fY
+ for (int index = 0; index < result; ++index) {
+ fT[1][index] = 1 - fT[1][index];
+ }
+ }
+ return computePoints(line, result);
+ }
+ }
+ if (!fAllowNear && result != 2) {
+ return fUsed;
+ }
+ if ((t = line.nearPoint(topPt)) >= 0) {
+ insert(t, (double) flipped, topPt);
+ }
+ if (top != bottom) {
+ SkDPoint bottomPt = { x, bottom };
+ if ((t = line.nearPoint(bottomPt)) >= 0) {
+ insert(t, (double) !flipped, bottomPt);
+ }
+ for (int index = 0; index < 2; ++index) {
+ if ((t = SkDLine::NearPointV(line[index], top, bottom, x)) >= 0) {
+ insert((double) index, flipped ? 1 - t : t, line[index]);
+ }
+ }
+ }
+ return fUsed;
+}
+
+// from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py
+// 4 subs, 2 muls, 1 cmp
+static bool ccw(const SkDPoint& A, const SkDPoint& B, const SkDPoint& C) {
+ return (C.fY - A.fY) * (B.fX - A.fX) > (B.fY - A.fY) * (C.fX - A.fX);
+}
+
+// 16 subs, 8 muls, 6 cmps
+bool SkIntersections::Test(const SkDLine& a, const SkDLine& 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/pathops/SkDQuadImplicit.cpp b/pathops/SkDQuadImplicit.cpp
new file mode 100644
index 00000000..84ad452f
--- /dev/null
+++ b/pathops/SkDQuadImplicit.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 "SkDQuadImplicit.h"
+
+/* from http://tom.cs.byu.edu/~tom/papers/cvgip84.pdf 4.1
+ *
+ * This paper proves that Syvester's method can compute the implicit form of
+ * the quadratic from the parameterized form.
+ *
+ * Given x = a*t*t + b*t + c (the parameterized form)
+ * y = d*t*t + e*t + f
+ *
+ * we want to find an equation of the implicit form:
+ *
+ * A*x*x + B*x*y + C*y*y + D*x + E*y + F = 0
+ *
+ * The implicit form can be expressed as a 4x4 determinant, as shown.
+ *
+ * The resultant obtained by Syvester's method is
+ *
+ * | a b (c - x) 0 |
+ * | 0 a b (c - x) |
+ * | d e (f - y) 0 |
+ * | 0 d e (f - y) |
+ *
+ * which expands to
+ *
+ * d*d*x*x + -2*a*d*x*y + a*a*y*y
+ * + (-2*c*d*d + b*e*d - a*e*e + 2*a*f*d)*x
+ * + (-2*f*a*a + e*b*a - d*b*b + 2*d*c*a)*y
+ * +
+ * | a b c 0 |
+ * | 0 a b c | == 0.
+ * | d e f 0 |
+ * | 0 d e f |
+ *
+ * Expanding the constant determinant results in
+ *
+ * | a b c | | b c 0 |
+ * a*| e f 0 | + d*| a b c | ==
+ * | d e f | | d e f |
+ *
+ * a*(a*f*f + c*e*e - c*f*d - b*e*f) + d*(b*b*f + c*c*d - c*a*f - c*e*b)
+ *
+ */
+
+// use the tricky arithmetic path, but leave the original to compare just in case
+static bool straight_forward = false;
+
+SkDQuadImplicit::SkDQuadImplicit(const SkDQuad& q) {
+ double a, b, c;
+ SkDQuad::SetABC(&q[0].fX, &a, &b, &c);
+ double d, e, f;
+ SkDQuad::SetABC(&q[0].fY, &d, &e, &f);
+ // compute the implicit coefficients
+ if (straight_forward) { // 42 muls, 13 adds
+ fP[kXx_Coeff] = d * d;
+ fP[kXy_Coeff] = -2 * a * d;
+ fP[kYy_Coeff] = a * a;
+ fP[kX_Coeff] = -2*c*d*d + b*e*d - a*e*e + 2*a*f*d;
+ fP[kY_Coeff] = -2*f*a*a + e*b*a - d*b*b + 2*d*c*a;
+ fP[kC_Coeff] = a*(a*f*f + c*e*e - c*f*d - b*e*f)
+ + d*(b*b*f + c*c*d - c*a*f - c*e*b);
+ } else { // 26 muls, 11 adds
+ double aa = a * a;
+ double ad = a * d;
+ double dd = d * d;
+ fP[kXx_Coeff] = dd;
+ fP[kXy_Coeff] = -2 * ad;
+ fP[kYy_Coeff] = aa;
+ double be = b * e;
+ double bde = be * d;
+ double cdd = c * dd;
+ double ee = e * e;
+ fP[kX_Coeff] = -2*cdd + bde - a*ee + 2*ad*f;
+ double aaf = aa * f;
+ double abe = a * be;
+ double ac = a * c;
+ double bb_2ac = b*b - 2*ac;
+ fP[kY_Coeff] = -2*aaf + abe - d*bb_2ac;
+ fP[kC_Coeff] = aaf*f + ac*ee + d*f*bb_2ac - abe*f + c*cdd - c*bde;
+ }
+}
+
+ /* Given a pair of quadratics, determine their parametric coefficients.
+ * If the scaled coefficients are nearly equal, then the part of the quadratics
+ * may be coincident.
+ * OPTIMIZATION -- since comparison short-circuits on no match,
+ * lazily compute the coefficients, comparing the easiest to compute first.
+ * xx and yy first; then xy; and so on.
+ */
+bool SkDQuadImplicit::match(const SkDQuadImplicit& p2) const {
+ int first = 0;
+ for (int index = 0; index <= kC_Coeff; ++index) {
+ if (approximately_zero(fP[index]) && approximately_zero(p2.fP[index])) {
+ first += first == index;
+ continue;
+ }
+ if (first == index) {
+ continue;
+ }
+ if (!AlmostEqualUlps(fP[index] * p2.fP[first], fP[first] * p2.fP[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SkDQuadImplicit::Match(const SkDQuad& quad1, const SkDQuad& quad2) {
+ SkDQuadImplicit i1(quad1); // a'xx , b'xy , c'yy , d'x , e'y , f
+ SkDQuadImplicit i2(quad2);
+ return i1.match(i2);
+}
diff --git a/pathops/SkDQuadImplicit.h b/pathops/SkDQuadImplicit.h
new file mode 100644
index 00000000..24f1aac2
--- /dev/null
+++ b/pathops/SkDQuadImplicit.h
@@ -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.
+ */
+#ifndef SkDQuadImplicit_DEFINED
+#define SkDQuadImplicit_DEFINED
+
+#include "SkPathOpsQuad.h"
+
+class SkDQuadImplicit {
+public:
+ explicit SkDQuadImplicit(const SkDQuad& q);
+
+ bool match(const SkDQuadImplicit& two) const;
+ static bool Match(const SkDQuad& quad1, const SkDQuad& quad2);
+
+ double x2() const { return fP[kXx_Coeff]; }
+ double xy() const { return fP[kXy_Coeff]; }
+ double y2() const { return fP[kYy_Coeff]; }
+ double x() const { return fP[kX_Coeff]; }
+ double y() const { return fP[kY_Coeff]; }
+ double c() const { return fP[kC_Coeff]; }
+
+private:
+ enum Coeffs {
+ kXx_Coeff,
+ kXy_Coeff,
+ kYy_Coeff,
+ kX_Coeff,
+ kY_Coeff,
+ kC_Coeff,
+ };
+
+ double fP[kC_Coeff + 1];
+};
+
+#endif
diff --git a/pathops/SkDQuadIntersection.cpp b/pathops/SkDQuadIntersection.cpp
new file mode 100644
index 00000000..e10fb3f1
--- /dev/null
+++ b/pathops/SkDQuadIntersection.cpp
@@ -0,0 +1,551 @@
+// 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 "SkDQuadImplicit.h"
+#include "SkIntersections.h"
+#include "SkPathOpsLine.h"
+#include "SkQuarticRoot.h"
+#include "SkTArray.h"
+#include "SkTSort.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 SkDQuadImplicit& i, const SkDQuad& quad, double roots[4],
+ bool oneHint, bool flip, int firstCubicRoot) {
+ SkDQuad flipped;
+ const SkDQuad& q = flip ? (flipped = quad.flip()) : quad;
+ double a, b, c;
+ SkDQuad::SetABC(&q[0].fX, &a, &b, &c);
+ double d, e, f;
+ SkDQuad::SetABC(&q[0].fY, &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();
+ int rootCount = SkReducedQuarticRoots(t4, t3, t2, t1, t0, oneHint, roots);
+ if (rootCount < 0) {
+ rootCount = SkQuarticRootsReal(firstCubicRoot, t4, t3, t2, t1, t0, roots);
+ }
+ if (flip) {
+ for (int index = 0; index < rootCount; ++index) {
+ roots[index] = 1 - roots[index];
+ }
+ }
+ return rootCount;
+}
+
+static int addValidRoots(const double roots[4], const int count, double valid[4]) {
+ int result = 0;
+ 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;
+ }
+ valid[result++] = t;
+ }
+ return result;
+}
+
+static bool only_end_pts_in_common(const SkDQuad& q1, const SkDQuad& q2) {
+// 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 SkDPoint* endPt[2];
+ for (int opp = 1; opp < 3; ++opp) {
+ int end = oddMan ^ opp; // choose a value not equal to oddMan
+ if (3 == end) { // and correct so that largest value is 1 or 2
+ end = opp;
+ }
+ endPt[opp - 1] = &q1[end];
+ }
+ double origX = endPt[0]->fX;
+ double origY = endPt[0]->fY;
+ double adj = endPt[1]->fX - origX;
+ double opp = endPt[1]->fY - origY;
+ double sign = (q1[oddMan].fY - origY) * adj - (q1[oddMan].fX - origX) * opp;
+ if (approximately_zero(sign)) {
+ goto tryNextHalfPlane;
+ }
+ for (int n = 0; n < 3; ++n) {
+ double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp;
+ if (test * sign > 0 && !precisely_zero(test)) {
+ goto tryNextHalfPlane;
+ }
+ }
+ return true;
+tryNextHalfPlane:
+ ;
+ }
+ return false;
+}
+
+// returns false if there's more than one intercept or the intercept doesn't match the point
+// returns true if the intercept was successfully added or if the
+// original quads need to be subdivided
+static bool add_intercept(const SkDQuad& q1, const SkDQuad& q2, double tMin, double tMax,
+ SkIntersections* i, bool* subDivide) {
+ double tMid = (tMin + tMax) / 2;
+ SkDPoint mid = q2.ptAtT(tMid);
+ SkDLine line;
+ line[0] = line[1] = mid;
+ SkDVector dxdy = q2.dxdyAtT(tMid);
+ line[0] -= dxdy;
+ line[1] += dxdy;
+ SkIntersections rootTs;
+ rootTs.allowNear(false);
+ int roots = rootTs.intersect(q1, line);
+ if (roots == 0) {
+ if (subDivide) {
+ *subDivide = true;
+ }
+ return true;
+ }
+ if (roots == 2) {
+ return false;
+ }
+ SkDPoint pt2 = q1.ptAtT(rootTs[0][0]);
+ if (!pt2.approximatelyEqualHalf(mid)) {
+ return false;
+ }
+ i->insertSwap(rootTs[0][0], tMid, pt2);
+ return true;
+}
+
+static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2,
+ double t2s, double t2e, SkIntersections* i, bool* subDivide) {
+ SkDQuad hull = q1.subDivide(t1s, t1e);
+ SkDLine line = {{hull[2], hull[0]}};
+ const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] };
+ const size_t kTestCount = SK_ARRAY_COUNT(testLines);
+ SkSTArray<kTestCount * 2, double, true> tsFound;
+ for (size_t index = 0; index < kTestCount; ++index) {
+ SkIntersections rootTs;
+ rootTs.allowNear(false);
+ int roots = rootTs.intersect(q2, *testLines[index]);
+ for (int idx2 = 0; idx2 < roots; ++idx2) {
+ double t = rootTs[0][idx2];
+#ifdef SK_DEBUG
+ SkDPoint qPt = q2.ptAtT(t);
+ SkDPoint lPt = testLines[index]->ptAtT(rootTs[1][idx2]);
+ SkASSERT(qPt.approximatelyEqual(lPt));
+#endif
+ if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) {
+ continue;
+ }
+ tsFound.push_back(rootTs[0][idx2]);
+ }
+ }
+ int tCount = tsFound.count();
+ if (tCount <= 0) {
+ return true;
+ }
+ double tMin, tMax;
+ if (tCount == 1) {
+ tMin = tMax = tsFound[0];
+ } else {
+ SkASSERT(tCount > 1);
+ SkTQSort<double>(tsFound.begin(), tsFound.end() - 1);
+ tMin = tsFound[0];
+ tMax = tsFound[tsFound.count() - 1];
+ }
+ SkDPoint end = q2.ptAtT(t2s);
+ bool startInTriangle = hull.pointInHull(end);
+ if (startInTriangle) {
+ tMin = t2s;
+ }
+ end = q2.ptAtT(t2e);
+ bool endInTriangle = hull.pointInHull(end);
+ if (endInTriangle) {
+ tMax = t2e;
+ }
+ int split = 0;
+ SkDVector dxy1, dxy2;
+ if (tMin != tMax || tCount > 2) {
+ dxy2 = q2.dxdyAtT(tMin);
+ for (int index = 1; index < tCount; ++index) {
+ dxy1 = dxy2;
+ dxy2 = q2.dxdyAtT(tsFound[index]);
+ double dot = dxy1.dot(dxy2);
+ if (dot < 0) {
+ split = index - 1;
+ break;
+ }
+ }
+ }
+ if (split == 0) { // there's one point
+ if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) {
+ return true;
+ }
+ i->swap();
+ return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide);
+ }
+ // At this point, we have two ranges of t values -- treat each separately at the split
+ bool result;
+ if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) {
+ result = true;
+ } else {
+ i->swap();
+ result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide);
+ }
+ if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) {
+ result = true;
+ } else {
+ i->swap();
+ result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide);
+ }
+ return result;
+}
+
+static double flat_measure(const SkDQuad& q) {
+ SkDVector mid = q[1] - q[0];
+ SkDVector dxy = q[2] - q[0];
+ double length = dxy.length(); // OPTIMIZE: get rid of sqrt
+ return fabs(mid.cross(dxy) / length);
+}
+
+// FIXME ? should this measure both and then use the quad that is the flattest as the line?
+static bool is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) {
+ double measure = flat_measure(q1);
+ // OPTIMIZE: (get rid of sqrt) use approximately_zero
+ if (!approximately_zero_sqrt(measure)) {
+ return false;
+ }
+ return is_linear_inner(q1, 0, 1, q2, 0, 1, i, NULL);
+}
+
+// FIXME: if flat measure is sufficiently large, then probably the quartic solution failed
+static void relaxed_is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) {
+ double m1 = flat_measure(q1);
+ double m2 = flat_measure(q2);
+#if DEBUG_FLAT_QUADS
+ double min = SkTMin(m1, m2);
+ if (min > 5) {
+ SkDebugf("%s maybe not flat enough.. %1.9g\n", __FUNCTION__, min);
+ }
+#endif
+ i->reset();
+ const SkDQuad& rounder = m2 < m1 ? q1 : q2;
+ const SkDQuad& flatter = m2 < m1 ? q2 : q1;
+ bool subDivide = false;
+ is_linear_inner(flatter, 0, 1, rounder, 0, 1, i, &subDivide);
+ if (subDivide) {
+ SkDQuadPair pair = flatter.chopAt(0.5);
+ SkIntersections firstI, secondI;
+ relaxed_is_linear(pair.first(), rounder, &firstI);
+ for (int index = 0; index < firstI.used(); ++index) {
+ i->insert(firstI[0][index] * 0.5, firstI[1][index], firstI.pt(index));
+ }
+ relaxed_is_linear(pair.second(), rounder, &secondI);
+ for (int index = 0; index < secondI.used(); ++index) {
+ i->insert(0.5 + secondI[0][index] * 0.5, secondI[1][index], secondI.pt(index));
+ }
+ }
+ if (m2 < m1) {
+ i->swapPts();
+ }
+}
+
+// each time through the loop, this computes values it had from the last loop
+// if i == j == 1, the center values are still good
+// otherwise, for i != 1 or j != 1, four of the values are still good
+// and if i == 1 ^ j == 1, an additional value is good
+static bool binary_search(const SkDQuad& quad1, const SkDQuad& quad2, double* t1Seed,
+ double* t2Seed, SkDPoint* pt) {
+ double tStep = ROUGH_EPSILON;
+ SkDPoint t1[3], t2[3];
+ int calcMask = ~0;
+ do {
+ if (calcMask & (1 << 1)) t1[1] = quad1.ptAtT(*t1Seed);
+ if (calcMask & (1 << 4)) t2[1] = quad2.ptAtT(*t2Seed);
+ if (t1[1].approximatelyEqual(t2[1])) {
+ *pt = t1[1];
+ #if ONE_OFF_DEBUG
+ SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__,
+ t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY);
+ #endif
+ return true;
+ }
+ if (calcMask & (1 << 0)) t1[0] = quad1.ptAtT(*t1Seed - tStep);
+ if (calcMask & (1 << 2)) t1[2] = quad1.ptAtT(*t1Seed + tStep);
+ if (calcMask & (1 << 3)) t2[0] = quad2.ptAtT(*t2Seed - tStep);
+ if (calcMask & (1 << 5)) t2[2] = quad2.ptAtT(*t2Seed + tStep);
+ double dist[3][3];
+ // OPTIMIZE: using calcMask value permits skipping some distance calcuations
+ // if prior loop's results are moved to correct slot for reuse
+ dist[1][1] = t1[1].distanceSquared(t2[1]);
+ int best_i = 1, best_j = 1;
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ if (i == 1 && j == 1) {
+ continue;
+ }
+ dist[i][j] = t1[i].distanceSquared(t2[j]);
+ if (dist[best_i][best_j] > dist[i][j]) {
+ best_i = i;
+ best_j = j;
+ }
+ }
+ }
+ if (best_i == 1 && best_j == 1) {
+ tStep /= 2;
+ if (tStep < FLT_EPSILON_HALF) {
+ break;
+ }
+ calcMask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5);
+ continue;
+ }
+ if (best_i == 0) {
+ *t1Seed -= tStep;
+ t1[2] = t1[1];
+ t1[1] = t1[0];
+ calcMask = 1 << 0;
+ } else if (best_i == 2) {
+ *t1Seed += tStep;
+ t1[0] = t1[1];
+ t1[1] = t1[2];
+ calcMask = 1 << 2;
+ } else {
+ calcMask = 0;
+ }
+ if (best_j == 0) {
+ *t2Seed -= tStep;
+ t2[2] = t2[1];
+ t2[1] = t2[0];
+ calcMask |= 1 << 3;
+ } else if (best_j == 2) {
+ *t2Seed += tStep;
+ t2[0] = t2[1];
+ t2[1] = t2[2];
+ calcMask |= 1 << 5;
+ }
+ } while (true);
+#if ONE_OFF_DEBUG
+ SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) != (%1.9g,%1.9g) %s\n", __FUNCTION__,
+ t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY);
+#endif
+ return false;
+}
+
+static void lookNearEnd(const SkDQuad& q1, const SkDQuad& q2, int testT,
+ const SkIntersections& orig, bool swap, SkIntersections* i) {
+ if (orig.used() == 1 && orig[!swap][0] == testT) {
+ return;
+ }
+ if (orig.used() == 2 && orig[!swap][1] == testT) {
+ return;
+ }
+ SkDLine tmpLine;
+ int testTIndex = testT << 1;
+ tmpLine[0] = tmpLine[1] = q2[testTIndex];
+ tmpLine[1].fX += q2[1].fY - q2[testTIndex].fY;
+ tmpLine[1].fY -= q2[1].fX - q2[testTIndex].fX;
+ SkIntersections impTs;
+ impTs.intersectRay(q1, tmpLine);
+ for (int index = 0; index < impTs.used(); ++index) {
+ SkDPoint realPt = impTs.pt(index);
+ if (!tmpLine[0].approximatelyEqualHalf(realPt)) {
+ continue;
+ }
+ if (swap) {
+ i->insert(testT, impTs[0][index], tmpLine[0]);
+ } else {
+ i->insert(impTs[0][index], testT, tmpLine[0]);
+ }
+ }
+}
+
+int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) {
+ // if the quads share an end point, check to see if they overlap
+
+ for (int i1 = 0; i1 < 3; i1 += 2) {
+ for (int i2 = 0; i2 < 3; i2 += 2) {
+ if (q1[i1].approximatelyEqualHalf(q2[i2])) {
+ insert(i1 >> 1, i2 >> 1, q1[i1]);
+ }
+ }
+ }
+ SkASSERT(fUsed < 3);
+ if (only_end_pts_in_common(q1, q2)) {
+ return fUsed;
+ }
+ if (only_end_pts_in_common(q2, q1)) {
+ return fUsed;
+ }
+ // see if either quad is really a line
+ // FIXME: figure out why reduce step didn't find this earlier
+ if (is_linear(q1, q2, this)) {
+ return fUsed;
+ }
+ SkIntersections swapped;
+ if (is_linear(q2, q1, &swapped)) {
+ swapped.swapPts();
+ set(swapped);
+ return fUsed;
+ }
+ SkIntersections copyI(*this);
+ lookNearEnd(q1, q2, 0, *this, false, &copyI);
+ lookNearEnd(q1, q2, 1, *this, false, &copyI);
+ lookNearEnd(q2, q1, 0, *this, true, &copyI);
+ lookNearEnd(q2, q1, 1, *this, true, &copyI);
+ int innerEqual = 0;
+ if (copyI.fUsed >= 2) {
+ SkASSERT(copyI.fUsed <= 4);
+ double width = copyI[0][1] - copyI[0][0];
+ int midEnd = 1;
+ for (int index = 2; index < copyI.fUsed; ++index) {
+ double testWidth = copyI[0][index] - copyI[0][index - 1];
+ if (testWidth <= width) {
+ continue;
+ }
+ midEnd = index;
+ }
+ for (int index = 0; index < 2; ++index) {
+ double testT = (copyI[0][midEnd] * (index + 1)
+ + copyI[0][midEnd - 1] * (2 - index)) / 3;
+ SkDPoint testPt1 = q1.ptAtT(testT);
+ testT = (copyI[1][midEnd] * (index + 1) + copyI[1][midEnd - 1] * (2 - index)) / 3;
+ SkDPoint testPt2 = q2.ptAtT(testT);
+ innerEqual += testPt1.approximatelyEqual(testPt2);
+ }
+ }
+ bool expectCoincident = copyI.fUsed >= 2 && innerEqual == 2;
+ if (expectCoincident) {
+ reset();
+ insertCoincident(copyI[0][0], copyI[1][0], copyI.fPt[0]);
+ int last = copyI.fUsed - 1;
+ insertCoincident(copyI[0][last], copyI[1][last], copyI.fPt[last]);
+ return fUsed;
+ }
+ SkDQuadImplicit i1(q1);
+ SkDQuadImplicit i2(q2);
+ int index;
+ bool flip1 = q1[2] == q2[0];
+ bool flip2 = q1[0] == q2[2];
+ bool useCubic = q1[0] == q2[0];
+ double roots1[4];
+ int rootCount = findRoots(i2, q1, roots1, useCubic, flip1, 0);
+ // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1
+ double roots1Copy[4];
+ int r1Count = addValidRoots(roots1, rootCount, roots1Copy);
+ SkDPoint pts1[4];
+ for (index = 0; index < r1Count; ++index) {
+ pts1[index] = q1.ptAtT(roots1Copy[index]);
+ }
+ double roots2[4];
+ int rootCount2 = findRoots(i1, q2, roots2, useCubic, flip2, 0);
+ double roots2Copy[4];
+ int r2Count = addValidRoots(roots2, rootCount2, roots2Copy);
+ SkDPoint pts2[4];
+ for (index = 0; index < r2Count; ++index) {
+ pts2[index] = q2.ptAtT(roots2Copy[index]);
+ }
+ if (r1Count == r2Count && r1Count <= 1) {
+ if (r1Count == 1) {
+ if (pts1[0].approximatelyEqualHalf(pts2[0])) {
+ insert(roots1Copy[0], roots2Copy[0], pts1[0]);
+ } else if (pts1[0].moreRoughlyEqual(pts2[0])) {
+ // experiment: try to find intersection by chasing t
+ rootCount = findRoots(i2, q1, roots1, useCubic, flip1, 0);
+ (void) addValidRoots(roots1, rootCount, roots1Copy);
+ rootCount2 = findRoots(i1, q2, roots2, useCubic, flip2, 0);
+ (void) addValidRoots(roots2, rootCount2, roots2Copy);
+ if (binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) {
+ insert(roots1Copy[0], roots2Copy[0], pts1[0]);
+ }
+ }
+ }
+ return fUsed;
+ }
+ int closest[4];
+ double dist[4];
+ bool foundSomething = false;
+ for (index = 0; index < r1Count; ++index) {
+ dist[index] = DBL_MAX;
+ closest[index] = -1;
+ for (int ndex2 = 0; ndex2 < r2Count; ++ndex2) {
+ if (!pts2[ndex2].approximatelyEqualHalf(pts1[index])) {
+ continue;
+ }
+ double dx = pts2[ndex2].fX - pts1[index].fX;
+ double dy = pts2[ndex2].fY - pts1[index].fY;
+ 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;
+ foundSomething = true;
+ next:
+ ;
+ }
+ }
+ if (r1Count && r2Count && !foundSomething) {
+ relaxed_is_linear(q1, q2, this);
+ return fUsed;
+ }
+ int used = 0;
+ do {
+ double lowest = DBL_MAX;
+ int lowestIndex = -1;
+ for (index = 0; index < r1Count; ++index) {
+ if (closest[index] < 0) {
+ continue;
+ }
+ if (roots1Copy[index] < lowest) {
+ lowestIndex = index;
+ lowest = roots1Copy[index];
+ }
+ }
+ if (lowestIndex < 0) {
+ break;
+ }
+ insert(roots1Copy[lowestIndex], roots2Copy[closest[lowestIndex]],
+ pts1[lowestIndex]);
+ closest[lowestIndex] = -1;
+ } while (++used < r1Count);
+ return fUsed;
+}
diff --git a/pathops/SkDQuadLineIntersection.cpp b/pathops/SkDQuadLineIntersection.cpp
new file mode 100644
index 00000000..58b3060a
--- /dev/null
+++ b/pathops/SkDQuadLineIntersection.cpp
@@ -0,0 +1,402 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.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:
+ enum PinTPoint {
+ kPointUninitialized,
+ kPointInitialized
+ };
+
+ LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i)
+ : fQuad(q)
+ , fLine(l)
+ , fIntersections(i)
+ , fAllowNear(true) {
+ }
+
+ void allowNear(bool allow) {
+ fAllowNear = allow;
+ }
+
+ 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].fX - line[0].fX (adjacent side of the right triangle)
+ O = line[1].fY - line[0].fY (opposite side of the right triangle)
+ for each of the three points (e.g. n = 0 to 2)
+ quad[n].fY' = (quad[n].fY - line[0].fY) * A - (quad[n].fX - line[0].fX) * O
+ */
+ double adj = fLine[1].fX - fLine[0].fX;
+ double opp = fLine[1].fY - fLine[0].fY;
+ double r[3];
+ for (int n = 0; n < 3; ++n) {
+ r[n] = (fQuad[n].fY - fLine[0].fY) * adj - (fQuad[n].fX - fLine[0].fX) * 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 SkDQuad::RootsValidT(A, 2 * B, C, roots);
+ }
+
+ int intersect() {
+ addExactEndPoints();
+ double rootVals[2];
+ int roots = intersectRay(rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ double lineT = findLineT(quadT);
+ SkDPoint pt;
+ if (pinTs(&quadT, &lineT, &pt, kPointUninitialized)) {
+ fIntersections->insert(quadT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearEndPoints();
+ }
+ return fIntersections->used();
+ }
+
+ int horizontalIntersect(double axisIntercept, double roots[2]) {
+ double D = fQuad[2].fY; // f
+ double E = fQuad[1].fY; // e
+ double F = fQuad[0].fY; // d
+ D += F - 2 * E; // D = d - 2*e + f
+ E -= F; // E = -(d - e)
+ F -= axisIntercept;
+ return SkDQuad::RootsValidT(D, 2 * E, F, roots);
+ }
+
+ int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) {
+ addExactHorizontalEndPoints(left, right, axisIntercept);
+ double rootVals[2];
+ int roots = horizontalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ SkDPoint pt = fQuad.ptAtT(quadT);
+ double lineT = (pt.fX - left) / (right - left);
+ if (pinTs(&quadT, &lineT, &pt, kPointInitialized)) {
+ fIntersections->insert(quadT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearHorizontalEndPoints(left, right, axisIntercept);
+ }
+ if (flipped) {
+ fIntersections->flip();
+ }
+ return fIntersections->used();
+ }
+
+ int verticalIntersect(double axisIntercept, double roots[2]) {
+ double D = fQuad[2].fX; // f
+ double E = fQuad[1].fX; // e
+ double F = fQuad[0].fX; // d
+ D += F - 2 * E; // D = d - 2*e + f
+ E -= F; // E = -(d - e)
+ F -= axisIntercept;
+ return SkDQuad::RootsValidT(D, 2 * E, F, roots);
+ }
+
+ int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) {
+ addExactVerticalEndPoints(top, bottom, axisIntercept);
+ double rootVals[2];
+ int roots = verticalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ SkDPoint pt = fQuad.ptAtT(quadT);
+ double lineT = (pt.fY - top) / (bottom - top);
+ if (pinTs(&quadT, &lineT, &pt, kPointInitialized)) {
+ fIntersections->insert(quadT, lineT, pt);
+ }
+ }
+ if (fAllowNear) {
+ addNearVerticalEndPoints(top, bottom, axisIntercept);
+ }
+ if (flipped) {
+ fIntersections->flip();
+ }
+ return fIntersections->used();
+ }
+
+protected:
+ // add endpoints first to get zero and one t values exactly
+ void addExactEndPoints() {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double lineT = fLine.exactPoint(fQuad[qIndex]);
+ if (lineT < 0) {
+ continue;
+ }
+ double quadT = (double) (qIndex >> 1);
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ }
+
+ void addNearEndPoints() {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double quadT = (double) (qIndex >> 1);
+ if (fIntersections->hasT(quadT)) {
+ continue;
+ }
+ double lineT = fLine.nearPoint(fQuad[qIndex]);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ // FIXME: see if line end is nearly on quad
+ }
+
+ void addExactHorizontalEndPoints(double left, double right, double y) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double lineT = SkDLine::ExactPointH(fQuad[qIndex], left, right, y);
+ if (lineT < 0) {
+ continue;
+ }
+ double quadT = (double) (qIndex >> 1);
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ }
+
+ void addNearHorizontalEndPoints(double left, double right, double y) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double quadT = (double) (qIndex >> 1);
+ if (fIntersections->hasT(quadT)) {
+ continue;
+ }
+ double lineT = SkDLine::NearPointH(fQuad[qIndex], left, right, y);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ // FIXME: see if line end is nearly on quad
+ }
+
+ void addExactVerticalEndPoints(double top, double bottom, double x) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double lineT = SkDLine::ExactPointV(fQuad[qIndex], top, bottom, x);
+ if (lineT < 0) {
+ continue;
+ }
+ double quadT = (double) (qIndex >> 1);
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ }
+
+ void addNearVerticalEndPoints(double top, double bottom, double x) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ double quadT = (double) (qIndex >> 1);
+ if (fIntersections->hasT(quadT)) {
+ continue;
+ }
+ double lineT = SkDLine::NearPointV(fQuad[qIndex], top, bottom, x);
+ if (lineT < 0) {
+ continue;
+ }
+ fIntersections->insert(quadT, lineT, fQuad[qIndex]);
+ }
+ // FIXME: see if line end is nearly on quad
+ }
+
+ double findLineT(double t) {
+ SkDPoint xy = fQuad.ptAtT(t);
+ double dx = fLine[1].fX - fLine[0].fX;
+ double dy = fLine[1].fY - fLine[0].fY;
+ double dxT = (xy.fX - fLine[0].fX) / dx;
+ double dyT = (xy.fY - fLine[0].fY) / dy;
+ if (!between(FLT_EPSILON, dxT, 1 - FLT_EPSILON) && between(0, dyT, 1)) {
+ return dyT;
+ }
+ if (!between(FLT_EPSILON, dyT, 1 - FLT_EPSILON) && between(0, dxT, 1)) {
+ return dxT;
+ }
+ return fabs(dx) > fabs(dy) ? dxT : dyT;
+ }
+
+ bool pinTs(double* quadT, double* lineT, SkDPoint* pt, PinTPoint ptSet) {
+ if (!approximately_one_or_less(*lineT)) {
+ return false;
+ }
+ if (!approximately_zero_or_more(*lineT)) {
+ return false;
+ }
+ double qT = *quadT = SkPinT(*quadT);
+ double lT = *lineT = SkPinT(*lineT);
+ if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) {
+ *pt = fLine.ptAtT(lT);
+ } else if (ptSet == kPointUninitialized) {
+ *pt = fQuad.ptAtT(qT);
+ }
+ return true;
+ }
+
+private:
+ const SkDQuad& fQuad;
+ const SkDLine& fLine;
+ SkIntersections* fIntersections;
+ bool fAllowNear;
+};
+
+// utility for pairs of coincident quads
+static double horizontalIntersect(const SkDQuad& quad, const SkDPoint& pt) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)),
+ static_cast<SkIntersections*>(0));
+ double rootVals[2];
+ int roots = q.horizontalIntersect(pt.fY, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double t = rootVals[index];
+ SkDPoint qPt = quad.ptAtT(t);
+ if (AlmostEqualUlps(qPt.fX, pt.fX)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+static double verticalIntersect(const SkDQuad& quad, const SkDPoint& pt) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)),
+ static_cast<SkIntersections*>(0));
+ double rootVals[2];
+ int roots = q.verticalIntersect(pt.fX, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double t = rootVals[index];
+ SkDPoint qPt = quad.ptAtT(t);
+ if (AlmostEqualUlps(qPt.fY, pt.fY)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+double SkIntersections::Axial(const SkDQuad& q1, const SkDPoint& p, bool vertical) {
+ if (vertical) {
+ return verticalIntersect(q1, p);
+ }
+ return horizontalIntersect(q1, p);
+}
+
+int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y,
+ bool flipped) {
+ SkDLine line = {{{ left, y }, { right, y }}};
+ LineQuadraticIntersections q(quad, line, this);
+ return q.horizontalIntersect(y, left, right, flipped);
+}
+
+int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x,
+ bool flipped) {
+ SkDLine line = {{{ x, top }, { x, bottom }}};
+ LineQuadraticIntersections q(quad, line, this);
+ return q.verticalIntersect(x, top, bottom, flipped);
+}
+
+int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) {
+ LineQuadraticIntersections q(quad, line, this);
+ q.allowNear(fAllowNear);
+ return q.intersect();
+}
+
+int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) {
+ LineQuadraticIntersections q(quad, line, this);
+ fUsed = q.intersectRay(fT[0]);
+ for (int index = 0; index < fUsed; ++index) {
+ fPt[index] = quad.ptAtT(fT[0][index]);
+ }
+ return fUsed;
+}
diff --git a/pathops/SkIntersectionHelper.h b/pathops/SkIntersectionHelper.h
new file mode 100644
index 00000000..5d8ebcd5
--- /dev/null
+++ b/pathops/SkIntersectionHelper.h
@@ -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 "SkOpContour.h"
+#include "SkPath.h"
+
+class SkIntersectionHelper {
+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(SkIntersectionHelper& other, const SkIntersections& 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(const SkIntersectionHelper& other, const SkPoint& pt, double newT) {
+ return fContour->addT(fIndex, other.fContour, other.fIndex, pt, newT);
+ }
+
+ int addSelfT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) {
+ return fContour->addSelfT(fIndex, other.fContour, other.fIndex, pt, newT);
+ }
+
+ int addUnsortableT(const SkIntersectionHelper& other, bool start, const SkPoint& pt,
+ double newT) {
+ return fContour->addUnsortableT(fIndex, other.fContour, other.fIndex, start, pt, newT);
+ }
+
+ bool advance() {
+ return ++fIndex < fLast;
+ }
+
+ SkScalar bottom() const {
+ return bounds().fBottom;
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fContour->segments()[fIndex].bounds();
+ }
+
+ void init(SkOpContour* contour) {
+ fContour = contour;
+ fIndex = 0;
+ fLast = contour->segments().count();
+ }
+
+ bool isAdjacent(const SkIntersectionHelper& next) {
+ return fContour == next.fContour && fIndex + 1 == next.fIndex;
+ }
+
+ bool isFirstLast(const SkIntersectionHelper& next) {
+ return fContour == next.fContour && fIndex == 0
+ && next.fIndex == fLast - 1;
+ }
+
+ SkScalar left() const {
+ return bounds().fLeft;
+ }
+
+ const SkPoint* pts() const {
+ return fContour->segments()[fIndex].pts();
+ }
+
+ SkScalar right() const {
+ return bounds().fRight;
+ }
+
+ SegmentType segmentType() const {
+ const SkOpSegment& 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 SkIntersectionHelper& 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;
+ }
+
+private:
+ SkOpContour* fContour;
+ int fIndex;
+ int fLast;
+};
diff --git a/pathops/SkIntersections.cpp b/pathops/SkIntersections.cpp
new file mode 100644
index 00000000..242c67b9
--- /dev/null
+++ b/pathops/SkIntersections.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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 "SkIntersections.h"
+
+int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = {
+ NULL,
+ &SkIntersections::verticalLine,
+ &SkIntersections::verticalQuad,
+ &SkIntersections::verticalCubic
+};
+
+int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine&) = {
+ NULL,
+ NULL,
+ &SkIntersections::quadRay,
+ &SkIntersections::cubicRay
+};
+
+int SkIntersections::coincidentUsed() const {
+ if (!fIsCoincident[0]) {
+ SkASSERT(!fIsCoincident[1]);
+ return 0;
+ }
+ int count = 0;
+ SkDEBUGCODE(int count2 = 0;)
+ for (int index = 0; index < fUsed; ++index) {
+ if (fIsCoincident[0] & (1 << index)) {
+ ++count;
+ }
+#ifdef SK_DEBUG
+ if (fIsCoincident[1] & (1 << index)) {
+ ++count2;
+ }
+#endif
+ }
+ SkASSERT(count == count2);
+ return count;
+}
+
+int SkIntersections::cubicRay(const SkPoint pts[4], const SkDLine& line) {
+ SkDCubic cubic;
+ cubic.set(pts);
+ return intersectRay(cubic, line);
+}
+
+void SkIntersections::flip() {
+ for (int index = 0; index < fUsed; ++index) {
+ fT[1][index] = 1 - fT[1][index];
+ }
+}
+
+int SkIntersections::insert(double one, double two, const SkDPoint& pt) {
+ if (fIsCoincident[0] == 3 && between(fT[0][0], one, fT[0][1])) {
+ // For now, don't allow a mix of coincident and non-coincident intersections
+ return -1;
+ }
+ SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]);
+ int index;
+ for (index = 0; index < fUsed; ++index) {
+ double oldOne = fT[0][index];
+ double oldTwo = fT[1][index];
+ if (one == oldOne && two == oldTwo) {
+ return -1;
+ }
+ if (more_roughly_equal(oldOne, one) && more_roughly_equal(oldTwo, two)) {
+ if ((precisely_zero(one) && !precisely_zero(oldOne))
+ || (precisely_equal(one, 1) && !precisely_equal(oldOne, 1))
+ || (precisely_zero(two) && !precisely_zero(oldTwo))
+ || (precisely_equal(two, 1) && !precisely_equal(oldTwo, 1))) {
+ fT[0][index] = one;
+ fT[1][index] = two;
+ fPt[index] = pt;
+ }
+ return -1;
+ }
+ #if ONE_OFF_DEBUG
+ if (pt.roughlyEqual(fPt[index])) {
+ SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one);
+ }
+ #endif
+ if (fT[0][index] > one) {
+ break;
+ }
+ }
+ SkASSERT(fUsed < 9);
+ int remaining = fUsed - index;
+ if (remaining > 0) {
+ memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining);
+ 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);
+ fIsCoincident[0] += fIsCoincident[0] & ~((1 << index) - 1);
+ fIsCoincident[1] += fIsCoincident[1] & ~((1 << index) - 1);
+ }
+ fPt[index] = pt;
+ fT[0][index] = one;
+ fT[1][index] = two;
+ ++fUsed;
+ return index;
+}
+
+void SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) {
+ int index = insertSwap(one, two, pt);
+ int bit = 1 << index;
+ fIsCoincident[0] |= bit;
+ fIsCoincident[1] |= bit;
+}
+
+void SkIntersections::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;
+ }
+}
+
+int SkIntersections::quadRay(const SkPoint pts[3], const SkDLine& line) {
+ SkDQuad quad;
+ quad.set(pts);
+ return intersectRay(quad, line);
+}
+
+void SkIntersections::quickRemoveOne(int index, int replace) {
+ if (index < replace) {
+ fT[0][index] = fT[0][replace];
+ }
+}
+
+#if 0
+void SkIntersections::remove(double one, double two, const SkDPoint& startPt,
+ const SkDPoint& endPt) {
+ for (int index = fUsed - 1; index >= 0; --index) {
+ if (!(fIsCoincident[0] & (1 << index)) && (between(one, fT[fSwap][index], two)
+ || startPt.approximatelyEqual(fPt[index])
+ || endPt.approximatelyEqual(fPt[index]))) {
+ SkASSERT(fUsed > 0);
+ removeOne(index);
+ }
+ }
+}
+#endif
+
+void SkIntersections::removeOne(int index) {
+ int remaining = --fUsed - index;
+ if (remaining <= 0) {
+ return;
+ }
+ memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining);
+ memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining);
+ memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining);
+ SkASSERT(fIsCoincident[0] == 0);
+ int coBit = fIsCoincident[0] & (1 << index);
+ fIsCoincident[0] -= ((fIsCoincident[0] >> 1) & ~((1 << index) - 1)) + coBit;
+ SkASSERT(!(coBit ^ (fIsCoincident[1] & (1 << index))));
+ fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit;
+}
+
+void SkIntersections::swapPts() {
+ int index;
+ for (index = 0; index < fUsed; ++index) {
+ SkTSwap(fT[0][index], fT[1][index]);
+ }
+}
+
+int SkIntersections::verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDLine line;
+ line.set(a);
+ return vertical(line, top, bottom, x, flipped);
+}
+
+int SkIntersections::verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDQuad quad;
+ quad.set(a);
+ return vertical(quad, top, bottom, x, flipped);
+}
+
+int SkIntersections::verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return vertical(cubic, top, bottom, x, flipped);
+}
diff --git a/pathops/SkIntersections.h b/pathops/SkIntersections.h
new file mode 100644
index 00000000..26a1d1a5
--- /dev/null
+++ b/pathops/SkIntersections.h
@@ -0,0 +1,261 @@
+/*
+ * 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 SkIntersections_DEFINE
+#define SkIntersections_DEFINE
+
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsPoint.h"
+#include "SkPathOpsQuad.h"
+
+class SkIntersections {
+public:
+ SkIntersections()
+ : fSwap(0)
+#ifdef SK_DEBUG
+ , fDepth(0)
+#endif
+ {
+ sk_bzero(fPt, sizeof(fPt));
+ sk_bzero(fT, sizeof(fT));
+ sk_bzero(fIsCoincident, sizeof(fIsCoincident));
+ reset();
+ }
+
+ class TArray {
+ public:
+ explicit TArray(const double ts[9]) : fTArray(ts) {}
+ double operator[](int n) const {
+ return fTArray[n];
+ }
+ const double* fTArray;
+ };
+ TArray operator[](int n) const { return TArray(fT[n]); }
+
+ void set(const SkIntersections& i) {
+ memcpy(fPt, i.fPt, sizeof(fPt));
+ memcpy(fT, i.fT, sizeof(fT));
+ memcpy(fIsCoincident, i.fIsCoincident, sizeof(fIsCoincident));
+ fUsed = i.fUsed;
+ fSwap = i.fSwap;
+ SkDEBUGCODE(fDepth = i.fDepth);
+ }
+
+ void allowNear(bool nearAllowed) {
+ fAllowNear = nearAllowed;
+ }
+
+ int cubic(const SkPoint a[4]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return intersect(cubic);
+ }
+
+ int cubicCubic(const SkPoint a[4], const SkPoint b[4]) {
+ SkDCubic aCubic;
+ aCubic.set(a);
+ SkDCubic bCubic;
+ bCubic.set(b);
+ return intersect(aCubic, bCubic);
+ }
+
+ int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y,
+ bool flipped) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return horizontal(cubic, left, right, y, flipped);
+ }
+
+ int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return vertical(cubic, top, bottom, x, flipped);
+ }
+
+ int cubicLine(const SkPoint a[4], const SkPoint b[2]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDLine line;
+ line.set(b);
+ return intersect(cubic, line);
+ }
+
+ int cubicQuad(const SkPoint a[4], const SkPoint b[3]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDQuad quad;
+ quad.set(b);
+ return intersect(cubic, quad);
+ }
+
+ bool hasT(double t) const {
+ SkASSERT(t == 0 || t == 1);
+ return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1);
+ }
+
+ int insertSwap(double one, double two, const SkDPoint& pt) {
+ if (fSwap) {
+ return insert(two, one, pt);
+ } else {
+ return insert(one, two, pt);
+ }
+ }
+
+ bool isCoincident(int index) {
+ return (fIsCoincident[0] & 1 << index) != 0;
+ }
+
+ int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y,
+ bool flipped) {
+ SkDLine line;
+ line.set(a);
+ return horizontal(line, left, right, y, flipped);
+ }
+
+ int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
+ SkDLine line;
+ line.set(a);
+ return vertical(line, top, bottom, x, flipped);
+ }
+
+ int lineLine(const SkPoint a[2], const SkPoint b[2]) {
+ SkDLine aLine, bLine;
+ aLine.set(a);
+ bLine.set(b);
+ return intersect(aLine, bLine);
+ }
+
+ const SkDPoint& pt(int index) const {
+ return fPt[index];
+ }
+
+ int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y,
+ bool flipped) {
+ SkDQuad quad;
+ quad.set(a);
+ return horizontal(quad, left, right, y, flipped);
+ }
+
+ int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
+ SkDQuad quad;
+ quad.set(a);
+ return vertical(quad, top, bottom, x, flipped);
+ }
+
+ int quadLine(const SkPoint a[3], const SkPoint b[2]) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDLine line;
+ line.set(b);
+ return intersect(quad, line);
+ }
+
+ int quadQuad(const SkPoint a[3], const SkPoint b[3]) {
+ SkDQuad aQuad;
+ aQuad.set(a);
+ SkDQuad bQuad;
+ bQuad.set(b);
+ return intersect(aQuad, bQuad);
+ }
+
+ int quadRay(const SkPoint pts[3], const SkDLine& line);
+ void removeOne(int index);
+
+ // leaves flip, swap alone
+ void reset() {
+ fAllowNear = true;
+ fUsed = 0;
+ }
+
+ void swap() {
+ fSwap ^= true;
+ }
+
+ void swapPts();
+
+ bool swapped() const {
+ return fSwap;
+ }
+
+ int used() const {
+ return fUsed;
+ }
+
+ void downDepth() {
+ SkASSERT(--fDepth >= 0);
+ }
+
+ void upDepth() {
+ SkASSERT(++fDepth < 16);
+ }
+
+ static double Axial(const SkDQuad& , const SkDPoint& , bool vertical);
+ int coincidentUsed() const;
+ int cubicRay(const SkPoint pts[4], const SkDLine& line);
+ void flip();
+ int horizontal(const SkDLine&, double y);
+ int horizontal(const SkDLine&, double left, double right, double y, bool flipped);
+ int horizontal(const SkDQuad&, double left, double right, double y, bool flipped);
+ int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]);
+ int horizontal(const SkDCubic&, double y, double tRange[3]);
+ int horizontal(const SkDCubic&, double left, double right, double y, bool flipped);
+ int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]);
+ // FIXME : does not respect swap
+ int insert(double one, double two, const SkDPoint& pt);
+ // start if index == 0 : end if index == 1
+ void insertCoincident(double one, double two, const SkDPoint& pt);
+ int intersect(const SkDLine&, const SkDLine&);
+ int intersect(const SkDQuad&, const SkDLine&);
+ int intersect(const SkDQuad&, const SkDQuad&);
+ int intersect(const SkDCubic&); // return true if cubic self-intersects
+ int intersect(const SkDCubic&, const SkDLine&);
+ int intersect(const SkDCubic&, const SkDQuad&);
+ int intersect(const SkDCubic&, const SkDCubic&);
+ int intersectRay(const SkDLine&, const SkDLine&);
+ int intersectRay(const SkDQuad&, const SkDLine&);
+ int intersectRay(const SkDCubic&, const SkDLine&);
+ static SkDPoint Line(const SkDLine&, const SkDLine&);
+ void offset(int base, double start, double end);
+ void quickRemoveOne(int index, int replace);
+ static bool Test(const SkDLine& , const SkDLine&);
+ int vertical(const SkDLine&, double x);
+ int vertical(const SkDLine&, double top, double bottom, double x, bool flipped);
+ int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped);
+ int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped);
+ int verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+ int verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+ int verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+
+ int depth() const {
+#ifdef SK_DEBUG
+ return fDepth;
+#else
+ return 0;
+#endif
+ }
+
+private:
+ int computePoints(const SkDLine& line, int used);
+ // used by addCoincident to remove ordinary intersections in range
+ // void remove(double one, double two, const SkDPoint& startPt, const SkDPoint& endPt);
+
+ SkDPoint fPt[9];
+ double fT[2][9];
+ uint16_t fIsCoincident[2]; // bit arrays, one bit set for each coincident T
+ unsigned char fUsed;
+ bool fAllowNear;
+ bool fSwap;
+#ifdef SK_DEBUG
+ int fDepth;
+#endif
+};
+
+extern int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine& );
+extern int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped);
+
+#endif
diff --git a/pathops/SkLineParameters.h b/pathops/SkLineParameters.h
new file mode 100644
index 00000000..8824e54b
--- /dev/null
+++ b/pathops/SkLineParameters.h
@@ -0,0 +1,119 @@
+/*
+ * 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 "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+
+// Sources
+// computer-aided design - volume 22 number 9 november 1990 pp 538 - 549
+// online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+
+// This turns a line segment into a parameterized line, of the form
+// ax + by + c = 0
+// When a^2 + b^2 == 1, the line is normalized.
+// The distance to the line for (x, y) is d(x,y) = ax + by + c
+//
+// Note that the distances below are not necessarily normalized. To get the true
+// distance, it's necessary to either call normalize() after xxxEndPoints(), or
+// divide the result of xxxDistance() by sqrt(normalSquared())
+
+class SkLineParameters {
+public:
+ void cubicEndPoints(const SkDCubic& pts) {
+ cubicEndPoints(pts, 0, 1);
+ if (dx() == 0 && dy() == 0) {
+ cubicEndPoints(pts, 0, 2);
+ if (dx() == 0 && dy() == 0) {
+ cubicEndPoints(pts, 0, 3);
+ }
+ }
+ }
+
+ void cubicEndPoints(const SkDCubic& pts, int s, int e) {
+ a = pts[s].fY - pts[e].fY;
+ b = pts[e].fX - pts[s].fX;
+ c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY;
+ }
+
+ void lineEndPoints(const SkDLine& pts) {
+ a = pts[0].fY - pts[1].fY;
+ b = pts[1].fX - pts[0].fX;
+ c = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY;
+ }
+
+ void quadEndPoints(const SkDQuad& pts) {
+ quadEndPoints(pts, 0, 1);
+ if (dx() == 0 && dy() == 0) {
+ quadEndPoints(pts, 0, 2);
+ }
+ }
+
+ void quadEndPoints(const SkDQuad& pts, int s, int e) {
+ a = pts[s].fY - pts[e].fY;
+ b = pts[e].fX - pts[s].fX;
+ c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY;
+ }
+
+ double normalSquared() const {
+ return a * a + b * b;
+ }
+
+ bool normalize() {
+ double normal = sqrt(normalSquared());
+ if (approximately_zero(normal)) {
+ a = b = c = 0;
+ return false;
+ }
+ double reciprocal = 1 / normal;
+ a *= reciprocal;
+ b *= reciprocal;
+ c *= reciprocal;
+ return true;
+ }
+
+ void cubicDistanceY(const SkDCubic& pts, SkDCubic& distance) const {
+ double oneThird = 1 / 3.0;
+ for (int index = 0; index < 4; ++index) {
+ distance[index].fX = index * oneThird;
+ distance[index].fY = a * pts[index].fX + b * pts[index].fY + c;
+ }
+ }
+
+ void quadDistanceY(const SkDQuad& pts, SkDQuad& distance) const {
+ double oneHalf = 1 / 2.0;
+ for (int index = 0; index < 3; ++index) {
+ distance[index].fX = index * oneHalf;
+ distance[index].fY = a * pts[index].fX + b * pts[index].fY + c;
+ }
+ }
+
+ double controlPtDistance(const SkDCubic& pts, int index) const {
+ SkASSERT(index == 1 || index == 2);
+ return a * pts[index].fX + b * pts[index].fY + c;
+ }
+
+ double controlPtDistance(const SkDQuad& pts) const {
+ return a * pts[1].fX + b * pts[1].fY + c;
+ }
+
+ double pointDistance(const SkDPoint& pt) const {
+ return a * pt.fX + b * pt.fY + c;
+ }
+
+ double dx() const {
+ return b;
+ }
+
+ double dy() const {
+ return -a;
+ }
+
+private:
+ double a;
+ double b;
+ double c;
+};
diff --git a/pathops/SkOpAngle.cpp b/pathops/SkOpAngle.cpp
new file mode 100644
index 00000000..0dd0d65f
--- /dev/null
+++ b/pathops/SkOpAngle.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkOpAngle.h"
+#include "SkOpSegment.h"
+#include "SkPathOpsCurve.h"
+#include "SkTSort.h"
+
+#if DEBUG_ANGLE
+#include "SkString.h"
+
+static const char funcName[] = "SkOpSegment::operator<";
+static const int bugChar = strlen(funcName) + 1;
+#endif
+
+/* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest
+ positive y. The largest angle has a positive x and a zero y. */
+
+#if DEBUG_ANGLE
+ static bool CompareResult(SkString* bugOut, const char* append, bool compare) {
+ bugOut->appendf("%s", append);
+ bugOut->writable_str()[bugChar] = "><"[compare];
+ SkDebugf("%s\n", bugOut->c_str());
+ return compare;
+ }
+
+ #define COMPARE_RESULT(append, compare) CompareResult(&bugOut, append, compare)
+#else
+ #define COMPARE_RESULT(append, compare) compare
+#endif
+
+bool SkOpAngle::calcSlop(double x, double y, double rx, double ry, bool* result) const{
+ double absX = fabs(x);
+ double absY = fabs(y);
+ double length = absX < absY ? absX / 2 + absY : absX + absY / 2;
+ int exponent;
+ (void) frexp(length, &exponent);
+ double epsilon = ldexp(FLT_EPSILON, exponent);
+ SkPath::Verb verb = fSegment->verb();
+ SkASSERT(verb == SkPath::kQuad_Verb || verb == SkPath::kCubic_Verb);
+ // FIXME: the quad and cubic factors are made up ; determine actual values
+ double slop = verb == SkPath::kQuad_Verb ? 4 * epsilon : 512 * epsilon;
+ double xSlop = slop;
+ double ySlop = x * y < 0 ? -xSlop : xSlop; // OPTIMIZATION: use copysign / _copysign ?
+ double x1 = x - xSlop;
+ double y1 = y + ySlop;
+ double x_ry1 = x1 * ry;
+ double rx_y1 = rx * y1;
+ *result = x_ry1 < rx_y1;
+ double x2 = x + xSlop;
+ double y2 = y - ySlop;
+ double x_ry2 = x2 * ry;
+ double rx_y2 = rx * y2;
+ bool less2 = x_ry2 < rx_y2;
+ return *result == less2;
+}
+
+/*
+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
+
+FIXME: maybe I could set up LineParameters lazily
+*/
+bool SkOpAngle::operator<(const SkOpAngle& rh) const { // this/lh: left-hand; rh: right-hand
+ double y = dy();
+ double ry = rh.dy();
+#if DEBUG_ANGLE
+ SkString bugOut;
+ bugOut.printf("%s _ id=%d segId=%d tStart=%1.9g tEnd=%1.9g"
+ " | id=%d segId=%d tStart=%1.9g tEnd=%1.9g ", funcName,
+ fID, fSegment->debugID(), fSegment->t(fStart), fSegment->t(fEnd),
+ rh.fID, rh.fSegment->debugID(), rh.fSegment->t(rh.fStart), rh.fSegment->t(rh.fEnd));
+#endif
+ double y_ry = y * ry;
+ if (y_ry < 0) { // if y's are opposite signs, we can do a quick return
+ return COMPARE_RESULT("1 y * ry < 0", y < 0);
+ }
+ // at this point, both y's must be the same sign, or one (or both) is zero
+ double x = dx();
+ double rx = rh.dx();
+ if (x * rx < 0) { // if x's are opposite signs, use y to determine first or second half
+ if (y < 0 && ry < 0) { // if y's are negative, lh x is smaller if positive
+ return COMPARE_RESULT("2 x_rx < 0 && y < 0 ...", x > 0);
+ }
+ if (y >= 0 && ry >= 0) { // if y's are zero or positive, lh x is smaller if negative
+ return COMPARE_RESULT("3 x_rx < 0 && y >= 0 ...", x < 0);
+ }
+ SkASSERT((y == 0) ^ (ry == 0)); // if one y is zero and one is negative, neg y is smaller
+ return COMPARE_RESULT("4 x_rx < 0 && y == 0 ...", y < 0);
+ }
+ // at this point, both x's must be the same sign, or one (or both) is zero
+ if (y_ry == 0) { // if either y is zero
+ if (y + ry < 0) { // if the other y is less than zero, it must be smaller
+ return COMPARE_RESULT("5 y_ry == 0 && y + ry < 0", y < 0);
+ }
+ if (y + ry > 0) { // if a y is greater than zero and an x is positive, non zero is smaller
+ return COMPARE_RESULT("6 y_ry == 0 && y + ry > 0", (x + rx > 0) ^ (y == 0));
+ }
+ // at this point, both y's are zero, so lines are coincident or one is degenerate
+ SkASSERT(x * rx != 0); // and a degenerate line should haven't gotten this far
+ }
+ // see if either curve can be lengthened before trying the tangent
+ if (fSegment->other(fEnd) != rh.fSegment // tangents not absolutely identical
+ && rh.fSegment->other(rh.fEnd) != fSegment) { // and not intersecting
+ SkOpAngle longer = *this;
+ SkOpAngle rhLonger = rh;
+ if ((longer.lengthen(rh) | rhLonger.lengthen(*this)) // lengthen both
+ && (fUnorderable || !longer.fUnorderable)
+ && (rh.fUnorderable || !rhLonger.fUnorderable)) {
+#if DEBUG_ANGLE
+ bugOut.prepend(" ");
+#endif
+ return COMPARE_RESULT("10 longer.lengthen(rh) ...", longer < rhLonger);
+ }
+ }
+ if (y_ry != 0) { // if they aren't coincident, look for a stable cross product
+ // at this point, y's are the same sign, neither is zero
+ // and x's are the same sign, or one (or both) is zero
+ double x_ry = x * ry;
+ double rx_y = rx * y;
+ if (!fComputed && !rh.fComputed) {
+ if (!AlmostEqualUlps(x_ry, rx_y)) {
+ return COMPARE_RESULT("7 !fComputed && !rh.fComputed", x_ry < rx_y);
+ }
+ } else {
+ // if the vector was a result of subdividing a curve, see if it is stable
+ bool sloppy1 = x_ry < rx_y;
+ bool sloppy2 = !sloppy1;
+ if ((!fComputed || calcSlop(x, y, rx, ry, &sloppy1))
+ && (!rh.fComputed || rh.calcSlop(rx, ry, x, y, &sloppy2))
+ && sloppy1 != sloppy2) {
+ return COMPARE_RESULT("8 CalcSlop(x, y ...", sloppy1);
+ }
+ }
+ }
+ if (fSide * rh.fSide == 0) {
+ SkASSERT(fSide + rh.fSide != 0); // hitting this assert means coincidence was undetected
+ return COMPARE_RESULT("9 fSide * rh.fSide == 0 ...", fSide < rh.fSide);
+ }
+ // at this point, the initial tangent line is nearly coincident
+ // see if edges curl away from each other
+ if (fSide * rh.fSide < 0 && (!approximately_zero(fSide) || !approximately_zero(rh.fSide))) {
+ return COMPARE_RESULT("9b fSide * rh.fSide < 0 ...", fSide < rh.fSide);
+ }
+ if (fUnsortable || rh.fUnsortable) {
+ // even with no solution, return a stable sort
+ return COMPARE_RESULT("11 fUnsortable || rh.fUnsortable", this < &rh);
+ }
+ SkPath::Verb verb = fSegment->verb();
+ SkPath::Verb rVerb = rh.fSegment->verb();
+ if ((verb == SkPath::kLine_Verb && approximately_zero(y) && approximately_zero(x))
+ || (rVerb == SkPath::kLine_Verb
+ && approximately_zero(ry) && approximately_zero(rx))) {
+ // 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;
+ return COMPARE_RESULT("12 verb == SkPath::kLine_Verb ...", this < &rh);
+ }
+ if (fSegment->isTiny(this) || rh.fSegment->isTiny(&rh)) {
+ fUnsortable = true;
+ return COMPARE_RESULT("13 verb == fSegment->isTiny(this) ...", this < &rh);
+ }
+ SkASSERT(verb >= SkPath::kQuad_Verb);
+ SkASSERT(rVerb >= 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();
+ SkDLine ray;
+ SkIntersections i, ri;
+ int roots, rroots;
+ bool flip = false;
+ bool useThis;
+ bool leftLessThanRight = fSide > 0;
+ do {
+ useThis = (len < rlen) ^ flip;
+ const SkDCubic& part = useThis ? fCurvePart : rh.fCurvePart;
+ SkPath::Verb partVerb = useThis ? verb : rVerb;
+ ray[0] = partVerb == SkPath::kCubic_Verb && part[0].approximatelyEqual(part[1]) ?
+ part[2] : part[1];
+ ray[1] = SkDPoint::Mid(part[0], part[SkPathOpsVerbToPoints(partVerb)]);
+ SkASSERT(ray[0] != ray[1]);
+ roots = (i.*CurveRay[SkPathOpsVerbToPoints(verb)])(fSegment->pts(), ray);
+ rroots = (ri.*CurveRay[SkPathOpsVerbToPoints(rVerb)])(rh.fSegment->pts(), ray);
+ } 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;
+ return COMPARE_RESULT("roots == 0 || rroots == 0", this < &rh);
+ }
+ SkASSERT(fSide != 0 && rh.fSide != 0);
+ SkASSERT(fSide * rh.fSide > 0); // both are the same sign
+ SkDPoint lLoc;
+ double best = SK_ScalarInfinity;
+#if DEBUG_SORT
+ SkDebugf("lh=%d rh=%d use-lh=%d ray={{%1.9g,%1.9g}, {%1.9g,%1.9g}} %c\n",
+ fSegment->debugID(), rh.fSegment->debugID(), useThis, ray[0].fX, ray[0].fY,
+ ray[1].fX, ray[1].fY, "-+"[fSide > 0]);
+#endif
+ for (int index = 0; index < roots; ++index) {
+ SkDPoint loc = i.pt(index);
+ SkDVector dxy = loc - ray[0];
+ double dist = dxy.lengthSquared();
+#if DEBUG_SORT
+ SkDebugf("best=%1.9g dist=%1.9g loc={%1.9g,%1.9g} dxy={%1.9g,%1.9g}\n",
+ best, dist, loc.fX, loc.fY, dxy.fX, dxy.fY);
+#endif
+ if (best > dist) {
+ lLoc = loc;
+ best = dist;
+ }
+ }
+ flip = false;
+ SkDPoint rLoc;
+ for (int index = 0; index < rroots; ++index) {
+ rLoc = ri.pt(index);
+ SkDVector dxy = rLoc - ray[0];
+ double dist = dxy.lengthSquared();
+#if DEBUG_SORT
+ SkDebugf("best=%1.9g dist=%1.9g %c=(fSide < 0) rLoc={%1.9g,%1.9g} dxy={%1.9g,%1.9g}\n",
+ best, dist, "><"[fSide < 0], rLoc.fX, rLoc.fY, dxy.fX, dxy.fY);
+#endif
+ if (best > dist) {
+ flip = true;
+ break;
+ }
+ }
+ if (flip) {
+ leftLessThanRight = !leftLessThanRight;
+ }
+ return COMPARE_RESULT("14 leftLessThanRight", leftLessThanRight);
+}
+
+bool SkOpAngle::isHorizontal() const {
+ return dy() == 0 && fSegment->verb() == SkPath::kLine_Verb;
+}
+
+// lengthen cannot cross opposite angle
+bool SkOpAngle::lengthen(const SkOpAngle& opp) {
+ if (fSegment->other(fEnd) == opp.fSegment) {
+ return false;
+ }
+ // FIXME: make this a while loop instead and make it as large as possible?
+ int newEnd = fEnd;
+ if (fStart < fEnd ? ++newEnd < fSegment->count() : --newEnd >= 0) {
+ fEnd = newEnd;
+ setSpans();
+ return true;
+ }
+ return false;
+}
+
+void SkOpAngle::set(const SkOpSegment* segment, int start, int end) {
+ fSegment = segment;
+ fStart = start;
+ fEnd = end;
+ setSpans();
+}
+
+void SkOpAngle::setSpans() {
+ fUnorderable = false;
+ if (fSegment->verb() == SkPath::kLine_Verb) {
+ fUnsortable = false;
+ } else {
+ // if start-1 exists and is tiny, then start pt may have moved
+ int smaller = SkMin32(fStart, fEnd);
+ int tinyCheck = smaller;
+ while (tinyCheck > 0 && fSegment->isTiny(tinyCheck - 1)) {
+ --tinyCheck;
+ }
+ if ((fUnsortable = smaller > 0 && tinyCheck == 0)) {
+ return;
+ }
+ int larger = SkMax32(fStart, fEnd);
+ tinyCheck = larger;
+ int max = fSegment->count() - 1;
+ while (tinyCheck < max && fSegment->isTiny(tinyCheck + 1)) {
+ ++tinyCheck;
+ }
+ if ((fUnsortable = larger < max && tinyCheck == max)) {
+ return;
+ }
+ }
+ fComputed = fSegment->subDivide(fStart, fEnd, &fCurvePart);
+ // FIXME: slight errors in subdivision cause sort trouble later on. As an experiment, try
+ // rounding the curve part to float precision here
+ // fCurvePart.round(fSegment->verb());
+ switch (fSegment->verb()) {
+ case SkPath::kLine_Verb: {
+ // OPTIMIZATION: for pure line compares, we never need fTangent1.c
+ fTangent1.lineEndPoints(*SkTCast<SkDLine*>(&fCurvePart));
+ fSide = 0;
+ } break;
+ case SkPath::kQuad_Verb: {
+ SkDQuad& quad = *SkTCast<SkDQuad*>(&fCurvePart);
+ fTangent1.quadEndPoints(quad);
+ fSide = -fTangent1.pointDistance(fCurvePart[2]); // not normalized -- compare sign only
+ if (fComputed && dx() > 0 && approximately_zero(dy())) {
+ SkDCubic origCurve; // can't use segment's curve in place since it may be flipped
+ int last = fSegment->count() - 1;
+ fSegment->subDivide(fStart < fEnd ? 0 : last, fStart < fEnd ? last : 0, &origCurve);
+ SkLineParameters origTan;
+ origTan.quadEndPoints(*SkTCast<SkDQuad*>(&origCurve));
+ if ((fUnorderable = origTan.dx() <= 0
+ || (dy() != origTan.dy() && dy() * origTan.dy() <= 0))) { // signs match?
+ return;
+ }
+ }
+ } break;
+ case SkPath::kCubic_Verb: {
+ fTangent1.cubicEndPoints(fCurvePart);
+ double testTs[4];
+ // OPTIMIZATION: keep inflections precomputed with cubic segment?
+ const SkPoint* pts = fSegment->pts();
+ int testCount = SkDCubic::FindInflections(pts, testTs);
+ double startT = fSegment->t(fStart);
+ double endT = fSegment->t(fEnd);
+ double limitT = endT;
+ int index;
+ for (index = 0; index < testCount; ++index) {
+ if (!between(startT, testTs[index], limitT)) {
+ testTs[index] = -1;
+ }
+ }
+ testTs[testCount++] = startT;
+ testTs[testCount++] = endT;
+ SkTQSort<double>(testTs, &testTs[testCount - 1]);
+ double bestSide = 0;
+ int testCases = (testCount << 1) - 1;
+ index = 0;
+ while (testTs[index] < 0) {
+ ++index;
+ }
+ index <<= 1;
+ for (; index < testCases; ++index) {
+ int testIndex = index >> 1;
+ double testT = testTs[testIndex];
+ if (index & 1) {
+ testT = (testT + testTs[testIndex + 1]) / 2;
+ }
+ // OPTIMIZE: could avoid call for t == startT, endT
+ SkDPoint pt = dcubic_xy_at_t(pts, testT);
+ double testSide = fTangent1.pointDistance(pt);
+ if (fabs(bestSide) < fabs(testSide)) {
+ bestSide = testSide;
+ }
+ }
+ fSide = -bestSide; // compare sign only
+ if (fComputed && dx() > 0 && approximately_zero(dy())) {
+ SkDCubic origCurve; // can't use segment's curve in place since it may be flipped
+ int last = fSegment->count() - 1;
+ fSegment->subDivide(fStart < fEnd ? 0 : last, fStart < fEnd ? last : 0, &origCurve);
+ SkLineParameters origTan;
+ origTan.cubicEndPoints(origCurve);
+ if ((fUnorderable = origTan.dx() <= 0)) {
+ fUnsortable = fSegment->isTiny(this);
+ return;
+ }
+ // if one is < 0 and the other is >= 0
+ if ((fUnorderable = (dy() < 0) ^ (origTan.dy() < 0))) {
+ fUnsortable = fSegment->isTiny(this);
+ return;
+ }
+ SkDCubicPair split = origCurve.chopAt(startT);
+ SkLineParameters splitTan;
+ splitTan.cubicEndPoints(fStart < fEnd ? split.second() : split.first());
+ if ((fUnorderable = splitTan.dx() <= 0)) {
+ fUnsortable = fSegment->isTiny(this);
+ return;
+ }
+ // if one is < 0 and the other is >= 0
+ if ((fUnorderable = (dy() < 0) ^ (splitTan.dy() < 0))) {
+ fUnsortable = fSegment->isTiny(this);
+ return;
+ }
+ }
+ } break;
+ default:
+ SkASSERT(0);
+ }
+ if ((fUnsortable = approximately_zero(dx()) && approximately_zero(dy()))) {
+ 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 1
+ const SkOpSpan& thisSpan = fSegment->span(index);
+ const SkOpSpan& nextSpan = fSegment->span(index + step);
+ if (thisSpan.fTiny || precisely_equal(thisSpan.fT, nextSpan.fT)) {
+ continue;
+ }
+ fUnsortable = step > 0 ? thisSpan.fUnsortableStart : nextSpan.fUnsortableEnd;
+#if DEBUG_UNSORTABLE
+ if (fUnsortable) {
+ SkPoint iPt = fSegment->xyAtT(index);
+ SkPoint ePt = fSegment->xyAtT(index + step);
+ SkDebugf("%s unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__,
+ index, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY);
+ }
+#endif
+ return;
+#else
+ if ((*fSpans)[index].fUnsortableStart) {
+ fUnsortable = true;
+ return;
+ }
+#endif
+ }
+#if 1
+#if DEBUG_UNSORTABLE
+ SkPoint iPt = fSegment->xyAtT(fStart);
+ SkPoint ePt = fSegment->xyAtT(fEnd);
+ SkDebugf("%s all tiny unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__,
+ fStart, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY);
+#endif
+ fUnsortable = true;
+#endif
+}
diff --git a/pathops/SkOpAngle.h b/pathops/SkOpAngle.h
new file mode 100644
index 00000000..e7e5e1f5
--- /dev/null
+++ b/pathops/SkOpAngle.h
@@ -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.
+ */
+#ifndef SkOpAngle_DEFINED
+#define SkOpAngle_DEFINED
+
+#include "SkLineParameters.h"
+#include "SkPath.h"
+#include "SkPathOpsCubic.h"
+
+class SkOpSegment;
+
+// sorting angles
+// given angles of {dx dy ddx ddy dddx dddy} sort them
+class SkOpAngle {
+public:
+ enum { kStackBasedCount = 8 }; // FIXME: determine what this should be
+
+ bool operator<(const SkOpAngle& rh) const;
+
+ bool calcSlop(double x, double y, double rx, double ry, bool* result) const;
+
+ double dx() const {
+ return fTangent1.dx();
+ }
+
+ double dy() const {
+ return fTangent1.dy();
+ }
+
+ int end() const {
+ return fEnd;
+ }
+
+ bool isHorizontal() const;
+
+ void set(const SkOpSegment* segment, int start, int end);
+
+ SkOpSegment* segment() const {
+ return const_cast<SkOpSegment*>(fSegment);
+ }
+
+ int sign() const {
+ return SkSign32(fStart - fEnd);
+ }
+
+ int start() const {
+ return fStart;
+ }
+
+ bool unorderable() const {
+ return fUnorderable;
+ }
+
+ bool unsortable() const {
+ return fUnsortable;
+ }
+
+#if DEBUG_ANGLE
+ void debugShow(const SkPoint& a) const {
+ SkDebugf(" d=(%1.9g,%1.9g) side=%1.9g\n", dx(), dy(), fSide);
+ }
+
+ void setID(int id) {
+ fID = id;
+ }
+#endif
+
+private:
+ bool lengthen(const SkOpAngle& );
+ void setSpans();
+
+ SkDCubic fCurvePart;
+ double fSide;
+ SkLineParameters fTangent1;
+ const SkOpSegment* fSegment;
+ int fStart;
+ int fEnd;
+ bool fComputed; // tangent is computed, may contain some error
+ // if subdividing a quad or cubic causes the tangent to go from the maximum angle to the
+ // minimum, mark it unorderable. It still can be sorted, which is good enough for find-top
+ // but can't be ordered, and therefore can't be used to compute winding
+ bool fUnorderable;
+ mutable bool fUnsortable; // this alone is editable by the less than operator
+#if DEBUG_ANGLE
+ int fID;
+#endif
+};
+
+#endif
diff --git a/pathops/SkOpContour.cpp b/pathops/SkOpContour.cpp
new file mode 100644
index 00000000..f3861a1e
--- /dev/null
+++ b/pathops/SkOpContour.cpp
@@ -0,0 +1,261 @@
+/*
+* Copyright 2013 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+#include "SkIntersections.h"
+#include "SkOpContour.h"
+#include "SkPathWriter.h"
+#include "SkTSort.h"
+
+void SkOpContour::addCoincident(int index, SkOpContour* other, int otherIndex,
+ const SkIntersections& ts, bool swap) {
+ SkCoincidence& coincidence = fCoincidences.push_back();
+ coincidence.fContours[0] = this; // FIXME: no need to store
+ coincidence.fContours[1] = other;
+ coincidence.fSegments[0] = index;
+ coincidence.fSegments[1] = otherIndex;
+ coincidence.fTs[swap][0] = ts[0][0];
+ coincidence.fTs[swap][1] = ts[0][1];
+ coincidence.fTs[!swap][0] = ts[1][0];
+ coincidence.fTs[!swap][1] = ts[1][1];
+ coincidence.fPts[0] = ts.pt(0).asSkPoint();
+ coincidence.fPts[1] = ts.pt(1).asSkPoint();
+}
+
+SkOpSegment* SkOpContour::nonVerticalSegment(int* start, int* end) {
+ int segmentCount = fSortedSegments.count();
+ SkASSERT(segmentCount > 0);
+ for (int sortedIndex = fFirstSorted; sortedIndex < segmentCount; ++sortedIndex) {
+ SkOpSegment* testSegment = fSortedSegments[sortedIndex];
+ if (testSegment->done()) {
+ continue;
+ }
+ *start = *end = 0;
+ while (testSegment->nextCandidate(start, end)) {
+ if (!testSegment->isVertical(*start, *end)) {
+ return testSegment;
+ }
+ }
+ }
+ return NULL;
+}
+
+// first pass, add missing T values
+// second pass, determine winding values of overlaps
+void SkOpContour::addCoincidentPoints() {
+ int count = fCoincidences.count();
+ for (int index = 0; index < count; ++index) {
+ SkCoincidence& coincidence = fCoincidences[index];
+ SkASSERT(coincidence.fContours[0] == this);
+ int thisIndex = coincidence.fSegments[0];
+ SkOpSegment& thisOne = fSegments[thisIndex];
+ SkOpContour* otherContour = coincidence.fContours[1];
+ int otherIndex = coincidence.fSegments[1];
+ SkOpSegment& other = otherContour->fSegments[otherIndex];
+ if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) {
+ // OPTIMIZATION: remove from array
+ continue;
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ double startT = coincidence.fTs[0][0];
+ double endT = coincidence.fTs[0][1];
+ bool startSwapped, oStartSwapped, cancelers;
+ if ((cancelers = startSwapped = startT > endT)) {
+ SkTSwap(startT, endT);
+ }
+ SkASSERT(!approximately_negative(endT - startT));
+ double oStartT = coincidence.fTs[1][0];
+ double oEndT = coincidence.fTs[1][1];
+ if ((oStartSwapped = oStartT > oEndT)) {
+ SkTSwap(oStartT, oEndT);
+ cancelers ^= true;
+ }
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ if (cancelers) {
+ // 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, coincidence.fPts[startSwapped]);
+ }
+ if (oStartT > 0 || endT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oStartT)) {
+ other.addTPair(oStartT, &thisOne, endT, true, coincidence.fPts[oStartSwapped]);
+ }
+ } else {
+ if (startT > 0 || oStartT > 0
+ || thisOne.isMissing(startT) || other.isMissing(oStartT)) {
+ thisOne.addTPair(startT, &other, oStartT, true, coincidence.fPts[startSwapped]);
+ }
+ if (endT < 1 || oEndT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oEndT)) {
+ other.addTPair(oEndT, &thisOne, endT, true, coincidence.fPts[!oStartSwapped]);
+ }
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ }
+}
+
+void SkOpContour::calcCoincidentWinding() {
+ int count = fCoincidences.count();
+ for (int index = 0; index < count; ++index) {
+ SkCoincidence& coincidence = fCoincidences[index];
+ SkASSERT(coincidence.fContours[0] == this);
+ int thisIndex = coincidence.fSegments[0];
+ SkOpSegment& thisOne = fSegments[thisIndex];
+ if (thisOne.done()) {
+ continue;
+ }
+ SkOpContour* otherContour = coincidence.fContours[1];
+ int otherIndex = coincidence.fSegments[1];
+ SkOpSegment& other = otherContour->fSegments[otherIndex];
+ if (other.done()) {
+ continue;
+ }
+ double startT = coincidence.fTs[0][0];
+ double endT = coincidence.fTs[0][1];
+ bool cancelers;
+ if ((cancelers = startT > endT)) {
+ SkTSwap<double>(startT, endT);
+ }
+ 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));
+ if (cancelers) {
+ // make sure startT and endT have t entries
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCancel(startT, endT, &other, oStartT, oEndT);
+ }
+ } else {
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCoincident(startT, endT, &other, oStartT, oEndT);
+ }
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ }
+}
+
+void SkOpContour::sortSegments() {
+ int segmentCount = fSegments.count();
+ fSortedSegments.push_back_n(segmentCount);
+ for (int test = 0; test < segmentCount; ++test) {
+ fSortedSegments[test] = &fSegments[test];
+ }
+ SkTQSort<SkOpSegment>(fSortedSegments.begin(), fSortedSegments.end() - 1);
+ fFirstSorted = 0;
+}
+
+void SkOpContour::toPath(SkPathWriter* 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 SkOpContour::topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY,
+ SkOpSegment** topStart) {
+ int segmentCount = fSortedSegments.count();
+ SkASSERT(segmentCount > 0);
+ int sortedIndex = fFirstSorted;
+ fDone = true; // may be cleared below
+ for ( ; sortedIndex < segmentCount; ++sortedIndex) {
+ SkOpSegment* testSegment = fSortedSegments[sortedIndex];
+ if (testSegment->done()) {
+ if (sortedIndex == fFirstSorted) {
+ ++fFirstSorted;
+ }
+ continue;
+ }
+ fDone = false;
+ SkPoint testXY = testSegment->activeLeftTop(true, NULL);
+ if (*topStart) {
+ 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;
+ }
+ }
+ *topStart = testSegment;
+ *bestXY = testXY;
+ }
+}
+
+SkOpSegment* SkOpContour::undoneSegment(int* start, int* end) {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ SkOpSegment* testSegment = &fSegments[test];
+ if (testSegment->done()) {
+ continue;
+ }
+ testSegment->undoneSpan(start, end);
+ return testSegment;
+ }
+ return NULL;
+}
+
+#if DEBUG_SHOW_WINDING
+int SkOpContour::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 SkOpContour::debugShowWindingValues(const SkTArray<SkOpContour*, true>& 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
+
+void SkOpContour::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());
+ }
+}
diff --git a/pathops/SkOpContour.h b/pathops/SkOpContour.h
new file mode 100644
index 00000000..456e6c00
--- /dev/null
+++ b/pathops/SkOpContour.h
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkOpContour_DEFINED
+#define SkOpContour_DEFINED
+
+#include "SkOpSegment.h"
+#include "SkTArray.h"
+
+class SkIntersections;
+class SkOpContour;
+class SkPathWriter;
+
+struct SkCoincidence {
+ SkOpContour* fContours[2];
+ int fSegments[2];
+ double fTs[2][2];
+ SkPoint fPts[2];
+};
+
+class SkOpContour {
+public:
+ SkOpContour() {
+ reset();
+#if DEBUG_DUMP
+ fID = ++gContourID;
+#endif
+ }
+
+ bool operator<(const SkOpContour& rh) const {
+ return fBounds.fTop == rh.fBounds.fTop
+ ? fBounds.fLeft < rh.fBounds.fLeft
+ : fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ void addCoincident(int index, SkOpContour* other, int otherIndex,
+ const SkIntersections& ts, bool swap);
+ void addCoincidentPoints();
+
+ void addCross(const SkOpContour* crosser) {
+#ifdef DEBUG_CROSS
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ SkASSERT(fCrosses[index] != crosser);
+ }
+#endif
+ fCrosses.push_back(crosser);
+ }
+
+ void addCubic(const SkPoint pts[4]) {
+ fSegments.push_back().addCubic(pts, fOperand, fXor);
+ fContainsCurves = fContainsCubics = 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, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) {
+ setContainsIntercepts();
+ return fSegments[segIndex].addT(&other->fSegments[otherIndex], pt, newT);
+ }
+
+ int addSelfT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) {
+ setContainsIntercepts();
+ return fSegments[segIndex].addSelfT(&other->fSegments[otherIndex], pt, newT);
+ }
+
+ int addUnsortableT(int segIndex, SkOpContour* other, int otherIndex, bool start,
+ const SkPoint& pt, double newT) {
+ return fSegments[segIndex].addUnsortableT(&other->fSegments[otherIndex], start, pt, newT);
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fBounds;
+ }
+
+ void calcCoincidentWinding();
+
+ void checkEnds() {
+ if (!fContainsCurves) {
+ return;
+ }
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ SkOpSegment* segment = &fSegments[sIndex];
+ if (segment->verb() == SkPath::kLine_Verb) {
+ continue;
+ }
+ fSegments[sIndex].checkEnds();
+ }
+ }
+
+ void complete() {
+ setBounds();
+ fContainsIntercepts = false;
+ }
+
+ bool containsCubics() const {
+ return fContainsCubics;
+ }
+
+ bool crosses(const SkOpContour* crosser) const {
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ if (fCrosses[index] == crosser) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool done() const {
+ return fDone;
+ }
+
+ const SkPoint& end() const {
+ const SkOpSegment& segment = fSegments.back();
+ return segment.pts()[SkPathOpsVerbToPoints(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();
+ }
+ }
+
+ SkOpSegment* nonVerticalSegment(int* start, int* end);
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ void reset() {
+ fSegments.reset();
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = false;
+ }
+
+ SkTArray<SkOpSegment>& segments() {
+ return fSegments;
+ }
+
+ void setContainsIntercepts() {
+ fContainsIntercepts = true;
+ }
+
+ 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();
+
+ const SkPoint& start() const {
+ return fSegments.front().pts()[0];
+ }
+
+ void toPath(SkPathWriter* path) const;
+
+ void toPartialBackward(SkPathWriter* path) const {
+ int segmentCount = fSegments.count();
+ for (int test = segmentCount - 1; test >= 0; --test) {
+ fSegments[test].addCurveTo(1, 0, path, true);
+ }
+ }
+
+ void toPartialForward(SkPathWriter* path) const {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].addCurveTo(0, 1, path, true);
+ }
+ }
+
+ void topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, SkOpSegment** topStart);
+ SkOpSegment* undoneSegment(int* start, int* end);
+
+ int updateSegment(int index, const SkPoint* pts) {
+ SkOpSegment& segment = fSegments[index];
+ segment.updatePts(pts);
+ return SkPathOpsVerbToPoints(segment.verb()) + 1;
+ }
+
+#if DEBUG_TEST
+ SkTArray<SkOpSegment>& debugSegments() {
+ return fSegments;
+ }
+#endif
+
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+ void debugShowActiveSpans() {
+ for (int index = 0; index < fSegments.count(); ++index) {
+ fSegments[index].debugShowActiveSpans();
+ }
+ }
+#endif
+
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int totalSegments, int ofInterest);
+ static void debugShowWindingValues(const SkTArray<SkOpContour*, true>& contourList);
+#endif
+
+private:
+ void setBounds();
+
+ SkTArray<SkOpSegment> fSegments;
+ SkTArray<SkOpSegment*, true> fSortedSegments;
+ int fFirstSorted;
+ SkTArray<SkCoincidence, true> fCoincidences;
+ SkTArray<const SkOpContour*, true> fCrosses;
+ SkPathOpsBounds fBounds;
+ bool fContainsIntercepts; // FIXME: is this used by anybody?
+ bool fContainsCubics;
+ bool fContainsCurves;
+ bool fDone;
+ bool fOperand; // true for the second argument to a binary operator
+ bool fXor;
+ bool fOppXor;
+#if DEBUG_DUMP
+ int fID;
+#endif
+};
+
+#endif
diff --git a/pathops/SkOpEdgeBuilder.cpp b/pathops/SkOpEdgeBuilder.cpp
new file mode 100644
index 00000000..a5a65848
--- /dev/null
+++ b/pathops/SkOpEdgeBuilder.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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 "SkGeometry.h"
+#include "SkOpEdgeBuilder.h"
+#include "SkReduceOrder.h"
+
+void SkOpEdgeBuilder::init() {
+ fCurrentContour = NULL;
+ fOperand = false;
+ fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask
+ : kWinding_PathOpsMask;
+#if DEBUG_DUMP
+ gContourID = 0;
+ gSegmentID = 0;
+#endif
+ fUnparseable = false;
+ fSecondHalf = preFetch();
+}
+
+void SkOpEdgeBuilder::addOperand(const SkPath& path) {
+ SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb);
+ fPathVerbs.pop_back();
+ fPath = &path;
+ fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask
+ : kWinding_PathOpsMask;
+ preFetch();
+}
+
+bool SkOpEdgeBuilder::finish() {
+ if (fUnparseable || !walk()) {
+ return false;
+ }
+ complete();
+ if (fCurrentContour && !fCurrentContour->segments().count()) {
+ fContours.pop_back();
+ }
+ return true;
+}
+
+void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) {
+ if ((!AlmostEqualUlps(curveEnd.fX, curveStart.fX)
+ || !AlmostEqualUlps(curveEnd.fY, curveStart.fY))) {
+ fPathVerbs.push_back(SkPath::kLine_Verb);
+ fPathPts.push_back_n(1, &curveStart);
+ } else {
+ if (curveEnd.fX != curveStart.fX || curveEnd.fY != curveStart.fY) {
+ fPathPts[fPathPts.count() - 1] = curveStart;
+ } else {
+ fPathPts[fPathPts.count() - 1] = curveStart;
+ }
+ }
+ fPathVerbs.push_back(SkPath::kClose_Verb);
+}
+
+int SkOpEdgeBuilder::preFetch() {
+ if (!fPath->isFinite()) {
+ fUnparseable = true;
+ return 0;
+ }
+ SkAutoConicToQuads quadder;
+ const SkScalar quadderTol = SK_Scalar1 / 16;
+ SkPath::RawIter iter(*fPath);
+ SkPoint curveStart;
+ SkPoint curve[4];
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ bool lastCurve = false;
+ do {
+ verb = iter.next(pts);
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (!fAllowOpenContours && lastCurve) {
+ closeContour(curve[0], curveStart);
+ }
+ fPathVerbs.push_back(verb);
+ fPathPts.push_back(pts[0]);
+ curveStart = curve[0] = pts[0];
+ lastCurve = false;
+ continue;
+ case SkPath::kLine_Verb:
+ if (AlmostEqualUlps(curve[0].fX, pts[1].fX)
+ && AlmostEqualUlps(curve[0].fY, pts[1].fY)) {
+ continue; // skip degenerate points
+ }
+ break;
+ case SkPath::kQuad_Verb:
+ curve[1] = pts[1];
+ curve[2] = pts[2];
+ verb = SkReduceOrder::Quad(curve, pts);
+ if (verb == SkPath::kMove_Verb) {
+ continue; // skip degenerate points
+ }
+ break;
+ case SkPath::kConic_Verb: {
+ const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(),
+ quadderTol);
+ const int nQuads = quadder.countQuads();
+ for (int i = 0; i < nQuads; ++i) {
+ fPathVerbs.push_back(SkPath::kQuad_Verb);
+ }
+ fPathPts.push_back_n(nQuads * 2, quadPts);
+ curve[0] = quadPts[nQuads * 2 - 1];
+ lastCurve = true;
+ }
+ continue;
+ case SkPath::kCubic_Verb:
+ curve[1] = pts[1];
+ curve[2] = pts[2];
+ curve[3] = pts[3];
+ verb = SkReduceOrder::Cubic(curve, pts);
+ if (verb == SkPath::kMove_Verb) {
+ continue; // skip degenerate points
+ }
+ break;
+ case SkPath::kClose_Verb:
+ closeContour(curve[0], curveStart);
+ lastCurve = false;
+ continue;
+ case SkPath::kDone_Verb:
+ continue;
+ }
+ fPathVerbs.push_back(verb);
+ int ptCount = SkPathOpsVerbToPoints(verb);
+ fPathPts.push_back_n(ptCount, &pts[1]);
+ curve[0] = pts[ptCount];
+ lastCurve = true;
+ } while (verb != SkPath::kDone_Verb);
+ if (!fAllowOpenContours && lastCurve) {
+ closeContour(curve[0], curveStart);
+ }
+ fPathVerbs.push_back(SkPath::kDone_Verb);
+ return fPathVerbs.count() - 1;
+}
+
+bool SkOpEdgeBuilder::close() {
+ complete();
+ return true;
+}
+
+bool SkOpEdgeBuilder::walk() {
+ uint8_t* verbPtr = fPathVerbs.begin();
+ uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf];
+ const SkPoint* pointsPtr = fPathPts.begin() - 1;
+ SkPath::Verb verb;
+ while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) {
+ if (verbPtr == endOfFirstHalf) {
+ fOperand = true;
+ }
+ verbPtr++;
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (fCurrentContour) {
+ if (fAllowOpenContours) {
+ complete();
+ } else if (!close()) {
+ return false;
+ }
+ }
+ if (!fCurrentContour) {
+ fCurrentContour = fContours.push_back_n(1);
+ fCurrentContour->setOperand(fOperand);
+ fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_PathOpsMask);
+ }
+ pointsPtr += 1;
+ continue;
+ case SkPath::kLine_Verb:
+ fCurrentContour->addLine(pointsPtr);
+ break;
+ case SkPath::kQuad_Verb:
+ fCurrentContour->addQuad(pointsPtr);
+ break;
+ case SkPath::kCubic_Verb:
+ fCurrentContour->addCubic(pointsPtr);
+ break;
+ case SkPath::kClose_Verb:
+ SkASSERT(fCurrentContour);
+ if (!close()) {
+ return false;
+ }
+ continue;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return false;
+ }
+ pointsPtr += SkPathOpsVerbToPoints(verb);
+ SkASSERT(fCurrentContour);
+ }
+ if (fCurrentContour && !fAllowOpenContours && !close()) {
+ return false;
+ }
+ return true;
+}
diff --git a/pathops/SkOpEdgeBuilder.h b/pathops/SkOpEdgeBuilder.h
new file mode 100644
index 00000000..df0795b0
--- /dev/null
+++ b/pathops/SkOpEdgeBuilder.h
@@ -0,0 +1,63 @@
+/*
+ * 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 SkOpEdgeBuilder_DEFINED
+#define SkOpEdgeBuilder_DEFINED
+
+#include "SkOpContour.h"
+#include "SkPathWriter.h"
+#include "SkTArray.h"
+
+class SkOpEdgeBuilder {
+public:
+ SkOpEdgeBuilder(const SkPathWriter& path, SkTArray<SkOpContour>& contours)
+ : fPath(path.nativePath())
+ , fContours(contours)
+ , fAllowOpenContours(true) {
+ init();
+ }
+
+ SkOpEdgeBuilder(const SkPath& path, SkTArray<SkOpContour>& contours)
+ : fPath(&path)
+ , fContours(contours)
+ , fAllowOpenContours(false) {
+ init();
+ }
+
+ void complete() {
+ if (fCurrentContour && fCurrentContour->segments().count()) {
+ fCurrentContour->complete();
+ fCurrentContour = NULL;
+ }
+ }
+
+ SkPathOpsMask xorMask() const {
+ return fXorMask[fOperand];
+ }
+
+ void addOperand(const SkPath& path);
+ bool finish();
+ void init();
+
+private:
+ void closeContour(const SkPoint& curveEnd, const SkPoint& curveStart);
+ bool close();
+ int preFetch();
+ bool walk();
+
+ const SkPath* fPath;
+ SkTArray<SkPoint, true> fPathPts;
+ SkTArray<uint8_t, true> fPathVerbs;
+ SkOpContour* fCurrentContour;
+ SkTArray<SkOpContour>& fContours;
+ SkPathOpsMask fXorMask[2];
+ int fSecondHalf;
+ bool fOperand;
+ bool fAllowOpenContours;
+ bool fUnparseable;
+};
+
+#endif
diff --git a/pathops/SkOpSegment.cpp b/pathops/SkOpSegment.cpp
new file mode 100644
index 00000000..7e69bb83
--- /dev/null
+++ b/pathops/SkOpSegment.cpp
@@ -0,0 +1,3027 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkOpSegment.h"
+#include "SkPathWriter.h"
+#include "SkTSort.h"
+
+#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[kXOR_PathOp + 1][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
+
+enum { kOutsideTrackedTCount = 16 }; // FIXME: determine what this should be
+
+// OPTIMIZATION: does the following also work, and is it any faster?
+// return outerWinding * innerWinding > 0
+// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
+bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) {
+ SkASSERT(outerWinding != SK_MaxS32);
+ SkASSERT(innerWinding != SK_MaxS32);
+ int absOut = abs(outerWinding);
+ int absIn = abs(innerWinding);
+ bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
+ return result;
+}
+
+bool SkOpSegment::activeAngle(int index, int* done, SkTArray<SkOpAngle, true>* angles) {
+ if (activeAngleInner(index, done, angles)) {
+ return true;
+ }
+ int lesser = index;
+ while (--lesser >= 0 && equalPoints(index, lesser)) {
+ if (activeAngleOther(lesser, done, angles)) {
+ return true;
+ }
+ }
+ lesser = index;
+ do {
+ if (activeAngleOther(index, done, angles)) {
+ return true;
+ }
+ } while (++index < fTs.count() && equalPoints(index, lesser));
+ return false;
+}
+
+bool SkOpSegment::activeAngleOther(int index, int* done, SkTArray<SkOpAngle, true>* angles) {
+ SkOpSpan* span = &fTs[index];
+ SkOpSegment* other = span->fOther;
+ int oIndex = span->fOtherIndex;
+ return other->activeAngleInner(oIndex, done, angles);
+}
+
+bool SkOpSegment::activeAngleInner(int index, int* done, SkTArray<SkOpAngle, true>* angles) {
+ int next = nextExactSpan(index, 1);
+ if (next > 0) {
+ SkOpSpan& 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) {
+ SkOpSpan& 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;
+}
+
+SkPoint SkOpSegment::activeLeftTop(bool onlySortable, int* firstT) const {
+ SkASSERT(!done());
+ SkPoint topPt = {SK_ScalarMax, 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;
+ double lastT = -1;
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = fTs[index];
+ if (onlySortable && (span.fUnsortableStart || lastUnsortable)) {
+ goto next;
+ }
+ if (span.fDone && lastDone) {
+ goto next;
+ }
+ if (approximately_negative(span.fT - lastT)) {
+ goto next;
+ }
+ {
+ const SkPoint& xy = xyAtT(&span);
+ if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) {
+ topPt = xy;
+ if (firstT) {
+ *firstT = index;
+ }
+ }
+ if (fVerb != SkPath::kLine_Verb && !lastDone) {
+ SkPoint curveTop = (*CurveTop[SkPathOpsVerbToPoints(fVerb)])(fPts, lastT, span.fT);
+ if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY
+ && topPt.fX > curveTop.fX)) {
+ topPt = curveTop;
+ if (firstT) {
+ *firstT = index;
+ }
+ }
+ }
+ lastT = span.fT;
+ }
+next:
+ lastDone = span.fDone;
+ lastUnsortable = span.fUnsortableEnd;
+ }
+ return topPt;
+}
+
+bool SkOpSegment::activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp 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 SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp 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];
+#if DEBUG_ACTIVE_OP
+ SkDebugf("%s op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__,
+ kPathOpStr[op], miFrom, miTo, suFrom, suTo, result);
+#endif
+ return result;
+}
+
+bool SkOpSegment::activeWinding(int index, int endIndex) {
+ int sumWinding = updateWinding(endIndex, index);
+ int maxWinding;
+ return activeWinding(index, endIndex, &maxWinding, &sumWinding);
+}
+
+bool SkOpSegment::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];
+ return result;
+}
+
+void SkOpSegment::addAngle(SkTArray<SkOpAngle, true>* anglesPtr, int start, int end) const {
+ SkASSERT(start != end);
+ SkOpAngle& angle = anglesPtr->push_back();
+#if 0 && DEBUG_ANGLE // computed pt and actual pt may differ by more than approx eq
+ SkTArray<SkOpAngle, true>& angles = *anglesPtr;
+ if (angles.count() > 1) {
+ const SkOpSegment* aSeg = angles[0].segment();
+ int aStart = angles[0].start();
+ if (!aSeg->fTs[aStart].fTiny) {
+ const SkPoint& angle0Pt = aSeg->xyAtT(aStart);
+ SkDPoint newPt = (*CurveDPointAtT[SkPathOpsVerbToPoints(aSeg->fVerb)])(aSeg->fPts,
+ aSeg->fTs[aStart].fT);
+#if ONE_OFF_DEBUG
+ SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g)\n", __FUNCTION__,
+ aSeg->fTs[aStart].fT, newPt.fX, newPt.fY, angle0Pt.fX, angle0Pt.fY);
+#endif
+ SkASSERT(newPt.approximatelyEqual(angle0Pt));
+ }
+ }
+#endif
+ angle.set(this, start, end);
+}
+
+void SkOpSegment::addCancelOutsides(double tStart, double oStart, SkOpSegment* 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,
+ fTs[tIndexStart].fPt);
+ }
+ 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, fTs[tIndex].fPt);
+ }
+ } 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 SkOpSegment::addCoinOutsides(const SkTArray<double, true>& outsideTs, SkOpSegment* 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));
+ SkPoint ptStart = fTs[tIndex].fPt;
+ do {
+ ++oIndex;
+ } while (!approximately_negative(oStart - other->fTs[oIndex].fT));
+ if (tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) {
+ addTPair(tStart, other, oStart, false, ptStart);
+ }
+ tStart = fTs[tIndex].fT;
+ oStart = other->fTs[oIndex].fT;
+ do {
+ double nextT;
+ do {
+ nextT = fTs[++tIndex].fT;
+ } while (approximately_negative(nextT - tStart));
+ tStart = nextT;
+ ptStart = fTs[tIndex].fPt;
+ 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, ptStart);
+ } while (tStart < 1 && oStart < 1 && !approximately_negative(oEnd - oStart));
+}
+
+void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) {
+ init(pts, SkPath::kCubic_Verb, operand, evenOdd);
+ fBounds.setCubicBounds(pts);
+}
+
+void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* 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 xyAtT(end)
+ subDivide(start, end, edge);
+ ePtr = edge;
+ }
+ if (active) {
+ bool reverse = ePtr == fPts && start != 0;
+ if (reverse) {
+ path->deferredMoveLine(ePtr[SkPathOpsVerbToPoints(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[SkPathOpsVerbToPoints(fVerb)];
+}
+
+void SkOpSegment::addLine(const SkPoint pts[2], bool operand, bool evenOdd) {
+ init(pts, SkPath::kLine_Verb, operand, evenOdd);
+ fBounds.set(pts, 2);
+}
+
+// add 2 to edge or out of range values to get T extremes
+void SkOpSegment::addOtherT(int index, double otherT, int otherIndex) {
+ SkOpSpan& span = fTs[index];
+ if (precisely_zero(otherT)) {
+ otherT = 0;
+ } else if (precisely_equal(otherT, 1)) {
+ otherT = 1;
+ }
+ span.fOtherT = otherT;
+ span.fOtherIndex = otherIndex;
+}
+
+void SkOpSegment::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 SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
+ if (precisely_zero(newT)) {
+ newT = 0;
+ } else if (precisely_equal(newT, 1)) {
+ newT = 1;
+ }
+ // FIXME: in the pathological case where there is a ton of intercepts,
+ // binary search?
+ int insertedAt = -1;
+ size_t tCount = fTs.count();
+ 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;
+ }
+ }
+ SkOpSpan* span;
+ if (insertedAt >= 0) {
+ span = fTs.insert(insertedAt);
+ } else {
+ insertedAt = tCount;
+ span = fTs.append();
+ }
+ span->fT = newT;
+ span->fOther = other;
+ span->fPt = pt;
+#if 0
+ // cubics, for instance, may not be exact enough to satisfy this check (e.g., cubicOp69d)
+ SkASSERT(approximately_equal(xyAtT(newT).fX, pt.fX)
+ && approximately_equal(xyAtT(newT).fY, pt.fY));
+#endif
+ span->fWindSum = SK_MinS32;
+ span->fOppSum = SK_MinS32;
+ span->fWindValue = 1;
+ span->fOppValue = 0;
+ span->fTiny = false;
+ span->fLoop = false;
+ if ((span->fDone = newT == 1)) {
+ ++fDoneSpans;
+ }
+ span->fUnsortableStart = false;
+ span->fUnsortableEnd = false;
+ int less = -1;
+ while (&span[less + 1] - fTs.begin() > 0 && xyAtT(&span[less]) == xyAtT(span)) {
+ if (span[less].fDone) {
+ break;
+ }
+ double tInterval = newT - span[less].fT;
+ if (precisely_negative(tInterval)) {
+ break;
+ }
+ if (fVerb == SkPath::kCubic_Verb) {
+ double tMid = newT - tInterval / 2;
+ SkDPoint midPt = dcubic_xy_at_t(fPts, tMid);
+ if (!midPt.approximatelyEqual(xyAtT(span))) {
+ break;
+ }
+ }
+ span[less].fTiny = true;
+ span[less].fDone = true;
+ if (approximately_negative(newT - span[less].fT)) {
+ if (approximately_greater_than_one(newT)) {
+ SkASSERT(&span[less] - fTs.begin() < fTs.count());
+ span[less].fUnsortableStart = true;
+ if (&span[less - 1] - fTs.begin() >= 0) {
+ span[less - 1].fUnsortableEnd = true;
+ }
+ }
+ if (approximately_less_than_zero(span[less].fT)) {
+ SkASSERT(&span[less + 1] - fTs.begin() < fTs.count());
+ SkASSERT(&span[less] - fTs.begin() >= 0);
+ span[less + 1].fUnsortableStart = true;
+ span[less].fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ --less;
+ }
+ int more = 1;
+ while (fTs.end() - &span[more - 1] > 1 && xyAtT(&span[more]) == xyAtT(span)) {
+ if (span[more - 1].fDone) {
+ break;
+ }
+ double tEndInterval = span[more].fT - newT;
+ if (precisely_negative(tEndInterval)) {
+ break;
+ }
+ if (fVerb == SkPath::kCubic_Verb) {
+ double tMid = newT - tEndInterval / 2;
+ SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid);
+ if (!midEndPt.approximatelyEqual(xyAtT(span))) {
+ break;
+ }
+ }
+ span[more - 1].fTiny = true;
+ span[more - 1].fDone = true;
+ if (approximately_negative(span[more].fT - newT)) {
+ if (approximately_greater_than_one(span[more].fT)) {
+ span[more + 1].fUnsortableStart = true;
+ span[more].fUnsortableEnd = true;
+ }
+ if (approximately_less_than_zero(newT)) {
+ span[more].fUnsortableStart = true;
+ span[more - 1].fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ ++more;
+ }
+ return insertedAt;
+}
+
+// set spans from start to end to decrement by one
+// note this walks other backwards
+// FIXME: 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.
+// FIXME? It seems that decrementing by one will fail for complex paths that
+// have three or more coincident edges. Shouldn't this subtract the difference
+// between the winding values?
+void SkOpSegment::addTCancel(double startT, double endT, SkOpSegment* 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);
+ SkOpSpan* test = &fTs[index];
+ SkOpSpan* oTest = &other->fTs[oIndex];
+ SkSTArray<kOutsideTrackedTCount, double, true> outsideTs;
+ SkSTArray<kOutsideTrackedTCount, double, true> oOutsideTs;
+ do {
+ bool decrement = test->fWindValue && oTest->fWindValue;
+ bool track = test->fWindValue || oTest->fWindValue;
+ bool bigger = test->fWindValue >= oTest->fWindValue;
+ double testT = test->fT;
+ double oTestT = oTest->fT;
+ SkOpSpan* span = test;
+ do {
+ if (decrement) {
+ if (binary && bigger) {
+ span->fOppValue--;
+ } else {
+ decrementSpan(span);
+ }
+ } else if (track && span->fT < 1 && oTestT < 1) {
+ TrackOutside(&outsideTs, span->fT, oTestT);
+ }
+ span = &fTs[++index];
+ } while (approximately_negative(span->fT - testT));
+ SkOpSpan* 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) {
+ if (binary && !bigger) {
+ oSpan->fOppValue--;
+ } else {
+ 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 SkOpSegment::addSelfT(SkOpSegment* other, const SkPoint& pt, double newT) {
+ int result = addT(other, pt, newT);
+ SkOpSpan* span = &fTs[result];
+ span->fLoop = true;
+ return result;
+}
+
+int SkOpSegment::addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT) {
+ int result = addT(other, pt, newT);
+ SkOpSpan* span = &fTs[result];
+ if (start) {
+ if (result > 0) {
+ span[result - 1].fUnsortableEnd = true;
+ }
+ span[result].fUnsortableStart = true;
+ } else {
+ span[result].fUnsortableEnd = true;
+ if (result + 1 < fTs.count()) {
+ span[result + 1].fUnsortableStart = true;
+ }
+ }
+ return result;
+}
+
+int SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index,
+ SkTArray<double, true>* outsideTs) {
+ int oWindValue = oTest.fWindValue;
+ int oOppValue = oTest.fOppValue;
+ if (opp) {
+ SkTSwap<int>(oWindValue, oOppValue);
+ }
+ SkOpSpan* const test = &fTs[index];
+ SkOpSpan* 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 SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex,
+ SkTArray<double, true>* oOutsideTs) {
+ SkOpSpan* const oTest = &fTs[oIndex];
+ SkOpSpan* 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 SkOpSegment::addTCoincident(double startT, double endT, SkOpSegment* 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;
+ }
+ SkOpSpan* test = &fTs[index];
+ SkOpSpan* oTest = &other->fTs[oIndex];
+ SkSTArray<kOutsideTrackedTCount, double, true> outsideTs;
+ SkSTArray<kOutsideTrackedTCount, double, true> 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, SkASSERT here?
+void SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
+ const SkPoint& pt) {
+ int tCount = fTs.count();
+ for (int tIndex = 0; tIndex < tCount; ++tIndex) {
+ const SkOpSpan& 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(other, pt, t);
+ int otherInsertedAt = other->addT(this, pt, otherT);
+ addOtherT(insertedAt, otherT, otherInsertedAt);
+ other->addOtherT(otherInsertedAt, t, insertedAt);
+ matchWindingValue(insertedAt, t, borrowWind);
+ other->matchWindingValue(otherInsertedAt, otherT, borrowWind);
+}
+
+void SkOpSegment::addTwoAngles(int start, int end, SkTArray<SkOpAngle, true>* 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 SkOpSegment::advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index) {
+ SkOpSpan* const test = &fTs[index];
+ SkOpSpan* end;
+ do {
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ return index;
+}
+
+int SkOpSegment::advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex) {
+ SkOpSpan* const oTest = &fTs[oIndex];
+ SkOpSpan* oEnd = oTest;
+ const double oStartT = oTest->fT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - oStartT)) {
+ oEnd = &fTs[++oIndex];
+ }
+ return oIndex;
+}
+
+bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const {
+ if (lesser > greater) {
+ SkTSwap<int>(lesser, greater);
+ }
+ return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT);
+}
+
+void SkOpSegment::buildAngles(int index, SkTArray<SkOpAngle, true>* 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 SkOpSegment::buildAnglesInner(int index, SkTArray<SkOpAngle, true>* angles) const {
+ const SkOpSpan* span = &fTs[index];
+ SkOpSegment* 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 SkOpSegment::computeSum(int startIndex, int endIndex, bool binary) {
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> 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)
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ // FIXME?: Not sure if this sort must be ordered or if the relaxed ordering is OK ...
+ bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0, sortable);
+#endif
+ if (!sortable) {
+ return SK_MinS32;
+ }
+ int angleCount = angles.count();
+ const SkOpAngle* angle;
+ const SkOpSegment* 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, sortable);
+#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];
+ SkOpSegment* 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;
+ }
+#ifdef SK_DEBUG
+ SkASSERT(abs(maxWinding) <= gDebugMaxWindSum);
+ SkASSERT(abs(oMaxWinding) <= gDebugMaxWindSum);
+#endif
+ (void) segment->markAndChaseWinding(angle, oMaxWinding, maxWinding);
+ } else {
+ if (UseInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ if (oppoSign && UseInnerWinding(oMaxWinding, oWinding)) {
+ oMaxWinding = oWinding;
+ }
+#ifdef SK_DEBUG
+ SkASSERT(abs(maxWinding) <= gDebugMaxWindSum);
+ SkASSERT(abs(binary ? oMaxWinding : 0) <= gDebugMaxWindSum);
+#endif
+ (void) segment->markAndChaseWinding(angle, maxWinding,
+ binary ? oMaxWinding : 0);
+ }
+ }
+ } while (++nextIndex != lastIndex);
+ int minIndex = SkMin32(startIndex, endIndex);
+ return windSum(minIndex);
+}
+
+int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT,
+ bool* hitSomething, double mid, bool opp, bool current) const {
+ SkScalar bottom = fBounds.fBottom;
+ int bestTIndex = -1;
+ if (bottom <= *bestY) {
+ return bestTIndex;
+ }
+ SkScalar top = fBounds.fTop;
+ if (top >= basePt.fY) {
+ return bestTIndex;
+ }
+ if (fBounds.fLeft > basePt.fX) {
+ return bestTIndex;
+ }
+ if (fBounds.fRight < basePt.fX) {
+ return bestTIndex;
+ }
+ if (fBounds.fLeft == fBounds.fRight) {
+ // if vertical, and directly above test point, wait for another one
+ return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTIndex;
+ }
+ // intersect ray starting at basePt with edge
+ SkIntersections intersections;
+ // 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 = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)])
+ (fPts, top, bottom, basePt.fX, false);
+ if (pts == 0 || (current && pts == 1)) {
+ return bestTIndex;
+ }
+ if (current) {
+ SkASSERT(pts > 1);
+ int closestIdx = 0;
+ double closest = fabs(intersections[0][0] - mid);
+ for (int idx = 1; idx < pts; ++idx) {
+ double test = fabs(intersections[0][idx] - mid);
+ if (closest > test) {
+ closestIdx = idx;
+ closest = test;
+ }
+ }
+ intersections.quickRemoveOne(closestIdx, --pts);
+ }
+ double bestT = -1;
+ for (int index = 0; index < pts; ++index) {
+ double foundT = intersections[0][index];
+ if (approximately_less_than_zero(foundT)
+ || approximately_greater_than_one(foundT)) {
+ continue;
+ }
+ SkScalar testY = (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fY;
+ if (approximately_negative(testY - *bestY)
+ || approximately_negative(basePt.fY - testY)) {
+ continue;
+ }
+ if (pts > 1 && fVerb == SkPath::kLine_Verb) {
+ return SK_MinS32; // if the intersection is edge on, wait for another one
+ }
+ if (fVerb > SkPath::kLine_Verb) {
+ SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fX;
+ if (approximately_zero(dx)) {
+ return SK_MinS32; // hit vertical, wait for another one
+ }
+ }
+ *bestY = testY;
+ bestT = foundT;
+ }
+ if (bestT < 0) {
+ return bestTIndex;
+ }
+ SkASSERT(bestT >= 0);
+ SkASSERT(bestT <= 1);
+ int start;
+ int end = 0;
+ do {
+ start = end;
+ end = nextSpan(start, 1);
+ } while (fTs[end].fT < bestT);
+ // FIXME: see next candidate for a better pattern to find the next start/end pair
+ while (start + 1 < end && fTs[start].fDone) {
+ ++start;
+ }
+ if (!isCanceled(start)) {
+ *hitT = bestT;
+ bestTIndex = start;
+ *hitSomething = true;
+ }
+ return bestTIndex;
+}
+
+void SkOpSegment::decrementSpan(SkOpSpan* span) {
+ SkASSERT(span->fWindValue > 0);
+ if (--(span->fWindValue) == 0) {
+ if (!span->fOppValue && !span->fDone) {
+ span->fDone = true;
+ ++fDoneSpans;
+ }
+ }
+}
+
+bool SkOpSegment::bumpSpan(SkOpSpan* 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;
+}
+
+// look to see if the curve end intersects an intermediary that intersects the other
+void SkOpSegment::checkEnds() {
+ debugValidate();
+ SkTDArray<SkOpSpan> missingSpans;
+ int count = fTs.count();
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = fTs[index];
+ const SkOpSegment* other = span.fOther;
+ const SkOpSpan* otherSpan = &other->fTs[span.fOtherIndex];
+ double otherT = otherSpan->fT;
+ if (otherT != 0 && otherT != 1) {
+ continue;
+ }
+ int peekStart = span.fOtherIndex;
+ while (peekStart > 0) {
+ const SkOpSpan* peeker = &other->fTs[peekStart - 1];
+ if (peeker->fT != otherT) {
+ break;
+ }
+ --peekStart;
+ }
+ int otherLast = other->fTs.count() - 1;
+ int peekLast = span.fOtherIndex;
+ while (peekLast < otherLast) {
+ const SkOpSpan* peeker = &other->fTs[peekLast + 1];
+ if (peeker->fT != otherT) {
+ break;
+ }
+ ++peekLast;
+ }
+ if (peekStart == peekLast) {
+ continue;
+ }
+ double t = span.fT;
+ int tStart = index;
+ while (tStart > 0 && t == fTs[tStart - 1].fT) {
+ --tStart;
+ }
+ int tLast = index;
+ int last = count - 1;
+ while (tLast < last && t == fTs[tLast + 1].fT) {
+ ++tLast;
+ }
+ for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) {
+ if (peekIndex == span.fOtherIndex) {
+ continue;
+ }
+ const SkOpSpan& peekSpan = other->fTs[peekIndex];
+ SkOpSegment* match = peekSpan.fOther;
+ const double matchT = peekSpan.fOtherT;
+ for (int tIndex = tStart; tIndex <= tLast; ++tIndex) {
+ const SkOpSpan& tSpan = fTs[tIndex];
+ if (tSpan.fOther == match && tSpan.fOtherT == matchT) {
+ goto nextPeeker;
+ }
+ }
+ // this segment is missing a entry that the other contains
+ // remember so we can add the missing one and recompute the indices
+ SkOpSpan* missing = missingSpans.append();
+ missing->fT = t;
+ missing->fOther = match;
+ missing->fOtherT = matchT;
+ missing->fPt = peekSpan.fPt;
+ }
+nextPeeker:
+ ;
+ }
+ int missingCount = missingSpans.count();
+ if (missingCount == 0) {
+ return;
+ }
+ debugValidate();
+ for (int index = 0; index < missingCount; ++index) {
+ const SkOpSpan& missing = missingSpans[index];
+ addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt);
+ }
+ fixOtherTIndex();
+ for (int index = 0; index < missingCount; ++index) {
+ const SkOpSpan& missing = missingSpans[index];
+ missing.fOther->fixOtherTIndex();
+ }
+ debugValidate();
+}
+
+bool SkOpSegment::equalPoints(int greaterTIndex, int lesserTIndex) {
+ SkASSERT(greaterTIndex >= lesserTIndex);
+ double greaterT = fTs[greaterTIndex].fT;
+ double lesserT = fTs[lesserTIndex].fT;
+ if (greaterT == lesserT) {
+ return true;
+ }
+ if (!approximately_negative(greaterT - lesserT)) {
+ return false;
+ }
+ return xyAtT(greaterTIndex) == xyAtT(lesserTIndex);
+}
+
+/*
+ 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.
+ */
+SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable, SkPathOp op, const int xorMiMask,
+ const int xorSuMask) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(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);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* 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());
+ if (other->isTiny(SkMin32(*nextStart, *nextEnd))) {
+ *unsortable = true;
+ return NULL;
+ }
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, true);
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, sortable);
+#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 SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* 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) {
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->isTiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle) && !nextSegment->isTiny(nextAngle);
+ }
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ SkOpSpan* 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;
+}
+
+SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart,
+ int* nextEnd, bool* unsortable) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(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);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* 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
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, true);
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, sortable);
+#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 nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ int maxWinding;
+ bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(),
+ &maxWinding, &sumWinding);
+ if (activeAngle) {
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->isTiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle);
+ }
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ SkOpSpan* 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;
+}
+
+SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsortable) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(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);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* 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;
+ // FIXME: I don't know why the logic here is difference from the winding case
+ 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;
+ }
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, false);
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+ if (!sortable) {
+ *unsortable = true;
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, findStartingEdge(sorted, startIndex, end), 0, 0,
+ sortable);
+#endif
+ return NULL;
+ }
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, 0, 0, sortable);
+#endif
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->isTiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle);
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ } while (++nextIndex != lastIndex);
+ markDone(SkMin32(startIndex, endIndex), 1);
+ 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;
+}
+
+int SkOpSegment::findStartingEdge(const SkTArray<SkOpAngle*, true>& sorted, int start, int end) {
+ int angleCount = sorted.count();
+ int firstIndex = -1;
+ for (int angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle* 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
+// note that fOtherIndex isn't computed yet, so it can't be used here
+void SkOpSegment::findTooCloseToCall() {
+ int count = fTs.count();
+ if (count < 3) { // require t=0, x, 1 at minimum
+ return;
+ }
+ int matchIndex = 0;
+ int moCount;
+ SkOpSpan* match;
+ SkOpSegment* 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) {
+ SkOpSpan* test = &fTs[index];
+ if (test->fDone) {
+ continue;
+ }
+ SkOpSegment* 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 = 0;
+ double moEndT = 0;
+ for (int moIndex = 0; moIndex < moCount; ++moIndex) {
+ SkOpSpan& 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->windValueAt(moSpan.fOtherT) == 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 = 0;
+ double toEndT = 0;
+ for (int toIndex = 0; toIndex < toCount; ++toIndex) {
+ SkOpSpan& 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->windValueAt(toSpan.fOtherT) == 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);
+ }
+ }
+}
+
+// FIXME: 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
+SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, 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 = */ activeLeftTop(onlySortable, &firstT);
+ if (firstT < 0) {
+ *unsortable = true;
+ firstT = 0;
+ while (fTs[firstT].fDone) {
+ SkASSERT(firstT < fTs.count());
+ ++firstT;
+ }
+ *tIndexPtr = firstT;
+ *endIndexPtr = nextExactSpan(firstT, 1);
+ return this;
+ }
+ // 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)
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ SkASSERT(firstT - end != 0);
+ addTwoAngles(end, firstT, &angles);
+ buildAngles(firstT, &angles, true);
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMayBeUnordered_SortAngleKind);
+ int first = SK_MaxS32;
+ SkScalar top = SK_ScalarMax;
+ int count = sorted.count();
+ for (int index = 0; index < count; ++index) {
+ const SkOpAngle* angle = sorted[index];
+ SkOpSegment* next = angle->segment();
+ SkPathOpsBounds bounds;
+ next->subDivideBounds(angle->end(), angle->start(), &bounds);
+ if (approximately_greater(top, bounds.fTop)) {
+ top = bounds.fTop;
+ first = index;
+ }
+ }
+ SkASSERT(first < SK_MaxS32);
+#if DEBUG_SORT // || DEBUG_SWAP_TOP
+ sorted[first]->segment()->debugShowSort(__FUNCTION__, sorted, first, 0, 0, sortable);
+#endif
+ if (onlySortable && !sortable) {
+ *unsortable = true;
+ return NULL;
+ }
+ // skip edges that have already been processed
+ firstT = first - 1;
+ SkOpSegment* leftSegment;
+ do {
+ if (++firstT == count) {
+ firstT = 0;
+ }
+ const SkOpAngle* angle = sorted[firstT];
+ SkASSERT(!onlySortable || !angle->unsortable());
+ leftSegment = angle->segment();
+ *tIndexPtr = angle->end();
+ *endIndexPtr = angle->start();
+ } while (leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fDone);
+ if (leftSegment->verb() >= SkPath::kQuad_Verb) {
+ const int tIndex = *tIndexPtr;
+ const int endIndex = *endIndexPtr;
+ if (!leftSegment->clockwise(tIndex, endIndex)) {
+ bool swap = !leftSegment->monotonicInY(tIndex, endIndex)
+ && !leftSegment->serpentine(tIndex, endIndex);
+ #if DEBUG_SWAP_TOP
+ SkDebugf("%s swap=%d serpentine=%d containedByEnds=%d monotonic=%d\n", __FUNCTION__,
+ swap,
+ leftSegment->serpentine(tIndex, endIndex),
+ leftSegment->controlsContainedByEnds(tIndex, endIndex),
+ leftSegment->monotonicInY(tIndex, endIndex));
+ #endif
+ if (swap) {
+ // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first
+ // sorted but merely the first not already processed (i.e., not done)
+ SkTSwap(*tIndexPtr, *endIndexPtr);
+ }
+ }
+ }
+ SkASSERT(!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].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 SkOpSegment::fixOtherTIndex() {
+ int iCount = fTs.count();
+ for (int i = 0; i < iCount; ++i) {
+ SkOpSpan& iSpan = fTs[i];
+ double oT = iSpan.fOtherT;
+ SkOpSegment* other = iSpan.fOther;
+ int oCount = other->fTs.count();
+ SkDEBUGCODE(iSpan.fOtherIndex = -1);
+ for (int o = 0; o < oCount; ++o) {
+ SkOpSpan& oSpan = other->fTs[o];
+ if (oT == oSpan.fT && this == oSpan.fOther && oSpan.fOtherT == iSpan.fT) {
+ iSpan.fOtherIndex = o;
+ oSpan.fOtherIndex = i;
+ break;
+ }
+ }
+ SkASSERT(iSpan.fOtherIndex >= 0);
+ }
+}
+
+void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) {
+ fDoneSpans = 0;
+ fOperand = operand;
+ fXor = evenOdd;
+ fPts = pts;
+ fVerb = verb;
+}
+
+void SkOpSegment::initWinding(int start, int end) {
+ int local = spanSign(start, end);
+ int oppLocal = oppSign(start, end);
+ (void) markAndChaseWinding(start, end, local, oppLocal);
+ // OPTIMIZATION: the reverse mark and chase could skip the first marking
+ (void) markAndChaseWinding(end, start, local, oppLocal);
+}
+
+/*
+when we start with a vertical intersect, we try to use the dx to determine if the edge is to
+the left or the right of vertical. This determines if we need to add the span's
+sign or not. However, this isn't enough.
+If the supplied sign (winding) is zero, then we didn't hit another vertical span, so dx is needed.
+If there was a winding, then it may or may not need adjusting. If the span the winding was borrowed
+from has the same x direction as this span, the winding should change. If the dx is opposite, then
+the same winding is shared by both.
+*/
+void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkScalar hitDx,
+ int oppWind, SkScalar hitOppDx) {
+ SkASSERT(hitDx || !winding);
+ SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX;
+ SkASSERT(dx);
+ int windVal = windValue(SkMin32(start, end));
+#if DEBUG_WINDING_AT_T
+ SkDebugf("%s oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, winding,
+ hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal);
+#endif
+ if (!winding) {
+ winding = dx < 0 ? windVal : -windVal;
+ } else if (winding * dx < 0) {
+ int sideWind = winding + (dx < 0 ? windVal : -windVal);
+ if (abs(winding) < abs(sideWind)) {
+ winding = sideWind;
+ }
+ }
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" winding=%d\n", winding);
+#endif
+ SkDEBUGCODE(int oppLocal = oppSign(start, end));
+ SkASSERT(hitOppDx || !oppWind || !oppLocal);
+ int oppWindVal = oppValue(SkMin32(start, end));
+ if (!oppWind) {
+ oppWind = dx < 0 ? oppWindVal : -oppWindVal;
+ } else if (hitOppDx * dx >= 0) {
+ int oppSideWind = oppWind + (dx < 0 ? oppWindVal : -oppWindVal);
+ if (abs(oppWind) < abs(oppSideWind)) {
+ oppWind = oppSideWind;
+ }
+ }
+ (void) markAndChaseWinding(start, end, winding, oppWind);
+}
+
+bool SkOpSegment::isLinear(int start, int end) const {
+ if (fVerb == SkPath::kLine_Verb) {
+ return true;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ SkDQuad qPart = SkDQuad::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
+ return qPart.isLinear(0, 2);
+ } else {
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDCubic cPart = SkDCubic::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
+ return cPart.isLinear(0, 3);
+ }
+}
+
+// OPTIMIZE: successive calls could start were the last leaves off
+// or calls could specialize to walk forwards or backwards
+bool SkOpSegment::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 SkOpSegment::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;
+}
+
+// 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
+SkOpSpan* SkOpSegment::markAndChaseDone(int index, int endIndex, int winding) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDone(min, winding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ other->markDone(min, winding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneBinary(const SkOpAngle* 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);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ other->markDoneBinary(min, winding, oppWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneBinary(min);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->done()) {
+ return NULL;
+ }
+ other->markDoneBinary(min);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneUnary(min);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->done()) {
+ return NULL;
+ }
+ other->markDoneUnary(min);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneUnary(const SkOpAngle* angle, int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ return markAndChaseDone(index, endIndex, winding);
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* 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);
+ SkOpSpan* last;
+ SkOpSegment* 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;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int oppWinding) {
+ int min = SkMin32(index, endIndex);
+ int step = SkSign32(endIndex - index);
+ markWinding(min, winding, oppWinding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->fTs[min].fWindSum != SK_MinS32) {
+ SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop);
+ return NULL;
+ }
+ other->markWinding(min, winding, oppWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding) {
+ int start = angle->start();
+ int end = angle->end();
+ return markAndChaseWinding(start, end, winding, oppWinding);
+}
+
+SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, bool activeAngle,
+ const SkOpAngle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (UseInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ SkOpSpan* last;
+ if (activeAngle) {
+ last = markAndChaseWinding(angle, maxWinding);
+ } else {
+ last = markAndChaseDoneUnary(angle, maxWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding,
+ int oppSumWinding, bool activeAngle, const SkOpAngle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (UseInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) {
+ oppMaxWinding = oppSumWinding;
+ }
+ SkOpSpan* 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 SkOpSegment::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 SkOpSegment::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 SkOpSegment::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 SkOpSegment::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 SkOpSegment::markOneDone(const char* funName, int tIndex, int winding) {
+ SkOpSpan* span = markOneWinding(funName, tIndex, winding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex) {
+ SkOpSpan* span = verifyOneWinding(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding) {
+ SkOpSpan* span = markOneWinding(funName, tIndex, winding, oppWinding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneUnary(const char* funName, int tIndex) {
+ SkOpSpan* span = verifyOneWindingU(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding) {
+ SkOpSpan& 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;
+}
+
+SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding,
+ int oppWinding) {
+ SkOpSpan& 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;
+}
+
+// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
+bool SkOpSegment::clockwise(int tStart, int tEnd) const {
+ SkASSERT(fVerb != SkPath::kLine_Verb);
+ SkPoint edge[4];
+ subDivide(tStart, tEnd, edge);
+ int points = SkPathOpsVerbToPoints(fVerb);
+ double sum = (edge[0].fX - edge[points].fX) * (edge[0].fY + edge[points].fY);
+ if (fVerb == SkPath::kCubic_Verb) {
+ SkScalar lesser = SkTMin<SkScalar>(edge[0].fY, edge[3].fY);
+ if (edge[1].fY < lesser && edge[2].fY < lesser) {
+ SkDLine tangent1 = {{ {edge[0].fX, edge[0].fY}, {edge[1].fX, edge[1].fY} }};
+ SkDLine tangent2 = {{ {edge[2].fX, edge[2].fY}, {edge[3].fX, edge[3].fY} }};
+ if (SkIntersections::Test(tangent1, tangent2)) {
+ SkPoint topPt = cubic_top(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ sum += (topPt.fX - edge[0].fX) * (topPt.fY + edge[0].fY);
+ sum += (edge[3].fX - topPt.fX) * (edge[3].fY + topPt.fY);
+ return sum <= 0;
+ }
+ }
+ }
+ for (int idx = 0; idx < points; ++idx){
+ sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY);
+ }
+ return sum <= 0;
+}
+
+bool SkOpSegment::monotonicInY(int tStart, int tEnd) const {
+ if (fVerb == SkPath::kLine_Verb) {
+ return false;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ SkDQuad dst = SkDQuad::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.monotonicInY();
+ }
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.monotonicInY();
+}
+
+bool SkOpSegment::serpentine(int tStart, int tEnd) const {
+ if (fVerb != SkPath::kCubic_Verb) {
+ return false;
+ }
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.serpentine();
+}
+
+SkOpSpan* SkOpSegment::verifyOneWinding(const char* funName, int tIndex) {
+ SkOpSpan& 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;
+}
+
+SkOpSpan* SkOpSegment::verifyOneWindingU(const char* funName, int tIndex) {
+ SkOpSpan& 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.
+// FIXME: if abs(start - end) > 1, mark intermediates as unsortable on both ends
+void SkOpSegment::markUnsortable(int start, int end) {
+ SkOpSpan* span = &fTs[start];
+ if (start < end) {
+#if DEBUG_UNSORTABLE
+ debugShowNewWinding(__FUNCTION__, *span, 0);
+#endif
+ span->fUnsortableStart = true;
+ } else {
+ --span;
+#if DEBUG_UNSORTABLE
+ debugShowNewWinding(__FUNCTION__, *span, 0);
+#endif
+ span->fUnsortableEnd = true;
+ }
+ if (!span->fUnsortableStart || !span->fUnsortableEnd || span->fDone) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::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 SkOpSegment::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 SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) {
+ int nextDoorWind = SK_MaxS32;
+ int nextOppWind = SK_MaxS32;
+ if (tIndex > 0) {
+ const SkOpSpan& 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 SkOpSpan& 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 SkOpSpan& below = fTs[tIndex - 1];
+ nextDoorWind = below.fWindValue;
+ nextOppWind = below.fOppValue;
+ }
+ if (nextDoorWind != SK_MaxS32) {
+ SkOpSpan& 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 SkOpSegment::multipleSpans(int end) const {
+ return end > 0 && end < fTs.count() - 1;
+}
+
+bool SkOpSegment::nextCandidate(int* start, int* end) const {
+ while (fTs[*end].fDone) {
+ if (fTs[*end].fT == 1) {
+ return false;
+ }
+ ++(*end);
+ }
+ *start = *end;
+ *end = nextExactSpan(*start, 1);
+ return true;
+}
+
+SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSpan** last) {
+ int end = nextExactSpan(*index, step);
+ SkASSERT(end >= 0);
+ if (multipleSpans(end)) {
+ *last = &fTs[end];
+ return NULL;
+ }
+ const SkOpSpan& endSpan = fTs[end];
+ SkOpSegment* other = endSpan.fOther;
+ *index = endSpan.fOtherIndex;
+ SkASSERT(*index >= 0);
+ int otherEnd = other->nextExactSpan(*index, step);
+ SkASSERT(otherEnd >= 0);
+ *min = SkMin32(*index, otherEnd);
+ if (other->fTs[*min].fTiny) {
+ *last = NULL;
+ return NULL;
+ }
+ 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 SkOpSegment::nextSpan(int from, int step) const {
+ const SkOpSpan& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const SkOpSpan& 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 SkOpSegment::nextExactSpan(int from, int step) const {
+ const SkOpSpan& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const SkOpSpan& span = fTs[to];
+ if (precisely_zero(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
+ }
+ return -1;
+}
+
+void SkOpSegment::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.
+bool SkOpSegment::SortAngles(const SkTArray<SkOpAngle, true>& angles,
+ SkTArray<SkOpAngle*, true>* angleList,
+ SortAngleKind orderKind) {
+ bool sortable = true;
+ int angleCount = angles.count();
+ int angleIndex;
+// FIXME: caller needs to use SkTArray constructor with reserve count
+// angleList->setReserve(angleCount);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle& angle = angles[angleIndex];
+ angleList->push_back(const_cast<SkOpAngle*>(&angle));
+#if DEBUG_ANGLE
+ (*(angleList->end() - 1))->setID(angleIndex);
+#endif
+ sortable &= !(angle.unsortable() || (orderKind == kMustBeOrdered_SortAngleKind
+ && angle.unorderable()));
+ }
+ if (sortable) {
+ SkTQSort<SkOpAngle>(angleList->begin(), angleList->end() - 1);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ if (angles[angleIndex].unsortable() || (orderKind == kMustBeOrdered_SortAngleKind
+ && angles[angleIndex].unorderable())) {
+ sortable = false;
+ break;
+ }
+ }
+ }
+ if (!sortable) {
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle& angle = angles[angleIndex];
+ angle.segment()->markUnsortable(angle.start(), angle.end());
+ }
+ }
+ return sortable;
+}
+
+// return true if midpoints were computed
+bool SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const {
+ SkASSERT(start != end);
+ edge[0] = fTs[start].fPt;
+ int points = SkPathOpsVerbToPoints(fVerb);
+ edge[points] = fTs[end].fPt;
+ if (fVerb == SkPath::kLine_Verb) {
+ return false;
+ }
+ double startT = fTs[start].fT;
+ double endT = fTs[end].fT;
+ if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) {
+ // don't compute midpoints if we already have them
+ if (fVerb == SkPath::kQuad_Verb) {
+ edge[1] = fPts[1];
+ return false;
+ }
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ if (start < end) {
+ edge[1] = fPts[1];
+ edge[2] = fPts[2];
+ return false;
+ }
+ edge[1] = fPts[2];
+ edge[2] = fPts[1];
+ return false;
+ }
+ const SkDPoint sub[2] = {{ edge[0].fX, edge[0].fY}, {edge[points].fX, edge[points].fY }};
+ if (fVerb == SkPath::kQuad_Verb) {
+ edge[1] = SkDQuad::SubDivide(fPts, sub[0], sub[1], startT, endT).asSkPoint();
+ } else {
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDPoint ctrl[2];
+ SkDCubic::SubDivide(fPts, sub[0], sub[1], startT, endT, ctrl);
+ edge[1] = ctrl[0].asSkPoint();
+ edge[2] = ctrl[1].asSkPoint();
+ }
+ return true;
+}
+
+// return true if midpoints were computed
+bool SkOpSegment::subDivide(int start, int end, SkDCubic* result) const {
+ SkASSERT(start != end);
+ (*result)[0].set(fTs[start].fPt);
+ int points = SkPathOpsVerbToPoints(fVerb);
+ (*result)[points].set(fTs[end].fPt);
+ if (fVerb == SkPath::kLine_Verb) {
+ return false;
+ }
+ double startT = fTs[start].fT;
+ double endT = fTs[end].fT;
+ if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) {
+ // don't compute midpoints if we already have them
+ if (fVerb == SkPath::kQuad_Verb) {
+ (*result)[1].set(fPts[1]);
+ return false;
+ }
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ if (start < end) {
+ (*result)[1].set(fPts[1]);
+ (*result)[2].set(fPts[2]);
+ return false;
+ }
+ (*result)[1].set(fPts[2]);
+ (*result)[2].set(fPts[1]);
+ return false;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ (*result)[1] = SkDQuad::SubDivide(fPts, (*result)[0], (*result)[2], startT, endT);
+ } else {
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDCubic::SubDivide(fPts, (*result)[0], (*result)[3], startT, endT, &(*result)[1]);
+ }
+ return true;
+}
+
+void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const {
+ SkPoint edge[4];
+ subDivide(start, end, edge);
+ (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge);
+}
+
+bool SkOpSegment::isTiny(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ const SkOpSpan& mSpan = fTs[SkMin32(start, end)];
+ return mSpan.fTiny;
+}
+
+bool SkOpSegment::isTiny(int index) const {
+ return fTs[index].fTiny;
+}
+
+void SkOpSegment::TrackOutside(SkTArray<double, true>* outsideTs, double end, double start) {
+ int outCount = outsideTs->count();
+ if (outCount == 0 || !approximately_negative(end - (*outsideTs)[outCount - 2])) {
+ outsideTs->push_back(end);
+ outsideTs->push_back(start);
+ }
+}
+
+void SkOpSegment::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;
+}
+
+int SkOpSegment::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 != SK_MaxS32) {
+ oppWinding -= oppSpanWinding;
+ }
+ return oppWinding;
+}
+
+int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(endIndex, startIndex);
+}
+
+int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(startIndex, endIndex);
+}
+
+int SkOpSegment::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 != SK_MaxS32) {
+ winding -= spanWinding;
+ }
+ return winding;
+}
+
+int SkOpSegment::updateWinding(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(endIndex, startIndex);
+}
+
+int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(startIndex, endIndex);
+}
+
+int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) 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_AT_T
+ SkDebugf("%s oldWinding=%d windValue=%d", __FUNCTION__, winding, windVal);
+#endif
+ // see if a + change in T results in a +/- change in X (compute x'(T))
+ *dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX;
+ if (fVerb > SkPath::kLine_Verb && approximately_zero(*dx)) {
+ *dx = fPts[2].fX - fPts[1].fX - *dx;
+ }
+ if (*dx == 0) {
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" dx=0 winding=SK_MinS32\n");
+#endif
+ return SK_MinS32;
+ }
+ if (windVal < 0) { // reverse sign if opp contour traveled in reverse
+ *dx = -*dx;
+ }
+ if (winding * *dx > 0) { // if same signs, result is negative
+ winding += *dx > 0 ? -windVal : windVal;
+ }
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" dx=%c winding=%d\n", *dx > 0 ? '+' : '-', winding);
+#endif
+ return winding;
+}
+
+int SkOpSegment::windSum(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windSum(index);
+}
+
+int SkOpSegment::windValue(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windValue(index);
+}
+
+int SkOpSegment::windValueAt(double t) const {
+ int count = fTs.count();
+ for (int index = 0; index < count; ++index) {
+ if (fTs[index].fT == t) {
+ return fTs[index].fWindValue;
+ }
+ }
+ SkASSERT(0);
+ return 0;
+}
+
+void SkOpSegment::zeroSpan(SkOpSpan* span) {
+ SkASSERT(span->fWindValue > 0 || span->fOppValue != 0);
+ span->fWindValue = 0;
+ span->fOppValue = 0;
+ SkASSERT(!span->fDone);
+ span->fDone = true;
+ ++fDoneSpans;
+}
+
+#if DEBUG_SWAP_TOP
+bool SkOpSegment::controlsContainedByEnds(int tStart, int tEnd) const {
+ if (fVerb != SkPath::kCubic_Verb) {
+ return false;
+ }
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.controlsContainedByEnds();
+}
+#endif
+
+#if DEBUG_CONCIDENT
+// SkASSERT if pair has not already been added
+void SkOpSegment::debugAddTPair(double t, const SkOpSegment& 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_CONCIDENT
+void SkOpSegment::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 || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+void SkOpSegment::debugShowActiveSpans() const {
+ debugValidate();
+ 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;
+ }
+ SkASSERT(i < fTs.count() - 1);
+#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 <= SkPathOpsVerbToPoints(fVerb); ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ const SkOpSpan* span = &fTs[i];
+ SkDebugf(") t=%1.9g (%1.9g,%1.9g)", span->fT, xAtT(span), yAtT(span));
+ int iEnd = i + 1;
+ while (fTs[iEnd].fT < 1 && approximately_equal(fTs[i].fT, fTs[iEnd].fT)) {
+ ++iEnd;
+ }
+ SkDebugf(" tEnd=%1.9g", fTs[iEnd].fT);
+ const SkOpSegment* 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);
+ }
+}
+#endif
+
+
+#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE
+void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& 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 <= SkPathOpsVerbToPoints(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) tEnd=%1.9g newWindSum=%d windSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY,
+ (&span)[1].fT, winding);
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf(" windValue=%d\n", span.fWindValue);
+}
+
+void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& 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 <= SkPathOpsVerbToPoints(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) tEnd=%1.9g newWindSum=%d newOppSum=%d oppSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY,
+ (&span)[1].fT, 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 || DEBUG_SWAP_TOP
+void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles,
+ int first, const int contourWinding,
+ const int oppContourWinding, bool sortable) const {
+ if (--gDebugSortCount < 0) {
+ return;
+ }
+ SkASSERT(angles[first]->segment() == this);
+ SkASSERT(!sortable || angles.count() > 1);
+ int lastSum = contourWinding;
+ int oppLastSum = oppContourWinding;
+ const SkOpAngle* firstAngle = angles[first];
+ int windSum = lastSum - spanSign(firstAngle);
+ int oppoSign = oppSign(firstAngle);
+ int oppWindSum = oppLastSum - oppoSign;
+ #define WIND_AS_STRING(x) char x##Str[12]; if (!valid_wind(x)) strcpy(x##Str, "?"); \
+ else SK_SNPRINTF(x##Str, sizeof(x##Str), "%d", x)
+ WIND_AS_STRING(contourWinding);
+ WIND_AS_STRING(oppContourWinding);
+ SkDebugf("%s %s contourWinding=%s oppContourWinding=%s sign=%d\n", fun, __FUNCTION__,
+ contourWindingStr, oppContourWindingStr, spanSign(angles[first]));
+ int index = first;
+ bool firstTime = true;
+ do {
+ const SkOpAngle& angle = *angles[index];
+ const SkOpSegment& segment = *angle.segment();
+ int start = angle.start();
+ int end = angle.end();
+ const SkOpSpan& sSpan = segment.fTs[start];
+ const SkOpSpan& eSpan = segment.fTs[end];
+ const SkOpSpan& 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] %s", __FUNCTION__, index,
+ angle.unsortable() ? "*** UNSORTABLE *** " : "");
+ #if DEBUG_SORT_COMPACT
+ SkDebugf("id=%d %s start=%d (%1.9g,%1.9g) end=%d (%1.9g,%1.9g)",
+ segment.fID, kLVerbStr[SkPathOpsVerbToPoints(segment.fVerb)],
+ start, segment.xAtT(&sSpan), segment.yAtT(&sSpan), end,
+ segment.xAtT(&eSpan), segment.yAtT(&eSpan));
+ #else
+ switch (segment.fVerb) {
+ case SkPath::kLine_Verb:
+ SkDebugf(LINE_DEBUG_STR, LINE_DEBUG_DATA(segment.fPts));
+ break;
+ case SkPath::kQuad_Verb:
+ SkDebugf(QUAD_DEBUG_STR, QUAD_DEBUG_DATA(segment.fPts));
+ break;
+ case SkPath::kCubic_Verb:
+ SkDebugf(CUBIC_DEBUG_STR, CUBIC_DEBUG_DATA(segment.fPts));
+ break;
+ default:
+ SkASSERT(0);
+ }
+ SkDebugf(" tStart=%1.9g tEnd=%1.9g", sSpan.fT, eSpan.fT);
+ #endif
+ SkDebugf(" sign=%d windValue=%d windSum=", angle.sign(), mSpan.fWindValue);
+ winding_printf(mSpan.fWindSum);
+ int last, wind;
+ if (opp) {
+ last = oppLastSum;
+ wind = oppWindSum;
+ } else {
+ last = lastSum;
+ wind = windSum;
+ }
+ bool useInner = valid_wind(last) && valid_wind(wind) && UseInnerWinding(last, wind);
+ WIND_AS_STRING(last);
+ WIND_AS_STRING(wind);
+ WIND_AS_STRING(lastSum);
+ WIND_AS_STRING(oppLastSum);
+ WIND_AS_STRING(windSum);
+ WIND_AS_STRING(oppWindSum);
+ #undef WIND_AS_STRING
+ if (!oppoSign) {
+ SkDebugf(" %s->%s (max=%s)", lastStr, windStr, useInner ? windStr : lastStr);
+ } else {
+ SkDebugf(" %s->%s (%s->%s)", lastStr, windStr, opp ? lastSumStr : oppLastSumStr,
+ opp ? windSumStr : oppWindSumStr);
+ }
+ SkDebugf(" done=%d tiny=%d opp=%d\n", mSpan.fDone, mSpan.fTiny, opp);
+#if 0 && DEBUG_ANGLE
+ angle.debugShow(segment.xyAtT(&sSpan));
+#endif
+ ++index;
+ if (index == angles.count()) {
+ index = 0;
+ }
+ if (firstTime) {
+ firstTime = false;
+ }
+ } while (index != first);
+}
+
+void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles,
+ int first, bool sortable) {
+ const SkOpAngle* firstAngle = angles[first];
+ const SkOpSegment* segment = firstAngle->segment();
+ int winding = segment->updateWinding(firstAngle);
+ int oppWinding = segment->updateOppWinding(firstAngle);
+ debugShowSort(fun, angles, first, winding, oppWinding, sortable);
+}
+
+#endif
+
+#if DEBUG_SHOW_WINDING
+int SkOpSegment::debugShowWindingValues(int slotCount, int ofInterest) const {
+ if (!(1 << fID & ofInterest)) {
+ return 0;
+ }
+ int sum = 0;
+ SkTArray<char, true> slots(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
+
+void SkOpSegment::debugValidate() const {
+#if DEBUG_VALIDATE
+ int count = fTs.count();
+ SkASSERT(count >= 2);
+ SkASSERT(fTs[0].fT == 0);
+ SkASSERT(fTs[count - 1].fT == 1);
+ int done = 0;
+ double t = -1;
+ for (int i = 0; i < count; ++i) {
+ const SkOpSpan& span = fTs[i];
+ SkASSERT(t <= span.fT);
+ t = span.fT;
+ int otherIndex = span.fOtherIndex;
+ const SkOpSegment* other = span.fOther;
+ const SkOpSpan& otherSpan = other->fTs[otherIndex];
+ SkASSERT(otherSpan.fPt == span.fPt);
+ SkASSERT(otherSpan.fOtherT == t);
+ SkASSERT(&fTs[i] == &otherSpan.fOther->fTs[otherSpan.fOtherIndex]);
+ done += span.fDone;
+ }
+ SkASSERT(done == fDoneSpans);
+#endif
+}
diff --git a/pathops/SkOpSegment.h b/pathops/SkOpSegment.h
new file mode 100644
index 00000000..bfaf4ed9
--- /dev/null
+++ b/pathops/SkOpSegment.h
@@ -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.
+ */
+#ifndef SkOpSegment_DEFINE
+#define SkOpSegment_DEFINE
+
+#include "SkOpAngle.h"
+#include "SkOpSpan.h"
+#include "SkPathOpsBounds.h"
+#include "SkPathOpsCurve.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+
+class SkPathWriter;
+
+class SkOpSegment {
+public:
+ SkOpSegment() {
+#if DEBUG_DUMP
+ fID = ++gSegmentID;
+#endif
+ }
+
+ bool operator<(const SkOpSegment& rh) const {
+ return fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fBounds;
+ }
+
+ // 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;
+ }
+
+ int count() const {
+ return fTs.count();
+ }
+
+ bool done() const {
+ SkASSERT(fDoneSpans <= fTs.count());
+ return fDoneSpans == fTs.count();
+ }
+
+ bool done(int min) const {
+ return fTs[min].fDone;
+ }
+
+ bool done(const SkOpAngle* angle) const {
+ return done(SkMin32(angle->start(), angle->end()));
+ }
+
+ SkVector dxdy(int index) const {
+ return (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, fTs[index].fT);
+ }
+
+ SkScalar dy(int index) const {
+ return dxdy(index).fY;
+ }
+
+ bool intersected() const {
+ return fTs.count() > 0;
+ }
+
+ bool isCanceled(int tIndex) const {
+ return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 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 isVertical() const {
+ return fBounds.fLeft == fBounds.fRight;
+ }
+
+ bool isVertical(int start, int end) const {
+ return (*CurveIsVertical[SkPathOpsVerbToPoints(fVerb)])(fPts, start, end);
+ }
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ int oppSign(const SkOpAngle* 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 SkOpAngle* 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 SkOpAngle* angle) const {
+ int lesser = SkMin32(angle->start(), angle->end());
+ return fTs[lesser].fOppValue;
+ }
+
+ const SkOpSegment* other(int index) const {
+ return fTs[index].fOther;
+ }
+
+ // was used only by right angle winding finding
+ SkPoint ptAtT(double mid) const {
+ return (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid);
+ }
+
+ 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 -= deltaSum;
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ const SkOpSpan& span(int tIndex) const {
+ return fTs[tIndex];
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ const SkTDArray<SkOpSpan>& spans() const {
+ return fTs;
+ }
+
+ int spanSign(const SkOpAngle* 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;
+ }
+
+ double tAtMid(int start, int end, double mid) const {
+ return fTs[start].fT * (1 - mid) + fTs[end].fT * mid;
+ }
+
+ bool unsortable(int index) const {
+ return fTs[index].fUnsortableStart || fTs[index].fUnsortableEnd;
+ }
+
+ void updatePts(const SkPoint pts[]) {
+ fPts = pts;
+ }
+
+ SkPath::Verb verb() const {
+ return fVerb;
+ }
+
+ int windSum(int tIndex) const {
+ return fTs[tIndex].fWindSum;
+ }
+
+ int windValue(int tIndex) const {
+ return fTs[tIndex].fWindValue;
+ }
+
+ SkScalar xAtT(int index) const {
+ return xAtT(&fTs[index]);
+ }
+
+ SkScalar xAtT(const SkOpSpan* span) const {
+ return xyAtT(span).fX;
+ }
+
+ const SkPoint& xyAtT(const SkOpSpan* span) const {
+ return span->fPt;
+ }
+
+ const SkPoint& xyAtT(int index) const {
+ return xyAtT(&fTs[index]);
+ }
+
+ SkScalar yAtT(int index) const {
+ return yAtT(&fTs[index]);
+ }
+
+ SkScalar yAtT(const SkOpSpan* span) const {
+ return xyAtT(span).fY;
+ }
+
+ bool activeAngle(int index, int* done, SkTArray<SkOpAngle, true>* angles);
+ SkPoint activeLeftTop(bool onlySortable, int* firstT) const;
+ bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op);
+ bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op,
+ int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding,
+ int* oppMaxWinding, int* oppSumWinding);
+ bool activeWinding(int index, int endIndex);
+ bool activeWinding(int index, int endIndex, int* maxWinding, int* sumWinding);
+ void addCubic(const SkPoint pts[4], bool operand, bool evenOdd);
+ void addCurveTo(int start, int end, SkPathWriter* path, bool active) const;
+ void addLine(const SkPoint pts[2], bool operand, bool evenOdd);
+ void addOtherT(int index, double otherT, int otherIndex);
+ void addQuad(const SkPoint pts[3], bool operand, bool evenOdd);
+ int addSelfT(SkOpSegment* other, const SkPoint& pt, double newT);
+ int addT(SkOpSegment* other, const SkPoint& pt, double newT);
+ void addTCancel(double startT, double endT, SkOpSegment* other, double oStartT, double oEndT);
+ void addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT,
+ double oEndT);
+ void addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt);
+ void addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt,
+ const SkPoint& oPt);
+ int addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT);
+ bool betweenTs(int lesser, double testT, int greater) const;
+ void checkEnds();
+ int computeSum(int startIndex, int endIndex, bool binary);
+ int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething,
+ double mid, bool opp, bool current) const;
+ SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable, SkPathOp op, const int xorMiMask,
+ const int xorSuMask);
+ SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable);
+ SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable);
+ void findTooCloseToCall();
+ SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool onlySortable);
+ void fixOtherTIndex();
+ void initWinding(int start, int end);
+ void initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind,
+ SkScalar hitOppDx);
+ bool isLinear(int start, int end) const;
+ bool isMissing(double startT) const;
+ bool isSimple(int end) const;
+ bool isTiny(const SkOpAngle* angle) const;
+ bool isTiny(int index) const;
+ SkOpSpan* markAndChaseDoneBinary(int index, int endIndex);
+ SkOpSpan* markAndChaseDoneUnary(int index, int endIndex);
+ SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding);
+ SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding,
+ bool activeAngle, const SkOpAngle* angle);
+ void markDone(int index, int winding);
+ void markDoneBinary(int index);
+ void markDoneUnary(int index);
+ SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding);
+ SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding);
+ void markWinding(int index, int winding);
+ void markWinding(int index, int winding, int oppWinding);
+ bool nextCandidate(int* start, int* end) const;
+ int nextExactSpan(int from, int step) const;
+ int nextSpan(int from, int step) const;
+ void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding,
+ int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding);
+ enum SortAngleKind {
+ kMustBeOrdered_SortAngleKind, // required for winding calc
+ kMayBeUnordered_SortAngleKind // ok for find top
+ };
+ static bool SortAngles(const SkTArray<SkOpAngle, true>& angles,
+ SkTArray<SkOpAngle*, true>* angleList,
+ SortAngleKind );
+ bool subDivide(int start, int end, SkPoint edge[4]) const;
+ bool subDivide(int start, int end, SkDCubic* result) const;
+ void undoneSpan(int* start, int* end);
+ int updateOppWindingReverse(const SkOpAngle* angle) const;
+ int updateWindingReverse(const SkOpAngle* angle) const;
+ static bool UseInnerWinding(int outerWinding, int innerWinding);
+ int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const;
+ int windSum(const SkOpAngle* angle) const;
+ int windValue(const SkOpAngle* angle) const;
+
+#if DEBUG_DUMP
+ int debugID() const {
+ return fID;
+ }
+#endif
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+ void debugShowActiveSpans() const;
+#endif
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ void debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles, int first,
+ const int contourWinding, const int oppContourWinding, bool sortable) const;
+ void debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles, int first,
+ bool sortable);
+#endif
+#if DEBUG_CONCIDENT
+ void debugShowTs() const;
+#endif
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int slotCount, int ofInterest) const;
+#endif
+
+private:
+ bool activeAngleOther(int index, int* done, SkTArray<SkOpAngle, true>* angles);
+ bool activeAngleInner(int index, int* done, SkTArray<SkOpAngle, true>* angles);
+ void addAngle(SkTArray<SkOpAngle, true>* angles, int start, int end) const;
+ void addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd);
+ void addCoinOutsides(const SkTArray<double, true>& outsideTs, SkOpSegment* other, double oEnd);
+ void addTwoAngles(int start, int end, SkTArray<SkOpAngle, true>* angles) const;
+ int advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex);
+ int advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index);
+ void buildAngles(int index, SkTArray<SkOpAngle, true>* angles, bool includeOpp) const;
+ void buildAnglesInner(int index, SkTArray<SkOpAngle, true>* angles) const;
+ int bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index,
+ SkTArray<double, true>* outsideTs);
+ int bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex,
+ SkTArray<double, true>* oOutsideTs);
+ bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta);
+ bool clockwise(int tStart, int tEnd) const;
+ void decrementSpan(SkOpSpan* span);
+ bool equalPoints(int greaterTIndex, int lesserTIndex);
+ int findStartingEdge(const SkTArray<SkOpAngle*, true>& sorted, int start, int end);
+ void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd);
+ void matchWindingValue(int tIndex, double t, bool borrowWind);
+ SkOpSpan* markAndChaseDone(int index, int endIndex, int winding);
+ SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding);
+ SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, const int winding);
+ SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding);
+ SkOpSpan* markAngle(int maxWinding, int sumWinding, bool activeAngle, const SkOpAngle* angle);
+ void markDoneBinary(int index, int winding, int oppWinding);
+ SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding);
+ void markOneDone(const char* funName, int tIndex, int winding);
+ void markOneDoneBinary(const char* funName, int tIndex);
+ void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding);
+ void markOneDoneUnary(const char* funName, int tIndex);
+ void markUnsortable(int start, int end);
+ bool monotonicInY(int tStart, int tEnd) const;
+ bool multipleSpans(int end) const;
+ SkOpSegment* nextChase(int* index, const int step, int* min, SkOpSpan** last);
+ bool serpentine(int tStart, int tEnd) const;
+ void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const;
+ static void TrackOutside(SkTArray<double, true>* outsideTs, double end, double start);
+ int updateOppWinding(int index, int endIndex) const;
+ int updateOppWinding(const SkOpAngle* angle) const;
+ int updateWinding(int index, int endIndex) const;
+ int updateWinding(const SkOpAngle* angle) const;
+ SkOpSpan* verifyOneWinding(const char* funName, int tIndex);
+ SkOpSpan* verifyOneWindingU(const char* funName, int tIndex);
+ int windValueAt(double t) const;
+ void zeroSpan(SkOpSpan* span);
+
+#if DEBUG_SWAP_TOP
+ bool controlsContainedByEnds(int tStart, int tEnd) const;
+#endif
+#if DEBUG_CONCIDENT
+ void debugAddTPair(double t, const SkOpSegment& other, double otherT) const;
+#endif
+#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE
+ void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding);
+ void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding);
+#endif
+#if DEBUG_WINDING
+ static char as_digit(int value) {
+ return value < 0 ? '?' : value <= 9 ? '0' + value : '+';
+ }
+#endif
+ void debugValidate() const;
+
+ const SkPoint* fPts;
+ SkPathOpsBounds fBounds;
+ // FIXME: can't convert to SkTArray because it uses insert
+ SkTDArray<SkOpSpan> 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
+};
+
+#endif
diff --git a/pathops/SkOpSpan.h b/pathops/SkOpSpan.h
new file mode 100644
index 00000000..3666623f
--- /dev/null
+++ b/pathops/SkOpSpan.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 SkOpSpan_DEFINED
+#define SkOpSpan_DEFINED
+
+#include "SkPoint.h"
+
+class SkOpSegment;
+
+struct SkOpSpan {
+ SkOpSegment* fOther;
+ SkPoint fPt; // computed when the curves are intersected
+ 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
+ bool fLoop; // set when a cubic loops back to this point
+};
+
+#endif
diff --git a/pathops/SkPathOpsBounds.cpp b/pathops/SkPathOpsBounds.cpp
new file mode 100644
index 00000000..106cd300
--- /dev/null
+++ b/pathops/SkPathOpsBounds.cpp
@@ -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 "SkPathOpsBounds.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+
+void SkPathOpsBounds::setCubicBounds(const SkPoint a[4]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDRect dRect;
+ dRect.setBounds(cubic);
+ set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop),
+ SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom));
+}
+
+void SkPathOpsBounds::setLineBounds(const SkPoint a[2]) {
+ setPointBounds(a[0]);
+ add(a[1]);
+}
+
+void SkPathOpsBounds::setQuadBounds(const SkPoint a[3]) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDRect dRect;
+ dRect.setBounds(quad);
+ set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop),
+ SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom));
+}
+
+void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]) = {
+ NULL,
+ &SkPathOpsBounds::setLineBounds,
+ &SkPathOpsBounds::setQuadBounds,
+ &SkPathOpsBounds::setCubicBounds
+};
diff --git a/pathops/SkPathOpsBounds.h b/pathops/SkPathOpsBounds.h
new file mode 100644
index 00000000..61ef7bb8
--- /dev/null
+++ b/pathops/SkPathOpsBounds.h
@@ -0,0 +1,63 @@
+/*
+ * 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 SkPathOpBounds_DEFINED
+#define SkPathOpBounds_DEFINED
+
+#include "SkPathOpsRect.h"
+#include "SkRect.h"
+
+// SkPathOpsBounds, unlike SkRect, does not consider a line to be empty.
+struct SkPathOpsBounds : public SkRect {
+ static bool Intersects(const SkPathOpsBounds& a, const SkPathOpsBounds& b) {
+ return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
+ a.fTop <= b.fBottom && b.fTop <= a.fBottom;
+ }
+
+ // Note that add(), unlike SkRect::join() or SkRect::growToInclude()
+ // does not treat the bounds of horizontal and vertical lines as
+ // empty rectangles.
+ 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 SkPathOpsBounds& 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;
+ }
+
+ // unlike isEmpty(), this permits lines, but not points
+ // FIXME: unused for now
+ bool isReallyEmpty() const {
+ // use !<= instead of > to detect NaN values
+ return !(fLeft <= fRight) || !(fTop <= fBottom)
+ || (fLeft == fRight && fTop == fBottom);
+ }
+
+ void setCubicBounds(const SkPoint a[4]);
+ void setLineBounds(const SkPoint a[2]);
+ void setQuadBounds(const SkPoint a[3]);
+
+ void setPointBounds(const SkPoint& pt) {
+ fLeft = fRight = pt.fX;
+ fTop = fBottom = pt.fY;
+ }
+
+ typedef SkRect INHERITED;
+};
+
+extern void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]);
+
+#endif
diff --git a/pathops/SkPathOpsCommon.cpp b/pathops/SkPathOpsCommon.cpp
new file mode 100644
index 00000000..28cf59cd
--- /dev/null
+++ b/pathops/SkPathOpsCommon.cpp
@@ -0,0 +1,594 @@
+/*
+ * 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 "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+#include "SkTSort.h"
+
+static int contourRangeCheckY(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment** currentPtr,
+ int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx,
+ bool* tryAgain, double* midPtr, bool opp) {
+ const int index = *indexPtr;
+ const int endIndex = *endIndexPtr;
+ const double mid = *midPtr;
+ const SkOpSegment* current = *currentPtr;
+ double tAtMid = current->tAtMid(index, endIndex, mid);
+ SkPoint basePt = current->ptAtT(tAtMid);
+ int contourCount = contourList.count();
+ SkScalar bestY = SK_ScalarMin;
+ SkOpSegment* bestSeg = NULL;
+ int bestTIndex = 0;
+ bool bestOpp;
+ bool hitSomething = false;
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = contourList[cTest];
+ bool testOpp = contour->operand() ^ current->operand() ^ opp;
+ if (basePt.fY < contour->bounds().fTop) {
+ continue;
+ }
+ if (bestY > contour->bounds().fBottom) {
+ continue;
+ }
+ int segmentCount = contour->segments().count();
+ for (int test = 0; test < segmentCount; ++test) {
+ SkOpSegment* testSeg = &contour->segments()[test];
+ SkScalar testY = bestY;
+ double testHit;
+ int testTIndex = testSeg->crossedSpanY(basePt, &testY, &testHit, &hitSomething, tAtMid,
+ testOpp, testSeg == current);
+ if (testTIndex < 0) {
+ if (testTIndex == SK_MinS32) {
+ hitSomething = true;
+ bestSeg = NULL;
+ goto abortContours; // vertical encountered, return and try different point
+ }
+ continue;
+ }
+ if (testSeg == current && current->betweenTs(index, testHit, endIndex)) {
+ double baseT = current->t(index);
+ double endT = current->t(endIndex);
+ double newMid = (testHit - baseT) / (endT - baseT);
+#if DEBUG_WINDING
+ double midT = current->tAtMid(index, endIndex, mid);
+ SkPoint midXY = current->xyAtT(midT);
+ double newMidT = current->tAtMid(index, endIndex, newMid);
+ SkPoint newXY = current->xyAtT(newMidT);
+ SkDebugf("%s [%d] mid=%1.9g->%1.9g s=%1.9g (%1.9g,%1.9g) m=%1.9g (%1.9g,%1.9g)"
+ " n=%1.9g (%1.9g,%1.9g) e=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), mid, newMid,
+ baseT, current->xAtT(index), current->yAtT(index),
+ baseT + mid * (endT - baseT), midXY.fX, midXY.fY,
+ baseT + newMid * (endT - baseT), newXY.fX, newXY.fY,
+ endT, current->xAtT(endIndex), current->yAtT(endIndex));
+#endif
+ *midPtr = newMid * 2; // calling loop with divide by 2 before continuing
+ return SK_MinS32;
+ }
+ bestSeg = testSeg;
+ *bestHit = testHit;
+ bestOpp = testOpp;
+ bestTIndex = testTIndex;
+ bestY = testY;
+ }
+ }
+abortContours:
+ int result;
+ if (!bestSeg) {
+ result = hitSomething ? SK_MinS32 : 0;
+ } else {
+ if (bestSeg->windSum(bestTIndex) == SK_MinS32) {
+ *currentPtr = bestSeg;
+ *indexPtr = bestTIndex;
+ *endIndexPtr = bestSeg->nextSpan(bestTIndex, 1);
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ *tryAgain = true;
+ return 0;
+ }
+ result = bestSeg->windingAtT(*bestHit, bestTIndex, bestOpp, bestDx);
+ SkASSERT(result == SK_MinS32 || *bestDx);
+ }
+ double baseT = current->t(index);
+ double endT = current->t(endIndex);
+ *bestHit = baseT + mid * (endT - baseT);
+ return result;
+}
+
+SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end) {
+ int contourCount = contourList.count();
+ SkOpSegment* result;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ result = contour->undoneSegment(start, end);
+ if (result) {
+ return result;
+ }
+ }
+ return NULL;
+}
+
+SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex) {
+ while (chase.count()) {
+ SkOpSpan* span;
+ chase.pop(&span);
+ const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex);
+ SkOpSegment* segment = backPtr.fOther;
+ tIndex = backPtr.fOtherIndex;
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ int done = 0;
+ if (segment->activeAngle(tIndex, &done, &angles)) {
+ SkOpAngle* 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;
+ }
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SkOpSegment::SortAngles(angles, &sorted,
+ SkOpSegment::kMayBeUnordered_SortAngleKind);
+ int angleCount = sorted.count();
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0, sortable);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const SkOpAngle* 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, sortable);
+ #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 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 SkOpSpan& nextSpan = segment->span(lesser);
+ if (!nextSpan.fDone) {
+ // 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 (SkOpSegment::UseInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ segment->markAndChaseWinding(angle, maxWinding, 0);
+ break;
+ }
+ } while (++nextIndex != lastIndex);
+ *chase.insert(0) = span;
+ return segment;
+ }
+ return NULL;
+}
+
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList) {
+ int index;
+ for (index = 0; index < contourList.count(); ++ index) {
+ contourList[index]->debugShowActiveSpans();
+ }
+}
+#endif
+
+static SkOpSegment* findSortableTop(const SkTArray<SkOpContour*, true>& contourList,
+ int* index, int* endIndex, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool onlySortable) {
+ SkOpSegment* result;
+ do {
+ SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax};
+ int contourCount = contourList.count();
+ SkOpSegment* topStart = NULL;
+ *done = true;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ if (contour->done()) {
+ continue;
+ }
+ const SkPathOpsBounds& bounds = contour->bounds();
+ if (bounds.fBottom < topLeft->fY) {
+ *done = false;
+ continue;
+ }
+ if (bounds.fBottom == topLeft->fY && bounds.fRight < topLeft->fX) {
+ *done = false;
+ continue;
+ }
+ contour->topSortableSegment(*topLeft, &bestXY, &topStart);
+ if (!contour->done()) {
+ *done = false;
+ }
+ }
+ if (!topStart) {
+ return NULL;
+ }
+ *topLeft = bestXY;
+ result = topStart->findTop(index, endIndex, unsortable, onlySortable);
+ } while (!result);
+ return result;
+}
+
+static int rightAngleWinding(const SkTArray<SkOpContour*, true>& contourList,
+ SkOpSegment** current, int* index, int* endIndex, double* tHit,
+ SkScalar* hitDx, bool* tryAgain, bool opp) {
+ double test = 0.9;
+ int contourWinding;
+ do {
+ contourWinding = contourRangeCheckY(contourList, current, index, endIndex, tHit, hitDx,
+ tryAgain, &test, opp);
+ if (contourWinding != SK_MinS32 || *tryAgain) {
+ return contourWinding;
+ }
+ test /= 2;
+ } while (!approximately_negative(test));
+ SkASSERT(0); // should be OK to comment out, but interested when this hits
+ return contourWinding;
+}
+
+static void skipVertical(const SkTArray<SkOpContour*, true>& contourList,
+ SkOpSegment** current, int* index, int* endIndex) {
+ if (!(*current)->isVertical(*index, *endIndex)) {
+ return;
+ }
+ int contourCount = contourList.count();
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ if (contour->done()) {
+ continue;
+ }
+ *current = contour->nonVerticalSegment(index, endIndex);
+ if (*current) {
+ return;
+ }
+ }
+}
+
+SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, bool* firstContour,
+ int* indexPtr, int* endIndexPtr, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool binary) {
+ SkOpSegment* current = findSortableTop(contourList, indexPtr, endIndexPtr, topLeft, unsortable,
+ done, true);
+ if (!current) {
+ return NULL;
+ }
+ const int index = *indexPtr;
+ const int endIndex = *endIndexPtr;
+ if (*firstContour) {
+ current->initWinding(index, endIndex);
+ *firstContour = false;
+ return current;
+ }
+ int minIndex = SkMin32(index, endIndex);
+ int sumWinding = current->windSum(minIndex);
+ if (sumWinding != SK_MinS32) {
+ return current;
+ }
+ sumWinding = current->computeSum(index, endIndex, binary);
+ if (sumWinding != SK_MinS32) {
+ return current;
+ }
+ int contourWinding;
+ int oppContourWinding = 0;
+ // 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
+ bool tryAgain;
+ double tHit;
+ SkScalar hitDx = 0;
+ SkScalar hitOppDx = 0;
+ do {
+ // if current is vertical, find another candidate which is not
+ // if only remaining candidates are vertical, then they can be marked done
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ skipVertical(contourList, &current, indexPtr, endIndexPtr);
+
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ tryAgain = false;
+ contourWinding = rightAngleWinding(contourList, &current, indexPtr, endIndexPtr, &tHit,
+ &hitDx, &tryAgain, false);
+ if (tryAgain) {
+ continue;
+ }
+ if (!binary) {
+ break;
+ }
+ oppContourWinding = rightAngleWinding(contourList, &current, indexPtr, endIndexPtr, &tHit,
+ &hitOppDx, &tryAgain, true);
+ } while (tryAgain);
+ current->initWinding(*indexPtr, *endIndexPtr, tHit, contourWinding, hitDx, oppContourWinding,
+ hitOppDx);
+ return current;
+}
+
+void CheckEnds(SkTArray<SkOpContour*, true>* contourList) {
+ // it's hard to determine if the end of a cubic or conic nearly intersects another curve.
+ // instead, look to see if the connecting curve intersected at that same end.
+ int contourCount = (*contourList).count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = (*contourList)[cTest];
+ contour->checkEnds();
+ }
+}
+
+void FixOtherTIndex(SkTArray<SkOpContour*, true>* contourList) {
+ int contourCount = (*contourList).count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = (*contourList)[cTest];
+ contour->fixOtherTIndex();
+ }
+}
+
+void SortSegments(SkTArray<SkOpContour*, true>* contourList) {
+ int contourCount = (*contourList).count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = (*contourList)[cTest];
+ contour->sortSegments();
+ }
+}
+
+void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list,
+ bool evenOdd, bool oppEvenOdd) {
+ int count = contours.count();
+ if (count == 0) {
+ return;
+ }
+ for (int index = 0; index < count; ++index) {
+ SkOpContour& contour = contours[index];
+ contour.setOppXor(contour.operand() ? evenOdd : oppEvenOdd);
+ list.push_back(&contour);
+ }
+ SkTQSort<SkOpContour>(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);
+}
+
+class DistanceLessThan {
+public:
+ DistanceLessThan(double* distances) : fDistances(distances) { }
+ double* fDistances;
+ bool operator()(const int one, const int two) {
+ return fDistances[one] < fDistances[two];
+ }
+};
+
+ /*
+ check start and end of each contour
+ if not the same, record them
+ match them up
+ connect closest
+ reassemble contour pieces into new path
+ */
+void Assemble(const SkPathWriter& path, SkPathWriter* simple) {
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("%s\n", __FUNCTION__);
+#endif
+ SkTArray<SkOpContour> contours;
+ SkOpEdgeBuilder builder(path, contours);
+ builder.finish();
+ int count = contours.count();
+ int outer;
+ SkTArray<int, true> runs(count); // indices of partial contours
+ for (outer = 0; outer < count; ++outer) {
+ const SkOpContour& 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.push_back(outer);
+ }
+ count = runs.count();
+ if (count == 0) {
+ return;
+ }
+ SkTArray<int, true> sLink, eLink;
+ sLink.push_back_n(count);
+ eLink.push_back_n(count);
+ int rIndex, iIndex;
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ sLink[rIndex] = eLink[rIndex] = SK_MaxS32;
+ }
+ const int ends = count * 2; // all starts and ends
+ const int entries = (ends - 1) * count; // folded triangle : n * (n - 1) / 2
+ SkTArray<double, true> distances;
+ distances.push_back_n(entries);
+ for (rIndex = 0; rIndex < ends - 1; ++rIndex) {
+ outer = runs[rIndex >> 1];
+ const SkOpContour& oContour = contours[outer];
+ const SkPoint& oPt = rIndex & 1 ? oContour.end() : oContour.start();
+ const int row = rIndex < count - 1 ? rIndex * ends : (ends - rIndex - 2)
+ * ends - rIndex - 1;
+ for (iIndex = rIndex + 1; iIndex < ends; ++iIndex) {
+ int inner = runs[iIndex >> 1];
+ const SkOpContour& iContour = contours[inner];
+ const SkPoint& iPt = iIndex & 1 ? iContour.end() : iContour.start();
+ double dx = iPt.fX - oPt.fX;
+ double dy = iPt.fY - oPt.fY;
+ double dist = dx * dx + dy * dy;
+ distances[row + iIndex] = dist; // oStart distance from iStart
+ }
+ }
+ SkTArray<int, true> sortedDist;
+ sortedDist.push_back_n(entries);
+ for (rIndex = 0; rIndex < entries; ++rIndex) {
+ sortedDist[rIndex] = rIndex;
+ }
+ SkTQSort<int>(sortedDist.begin(), sortedDist.end() - 1, DistanceLessThan(distances.begin()));
+ int remaining = count; // number of start/end pairs
+ for (rIndex = 0; rIndex < entries; ++rIndex) {
+ int pair = sortedDist[rIndex];
+ int row = pair / ends;
+ int col = pair - row * ends;
+ int thingOne = row < col ? row : ends - row - 2;
+ int ndxOne = thingOne >> 1;
+ bool endOne = thingOne & 1;
+ int* linkOne = endOne ? eLink.begin() : sLink.begin();
+ if (linkOne[ndxOne] != SK_MaxS32) {
+ continue;
+ }
+ int thingTwo = row < col ? col : ends - row + col - 1;
+ int ndxTwo = thingTwo >> 1;
+ bool endTwo = thingTwo & 1;
+ int* linkTwo = endTwo ? eLink.begin() : sLink.begin();
+ if (linkTwo[ndxTwo] != SK_MaxS32) {
+ continue;
+ }
+ SkASSERT(&linkOne[ndxOne] != &linkTwo[ndxTwo]);
+ bool flip = endOne == endTwo;
+ linkOne[ndxOne] = flip ? ~ndxTwo : ndxTwo;
+ linkTwo[ndxTwo] = flip ? ~ndxOne : ndxOne;
+ if (!--remaining) {
+ break;
+ }
+ }
+ SkASSERT(!remaining);
+#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 != SK_MaxS32);
+ sLink[rIndex] = SK_MaxS32;
+ int eIndex;
+ if (sIndex < 0) {
+ eIndex = sLink[~sIndex];
+ sLink[~sIndex] = SK_MaxS32;
+ } else {
+ eIndex = eLink[sIndex];
+ eLink[sIndex] = SK_MaxS32;
+ }
+ SkASSERT(eIndex != SK_MaxS32);
+#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 SkOpContour& 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 != SK_MaxS32);
+ eLink[rIndex] = SK_MaxS32;
+ if (eIndex >= 0) {
+ SkASSERT(sLink[eIndex] == rIndex);
+ sLink[eIndex] = SK_MaxS32;
+ } else {
+ SkASSERT(eLink[~eIndex] == ~rIndex);
+ eLink[~eIndex] = SK_MaxS32;
+ }
+ } else {
+ eIndex = sLink[rIndex];
+ SkASSERT(eIndex != SK_MaxS32);
+ sLink[rIndex] = SK_MaxS32;
+ if (eIndex >= 0) {
+ SkASSERT(eLink[eIndex] == rIndex);
+ eLink[eIndex] = SK_MaxS32;
+ } else {
+ SkASSERT(sLink[~eIndex] == ~rIndex);
+ sLink[~eIndex] = SK_MaxS32;
+ }
+ }
+ rIndex = eIndex;
+ if (rIndex < 0) {
+ forward ^= 1;
+ rIndex = ~rIndex;
+ }
+ } while (true);
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ if (sLink[rIndex] != SK_MaxS32) {
+ break;
+ }
+ }
+ } while (rIndex < count);
+#if DEBUG_ASSEMBLE
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ SkASSERT(sLink[rIndex] == SK_MaxS32);
+ SkASSERT(eLink[rIndex] == SK_MaxS32);
+ }
+#endif
+}
diff --git a/pathops/SkPathOpsCommon.h b/pathops/SkPathOpsCommon.h
new file mode 100644
index 00000000..4ba4af23
--- /dev/null
+++ b/pathops/SkPathOpsCommon.h
@@ -0,0 +1,32 @@
+/*
+ * 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 SkPathOpsCommon_DEFINED
+#define SkPathOpsCommon_DEFINED
+
+#include "SkOpContour.h"
+#include "SkTDArray.h"
+
+class SkPathWriter;
+
+void Assemble(const SkPathWriter& path, SkPathWriter* simple);
+void CheckEnds(SkTArray<SkOpContour*, true>* contourList);
+// FIXME: find chase uses insert, so it can't be converted to SkTArray yet
+SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex);
+SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, bool* firstContour,
+ int* index, int* endIndex, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool binary);
+SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end);
+void FixOtherTIndex(SkTArray<SkOpContour*, true>* contourList);
+void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list,
+ bool evenOdd, bool oppEvenOdd);
+void SortSegments(SkTArray<SkOpContour*, true>* contourList);
+
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList);
+#endif
+
+#endif
diff --git a/pathops/SkPathOpsCubic.cpp b/pathops/SkPathOpsCubic.cpp
new file mode 100644
index 00000000..5fbfeba5
--- /dev/null
+++ b/pathops/SkPathOpsCubic.cpp
@@ -0,0 +1,510 @@
+/*
+ * 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 "SkLineParameters.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+
+const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
+
+// FIXME: cache keep the bounds and/or precision with the caller?
+double SkDCubic::calcPrecision() const {
+ SkDRect dRect;
+ dRect.setBounds(*this); // OPTIMIZATION: just use setRawBounds ?
+ double width = dRect.fRight - dRect.fLeft;
+ double height = dRect.fBottom - dRect.fTop;
+ return (width > height ? width : height) / gPrecisionUnit;
+}
+
+bool SkDCubic::clockwise() const {
+ double sum = (fPts[0].fX - fPts[3].fX) * (fPts[0].fY + fPts[3].fY);
+ for (int idx = 0; idx < 3; ++idx) {
+ sum += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ return sum <= 0;
+}
+
+void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
+ *A = src[6]; // d
+ *B = src[4] * 3; // 3*c
+ *C = src[2] * 3; // 3*b
+ *D = src[0]; // a
+ *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d
+ *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c
+ *C -= 3 * *D; // C = -3*a + 3*b
+}
+
+bool SkDCubic::controlsContainedByEnds() const {
+ SkDVector startTan = fPts[1] - fPts[0];
+ if (startTan.fX == 0 && startTan.fY == 0) {
+ startTan = fPts[2] - fPts[0];
+ }
+ SkDVector endTan = fPts[2] - fPts[3];
+ if (endTan.fX == 0 && endTan.fY == 0) {
+ endTan = fPts[1] - fPts[3];
+ }
+ if (startTan.dot(endTan) >= 0) {
+ return false;
+ }
+ SkDLine startEdge = {{fPts[0], fPts[0]}};
+ startEdge[1].fX -= startTan.fY;
+ startEdge[1].fY += startTan.fX;
+ SkDLine endEdge = {{fPts[3], fPts[3]}};
+ endEdge[1].fX -= endTan.fY;
+ endEdge[1].fY += endTan.fX;
+ double leftStart1 = startEdge.isLeft(fPts[1]);
+ if (leftStart1 * startEdge.isLeft(fPts[2]) < 0) {
+ return false;
+ }
+ double leftEnd1 = endEdge.isLeft(fPts[1]);
+ if (leftEnd1 * endEdge.isLeft(fPts[2]) < 0) {
+ return false;
+ }
+ return leftStart1 * leftEnd1 >= 0;
+}
+
+bool SkDCubic::endsAreExtremaInXOrY() const {
+ return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
+ && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
+ || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
+ && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
+}
+
+bool SkDCubic::isLinear(int startIndex, int endIndex) const {
+ SkLineParameters lineParameters;
+ lineParameters.cubicEndPoints(*this, startIndex, endIndex);
+ // FIXME: maybe it's possible to avoid this and compare non-normalized
+ lineParameters.normalize();
+ double distance = lineParameters.controlPtDistance(*this, 1);
+ if (!approximately_zero(distance)) {
+ return false;
+ }
+ distance = lineParameters.controlPtDistance(*this, 2);
+ return approximately_zero(distance);
+}
+
+bool SkDCubic::monotonicInY() const {
+ return between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
+ && between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
+}
+
+bool SkDCubic::serpentine() const {
+ if (!controlsContainedByEnds()) {
+ return false;
+ }
+ double wiggle = (fPts[0].fX - fPts[2].fX) * (fPts[0].fY + fPts[2].fY);
+ for (int idx = 0; idx < 2; ++idx) {
+ wiggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ double waggle = (fPts[1].fX - fPts[3].fX) * (fPts[1].fY + fPts[3].fY);
+ for (int idx = 1; idx < 3; ++idx) {
+ waggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ return wiggle * waggle < 0;
+}
+
+// cubic roots
+
+static const double PI = 3.141592653589793;
+
+// from SkGeometry.cpp (and Numeric Solutions, 5.6)
+int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
+ double s[3];
+ int realRoots = RootsReal(A, B, C, D, s);
+ int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
+ return foundRoots;
+}
+
+int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
+#ifdef SK_DEBUG
+ // create a string mathematica understands
+ // GDB set print repe 15 # if repeated digits is a bother
+ // set print elements 400 # if line doesn't fit
+ char str[1024];
+ sk_bzero(str, sizeof(str));
+ SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
+ A, B, C, D);
+ mathematica_ize(str, sizeof(str));
+#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
+ SkDebugf("%s\n", str);
+#endif
+#endif
+ if (approximately_zero(A)
+ && approximately_zero_when_compared_to(A, B)
+ && approximately_zero_when_compared_to(A, C)
+ && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
+ return SkDQuad::RootsReal(B, C, D, s);
+ }
+ if (approximately_zero_when_compared_to(D, A)
+ && approximately_zero_when_compared_to(D, B)
+ && approximately_zero_when_compared_to(D, C)) { // 0 is one root
+ int num = SkDQuad::RootsReal(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 = SkDQuad::RootsReal(A, A + B, -D, s);
+ for (int i = 0; i < num; ++i) {
+ if (AlmostEqualUlps(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 R2 = R * R;
+ double Q3 = Q * Q * Q;
+ double R2MinusQ3 = R2 - Q3;
+ double adiv3 = a / 3;
+ double r;
+ double* roots = s;
+ 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;
+ if (!AlmostEqualUlps(s[0], r)) {
+ *roots++ = r;
+ }
+ r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
+ if (!AlmostEqualUlps(s[0], r) && (roots - s == 1 || !AlmostEqualUlps(s[1], r))) {
+ *roots++ = r;
+ }
+ } else { // we have 1 real root
+ double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
+ double A = fabs(R) + sqrtR2MinusQ3;
+ A = SkDCubeRoot(A);
+ if (R > 0) {
+ A = -A;
+ }
+ if (A != 0) {
+ A += Q / A;
+ }
+ r = A - adiv3;
+ *roots++ = r;
+ if (AlmostEqualUlps(R2, Q3)) {
+ r = -A / 2 - adiv3;
+ if (!AlmostEqualUlps(s[0], r)) {
+ *roots++ = r;
+ }
+ }
+ }
+ return static_cast<int>(roots - s);
+}
+
+// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
+// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
+// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
+// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
+static double derivative_at_t(const double* src, double t) {
+ double one_t = 1 - t;
+ double a = src[0];
+ double b = src[2];
+ double c = src[4];
+ double d = src[6];
+ return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
+}
+
+// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
+SkDVector SkDCubic::dxdyAtT(double t) const {
+ SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
+ return result;
+}
+
+// OPTIMIZE? share code with formulate_F1DotF2
+int SkDCubic::findInflections(double tValues[]) const {
+ double Ax = fPts[1].fX - fPts[0].fX;
+ double Ay = fPts[1].fY - fPts[0].fY;
+ double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
+ double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
+ double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
+ double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
+ return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
+}
+
+static void formulate_F1DotF2(const double src[], double coeff[4]) {
+ double a = src[2] - src[0];
+ double b = src[4] - 2 * src[2] + src[0];
+ double c = src[6] + 3 * (src[2] - src[4]) - src[0];
+ coeff[0] = c * c;
+ coeff[1] = 3 * b * c;
+ coeff[2] = 2 * b * b + c * a;
+ coeff[3] = a * b;
+}
+
+/** SkDCubic'(t) = At^2 + Bt + C, where
+ A = 3(-a + 3(b - c) + d)
+ B = 6(a - 2b + c)
+ C = 3(b - a)
+ Solve for t, keeping only those that fit between 0 < t < 1
+*/
+int SkDCubic::FindExtrema(double a, double b, double c, double d, double tValues[2]) {
+ // we divide A,B,C by 3 to simplify
+ double A = d - a + 3*(b - c);
+ double B = 2*(a - b - b + c);
+ double C = b - a;
+
+ return SkDQuad::RootsValidT(A, B, C, tValues);
+}
+
+/* from SkGeometry.cpp
+ Looking for F' dot F'' == 0
+
+ A = b - a
+ B = c - 2b + a
+ C = d - 3c + 3b - a
+
+ F' = 3Ct^2 + 6Bt + 3A
+ F'' = 6Ct + 6B
+
+ F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
+*/
+int SkDCubic::findMaxCurvature(double tValues[]) const {
+ double coeffX[4], coeffY[4];
+ int i;
+ formulate_F1DotF2(&fPts[0].fX, coeffX);
+ formulate_F1DotF2(&fPts[0].fY, coeffY);
+ for (i = 0; i < 4; i++) {
+ coeffX[i] = coeffX[i] + coeffY[i];
+ }
+ return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
+}
+
+SkDPoint SkDCubic::top(double startT, double endT) const {
+ SkDCubic sub = subDivide(startT, endT);
+ SkDPoint topPt = sub[0];
+ if (topPt.fY > sub[3].fY || (topPt.fY == sub[3].fY && topPt.fX > sub[3].fX)) {
+ topPt = sub[3];
+ }
+ double extremeTs[2];
+ if (!sub.monotonicInY()) {
+ int roots = FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, sub[3].fY, extremeTs);
+ for (int index = 0; index < roots; ++index) {
+ double t = startT + (endT - startT) * extremeTs[index];
+ SkDPoint mid = ptAtT(t);
+ if (topPt.fY > mid.fY || (topPt.fY == mid.fY && topPt.fX > mid.fX)) {
+ topPt = mid;
+ }
+ }
+ }
+ return topPt;
+}
+
+SkDPoint SkDCubic::ptAtT(double t) const {
+ if (0 == t) {
+ return fPts[0];
+ }
+ if (1 == t) {
+ return fPts[3];
+ }
+ double one_t = 1 - t;
+ double one_t2 = one_t * one_t;
+ double a = one_t2 * one_t;
+ double b = 3 * one_t2 * t;
+ double t2 = t * t;
+ double c = 3 * one_t * t2;
+ double d = t2 * t;
+ SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
+ a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
+ return result;
+}
+
+/*
+ Given a cubic c, t1, and t2, find a small cubic segment.
+
+ The new cubic is defined as points A, B, C, and D, where
+ s1 = 1 - t1
+ s2 = 1 - t2
+ A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
+ D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
+
+ We don't have B or C. So We define two equations to isolate them.
+ First, compute two reference T values 1/3 and 2/3 from t1 to t2:
+
+ c(at (2*t1 + t2)/3) == E
+ c(at (t1 + 2*t2)/3) == F
+
+ Next, compute where those values must be if we know the values of B and C:
+
+ _12 = A*2/3 + B*1/3
+ 12_ = A*1/3 + B*2/3
+ _23 = B*2/3 + C*1/3
+ 23_ = B*1/3 + C*2/3
+ _34 = C*2/3 + D*1/3
+ 34_ = C*1/3 + D*2/3
+ _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9
+ 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9
+ _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9
+ 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9
+ _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
+ = A*8/27 + B*12/27 + C*6/27 + D*1/27
+ = E
+ 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
+ = A*1/27 + B*6/27 + C*12/27 + D*8/27
+ = F
+ E*27 = A*8 + B*12 + C*6 + D
+ F*27 = A + B*6 + C*12 + D*8
+
+Group the known values on one side:
+
+ M = E*27 - A*8 - D = B*12 + C* 6
+ N = F*27 - A - D*8 = B* 6 + C*12
+ M*2 - N = B*18
+ N*2 - M = C*18
+ B = (M*2 - N)/18
+ C = (N*2 - M)/18
+ */
+
+static double interp_cubic_coords(const double* src, double t) {
+ double ab = SkDInterp(src[0], src[2], t);
+ double bc = SkDInterp(src[2], src[4], t);
+ double cd = SkDInterp(src[4], src[6], t);
+ double abc = SkDInterp(ab, bc, t);
+ double bcd = SkDInterp(bc, cd, t);
+ double abcd = SkDInterp(abc, bcd, t);
+ return abcd;
+}
+
+SkDCubic SkDCubic::subDivide(double t1, double t2) const {
+ if (t1 == 0 || t2 == 1) {
+ if (t1 == 0 && t2 == 1) {
+ return *this;
+ }
+ SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
+ SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
+ return dst;
+ }
+ SkDCubic dst;
+ double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
+ double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
+ double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
+ double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
+ double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
+ double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
+ double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
+ double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
+ double mx = ex * 27 - ax * 8 - dx;
+ double my = ey * 27 - ay * 8 - dy;
+ double nx = fx * 27 - ax - dx * 8;
+ double ny = fy * 27 - ay - dy * 8;
+ /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
+ /* by = */ dst[1].fY = (my * 2 - ny) / 18;
+ /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
+ /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
+ // FIXME: call align() ?
+ return dst;
+}
+
+void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const {
+ if (fPts[endIndex].fX == fPts[ctrlIndex].fX) {
+ dstPt->fX = fPts[endIndex].fX;
+ }
+ if (fPts[endIndex].fY == fPts[ctrlIndex].fY) {
+ dstPt->fY = fPts[endIndex].fY;
+ }
+}
+
+void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
+ double t1, double t2, SkDPoint dst[2]) const {
+ SkASSERT(t1 != t2);
+#if 0
+ double ex = interp_cubic_coords(&fPts[0].fX, (t1 * 2 + t2) / 3);
+ double ey = interp_cubic_coords(&fPts[0].fY, (t1 * 2 + t2) / 3);
+ double fx = interp_cubic_coords(&fPts[0].fX, (t1 + t2 * 2) / 3);
+ double fy = interp_cubic_coords(&fPts[0].fY, (t1 + t2 * 2) / 3);
+ double mx = ex * 27 - a.fX * 8 - d.fX;
+ double my = ey * 27 - a.fY * 8 - d.fY;
+ double nx = fx * 27 - a.fX - d.fX * 8;
+ double ny = fy * 27 - a.fY - d.fY * 8;
+ /* bx = */ dst[0].fX = (mx * 2 - nx) / 18;
+ /* by = */ dst[0].fY = (my * 2 - ny) / 18;
+ /* cx = */ dst[1].fX = (nx * 2 - mx) / 18;
+ /* cy = */ dst[1].fY = (ny * 2 - my) / 18;
+#else
+ // this approach assumes that the control points computed directly are accurate enough
+ SkDCubic sub = subDivide(t1, t2);
+ dst[0] = sub[1] + (a - sub[0]);
+ dst[1] = sub[2] + (d - sub[3]);
+#endif
+ if (t1 == 0 || t2 == 0) {
+ align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
+ }
+ if (t1 == 1 || t2 == 1) {
+ align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
+ }
+ if (precisely_subdivide_equal(dst[0].fX, a.fX)) {
+ dst[0].fX = a.fX;
+ }
+ if (precisely_subdivide_equal(dst[0].fY, a.fY)) {
+ dst[0].fY = a.fY;
+ }
+ if (precisely_subdivide_equal(dst[1].fX, d.fX)) {
+ dst[1].fX = d.fX;
+ }
+ if (precisely_subdivide_equal(dst[1].fY, d.fY)) {
+ dst[1].fY = d.fY;
+ }
+}
+
+/* classic one t subdivision */
+static void interp_cubic_coords(const double* src, double* dst, double t) {
+ double ab = SkDInterp(src[0], src[2], t);
+ double bc = SkDInterp(src[2], src[4], t);
+ double cd = SkDInterp(src[4], src[6], t);
+ double abc = SkDInterp(ab, bc, t);
+ double bcd = SkDInterp(bc, cd, t);
+ double abcd = SkDInterp(abc, bcd, t);
+
+ dst[0] = src[0];
+ dst[2] = ab;
+ dst[4] = abc;
+ dst[6] = abcd;
+ dst[8] = bcd;
+ dst[10] = cd;
+ dst[12] = src[6];
+}
+
+SkDCubicPair SkDCubic::chopAt(double t) const {
+ SkDCubicPair dst;
+ if (t == 0.5) {
+ dst.pts[0] = fPts[0];
+ dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
+ dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
+ dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
+ dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
+ dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
+ dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
+ dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
+ dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
+ dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
+ dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
+ dst.pts[6] = fPts[3];
+ return dst;
+ }
+ interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
+ interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
+ return dst;
+}
diff --git a/pathops/SkPathOpsCubic.h b/pathops/SkPathOpsCubic.h
new file mode 100644
index 00000000..1ba35be2
--- /dev/null
+++ b/pathops/SkPathOpsCubic.h
@@ -0,0 +1,82 @@
+/*
+ * 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 SkPathOpsCubic_DEFINED
+#define SkPathOpsCubic_DEFINED
+
+#include "SkPath.h"
+#include "SkPathOpsPoint.h"
+#include "SkTArray.h"
+
+struct SkDCubicPair {
+ const SkDCubic& first() const { return (const SkDCubic&) pts[0]; }
+ const SkDCubic& second() const { return (const SkDCubic&) pts[3]; }
+ SkDPoint pts[7];
+};
+
+struct SkDCubic {
+ SkDPoint fPts[4];
+
+ void set(const SkPoint pts[4]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ fPts[2] = pts[2];
+ fPts[3] = pts[3];
+ }
+
+ static const int gPrecisionUnit;
+
+ const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
+ SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
+
+ void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const;
+ double calcPrecision() const;
+ SkDCubicPair chopAt(double t) const;
+ bool clockwise() const;
+ static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D);
+ bool controlsContainedByEnds() const;
+ SkDVector dxdyAtT(double t) const;
+ bool endsAreExtremaInXOrY() const;
+ static int FindExtrema(double a, double b, double c, double d, double tValue[2]);
+ int findInflections(double tValues[]) const;
+
+ static int FindInflections(const SkPoint a[4], double tValues[]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.findInflections(tValues);
+ }
+
+ int findMaxCurvature(double tValues[]) const;
+ bool isLinear(int startIndex, int endIndex) const;
+ bool monotonicInY() const;
+ SkDPoint ptAtT(double t) const;
+ static int RootsReal(double A, double B, double C, double D, double t[3]);
+ static int RootsValidT(const double A, const double B, const double C, double D, double s[3]);
+ bool serpentine() const;
+ SkDCubic subDivide(double t1, double t2) const;
+
+ static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.subDivide(t1, t2);
+ }
+
+ void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const;
+
+ static void SubDivide(const SkPoint pts[4], const SkDPoint& a, const SkDPoint& d, double t1,
+ double t2, SkDPoint p[2]) {
+ SkDCubic cubic;
+ cubic.set(pts);
+ cubic.subDivide(a, d, t1, t2, p);
+ }
+
+ SkDPoint top(double startT, double endT) const;
+ void toQuadraticTs(double precision, SkTArray<double, true>* ts) const;
+ SkDQuad toQuad() const;
+};
+
+#endif
diff --git a/pathops/SkPathOpsCurve.h b/pathops/SkPathOpsCurve.h
new file mode 100644
index 00000000..5d12cb81
--- /dev/null
+++ b/pathops/SkPathOpsCurve.h
@@ -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.
+ */
+#ifndef SkPathOpsCurve_DEFINE
+#define SkPathOpsCurve_DEFINE
+
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+
+static SkDPoint dline_xy_at_t(const SkPoint a[2], double t) {
+ SkDLine line;
+ line.set(a);
+ return line.ptAtT(t);
+}
+
+static SkDPoint dquad_xy_at_t(const SkPoint a[3], double t) {
+ SkDQuad quad;
+ quad.set(a);
+ return quad.ptAtT(t);
+}
+
+static SkDPoint dcubic_xy_at_t(const SkPoint a[4], double t) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.ptAtT(t);
+}
+
+static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], double ) = {
+ NULL,
+ dline_xy_at_t,
+ dquad_xy_at_t,
+ dcubic_xy_at_t
+};
+
+static SkPoint fline_xy_at_t(const SkPoint a[2], double t) {
+ return dline_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint fquad_xy_at_t(const SkPoint a[3], double t) {
+ return dquad_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint fcubic_xy_at_t(const SkPoint a[4], double t) {
+ return dcubic_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint (* const CurvePointAtT[])(const SkPoint[], double ) = {
+ NULL,
+ fline_xy_at_t,
+ fquad_xy_at_t,
+ fcubic_xy_at_t
+};
+
+static SkDVector dline_dxdy_at_t(const SkPoint a[2], double ) {
+ SkDLine line;
+ line.set(a);
+ return line[1] - line[0];
+}
+
+static SkDVector dquad_dxdy_at_t(const SkPoint a[3], double t) {
+ SkDQuad quad;
+ quad.set(a);
+ return quad.dxdyAtT(t);
+}
+
+static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], double t) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.dxdyAtT(t);
+}
+
+static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], double ) = {
+ NULL,
+ dline_dxdy_at_t,
+ dquad_dxdy_at_t,
+ dcubic_dxdy_at_t
+};
+
+static SkVector fline_dxdy_at_t(const SkPoint a[2], double ) {
+ return a[1] - a[0];
+}
+
+static SkVector fquad_dxdy_at_t(const SkPoint a[3], double t) {
+ return dquad_dxdy_at_t(a, t).asSkVector();
+}
+
+static SkVector fcubic_dxdy_at_t(const SkPoint a[4], double t) {
+ return dcubic_dxdy_at_t(a, t).asSkVector();
+}
+
+static SkVector (* const CurveSlopeAtT[])(const SkPoint[], double ) = {
+ NULL,
+ fline_dxdy_at_t,
+ fquad_dxdy_at_t,
+ fcubic_dxdy_at_t
+};
+
+static SkPoint quad_top(const SkPoint a[3], double startT, double endT) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDPoint topPt = quad.top(startT, endT);
+ return topPt.asSkPoint();
+}
+
+static SkPoint cubic_top(const SkPoint a[4], double startT, double endT) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDPoint topPt = cubic.top(startT, endT);
+ return topPt.asSkPoint();
+}
+
+static SkPoint (* const CurveTop[])(const SkPoint[], double , double ) = {
+ NULL,
+ NULL,
+ quad_top,
+ cubic_top
+};
+
+static bool line_is_vertical(const SkPoint a[2], double startT, double endT) {
+ SkDLine line;
+ line.set(a);
+ SkDPoint dst[2] = { line.ptAtT(startT), line.ptAtT(endT) };
+ return AlmostEqualUlps(dst[0].fX, dst[1].fX);
+}
+
+static bool quad_is_vertical(const SkPoint a[3], double startT, double endT) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDQuad dst = quad.subDivide(startT, endT);
+ return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX);
+}
+
+static bool cubic_is_vertical(const SkPoint a[4], double startT, double endT) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDCubic dst = cubic.subDivide(startT, endT);
+ return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX)
+ && AlmostEqualUlps(dst[2].fX, dst[3].fX);
+}
+
+static bool (* const CurveIsVertical[])(const SkPoint[], double , double) = {
+ NULL,
+ line_is_vertical,
+ quad_is_vertical,
+ cubic_is_vertical
+};
+
+#endif
diff --git a/pathops/SkPathOpsDebug.cpp b/pathops/SkPathOpsDebug.cpp
new file mode 100644
index 00000000..1436c8ea
--- /dev/null
+++ b/pathops/SkPathOpsDebug.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPathOpsDebug.h"
+#include "SkPath.h"
+
+#if defined SK_DEBUG || !FORCE_RELEASE
+
+int gDebugMaxWindSum = SK_MaxS32;
+int gDebugMaxWindValue = SK_MaxS32;
+
+void mathematica_ize(char* str, size_t bufferLen) {
+ size_t len = strlen(str);
+ bool num = false;
+ for (size_t idx = 0; idx < len; ++idx) {
+ if (num && str[idx] == 'e') {
+ if (len + 2 >= bufferLen) {
+ return;
+ }
+ memmove(&str[idx + 2], &str[idx + 1], len - idx);
+ str[idx] = '*';
+ str[idx + 1] = '^';
+ ++len;
+ }
+ num = str[idx] >= '0' && str[idx] <= '9';
+ }
+}
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+bool valid_wind(int wind) {
+ return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF;
+}
+
+void winding_printf(int wind) {
+ if (wind == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", wind);
+ }
+}
+#endif
+
+#if DEBUG_DUMP
+const char* kLVerbStr[] = {"", "line", "quad", "cubic"};
+// static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"};
+int gContourID;
+int gSegmentID;
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+int gDebugSortCountDefault = SK_MaxS32;
+int gDebugSortCount;
+#endif
+
+#if DEBUG_ACTIVE_OP
+const char* kPathOpStr[] = {"diff", "sect", "union", "xor"};
+#endif
+
+#if DEBUG_SHOW_TEST_NAME
+void* PathOpsDebugCreateNameStr() {
+ return SkNEW_ARRAY(char, DEBUG_FILENAME_STRING_LENGTH);
+}
+
+void PathOpsDebugDeleteNameStr(void* v) {
+ SkDELETE_ARRAY(reinterpret_cast<char* >(v));
+}
+
+void DebugBumpTestName(char* test) {
+ char* num = test + strlen(test);
+ while (num[-1] >= '0' && num[-1] <= '9') {
+ --num;
+ }
+ if (num[0] == '\0') {
+ return;
+ }
+ int dec = atoi(num);
+ if (dec == 0) {
+ return;
+ }
+ ++dec;
+ SK_SNPRINTF(num, DEBUG_FILENAME_STRING_LENGTH - (num - test), "%d", dec);
+}
+#endif
diff --git a/pathops/SkPathOpsDebug.h b/pathops/SkPathOpsDebug.h
new file mode 100644
index 00000000..e4ef072b
--- /dev/null
+++ b/pathops/SkPathOpsDebug.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkPathOpsDebug_DEFINED
+#define SkPathOpsDebug_DEFINED
+
+#include "SkPathOps.h"
+#include "SkTypes.h"
+
+#ifdef SK_RELEASE
+#define FORCE_RELEASE 1
+#else
+#define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging
+#endif
+
+#define ONE_OFF_DEBUG 0
+#define ONE_OFF_DEBUG_MATHEMATICA 0
+
+#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_ANDROID)
+ #define SK_RAND(seed) rand()
+#else
+ #define SK_RAND(seed) rand_r(&seed)
+#endif
+#ifdef SK_BUILD_FOR_WIN
+ #define SK_SNPRINTF _snprintf
+#else
+ #define SK_SNPRINTF snprintf
+#endif
+
+#if defined SK_DEBUG || !FORCE_RELEASE
+
+void mathematica_ize(char* str, size_t bufferSize);
+
+extern int gDebugMaxWindSum;
+extern int gDebugMaxWindValue;
+
+#endif
+
+#if FORCE_RELEASE
+
+#define DEBUG_ACTIVE_OP 0
+#define DEBUG_ACTIVE_SPANS 0
+#define DEBUG_ACTIVE_SPANS_FIRST_ONLY 0
+#define DEBUG_ACTIVE_SPANS_SHORT_FORM 1
+#define DEBUG_ADD_INTERSECTING_TS 0
+#define DEBUG_ADD_T_PAIR 0
+#define DEBUG_ANGLE 0
+#define DEBUG_AS_C_CODE 1
+#define DEBUG_ASSEMBLE 0
+#define DEBUG_CONCIDENT 0
+#define DEBUG_CROSS 0
+#define DEBUG_FLAT_QUADS 0
+#define DEBUG_FLOW 0
+#define DEBUG_MARK_DONE 0
+#define DEBUG_PATH_CONSTRUCTION 0
+#define DEBUG_SHOW_TEST_NAME 0
+#define DEBUG_SHOW_TEST_PROGRESS 0
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 0
+#define DEBUG_SORT_COMPACT 0
+#define DEBUG_SORT_SINGLE 0
+#define DEBUG_SWAP_TOP 0
+#define DEBUG_UNSORTABLE 0
+#define DEBUG_VALIDATE 0
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 0
+#define DEBUG_WINDING_AT_T 0
+
+#else
+
+#define DEBUG_ACTIVE_OP 1
+#define DEBUG_ACTIVE_SPANS 1
+#define DEBUG_ACTIVE_SPANS_FIRST_ONLY 0
+#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_AS_C_CODE 1
+#define DEBUG_ASSEMBLE 1
+#define DEBUG_CONCIDENT 1
+#define DEBUG_CROSS 0
+#define DEBUG_FLAT_QUADS 0
+#define DEBUG_FLOW 1
+#define DEBUG_MARK_DONE 1
+#define DEBUG_PATH_CONSTRUCTION 1
+#define DEBUG_SHOW_TEST_NAME 1
+#define DEBUG_SHOW_TEST_PROGRESS 1
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 1
+#define DEBUG_SORT_COMPACT 0
+#define DEBUG_SORT_SINGLE 0
+#define DEBUG_SWAP_TOP 1
+#define DEBUG_UNSORTABLE 1
+#define DEBUG_VALIDATE 1
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 1
+#define DEBUG_WINDING_AT_T 1
+
+#endif
+
+#define DEBUG_DUMP (DEBUG_ACTIVE_OP | DEBUG_ACTIVE_SPANS | DEBUG_CONCIDENT | DEBUG_SORT | \
+ DEBUG_SORT_SINGLE | DEBUG_PATH_CONSTRUCTION)
+
+#if DEBUG_AS_C_CODE
+#define CUBIC_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}"
+#define QUAD_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}"
+#define LINE_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}}"
+#define PT_DEBUG_STR "{{%1.9g,%1.9g}}"
+#else
+#define CUBIC_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)"
+#define QUAD_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)"
+#define LINE_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g)"
+#define PT_DEBUG_STR "(%1.9g,%1.9g)"
+#endif
+#define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g"
+#define TX_DEBUG_STR(t) #t "[%d]=%1.9g"
+#define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY
+#define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY
+#define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY
+#define PT_DEBUG_DATA(i, n) i.pt(n).fX, i.pt(n).fY
+
+#if DEBUG_DUMP
+extern const char* kLVerbStr[];
+// extern const char* kUVerbStr[];
+extern int gContourID;
+extern int gSegmentID;
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+extern int gDebugSortCountDefault;
+extern int gDebugSortCount;
+
+bool valid_wind(int winding);
+void winding_printf(int winding);
+#endif
+
+#if DEBUG_ACTIVE_OP
+extern const char* kPathOpStr[];
+#endif
+
+#if DEBUG_SHOW_TEST_NAME
+#include "SkTLS.h"
+
+extern void* PathOpsDebugCreateNameStr();
+extern void PathOpsDebugDeleteNameStr(void* v);
+#define DEBUG_FILENAME_STRING_LENGTH 64
+#define DEBUG_FILENAME_STRING \
+ (reinterpret_cast<char* >(SkTLS::Get(PathOpsDebugCreateNameStr, PathOpsDebugDeleteNameStr)))
+extern void DebugBumpTestName(char* );
+extern void DebugShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name);
+#endif
+
+#ifndef DEBUG_TEST
+#define DEBUG_TEST 0
+#endif
+
+#endif
diff --git a/pathops/SkPathOpsLine.cpp b/pathops/SkPathOpsLine.cpp
new file mode 100644
index 00000000..48c042a7
--- /dev/null
+++ b/pathops/SkPathOpsLine.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 "SkPathOpsLine.h"
+
+SkDLine SkDLine::subDivide(double t1, double t2) const {
+ SkDVector delta = tangent();
+ SkDLine dst = {{{
+ fPts[0].fX - t1 * delta.fX, fPts[0].fY - t1 * delta.fY}, {
+ fPts[0].fX - t2 * delta.fX, fPts[0].fY - t2 * delta.fY}}};
+ return dst;
+}
+
+// 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
+// return (float) ((P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y));
+double SkDLine::isLeft(const SkDPoint& pt) const {
+ SkDVector p0 = fPts[1] - fPts[0];
+ SkDVector p2 = pt - fPts[0];
+ return p0.cross(p2);
+}
+
+SkDPoint SkDLine::ptAtT(double t) const {
+ if (0 == t) {
+ return fPts[0];
+ }
+ if (1 == t) {
+ return fPts[1];
+ }
+ double one_t = 1 - t;
+ SkDPoint result = { one_t * fPts[0].fX + t * fPts[1].fX, one_t * fPts[0].fY + t * fPts[1].fY };
+ return result;
+}
+
+double SkDLine::exactPoint(const SkDPoint& xy) const {
+ if (xy == fPts[0]) { // do cheapest test first
+ return 0;
+ }
+ if (xy == fPts[1]) {
+ return 1;
+ }
+ return -1;
+}
+
+double SkDLine::nearPoint(const SkDPoint& xy) const {
+ if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX)
+ || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) {
+ return -1;
+ }
+ // project a perpendicular ray from the point to the line; find the T on the line
+ SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line
+ double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay
+ SkDVector ab0 = xy - fPts[0];
+ double numer = len.fX * ab0.fX + ab0.fY * len.fY;
+ if (!between(0, numer, denom)) {
+ return -1;
+ }
+ double t = numer / denom;
+ SkDPoint realPt = ptAtT(t);
+ SkDVector distU = xy - realPt;
+ double distSq = distU.fX * distU.fX + distU.fY * distU.fY;
+ double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ?
+ // find the ordinal in the original line with the largest unsigned exponent
+ double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY);
+ double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY);
+ largest = SkTMax(largest, -tiniest);
+ if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance?
+ return -1;
+ }
+ t = SkPinT(t);
+ SkASSERT(between(0, t, 1));
+ return t;
+}
+
+double SkDLine::ExactPointH(const SkDPoint& xy, double left, double right, double y) {
+ if (xy.fY == y) {
+ if (xy.fX == left) {
+ return 0;
+ }
+ if (xy.fX == right) {
+ return 1;
+ }
+ }
+ return -1;
+}
+
+double SkDLine::NearPointH(const SkDPoint& xy, double left, double right, double y) {
+ if (!AlmostEqualUlps(xy.fY, y)) {
+ return -1;
+ }
+ if (!AlmostBetweenUlps(left, xy.fX, right)) {
+ return -1;
+ }
+ double t = (xy.fX - left) / (right - left);
+ t = SkPinT(t);
+ SkASSERT(between(0, t, 1));
+ return t;
+}
+
+double SkDLine::ExactPointV(const SkDPoint& xy, double top, double bottom, double x) {
+ if (xy.fX == x) {
+ if (xy.fY == top) {
+ return 0;
+ }
+ if (xy.fY == bottom) {
+ return 1;
+ }
+ }
+ return -1;
+}
+
+double SkDLine::NearPointV(const SkDPoint& xy, double top, double bottom, double x) {
+ if (!AlmostEqualUlps(xy.fX, x)) {
+ return -1;
+ }
+ if (!AlmostBetweenUlps(top, xy.fY, bottom)) {
+ return -1;
+ }
+ double t = (xy.fY - top) / (bottom - top);
+ t = SkPinT(t);
+ SkASSERT(between(0, t, 1));
+ return t;
+}
diff --git a/pathops/SkPathOpsLine.h b/pathops/SkPathOpsLine.h
new file mode 100644
index 00000000..75f3bd10
--- /dev/null
+++ b/pathops/SkPathOpsLine.h
@@ -0,0 +1,42 @@
+/*
+ * 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 SkPathOpsLine_DEFINED
+#define SkPathOpsLine_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDLine {
+ SkDPoint fPts[2];
+
+ const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 2); return fPts[n]; }
+ SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 2); return fPts[n]; }
+
+ void set(const SkPoint pts[2]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ }
+
+ static SkDLine SubDivide(const SkPoint a[2], double t1, double t2) {
+ SkDLine line;
+ line.set(a);
+ return line.subDivide(t1, t2);
+ }
+
+ double exactPoint(const SkDPoint& xy) const;
+ static double ExactPointH(const SkDPoint& xy, double left, double right, double y);
+ static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x);
+ double isLeft(const SkDPoint& pt) const;
+ double nearPoint(const SkDPoint& xy) const;
+ static double NearPointH(const SkDPoint& xy, double left, double right, double y);
+ static double NearPointV(const SkDPoint& xy, double top, double bottom, double x);
+ SkDPoint ptAtT(double t) const;
+ SkDLine subDivide(double t1, double t2) const;
+private:
+ SkDVector tangent() const { return fPts[0] - fPts[1]; }
+};
+
+#endif
diff --git a/pathops/SkPathOpsOp.cpp b/pathops/SkPathOpsOp.cpp
new file mode 100644
index 00000000..71efeeea
--- /dev/null
+++ b/pathops/SkPathOpsOp.cpp
@@ -0,0 +1,319 @@
+/*
+ * 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 "SkAddIntersections.h"
+#include "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+
+// 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 SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int& nextStart, int& nextEnd) {
+ while (chase.count()) {
+ SkOpSpan* span;
+ chase.pop(&span);
+ const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex);
+ SkOpSegment* segment = backPtr.fOther;
+ nextStart = backPtr.fOtherIndex;
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
+ int done = 0;
+ if (segment->activeAngle(nextStart, &done, &angles)) {
+ SkOpAngle* 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;
+ }
+ SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
+ bool sortable = SkOpSegment::SortAngles(angles, &sorted,
+ SkOpSegment::kMayBeUnordered_SortAngleKind);
+ int angleCount = sorted.count();
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, sortable);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const SkOpAngle* angle;
+ do {
+ angle = sorted[++firstIndex];
+ segment = angle->segment();
+ } while (segment->windSum(angle) == SK_MinS32);
+ #if DEBUG_SORT
+ segment->debugShowSort(__FUNCTION__, sorted, firstIndex, sortable);
+ #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;
+ SkOpSegment* first = NULL;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ segment = angle->segment();
+ int start = angle->start();
+ int end = angle->end();
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ 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, PathOp 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(SkTArray<SkOpContour*, true>& contourList, const SkPathOp op,
+ const int xorMask, const int xorOpMask, SkPathWriter* simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ bool done;
+ SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex,
+ &topLeft, &topUnsortable, &done, true);
+ if (!current) {
+ if (topUnsortable || !done) {
+ topUnsortable = false;
+ SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<SkOpSpan*> chaseArray;
+ do {
+ if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) {
+ do {
+ if (!unsortable && current->done()) {
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ if (simple->isEmpty()) {
+ simple->init();
+ break;
+ }
+ }
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd,
+ &unsortable, op, xorMask, xorOpMask);
+ if (!next) {
+ if (!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, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple->isClosed() && (!unsortable
+ || !current->done(SkMin32(index, endIndex))));
+ if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
+ SkASSERT(unsortable || simple->isEmpty());
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneBinary(min);
+ }
+ }
+ simple->close();
+ } else {
+ SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex);
+ if (last && !last->fLoop) {
+ *chaseArray.append() = last;
+ }
+ }
+ current = findChaseOp(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ } while (true);
+ } while (true);
+ return simple->someAssemblyRequired();
+}
+
+// pretty picture:
+// https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing
+static const SkPathOp gOpInverse[kReverseDifference_PathOp + 1][2][2] = {
+// inside minuend outside minuend
+// inside subtrahend outside subtrahend inside subtrahend outside subtrahend
+ {{ kDifference_PathOp, kIntersect_PathOp }, { kUnion_PathOp, kReverseDifference_PathOp }},
+ {{ kIntersect_PathOp, kDifference_PathOp }, { kReverseDifference_PathOp, kUnion_PathOp }},
+ {{ kUnion_PathOp, kReverseDifference_PathOp }, { kDifference_PathOp, kIntersect_PathOp }},
+ {{ kXOR_PathOp, kXOR_PathOp }, { kXOR_PathOp, kXOR_PathOp }},
+ {{ kReverseDifference_PathOp, kUnion_PathOp }, { kIntersect_PathOp, kDifference_PathOp }},
+};
+
+static const bool gOutInverse[kReverseDifference_PathOp + 1][2][2] = {
+ {{ false, false }, { true, false }}, // diff
+ {{ false, false }, { false, true }}, // sect
+ {{ false, true }, { true, true }}, // union
+ {{ false, true }, { true, false }}, // xor
+ {{ false, true }, { false, false }}, // rev diff
+};
+
+bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
+#if DEBUG_SHOW_TEST_NAME
+ char* debugName = DEBUG_FILENAME_STRING;
+ if (debugName && debugName[0]) {
+ DebugBumpTestName(debugName);
+ DebugShowPath(one, two, op, debugName);
+ }
+#endif
+ op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
+ SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
+ ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
+ const SkPath* minuend = &one;
+ const SkPath* subtrahend = &two;
+ if (op == kReverseDifference_PathOp) {
+ minuend = &two;
+ subtrahend = &one;
+ op = kDifference_PathOp;
+ }
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ gDebugSortCount = gDebugSortCountDefault;
+#endif
+ // turn path into list of segments
+ SkTArray<SkOpContour> contours;
+ // FIXME: add self-intersecting cubics' T values to segment
+ SkOpEdgeBuilder builder(*minuend, contours);
+ const int xorMask = builder.xorMask();
+ builder.addOperand(*subtrahend);
+ if (!builder.finish()) {
+ return false;
+ }
+ result->reset();
+ result->setFillType(fillType);
+ const int xorOpMask = builder.xorMask();
+ SkTArray<SkOpContour*, true> contourList;
+ MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask,
+ xorOpMask == kEvenOdd_PathOpsMask);
+ SkOpContour** currentPtr = contourList.begin();
+ if (!currentPtr) {
+ return true;
+ }
+ SkOpContour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ SkOpContour** nextPtr = currentPtr;
+ SkOpContour* current = *currentPtr++;
+ if (current->containsCubics()) {
+ AddSelfIntersectTs(current);
+ }
+ SkOpContour* 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
+ SkOpContour::debugShowWindingValues(contourList);
+#endif
+ CoincidenceCheck(&contourList, total);
+#if DEBUG_SHOW_WINDING
+ SkOpContour::debugShowWindingValues(contourList);
+#endif
+ FixOtherTIndex(&contourList);
+ CheckEnds(&contourList);
+ SortSegments(&contourList);
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+ DebugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ SkPathWriter wrapper(*result);
+ bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
+ { // if some edges could not be resolved, assemble remaining fragments
+ SkPath temp;
+ temp.setFillType(fillType);
+ SkPathWriter assembled(temp);
+ Assemble(wrapper, &assembled);
+ *result = *assembled.nativePath();
+ result->setFillType(fillType);
+ }
+ return true;
+}
diff --git a/pathops/SkPathOpsPoint.cpp b/pathops/SkPathOpsPoint.cpp
new file mode 100644
index 00000000..dc9cde55
--- /dev/null
+++ b/pathops/SkPathOpsPoint.cpp
@@ -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.
+ */
+#include "SkPathOpsPoint.h"
+
+SkDVector operator-(const SkDPoint& a, const SkDPoint& b) {
+ SkDVector v = {a.fX - b.fX, a.fY - b.fY};
+ return v;
+}
+
+SkDPoint operator+(const SkDPoint& a, const SkDVector& b) {
+ SkDPoint v = {a.fX + b.fX, a.fY + b.fY};
+ return v;
+}
diff --git a/pathops/SkPathOpsPoint.h b/pathops/SkPathOpsPoint.h
new file mode 100644
index 00000000..045b1b4d
--- /dev/null
+++ b/pathops/SkPathOpsPoint.h
@@ -0,0 +1,164 @@
+/*
+ * 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 SkPathOpsPoint_DEFINED
+#define SkPathOpsPoint_DEFINED
+
+#include "SkPathOpsTypes.h"
+#include "SkPoint.h"
+
+inline bool AlmostEqualUlps(const SkPoint& pt1, const SkPoint& pt2) {
+ return AlmostEqualUlps(pt1.fX, pt2.fX) && AlmostEqualUlps(pt1.fY, pt2.fY);
+}
+
+struct SkDVector {
+ double fX, fY;
+
+ friend SkDPoint operator+(const SkDPoint& a, const SkDVector& b);
+
+ void operator+=(const SkDVector& v) {
+ fX += v.fX;
+ fY += v.fY;
+ }
+
+ void operator-=(const SkDVector& v) {
+ fX -= v.fX;
+ fY -= v.fY;
+ }
+
+ void operator/=(const double s) {
+ fX /= s;
+ fY /= s;
+ }
+
+ void operator*=(const double s) {
+ fX *= s;
+ fY *= s;
+ }
+
+ SkVector asSkVector() const {
+ SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)};
+ return v;
+ }
+
+ double cross(const SkDVector& a) const {
+ return fX * a.fY - fY * a.fX;
+ }
+
+ double dot(const SkDVector& a) const {
+ return fX * a.fX + fY * a.fY;
+ }
+
+ double length() const {
+ return sqrt(lengthSquared());
+ }
+
+ double lengthSquared() const {
+ return fX * fX + fY * fY;
+ }
+};
+
+struct SkDPoint {
+ double fX;
+ double fY;
+
+ void set(const SkPoint& pt) {
+ fX = pt.fX;
+ fY = pt.fY;
+ }
+
+ friend SkDVector operator-(const SkDPoint& a, const SkDPoint& b);
+
+ friend bool operator==(const SkDPoint& a, const SkDPoint& b) {
+ return a.fX == b.fX && a.fY == b.fY;
+ }
+
+ friend bool operator!=(const SkDPoint& a, const SkDPoint& b) {
+ return a.fX != b.fX || a.fY != b.fY;
+ }
+
+ void operator=(const SkPoint& pt) {
+ fX = pt.fX;
+ fY = pt.fY;
+ }
+
+
+ void operator+=(const SkDVector& v) {
+ fX += v.fX;
+ fY += v.fY;
+ }
+
+ void operator-=(const SkDVector& v) {
+ fX -= v.fX;
+ fY -= v.fY;
+ }
+
+ // note: this can not be implemented with
+ // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX);
+ // because that will not take the magnitude of the values
+ bool approximatelyEqual(const SkDPoint& a) const {
+ double denom = SkTMax(fabs(fX), SkTMax(fabs(fY),
+ SkTMax(fabs(a.fX), fabs(a.fY))));
+ if (precisely_zero(denom)) {
+ return true;
+ }
+ double inv = 1 / denom;
+ return approximately_equal(fX * inv, a.fX * inv)
+ && approximately_equal(fY * inv, a.fY * inv);
+ }
+
+ bool approximatelyEqual(const SkPoint& a) const {
+ return AlmostEqualUlps(SkDoubleToScalar(fX), a.fX)
+ && AlmostEqualUlps(SkDoubleToScalar(fY), a.fY);
+ }
+
+ bool approximatelyEqualHalf(const SkDPoint& a) const {
+ double denom = SkTMax(fabs(fX), SkTMax(fabs(fY),
+ SkTMax(fabs(a.fX), fabs(a.fY))));
+ if (denom == 0) {
+ return true;
+ }
+ double inv = 1 / denom;
+ return approximately_equal_half(fX * inv, a.fX * inv)
+ && approximately_equal_half(fY * inv, a.fY * inv);
+ }
+
+ bool approximatelyZero() const {
+ return approximately_zero(fX) && approximately_zero(fY);
+ }
+
+ SkPoint asSkPoint() const {
+ SkPoint pt = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)};
+ return pt;
+ }
+
+ double distance(const SkDPoint& a) const {
+ SkDVector temp = *this - a;
+ return temp.length();
+ }
+
+ double distanceSquared(const SkDPoint& a) const {
+ SkDVector temp = *this - a;
+ return temp.lengthSquared();
+ }
+
+ static SkDPoint Mid(const SkDPoint& a, const SkDPoint& b) {
+ SkDPoint result;
+ result.fX = (a.fX + b.fX) / 2;
+ result.fY = (a.fY + b.fY) / 2;
+ return result;
+ }
+
+ double moreRoughlyEqual(const SkDPoint& a) const {
+ return more_roughly_equal(a.fY, fY) && more_roughly_equal(a.fX, fX);
+ }
+
+ double roughlyEqual(const SkDPoint& a) const {
+ return roughly_equal(a.fY, fY) && roughly_equal(a.fX, fX);
+ }
+};
+
+#endif
diff --git a/pathops/SkPathOpsQuad.cpp b/pathops/SkPathOpsQuad.cpp
new file mode 100644
index 00000000..7d9ff52a
--- /dev/null
+++ b/pathops/SkPathOpsQuad.cpp
@@ -0,0 +1,342 @@
+/*
+ * 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 "SkIntersections.h"
+#include "SkLineParameters.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsTriangle.h"
+
+// from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html
+// (currently only used by testing)
+double SkDQuad::nearestT(const SkDPoint& pt) const {
+ SkDVector pos = fPts[0] - pt;
+ // search points P of bezier curve with PM.(dP / dt) = 0
+ // a calculus leads to a 3d degree equation :
+ SkDVector A = fPts[1] - fPts[0];
+ SkDVector B = fPts[2] - fPts[1];
+ B -= A;
+ double a = B.dot(B);
+ double b = 3 * A.dot(B);
+ double c = 2 * A.dot(A) + pos.dot(B);
+ double d = pos.dot(A);
+ double ts[3];
+ int roots = SkDCubic::RootsValidT(a, b, c, d, ts);
+ double d0 = pt.distanceSquared(fPts[0]);
+ double d2 = pt.distanceSquared(fPts[2]);
+ double distMin = SkTMin(d0, d2);
+ int bestIndex = -1;
+ for (int index = 0; index < roots; ++index) {
+ SkDPoint onQuad = ptAtT(ts[index]);
+ double dist = pt.distanceSquared(onQuad);
+ if (distMin > dist) {
+ distMin = dist;
+ bestIndex = index;
+ }
+ }
+ if (bestIndex >= 0) {
+ return ts[bestIndex];
+ }
+ return d0 < d2 ? 0 : 1;
+}
+
+bool SkDQuad::pointInHull(const SkDPoint& pt) const {
+ return ((const SkDTriangle&) fPts).contains(pt);
+}
+
+SkDPoint SkDQuad::top(double startT, double endT) const {
+ SkDQuad sub = subDivide(startT, endT);
+ SkDPoint topPt = sub[0];
+ if (topPt.fY > sub[2].fY || (topPt.fY == sub[2].fY && topPt.fX > sub[2].fX)) {
+ topPt = sub[2];
+ }
+ if (!between(sub[0].fY, sub[1].fY, sub[2].fY)) {
+ double extremeT;
+ if (FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, &extremeT)) {
+ extremeT = startT + (endT - startT) * extremeT;
+ SkDPoint test = ptAtT(extremeT);
+ if (topPt.fY > test.fY || (topPt.fY == test.fY && topPt.fX > test.fX)) {
+ topPt = test;
+ }
+ }
+ }
+ return topPt;
+}
+
+int SkDQuad::AddValidTs(double s[], int realRoots, double* t) {
+ int foundRoots = 0;
+ for (int index = 0; index < realRoots; ++index) {
+ double tValue = s[index];
+ if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
+ if (approximately_less_than_zero(tValue)) {
+ tValue = 0;
+ } else if (approximately_greater_than_one(tValue)) {
+ tValue = 1;
+ }
+ for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
+ if (approximately_equal(t[idx2], tValue)) {
+ goto nextRoot;
+ }
+ }
+ t[foundRoots++] = tValue;
+ }
+nextRoot:
+ {}
+ }
+ return foundRoots;
+}
+
+// note: caller expects multiple results to be sorted smaller first
+// note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting
+// analysis of the quadratic equation, suggesting why the following looks at
+// the sign of B -- and further suggesting that the greatest loss of precision
+// is in b squared less two a c
+int SkDQuad::RootsValidT(double A, double B, double C, double t[2]) {
+ double s[2];
+ int realRoots = RootsReal(A, B, C, s);
+ int foundRoots = AddValidTs(s, realRoots, t);
+ return foundRoots;
+}
+
+/*
+Numeric Solutions (5.6) suggests to solve the quadratic by computing
+ Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C))
+and using the roots
+ t1 = Q / A
+ t2 = C / Q
+*/
+// this does not discard real roots <= 0 or >= 1
+int SkDQuad::RootsReal(const double A, const double B, const double C, double s[2]) {
+ const double p = B / (2 * A);
+ const double q = C / A;
+ if (approximately_zero(A) && (approximately_zero_inverse(p) || approximately_zero_inverse(q))) {
+ 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 p2 = p * p;
+ if (!AlmostEqualUlps(p2, q) && p2 < q) {
+ return 0;
+ }
+ double sqrt_D = 0;
+ if (p2 > q) {
+ sqrt_D = sqrt(p2 - q);
+ }
+ s[0] = sqrt_D - p;
+ s[1] = -sqrt_D - p;
+ return 1 + !AlmostEqualUlps(s[0], s[1]);
+}
+
+bool SkDQuad::isLinear(int startIndex, int endIndex) const {
+ SkLineParameters lineParameters;
+ lineParameters.quadEndPoints(*this, startIndex, endIndex);
+ // FIXME: maybe it's possible to avoid this and compare non-normalized
+ lineParameters.normalize();
+ double distance = lineParameters.controlPtDistance(*this);
+ return approximately_zero(distance);
+}
+
+SkDCubic SkDQuad::toCubic() const {
+ SkDCubic cubic;
+ cubic[0] = fPts[0];
+ cubic[2] = fPts[1];
+ cubic[3] = fPts[2];
+ cubic[1].fX = (cubic[0].fX + cubic[2].fX * 2) / 3;
+ cubic[1].fY = (cubic[0].fY + cubic[2].fY * 2) / 3;
+ cubic[2].fX = (cubic[3].fX + cubic[2].fX * 2) / 3;
+ cubic[2].fY = (cubic[3].fY + cubic[2].fY * 2) / 3;
+ return cubic;
+}
+
+SkDVector SkDQuad::dxdyAtT(double t) const {
+ double a = t - 1;
+ double b = 1 - 2 * t;
+ double c = t;
+ SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX,
+ a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY };
+ return result;
+}
+
+// OPTIMIZE: assert if caller passes in t == 0 / t == 1 ?
+SkDPoint SkDQuad::ptAtT(double t) const {
+ if (0 == t) {
+ return fPts[0];
+ }
+ if (1 == t) {
+ return fPts[2];
+ }
+ double one_t = 1 - t;
+ double a = one_t * one_t;
+ double b = 2 * one_t * t;
+ double c = t * t;
+ SkDPoint result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX,
+ a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY };
+ return result;
+}
+
+/*
+Given a quadratic q, t1, and t2, find a small quadratic segment.
+
+The new quadratic is defined by A, B, and C, where
+ A = c[0]*(1 - t1)*(1 - t1) + 2*c[1]*t1*(1 - t1) + c[2]*t1*t1
+ C = c[3]*(1 - t1)*(1 - t1) + 2*c[2]*t1*(1 - t1) + c[1]*t1*t1
+
+To find B, compute the point halfway between t1 and t2:
+
+q(at (t1 + t2)/2) == D
+
+Next, compute where D must be if we know the value of B:
+
+_12 = A/2 + B/2
+12_ = B/2 + C/2
+123 = A/4 + B/2 + C/4
+ = D
+
+Group the known values on one side:
+
+B = D*2 - A/2 - C/2
+*/
+
+static double interp_quad_coords(const double* src, double t) {
+ double ab = SkDInterp(src[0], src[2], t);
+ double bc = SkDInterp(src[2], src[4], t);
+ double abc = SkDInterp(ab, bc, t);
+ return abc;
+}
+
+bool SkDQuad::monotonicInY() const {
+ return between(fPts[0].fY, fPts[1].fY, fPts[2].fY);
+}
+
+SkDQuad SkDQuad::subDivide(double t1, double t2) const {
+ SkDQuad dst;
+ double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1);
+ double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1);
+ double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2);
+ double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2);
+ double cx = dst[2].fX = interp_quad_coords(&fPts[0].fX, t2);
+ double cy = dst[2].fY = interp_quad_coords(&fPts[0].fY, t2);
+ /* bx = */ dst[1].fX = 2*dx - (ax + cx)/2;
+ /* by = */ dst[1].fY = 2*dy - (ay + cy)/2;
+ return dst;
+}
+
+void SkDQuad::align(int endIndex, SkDPoint* dstPt) const {
+ if (fPts[endIndex].fX == fPts[1].fX) {
+ dstPt->fX = fPts[endIndex].fX;
+ }
+ if (fPts[endIndex].fY == fPts[1].fY) {
+ dstPt->fY = fPts[endIndex].fY;
+ }
+}
+
+SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const {
+ SkASSERT(t1 != t2);
+ SkDPoint b;
+#if 0
+ // this approach assumes that the control point computed directly is accurate enough
+ double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2);
+ double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2);
+ b.fX = 2 * dx - (a.fX + c.fX) / 2;
+ b.fY = 2 * dy - (a.fY + c.fY) / 2;
+#else
+ SkDQuad sub = subDivide(t1, t2);
+ SkDLine b0 = {{a, sub[1] + (a - sub[0])}};
+ SkDLine b1 = {{c, sub[1] + (c - sub[2])}};
+ SkIntersections i;
+ i.intersectRay(b0, b1);
+ if (i.used() == 1) {
+ b = i.pt(0);
+ } else {
+ SkASSERT(i.used() == 2 || i.used() == 0);
+ b = SkDPoint::Mid(b0[1], b1[1]);
+ }
+#endif
+ if (t1 == 0 || t2 == 0) {
+ align(0, &b);
+ }
+ if (t1 == 1 || t2 == 1) {
+ align(2, &b);
+ }
+ if (precisely_subdivide_equal(b.fX, a.fX)) {
+ b.fX = a.fX;
+ } else if (precisely_subdivide_equal(b.fX, c.fX)) {
+ b.fX = c.fX;
+ }
+ if (precisely_subdivide_equal(b.fY, a.fY)) {
+ b.fY = a.fY;
+ } else if (precisely_subdivide_equal(b.fY, c.fY)) {
+ b.fY = c.fY;
+ }
+ return b;
+}
+
+/* classic one t subdivision */
+static void interp_quad_coords(const double* src, double* dst, double t) {
+ double ab = SkDInterp(src[0], src[2], t);
+ double bc = SkDInterp(src[2], src[4], t);
+ dst[0] = src[0];
+ dst[2] = ab;
+ dst[4] = SkDInterp(ab, bc, t);
+ dst[6] = bc;
+ dst[8] = src[4];
+}
+
+SkDQuadPair SkDQuad::chopAt(double t) const
+{
+ SkDQuadPair dst;
+ interp_quad_coords(&fPts[0].fX, &dst.pts[0].fX, t);
+ interp_quad_coords(&fPts[0].fY, &dst.pts[0].fY, t);
+ return dst;
+}
+
+static int valid_unit_divide(double numer, double denom, double* ratio)
+{
+ if (numer < 0) {
+ numer = -numer;
+ denom = -denom;
+ }
+ if (denom == 0 || numer == 0 || numer >= denom) {
+ return 0;
+ }
+ double r = numer / denom;
+ if (r == 0) { // catch underflow if numer <<<< denom
+ return 0;
+ }
+ *ratio = r;
+ return 1;
+}
+
+/** Quad'(t) = At + B, where
+ A = 2(a - 2b + c)
+ B = 2(b - a)
+ Solve for t, only if it fits between 0 < t < 1
+*/
+int SkDQuad::FindExtrema(double a, double b, double c, double tValue[1]) {
+ /* At + B == 0
+ t = -B / A
+ */
+ return valid_unit_divide(a - b, a - b - b + c, tValue);
+}
+
+/* Parameterization form, given A*t*t + 2*B*t*(1-t) + C*(1-t)*(1-t)
+ *
+ * a = A - 2*B + C
+ * b = 2*B - 2*C
+ * c = C
+ */
+void SkDQuad::SetABC(const double* quad, double* a, double* b, double* c) {
+ *a = quad[0]; // a = A
+ *b = 2 * quad[2]; // b = 2*B
+ *c = quad[4]; // c = C
+ *b -= *c; // b = 2*B - C
+ *a -= *b; // a = A - 2*B + C
+ *b -= *c; // b = 2*B - 2*C
+}
diff --git a/pathops/SkPathOpsQuad.h b/pathops/SkPathOpsQuad.h
new file mode 100644
index 00000000..c8650515
--- /dev/null
+++ b/pathops/SkPathOpsQuad.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 SkPathOpsQuad_DEFINED
+#define SkPathOpsQuad_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDQuadPair {
+ const SkDQuad& first() const { return (const SkDQuad&) pts[0]; }
+ const SkDQuad& second() const { return (const SkDQuad&) pts[2]; }
+ SkDPoint pts[5];
+};
+
+struct SkDQuad {
+ SkDPoint fPts[3];
+
+ SkDQuad flip() const {
+ SkDQuad result = {{fPts[2], fPts[1], fPts[0]}};
+ return result;
+ }
+
+ void set(const SkPoint pts[3]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ fPts[2] = pts[2];
+ }
+
+ const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 3); return fPts[n]; }
+ SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 3); return fPts[n]; }
+
+ static int AddValidTs(double s[], int realRoots, double* t);
+ void align(int endIndex, SkDPoint* dstPt) const;
+ SkDQuadPair chopAt(double t) const;
+ SkDVector dxdyAtT(double t) const;
+ static int FindExtrema(double a, double b, double c, double tValue[1]);
+ bool isLinear(int startIndex, int endIndex) const;
+ bool monotonicInY() const;
+ double nearestT(const SkDPoint&) const;
+ bool pointInHull(const SkDPoint&) const;
+ SkDPoint ptAtT(double t) const;
+ static int RootsReal(double A, double B, double C, double t[2]);
+ static int RootsValidT(const double A, const double B, const double C, double s[2]);
+ static void SetABC(const double* quad, double* a, double* b, double* c);
+ SkDQuad subDivide(double t1, double t2) const;
+ static SkDQuad SubDivide(const SkPoint a[3], double t1, double t2) {
+ SkDQuad quad;
+ quad.set(a);
+ return quad.subDivide(t1, t2);
+ }
+ SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const;
+ static SkDPoint SubDivide(const SkPoint pts[3], const SkDPoint& a, const SkDPoint& c,
+ double t1, double t2) {
+ SkDQuad quad;
+ quad.set(pts);
+ return quad.subDivide(a, c, t1, t2);
+ }
+ SkDCubic toCubic() const;
+ SkDPoint top(double startT, double endT) const;
+private:
+// static double Tangent(const double* quadratic, double t); // uncalled
+};
+
+#endif
diff --git a/pathops/SkPathOpsRect.cpp b/pathops/SkPathOpsRect.cpp
new file mode 100644
index 00000000..2ceed329
--- /dev/null
+++ b/pathops/SkPathOpsRect.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+
+void SkDRect::setBounds(const SkDLine& line) {
+ set(line[0]);
+ add(line[1]);
+}
+
+void SkDRect::setBounds(const SkDQuad& quad) {
+ set(quad[0]);
+ add(quad[2]);
+ double tValues[2];
+ int roots = 0;
+ if (!between(quad[0].fX, quad[1].fX, quad[2].fX)) {
+ roots = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, tValues);
+ }
+ if (!between(quad[0].fY, quad[1].fY, quad[2].fY)) {
+ roots += SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValues[roots]);
+ }
+ for (int x = 0; x < roots; ++x) {
+ add(quad.ptAtT(tValues[x]));
+ }
+}
+
+void SkDRect::setRawBounds(const SkDQuad& quad) {
+ set(quad[0]);
+ for (int x = 1; x < 3; ++x) {
+ add(quad[x]);
+ }
+}
+
+static bool is_bounded_by_end_points(double a, double b, double c, double d) {
+ return between(a, b, d) && between(a, c, d);
+}
+
+void SkDRect::setBounds(const SkDCubic& c) {
+ set(c[0]);
+ add(c[3]);
+ double tValues[4];
+ int roots = 0;
+ if (!is_bounded_by_end_points(c[0].fX, c[1].fX, c[2].fX, c[3].fX)) {
+ roots = SkDCubic::FindExtrema(c[0].fX, c[1].fX, c[2].fX, c[3].fX, tValues);
+ }
+ if (!is_bounded_by_end_points(c[0].fY, c[1].fY, c[2].fY, c[3].fY)) {
+ roots += SkDCubic::FindExtrema(c[0].fY, c[1].fY, c[2].fY, c[3].fY, &tValues[roots]);
+ }
+ for (int x = 0; x < roots; ++x) {
+ add(c.ptAtT(tValues[x]));
+ }
+}
+
+void SkDRect::setRawBounds(const SkDCubic& cubic) {
+ set(cubic[0]);
+ for (int x = 1; x < 4; ++x) {
+ add(cubic[x]);
+ }
+}
diff --git a/pathops/SkPathOpsRect.h b/pathops/SkPathOpsRect.h
new file mode 100644
index 00000000..2c47f43b
--- /dev/null
+++ b/pathops/SkPathOpsRect.h
@@ -0,0 +1,63 @@
+/*
+ * 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 SkPathOpsRect_DEFINED
+#define SkPathOpsRect_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDRect {
+ double fLeft, fTop, fRight, fBottom;
+
+ void add(const SkDPoint& pt) {
+ if (fLeft > pt.fX) {
+ fLeft = pt.fX;
+ }
+ if (fTop > pt.fY) {
+ fTop = pt.fY;
+ }
+ if (fRight < pt.fX) {
+ fRight = pt.fX;
+ }
+ if (fBottom < pt.fY) {
+ fBottom = pt.fY;
+ }
+ }
+
+ bool contains(const SkDPoint& pt) const {
+ return approximately_between(fLeft, pt.fX, fRight)
+ && approximately_between(fTop, pt.fY, fBottom);
+ }
+
+ bool intersects(SkDRect* r) const {
+ SkASSERT(fLeft <= fRight);
+ SkASSERT(fTop <= fBottom);
+ SkASSERT(r->fLeft <= r->fRight);
+ SkASSERT(r->fTop <= r->fBottom);
+ return r->fLeft <= fRight && fLeft <= r->fRight && r->fTop <= fBottom && fTop <= r->fBottom;
+ }
+
+ void set(const SkDPoint& pt) {
+ fLeft = fRight = pt.fX;
+ fTop = fBottom = pt.fY;
+ }
+
+ double width() const {
+ return fRight - fLeft;
+ }
+
+ double height() const {
+ return fBottom - fTop;
+ }
+
+ void setBounds(const SkDLine&);
+ void setBounds(const SkDCubic&);
+ void setBounds(const SkDQuad&);
+ void setRawBounds(const SkDCubic&);
+ void setRawBounds(const SkDQuad&);
+};
+
+#endif
diff --git a/pathops/SkPathOpsSimplify.cpp b/pathops/SkPathOpsSimplify.cpp
new file mode 100644
index 00000000..48877890
--- /dev/null
+++ b/pathops/SkPathOpsSimplify.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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 "SkAddIntersections.h"
+#include "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+
+static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWriter* simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ bool topDone;
+ SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex,
+ &topLeft, &topUnsortable, &topDone, false);
+ if (!current) {
+ if (topUnsortable || !topDone) {
+ topUnsortable = false;
+ SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<SkOpSpan*> chaseArray;
+ do {
+ if (current->activeWinding(index, endIndex)) {
+ do {
+ if (!unsortable && current->done()) {
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ if (simple->isEmpty()) {
+ simple->init();
+ break;
+ }
+ }
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ SkOpSegment* 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;
+ }
+ #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, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple->isClosed() && (!unsortable
+ || !current->done(SkMin32(index, endIndex))));
+ if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
+ SkASSERT(unsortable || simple->isEmpty());
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneUnary(min);
+ }
+ }
+ simple->close();
+ } else {
+ SkOpSpan* last = current->markAndChaseDoneUnary(index, endIndex);
+ if (last && !last->fLoop) {
+ *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(SkTArray<SkOpContour*, true>& contourList, SkPathWriter* simple) {
+ SkOpSegment* current;
+ int start, end;
+ bool unsortable = false;
+ bool closable = true;
+ while ((current = FindUndone(contourList, &start, &end))) {
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ DebugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = start;
+ int nextEnd = end;
+ SkOpSegment* 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(SkMin32(start, end))));
+ 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;
+}
+
+// FIXME : add this as a member of SkPath
+bool Simplify(const SkPath& path, SkPath* result) {
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ gDebugSortCount = gDebugSortCountDefault;
+#endif
+ // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
+ SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
+ : SkPath::kEvenOdd_FillType;
+
+ // turn path into list of segments
+ SkTArray<SkOpContour> contours;
+ SkOpEdgeBuilder builder(path, contours);
+ if (!builder.finish()) {
+ return false;
+ }
+ SkTArray<SkOpContour*, true> contourList;
+ MakeContourList(contours, contourList, false, false);
+ SkOpContour** currentPtr = contourList.begin();
+ result->reset();
+ result->setFillType(fillType);
+ if (!currentPtr) {
+ return true;
+ }
+ SkOpContour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ SkOpContour** nextPtr = currentPtr;
+ SkOpContour* current = *currentPtr++;
+ if (current->containsCubics()) {
+ AddSelfIntersectTs(current);
+ }
+ SkOpContour* next;
+ do {
+ next = *nextPtr++;
+ } while (AddIntersectTs(current, next) && nextPtr != listEnd);
+ } while (currentPtr != listEnd);
+ // eat through coincident edges
+ CoincidenceCheck(&contourList, 0);
+ FixOtherTIndex(&contourList);
+ CheckEnds(&contourList);
+ SortSegments(&contourList);
+#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
+ DebugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ SkPathWriter simple(*result);
+ if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple)
+ : !bridgeXor(contourList, &simple))
+ { // if some edges could not be resolved, assemble remaining fragments
+ SkPath temp;
+ temp.setFillType(fillType);
+ SkPathWriter assembled(temp);
+ Assemble(simple, &assembled);
+ *result = *assembled.nativePath();
+ result->setFillType(fillType);
+ }
+ return true;
+}
diff --git a/pathops/SkPathOpsSpan.h b/pathops/SkPathOpsSpan.h
new file mode 100644
index 00000000..33965922
--- /dev/null
+++ b/pathops/SkPathOpsSpan.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 SkOpSpan_DEFINED
+#define SkOpSpan_DEFINED
+
+#include "SkPoint.h"
+
+class SkOpSegment;
+
+struct SkOpSpan {
+ SkOpSegment* fOther;
+ SkPoint fPt; // computed when the curves are intersected
+ 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
+ bool fLoop; // set when a cubic loops back to this point
+};
+
+#endif
diff --git a/pathops/SkPathOpsTriangle.cpp b/pathops/SkPathOpsTriangle.cpp
new file mode 100644
index 00000000..49391667
--- /dev/null
+++ b/pathops/SkPathOpsTriangle.cpp
@@ -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.
+ */
+
+#include "SkPathOpsTriangle.h"
+
+// http://www.blackpawn.com/texts/pointinpoly/default.html
+bool SkDTriangle::contains(const SkDPoint& pt) const {
+// Compute vectors
+ SkDVector v0 = fPts[2] - fPts[0];
+ SkDVector v1 = fPts[1] - fPts[0];
+ SkDVector v2 = pt - fPts[0];
+
+// Compute dot products
+ double dot00 = v0.dot(v0);
+ double dot01 = v0.dot(v1);
+ double dot02 = v0.dot(v2);
+ double dot11 = v1.dot(v1);
+ double dot12 = v1.dot(v2);
+
+// Compute barycentric coordinates
+ double invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
+ double u = (dot11 * dot02 - dot01 * dot12) * invDenom;
+ double v = (dot00 * dot12 - dot01 * dot02) * invDenom;
+
+// Check if point is in triangle
+ return (u >= 0) && (v >= 0) && (u + v < 1);
+}
diff --git a/pathops/SkPathOpsTriangle.h b/pathops/SkPathOpsTriangle.h
new file mode 100644
index 00000000..8cc8c6d3
--- /dev/null
+++ b/pathops/SkPathOpsTriangle.h
@@ -0,0 +1,20 @@
+/*
+ * 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 SkPathOpsTriangle_DEFINED
+#define SkPathOpsTriangle_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDTriangle {
+ SkDPoint fPts[3];
+
+ bool contains(const SkDPoint& pt) const;
+
+};
+
+#endif
diff --git a/pathops/SkPathOpsTypes.cpp b/pathops/SkPathOpsTypes.cpp
new file mode 100644
index 00000000..8135c570
--- /dev/null
+++ b/pathops/SkPathOpsTypes.cpp
@@ -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.
+ */
+#include "SkFloatBits.h"
+#include "SkPathOpsTypes.h"
+
+
+
+// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+// FIXME: move to SkFloatBits.h
+static bool equal_ulps(float a, float b, int epsilon) {
+ SkFloatIntUnion floatIntA, floatIntB;
+ floatIntA.fFloat = a;
+ floatIntB.fFloat = b;
+ // Different signs means they do not match.
+ if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) {
+ // Check for equality to make sure +0 == -0
+ return a == b;
+ }
+ // Find the difference in ULPs.
+ int ulpsDiff = abs(floatIntA.fSignBitInt - floatIntB.fSignBitInt);
+ return ulpsDiff <= epsilon;
+}
+
+static bool less_ulps(float a, float b, int epsilon) {
+ SkFloatIntUnion floatIntA, floatIntB;
+ floatIntA.fFloat = a;
+ floatIntB.fFloat = b;
+ // Check different signs with float epsilon since we only care if they're both close to 0.
+ if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) {
+ return a <= b + FLT_EPSILON * epsilon;
+ }
+ // Find the difference in ULPs.
+ return floatIntA.fSignBitInt <= floatIntB.fSignBitInt + epsilon;
+}
+
+bool AlmostEqualUlps(float a, float b) {
+ const int UlpsEpsilon = 16;
+ return equal_ulps(a, b, UlpsEpsilon);
+}
+
+bool RoughlyEqualUlps(float a, float b) {
+ const int UlpsEpsilon = 256;
+ return equal_ulps(a, b, UlpsEpsilon);
+}
+
+bool AlmostBetweenUlps(float a, float b, float c) {
+ const int UlpsEpsilon = 1;
+ return a <= c ? less_ulps(a, b, UlpsEpsilon) && less_ulps(b, c, UlpsEpsilon)
+ : less_ulps(b, a, UlpsEpsilon) && less_ulps(c, b, UlpsEpsilon);
+}
+
+int UlpsDistance(float a, float b) {
+ SkFloatIntUnion floatIntA, floatIntB;
+ floatIntA.fFloat = a;
+ floatIntB.fFloat = b;
+ // Different signs means they do not match.
+ if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) {
+ // Check for equality to make sure +0 == -0
+ return a == b ? 0 : SK_MaxS32;
+ }
+ // Find the difference in ULPs.
+ return abs(floatIntA.fSignBitInt - floatIntB.fSignBitInt);
+}
+
+// cube root approximation using bit hack for 64-bit float
+// adapted from Kahan's cbrt
+static double cbrt_5d(double d) {
+ const unsigned int B1 = 715094163;
+ double t = 0.0;
+ unsigned int* pt = (unsigned int*) &t;
+ unsigned int* px = (unsigned int*) &d;
+ pt[1] = px[1] / 3 + B1;
+ return t;
+}
+
+// iterative cube root approximation using Halley's method (double)
+static double cbrta_halleyd(const double a, const double R) {
+ const double a3 = a * a * a;
+ const double b = a * (a3 + R + R) / (a3 + a3 + R);
+ return b;
+}
+
+// cube root approximation using 3 iterations of Halley's method (double)
+static double halley_cbrt3d(double d) {
+ double a = cbrt_5d(d);
+ a = cbrta_halleyd(a, d);
+ a = cbrta_halleyd(a, d);
+ return cbrta_halleyd(a, d);
+}
+
+double SkDCubeRoot(double x) {
+ if (approximately_zero_cubed(x)) {
+ return 0;
+ }
+ double result = halley_cbrt3d(fabs(x));
+ if (x < 0) {
+ result = -result;
+ }
+ return result;
+}
diff --git a/pathops/SkPathOpsTypes.h b/pathops/SkPathOpsTypes.h
new file mode 100644
index 00000000..6158f135
--- /dev/null
+++ b/pathops/SkPathOpsTypes.h
@@ -0,0 +1,277 @@
+/*
+ * 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 SkPathOpsTypes_DEFINED
+#define SkPathOpsTypes_DEFINED
+
+#include <float.h> // for FLT_EPSILON
+#include <math.h> // for fabs, sqrt
+
+#include "SkFloatingPoint.h"
+#include "SkPath.h"
+#include "SkPathOps.h"
+#include "SkPathOpsDebug.h"
+#include "SkScalar.h"
+
+enum SkPathOpsMask {
+ kWinding_PathOpsMask = -1,
+ kNo_PathOpsMask = 0,
+ kEvenOdd_PathOpsMask = 1
+};
+
+// Use Almost Equal when comparing coordinates. Use epsilon to compare T values.
+bool AlmostEqualUlps(float a, float b);
+inline bool AlmostEqualUlps(double a, double b) {
+ return AlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
+}
+
+bool RoughlyEqualUlps(float a, float b);
+inline bool RoughlyEqualUlps(double a, double b) {
+ return RoughlyEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
+}
+
+bool AlmostBetweenUlps(float a, float b, float c);
+inline bool AlmostBetweenUlps(double a, double b, double c) {
+ return AlmostBetweenUlps(SkDoubleToScalar(a), SkDoubleToScalar(b), SkDoubleToScalar(c));
+}
+
+int UlpsDistance(float a, float b);
+inline int UlpsDistance(double a, double b) {
+ return UlpsDistance(SkDoubleToScalar(a), SkDoubleToScalar(b));
+}
+
+// FLT_EPSILON == 1.19209290E-07 == 1 / (2 ^ 23)
+// DBL_EPSILON == 2.22045e-16
+const double FLT_EPSILON_CUBED = FLT_EPSILON * FLT_EPSILON * FLT_EPSILON;
+const double FLT_EPSILON_HALF = FLT_EPSILON / 2;
+const double FLT_EPSILON_DOUBLE = FLT_EPSILON * 2;
+const double FLT_EPSILON_SQUARED = FLT_EPSILON * FLT_EPSILON;
+const double FLT_EPSILON_SQRT = sqrt(FLT_EPSILON);
+const double FLT_EPSILON_INVERSE = 1 / FLT_EPSILON;
+const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few bits of error
+const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16;
+const double ROUGH_EPSILON = FLT_EPSILON * 64;
+const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256;
+
+inline bool approximately_zero(double x) {
+ return fabs(x) < FLT_EPSILON;
+}
+
+inline bool precisely_zero(double x) {
+ return fabs(x) < DBL_EPSILON_ERR;
+}
+
+inline bool precisely_subdivide_zero(double x) {
+ return fabs(x) < DBL_EPSILON_SUBDIVIDE_ERR;
+}
+
+inline bool approximately_zero(float x) {
+ return fabs(x) < FLT_EPSILON;
+}
+
+inline bool approximately_zero_cubed(double x) {
+ return fabs(x) < FLT_EPSILON_CUBED;
+}
+
+inline bool approximately_zero_half(double x) {
+ return fabs(x) < FLT_EPSILON_HALF;
+}
+
+inline bool approximately_zero_double(double x) {
+ return fabs(x) < FLT_EPSILON_DOUBLE;
+}
+
+inline bool approximately_zero_squared(double x) {
+ return fabs(x) < FLT_EPSILON_SQUARED;
+}
+
+inline bool approximately_zero_sqrt(double x) {
+ return fabs(x) < FLT_EPSILON_SQRT;
+}
+
+inline bool roughly_zero(double x) {
+ return fabs(x) < ROUGH_EPSILON;
+}
+
+inline bool approximately_zero_inverse(double x) {
+ return fabs(x) > FLT_EPSILON_INVERSE;
+}
+
+// OPTIMIZATION: if called multiple times with the same denom, we want to pass 1/y instead
+inline bool approximately_zero_when_compared_to(double x, double y) {
+ return x == 0 || fabs(x / y) < FLT_EPSILON;
+}
+
+// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use
+// AlmostEqualUlps instead.
+inline bool approximately_equal(double x, double y) {
+ return approximately_zero(x - y);
+}
+
+inline bool precisely_equal(double x, double y) {
+ return precisely_zero(x - y);
+}
+
+inline bool precisely_subdivide_equal(double x, double y) {
+ return precisely_subdivide_zero(x - y);
+}
+
+inline bool approximately_equal_half(double x, double y) {
+ return approximately_zero_half(x - y);
+}
+
+inline bool approximately_equal_double(double x, double y) {
+ return approximately_zero_double(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 x - FLT_EPSILON >= y;
+}
+
+inline bool approximately_greater_or_equal(double x, double y) {
+ return x + FLT_EPSILON > y;
+}
+
+inline bool approximately_lesser(double x, double y) {
+ return x + FLT_EPSILON <= y;
+}
+
+inline bool approximately_lesser_or_equal(double x, double y) {
+ return x - FLT_EPSILON < y;
+}
+
+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_ERR;
+}
+
+inline bool approximately_less_than_zero(double x) {
+ return x < FLT_EPSILON;
+}
+
+inline bool precisely_less_than_zero(double x) {
+ return x < DBL_EPSILON_ERR;
+}
+
+inline bool approximately_negative(double x) {
+ return x < FLT_EPSILON;
+}
+
+inline bool precisely_negative(double x) {
+ return x < DBL_EPSILON_ERR;
+}
+
+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_SQUARED);
+}
+
+inline bool approximately_zero_or_more(double x) {
+ return x > -FLT_EPSILON;
+}
+
+inline bool approximately_between(double a, double b, double c) {
+ return a <= c ? approximately_negative(a - b) && approximately_negative(b - c)
+ : approximately_negative(b - a) && approximately_negative(c - b);
+}
+
+inline bool precisely_between(double a, double b, double c) {
+ return a <= c ? precisely_negative(a - b) && precisely_negative(b - c)
+ : precisely_negative(b - a) && precisely_negative(c - b);
+}
+
+// returns true if (a <= b <= c) || (a >= b >= c)
+inline bool between(double a, double b, double c) {
+ SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0));
+ return (a - b) * (c - b) <= 0;
+}
+
+inline bool more_roughly_equal(double x, double y) {
+ return fabs(x - y) < MORE_ROUGH_EPSILON;
+}
+
+inline bool roughly_equal(double x, double y) {
+ return fabs(x - y) < ROUGH_EPSILON;
+}
+
+struct SkDPoint;
+struct SkDVector;
+struct SkDLine;
+struct SkDQuad;
+struct SkDTriangle;
+struct SkDCubic;
+struct SkDRect;
+
+inline SkPath::Verb SkPathOpsPointsToVerb(int points) {
+ int verb = (1 << points) >> 1;
+#ifdef SK_DEBUG
+ switch (points) {
+ case 0: SkASSERT(SkPath::kMove_Verb == verb); break;
+ case 1: SkASSERT(SkPath::kLine_Verb == verb); break;
+ case 2: SkASSERT(SkPath::kQuad_Verb == verb); break;
+ case 3: SkASSERT(SkPath::kCubic_Verb == verb); break;
+ default: SkASSERT(!"should not be here");
+ }
+#endif
+ return (SkPath::Verb)verb;
+}
+
+inline int SkPathOpsVerbToPoints(SkPath::Verb verb) {
+ int points = (int) verb - ((int) verb >> 2);
+#ifdef SK_DEBUG
+ switch (verb) {
+ case SkPath::kLine_Verb: SkASSERT(1 == points); break;
+ case SkPath::kQuad_Verb: SkASSERT(2 == points); break;
+ case SkPath::kCubic_Verb: SkASSERT(3 == points); break;
+ default: SkASSERT(!"should not get here");
+ }
+#endif
+ return points;
+}
+
+inline double SkDInterp(double A, double B, double t) {
+ return A + (B - A) * t;
+}
+
+double SkDCubeRoot(double x);
+
+/* Returns -1 if negative, 0 if zero, 1 if positive
+*/
+inline int SkDSign(double x) {
+ return (x > 0) - (x < 0);
+}
+
+/* Returns 0 if negative, 1 if zero, 2 if positive
+*/
+inline int SKDSide(double x) {
+ return (x > 0) + (x >= 0);
+}
+
+/* Returns 1 if negative, 2 if zero, 4 if positive
+*/
+inline int SkDSideBit(double x) {
+ return 1 << SKDSide(x);
+}
+
+inline double SkPinT(double t) {
+ return precisely_less_than_zero(t) ? 0 : precisely_greater_than_one(t) ? 1 : t;
+}
+
+#endif
diff --git a/pathops/SkPathWriter.cpp b/pathops/SkPathWriter.cpp
new file mode 100644
index 00000000..55590261
--- /dev/null
+++ b/pathops/SkPathWriter.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 "SkPathOpsPoint.h"
+#include "SkPathWriter.h"
+
+// wrap path to keep track of whether the contour is initialized and non-empty
+SkPathWriter::SkPathWriter(SkPath& path)
+ : fPathPtr(&path)
+ , fCloses(0)
+ , fMoves(0)
+{
+ init();
+}
+
+void SkPathWriter::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 SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) {
+ lineTo();
+ if (fEmpty && AlmostEqualUlps(fDefer[0], pt1) && AlmostEqualUlps(pt1, pt2)
+ && AlmostEqualUlps(pt2, pt3)) {
+ deferredLine(pt3);
+ return;
+ }
+ moveTo();
+ fDefer[1] = pt3;
+ nudge();
+ fDefer[0] = fDefer[1];
+#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, fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->cubicTo(pt1.fX, pt1.fY, pt2.fX, pt2.fY, fDefer[1].fX, fDefer[1].fY);
+ fEmpty = false;
+}
+
+void SkPathWriter::deferredLine(const SkPoint& pt) {
+ if (pt == fDefer[1]) {
+ return;
+ }
+ if (changedSlopes(pt)) {
+ lineTo();
+ fDefer[0] = fDefer[1];
+ }
+ fDefer[1] = pt;
+}
+
+void SkPathWriter::deferredMove(const SkPoint& pt) {
+ fMoved = true;
+ fHasMove = true;
+ fEmpty = true;
+ fDefer[0] = fDefer[1] = pt;
+}
+
+void SkPathWriter::deferredMoveLine(const SkPoint& pt) {
+ if (!fHasMove) {
+ deferredMove(pt);
+ }
+ deferredLine(pt);
+}
+
+bool SkPathWriter::hasMove() const {
+ return fHasMove;
+}
+
+void SkPathWriter::init() {
+ fEmpty = true;
+ fHasMove = false;
+ fMoved = false;
+}
+
+bool SkPathWriter::isClosed() const {
+ return !fEmpty && fFirstPt == fDefer[1];
+}
+
+void SkPathWriter::lineTo() {
+ if (fDefer[0] == fDefer[1]) {
+ return;
+ }
+ moveTo();
+ nudge();
+ 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* SkPathWriter::nativePath() const {
+ return fPathPtr;
+}
+
+void SkPathWriter::nudge() {
+ if (fEmpty || !AlmostEqualUlps(fDefer[1].fX, fFirstPt.fX)
+ || !AlmostEqualUlps(fDefer[1].fY, fFirstPt.fY)) {
+ return;
+ }
+ fDefer[1] = fFirstPt;
+}
+
+void SkPathWriter::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+ lineTo();
+ if (fEmpty && AlmostEqualUlps(fDefer[0], pt1) && AlmostEqualUlps(pt1, pt2)) {
+ deferredLine(pt2);
+ return;
+ }
+ moveTo();
+ fDefer[1] = pt2;
+ nudge();
+ fDefer[0] = fDefer[1];
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n",
+ pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->quadTo(pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY);
+ fEmpty = false;
+}
+
+bool SkPathWriter::someAssemblyRequired() const {
+ return fCloses < fMoves;
+}
+
+bool SkPathWriter::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 SkPathWriter::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++;
+}
diff --git a/pathops/SkPathWriter.h b/pathops/SkPathWriter.h
new file mode 100644
index 00000000..57470823
--- /dev/null
+++ b/pathops/SkPathWriter.h
@@ -0,0 +1,45 @@
+/*
+ * 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 SkPathWriter_DEFINED
+#define SkPathWriter_DEFINED
+
+#include "SkPath.h"
+
+class SkPathWriter {
+public:
+ SkPathWriter(SkPath& path);
+ void close();
+ void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3);
+ void deferredLine(const SkPoint& pt);
+ void deferredMove(const SkPoint& pt);
+ void deferredMoveLine(const SkPoint& pt);
+ bool hasMove() const;
+ void init();
+ bool isClosed() const;
+ bool isEmpty() const { return fEmpty; }
+ void lineTo();
+ const SkPath* nativePath() const;
+ void nudge();
+ void quadTo(const SkPoint& pt1, const SkPoint& pt2);
+ bool someAssemblyRequired() const;
+
+private:
+ bool changedSlopes(const SkPoint& pt) const;
+ void moveTo();
+
+ SkPath* fPathPtr;
+ SkPoint fDefer[2];
+ SkPoint fFirstPt;
+ int fCloses;
+ int fMoves;
+ bool fEmpty;
+ bool fHasMove;
+ bool fMoved;
+};
+
+
+#endif /* defined(__PathOps__SkPathWriter__) */
diff --git a/pathops/SkQuarticRoot.cpp b/pathops/SkQuarticRoot.cpp
new file mode 100644
index 00000000..596d2a25
--- /dev/null
+++ b/pathops/SkQuarticRoot.cpp
@@ -0,0 +1,165 @@
+// 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 "SkPathOpsCubic.h"
+#include "SkPathOpsQuad.h"
+#include "SkQuarticRoot.h"
+
+int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1,
+ const double t0, const bool oneHint, double roots[4]) {
+#ifdef SK_DEBUG
+ // create a string mathematica understands
+ // GDB set print repe 15 # if repeated digits is a bother
+ // set print elements 400 # if line doesn't fit
+ char str[1024];
+ sk_bzero(str, sizeof(str));
+ SK_SNPRINTF(str, sizeof(str),
+ "Solve[%1.19g x^4 + %1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
+ t4, t3, t2, t1, t0);
+ mathematica_ize(str, sizeof(str));
+#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
+ SkDebugf("%s\n", str);
+#endif
+#endif
+ if (approximately_zero_when_compared_to(t4, t0) // 0 is one root
+ && approximately_zero_when_compared_to(t4, t1)
+ && approximately_zero_when_compared_to(t4, t2)) {
+ if (approximately_zero_when_compared_to(t3, t0)
+ && approximately_zero_when_compared_to(t3, t1)
+ && approximately_zero_when_compared_to(t3, t2)) {
+ return SkDQuad::RootsReal(t2, t1, t0, roots);
+ }
+ if (approximately_zero_when_compared_to(t4, t3)) {
+ return SkDCubic::RootsReal(t3, t2, t1, t0, roots);
+ }
+ }
+ if ((approximately_zero_when_compared_to(t0, t1) || approximately_zero(t1)) // 0 is one root
+ // && approximately_zero_when_compared_to(t0, t2)
+ && approximately_zero_when_compared_to(t0, t3)
+ && approximately_zero_when_compared_to(t0, t4)) {
+ int num = SkDCubic::RootsReal(t4, t3, t2, t1, roots);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_zero(roots[i])) {
+ return num;
+ }
+ }
+ roots[num++] = 0;
+ return num;
+ }
+ if (oneHint) {
+ SkASSERT(approximately_zero(t4 + t3 + t2 + t1 + t0)); // 1 is one root
+ // note that -C == A + B + D + E
+ int num = SkDCubic::RootsReal(t4, t4 + t3, -(t1 + t0), -t0, roots);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_equal(roots[i], 1)) {
+ return num;
+ }
+ }
+ roots[num++] = 1;
+ return num;
+ }
+ return -1;
+}
+
+int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C,
+ const double D, const double E, double s[4]) {
+ 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;
+ int num;
+ if (approximately_zero(r)) {
+ /* no absolute term: y(y^3 + py + q) = 0 */
+ num = SkDCubic::RootsReal(1, 0, p, q, s);
+ s[num++] = 0;
+ } else {
+ /* solve the resolvent cubic ... */
+ double cubicRoots[3];
+ int roots = SkDCubic::RootsReal(1, -p / 2, -r, r * p / 2 - q * q / 8, cubicRoots);
+ int index;
+ /* ... and take one real solution ... */
+ double z;
+ num = 0;
+ int num2 = 0;
+ for (index = firstCubicRoot; index < roots; ++index) {
+ z = cubicRoots[index];
+ /* ... to build two quadric equations */
+ u = z * z - r;
+ v = 2 * z - p;
+ if (approximately_zero_squared(u)) {
+ u = 0;
+ } else if (u > 0) {
+ u = sqrt(u);
+ } else {
+ continue;
+ }
+ if (approximately_zero_squared(v)) {
+ v = 0;
+ } else if (v > 0) {
+ v = sqrt(v);
+ } else {
+ continue;
+ }
+ num = SkDQuad::RootsReal(1, q < 0 ? -v : v, z - u, s);
+ num2 = SkDQuad::RootsReal(1, q < 0 ? v : -v, z + u, s + num);
+ if (!((num | num2) & 1)) {
+ break; // prefer solutions without single quad roots
+ }
+ }
+ num += num2;
+ if (!num) {
+ return 0; // no valid cubic root
+ }
+ }
+ /* resubstitute */
+ const double sub = a / 4;
+ for (int i = 0; i < num; ++i) {
+ s[i] -= sub;
+ }
+ // eliminate duplicates
+ for (int i = 0; i < num - 1; ++i) {
+ for (int j = i + 1; j < num; ) {
+ if (AlmostEqualUlps(s[i], s[j])) {
+ if (j < --num) {
+ s[j] = s[num];
+ }
+ } else {
+ ++j;
+ }
+ }
+ }
+ return num;
+}
diff --git a/pathops/SkQuarticRoot.h b/pathops/SkQuarticRoot.h
new file mode 100644
index 00000000..6ce08671
--- /dev/null
+++ b/pathops/SkQuarticRoot.h
@@ -0,0 +1,16 @@
+/*
+ * 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 SkDQuarticRoot_DEFINED
+#define SkDQuarticRoot_DEFINED
+
+int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1,
+ const double t0, const bool oneHint, double s[4]);
+
+int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C,
+ const double D, const double E, double s[4]);
+
+#endif
diff --git a/pathops/SkReduceOrder.cpp b/pathops/SkReduceOrder.cpp
new file mode 100644
index 00000000..3dfdc9da
--- /dev/null
+++ b/pathops/SkReduceOrder.cpp
@@ -0,0 +1,452 @@
+/*
+ * 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 "SkReduceOrder.h"
+
+int SkReduceOrder::reduce(const SkDLine& line) {
+ fLine[0] = line[0];
+ int different = line[0] != line[1];
+ fLine[1] = line[different];
+ return 1 + different;
+}
+
+static double interp_quad_coords(double a, double b, double c, double t) {
+ double ab = SkDInterp(a, b, t);
+ double bc = SkDInterp(b, c, t);
+ return SkDInterp(ab, bc, t);
+}
+
+static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) {
+ reduction[0] = reduction[1] = quad[0];
+ return 1;
+}
+
+static int reductionLineCount(const SkDQuad& reduction) {
+ return 1 + !reduction[0].approximatelyEqual(reduction[1]);
+}
+
+static int vertical_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
+ SkDQuad& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fY > reduction[0].fY;
+ int larger = smaller ^ 1;
+ if (SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue)) {
+ double yExtrema = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
+ if (reduction[smaller].fY > yExtrema) {
+ reduction[smaller].fY = yExtrema;
+ } else if (reduction[larger].fY < yExtrema) {
+ reduction[larger].fY = yExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int horizontal_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
+ SkDQuad& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fX > reduction[0].fX;
+ int larger = smaller ^ 1;
+ if (SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue)) {
+ double xExtrema = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
+ if (reduction[smaller].fX > xExtrema) {
+ reduction[smaller].fX = xExtrema;
+ } else if (reduction[larger].fX < xExtrema) {
+ reduction[larger].fX = xExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int check_linear(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
+ int minX, int maxX, int minY, int maxY, SkDQuad& reduction) {
+ int startIndex = 0;
+ int endIndex = 2;
+ while (quad[startIndex].approximatelyEqual(quad[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ SkDebugf("%s shouldn't get here if all four points are about equal", __FUNCTION__);
+ SkASSERT(0);
+ }
+ }
+ if (!quad.isLinear(startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int sameSide;
+ bool useX = quad[maxX].fX - quad[minX].fX >= quad[maxY].fY - quad[minY].fY;
+ if (useX) {
+ sameSide = SkDSign(quad[0].fX - quad[1].fX) + SkDSign(quad[2].fX - quad[1].fX);
+ } else {
+ sameSide = SkDSign(quad[0].fY - quad[1].fY) + SkDSign(quad[2].fY - quad[1].fY);
+ }
+ if ((sameSide & 3) != 2) {
+ return reductionLineCount(reduction);
+ }
+ double tValue;
+ int root;
+ if (useX) {
+ root = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue);
+ } else {
+ root = SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue);
+ }
+ if (root) {
+ SkDPoint extrema;
+ extrema.fX = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
+ extrema.fY = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
+ // sameSide > 0 means mid is smaller than either [0] or [2], so replace smaller
+ int replace;
+ if (useX) {
+ if ((extrema.fX < quad[0].fX) ^ (extrema.fX < quad[2].fX)) {
+ return reductionLineCount(reduction);
+ }
+ replace = ((extrema.fX < quad[0].fX) | (extrema.fX < quad[2].fX))
+ ^ (quad[0].fX < quad[2].fX);
+ } else {
+ if ((extrema.fY < quad[0].fY) ^ (extrema.fY < quad[2].fY)) {
+ return reductionLineCount(reduction);
+ }
+ replace = ((extrema.fY < quad[0].fY) | (extrema.fY < quad[2].fY))
+ ^ (quad[0].fY < quad[2].fY);
+ }
+ reduction[replace] = extrema;
+ }
+ return reductionLineCount(reduction);
+}
+
+// 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 SkReduceOrder::reduce(const SkDQuad& quad, Style reduceStyle) {
+ 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].fX > quad[index].fX) {
+ minX = index;
+ }
+ if (quad[minY].fY > quad[index].fY) {
+ minY = index;
+ }
+ if (quad[maxX].fX < quad[index].fX) {
+ maxX = index;
+ }
+ if (quad[maxY].fY < quad[index].fY) {
+ maxY = index;
+ }
+ }
+ for (index = 0; index < 3; ++index) {
+ if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) {
+ minXSet |= 1 << index;
+ }
+ if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) {
+ minYSet |= 1 << index;
+ }
+ }
+ if (minXSet == 0x7) { // test for vertical line
+ if (minYSet == 0x7) { // return 1 if all four are coincident
+ return coincident_line(quad, fQuad);
+ }
+ return vertical_line(quad, reduceStyle, fQuad);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(quad, reduceStyle, fQuad);
+ }
+ int result = check_linear(quad, reduceStyle, minX, maxX, minY, maxY, fQuad);
+ if (result) {
+ return result;
+ }
+ fQuad = quad;
+ return 3;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+
+static double interp_cubic_coords(const double* src, double t) {
+ double ab = SkDInterp(src[0], src[2], t);
+ double bc = SkDInterp(src[2], src[4], t);
+ double cd = SkDInterp(src[4], src[6], t);
+ double abc = SkDInterp(ab, bc, t);
+ double bcd = SkDInterp(bc, cd, t);
+ return SkDInterp(abc, bcd, t);
+}
+
+static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) {
+ reduction[0] = reduction[1] = cubic[0];
+ return 1;
+}
+
+static int reductionLineCount(const SkDCubic& reduction) {
+ return 1 + !reduction[0].approximatelyEqual(reduction[1]);
+}
+
+static int vertical_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
+ SkDCubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fY > reduction[0].fY;
+ int larger = smaller ^ 1;
+ int roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double yExtrema = interp_cubic_coords(&cubic[0].fY, tValues[index]);
+ if (reduction[smaller].fY > yExtrema) {
+ reduction[smaller].fY = yExtrema;
+ continue;
+ }
+ if (reduction[larger].fY < yExtrema) {
+ reduction[larger].fY = yExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int horizontal_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
+ SkDCubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fX > reduction[0].fX;
+ int larger = smaller ^ 1;
+ int roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double xExtrema = interp_cubic_coords(&cubic[0].fX, tValues[index]);
+ if (reduction[smaller].fX > xExtrema) {
+ reduction[smaller].fX = xExtrema;
+ continue;
+ }
+ if (reduction[larger].fX < xExtrema) {
+ reduction[larger].fX = xExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+// check to see if it is a quadratic or a line
+static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) {
+ double dx10 = cubic[1].fX - cubic[0].fX;
+ double dx23 = cubic[2].fX - cubic[3].fX;
+ double midX = cubic[0].fX + dx10 * 3 / 2;
+ double sideAx = midX - cubic[3].fX;
+ double sideBx = dx23 * 3 / 2;
+ if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx)
+ : !AlmostEqualUlps(sideAx, sideBx)) {
+ return 0;
+ }
+ double dy10 = cubic[1].fY - cubic[0].fY;
+ double dy23 = cubic[2].fY - cubic[3].fY;
+ double midY = cubic[0].fY + dy10 * 3 / 2;
+ double sideAy = midY - cubic[3].fY;
+ double sideBy = dy23 * 3 / 2;
+ if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy)
+ : !AlmostEqualUlps(sideAy, sideBy)) {
+ return 0;
+ }
+ reduction[0] = cubic[0];
+ reduction[1].fX = midX;
+ reduction[1].fY = midY;
+ reduction[2] = cubic[3];
+ return 3;
+}
+
+static int check_linear(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
+ int minX, int maxX, int minY, int maxY, SkDCubic& reduction) {
+ int startIndex = 0;
+ int endIndex = 3;
+ while (cubic[startIndex].approximatelyEqual(cubic[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ SkDebugf("%s shouldn't get here if all four points are about equal\n", __FUNCTION__);
+ SkASSERT(0);
+ }
+ }
+ if (!cubic.isLinear(startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int sameSide1;
+ int sameSide2;
+ bool useX = cubic[maxX].fX - cubic[minX].fX >= cubic[maxY].fY - cubic[minY].fY;
+ if (useX) {
+ sameSide1 = SkDSign(cubic[0].fX - cubic[1].fX) + SkDSign(cubic[3].fX - cubic[1].fX);
+ sameSide2 = SkDSign(cubic[0].fX - cubic[2].fX) + SkDSign(cubic[3].fX - cubic[2].fX);
+ } else {
+ sameSide1 = SkDSign(cubic[0].fY - cubic[1].fY) + SkDSign(cubic[3].fY - cubic[1].fY);
+ sameSide2 = SkDSign(cubic[0].fY - cubic[2].fY) + SkDSign(cubic[3].fY - cubic[2].fY);
+ }
+ if (sameSide1 == sameSide2 && (sameSide1 & 3) != 2) {
+ return reductionLineCount(reduction);
+ }
+ double tValues[2];
+ int roots;
+ if (useX) {
+ roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
+ } else {
+ roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
+ }
+ for (int index = 0; index < roots; ++index) {
+ SkDPoint extrema;
+ extrema.fX = interp_cubic_coords(&cubic[0].fX, tValues[index]);
+ extrema.fY = interp_cubic_coords(&cubic[0].fY, tValues[index]);
+ // sameSide > 0 means mid is smaller than either [0] or [3], so replace smaller
+ int replace;
+ if (useX) {
+ if ((extrema.fX < cubic[0].fX) ^ (extrema.fX < cubic[3].fX)) {
+ continue;
+ }
+ replace = ((extrema.fX < cubic[0].fX) | (extrema.fX < cubic[3].fX))
+ ^ (cubic[0].fX < cubic[3].fX);
+ } else {
+ if ((extrema.fY < cubic[0].fY) ^ (extrema.fY < cubic[3].fY)) {
+ continue;
+ }
+ replace = ((extrema.fY < cubic[0].fY) | (extrema.fY < cubic[3].fY))
+ ^ (cubic[0].fY < cubic[3].fY);
+ }
+ reduction[replace] = extrema;
+ }
+ return reductionLineCount(reduction);
+}
+
+/* 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 SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics,
+ Style reduceStyle) {
+ 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].fX > cubic[index].fX) {
+ minX = index;
+ }
+ if (cubic[minY].fY > cubic[index].fY) {
+ minY = index;
+ }
+ if (cubic[maxX].fX < cubic[index].fX) {
+ maxX = index;
+ }
+ if (cubic[maxY].fY < cubic[index].fY) {
+ maxY = index;
+ }
+ }
+ for (index = 0; index < 4; ++index) {
+ double cx = cubic[index].fX;
+ double cy = cubic[index].fY;
+ double denom = SkTMax(fabs(cx), SkTMax(fabs(cy),
+ SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY))));
+ if (denom == 0) {
+ minXSet |= 1 << index;
+ minYSet |= 1 << index;
+ continue;
+ }
+ double inv = 1 / denom;
+ if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) {
+ minXSet |= 1 << index;
+ }
+ if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) {
+ minYSet |= 1 << index;
+ }
+ }
+ if (minXSet == 0xF) { // test for vertical line
+ if (minYSet == 0xF) { // return 1 if all four are coincident
+ return coincident_line(cubic, fCubic);
+ }
+ return vertical_line(cubic, reduceStyle, fCubic);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(cubic, reduceStyle, fCubic);
+ }
+ int result = check_linear(cubic, reduceStyle, minX, maxX, minY, maxY, fCubic);
+ if (result) {
+ return result;
+ }
+ if (allowQuadratics == SkReduceOrder::kAllow_Quadratics
+ && (result = check_quadratic(cubic, fCubic))) {
+ return result;
+ }
+ fCubic = cubic;
+ return 4;
+}
+
+SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) {
+ SkDQuad quad;
+ quad.set(a);
+ SkReduceOrder reducer;
+ int order = reducer.reduce(quad, kFill_Style);
+ if (order == 2) { // quad became line
+ for (int index = 0; index < order; ++index) {
+ *reducePts++ = reducer.fLine[index].asSkPoint();
+ }
+ }
+ return SkPathOpsPointsToVerb(order - 1);
+}
+
+SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkReduceOrder reducer;
+ int order = reducer.reduce(cubic, kAllow_Quadratics, kFill_Style);
+ if (order == 2 || order == 3) { // cubic became line or quad
+ for (int index = 0; index < order; ++index) {
+ *reducePts++ = reducer.fQuad[index].asSkPoint();
+ }
+ }
+ return SkPathOpsPointsToVerb(order - 1);
+}
diff --git a/pathops/SkReduceOrder.h b/pathops/SkReduceOrder.h
new file mode 100644
index 00000000..c167951f
--- /dev/null
+++ b/pathops/SkReduceOrder.h
@@ -0,0 +1,38 @@
+/*
+ * 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 SkReduceOrder_DEFINED
+#define SkReduceOrder_DEFINED
+
+#include "SkPath.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkTArray.h"
+
+union SkReduceOrder {
+ enum Quadratics {
+ kNo_Quadratics,
+ kAllow_Quadratics
+ };
+ enum Style {
+ kStroke_Style,
+ kFill_Style
+ };
+
+ int reduce(const SkDCubic& cubic, Quadratics, Style);
+ int reduce(const SkDLine& line);
+ int reduce(const SkDQuad& quad, Style);
+
+ static SkPath::Verb Cubic(const SkPoint pts[4], SkPoint* reducePts);
+ static SkPath::Verb Quad(const SkPoint pts[3], SkPoint* reducePts);
+
+ SkDLine fLine;
+ SkDQuad fQuad;
+ SkDCubic fCubic;
+};
+
+#endif
diff --git a/pathops/main.cpp b/pathops/main.cpp
new file mode 100644
index 00000000..65479c25
--- /dev/null
+++ b/pathops/main.cpp
@@ -0,0 +1,16 @@
+/*
+ * 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"
+
+using namespace skiatest;
+
+int main(int /*argc*/, char** /*argv*/) {
+ Test tester;
+ tester.run();
+ return 0;
+}
diff --git a/pdf/SkPDFCatalog.cpp b/pdf/SkPDFCatalog.cpp
new file mode 100644
index 00000000..8690b3ea
--- /dev/null
+++ b/pdf/SkPDFCatalog.cpp
@@ -0,0 +1,215 @@
+
+/*
+ * Copyright 2010 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 "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+
+SkPDFCatalog::SkPDFCatalog(SkPDFDocument::Flags flags)
+ : fFirstPageCount(0),
+ fNextObjNum(1),
+ fNextFirstPageObjNum(0),
+ fDocumentFlags(flags) {
+}
+
+SkPDFCatalog::~SkPDFCatalog() {
+ fSubstituteResourcesRemaining.safeUnrefAll();
+ fSubstituteResourcesFirstPage.safeUnrefAll();
+}
+
+SkPDFObject* SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) {
+ if (findObjectIndex(obj) != -1) { // object already added
+ return obj;
+ }
+ SkASSERT(fNextFirstPageObjNum == 0);
+ if (onFirstPage) {
+ fFirstPageCount++;
+ }
+
+ struct Rec newEntry(obj, onFirstPage);
+ fCatalog.append(1, &newEntry);
+ return obj;
+}
+
+size_t SkPDFCatalog::setFileOffset(SkPDFObject* obj, off_t offset) {
+ int objIndex = assignObjNum(obj) - 1;
+ SkASSERT(fCatalog[objIndex].fObjNumAssigned);
+ SkASSERT(fCatalog[objIndex].fFileOffset == 0);
+ fCatalog[objIndex].fFileOffset = offset;
+
+ return getSubstituteObject(obj)->getOutputSize(this, true);
+}
+
+void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) {
+ stream->writeDecAsText(assignObjNum(obj));
+ stream->writeText(" 0"); // Generation number is always 0.
+}
+
+size_t SkPDFCatalog::getObjectNumberSize(SkPDFObject* obj) {
+ SkDynamicMemoryWStream buffer;
+ emitObjectNumber(&buffer, obj);
+ return buffer.getOffset();
+}
+
+int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const {
+ for (int i = 0; i < fCatalog.count(); i++) {
+ if (fCatalog[i].fObject == obj) {
+ return i;
+ }
+ }
+ // If it's not in the main array, check if it's a substitute object.
+ for (int i = 0; i < fSubstituteMap.count(); ++i) {
+ if (fSubstituteMap[i].fSubstitute == obj) {
+ return findObjectIndex(fSubstituteMap[i].fOriginal);
+ }
+ }
+ return -1;
+}
+
+int SkPDFCatalog::assignObjNum(SkPDFObject* obj) {
+ int pos = findObjectIndex(obj);
+ // If this assert fails, it means you probably forgot to add an object
+ // to the resource list.
+ SkASSERT(pos >= 0);
+ uint32_t currentIndex = pos;
+ if (fCatalog[currentIndex].fObjNumAssigned) {
+ return currentIndex + 1;
+ }
+
+ // First assignment.
+ if (fNextFirstPageObjNum == 0) {
+ fNextFirstPageObjNum = fCatalog.count() - fFirstPageCount + 1;
+ }
+
+ uint32_t objNum;
+ if (fCatalog[currentIndex].fOnFirstPage) {
+ objNum = fNextFirstPageObjNum;
+ fNextFirstPageObjNum++;
+ } else {
+ objNum = fNextObjNum;
+ fNextObjNum++;
+ }
+
+ // When we assign an object an object number, we put it in that array
+ // offset (minus 1 because object number 0 is reserved).
+ SkASSERT(!fCatalog[objNum - 1].fObjNumAssigned);
+ if (objNum - 1 != currentIndex) {
+ SkTSwap(fCatalog[objNum - 1], fCatalog[currentIndex]);
+ }
+ fCatalog[objNum - 1].fObjNumAssigned = true;
+ return objNum;
+}
+
+int32_t SkPDFCatalog::emitXrefTable(SkWStream* stream, bool firstPage) {
+ int first = -1;
+ int last = fCatalog.count() - 1;
+ // TODO(vandebo): Support linearized format.
+ // int last = fCatalog.count() - fFirstPageCount - 1;
+ // if (firstPage) {
+ // first = fCatalog.count() - fFirstPageCount;
+ // last = fCatalog.count() - 1;
+ // }
+
+ stream->writeText("xref\n");
+ stream->writeDecAsText(first + 1);
+ stream->writeText(" ");
+ stream->writeDecAsText(last - first + 1);
+ stream->writeText("\n");
+
+ if (first == -1) {
+ stream->writeText("0000000000 65535 f \n");
+ first++;
+ }
+ for (int i = first; i <= last; i++) {
+ // For 32 bits platforms, the maximum offset has to fit within off_t
+ // which is a 32 bits signed integer on these platforms.
+ SkDEBUGCODE(static const off_t kMaxOff = SK_MaxS32;)
+ SkASSERT(fCatalog[i].fFileOffset > 0);
+ SkASSERT(fCatalog[i].fFileOffset < kMaxOff);
+ stream->writeBigDecAsText(fCatalog[i].fFileOffset, 10);
+ stream->writeText(" 00000 n \n");
+ }
+
+ return fCatalog.count() + 1;
+}
+
+void SkPDFCatalog::setSubstitute(SkPDFObject* original,
+ SkPDFObject* substitute) {
+#if defined(SK_DEBUG)
+ // Sanity check: is the original already in substitute list?
+ for (int i = 0; i < fSubstituteMap.count(); ++i) {
+ if (original == fSubstituteMap[i].fSubstitute ||
+ original == fSubstituteMap[i].fOriginal) {
+ SkASSERT(false);
+ return;
+ }
+ }
+#endif
+ // Check if the original is on first page.
+ bool onFirstPage = false;
+ for (int i = 0; i < fCatalog.count(); ++i) {
+ if (fCatalog[i].fObject == original) {
+ onFirstPage = fCatalog[i].fOnFirstPage;
+ break;
+ }
+#if defined(SK_DEBUG)
+ if (i == fCatalog.count() - 1) {
+ SkASSERT(false); // original not in catalog
+ return;
+ }
+#endif
+ }
+
+ SubstituteMapping newMapping(original, substitute);
+ fSubstituteMap.append(1, &newMapping);
+
+ // Add resource objects of substitute object to catalog.
+ SkTSet<SkPDFObject*>* targetSet = getSubstituteList(onFirstPage);
+ SkTSet<SkPDFObject*> newResourceObjects;
+ newMapping.fSubstitute->getResources(*targetSet, &newResourceObjects);
+ for (int i = 0; i < newResourceObjects.count(); ++i) {
+ addObject(newResourceObjects[i], onFirstPage);
+ }
+ // mergeInto returns the number of duplicates.
+ // If there are duplicates, there is a bug and we mess ref counting.
+ SkDEBUGCODE(int duplicates =) targetSet->mergeInto(newResourceObjects);
+ SkASSERT(duplicates == 0);
+}
+
+SkPDFObject* SkPDFCatalog::getSubstituteObject(SkPDFObject* object) {
+ for (int i = 0; i < fSubstituteMap.count(); ++i) {
+ if (object == fSubstituteMap[i].fOriginal) {
+ return fSubstituteMap[i].fSubstitute;
+ }
+ }
+ return object;
+}
+
+off_t SkPDFCatalog::setSubstituteResourcesOffsets(off_t fileOffset,
+ bool firstPage) {
+ SkTSet<SkPDFObject*>* targetSet = getSubstituteList(firstPage);
+ off_t offsetSum = fileOffset;
+ for (int i = 0; i < targetSet->count(); ++i) {
+ offsetSum += setFileOffset((*targetSet)[i], offsetSum);
+ }
+ return offsetSum - fileOffset;
+}
+
+void SkPDFCatalog::emitSubstituteResources(SkWStream *stream, bool firstPage) {
+ SkTSet<SkPDFObject*>* targetSet = getSubstituteList(firstPage);
+ for (int i = 0; i < targetSet->count(); ++i) {
+ (*targetSet)[i]->emit(stream, this, true);
+ }
+}
+
+SkTSet<SkPDFObject*>* SkPDFCatalog::getSubstituteList(bool firstPage) {
+ return firstPage ? &fSubstituteResourcesFirstPage :
+ &fSubstituteResourcesRemaining;
+}
diff --git a/pdf/SkPDFCatalog.h b/pdf/SkPDFCatalog.h
new file mode 100644
index 00000000..c7c6d6e2
--- /dev/null
+++ b/pdf/SkPDFCatalog.h
@@ -0,0 +1,137 @@
+
+/*
+ * Copyright 2010 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 SkPDFCatalog_DEFINED
+#define SkPDFCatalog_DEFINED
+
+#include <sys/types.h>
+
+#include "SkPDFDocument.h"
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+/** \class SkPDFCatalog
+
+ The PDF catalog manages object numbers and file offsets. It is used
+ to create the PDF cross reference table.
+*/
+class SkPDFCatalog {
+public:
+ /** Create a PDF catalog.
+ */
+ explicit SkPDFCatalog(SkPDFDocument::Flags flags);
+ ~SkPDFCatalog();
+
+ /** Add the passed object to the catalog. Refs obj.
+ * @param obj The object to add.
+ * @param onFirstPage Is the object on the first page.
+ * @return The obj argument is returned.
+ */
+ SkPDFObject* addObject(SkPDFObject* obj, bool onFirstPage);
+
+ /** Inform the catalog of the object's position in the final stream.
+ * The object should already have been added to the catalog. Returns
+ * the object's size.
+ * @param obj The object to add.
+ * @param offset The byte offset in the output stream of this object.
+ */
+ size_t setFileOffset(SkPDFObject* obj, off_t offset);
+
+ /** Output the object number for the passed object.
+ * @param obj The object of interest.
+ * @param stream The writable output stream to send the output to.
+ */
+ void emitObjectNumber(SkWStream* stream, SkPDFObject* obj);
+
+ /** Return the number of bytes that would be emitted for the passed
+ * object's object number.
+ * @param obj The object of interest
+ */
+ size_t getObjectNumberSize(SkPDFObject* obj);
+
+ /** Return the document flags in effect for this catalog/document.
+ */
+ SkPDFDocument::Flags getDocumentFlags() const { return fDocumentFlags; }
+
+ /** Output the cross reference table for objects in the catalog.
+ * Returns the total number of objects.
+ * @param stream The writable output stream to send the output to.
+ * @param firstPage If true, include first page objects only, otherwise
+ * include all objects not on the first page.
+ */
+ int32_t emitXrefTable(SkWStream* stream, bool firstPage);
+
+ /** Set substitute object for the passed object.
+ */
+ void setSubstitute(SkPDFObject* original, SkPDFObject* substitute);
+
+ /** Find and return any substitute object set for the passed object. If
+ * there is none, return the passed object.
+ */
+ SkPDFObject* getSubstituteObject(SkPDFObject* object);
+
+ /** Set file offsets for the resources of substitute objects.
+ * @param fileOffset Accumulated offset of current document.
+ * @param firstPage Indicate whether this is for the first page only.
+ * @return Total size of resources of substitute objects.
+ */
+ off_t setSubstituteResourcesOffsets(off_t fileOffset, bool firstPage);
+
+ /** Emit the resources of substitute objects.
+ */
+ void emitSubstituteResources(SkWStream* stream, bool firstPage);
+
+private:
+ struct Rec {
+ Rec(SkPDFObject* object, bool onFirstPage)
+ : fObject(object),
+ fFileOffset(0),
+ fObjNumAssigned(false),
+ fOnFirstPage(onFirstPage) {
+ }
+ SkPDFObject* fObject;
+ off_t fFileOffset;
+ bool fObjNumAssigned;
+ bool fOnFirstPage;
+ };
+
+ struct SubstituteMapping {
+ SubstituteMapping(SkPDFObject* original, SkPDFObject* substitute)
+ : fOriginal(original), fSubstitute(substitute) {
+ }
+ SkPDFObject* fOriginal;
+ SkPDFObject* fSubstitute;
+ };
+
+ // TODO(vandebo): Make this a hash if it's a performance problem.
+ SkTDArray<struct Rec> fCatalog;
+
+ // TODO(arthurhsu): Make this a hash if it's a performance problem.
+ SkTDArray<SubstituteMapping> fSubstituteMap;
+ SkTSet<SkPDFObject*> fSubstituteResourcesFirstPage;
+ SkTSet<SkPDFObject*> fSubstituteResourcesRemaining;
+
+ // Number of objects on the first page.
+ uint32_t fFirstPageCount;
+ // Next object number to assign (on page > 1).
+ uint32_t fNextObjNum;
+ // Next object number to assign on the first page.
+ uint32_t fNextFirstPageObjNum;
+
+ SkPDFDocument::Flags fDocumentFlags;
+
+ int findObjectIndex(SkPDFObject* obj) const;
+
+ int assignObjNum(SkPDFObject* obj);
+
+ SkTSet<SkPDFObject*>* getSubstituteList(bool firstPage);
+};
+
+#endif
diff --git a/pdf/SkPDFDevice.cpp b/pdf/SkPDFDevice.cpp
new file mode 100644
index 00000000..410b0499
--- /dev/null
+++ b/pdf/SkPDFDevice.cpp
@@ -0,0 +1,1929 @@
+/*
+ * 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 "SkPDFDevice.h"
+
+#include "SkAnnotation.h"
+#include "SkColor.h"
+#include "SkClipStack.h"
+#include "SkData.h"
+#include "SkDraw.h"
+#include "SkFontHost.h"
+#include "SkGlyphCache.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathOps.h"
+#include "SkPDFFont.h"
+#include "SkPDFFormXObject.h"
+#include "SkPDFGraphicState.h"
+#include "SkPDFImage.h"
+#include "SkPDFResourceDict.h"
+#include "SkPDFShader.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkPDFUtils.h"
+#include "SkRect.h"
+#include "SkString.h"
+#include "SkTextFormatParams.h"
+#include "SkTemplates.h"
+#include "SkTypefacePriv.h"
+#include "SkTSet.h"
+
+// Utility functions
+
+static void emit_pdf_color(SkColor color, SkWStream* result) {
+ SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
+ SkScalar colorMax = SkIntToScalar(0xFF);
+ SkPDFScalar::Append(
+ SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result);
+ result->writeText(" ");
+ SkPDFScalar::Append(
+ SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result);
+ result->writeText(" ");
+ SkPDFScalar::Append(
+ SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result);
+ result->writeText(" ");
+}
+
+static SkPaint calculate_text_paint(const SkPaint& paint) {
+ SkPaint result = paint;
+ if (result.isFakeBoldText()) {
+ SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
+ kStdFakeBoldInterpKeys,
+ kStdFakeBoldInterpValues,
+ kStdFakeBoldInterpLength);
+ SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
+ if (result.getStyle() == SkPaint::kFill_Style) {
+ result.setStyle(SkPaint::kStrokeAndFill_Style);
+ } else {
+ width += result.getStrokeWidth();
+ }
+ result.setStrokeWidth(width);
+ }
+ return result;
+}
+
+// Stolen from measure_text in SkDraw.cpp and then tweaked.
+static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
+ const uint16_t* glyphs, size_t len,
+ SkScalar* x, SkScalar* y) {
+ if (paint.getTextAlign() == SkPaint::kLeft_Align) {
+ return;
+ }
+
+ SkMatrix ident;
+ ident.reset();
+ SkAutoGlyphCache autoCache(paint, NULL, &ident);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ const char* start = reinterpret_cast<const char*>(glyphs);
+ const char* stop = reinterpret_cast<const char*>(glyphs + len);
+ SkFixed xAdv = 0, yAdv = 0;
+
+ // TODO(vandebo): This probably needs to take kerning into account.
+ while (start < stop) {
+ const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
+ xAdv += glyph.fAdvanceX;
+ yAdv += glyph.fAdvanceY;
+ };
+ if (paint.getTextAlign() == SkPaint::kLeft_Align) {
+ return;
+ }
+
+ SkScalar xAdj = SkFixedToScalar(xAdv);
+ SkScalar yAdj = SkFixedToScalar(yAdv);
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ xAdj = SkScalarHalf(xAdj);
+ yAdj = SkScalarHalf(yAdj);
+ }
+ *x = *x - xAdj;
+ *y = *y - yAdj;
+}
+
+static size_t max_glyphid_for_typeface(SkTypeface* typeface) {
+ SkAutoResolveDefaultTypeface autoResolve(typeface);
+ typeface = autoResolve.get();
+ return typeface->countGlyphs() - 1;
+}
+
+typedef SkAutoSTMalloc<128, uint16_t> SkGlyphStorage;
+
+static size_t force_glyph_encoding(const SkPaint& paint, const void* text,
+ size_t len, SkGlyphStorage* storage,
+ uint16_t** glyphIDs) {
+ // Make sure we have a glyph id encoding.
+ if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
+ size_t numGlyphs = paint.textToGlyphs(text, len, NULL);
+ storage->reset(numGlyphs);
+ paint.textToGlyphs(text, len, storage->get());
+ *glyphIDs = storage->get();
+ return numGlyphs;
+ }
+
+ // For user supplied glyph ids we need to validate them.
+ SkASSERT((len & 1) == 0);
+ size_t numGlyphs = len / 2;
+ const uint16_t* input =
+ reinterpret_cast<uint16_t*>(const_cast<void*>((text)));
+
+ int maxGlyphID = max_glyphid_for_typeface(paint.getTypeface());
+ size_t validated;
+ for (validated = 0; validated < numGlyphs; ++validated) {
+ if (input[validated] > maxGlyphID) {
+ break;
+ }
+ }
+ if (validated >= numGlyphs) {
+ *glyphIDs = reinterpret_cast<uint16_t*>(const_cast<void*>((text)));
+ return numGlyphs;
+ }
+
+ // Silently drop anything out of range.
+ storage->reset(numGlyphs);
+ if (validated > 0) {
+ memcpy(storage->get(), input, validated * sizeof(uint16_t));
+ }
+
+ for (size_t i = validated; i < numGlyphs; ++i) {
+ storage->get()[i] = input[i];
+ if (input[i] > maxGlyphID) {
+ storage->get()[i] = 0;
+ }
+ }
+ *glyphIDs = storage->get();
+ return numGlyphs;
+}
+
+static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX,
+ SkWStream* content) {
+ // Flip the text about the x-axis to account for origin swap and include
+ // the passed parameters.
+ content->writeText("1 0 ");
+ SkPDFScalar::Append(0 - textSkewX, content);
+ content->writeText(" -1 ");
+ SkPDFScalar::Append(x, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(y, content);
+ content->writeText(" Tm\n");
+}
+
+// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the
+// later being our representation of an object in the PDF file.
+struct GraphicStateEntry {
+ GraphicStateEntry();
+
+ // Compare the fields we care about when setting up a new content entry.
+ bool compareInitialState(const GraphicStateEntry& b);
+
+ SkMatrix fMatrix;
+ // We can't do set operations on Paths, though PDF natively supports
+ // intersect. If the clip stack does anything other than intersect,
+ // we have to fall back to the region. Treat fClipStack as authoritative.
+ // See http://code.google.com/p/skia/issues/detail?id=221
+ SkClipStack fClipStack;
+ SkRegion fClipRegion;
+
+ // When emitting the content entry, we will ensure the graphic state
+ // is set to these values first.
+ SkColor fColor;
+ SkScalar fTextScaleX; // Zero means we don't care what the value is.
+ SkPaint::Style fTextFill; // Only if TextScaleX is non-zero.
+ int fShaderIndex;
+ int fGraphicStateIndex;
+
+ // We may change the font (i.e. for Type1 support) within a
+ // ContentEntry. This is the one currently in effect, or NULL if none.
+ SkPDFFont* fFont;
+ // In PDF, text size has no default value. It is only valid if fFont is
+ // not NULL.
+ SkScalar fTextSize;
+};
+
+GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK),
+ fTextScaleX(SK_Scalar1),
+ fTextFill(SkPaint::kFill_Style),
+ fShaderIndex(-1),
+ fGraphicStateIndex(-1),
+ fFont(NULL),
+ fTextSize(SK_ScalarNaN) {
+ fMatrix.reset();
+}
+
+bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) {
+ return fColor == b.fColor &&
+ fShaderIndex == b.fShaderIndex &&
+ fGraphicStateIndex == b.fGraphicStateIndex &&
+ fMatrix == b.fMatrix &&
+ fClipStack == b.fClipStack &&
+ (fTextScaleX == 0 ||
+ b.fTextScaleX == 0 ||
+ (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill));
+}
+
+class GraphicStackState {
+public:
+ GraphicStackState(const SkClipStack& existingClipStack,
+ const SkRegion& existingClipRegion,
+ SkWStream* contentStream)
+ : fStackDepth(0),
+ fContentStream(contentStream) {
+ fEntries[0].fClipStack = existingClipStack;
+ fEntries[0].fClipRegion = existingClipRegion;
+ }
+
+ void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion,
+ const SkPoint& translation);
+ void updateMatrix(const SkMatrix& matrix);
+ void updateDrawingState(const GraphicStateEntry& state);
+
+ void drainStack();
+
+private:
+ void push();
+ void pop();
+ GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; }
+
+ // Conservative limit on save depth, see impl. notes in PDF 1.4 spec.
+ static const int kMaxStackDepth = 12;
+ GraphicStateEntry fEntries[kMaxStackDepth + 1];
+ int fStackDepth;
+ SkWStream* fContentStream;
+};
+
+void GraphicStackState::drainStack() {
+ while (fStackDepth) {
+ pop();
+ }
+}
+
+void GraphicStackState::push() {
+ SkASSERT(fStackDepth < kMaxStackDepth);
+ fContentStream->writeText("q\n");
+ fStackDepth++;
+ fEntries[fStackDepth] = fEntries[fStackDepth - 1];
+}
+
+void GraphicStackState::pop() {
+ SkASSERT(fStackDepth > 0);
+ fContentStream->writeText("Q\n");
+ fStackDepth--;
+}
+
+// This function initializes iter to be an iterator on the "stack" argument
+// and then skips over the leading entries as specified in prefix. It requires
+// and asserts that "prefix" will be a prefix to "stack."
+static void skip_clip_stack_prefix(const SkClipStack& prefix,
+ const SkClipStack& stack,
+ SkClipStack::Iter* iter) {
+ SkClipStack::B2TIter prefixIter(prefix);
+ iter->reset(stack, SkClipStack::Iter::kBottom_IterStart);
+
+ const SkClipStack::Element* prefixEntry;
+ const SkClipStack::Element* iterEntry;
+
+ for (prefixEntry = prefixIter.next(); prefixEntry;
+ prefixEntry = prefixIter.next()) {
+ iterEntry = iter->next();
+ SkASSERT(iterEntry);
+ // Because of SkClipStack does internal intersection, the last clip
+ // entry may differ.
+ if (*prefixEntry != *iterEntry) {
+ 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();
+ break;
+ }
+ }
+
+ SkASSERT(prefixEntry == NULL);
+}
+
+static void emit_clip(SkPath* clipPath, SkRect* clipRect,
+ SkWStream* contentStream) {
+ SkASSERT(clipPath || clipRect);
+
+ SkPath::FillType clipFill;
+ if (clipPath) {
+ SkPDFUtils::EmitPath(*clipPath, SkPaint::kFill_Style, contentStream);
+ clipFill = clipPath->getFillType();
+ } else {
+ SkPDFUtils::AppendRectangle(*clipRect, contentStream);
+ clipFill = SkPath::kWinding_FillType;
+ }
+
+ NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false);
+ NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false);
+ if (clipFill == SkPath::kEvenOdd_FillType) {
+ contentStream->writeText("W* n\n");
+ } else {
+ contentStream->writeText("W n\n");
+ }
+}
+
+#ifdef SK_PDF_USE_PATHOPS
+/* Calculate an inverted path's equivalent non-inverted path, given the
+ * canvas bounds.
+ * outPath may alias with invPath (since this is supported by PathOps).
+ */
+static bool calculate_inverse_path(const SkRect& bounds, const SkPath& invPath,
+ SkPath* outPath) {
+ SkASSERT(invPath.isInverseFillType());
+
+ SkPath clipPath;
+ clipPath.addRect(bounds);
+
+ return Op(clipPath, invPath, kIntersect_PathOp, outPath);
+}
+
+// Sanity check the numerical values of the SkRegion ops and PathOps ops
+// enums so region_op_to_pathops_op can do a straight passthrough cast.
+// If these are failing, it may be necessary to make region_op_to_pathops_op
+// do more.
+SK_COMPILE_ASSERT(SkRegion::kDifference_Op == (int)kDifference_PathOp,
+ region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kIntersect_Op == (int)kIntersect_PathOp,
+ region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kUnion_Op == (int)kUnion_PathOp,
+ region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kXOR_Op == (int)kXOR_PathOp,
+ region_pathop_mismatch);
+SK_COMPILE_ASSERT(SkRegion::kReverseDifference_Op ==
+ (int)kReverseDifference_PathOp,
+ region_pathop_mismatch);
+
+static SkPathOp region_op_to_pathops_op(SkRegion::Op op) {
+ SkASSERT(op >= 0);
+ SkASSERT(op <= SkRegion::kReverseDifference_Op);
+ return (SkPathOp)op;
+}
+
+/* Uses Path Ops to calculate a vector SkPath clip from a clip stack.
+ * Returns true if successful, or false if not successful.
+ * If successful, the resulting clip is stored in outClipPath.
+ * If not successful, outClipPath is undefined, and a fallback method
+ * should be used.
+ */
+static bool get_clip_stack_path(const SkMatrix& transform,
+ const SkClipStack& clipStack,
+ const SkRegion& clipRegion,
+ SkPath* outClipPath) {
+ outClipPath->reset();
+ outClipPath->setFillType(SkPath::kInverseWinding_FillType);
+
+ const SkClipStack::Element* clipEntry;
+ SkClipStack::Iter iter;
+ iter.reset(clipStack, SkClipStack::Iter::kBottom_IterStart);
+ for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
+ SkPath entryPath;
+ if (SkClipStack::Element::kEmpty_Type == clipEntry->getType()) {
+ outClipPath->reset();
+ outClipPath->setFillType(SkPath::kInverseWinding_FillType);
+ continue;
+ } else if (SkClipStack::Element::kRect_Type == clipEntry->getType()) {
+ entryPath.addRect(clipEntry->getRect());
+ } else if (SkClipStack::Element::kPath_Type == clipEntry->getType()) {
+ entryPath = clipEntry->getPath();
+ }
+ entryPath.transform(transform);
+
+ if (SkRegion::kReplace_Op == clipEntry->getOp()) {
+ *outClipPath = entryPath;
+ } else {
+ SkPathOp op = region_op_to_pathops_op(clipEntry->getOp());
+ if (!Op(*outClipPath, entryPath, op, outClipPath)) {
+ return false;
+ }
+ }
+ }
+
+ if (outClipPath->isInverseFillType()) {
+ // The bounds are slightly outset to ensure this is correct in the
+ // face of floating-point accuracy and possible SkRegion bitmap
+ // approximations.
+ SkRect clipBounds = SkRect::Make(clipRegion.getBounds());
+ clipBounds.outset(SK_Scalar1, SK_Scalar1);
+ if (!calculate_inverse_path(clipBounds, *outClipPath, outClipPath)) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+// TODO(vandebo): Take advantage of SkClipStack::getSaveCount(), the PDF
+// graphic state stack, and the fact that we can know all the clips used
+// on the page to optimize this.
+void GraphicStackState::updateClip(const SkClipStack& clipStack,
+ const SkRegion& clipRegion,
+ const SkPoint& translation) {
+ if (clipStack == currentEntry()->fClipStack) {
+ return;
+ }
+
+ while (fStackDepth > 0) {
+ pop();
+ if (clipStack == currentEntry()->fClipStack) {
+ return;
+ }
+ }
+ push();
+
+ currentEntry()->fClipStack = clipStack;
+ currentEntry()->fClipRegion = clipRegion;
+
+ SkMatrix transform;
+ transform.setTranslate(translation.fX, translation.fY);
+
+#ifdef SK_PDF_USE_PATHOPS
+ SkPath clipPath;
+ if (get_clip_stack_path(transform, clipStack, clipRegion, &clipPath)) {
+ emit_clip(&clipPath, NULL, fContentStream);
+ return;
+ }
+#endif
+ // gsState->initialEntry()->fClipStack/Region specifies the clip that has
+ // already been applied. (If this is a top level device, then it specifies
+ // a clip to the content area. If this is a layer, then it specifies
+ // the clip in effect when the layer was created.) There's no need to
+ // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the
+ // initial clip on the parent layer. (This means there's a bug if the user
+ // expands the clip and then uses any xfer mode that uses dst:
+ // http://code.google.com/p/skia/issues/detail?id=228 )
+ SkClipStack::Iter iter;
+ skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
+
+ // 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::Element* clipEntry;
+ for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
+ if (clipEntry->getOp() != SkRegion::kIntersect_Op || clipEntry->isInverseFilled()) {
+ needRegion = true;
+ break;
+ }
+ }
+
+ if (needRegion) {
+ SkPath clipPath;
+ SkAssertResult(clipRegion.getBoundaryPath(&clipPath));
+ emit_clip(&clipPath, NULL, fContentStream);
+ } else {
+ skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter);
+ const SkClipStack::Element* clipEntry;
+ for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) {
+ 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);
+ }
+ }
+ }
+}
+
+void GraphicStackState::updateMatrix(const SkMatrix& matrix) {
+ if (matrix == currentEntry()->fMatrix) {
+ return;
+ }
+
+ if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) {
+ SkASSERT(fStackDepth > 0);
+ SkASSERT(fEntries[fStackDepth].fClipStack ==
+ fEntries[fStackDepth -1].fClipStack);
+ pop();
+
+ SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask);
+ }
+ if (matrix.getType() == SkMatrix::kIdentity_Mask) {
+ return;
+ }
+
+ push();
+ SkPDFUtils::AppendTransform(matrix, fContentStream);
+ currentEntry()->fMatrix = matrix;
+}
+
+void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) {
+ // PDF treats a shader as a color, so we only set one or the other.
+ if (state.fShaderIndex >= 0) {
+ if (state.fShaderIndex != currentEntry()->fShaderIndex) {
+ SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
+ currentEntry()->fShaderIndex = state.fShaderIndex;
+ }
+ } else {
+ if (state.fColor != currentEntry()->fColor ||
+ currentEntry()->fShaderIndex >= 0) {
+ emit_pdf_color(state.fColor, fContentStream);
+ fContentStream->writeText("RG ");
+ emit_pdf_color(state.fColor, fContentStream);
+ fContentStream->writeText("rg\n");
+ currentEntry()->fColor = state.fColor;
+ currentEntry()->fShaderIndex = -1;
+ }
+ }
+
+ if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
+ SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
+ currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
+ }
+
+ if (state.fTextScaleX) {
+ if (state.fTextScaleX != currentEntry()->fTextScaleX) {
+ SkScalar pdfScale = SkScalarMul(state.fTextScaleX,
+ SkIntToScalar(100));
+ SkPDFScalar::Append(pdfScale, fContentStream);
+ fContentStream->writeText(" Tz\n");
+ currentEntry()->fTextScaleX = state.fTextScaleX;
+ }
+ if (state.fTextFill != currentEntry()->fTextFill) {
+ SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
+ SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
+ enum_must_match_value);
+ SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
+ enum_must_match_value);
+ fContentStream->writeDecAsText(state.fTextFill);
+ fContentStream->writeText(" Tr\n");
+ currentEntry()->fTextFill = state.fTextFill;
+ }
+ }
+}
+
+SkDevice* SkPDFDevice::onCreateCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque,
+ Usage usage) {
+ SkMatrix initialTransform;
+ initialTransform.reset();
+ SkISize size = SkISize::Make(width, height);
+ return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform));
+}
+
+
+struct ContentEntry {
+ GraphicStateEntry fState;
+ SkDynamicMemoryWStream fContent;
+ SkTScopedPtr<ContentEntry> fNext;
+
+ // If the stack is too deep we could get Stack Overflow.
+ // So we manually destruct the object.
+ ~ContentEntry() {
+ ContentEntry* val = fNext.release();
+ while (val != NULL) {
+ ContentEntry* valNext = val->fNext.release();
+ // When the destructor is called, fNext is NULL and exits.
+ delete val;
+ val = valNext;
+ }
+ }
+};
+
+// A helper class to automatically finish a ContentEntry at the end of a
+// drawing method and maintain the state needed between set up and finish.
+class ScopedContentEntry {
+public:
+ ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw,
+ const SkPaint& paint, bool hasText = false)
+ : fDevice(device),
+ fContentEntry(NULL),
+ fXfermode(SkXfermode::kSrcOver_Mode) {
+ init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText);
+ }
+ ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack,
+ const SkRegion& clipRegion, const SkMatrix& matrix,
+ const SkPaint& paint, bool hasText = false)
+ : fDevice(device),
+ fContentEntry(NULL),
+ fXfermode(SkXfermode::kSrcOver_Mode) {
+ init(clipStack, clipRegion, matrix, paint, hasText);
+ }
+
+ ~ScopedContentEntry() {
+ if (fContentEntry) {
+ fDevice->finishContentEntry(fXfermode, fDstFormXObject);
+ }
+ SkSafeUnref(fDstFormXObject);
+ }
+
+ ContentEntry* entry() { return fContentEntry; }
+private:
+ SkPDFDevice* fDevice;
+ ContentEntry* fContentEntry;
+ SkXfermode::Mode fXfermode;
+ SkPDFFormXObject* fDstFormXObject;
+
+ void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
+ const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
+ fDstFormXObject = NULL;
+ if (paint.getXfermode()) {
+ paint.getXfermode()->asMode(&fXfermode);
+ }
+ fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion,
+ matrix, paint, hasText,
+ &fDstFormXObject);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+static inline SkBitmap makeContentBitmap(const SkISize& contentSize,
+ const SkMatrix* initialTransform) {
+ SkBitmap bitmap;
+ if (initialTransform) {
+ // Compute the size of the drawing area.
+ SkVector drawingSize;
+ SkMatrix inverse;
+ drawingSize.set(SkIntToScalar(contentSize.fWidth),
+ SkIntToScalar(contentSize.fHeight));
+ if (!initialTransform->invert(&inverse)) {
+ // This shouldn't happen, initial transform should be invertible.
+ SkASSERT(false);
+ inverse.reset();
+ }
+ inverse.mapVectors(&drawingSize, 1);
+ SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound();
+ bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth),
+ abs(size.fHeight));
+ } else {
+ bitmap.setConfig(SkBitmap::kNo_Config, abs(contentSize.fWidth),
+ abs(contentSize.fHeight));
+ }
+
+ return bitmap;
+}
+
+// TODO(vandebo) change pageSize to SkSize.
+SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
+ const SkMatrix& initialTransform)
+ : SkDevice(makeContentBitmap(contentSize, &initialTransform)),
+ fPageSize(pageSize),
+ fContentSize(contentSize),
+ fLastContentEntry(NULL),
+ fLastMarginContentEntry(NULL),
+ fClipStack(NULL),
+ fEncoder(NULL) {
+ // Skia generally uses the top left as the origin but PDF natively has the
+ // origin at the bottom left. This matrix corrects for that. But that only
+ // needs to be done once, we don't do it when layering.
+ fInitialTransform.setTranslate(0, SkIntToScalar(pageSize.fHeight));
+ fInitialTransform.preScale(SK_Scalar1, -SK_Scalar1);
+ fInitialTransform.preConcat(initialTransform);
+
+ SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height());
+ fExistingClipRegion.setRect(existingClip);
+
+ this->init();
+}
+
+// TODO(vandebo) change layerSize to SkSize.
+SkPDFDevice::SkPDFDevice(const SkISize& layerSize,
+ const SkClipStack& existingClipStack,
+ const SkRegion& existingClipRegion)
+ : SkDevice(makeContentBitmap(layerSize, NULL)),
+ fPageSize(layerSize),
+ fContentSize(layerSize),
+ fExistingClipStack(existingClipStack),
+ fExistingClipRegion(existingClipRegion),
+ fLastContentEntry(NULL),
+ fLastMarginContentEntry(NULL),
+ fClipStack(NULL) {
+ fInitialTransform.reset();
+ this->init();
+}
+
+SkPDFDevice::~SkPDFDevice() {
+ this->cleanUp(true);
+}
+
+void SkPDFDevice::init() {
+ fAnnotations = NULL;
+ fResourceDict = NULL;
+ fContentEntries.reset();
+ fLastContentEntry = NULL;
+ fMarginContentEntries.reset();
+ fLastMarginContentEntry = NULL;
+ fDrawingArea = kContent_DrawingArea;
+ if (fFontGlyphUsage == NULL) {
+ fFontGlyphUsage.reset(new SkPDFGlyphSetMap());
+ }
+}
+
+void SkPDFDevice::cleanUp(bool clearFontUsage) {
+ fGraphicStateResources.unrefAll();
+ fXObjectResources.unrefAll();
+ fFontResources.unrefAll();
+ fShaderResources.unrefAll();
+ SkSafeUnref(fAnnotations);
+ SkSafeUnref(fResourceDict);
+ fNamedDestinations.deleteAll();
+
+ if (clearFontUsage) {
+ fFontGlyphUsage->reset();
+ }
+}
+
+uint32_t SkPDFDevice::getDeviceCapabilities() {
+ return kVector_Capability;
+}
+
+void SkPDFDevice::clear(SkColor color) {
+ this->cleanUp(true);
+ this->init();
+
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setStyle(SkPaint::kFill_Style);
+ SkMatrix identity;
+ identity.reset();
+ ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
+ identity, paint);
+ internalDrawPaint(paint, content.entry());
+}
+
+void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
+ SkPaint newPaint = paint;
+ newPaint.setStyle(SkPaint::kFill_Style);
+ ScopedContentEntry content(this, d, newPaint);
+ internalDrawPaint(newPaint, content.entry());
+}
+
+void SkPDFDevice::internalDrawPaint(const SkPaint& paint,
+ ContentEntry* contentEntry) {
+ if (!contentEntry) {
+ return;
+ }
+ SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()),
+ SkIntToScalar(this->height()));
+ SkMatrix totalTransform = fInitialTransform;
+ totalTransform.preConcat(contentEntry->fState.fMatrix);
+ SkMatrix inverse;
+ if (!totalTransform.invert(&inverse)) {
+ return;
+ }
+ inverse.mapRect(&bbox);
+
+ SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent);
+ SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
+ &contentEntry->fContent);
+}
+
+void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
+ size_t count, const SkPoint* points,
+ const SkPaint& passedPaint) {
+ if (count == 0) {
+ return;
+ }
+
+ if (handlePointAnnotation(points, count, *d.fMatrix, passedPaint)) {
+ return;
+ }
+
+ // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
+ // We only use this when there's a path effect because of the overhead
+ // of multiple calls to setUpContentEntry it causes.
+ if (passedPaint.getPathEffect()) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+ SkDraw pointDraw(d);
+ pointDraw.fDevice = this;
+ pointDraw.drawPoints(mode, count, points, passedPaint, true);
+ return;
+ }
+
+ const SkPaint* paint = &passedPaint;
+ SkPaint modifiedPaint;
+
+ if (mode == SkCanvas::kPoints_PointMode &&
+ paint->getStrokeCap() != SkPaint::kRound_Cap) {
+ modifiedPaint = *paint;
+ paint = &modifiedPaint;
+ if (paint->getStrokeWidth()) {
+ // PDF won't draw a single point with square/butt caps because the
+ // orientation is ambiguous. Draw a rectangle instead.
+ modifiedPaint.setStyle(SkPaint::kFill_Style);
+ SkScalar strokeWidth = paint->getStrokeWidth();
+ SkScalar halfStroke = SkScalarHalf(strokeWidth);
+ for (size_t i = 0; i < count; i++) {
+ SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0);
+ r.inset(-halfStroke, -halfStroke);
+ drawRect(d, r, modifiedPaint);
+ }
+ return;
+ } else {
+ modifiedPaint.setStrokeCap(SkPaint::kRound_Cap);
+ }
+ }
+
+ ScopedContentEntry content(this, d, *paint);
+ if (!content.entry()) {
+ return;
+ }
+
+ switch (mode) {
+ case SkCanvas::kPolygon_PointMode:
+ SkPDFUtils::MoveTo(points[0].fX, points[0].fY,
+ &content.entry()->fContent);
+ for (size_t i = 1; i < count; i++) {
+ SkPDFUtils::AppendLine(points[i].fX, points[i].fY,
+ &content.entry()->fContent);
+ }
+ SkPDFUtils::StrokePath(&content.entry()->fContent);
+ break;
+ case SkCanvas::kLines_PointMode:
+ for (size_t i = 0; i < count/2; i++) {
+ SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY,
+ &content.entry()->fContent);
+ SkPDFUtils::AppendLine(points[i * 2 + 1].fX,
+ points[i * 2 + 1].fY,
+ &content.entry()->fContent);
+ SkPDFUtils::StrokePath(&content.entry()->fContent);
+ }
+ break;
+ case SkCanvas::kPoints_PointMode:
+ SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap);
+ for (size_t i = 0; i < count; i++) {
+ SkPDFUtils::MoveTo(points[i].fX, points[i].fY,
+ &content.entry()->fContent);
+ SkPDFUtils::ClosePath(&content.entry()->fContent);
+ SkPDFUtils::StrokePath(&content.entry()->fContent);
+ }
+ break;
+ default:
+ SkASSERT(false);
+ }
+}
+
+void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& rect,
+ const SkPaint& paint) {
+ SkRect r = rect;
+ r.sort();
+
+ if (paint.getPathEffect()) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+ SkPath path;
+ path.addRect(r);
+ drawPath(d, path, paint, NULL, true);
+ return;
+ }
+
+ if (handleRectAnnotation(r, *d.fMatrix, paint)) {
+ return;
+ }
+
+ ScopedContentEntry content(this, d, paint);
+ if (!content.entry()) {
+ return;
+ }
+ SkPDFUtils::AppendRectangle(r, &content.entry()->fContent);
+ SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
+ &content.entry()->fContent);
+}
+
+void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
+ const SkPaint& paint, const SkMatrix* prePathMatrix,
+ bool pathIsMutable) {
+ SkPath modifiedPath;
+ SkPath* pathPtr = const_cast<SkPath*>(&origPath);
+
+ SkMatrix matrix = *d.fMatrix;
+ if (prePathMatrix) {
+ if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+ if (!pathIsMutable) {
+ pathPtr = &modifiedPath;
+ pathIsMutable = true;
+ }
+ origPath.transform(*prePathMatrix, pathPtr);
+ } else {
+ if (!matrix.preConcat(*prePathMatrix)) {
+ return;
+ }
+ }
+ }
+
+ if (paint.getPathEffect()) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+ if (!pathIsMutable) {
+ pathPtr = &modifiedPath;
+ pathIsMutable = true;
+ }
+ bool fill = paint.getFillPath(origPath, pathPtr);
+
+ SkPaint noEffectPaint(paint);
+ noEffectPaint.setPathEffect(NULL);
+ if (fill) {
+ noEffectPaint.setStyle(SkPaint::kFill_Style);
+ } else {
+ noEffectPaint.setStyle(SkPaint::kStroke_Style);
+ noEffectPaint.setStrokeWidth(0);
+ }
+ drawPath(d, *pathPtr, noEffectPaint, NULL, true);
+ return;
+ }
+
+#ifdef SK_PDF_USE_PATHOPS
+ if (handleInversePath(d, origPath, paint, pathIsMutable)) {
+ return;
+ }
+#endif
+
+ if (handleRectAnnotation(pathPtr->getBounds(), *d.fMatrix, paint)) {
+ return;
+ }
+
+ ScopedContentEntry content(this, d, paint);
+ if (!content.entry()) {
+ return;
+ }
+ SkPDFUtils::EmitPath(*pathPtr, paint.getStyle(),
+ &content.entry()->fContent);
+ SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(),
+ &content.entry()->fContent);
+}
+
+void SkPDFDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
+ const SkRect* src, const SkRect& dst,
+ const SkPaint& paint) {
+ SkMatrix matrix;
+ SkRect bitmapBounds, tmpSrc, tmpDst;
+ SkBitmap tmpBitmap;
+
+ bitmapBounds.isetWH(bitmap.width(), bitmap.height());
+
+ // Compute matrix from the two rectangles
+ if (src) {
+ tmpSrc = *src;
+ } else {
+ tmpSrc = bitmapBounds;
+ }
+ matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit);
+
+ const SkBitmap* bitmapPtr = &bitmap;
+
+ // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if
+ // needed (if the src was clipped). No check needed if src==null.
+ if (src) {
+ if (!bitmapBounds.contains(*src)) {
+ if (!tmpSrc.intersect(bitmapBounds)) {
+ return; // nothing to draw
+ }
+ // recompute dst, based on the smaller tmpSrc
+ matrix.mapRect(&tmpDst, tmpSrc);
+ }
+
+ // since we may need to clamp to the borders of the src rect within
+ // the bitmap, we extract a subset.
+ // TODO: make sure this is handled in drawBitmap and remove from here.
+ SkIRect srcIR;
+ tmpSrc.roundOut(&srcIR);
+ if (!bitmap.extractSubset(&tmpBitmap, srcIR)) {
+ return;
+ }
+ bitmapPtr = &tmpBitmap;
+
+ // Since we did an extract, we need to adjust the matrix accordingly
+ SkScalar dx = 0, dy = 0;
+ if (srcIR.fLeft > 0) {
+ dx = SkIntToScalar(srcIR.fLeft);
+ }
+ if (srcIR.fTop > 0) {
+ dy = SkIntToScalar(srcIR.fTop);
+ }
+ if (dx || dy) {
+ matrix.preTranslate(dx, dy);
+ }
+ }
+ this->drawBitmap(draw, *bitmapPtr, matrix, paint);
+}
+
+void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+
+ SkMatrix transform = matrix;
+ transform.postConcat(*d.fMatrix);
+ this->internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, NULL, paint);
+}
+
+void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap,
+ int x, int y, const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
+ this->internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, paint);
+}
+
+void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint& paint) {
+ NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
+ if (paint.getMaskFilter() != NULL) {
+ // Don't pretend we support drawing MaskFilters, it makes for artifacts
+ // making text unreadable (e.g. same text twice when using CSS shadows).
+ return;
+ }
+ SkPaint textPaint = calculate_text_paint(paint);
+ ScopedContentEntry content(this, d, textPaint, true);
+ if (!content.entry()) {
+ return;
+ }
+
+ SkGlyphStorage storage(0);
+ uint16_t* glyphIDs = NULL;
+ size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage,
+ &glyphIDs);
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
+ align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y);
+ content.entry()->fContent.writeText("BT\n");
+ set_text_transform(x, y, textPaint.getTextSkewX(),
+ &content.entry()->fContent);
+ size_t consumedGlyphCount = 0;
+ while (numGlyphs > consumedGlyphCount) {
+ updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry());
+ SkPDFFont* font = content.entry()->fState.fFont;
+ size_t availableGlyphs =
+ font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount,
+ numGlyphs - consumedGlyphCount);
+ fFontGlyphUsage->noteGlyphUsage(font, glyphIDs + consumedGlyphCount,
+ availableGlyphs);
+ SkString encodedString =
+ SkPDFString::FormatString(glyphIDs + consumedGlyphCount,
+ availableGlyphs, font->multiByteGlyphs());
+ content.entry()->fContent.writeText(encodedString.c_str());
+ consumedGlyphCount += availableGlyphs;
+ content.entry()->fContent.writeText(" Tj\n");
+ }
+ content.entry()->fContent.writeText("ET\n");
+}
+
+void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPos, const SkPaint& paint) {
+ NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
+ if (paint.getMaskFilter() != NULL) {
+ // Don't pretend we support drawing MaskFilters, it makes for artifacts
+ // making text unreadable (e.g. same text twice when using CSS shadows).
+ return;
+ }
+ SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
+ SkPaint textPaint = calculate_text_paint(paint);
+ ScopedContentEntry content(this, d, textPaint, true);
+ if (!content.entry()) {
+ return;
+ }
+
+ SkGlyphStorage storage(0);
+ uint16_t* glyphIDs = NULL;
+ size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage,
+ &glyphIDs);
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
+ content.entry()->fContent.writeText("BT\n");
+ updateFont(textPaint, glyphIDs[0], content.entry());
+ for (size_t i = 0; i < numGlyphs; i++) {
+ SkPDFFont* font = content.entry()->fState.fFont;
+ uint16_t encodedValue = glyphIDs[i];
+ if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) {
+ updateFont(textPaint, glyphIDs[i], content.entry());
+ i--;
+ continue;
+ }
+ fFontGlyphUsage->noteGlyphUsage(font, &encodedValue, 1);
+ SkScalar x = pos[i * scalarsPerPos];
+ SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
+ align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y);
+ set_text_transform(x, y, textPaint.getTextSkewX(),
+ &content.entry()->fContent);
+ SkString encodedString =
+ SkPDFString::FormatString(&encodedValue, 1,
+ font->multiByteGlyphs());
+ content.entry()->fContent.writeText(encodedString.c_str());
+ content.entry()->fContent.writeText(" Tj\n");
+ }
+ content.entry()->fContent.writeText("ET\n");
+}
+
+void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+ d.drawTextOnPath((const char*)text, len, path, matrix, paint);
+}
+
+void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode,
+ int vertexCount, const SkPoint verts[],
+ const SkPoint texs[], const SkColor colors[],
+ SkXfermode* xmode, const uint16_t indices[],
+ int indexCount, const SkPaint& paint) {
+ if (d.fClip->isEmpty()) {
+ return;
+ }
+ NOT_IMPLEMENTED("drawVerticies", true);
+}
+
+void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
+ const SkPaint& paint) {
+ if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
+ // If we somehow get a raster device, do what our parent would do.
+ SkDevice::drawDevice(d, device, x, y, paint);
+ return;
+ }
+
+ // Assume that a vector capable device means that it's a PDF Device.
+ SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
+ if (pdfDevice->isContentEmpty()) {
+ return;
+ }
+
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
+ ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint);
+ if (!content.entry()) {
+ return;
+ }
+
+ SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
+ fXObjectResources.push(xobject); // Transfer reference.
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &content.entry()->fContent);
+
+ // Merge glyph sets from the drawn device.
+ fFontGlyphUsage->merge(pdfDevice->getFontGlyphUsage());
+}
+
+void SkPDFDevice::onAttachToCanvas(SkCanvas* canvas) {
+ INHERITED::onAttachToCanvas(canvas);
+
+ // Canvas promises that this ptr is valid until onDetachFromCanvas is called
+ fClipStack = canvas->getClipStack();
+}
+
+void SkPDFDevice::onDetachFromCanvas() {
+ INHERITED::onDetachFromCanvas();
+
+ fClipStack = NULL;
+}
+
+ContentEntry* SkPDFDevice::getLastContentEntry() {
+ if (fDrawingArea == kContent_DrawingArea) {
+ return fLastContentEntry;
+ } else {
+ return fLastMarginContentEntry;
+ }
+}
+
+SkTScopedPtr<ContentEntry>* SkPDFDevice::getContentEntries() {
+ if (fDrawingArea == kContent_DrawingArea) {
+ return &fContentEntries;
+ } else {
+ return &fMarginContentEntries;
+ }
+}
+
+void SkPDFDevice::setLastContentEntry(ContentEntry* contentEntry) {
+ if (fDrawingArea == kContent_DrawingArea) {
+ fLastContentEntry = contentEntry;
+ } else {
+ fLastMarginContentEntry = contentEntry;
+ }
+}
+
+void SkPDFDevice::setDrawingArea(DrawingArea drawingArea) {
+ // A ScopedContentEntry only exists during the course of a draw call, so
+ // this can't be called while a ScopedContentEntry exists.
+ fDrawingArea = drawingArea;
+}
+
+SkPDFResourceDict* SkPDFDevice::getResourceDict() {
+ if (NULL == fResourceDict) {
+ fResourceDict = SkNEW(SkPDFResourceDict);
+
+ if (fGraphicStateResources.count()) {
+ for (int i = 0; i < fGraphicStateResources.count(); i++) {
+ fResourceDict->insertResourceAsReference(
+ SkPDFResourceDict::kExtGState_ResourceType,
+ i, fGraphicStateResources[i]);
+ }
+ }
+
+ if (fXObjectResources.count()) {
+ for (int i = 0; i < fXObjectResources.count(); i++) {
+ fResourceDict->insertResourceAsReference(
+ SkPDFResourceDict::kXObject_ResourceType,
+ i, fXObjectResources[i]);
+ }
+ }
+
+ if (fFontResources.count()) {
+ for (int i = 0; i < fFontResources.count(); i++) {
+ fResourceDict->insertResourceAsReference(
+ SkPDFResourceDict::kFont_ResourceType,
+ i, fFontResources[i]);
+ }
+ }
+
+ if (fShaderResources.count()) {
+ SkAutoTUnref<SkPDFDict> patterns(new SkPDFDict());
+ for (int i = 0; i < fShaderResources.count(); i++) {
+ fResourceDict->insertResourceAsReference(
+ SkPDFResourceDict::kPattern_ResourceType,
+ i, fShaderResources[i]);
+ }
+ }
+ }
+ return fResourceDict;
+}
+
+const SkTDArray<SkPDFFont*>& SkPDFDevice::getFontResources() const {
+ return fFontResources;
+}
+
+SkPDFArray* SkPDFDevice::copyMediaBox() const {
+ // should this be a singleton?
+ SkAutoTUnref<SkPDFInt> zero(SkNEW_ARGS(SkPDFInt, (0)));
+
+ SkPDFArray* mediaBox = SkNEW(SkPDFArray);
+ mediaBox->reserve(4);
+ mediaBox->append(zero.get());
+ mediaBox->append(zero.get());
+ mediaBox->appendInt(fPageSize.fWidth);
+ mediaBox->appendInt(fPageSize.fHeight);
+ return mediaBox;
+}
+
+SkStream* SkPDFDevice::content() const {
+ SkMemoryStream* result = new SkMemoryStream;
+ result->setData(this->copyContentToData())->unref();
+ return result;
+}
+
+void SkPDFDevice::copyContentEntriesToData(ContentEntry* entry,
+ SkWStream* data) const {
+ // TODO(ctguil): For margins, I'm not sure fExistingClipStack/Region is the
+ // right thing to pass here.
+ GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, data);
+ while (entry != NULL) {
+ SkPoint translation;
+ translation.iset(this->getOrigin());
+ translation.negate();
+ gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion,
+ translation);
+ gsState.updateMatrix(entry->fState.fMatrix);
+ gsState.updateDrawingState(entry->fState);
+
+ SkAutoDataUnref copy(entry->fContent.copyToData());
+ data->write(copy->data(), copy->size());
+ entry = entry->fNext.get();
+ }
+ gsState.drainStack();
+}
+
+SkData* SkPDFDevice::copyContentToData() const {
+ SkDynamicMemoryWStream data;
+ if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
+ SkPDFUtils::AppendTransform(fInitialTransform, &data);
+ }
+
+ // TODO(aayushkumar): Apply clip along the margins. Currently, webkit
+ // colors the contentArea white before it starts drawing into it and
+ // that currently acts as our clip.
+ // Also, think about adding a transform here (or assume that the values
+ // sent across account for that)
+ SkPDFDevice::copyContentEntriesToData(fMarginContentEntries.get(), &data);
+
+ // If the content area is the entire page, then we don't need to clip
+ // the content area (PDF area clips to the page size). Otherwise,
+ // we have to clip to the content area; we've already applied the
+ // initial transform, so just clip to the device size.
+ if (fPageSize != fContentSize) {
+ SkRect r = SkRect::MakeWH(SkIntToScalar(this->width()),
+ SkIntToScalar(this->height()));
+ emit_clip(NULL, &r, &data);
+ }
+
+ SkPDFDevice::copyContentEntriesToData(fContentEntries.get(), &data);
+
+ // potentially we could cache this SkData, and only rebuild it if we
+ // see that our state has changed.
+ return data.copyToData();
+}
+
+#ifdef SK_PDF_USE_PATHOPS
+/* Draws an inverse filled path by using Path Ops to compute the positive
+ * inverse using the current clip as the inverse bounds.
+ * Return true if this was an inverse path and was properly handled,
+ * otherwise returns false and the normal drawing routine should continue,
+ * either as a (incorrect) fallback or because the path was not inverse
+ * in the first place.
+ */
+bool SkPDFDevice::handleInversePath(const SkDraw& d, const SkPath& origPath,
+ const SkPaint& paint, bool pathIsMutable) {
+ if (!origPath.isInverseFillType()) {
+ return false;
+ }
+
+ if (d.fClip->isEmpty()) {
+ return false;
+ }
+
+ SkPath modifiedPath;
+ SkPath* pathPtr = const_cast<SkPath*>(&origPath);
+ SkPaint noInversePaint(paint);
+
+ // Merge stroking operations into final path.
+ if (SkPaint::kStroke_Style == paint.getStyle() ||
+ SkPaint::kStrokeAndFill_Style == paint.getStyle()) {
+ bool doFillPath = paint.getFillPath(origPath, &modifiedPath);
+ if (doFillPath) {
+ noInversePaint.setStyle(SkPaint::kFill_Style);
+ noInversePaint.setStrokeWidth(0);
+ pathPtr = &modifiedPath;
+ } else {
+ // To be consistent with the raster output, hairline strokes
+ // are rendered as non-inverted.
+ modifiedPath.toggleInverseFillType();
+ drawPath(d, modifiedPath, paint, NULL, true);
+ return true;
+ }
+ }
+
+ // Get bounds of clip in current transform space
+ // (clip bounds are given in device space).
+ SkRect bounds;
+ SkMatrix transformInverse;
+ if (!d.fMatrix->invert(&transformInverse)) {
+ return false;
+ }
+ bounds.set(d.fClip->getBounds());
+ transformInverse.mapRect(&bounds);
+
+ // Extend the bounds by the line width (plus some padding)
+ // so the edge doesn't cause a visible stroke.
+ bounds.outset(paint.getStrokeWidth() + SK_Scalar1,
+ paint.getStrokeWidth() + SK_Scalar1);
+
+ if (!calculate_inverse_path(bounds, *pathPtr, &modifiedPath)) {
+ return false;
+ }
+
+ drawPath(d, modifiedPath, noInversePaint, NULL, true);
+ return true;
+}
+#endif
+
+bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
+ const SkPaint& p) {
+ SkAnnotation* annotationInfo = p.getAnnotation();
+ if (!annotationInfo) {
+ return false;
+ }
+ SkData* urlData = annotationInfo->find(SkAnnotationKeys::URL_Key());
+ if (urlData) {
+ handleLinkToURL(urlData, r, matrix);
+ return p.isNoDrawAnnotation();
+ }
+ SkData* linkToName = annotationInfo->find(SkAnnotationKeys::Link_Named_Dest_Key());
+ if (linkToName) {
+ handleLinkToNamedDest(linkToName, r, matrix);
+ return p.isNoDrawAnnotation();
+ }
+ return false;
+}
+
+bool SkPDFDevice::handlePointAnnotation(const SkPoint* points, size_t count,
+ const SkMatrix& matrix,
+ const SkPaint& paint) {
+ SkAnnotation* annotationInfo = paint.getAnnotation();
+ if (!annotationInfo) {
+ return false;
+ }
+ SkData* nameData = annotationInfo->find(SkAnnotationKeys::Define_Named_Dest_Key());
+ if (nameData) {
+ for (size_t i = 0; i < count; i++) {
+ defineNamedDestination(nameData, points[i], matrix);
+ }
+ return paint.isNoDrawAnnotation();
+ }
+ return false;
+}
+
+SkPDFDict* SkPDFDevice::createLinkAnnotation(const SkRect& r, const SkMatrix& matrix) {
+ SkMatrix transform = matrix;
+ transform.postConcat(fInitialTransform);
+ SkRect translatedRect;
+ transform.mapRect(&translatedRect, r);
+
+ if (NULL == fAnnotations) {
+ fAnnotations = SkNEW(SkPDFArray);
+ }
+ SkPDFDict* annotation(SkNEW_ARGS(SkPDFDict, ("Annot")));
+ annotation->insertName("Subtype", "Link");
+ fAnnotations->append(annotation);
+
+ SkAutoTUnref<SkPDFArray> border(SkNEW(SkPDFArray));
+ border->reserve(3);
+ border->appendInt(0); // Horizontal corner radius.
+ border->appendInt(0); // Vertical corner radius.
+ border->appendInt(0); // Width, 0 = no border.
+ annotation->insert("Border", border.get());
+
+ SkAutoTUnref<SkPDFArray> rect(SkNEW(SkPDFArray));
+ rect->reserve(4);
+ rect->appendScalar(translatedRect.fLeft);
+ rect->appendScalar(translatedRect.fTop);
+ rect->appendScalar(translatedRect.fRight);
+ rect->appendScalar(translatedRect.fBottom);
+ annotation->insert("Rect", rect.get());
+
+ return annotation;
+}
+
+void SkPDFDevice::handleLinkToURL(SkData* urlData, const SkRect& r,
+ const SkMatrix& matrix) {
+ SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
+
+ SkString url(static_cast<const char *>(urlData->data()),
+ urlData->size() - 1);
+ SkAutoTUnref<SkPDFDict> action(SkNEW_ARGS(SkPDFDict, ("Action")));
+ action->insertName("S", "URI");
+ action->insert("URI", SkNEW_ARGS(SkPDFString, (url)))->unref();
+ annotation->insert("A", action.get());
+}
+
+void SkPDFDevice::handleLinkToNamedDest(SkData* nameData, const SkRect& r,
+ const SkMatrix& matrix) {
+ SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
+ SkString name(static_cast<const char *>(nameData->data()),
+ nameData->size() - 1);
+ annotation->insert("Dest", SkNEW_ARGS(SkPDFName, (name)))->unref();
+}
+
+struct NamedDestination {
+ const SkData* nameData;
+ SkPoint point;
+
+ NamedDestination(const SkData* nameData, const SkPoint& point)
+ : nameData(nameData), point(point) {
+ nameData->ref();
+ }
+
+ ~NamedDestination() {
+ nameData->unref();
+ }
+};
+
+void SkPDFDevice::defineNamedDestination(SkData* nameData, const SkPoint& point,
+ const SkMatrix& matrix) {
+ SkMatrix transform = matrix;
+ transform.postConcat(fInitialTransform);
+ SkPoint translatedPoint;
+ transform.mapXY(point.x(), point.y(), &translatedPoint);
+ fNamedDestinations.push(
+ SkNEW_ARGS(NamedDestination, (nameData, translatedPoint)));
+}
+
+void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) {
+ int nDest = fNamedDestinations.count();
+ for (int i = 0; i < nDest; i++) {
+ NamedDestination* dest = fNamedDestinations[i];
+ SkAutoTUnref<SkPDFArray> pdfDest(SkNEW(SkPDFArray));
+ pdfDest->reserve(5);
+ pdfDest->append(SkNEW_ARGS(SkPDFObjRef, (page)))->unref();
+ pdfDest->appendName("XYZ");
+ pdfDest->appendScalar(dest->point.x());
+ pdfDest->appendScalar(dest->point.y());
+ pdfDest->appendInt(0); // Leave zoom unchanged
+ dict->insert(static_cast<const char *>(dest->nameData->data()), pdfDest);
+ }
+}
+
+SkPDFFormXObject* SkPDFDevice::createFormXObjectFromDevice() {
+ SkPDFFormXObject* xobject = SkNEW_ARGS(SkPDFFormXObject, (this));
+ // We always draw the form xobjects that we create back into the device, so
+ // we simply preserve the font usage instead of pulling it out and merging
+ // it back in later.
+ cleanUp(false); // Reset this device to have no content.
+ init();
+ return xobject;
+}
+
+void SkPDFDevice::clearClipFromContent(const SkClipStack* clipStack,
+ const SkRegion& clipRegion) {
+ if (clipRegion.isEmpty() || isContentEmpty()) {
+ return;
+ }
+ SkAutoTUnref<SkPDFFormXObject> curContent(createFormXObjectFromDevice());
+
+ // Redraw what we already had, but with the clip as a mask.
+ drawFormXObjectWithClip(curContent, clipStack, clipRegion, true);
+}
+
+void SkPDFDevice::drawFormXObjectWithClip(SkPDFFormXObject* xobject,
+ const SkClipStack* clipStack,
+ const SkRegion& clipRegion,
+ bool invertClip) {
+ if (clipRegion.isEmpty() && !invertClip) {
+ return;
+ }
+
+ // Create the mask.
+ SkMatrix identity;
+ identity.reset();
+ SkDraw draw;
+ draw.fMatrix = &identity;
+ draw.fClip = &clipRegion;
+ draw.fClipStack = clipStack;
+ SkPaint stockPaint;
+ this->drawPaint(draw, stockPaint);
+ SkAutoTUnref<SkPDFFormXObject> maskFormXObject(createFormXObjectFromDevice());
+ SkAutoTUnref<SkPDFGraphicState> sMaskGS(
+ SkPDFGraphicState::GetSMaskGraphicState(maskFormXObject, invertClip,
+ SkPDFGraphicState::kAlpha_SMaskMode));
+
+ // Draw the xobject with the clip as a mask.
+ ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
+ identity, stockPaint);
+ if (!content.entry()) {
+ return;
+ }
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &content.entry()->fContent);
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count(),
+ &content.entry()->fContent);
+ fXObjectResources.push(xobject);
+ xobject->ref();
+
+ sMaskGS.reset(SkPDFGraphicState::GetNoSMaskGraphicState());
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &content.entry()->fContent);
+}
+
+ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
+ const SkRegion& clipRegion,
+ const SkMatrix& matrix,
+ const SkPaint& paint,
+ bool hasText,
+ SkPDFFormXObject** dst) {
+ *dst = NULL;
+ if (clipRegion.isEmpty()) {
+ return NULL;
+ }
+
+ // The clip stack can come from an SkDraw where it is technically optional.
+ SkClipStack synthesizedClipStack;
+ if (clipStack == NULL) {
+ if (clipRegion == fExistingClipRegion) {
+ clipStack = &fExistingClipStack;
+ } else {
+ // GraphicStackState::updateClip expects the clip stack to have
+ // fExistingClip as a prefix, so start there, then set the clip
+ // to the passed region.
+ synthesizedClipStack = fExistingClipStack;
+ SkPath clipPath;
+ clipRegion.getBoundaryPath(&clipPath);
+ synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op,
+ false);
+ clipStack = &synthesizedClipStack;
+ }
+ }
+
+ SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
+ if (paint.getXfermode()) {
+ paint.getXfermode()->asMode(&xfermode);
+ }
+
+ if (xfermode == SkXfermode::kClear_Mode ||
+ xfermode == SkXfermode::kSrc_Mode) {
+ this->clearClipFromContent(clipStack, clipRegion);
+ } else if (xfermode == SkXfermode::kSrcIn_Mode ||
+ xfermode == SkXfermode::kDstIn_Mode ||
+ xfermode == SkXfermode::kSrcOut_Mode ||
+ xfermode == SkXfermode::kDstOut_Mode) {
+ // For the following modes, we use both source and destination, but
+ // we use one as a smask for the other, so we have to make form xobjects
+ // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
+ if (isContentEmpty()) {
+ return NULL;
+ } else {
+ *dst = createFormXObjectFromDevice();
+ }
+ }
+ // TODO(vandebo): Figure out how/if we can handle the following modes:
+ // SrcAtop, DestAtop, Xor, Plus.
+
+ // These xfer modes don't draw source at all.
+ if (xfermode == SkXfermode::kClear_Mode ||
+ xfermode == SkXfermode::kDst_Mode) {
+ return NULL;
+ }
+
+ ContentEntry* entry;
+ SkTScopedPtr<ContentEntry> newEntry;
+
+ ContentEntry* lastContentEntry = getLastContentEntry();
+ if (lastContentEntry && lastContentEntry->fContent.getOffset() == 0) {
+ entry = lastContentEntry;
+ } else {
+ newEntry.reset(new ContentEntry);
+ entry = newEntry.get();
+ }
+
+ populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint,
+ hasText, &entry->fState);
+ if (lastContentEntry && xfermode != SkXfermode::kDstOver_Mode &&
+ entry->fState.compareInitialState(lastContentEntry->fState)) {
+ return lastContentEntry;
+ }
+
+ SkTScopedPtr<ContentEntry>* contentEntries = getContentEntries();
+ if (!lastContentEntry) {
+ contentEntries->reset(entry);
+ setLastContentEntry(entry);
+ } else if (xfermode == SkXfermode::kDstOver_Mode) {
+ entry->fNext.reset(contentEntries->release());
+ contentEntries->reset(entry);
+ } else {
+ lastContentEntry->fNext.reset(entry);
+ setLastContentEntry(entry);
+ }
+ newEntry.release();
+ return entry;
+}
+
+void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode,
+ SkPDFFormXObject* dst) {
+ if (xfermode != SkXfermode::kSrcIn_Mode &&
+ xfermode != SkXfermode::kDstIn_Mode &&
+ xfermode != SkXfermode::kSrcOut_Mode &&
+ xfermode != SkXfermode::kDstOut_Mode) {
+ SkASSERT(!dst);
+ return;
+ }
+
+ ContentEntry* contentEntries = getContentEntries()->get();
+ SkASSERT(dst);
+ SkASSERT(!contentEntries->fNext.get());
+ // We have to make a copy of these here because changing the current
+ // content into a form xobject will destroy them.
+ SkClipStack clipStack = contentEntries->fState.fClipStack;
+ SkRegion clipRegion = contentEntries->fState.fClipRegion;
+
+ SkAutoTUnref<SkPDFFormXObject> srcFormXObject;
+ if (!isContentEmpty()) {
+ srcFormXObject.reset(createFormXObjectFromDevice());
+ }
+
+ drawFormXObjectWithClip(dst, &clipStack, clipRegion, true);
+
+ // We've redrawn dst minus the clip area, if there's no src, we're done.
+ if (!srcFormXObject.get()) {
+ return;
+ }
+
+ SkMatrix identity;
+ identity.reset();
+ SkPaint stockPaint;
+ ScopedContentEntry inClipContentEntry(this, &fExistingClipStack,
+ fExistingClipRegion, identity,
+ stockPaint);
+ if (!inClipContentEntry.entry()) {
+ return;
+ }
+
+ SkAutoTUnref<SkPDFGraphicState> sMaskGS;
+ if (xfermode == SkXfermode::kSrcIn_Mode ||
+ xfermode == SkXfermode::kSrcOut_Mode) {
+ sMaskGS.reset(SkPDFGraphicState::GetSMaskGraphicState(
+ dst,
+ xfermode == SkXfermode::kSrcOut_Mode,
+ SkPDFGraphicState::kAlpha_SMaskMode));
+ fXObjectResources.push(srcFormXObject.get());
+ srcFormXObject.get()->ref();
+ } else {
+ sMaskGS.reset(SkPDFGraphicState::GetSMaskGraphicState(
+ srcFormXObject.get(),
+ xfermode == SkXfermode::kDstOut_Mode,
+ SkPDFGraphicState::kAlpha_SMaskMode));
+ // dst already added to fXObjectResources in drawFormXObjectWithClip.
+ }
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &inClipContentEntry.entry()->fContent);
+
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &inClipContentEntry.entry()->fContent);
+
+ sMaskGS.reset(SkPDFGraphicState::GetNoSMaskGraphicState());
+ SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+ &inClipContentEntry.entry()->fContent);
+}
+
+bool SkPDFDevice::isContentEmpty() {
+ ContentEntry* contentEntries = getContentEntries()->get();
+ if (!contentEntries || contentEntries->fContent.getOffset() == 0) {
+ SkASSERT(!contentEntries || !contentEntries->fNext.get());
+ return true;
+ }
+ return false;
+}
+
+void SkPDFDevice::populateGraphicStateEntryFromPaint(
+ const SkMatrix& matrix,
+ const SkClipStack& clipStack,
+ const SkRegion& clipRegion,
+ const SkPaint& paint,
+ bool hasText,
+ GraphicStateEntry* entry) {
+ SkASSERT(paint.getPathEffect() == NULL);
+
+ NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false);
+ NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false);
+
+ entry->fMatrix = matrix;
+ entry->fClipStack = clipStack;
+ entry->fClipRegion = clipRegion;
+ entry->fColor = SkColorSetA(paint.getColor(), 0xFF);
+ entry->fShaderIndex = -1;
+
+ // PDF treats a shader as a color, so we only set one or the other.
+ SkAutoTUnref<SkPDFObject> pdfShader;
+ const SkShader* shader = paint.getShader();
+ SkColor color = paint.getColor();
+ if (shader) {
+ // PDF positions patterns relative to the initial transform, so
+ // we need to apply the current transform to the shader parameters.
+ SkMatrix transform = matrix;
+ transform.postConcat(fInitialTransform);
+
+ // PDF doesn't support kClamp_TileMode, so we simulate it by making
+ // a pattern the size of the current clip.
+ SkIRect bounds = clipRegion.getBounds();
+
+ // We need to apply the initial transform to bounds in order to get
+ // bounds in a consistent coordinate system.
+ SkRect boundsTemp;
+ boundsTemp.set(bounds);
+ fInitialTransform.mapRect(&boundsTemp);
+ boundsTemp.roundOut(&bounds);
+
+ pdfShader.reset(SkPDFShader::GetPDFShader(*shader, transform, bounds));
+
+ if (pdfShader.get()) {
+ // pdfShader has been canonicalized so we can directly compare
+ // pointers.
+ int resourceIndex = fShaderResources.find(pdfShader.get());
+ if (resourceIndex < 0) {
+ resourceIndex = fShaderResources.count();
+ fShaderResources.push(pdfShader.get());
+ pdfShader.get()->ref();
+ }
+ entry->fShaderIndex = resourceIndex;
+ } else {
+ // A color shader is treated as an invalid shader so we don't have
+ // to set a shader just for a color.
+ SkShader::GradientInfo gradientInfo;
+ SkColor gradientColor;
+ gradientInfo.fColors = &gradientColor;
+ gradientInfo.fColorOffsets = NULL;
+ gradientInfo.fColorCount = 1;
+ if (shader->asAGradient(&gradientInfo) ==
+ SkShader::kColor_GradientType) {
+ entry->fColor = SkColorSetA(gradientColor, 0xFF);
+ color = gradientColor;
+ }
+ }
+ }
+
+ SkAutoTUnref<SkPDFGraphicState> newGraphicState;
+ if (color == paint.getColor()) {
+ newGraphicState.reset(
+ SkPDFGraphicState::GetGraphicStateForPaint(paint));
+ } else {
+ SkPaint newPaint = paint;
+ newPaint.setColor(color);
+ newGraphicState.reset(
+ SkPDFGraphicState::GetGraphicStateForPaint(newPaint));
+ }
+ int resourceIndex = addGraphicStateResource(newGraphicState.get());
+ entry->fGraphicStateIndex = resourceIndex;
+
+ if (hasText) {
+ entry->fTextScaleX = paint.getTextScaleX();
+ entry->fTextFill = paint.getStyle();
+ } else {
+ entry->fTextScaleX = 0;
+ }
+}
+
+int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
+ // Assumes that gs has been canonicalized (so we can directly compare
+ // pointers).
+ int result = fGraphicStateResources.find(gs);
+ if (result < 0) {
+ result = fGraphicStateResources.count();
+ fGraphicStateResources.push(gs);
+ gs->ref();
+ }
+ return result;
+}
+
+void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID,
+ ContentEntry* contentEntry) {
+ SkTypeface* typeface = paint.getTypeface();
+ if (contentEntry->fState.fFont == NULL ||
+ contentEntry->fState.fTextSize != paint.getTextSize() ||
+ !contentEntry->fState.fFont->hasGlyph(glyphID)) {
+ int fontIndex = getFontResourceIndex(typeface, glyphID);
+ contentEntry->fContent.writeText("/");
+ contentEntry->fContent.writeText(SkPDFResourceDict::getResourceName(
+ SkPDFResourceDict::kFont_ResourceType,
+ fontIndex).c_str());
+ contentEntry->fContent.writeText(" ");
+ SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent);
+ contentEntry->fContent.writeText(" Tf\n");
+ contentEntry->fState.fFont = fFontResources[fontIndex];
+ }
+}
+
+int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) {
+ SkAutoTUnref<SkPDFFont> newFont(SkPDFFont::GetFontResource(typeface, glyphID));
+ int resourceIndex = fFontResources.find(newFont.get());
+ if (resourceIndex < 0) {
+ resourceIndex = fFontResources.count();
+ fFontResources.push(newFont.get());
+ newFont.get()->ref();
+ }
+ return resourceIndex;
+}
+
+void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
+ const SkClipStack* clipStack,
+ const SkRegion& clipRegion,
+ const SkBitmap& bitmap,
+ const SkIRect* srcRect,
+ const SkPaint& paint) {
+ SkMatrix scaled;
+ // Adjust for origin flip.
+ scaled.setScale(SK_Scalar1, -SK_Scalar1);
+ scaled.postTranslate(0, SK_Scalar1);
+ // Scale the image up from 1x1 to WxH.
+ SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
+ scaled.postScale(SkIntToScalar(subset.width()),
+ SkIntToScalar(subset.height()));
+ scaled.postConcat(matrix);
+ ScopedContentEntry content(this, clipStack, clipRegion, scaled, paint);
+ if (!content.entry()) {
+ return;
+ }
+
+ if (srcRect && !subset.intersect(*srcRect)) {
+ return;
+ }
+
+ SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, fEncoder);
+ if (!image) {
+ return;
+ }
+
+ fXObjectResources.push(image); // Transfer reference.
+ SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+ &content.entry()->fContent);
+}
+
+bool SkPDFDevice::onReadPixels(const SkBitmap& bitmap, int x, int y,
+ SkCanvas::Config8888) {
+ return false;
+}
+
+bool SkPDFDevice::allowImageFilter(SkImageFilter*) {
+ return false;
+}
diff --git a/pdf/SkPDFDocument.cpp b/pdf/SkPDFDocument.cpp
new file mode 100644
index 00000000..0633d308
--- /dev/null
+++ b/pdf/SkPDFDocument.cpp
@@ -0,0 +1,309 @@
+
+/*
+ * 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 "SkPDFCatalog.h"
+#include "SkPDFDevice.h"
+#include "SkPDFDocument.h"
+#include "SkPDFFont.h"
+#include "SkPDFPage.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+#include "SkTSet.h"
+
+static void addResourcesToCatalog(bool firstPage,
+ SkTSet<SkPDFObject*>* resourceSet,
+ SkPDFCatalog* catalog) {
+ for (int i = 0; i < resourceSet->count(); i++) {
+ catalog->addObject((*resourceSet)[i], firstPage);
+ }
+}
+
+static void perform_font_subsetting(SkPDFCatalog* catalog,
+ const SkTDArray<SkPDFPage*>& pages,
+ SkTDArray<SkPDFObject*>* substitutes) {
+ SkASSERT(catalog);
+ SkASSERT(substitutes);
+
+ SkPDFGlyphSetMap usage;
+ for (int i = 0; i < pages.count(); ++i) {
+ usage.merge(pages[i]->getFontGlyphUsage());
+ }
+ SkPDFGlyphSetMap::F2BIter iterator(usage);
+ const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
+ while (entry) {
+ SkPDFFont* subsetFont =
+ entry->fFont->getFontSubset(entry->fGlyphSet);
+ if (subsetFont) {
+ catalog->setSubstitute(entry->fFont, subsetFont);
+ substitutes->push(subsetFont); // Transfer ownership to substitutes
+ }
+ entry = iterator.next();
+ }
+}
+
+SkPDFDocument::SkPDFDocument(Flags flags)
+ : fXRefFileOffset(0),
+ fTrailerDict(NULL) {
+ fCatalog.reset(new SkPDFCatalog(flags));
+ fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
+ fCatalog->addObject(fDocCatalog, true);
+ fFirstPageResources = NULL;
+ fOtherPageResources = NULL;
+}
+
+SkPDFDocument::~SkPDFDocument() {
+ fPages.safeUnrefAll();
+
+ // The page tree has both child and parent pointers, so it creates a
+ // reference cycle. We must clear that cycle to properly reclaim memory.
+ for (int i = 0; i < fPageTree.count(); i++) {
+ fPageTree[i]->clear();
+ }
+ fPageTree.safeUnrefAll();
+
+ if (fFirstPageResources) {
+ fFirstPageResources->safeUnrefAll();
+ }
+ if (fOtherPageResources) {
+ fOtherPageResources->safeUnrefAll();
+ }
+
+ fSubstitutes.safeUnrefAll();
+
+ fDocCatalog->unref();
+ SkSafeUnref(fTrailerDict);
+ SkDELETE(fFirstPageResources);
+ SkDELETE(fOtherPageResources);
+}
+
+bool SkPDFDocument::emitPDF(SkWStream* stream) {
+ if (fPages.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < fPages.count(); i++) {
+ if (fPages[i] == NULL) {
+ return false;
+ }
+ }
+
+ fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>);
+ fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>);
+
+ // We haven't emitted the document before if fPageTree is empty.
+ if (fPageTree.isEmpty()) {
+ SkPDFDict* pageTreeRoot;
+ SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
+ &pageTreeRoot);
+ fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
+
+ /* TODO(vandebo): output intent
+ SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
+ outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
+ outputIntent->insert("OutputConditionIdentifier",
+ new SkPDFString("sRGB"))->unref();
+ SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
+ intentArray->append(outputIntent.get());
+ fDocCatalog->insert("OutputIntent", intentArray.get());
+ */
+
+ SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict));
+
+ bool firstPage = true;
+ /* The references returned in newResources are transfered to
+ * fFirstPageResources or fOtherPageResources depending on firstPage and
+ * knownResources doesn't have a reference but just relies on the other
+ * two sets to maintain a reference.
+ */
+ SkTSet<SkPDFObject*> knownResources;
+
+ // mergeInto returns the number of duplicates.
+ // If there are duplicates, there is a bug and we mess ref counting.
+ SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources);
+ SkASSERT(duplicates == 0);
+
+ for (int i = 0; i < fPages.count(); i++) {
+ if (i == 1) {
+ firstPage = false;
+ SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources);
+ }
+ SkTSet<SkPDFObject*> newResources;
+ fPages[i]->finalizePage(
+ fCatalog.get(), firstPage, knownResources, &newResources);
+ addResourcesToCatalog(firstPage, &newResources, fCatalog.get());
+ if (firstPage) {
+ SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources);
+ } else {
+ SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources);
+ }
+ SkASSERT(duplicates == 0);
+
+ SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources);
+ SkASSERT(duplicates == 0);
+
+ fPages[i]->appendDestinations(dests);
+ }
+
+ if (dests->size() > 0) {
+ SkPDFDict* raw_dests = dests.get();
+ fFirstPageResources->add(dests.detach()); // Transfer ownership.
+ fCatalog->addObject(raw_dests, true /* onFirstPage */);
+ fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref();
+ }
+
+ // Build font subsetting info before proceeding.
+ perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
+
+ // Figure out the size of things and inform the catalog of file offsets.
+ off_t fileOffset = headerSize();
+ fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
+ fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
+ fileOffset += fPages[0]->getPageSize(fCatalog.get(),
+ (size_t) fileOffset);
+ for (int i = 0; i < fFirstPageResources->count(); i++) {
+ fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i],
+ fileOffset);
+ }
+ // Add the size of resources of substitute objects used on page 1.
+ fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
+ if (fPages.count() > 1) {
+ // TODO(vandebo): For linearized format, save the start of the
+ // first page xref table and calculate the size.
+ }
+
+ for (int i = 0; i < fPageTree.count(); i++) {
+ fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
+ }
+
+ for (int i = 1; i < fPages.count(); i++) {
+ fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
+ }
+
+ for (int i = 0; i < fOtherPageResources->count(); i++) {
+ fileOffset += fCatalog->setFileOffset(
+ (*fOtherPageResources)[i], fileOffset);
+ }
+
+ fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
+ false);
+ fXRefFileOffset = fileOffset;
+ }
+
+ emitHeader(stream);
+ fDocCatalog->emitObject(stream, fCatalog.get(), true);
+ fPages[0]->emitObject(stream, fCatalog.get(), true);
+ fPages[0]->emitPage(stream, fCatalog.get());
+ for (int i = 0; i < fFirstPageResources->count(); i++) {
+ (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true);
+ }
+ fCatalog->emitSubstituteResources(stream, true);
+ // TODO(vandebo): Support linearized format
+ // if (fPages.size() > 1) {
+ // // TODO(vandebo): Save the file offset for the first page xref table.
+ // fCatalog->emitXrefTable(stream, true);
+ // }
+
+ for (int i = 0; i < fPageTree.count(); i++) {
+ fPageTree[i]->emitObject(stream, fCatalog.get(), true);
+ }
+
+ for (int i = 1; i < fPages.count(); i++) {
+ fPages[i]->emitPage(stream, fCatalog.get());
+ }
+
+ for (int i = 0; i < fOtherPageResources->count(); i++) {
+ (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true);
+ }
+
+ fCatalog->emitSubstituteResources(stream, false);
+ int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
+ emitFooter(stream, objCount);
+ return true;
+}
+
+bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
+ if (!fPageTree.isEmpty()) {
+ return false;
+ }
+
+ pageNumber--;
+ SkASSERT(pageNumber >= 0);
+
+ if (pageNumber >= fPages.count()) {
+ int oldSize = fPages.count();
+ fPages.setCount(pageNumber + 1);
+ for (int i = oldSize; i <= pageNumber; i++) {
+ fPages[i] = NULL;
+ }
+ }
+
+ SkPDFPage* page = new SkPDFPage(pdfDevice);
+ SkSafeUnref(fPages[pageNumber]);
+ fPages[pageNumber] = page; // Reference from new passed to fPages.
+ return true;
+}
+
+bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
+ if (!fPageTree.isEmpty()) {
+ return false;
+ }
+
+ SkPDFPage* page = new SkPDFPage(pdfDevice);
+ fPages.push(page); // Reference from new passed to fPages.
+ return true;
+}
+
+void SkPDFDocument::getCountOfFontTypes(
+ int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
+ sk_bzero(counts, sizeof(int) *
+ (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
+ SkTDArray<SkFontID> seenFonts;
+
+ for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
+ const SkTDArray<SkPDFFont*>& fontResources =
+ fPages[pageNumber]->getFontResources();
+ for (int font = 0; font < fontResources.count(); font++) {
+ SkFontID fontID = fontResources[font]->typeface()->uniqueID();
+ if (seenFonts.find(fontID) == -1) {
+ counts[fontResources[font]->getType()]++;
+ seenFonts.push(fontID);
+ }
+ }
+ }
+}
+
+void SkPDFDocument::emitHeader(SkWStream* stream) {
+ stream->writeText("%PDF-1.4\n%");
+ // The PDF spec recommends including a comment with four bytes, all
+ // with their high bits set. This is "Skia" with the high bits set.
+ stream->write32(0xD3EBE9E1);
+ stream->writeText("\n");
+}
+
+size_t SkPDFDocument::headerSize() {
+ SkDynamicMemoryWStream buffer;
+ emitHeader(&buffer);
+ return buffer.getOffset();
+}
+
+void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
+ if (NULL == fTrailerDict) {
+ fTrailerDict = SkNEW(SkPDFDict);
+
+ // TODO(vandebo): Linearized format will take a Prev entry too.
+ // TODO(vandebo): PDF/A requires an ID entry.
+ fTrailerDict->insertInt("Size", int(objCount));
+ fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
+ }
+
+ stream->writeText("trailer\n");
+ fTrailerDict->emitObject(stream, fCatalog.get(), false);
+ stream->writeText("\nstartxref\n");
+ stream->writeBigDecAsText(fXRefFileOffset);
+ stream->writeText("\n%%EOF");
+}
diff --git a/pdf/SkPDFFont.cpp b/pdf/SkPDFFont.cpp
new file mode 100644
index 00000000..7d8c286e
--- /dev/null
+++ b/pdf/SkPDFFont.cpp
@@ -0,0 +1,1418 @@
+/*
+ * 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 <ctype.h>
+
+#include "SkData.h"
+#include "SkFontHost.h"
+#include "SkGlyphCache.h"
+#include "SkPaint.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFDevice.h"
+#include "SkPDFFont.h"
+#include "SkPDFFontImpl.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkPDFUtils.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkStream.h"
+#include "SkTypefacePriv.h"
+#include "SkTypes.h"
+#include "SkUtils.h"
+
+#if defined (SK_SFNTLY_SUBSETTER)
+#include SK_SFNTLY_SUBSETTER
+#endif
+
+// PDF's notion of symbolic vs non-symbolic is related to the character set, not
+// symbols vs. characters. Rarely is a font the right character set to call it
+// non-symbolic, so always call it symbolic. (PDF 1.4 spec, section 5.7.1)
+static const int kPdfSymbolic = 4;
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+// File-Local Functions
+///////////////////////////////////////////////////////////////////////////////
+
+bool parsePFBSection(const uint8_t** src, size_t* len, int sectionType,
+ size_t* size) {
+ // PFB sections have a two or six bytes header. 0x80 and a one byte
+ // section type followed by a four byte section length. Type one is
+ // an ASCII section (includes a length), type two is a binary section
+ // (includes a length) and type three is an EOF marker with no length.
+ const uint8_t* buf = *src;
+ if (*len < 2 || buf[0] != 0x80 || buf[1] != sectionType) {
+ return false;
+ } else if (buf[1] == 3) {
+ return true;
+ } else if (*len < 6) {
+ return false;
+ }
+
+ *size = (size_t)buf[2] | ((size_t)buf[3] << 8) | ((size_t)buf[4] << 16) |
+ ((size_t)buf[5] << 24);
+ size_t consumed = *size + 6;
+ if (consumed > *len) {
+ return false;
+ }
+ *src = *src + consumed;
+ *len = *len - consumed;
+ return true;
+}
+
+bool parsePFB(const uint8_t* src, size_t size, size_t* headerLen,
+ size_t* dataLen, size_t* trailerLen) {
+ const uint8_t* srcPtr = src;
+ size_t remaining = size;
+
+ return parsePFBSection(&srcPtr, &remaining, 1, headerLen) &&
+ parsePFBSection(&srcPtr, &remaining, 2, dataLen) &&
+ parsePFBSection(&srcPtr, &remaining, 1, trailerLen) &&
+ parsePFBSection(&srcPtr, &remaining, 3, NULL);
+}
+
+/* The sections of a PFA file are implicitly defined. The body starts
+ * after the line containing "eexec," and the trailer starts with 512
+ * literal 0's followed by "cleartomark" (plus arbitrary white space).
+ *
+ * This function assumes that src is NUL terminated, but the NUL
+ * termination is not included in size.
+ *
+ */
+bool parsePFA(const char* src, size_t size, size_t* headerLen,
+ size_t* hexDataLen, size_t* dataLen, size_t* trailerLen) {
+ const char* end = src + size;
+
+ const char* dataPos = strstr(src, "eexec");
+ if (!dataPos) {
+ return false;
+ }
+ dataPos += strlen("eexec");
+ while ((*dataPos == '\n' || *dataPos == '\r' || *dataPos == ' ') &&
+ dataPos < end) {
+ dataPos++;
+ }
+ *headerLen = dataPos - src;
+
+ const char* trailerPos = strstr(dataPos, "cleartomark");
+ if (!trailerPos) {
+ return false;
+ }
+ int zeroCount = 0;
+ for (trailerPos--; trailerPos > dataPos && zeroCount < 512; trailerPos--) {
+ if (*trailerPos == '\n' || *trailerPos == '\r' || *trailerPos == ' ') {
+ continue;
+ } else if (*trailerPos == '0') {
+ zeroCount++;
+ } else {
+ return false;
+ }
+ }
+ if (zeroCount != 512) {
+ return false;
+ }
+
+ *hexDataLen = trailerPos - src - *headerLen;
+ *trailerLen = size - *headerLen - *hexDataLen;
+
+ // Verify that the data section is hex encoded and count the bytes.
+ int nibbles = 0;
+ for (; dataPos < trailerPos; dataPos++) {
+ if (isspace(*dataPos)) {
+ continue;
+ }
+ if (!isxdigit(*dataPos)) {
+ return false;
+ }
+ nibbles++;
+ }
+ *dataLen = (nibbles + 1) / 2;
+
+ return true;
+}
+
+int8_t hexToBin(uint8_t c) {
+ if (!isxdigit(c)) {
+ return -1;
+ } else if (c <= '9') {
+ return c - '0';
+ } else if (c <= 'F') {
+ return c - 'A' + 10;
+ } else if (c <= 'f') {
+ return c - 'a' + 10;
+ }
+ return -1;
+}
+
+SkStream* handleType1Stream(SkStream* srcStream, size_t* headerLen,
+ size_t* dataLen, size_t* trailerLen) {
+ // srcStream may be backed by a file or a unseekable fd, so we may not be
+ // able to use skip(), rewind(), or getMemoryBase(). read()ing through
+ // the input only once is doable, but very ugly. Furthermore, it'd be nice
+ // if the data was NUL terminated so that we can use strstr() to search it.
+ // Make as few copies as possible given these constraints.
+ SkDynamicMemoryWStream dynamicStream;
+ SkAutoTUnref<SkMemoryStream> staticStream;
+ SkData* data = NULL;
+ const uint8_t* src;
+ size_t srcLen;
+ if ((srcLen = srcStream->getLength()) > 0) {
+ staticStream.reset(new SkMemoryStream(srcLen + 1));
+ src = (const uint8_t*)staticStream->getMemoryBase();
+ if (srcStream->getMemoryBase() != NULL) {
+ memcpy((void *)src, srcStream->getMemoryBase(), srcLen);
+ } else {
+ size_t read = 0;
+ while (read < srcLen) {
+ size_t got = srcStream->read((void *)staticStream->getAtPos(),
+ srcLen - read);
+ if (got == 0) {
+ return NULL;
+ }
+ read += got;
+ staticStream->seek(read);
+ }
+ }
+ ((uint8_t *)src)[srcLen] = 0;
+ } else {
+ static const size_t kBufSize = 4096;
+ uint8_t buf[kBufSize];
+ size_t amount;
+ while ((amount = srcStream->read(buf, kBufSize)) > 0) {
+ dynamicStream.write(buf, amount);
+ }
+ amount = 0;
+ dynamicStream.write(&amount, 1); // NULL terminator.
+ data = dynamicStream.copyToData();
+ src = data->bytes();
+ srcLen = data->size() - 1;
+ }
+
+ // this handles releasing the data we may have gotten from dynamicStream.
+ // if data is null, it is a no-op
+ SkAutoDataUnref aud(data);
+
+ if (parsePFB(src, srcLen, headerLen, dataLen, trailerLen)) {
+ SkMemoryStream* result =
+ new SkMemoryStream(*headerLen + *dataLen + *trailerLen);
+ memcpy((char*)result->getAtPos(), src + 6, *headerLen);
+ result->seek(*headerLen);
+ memcpy((char*)result->getAtPos(), src + 6 + *headerLen + 6, *dataLen);
+ result->seek(*headerLen + *dataLen);
+ memcpy((char*)result->getAtPos(), src + 6 + *headerLen + 6 + *dataLen,
+ *trailerLen);
+ result->rewind();
+ return result;
+ }
+
+ // A PFA has to be converted for PDF.
+ size_t hexDataLen;
+ if (parsePFA((const char*)src, srcLen, headerLen, &hexDataLen, dataLen,
+ trailerLen)) {
+ SkMemoryStream* result =
+ new SkMemoryStream(*headerLen + *dataLen + *trailerLen);
+ memcpy((char*)result->getAtPos(), src, *headerLen);
+ result->seek(*headerLen);
+
+ const uint8_t* hexData = src + *headerLen;
+ const uint8_t* trailer = hexData + hexDataLen;
+ size_t outputOffset = 0;
+ uint8_t dataByte = 0; // To hush compiler.
+ bool highNibble = true;
+ for (; hexData < trailer; hexData++) {
+ int8_t curNibble = hexToBin(*hexData);
+ if (curNibble < 0) {
+ continue;
+ }
+ if (highNibble) {
+ dataByte = curNibble << 4;
+ highNibble = false;
+ } else {
+ dataByte |= curNibble;
+ highNibble = true;
+ ((char *)result->getAtPos())[outputOffset++] = dataByte;
+ }
+ }
+ if (!highNibble) {
+ ((char *)result->getAtPos())[outputOffset++] = dataByte;
+ }
+ SkASSERT(outputOffset == *dataLen);
+ result->seek(*headerLen + outputOffset);
+
+ memcpy((char *)result->getAtPos(), src + *headerLen + hexDataLen,
+ *trailerLen);
+ result->rewind();
+ return result;
+ }
+
+ return NULL;
+}
+
+// scale from em-units to base-1000, returning as a SkScalar
+SkScalar scaleFromFontUnits(int16_t val, uint16_t emSize) {
+ SkScalar scaled = SkIntToScalar(val);
+ if (emSize == 1000) {
+ return scaled;
+ } else {
+ return SkScalarMulDiv(scaled, 1000, emSize);
+ }
+}
+
+void setGlyphWidthAndBoundingBox(SkScalar width, SkIRect box,
+ SkWStream* content) {
+ // Specify width and bounding box for the glyph.
+ SkPDFScalar::Append(width, content);
+ content->writeText(" 0 ");
+ content->writeDecAsText(box.fLeft);
+ content->writeText(" ");
+ content->writeDecAsText(box.fTop);
+ content->writeText(" ");
+ content->writeDecAsText(box.fRight);
+ content->writeText(" ");
+ content->writeDecAsText(box.fBottom);
+ content->writeText(" d1\n");
+}
+
+SkPDFArray* makeFontBBox(SkIRect glyphBBox, uint16_t emSize) {
+ SkPDFArray* bbox = new SkPDFArray;
+ bbox->reserve(4);
+ bbox->appendScalar(scaleFromFontUnits(glyphBBox.fLeft, emSize));
+ bbox->appendScalar(scaleFromFontUnits(glyphBBox.fBottom, emSize));
+ bbox->appendScalar(scaleFromFontUnits(glyphBBox.fRight, emSize));
+ bbox->appendScalar(scaleFromFontUnits(glyphBBox.fTop, emSize));
+ return bbox;
+}
+
+SkPDFArray* appendWidth(const int16_t& width, uint16_t emSize,
+ SkPDFArray* array) {
+ array->appendScalar(scaleFromFontUnits(width, emSize));
+ return array;
+}
+
+SkPDFArray* appendVerticalAdvance(
+ const SkAdvancedTypefaceMetrics::VerticalMetric& advance,
+ uint16_t emSize, SkPDFArray* array) {
+ appendWidth(advance.fVerticalAdvance, emSize, array);
+ appendWidth(advance.fOriginXDisp, emSize, array);
+ appendWidth(advance.fOriginYDisp, emSize, array);
+ return array;
+}
+
+template <typename Data>
+SkPDFArray* composeAdvanceData(
+ SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* advanceInfo,
+ uint16_t emSize,
+ SkPDFArray* (*appendAdvance)(const Data& advance, uint16_t emSize,
+ SkPDFArray* array),
+ Data* defaultAdvance) {
+ SkPDFArray* result = new SkPDFArray();
+ for (; advanceInfo != NULL; advanceInfo = advanceInfo->fNext.get()) {
+ switch (advanceInfo->fType) {
+ case SkAdvancedTypefaceMetrics::WidthRange::kDefault: {
+ SkASSERT(advanceInfo->fAdvance.count() == 1);
+ *defaultAdvance = advanceInfo->fAdvance[0];
+ break;
+ }
+ case SkAdvancedTypefaceMetrics::WidthRange::kRange: {
+ SkAutoTUnref<SkPDFArray> advanceArray(new SkPDFArray());
+ for (int j = 0; j < advanceInfo->fAdvance.count(); j++)
+ appendAdvance(advanceInfo->fAdvance[j], emSize,
+ advanceArray.get());
+ result->appendInt(advanceInfo->fStartId);
+ result->append(advanceArray.get());
+ break;
+ }
+ case SkAdvancedTypefaceMetrics::WidthRange::kRun: {
+ SkASSERT(advanceInfo->fAdvance.count() == 1);
+ result->appendInt(advanceInfo->fStartId);
+ result->appendInt(advanceInfo->fEndId);
+ appendAdvance(advanceInfo->fAdvance[0], emSize, result);
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+static void append_tounicode_header(SkDynamicMemoryWStream* cmap) {
+ // 12 dict begin: 12 is an Adobe-suggested value. Shall not change.
+ // It's there to prevent old version Adobe Readers from malfunctioning.
+ const char* kHeader =
+ "/CIDInit /ProcSet findresource begin\n"
+ "12 dict begin\n"
+ "begincmap\n";
+ cmap->writeText(kHeader);
+
+ // The /CIDSystemInfo must be consistent to the one in
+ // SkPDFFont::populateCIDFont().
+ // We can not pass over the system info object here because the format is
+ // different. This is not a reference object.
+ const char* kSysInfo =
+ "/CIDSystemInfo\n"
+ "<< /Registry (Adobe)\n"
+ "/Ordering (UCS)\n"
+ "/Supplement 0\n"
+ ">> def\n";
+ cmap->writeText(kSysInfo);
+
+ // The CMapName must be consistent to /CIDSystemInfo above.
+ // /CMapType 2 means ToUnicode.
+ // We specify codespacerange from 0x0000 to 0xFFFF because we convert our
+ // code table from unsigned short (16-bits). Codespace range just tells the
+ // PDF processor the valid range. It does not matter whether a complete
+ // mapping is provided or not.
+ const char* kTypeInfo =
+ "/CMapName /Adobe-Identity-UCS def\n"
+ "/CMapType 2 def\n"
+ "1 begincodespacerange\n"
+ "<0000> <FFFF>\n"
+ "endcodespacerange\n";
+ cmap->writeText(kTypeInfo);
+}
+
+static void append_cmap_footer(SkDynamicMemoryWStream* cmap) {
+ const char* kFooter =
+ "endcmap\n"
+ "CMapName currentdict /CMap defineresource pop\n"
+ "end\n"
+ "end";
+ cmap->writeText(kFooter);
+}
+
+struct BFChar {
+ uint16_t fGlyphId;
+ SkUnichar fUnicode;
+};
+
+struct BFRange {
+ uint16_t fStart;
+ uint16_t fEnd;
+ SkUnichar fUnicode;
+};
+
+static void append_bfchar_section(const SkTDArray<BFChar>& bfchar,
+ SkDynamicMemoryWStream* cmap) {
+ // PDF spec defines that every bf* list can have at most 100 entries.
+ for (int i = 0; i < bfchar.count(); i += 100) {
+ int count = bfchar.count() - i;
+ count = SkMin32(count, 100);
+ cmap->writeDecAsText(count);
+ cmap->writeText(" beginbfchar\n");
+ for (int j = 0; j < count; ++j) {
+ cmap->writeText("<");
+ cmap->writeHexAsText(bfchar[i + j].fGlyphId, 4);
+ cmap->writeText("> <");
+ cmap->writeHexAsText(bfchar[i + j].fUnicode, 4);
+ cmap->writeText(">\n");
+ }
+ cmap->writeText("endbfchar\n");
+ }
+}
+
+static void append_bfrange_section(const SkTDArray<BFRange>& bfrange,
+ SkDynamicMemoryWStream* cmap) {
+ // PDF spec defines that every bf* list can have at most 100 entries.
+ for (int i = 0; i < bfrange.count(); i += 100) {
+ int count = bfrange.count() - i;
+ count = SkMin32(count, 100);
+ cmap->writeDecAsText(count);
+ cmap->writeText(" beginbfrange\n");
+ for (int j = 0; j < count; ++j) {
+ cmap->writeText("<");
+ cmap->writeHexAsText(bfrange[i + j].fStart, 4);
+ cmap->writeText("> <");
+ cmap->writeHexAsText(bfrange[i + j].fEnd, 4);
+ cmap->writeText("> <");
+ cmap->writeHexAsText(bfrange[i + j].fUnicode, 4);
+ cmap->writeText(">\n");
+ }
+ cmap->writeText("endbfrange\n");
+ }
+}
+
+// Generate <bfchar> and <bfrange> table according to PDF spec 1.4 and Adobe
+// Technote 5014.
+// The function is not static so we can test it in unit tests.
+//
+// Current implementation guarantees bfchar and bfrange entries do not overlap.
+//
+// Current implementation does not attempt aggresive optimizations against
+// following case because the specification is not clear.
+//
+// 4 beginbfchar 1 beginbfchar
+// <0003> <0013> <0020> <0014>
+// <0005> <0015> to endbfchar
+// <0007> <0017> 1 beginbfrange
+// <0020> <0014> <0003> <0007> <0013>
+// endbfchar endbfrange
+//
+// Adobe Technote 5014 said: "Code mappings (unlike codespace ranges) may
+// overlap, but succeeding maps superceded preceding maps."
+//
+// In case of searching text in PDF, bfrange will have higher precedence so
+// typing char id 0x0014 in search box will get glyph id 0x0004 first. However,
+// the spec does not mention how will this kind of conflict being resolved.
+//
+// For the worst case (having 65536 continuous unicode and we use every other
+// one of them), the possible savings by aggressive optimization is 416KB
+// pre-compressed and does not provide enough motivation for implementation.
+
+// FIXME: this should be in a header so that it is separately testable
+// ( see caller in tests/ToUnicode.cpp )
+void append_cmap_sections(const SkTDArray<SkUnichar>& glyphToUnicode,
+ const SkPDFGlyphSet* subset,
+ SkDynamicMemoryWStream* cmap);
+
+void append_cmap_sections(const SkTDArray<SkUnichar>& glyphToUnicode,
+ const SkPDFGlyphSet* subset,
+ SkDynamicMemoryWStream* cmap) {
+ if (glyphToUnicode.isEmpty()) {
+ return;
+ }
+
+ SkTDArray<BFChar> bfcharEntries;
+ SkTDArray<BFRange> bfrangeEntries;
+
+ BFRange currentRangeEntry = {0, 0, 0};
+ bool rangeEmpty = true;
+ const int count = glyphToUnicode.count();
+
+ for (int i = 0; i < count + 1; ++i) {
+ bool inSubset = i < count && (subset == NULL || subset->has(i));
+ if (!rangeEmpty) {
+ // PDF spec requires bfrange not changing the higher byte,
+ // e.g. <1035> <10FF> <2222> is ok, but
+ // <1035> <1100> <2222> is no good
+ bool inRange =
+ i == currentRangeEntry.fEnd + 1 &&
+ i >> 8 == currentRangeEntry.fStart >> 8 &&
+ i < count &&
+ glyphToUnicode[i] == currentRangeEntry.fUnicode + i -
+ currentRangeEntry.fStart;
+ if (!inSubset || !inRange) {
+ if (currentRangeEntry.fEnd > currentRangeEntry.fStart) {
+ bfrangeEntries.push(currentRangeEntry);
+ } else {
+ BFChar* entry = bfcharEntries.append();
+ entry->fGlyphId = currentRangeEntry.fStart;
+ entry->fUnicode = currentRangeEntry.fUnicode;
+ }
+ rangeEmpty = true;
+ }
+ }
+ if (inSubset) {
+ currentRangeEntry.fEnd = i;
+ if (rangeEmpty) {
+ currentRangeEntry.fStart = i;
+ currentRangeEntry.fUnicode = glyphToUnicode[i];
+ rangeEmpty = false;
+ }
+ }
+ }
+
+ // The spec requires all bfchar entries for a font must come before bfrange
+ // entries.
+ append_bfchar_section(bfcharEntries, cmap);
+ append_bfrange_section(bfrangeEntries, cmap);
+}
+
+static SkPDFStream* generate_tounicode_cmap(
+ const SkTDArray<SkUnichar>& glyphToUnicode,
+ const SkPDFGlyphSet* subset) {
+ SkDynamicMemoryWStream cmap;
+ append_tounicode_header(&cmap);
+ append_cmap_sections(glyphToUnicode, subset, &cmap);
+ append_cmap_footer(&cmap);
+ SkAutoTUnref<SkMemoryStream> cmapStream(new SkMemoryStream());
+ cmapStream->setData(cmap.copyToData())->unref();
+ return new SkPDFStream(cmapStream.get());
+}
+
+#if defined (SK_SFNTLY_SUBSETTER)
+static void sk_delete_array(const void* ptr, size_t, void*) {
+ // Use C-style cast to cast away const and cast type simultaneously.
+ delete[] (unsigned char*)ptr;
+}
+#endif
+
+static int get_subset_font_stream(const char* fontName,
+ const SkTypeface* typeface,
+ const SkTDArray<uint32_t>& subset,
+ SkPDFStream** fontStream) {
+ int ttcIndex;
+ SkAutoTUnref<SkStream> fontData(typeface->openStream(&ttcIndex));
+
+ int fontSize = fontData->getLength();
+
+#if defined (SK_SFNTLY_SUBSETTER)
+ // Read font into buffer.
+ SkPDFStream* subsetFontStream = NULL;
+ SkTDArray<unsigned char> originalFont;
+ originalFont.setCount(fontSize);
+ if (fontData->read(originalFont.begin(), fontSize) == (size_t)fontSize) {
+ unsigned char* subsetFont = NULL;
+ // sfntly requires unsigned int* to be passed in, as far as we know,
+ // unsigned int is equivalent to uint32_t on all platforms.
+ SK_COMPILE_ASSERT(sizeof(unsigned int) == sizeof(uint32_t),
+ unsigned_int_not_32_bits);
+ int subsetFontSize = SfntlyWrapper::SubsetFont(fontName,
+ originalFont.begin(),
+ fontSize,
+ subset.begin(),
+ subset.count(),
+ &subsetFont);
+ if (subsetFontSize > 0 && subsetFont != NULL) {
+ SkAutoDataUnref data(SkData::NewWithProc(subsetFont,
+ subsetFontSize,
+ sk_delete_array,
+ NULL));
+ subsetFontStream = new SkPDFStream(data.get());
+ fontSize = subsetFontSize;
+ }
+ }
+ if (subsetFontStream) {
+ *fontStream = subsetFontStream;
+ return fontSize;
+ }
+ fontData->rewind();
+#else
+ sk_ignore_unused_variable(fontName);
+ sk_ignore_unused_variable(subset);
+#endif
+
+ // Fail over: just embed the whole font.
+ *fontStream = new SkPDFStream(fontData.get());
+ return fontSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFGlyphSet
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFGlyphSet::SkPDFGlyphSet() : fBitSet(SK_MaxU16 + 1) {
+}
+
+void SkPDFGlyphSet::set(const uint16_t* glyphIDs, int numGlyphs) {
+ for (int i = 0; i < numGlyphs; ++i) {
+ fBitSet.setBit(glyphIDs[i], true);
+ }
+}
+
+bool SkPDFGlyphSet::has(uint16_t glyphID) const {
+ return fBitSet.isBitSet(glyphID);
+}
+
+void SkPDFGlyphSet::merge(const SkPDFGlyphSet& usage) {
+ fBitSet.orBits(usage.fBitSet);
+}
+
+void SkPDFGlyphSet::exportTo(SkTDArray<unsigned int>* glyphIDs) const {
+ fBitSet.exportTo(glyphIDs);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFGlyphSetMap
+///////////////////////////////////////////////////////////////////////////////
+SkPDFGlyphSetMap::FontGlyphSetPair::FontGlyphSetPair(SkPDFFont* font,
+ SkPDFGlyphSet* glyphSet)
+ : fFont(font),
+ fGlyphSet(glyphSet) {
+}
+
+SkPDFGlyphSetMap::F2BIter::F2BIter(const SkPDFGlyphSetMap& map) {
+ reset(map);
+}
+
+const SkPDFGlyphSetMap::FontGlyphSetPair* SkPDFGlyphSetMap::F2BIter::next() const {
+ if (fIndex >= fMap->count()) {
+ return NULL;
+ }
+ return &((*fMap)[fIndex++]);
+}
+
+void SkPDFGlyphSetMap::F2BIter::reset(const SkPDFGlyphSetMap& map) {
+ fMap = &(map.fMap);
+ fIndex = 0;
+}
+
+SkPDFGlyphSetMap::SkPDFGlyphSetMap() {
+}
+
+SkPDFGlyphSetMap::~SkPDFGlyphSetMap() {
+ reset();
+}
+
+void SkPDFGlyphSetMap::merge(const SkPDFGlyphSetMap& usage) {
+ for (int i = 0; i < usage.fMap.count(); ++i) {
+ SkPDFGlyphSet* myUsage = getGlyphSetForFont(usage.fMap[i].fFont);
+ myUsage->merge(*(usage.fMap[i].fGlyphSet));
+ }
+}
+
+void SkPDFGlyphSetMap::reset() {
+ for (int i = 0; i < fMap.count(); ++i) {
+ delete fMap[i].fGlyphSet; // Should not be NULL.
+ }
+ fMap.reset();
+}
+
+void SkPDFGlyphSetMap::noteGlyphUsage(SkPDFFont* font, const uint16_t* glyphIDs,
+ int numGlyphs) {
+ SkPDFGlyphSet* subset = getGlyphSetForFont(font);
+ if (subset) {
+ subset->set(glyphIDs, numGlyphs);
+ }
+}
+
+SkPDFGlyphSet* SkPDFGlyphSetMap::getGlyphSetForFont(SkPDFFont* font) {
+ int index = fMap.count();
+ for (int i = 0; i < index; ++i) {
+ if (fMap[i].fFont == font) {
+ return fMap[i].fGlyphSet;
+ }
+ }
+ fMap.append();
+ index = fMap.count() - 1;
+ fMap[index].fFont = font;
+ fMap[index].fGlyphSet = new SkPDFGlyphSet();
+ return fMap[index].fGlyphSet;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFFont
+///////////////////////////////////////////////////////////////////////////////
+
+/* Font subset design: It would be nice to be able to subset fonts
+ * (particularly type 3 fonts), but it's a lot of work and not a priority.
+ *
+ * Resources are canonicalized and uniqueified by pointer so there has to be
+ * some additional state indicating which subset of the font is used. It
+ * must be maintained at the page granularity and then combined at the document
+ * granularity. a) change SkPDFFont to fill in its state on demand, kind of
+ * like SkPDFGraphicState. b) maintain a per font glyph usage class in each
+ * page/pdf device. c) in the document, retrieve the per font glyph usage
+ * from each page and combine it and ask for a resource with that subset.
+ */
+
+SkPDFFont::~SkPDFFont() {
+ SkAutoMutexAcquire lock(CanonicalFontsMutex());
+ 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(fTypeface->uniqueID(),
+ fFirstGlyphID,
+ &indexFound) &&
+ index == indexFound));
+ if (index >= 0) {
+ CanonicalFonts().removeShuffle(index);
+ }
+ fResources.unrefAll();
+}
+
+void SkPDFFont::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
+}
+
+SkTypeface* SkPDFFont::typeface() {
+ return fTypeface.get();
+}
+
+SkAdvancedTypefaceMetrics::FontType SkPDFFont::getType() {
+ return fFontType;
+}
+
+bool SkPDFFont::hasGlyph(uint16_t id) {
+ return (id >= fFirstGlyphID && id <= fLastGlyphID) || id == 0;
+}
+
+size_t SkPDFFont::glyphsToPDFFontEncoding(uint16_t* glyphIDs,
+ size_t numGlyphs) {
+ // A font with multibyte glyphs will support all glyph IDs in a single font.
+ if (this->multiByteGlyphs()) {
+ return numGlyphs;
+ }
+
+ for (size_t i = 0; i < numGlyphs; i++) {
+ if (glyphIDs[i] == 0) {
+ continue;
+ }
+ if (glyphIDs[i] < fFirstGlyphID || glyphIDs[i] > fLastGlyphID) {
+ return i;
+ }
+ glyphIDs[i] -= (fFirstGlyphID - 1);
+ }
+
+ return numGlyphs;
+}
+
+// static
+SkPDFFont* SkPDFFont::GetFontResource(SkTypeface* typeface, uint16_t glyphID) {
+ SkAutoMutexAcquire lock(CanonicalFontsMutex());
+
+ SkAutoResolveDefaultTypeface autoResolve(typeface);
+ typeface = autoResolve.get();
+
+ const uint32_t fontID = typeface->uniqueID();
+ int relatedFontIndex;
+ if (Find(fontID, glyphID, &relatedFontIndex)) {
+ CanonicalFonts()[relatedFontIndex].fFont->ref();
+ return CanonicalFonts()[relatedFontIndex].fFont;
+ }
+
+ SkAutoTUnref<SkAdvancedTypefaceMetrics> fontMetrics;
+ SkPDFDict* relatedFontDescriptor = NULL;
+ if (relatedFontIndex >= 0) {
+ SkPDFFont* relatedFont = CanonicalFonts()[relatedFontIndex].fFont;
+ fontMetrics.reset(relatedFont->fontInfo());
+ SkSafeRef(fontMetrics.get());
+ 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;
+ info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>(
+ info, SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo);
+#if !defined (SK_SFNTLY_SUBSETTER)
+ info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>(
+ info, SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo);
+#endif
+ fontMetrics.reset(
+ typeface->getAdvancedTypefaceMetrics(info, NULL, 0));
+#if defined (SK_SFNTLY_SUBSETTER)
+ if (fontMetrics.get() &&
+ fontMetrics->fType != SkAdvancedTypefaceMetrics::kTrueType_Font) {
+ // Font does not support subsetting, get new info with advance.
+ info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>(
+ info, SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo);
+ fontMetrics.reset(
+ typeface->getAdvancedTypefaceMetrics(info, NULL, 0));
+ }
+#endif
+ }
+
+ SkPDFFont* font = Create(fontMetrics.get(), typeface, glyphID,
+ relatedFontDescriptor);
+ FontRec newEntry(font, fontID, font->fFirstGlyphID);
+ CanonicalFonts().push(newEntry);
+ return font; // Return the reference new SkPDFFont() created.
+}
+
+SkPDFFont* SkPDFFont::getFontSubset(const SkPDFGlyphSet*) {
+ return NULL; // Default: no support.
+}
+
+// static
+SkTDArray<SkPDFFont::FontRec>& SkPDFFont::CanonicalFonts() {
+ // This initialization is only thread safe with gcc.
+ static SkTDArray<FontRec> gCanonicalFonts;
+ return gCanonicalFonts;
+}
+
+// static
+SkBaseMutex& SkPDFFont::CanonicalFontsMutex() {
+ // This initialization is only thread safe with gcc, or when
+ // POD-style mutex initialization is used.
+ SK_DECLARE_STATIC_MUTEX(gCanonicalFontsMutex);
+ return gCanonicalFontsMutex;
+}
+
+// static
+bool SkPDFFont::Find(uint32_t fontID, uint16_t glyphID, int* index) {
+ // TODO(vandebo): Optimize this, do only one search?
+ FontRec search(NULL, fontID, glyphID);
+ *index = CanonicalFonts().find(search);
+ if (*index >= 0) {
+ return true;
+ }
+ search.fGlyphID = 0;
+ *index = CanonicalFonts().find(search);
+ return false;
+}
+
+SkPDFFont::SkPDFFont(SkAdvancedTypefaceMetrics* info, SkTypeface* typeface,
+ SkPDFDict* relatedFontDescriptor)
+ : SkPDFDict("Font"),
+ fTypeface(ref_or_default(typeface)),
+ fFirstGlyphID(1),
+ fLastGlyphID(info ? info->fLastGlyphID : 0),
+ fFontInfo(info),
+ fDescriptor(relatedFontDescriptor) {
+ SkSafeRef(typeface);
+ SkSafeRef(info);
+ if (info == NULL) {
+ fFontType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
+ } else if (info->fMultiMaster) {
+ fFontType = SkAdvancedTypefaceMetrics::kOther_Font;
+ } else {
+ fFontType = info->fType;
+ }
+}
+
+// static
+SkPDFFont* SkPDFFont::Create(SkAdvancedTypefaceMetrics* info,
+ SkTypeface* typeface, uint16_t glyphID,
+ SkPDFDict* relatedFontDescriptor) {
+ SkAdvancedTypefaceMetrics::FontType type =
+ info ? info->fType : SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
+
+ if (info && info->fMultiMaster) {
+ NOT_IMPLEMENTED(true, true);
+ return new SkPDFType3Font(info,
+ typeface,
+ glyphID);
+ }
+ if (type == SkAdvancedTypefaceMetrics::kType1CID_Font ||
+ type == SkAdvancedTypefaceMetrics::kTrueType_Font) {
+ SkASSERT(relatedFontDescriptor == NULL);
+ return new SkPDFType0Font(info, typeface);
+ }
+ if (type == SkAdvancedTypefaceMetrics::kType1_Font) {
+ return new SkPDFType1Font(info,
+ typeface,
+ glyphID,
+ relatedFontDescriptor);
+ }
+
+ SkASSERT(type == SkAdvancedTypefaceMetrics::kCFF_Font ||
+ type == SkAdvancedTypefaceMetrics::kOther_Font ||
+ type == SkAdvancedTypefaceMetrics::kNotEmbeddable_Font);
+
+ return new SkPDFType3Font(info, typeface, glyphID);
+}
+
+SkAdvancedTypefaceMetrics* SkPDFFont::fontInfo() {
+ return fFontInfo.get();
+}
+
+void SkPDFFont::setFontInfo(SkAdvancedTypefaceMetrics* info) {
+ if (info == NULL || info == fFontInfo.get()) {
+ return;
+ }
+ fFontInfo.reset(info);
+ SkSafeRef(info);
+}
+
+uint16_t SkPDFFont::firstGlyphID() const {
+ return fFirstGlyphID;
+}
+
+uint16_t SkPDFFont::lastGlyphID() const {
+ return fLastGlyphID;
+}
+
+void SkPDFFont::setLastGlyphID(uint16_t glyphID) {
+ fLastGlyphID = glyphID;
+}
+
+void SkPDFFont::addResource(SkPDFObject* object) {
+ SkASSERT(object != NULL);
+ fResources.push(object);
+ object->ref();
+}
+
+SkPDFDict* SkPDFFont::getFontDescriptor() {
+ return fDescriptor.get();
+}
+
+void SkPDFFont::setFontDescriptor(SkPDFDict* descriptor) {
+ fDescriptor.reset(descriptor);
+ SkSafeRef(descriptor);
+}
+
+bool SkPDFFont::addCommonFontDescriptorEntries(int16_t defaultWidth) {
+ if (fDescriptor.get() == NULL) {
+ return false;
+ }
+
+ const uint16_t emSize = fFontInfo->fEmSize;
+
+ fDescriptor->insertName("FontName", fFontInfo->fFontName);
+ fDescriptor->insertInt("Flags", fFontInfo->fStyle | kPdfSymbolic);
+ fDescriptor->insertScalar("Ascent",
+ scaleFromFontUnits(fFontInfo->fAscent, emSize));
+ fDescriptor->insertScalar("Descent",
+ scaleFromFontUnits(fFontInfo->fDescent, emSize));
+ fDescriptor->insertScalar("StemV",
+ scaleFromFontUnits(fFontInfo->fStemV, emSize));
+ fDescriptor->insertScalar("CapHeight",
+ scaleFromFontUnits(fFontInfo->fCapHeight, emSize));
+ fDescriptor->insertInt("ItalicAngle", fFontInfo->fItalicAngle);
+ fDescriptor->insert("FontBBox", makeFontBBox(fFontInfo->fBBox,
+ fFontInfo->fEmSize))->unref();
+
+ if (defaultWidth > 0) {
+ fDescriptor->insertScalar("MissingWidth",
+ scaleFromFontUnits(defaultWidth, emSize));
+ }
+ return true;
+}
+
+void SkPDFFont::adjustGlyphRangeForSingleByteEncoding(int16_t glyphID) {
+ // Single byte glyph encoding supports a max of 255 glyphs.
+ fFirstGlyphID = glyphID - (glyphID - 1) % 255;
+ if (fLastGlyphID > fFirstGlyphID + 255 - 1) {
+ fLastGlyphID = fFirstGlyphID + 255 - 1;
+ }
+}
+
+bool SkPDFFont::FontRec::operator==(const SkPDFFont::FontRec& b) const {
+ if (fFontID != b.fFontID) {
+ return false;
+ }
+ if (fFont != NULL && b.fFont != NULL) {
+ return fFont->fFirstGlyphID == b.fFont->fFirstGlyphID &&
+ fFont->fLastGlyphID == b.fFont->fLastGlyphID;
+ }
+ if (fGlyphID == 0 || b.fGlyphID == 0) {
+ return true;
+ }
+
+ if (fFont != NULL) {
+ return fFont->fFirstGlyphID <= b.fGlyphID &&
+ b.fGlyphID <= fFont->fLastGlyphID;
+ } else if (b.fFont != NULL) {
+ return b.fFont->fFirstGlyphID <= fGlyphID &&
+ fGlyphID <= b.fFont->fLastGlyphID;
+ }
+ return fGlyphID == b.fGlyphID;
+}
+
+SkPDFFont::FontRec::FontRec(SkPDFFont* font, uint32_t fontID, uint16_t glyphID)
+ : fFont(font),
+ fFontID(fontID),
+ fGlyphID(glyphID) {
+}
+
+void SkPDFFont::populateToUnicodeTable(const SkPDFGlyphSet* subset) {
+ if (fFontInfo == NULL || fFontInfo->fGlyphToUnicode.begin() == NULL) {
+ return;
+ }
+ SkAutoTUnref<SkPDFStream> pdfCmap(
+ generate_tounicode_cmap(fFontInfo->fGlyphToUnicode, subset));
+ addResource(pdfCmap.get());
+ insert("ToUnicode", new SkPDFObjRef(pdfCmap.get()))->unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFType0Font
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFType0Font::SkPDFType0Font(SkAdvancedTypefaceMetrics* info,
+ SkTypeface* typeface)
+ : SkPDFFont(info, typeface, NULL) {
+ SkDEBUGCODE(fPopulated = false);
+}
+
+SkPDFType0Font::~SkPDFType0Font() {}
+
+SkPDFFont* SkPDFType0Font::getFontSubset(const SkPDFGlyphSet* subset) {
+ SkPDFType0Font* newSubset = new SkPDFType0Font(fontInfo(), typeface());
+ newSubset->populate(subset);
+ return newSubset;
+}
+
+#ifdef SK_DEBUG
+void SkPDFType0Font::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ SkASSERT(fPopulated);
+ return INHERITED::emitObject(stream, catalog, indirect);
+}
+#endif
+
+bool SkPDFType0Font::populate(const SkPDFGlyphSet* subset) {
+ insertName("Subtype", "Type0");
+ insertName("BaseFont", fontInfo()->fFontName);
+ insertName("Encoding", "Identity-H");
+
+ SkAutoTUnref<SkPDFCIDFont> newCIDFont(
+ new SkPDFCIDFont(fontInfo(), typeface(), subset));
+ addResource(newCIDFont.get());
+ SkAutoTUnref<SkPDFArray> descendantFonts(new SkPDFArray());
+ descendantFonts->append(new SkPDFObjRef(newCIDFont.get()))->unref();
+ insert("DescendantFonts", descendantFonts.get());
+
+ populateToUnicodeTable(subset);
+
+ SkDEBUGCODE(fPopulated = true);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFCIDFont
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFCIDFont::SkPDFCIDFont(SkAdvancedTypefaceMetrics* info,
+ SkTypeface* typeface, const SkPDFGlyphSet* subset)
+ : SkPDFFont(info, typeface, NULL) {
+ populate(subset);
+}
+
+SkPDFCIDFont::~SkPDFCIDFont() {}
+
+bool SkPDFCIDFont::addFontDescriptor(int16_t defaultWidth,
+ const SkTDArray<uint32_t>* subset) {
+ SkAutoTUnref<SkPDFDict> descriptor(new SkPDFDict("FontDescriptor"));
+ setFontDescriptor(descriptor.get());
+ addResource(descriptor.get());
+
+ switch (getType()) {
+ case SkAdvancedTypefaceMetrics::kTrueType_Font: {
+ SkASSERT(subset);
+ // Font subsetting
+ SkPDFStream* rawStream = NULL;
+ int fontSize = get_subset_font_stream(fontInfo()->fFontName.c_str(),
+ typeface(),
+ *subset,
+ &rawStream);
+ SkASSERT(fontSize);
+ SkASSERT(rawStream);
+ SkAutoTUnref<SkPDFStream> fontStream(rawStream);
+ addResource(fontStream.get());
+
+ fontStream->insertInt("Length1", fontSize);
+ descriptor->insert("FontFile2",
+ new SkPDFObjRef(fontStream.get()))->unref();
+ break;
+ }
+ case SkAdvancedTypefaceMetrics::kCFF_Font:
+ case SkAdvancedTypefaceMetrics::kType1CID_Font: {
+ int ttcIndex;
+ SkAutoTUnref<SkStream> fontData(typeface()->openStream(&ttcIndex));
+ SkAutoTUnref<SkPDFStream> fontStream(
+ new SkPDFStream(fontData.get()));
+ addResource(fontStream.get());
+
+ if (getType() == SkAdvancedTypefaceMetrics::kCFF_Font) {
+ fontStream->insertName("Subtype", "Type1C");
+ } else {
+ fontStream->insertName("Subtype", "CIDFontType0c");
+ }
+ descriptor->insert("FontFile3",
+ new SkPDFObjRef(fontStream.get()))->unref();
+ break;
+ }
+ default:
+ SkASSERT(false);
+ }
+
+ insert("FontDescriptor", new SkPDFObjRef(descriptor.get()))->unref();
+ return addCommonFontDescriptorEntries(defaultWidth);
+}
+
+bool SkPDFCIDFont::populate(const SkPDFGlyphSet* subset) {
+ // Generate new font metrics with advance info for true type fonts.
+ if (fontInfo()->fType == SkAdvancedTypefaceMetrics::kTrueType_Font) {
+ // Generate glyph id array.
+ SkTDArray<uint32_t> glyphIDs;
+ glyphIDs.push(0); // Always include glyph 0.
+ if (subset) {
+ subset->exportTo(&glyphIDs);
+ }
+
+ SkAdvancedTypefaceMetrics::PerGlyphInfo info;
+ info = SkAdvancedTypefaceMetrics::kGlyphNames_PerGlyphInfo;
+ info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>(
+ info, SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo);
+ uint32_t* glyphs = (glyphIDs.count() == 1) ? NULL : glyphIDs.begin();
+ uint32_t glyphsCount = glyphs ? glyphIDs.count() : 0;
+ SkAutoTUnref<SkAdvancedTypefaceMetrics> fontMetrics(
+ typeface()->getAdvancedTypefaceMetrics(info, glyphs, glyphsCount));
+ setFontInfo(fontMetrics.get());
+ addFontDescriptor(0, &glyphIDs);
+ } else {
+ // Other CID fonts
+ addFontDescriptor(0, NULL);
+ }
+
+ insertName("BaseFont", fontInfo()->fFontName);
+
+ if (getType() == SkAdvancedTypefaceMetrics::kType1CID_Font) {
+ insertName("Subtype", "CIDFontType0");
+ } else if (getType() == SkAdvancedTypefaceMetrics::kTrueType_Font) {
+ insertName("Subtype", "CIDFontType2");
+ insertName("CIDToGIDMap", "Identity");
+ } else {
+ SkASSERT(false);
+ }
+
+ SkAutoTUnref<SkPDFDict> sysInfo(new SkPDFDict);
+ sysInfo->insert("Registry", new SkPDFString("Adobe"))->unref();
+ sysInfo->insert("Ordering", new SkPDFString("Identity"))->unref();
+ sysInfo->insertInt("Supplement", 0);
+ insert("CIDSystemInfo", sysInfo.get());
+
+ if (fontInfo()->fGlyphWidths.get()) {
+ int16_t defaultWidth = 0;
+ SkAutoTUnref<SkPDFArray> widths(
+ composeAdvanceData(fontInfo()->fGlyphWidths.get(),
+ fontInfo()->fEmSize, &appendWidth,
+ &defaultWidth));
+ if (widths->size())
+ insert("W", widths.get());
+ if (defaultWidth != 0) {
+ insertScalar("DW", scaleFromFontUnits(defaultWidth,
+ fontInfo()->fEmSize));
+ }
+ }
+ if (fontInfo()->fVerticalMetrics.get()) {
+ struct SkAdvancedTypefaceMetrics::VerticalMetric defaultAdvance;
+ defaultAdvance.fVerticalAdvance = 0;
+ defaultAdvance.fOriginXDisp = 0;
+ defaultAdvance.fOriginYDisp = 0;
+ SkAutoTUnref<SkPDFArray> advances(
+ composeAdvanceData(fontInfo()->fVerticalMetrics.get(),
+ fontInfo()->fEmSize, &appendVerticalAdvance,
+ &defaultAdvance));
+ if (advances->size())
+ insert("W2", advances.get());
+ if (defaultAdvance.fVerticalAdvance ||
+ defaultAdvance.fOriginXDisp ||
+ defaultAdvance.fOriginYDisp) {
+ insert("DW2", appendVerticalAdvance(defaultAdvance,
+ fontInfo()->fEmSize,
+ new SkPDFArray))->unref();
+ }
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFType1Font
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFType1Font::SkPDFType1Font(SkAdvancedTypefaceMetrics* info,
+ SkTypeface* typeface,
+ uint16_t glyphID,
+ SkPDFDict* relatedFontDescriptor)
+ : SkPDFFont(info, typeface, relatedFontDescriptor) {
+ populate(glyphID);
+}
+
+SkPDFType1Font::~SkPDFType1Font() {}
+
+bool SkPDFType1Font::addFontDescriptor(int16_t defaultWidth) {
+ if (getFontDescriptor() != NULL) {
+ SkPDFDict* descriptor = getFontDescriptor();
+ addResource(descriptor);
+ insert("FontDescriptor", new SkPDFObjRef(descriptor))->unref();
+ return true;
+ }
+
+ SkAutoTUnref<SkPDFDict> descriptor(new SkPDFDict("FontDescriptor"));
+ setFontDescriptor(descriptor.get());
+
+ int ttcIndex;
+ size_t header SK_INIT_TO_AVOID_WARNING;
+ size_t data SK_INIT_TO_AVOID_WARNING;
+ size_t trailer SK_INIT_TO_AVOID_WARNING;
+ SkAutoTUnref<SkStream> rawFontData(typeface()->openStream(&ttcIndex));
+ SkStream* fontData = handleType1Stream(rawFontData.get(), &header, &data,
+ &trailer);
+ if (fontData == NULL) {
+ return false;
+ }
+ SkAutoTUnref<SkPDFStream> fontStream(new SkPDFStream(fontData));
+ addResource(fontStream.get());
+ fontStream->insertInt("Length1", header);
+ fontStream->insertInt("Length2", data);
+ fontStream->insertInt("Length3", trailer);
+ descriptor->insert("FontFile", new SkPDFObjRef(fontStream.get()))->unref();
+
+ addResource(descriptor.get());
+ insert("FontDescriptor", new SkPDFObjRef(descriptor.get()))->unref();
+
+ return addCommonFontDescriptorEntries(defaultWidth);
+}
+
+bool SkPDFType1Font::populate(int16_t glyphID) {
+ SkASSERT(!fontInfo()->fVerticalMetrics.get());
+ SkASSERT(fontInfo()->fGlyphWidths.get());
+
+ adjustGlyphRangeForSingleByteEncoding(glyphID);
+
+ int16_t defaultWidth = 0;
+ const SkAdvancedTypefaceMetrics::WidthRange* widthRangeEntry = NULL;
+ const SkAdvancedTypefaceMetrics::WidthRange* widthEntry;
+ for (widthEntry = fontInfo()->fGlyphWidths.get();
+ widthEntry != NULL;
+ widthEntry = widthEntry->fNext.get()) {
+ switch (widthEntry->fType) {
+ case SkAdvancedTypefaceMetrics::WidthRange::kDefault:
+ defaultWidth = widthEntry->fAdvance[0];
+ break;
+ case SkAdvancedTypefaceMetrics::WidthRange::kRun:
+ SkASSERT(false);
+ break;
+ case SkAdvancedTypefaceMetrics::WidthRange::kRange:
+ SkASSERT(widthRangeEntry == NULL);
+ widthRangeEntry = widthEntry;
+ break;
+ }
+ }
+
+ if (!addFontDescriptor(defaultWidth)) {
+ return false;
+ }
+
+ insertName("Subtype", "Type1");
+ insertName("BaseFont", fontInfo()->fFontName);
+
+ addWidthInfoFromRange(defaultWidth, widthRangeEntry);
+
+ SkAutoTUnref<SkPDFDict> encoding(new SkPDFDict("Encoding"));
+ insert("Encoding", encoding.get());
+
+ SkAutoTUnref<SkPDFArray> encDiffs(new SkPDFArray);
+ encoding->insert("Differences", encDiffs.get());
+
+ encDiffs->reserve(lastGlyphID() - firstGlyphID() + 2);
+ encDiffs->appendInt(1);
+ for (int gID = firstGlyphID(); gID <= lastGlyphID(); gID++) {
+ encDiffs->appendName(fontInfo()->fGlyphNames->get()[gID].c_str());
+ }
+
+ return true;
+}
+
+void SkPDFType1Font::addWidthInfoFromRange(
+ int16_t defaultWidth,
+ const SkAdvancedTypefaceMetrics::WidthRange* widthRangeEntry) {
+ SkAutoTUnref<SkPDFArray> widthArray(new SkPDFArray());
+ int firstChar = 0;
+ if (widthRangeEntry) {
+ const uint16_t emSize = fontInfo()->fEmSize;
+ int startIndex = firstGlyphID() - widthRangeEntry->fStartId;
+ int endIndex = startIndex + lastGlyphID() - firstGlyphID() + 1;
+ if (startIndex < 0)
+ startIndex = 0;
+ if (endIndex > widthRangeEntry->fAdvance.count())
+ endIndex = widthRangeEntry->fAdvance.count();
+ if (widthRangeEntry->fStartId == 0) {
+ appendWidth(widthRangeEntry->fAdvance[0], emSize, widthArray.get());
+ } else {
+ firstChar = startIndex + widthRangeEntry->fStartId;
+ }
+ for (int i = startIndex; i < endIndex; i++) {
+ appendWidth(widthRangeEntry->fAdvance[i], emSize, widthArray.get());
+ }
+ } else {
+ appendWidth(defaultWidth, 1000, widthArray.get());
+ }
+ insertInt("FirstChar", firstChar);
+ insertInt("LastChar", firstChar + widthArray->size() - 1);
+ insert("Widths", widthArray.get());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFType3Font
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFType3Font::SkPDFType3Font(SkAdvancedTypefaceMetrics* info,
+ SkTypeface* typeface,
+ uint16_t glyphID)
+ : SkPDFFont(info, typeface, NULL) {
+ populate(glyphID);
+}
+
+SkPDFType3Font::~SkPDFType3Font() {}
+
+bool SkPDFType3Font::populate(int16_t glyphID) {
+ SkPaint paint;
+ paint.setTypeface(typeface());
+ paint.setTextSize(1000);
+ SkAutoGlyphCache autoCache(paint, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+ // If fLastGlyphID isn't set (because there is not fFontInfo), look it up.
+ if (lastGlyphID() == 0) {
+ setLastGlyphID(cache->getGlyphCount() - 1);
+ }
+
+ adjustGlyphRangeForSingleByteEncoding(glyphID);
+
+ insertName("Subtype", "Type3");
+ // Flip about the x-axis and scale by 1/1000.
+ SkMatrix fontMatrix;
+ fontMatrix.setScale(SkScalarInvert(1000), -SkScalarInvert(1000));
+ insert("FontMatrix", SkPDFUtils::MatrixToArray(fontMatrix))->unref();
+
+ SkAutoTUnref<SkPDFDict> charProcs(new SkPDFDict);
+ insert("CharProcs", charProcs.get());
+
+ SkAutoTUnref<SkPDFDict> encoding(new SkPDFDict("Encoding"));
+ insert("Encoding", encoding.get());
+
+ SkAutoTUnref<SkPDFArray> encDiffs(new SkPDFArray);
+ encoding->insert("Differences", encDiffs.get());
+ encDiffs->reserve(lastGlyphID() - firstGlyphID() + 2);
+ encDiffs->appendInt(1);
+
+ SkAutoTUnref<SkPDFArray> widthArray(new SkPDFArray());
+
+ SkIRect bbox = SkIRect::MakeEmpty();
+ for (int gID = firstGlyphID(); gID <= lastGlyphID(); gID++) {
+ SkString characterName;
+ characterName.printf("gid%d", gID);
+ encDiffs->appendName(characterName.c_str());
+
+ const SkGlyph& glyph = cache->getGlyphIDMetrics(gID);
+ widthArray->appendScalar(SkFixedToScalar(glyph.fAdvanceX));
+ SkIRect glyphBBox = SkIRect::MakeXYWH(glyph.fLeft, glyph.fTop,
+ glyph.fWidth, glyph.fHeight);
+ bbox.join(glyphBBox);
+
+ SkDynamicMemoryWStream content;
+ setGlyphWidthAndBoundingBox(SkFixedToScalar(glyph.fAdvanceX), glyphBBox,
+ &content);
+ const SkPath* path = cache->findPath(glyph);
+ if (path) {
+ SkPDFUtils::EmitPath(*path, paint.getStyle(), &content);
+ SkPDFUtils::PaintPath(paint.getStyle(), path->getFillType(),
+ &content);
+ }
+ SkAutoTUnref<SkMemoryStream> glyphStream(new SkMemoryStream());
+ glyphStream->setData(content.copyToData())->unref();
+
+ SkAutoTUnref<SkPDFStream> glyphDescription(
+ new SkPDFStream(glyphStream.get()));
+ addResource(glyphDescription.get());
+ charProcs->insert(characterName.c_str(),
+ new SkPDFObjRef(glyphDescription.get()))->unref();
+ }
+
+ insert("FontBBox", makeFontBBox(bbox, 1000))->unref();
+ insertInt("FirstChar", firstGlyphID());
+ insertInt("LastChar", lastGlyphID());
+ insert("Widths", widthArray.get());
+ insertName("CIDToGIDMap", "Identity");
+
+ populateToUnicodeTable(NULL);
+ return true;
+}
diff --git a/pdf/SkPDFFont.h b/pdf/SkPDFFont.h
new file mode 100644
index 00000000..f5d358f3
--- /dev/null
+++ b/pdf/SkPDFFont.h
@@ -0,0 +1,203 @@
+
+/*
+ * 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 SkPDFFont_DEFINED
+#define SkPDFFont_DEFINED
+
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkBitSet.h"
+#include "SkPDFTypes.h"
+#include "SkTDArray.h"
+#include "SkThread.h"
+#include "SkTypeface.h"
+
+class SkPaint;
+class SkPDFCatalog;
+class SkPDFFont;
+
+class SkPDFGlyphSet : public SkNoncopyable {
+public:
+ SkPDFGlyphSet();
+
+ void set(const uint16_t* glyphIDs, int numGlyphs);
+ bool has(uint16_t glyphID) const;
+ void merge(const SkPDFGlyphSet& usage);
+ void exportTo(SkTDArray<uint32_t>* glyphIDs) const;
+
+private:
+ SkBitSet fBitSet;
+};
+
+class SkPDFGlyphSetMap : public SkNoncopyable {
+public:
+ struct FontGlyphSetPair {
+ FontGlyphSetPair(SkPDFFont* font, SkPDFGlyphSet* glyphSet);
+
+ SkPDFFont* fFont;
+ SkPDFGlyphSet* fGlyphSet;
+ };
+
+ SkPDFGlyphSetMap();
+ ~SkPDFGlyphSetMap();
+
+ class F2BIter {
+ public:
+ explicit F2BIter(const SkPDFGlyphSetMap& map);
+ const FontGlyphSetPair* next() const;
+ void reset(const SkPDFGlyphSetMap& map);
+
+ private:
+ const SkTDArray<FontGlyphSetPair>* fMap;
+ mutable int fIndex;
+ };
+
+ void merge(const SkPDFGlyphSetMap& usage);
+ void reset();
+
+ void noteGlyphUsage(SkPDFFont* font, const uint16_t* glyphIDs,
+ int numGlyphs);
+
+private:
+ SkPDFGlyphSet* getGlyphSetForFont(SkPDFFont* font);
+
+ SkTDArray<FontGlyphSetPair> fMap;
+};
+
+
+/** \class SkPDFFont
+ A PDF Object class representing a font. The font may have resources
+ attached to it in order to embed the font. SkPDFFonts are canonicalized
+ so that resource deduplication will only include one copy of a font.
+ This class uses the same pattern as SkPDFGraphicState, a static weak
+ reference to each instantiated class.
+*/
+class SkPDFFont : public SkPDFDict {
+public:
+ virtual ~SkPDFFont();
+
+ virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+ /** Returns the typeface represented by this class. Returns NULL for the
+ * default typeface.
+ */
+ SkTypeface* typeface();
+
+ /** Returns the font type represented in this font. For Type0 fonts,
+ * returns the type of the decendant font.
+ */
+ virtual SkAdvancedTypefaceMetrics::FontType getType();
+
+ /** Returns true if this font encoding supports glyph IDs above 255.
+ */
+ virtual bool multiByteGlyphs() const = 0;
+
+ /** Return true if this font has an encoding for the passed glyph id.
+ */
+ bool hasGlyph(uint16_t glyphID);
+
+ /** Convert (in place) the input glyph IDs into the font encoding. If the
+ * font has more glyphs than can be encoded (like a type 1 font with more
+ * than 255 glyphs) this method only converts up to the first out of range
+ * glyph ID.
+ * @param glyphIDs The input text as glyph IDs.
+ * @param numGlyphs The number of input glyphs.
+ * @return Returns the number of glyphs consumed.
+ */
+ size_t glyphsToPDFFontEncoding(uint16_t* glyphIDs, size_t numGlyphs);
+
+ /** Get the font resource for the passed typeface and glyphID. The
+ * reference count of the object is incremented and it is the caller's
+ * responsibility to unreference it when done. This is needed to
+ * accommodate the weak reference pattern used when the returned object
+ * is new and has no other references.
+ * @param typeface The typeface to find.
+ * @param glyphID Specify which section of a large font is of interest.
+ */
+ static SkPDFFont* GetFontResource(SkTypeface* typeface,
+ uint16_t glyphID);
+
+ /** Subset the font based on usage set. Returns a SkPDFFont instance with
+ * subset.
+ * @param usage Glyph subset requested.
+ * @return NULL if font does not support subsetting, a new instance
+ * of SkPDFFont otherwise.
+ */
+ virtual SkPDFFont* getFontSubset(const SkPDFGlyphSet* usage);
+
+protected:
+ // Common constructor to handle common members.
+ SkPDFFont(SkAdvancedTypefaceMetrics* fontInfo, SkTypeface* typeface,
+ SkPDFDict* relatedFontDescriptor);
+
+ // Accessors for subclass.
+ SkAdvancedTypefaceMetrics* fontInfo();
+ void setFontInfo(SkAdvancedTypefaceMetrics* info);
+ uint16_t firstGlyphID() const;
+ uint16_t lastGlyphID() const;
+ void setLastGlyphID(uint16_t glyphID);
+
+ // Add object to resource list.
+ void addResource(SkPDFObject* object);
+
+ // Accessors for FontDescriptor associated with this object.
+ SkPDFDict* getFontDescriptor();
+ void setFontDescriptor(SkPDFDict* descriptor);
+
+ // Add common entries to FontDescriptor.
+ bool addCommonFontDescriptorEntries(int16_t defaultWidth);
+
+ /** Set fFirstGlyphID and fLastGlyphID to span at most 255 glyphs,
+ * including the passed glyphID.
+ */
+ void adjustGlyphRangeForSingleByteEncoding(int16_t glyphID);
+
+ // Generate ToUnicode table according to glyph usage subset.
+ // If subset is NULL, all available glyph ids will be used.
+ void populateToUnicodeTable(const SkPDFGlyphSet* subset);
+
+ // Create instances of derived types based on fontInfo.
+ static SkPDFFont* Create(SkAdvancedTypefaceMetrics* fontInfo,
+ SkTypeface* typeface, uint16_t glyphID,
+ SkPDFDict* relatedFontDescriptor);
+
+ static bool Find(uint32_t fontID, uint16_t glyphID, int* index);
+
+private:
+ class FontRec {
+ public:
+ SkPDFFont* fFont;
+ uint32_t fFontID;
+ uint16_t fGlyphID;
+
+ // A fGlyphID of 0 with no fFont always matches.
+ bool operator==(const FontRec& b) const;
+ FontRec(SkPDFFont* font, uint32_t fontID, uint16_t fGlyphID);
+ };
+
+ SkAutoTUnref<SkTypeface> fTypeface;
+
+ // The glyph IDs accessible with this font. For Type1 (non CID) fonts,
+ // this will be a subset if the font has more than 255 glyphs.
+ uint16_t fFirstGlyphID;
+ uint16_t fLastGlyphID;
+ // The font info is only kept around after construction for large
+ // Type1 (non CID) fonts that need multiple "fonts" to access all glyphs.
+ SkAutoTUnref<SkAdvancedTypefaceMetrics> fFontInfo;
+ SkTDArray<SkPDFObject*> fResources;
+ SkAutoTUnref<SkPDFDict> fDescriptor;
+
+ SkAdvancedTypefaceMetrics::FontType fFontType;
+
+ // This should be made a hash table if performance is a problem.
+ static SkTDArray<FontRec>& CanonicalFonts();
+ static SkBaseMutex& CanonicalFontsMutex();
+};
+
+#endif
diff --git a/pdf/SkPDFFontImpl.h b/pdf/SkPDFFontImpl.h
new file mode 100755
index 00000000..4b49683a
--- /dev/null
+++ b/pdf/SkPDFFontImpl.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 SkPDFFontImpl_DEFINED
+#define SkPDFFontImpl_DEFINED
+
+#include "SkPDFFont.h"
+
+class SkPDFType0Font : public SkPDFFont {
+public:
+ virtual ~SkPDFType0Font();
+ virtual bool multiByteGlyphs() const { return true; }
+ SK_API virtual SkPDFFont* getFontSubset(const SkPDFGlyphSet* usage);
+#ifdef SK_DEBUG
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+#endif
+
+private:
+ friend class SkPDFFont; // to access the constructor
+#ifdef SK_DEBUG
+ bool fPopulated;
+ typedef SkPDFDict INHERITED;
+#endif
+
+ SkPDFType0Font(SkAdvancedTypefaceMetrics* info, SkTypeface* typeface);
+
+ bool populate(const SkPDFGlyphSet* subset);
+};
+
+class SkPDFCIDFont : public SkPDFFont {
+public:
+ virtual ~SkPDFCIDFont();
+ virtual bool multiByteGlyphs() const { return true; }
+
+private:
+ friend class SkPDFType0Font; // to access the constructor
+
+ SkPDFCIDFont(SkAdvancedTypefaceMetrics* info, SkTypeface* typeface,
+ const SkPDFGlyphSet* subset);
+
+ bool populate(const SkPDFGlyphSet* subset);
+ bool addFontDescriptor(int16_t defaultWidth,
+ const SkTDArray<uint32_t>* subset);
+};
+
+class SkPDFType1Font : public SkPDFFont {
+public:
+ virtual ~SkPDFType1Font();
+ virtual bool multiByteGlyphs() const { return false; }
+
+private:
+ friend class SkPDFFont; // to access the constructor
+
+ SkPDFType1Font(SkAdvancedTypefaceMetrics* info, SkTypeface* typeface,
+ uint16_t glyphID, SkPDFDict* relatedFontDescriptor);
+
+ bool populate(int16_t glyphID);
+ bool addFontDescriptor(int16_t defaultWidth);
+ void addWidthInfoFromRange(int16_t defaultWidth,
+ const SkAdvancedTypefaceMetrics::WidthRange* widthRangeEntry);
+};
+
+class SkPDFType3Font : public SkPDFFont {
+public:
+ virtual ~SkPDFType3Font();
+ virtual bool multiByteGlyphs() const { return false; }
+
+private:
+ friend class SkPDFFont; // to access the constructor
+
+ SkPDFType3Font(SkAdvancedTypefaceMetrics* info, SkTypeface* typeface, uint16_t glyphID);
+
+ bool populate(int16_t glyphID);
+};
+
+#endif
diff --git a/pdf/SkPDFFormXObject.cpp b/pdf/SkPDFFormXObject.cpp
new file mode 100644
index 00000000..1635b0ff
--- /dev/null
+++ b/pdf/SkPDFFormXObject.cpp
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright 2010 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 "SkPDFFormXObject.h"
+
+#include "SkMatrix.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFDevice.h"
+#include "SkPDFResourceDict.h"
+#include "SkPDFUtils.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+
+SkPDFFormXObject::SkPDFFormXObject(SkPDFDevice* device) {
+ // We don't want to keep around device because we'd have two copies
+ // of content, so reference or copy everything we need (content and
+ // resources).
+ SkTSet<SkPDFObject*> emptySet;
+ SkPDFResourceDict* resourceDict = device->getResourceDict();
+ resourceDict->getReferencedResources(emptySet, &fResources, false);
+
+ SkAutoTUnref<SkStream> content(device->content());
+ setData(content.get());
+
+ SkAutoTUnref<SkPDFArray> bboxArray(device->copyMediaBox());
+ init(NULL, resourceDict, bboxArray);
+
+ // We invert the initial transform and apply that to the xobject so that
+ // it doesn't get applied twice. We can't just undo it because it's
+ // embedded in things like shaders and images.
+ if (!device->initialTransform().isIdentity()) {
+ SkMatrix inverse;
+ if (!device->initialTransform().invert(&inverse)) {
+ // The initial transform should be invertible.
+ SkASSERT(false);
+ inverse.reset();
+ }
+ insert("Matrix", SkPDFUtils::MatrixToArray(inverse))->unref();
+ }
+}
+
+/**
+ * Creates a FormXObject from a content stream and associated resources.
+ */
+SkPDFFormXObject::SkPDFFormXObject(SkStream* content, SkRect bbox,
+ SkPDFResourceDict* resourceDict) {
+ SkTSet<SkPDFObject*> emptySet;
+ resourceDict->getReferencedResources(emptySet, &fResources, false);
+
+ setData(content);
+
+ SkAutoTUnref<SkPDFArray> bboxArray(SkPDFUtils::RectToArray(bbox));
+ init("DeviceRGB", resourceDict, bboxArray);
+}
+
+/**
+ * Common initialization code.
+ * Note that bbox is unreferenced here, so calling code does not need worry.
+ */
+void SkPDFFormXObject::init(const char* colorSpace,
+ SkPDFDict* resourceDict, SkPDFArray* bbox) {
+ insertName("Type", "XObject");
+ insertName("Subtype", "Form");
+ insert("Resources", resourceDict);
+ insert("BBox", bbox);
+
+ // Right now SkPDFFormXObject is only used for saveLayer, which implies
+ // isolated blending. Do this conditionally if that changes.
+ SkAutoTUnref<SkPDFDict> group(new SkPDFDict("Group"));
+ group->insertName("S", "Transparency");
+
+ if (colorSpace != NULL) {
+ group->insertName("CS", colorSpace);
+ }
+ group->insert("I", new SkPDFBool(true))->unref(); // Isolated.
+ insert("Group", group.get());
+}
+
+SkPDFFormXObject::~SkPDFFormXObject() {
+ fResources.unrefAll();
+}
+
+void SkPDFFormXObject::getResources(
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources.toArray(),
+ knownResourceObjects,
+ newResourceObjects);
+}
diff --git a/pdf/SkPDFFormXObject.h b/pdf/SkPDFFormXObject.h
new file mode 100644
index 00000000..38c04b1f
--- /dev/null
+++ b/pdf/SkPDFFormXObject.h
@@ -0,0 +1,61 @@
+
+/*
+ * Copyright 2010 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 SkPDFFormXObject_DEFINED
+#define SkPDFFormXObject_DEFINED
+
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkPDFResourceDict.h"
+#include "SkString.h"
+
+class SkMatrix;
+class SkPDFDevice;
+class SkPDFCatalog;
+
+/** \class SkPDFFormXObject
+
+ A form XObject; a self contained description of graphics objects. A form
+ XObject is basically a page object with slightly different syntax, that
+ can be drawn onto a page.
+*/
+
+// The caller could keep track of the form XObjects it creates and
+// canonicalize them, but the Skia API doesn't provide enough context to
+// automatically do it (trivially).
+class SkPDFFormXObject : public SkPDFStream {
+public:
+ /** Create a PDF form XObject. Entries for the dictionary entries are
+ * automatically added.
+ * @param device The set of graphical elements on this form.
+ */
+ explicit SkPDFFormXObject(SkPDFDevice* device);
+ /**
+ * Create a PDF form XObject from a raw content stream and associated
+ * resources.
+ */
+ explicit SkPDFFormXObject(SkStream* content,
+ SkRect bbox,
+ SkPDFResourceDict* resourceDict);
+ virtual ~SkPDFFormXObject();
+
+ // The SkPDFObject interface.
+ virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+private:
+ void init(const char* colorSpace,
+ SkPDFDict* resourceDict, SkPDFArray* bbox);
+
+ SkTSet<SkPDFObject*> fResources;
+};
+
+#endif
diff --git a/pdf/SkPDFGraphicState.cpp b/pdf/SkPDFGraphicState.cpp
new file mode 100644
index 00000000..43d22f30
--- /dev/null
+++ b/pdf/SkPDFGraphicState.cpp
@@ -0,0 +1,289 @@
+/*
+ * 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 "SkPDFFormXObject.h"
+#include "SkPDFGraphicState.h"
+#include "SkPDFUtils.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+
+static const char* blend_mode_from_xfermode(SkXfermode::Mode mode) {
+ switch (mode) {
+ case SkXfermode::kSrcOver_Mode: return "Normal";
+ // kModulate is not really like multipy but similar most of the time.
+ case SkXfermode::kModulate_Mode:
+ case SkXfermode::kMultiply_Mode: return "Multiply";
+ case SkXfermode::kScreen_Mode: return "Screen";
+ case SkXfermode::kOverlay_Mode: return "Overlay";
+ case SkXfermode::kDarken_Mode: return "Darken";
+ case SkXfermode::kLighten_Mode: return "Lighten";
+ case SkXfermode::kColorDodge_Mode: return "ColorDodge";
+ case SkXfermode::kColorBurn_Mode: return "ColorBurn";
+ case SkXfermode::kHardLight_Mode: return "HardLight";
+ case SkXfermode::kSoftLight_Mode: return "SoftLight";
+ case SkXfermode::kDifference_Mode: return "Difference";
+ case SkXfermode::kExclusion_Mode: return "Exclusion";
+ case SkXfermode::kHue_Mode: return "Hue";
+ case SkXfermode::kSaturation_Mode: return "Saturation";
+ case SkXfermode::kColor_Mode: return "Color";
+ case SkXfermode::kLuminosity_Mode: return "Luminosity";
+
+ // These are handled in SkPDFDevice::setUpContentEntry.
+ case SkXfermode::kClear_Mode:
+ case SkXfermode::kSrc_Mode:
+ case SkXfermode::kDst_Mode:
+ case SkXfermode::kDstOver_Mode:
+ case SkXfermode::kSrcIn_Mode:
+ case SkXfermode::kDstIn_Mode:
+ case SkXfermode::kSrcOut_Mode:
+ case SkXfermode::kDstOut_Mode:
+ return "Normal";
+
+ // TODO(vandebo): Figure out if we can support more of these modes.
+ case SkXfermode::kSrcATop_Mode:
+ case SkXfermode::kDstATop_Mode:
+ case SkXfermode::kXor_Mode:
+ case SkXfermode::kPlus_Mode:
+ return NULL;
+ }
+ return NULL;
+}
+
+SkPDFGraphicState::~SkPDFGraphicState() {
+ SkAutoMutexAcquire lock(CanonicalPaintsMutex());
+ if (!fSMask) {
+ int index = Find(fPaint);
+ SkASSERT(index >= 0);
+ SkASSERT(CanonicalPaints()[index].fGraphicState == this);
+ CanonicalPaints().removeShuffle(index);
+ }
+ fResources.unrefAll();
+}
+
+void SkPDFGraphicState::getResources(
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
+}
+
+void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ populateDict();
+ SkPDFDict::emitObject(stream, catalog, indirect);
+}
+
+// static
+size_t SkPDFGraphicState::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ populateDict();
+ return SkPDFDict::getOutputSize(catalog, indirect);
+}
+
+// static
+SkTDArray<SkPDFGraphicState::GSCanonicalEntry>&
+SkPDFGraphicState::CanonicalPaints() {
+ // This initialization is only thread safe with gcc.
+ static SkTDArray<SkPDFGraphicState::GSCanonicalEntry> gCanonicalPaints;
+ return gCanonicalPaints;
+}
+
+// static
+SkBaseMutex& SkPDFGraphicState::CanonicalPaintsMutex() {
+ // This initialization is only thread safe with gcc or when
+ // POD-style mutex initialization is used.
+ SK_DECLARE_STATIC_MUTEX(gCanonicalPaintsMutex);
+ return gCanonicalPaintsMutex;
+}
+
+// static
+SkPDFGraphicState* SkPDFGraphicState::GetGraphicStateForPaint(
+ const SkPaint& paint) {
+ SkAutoMutexAcquire lock(CanonicalPaintsMutex());
+ int index = Find(paint);
+ if (index >= 0) {
+ CanonicalPaints()[index].fGraphicState->ref();
+ return CanonicalPaints()[index].fGraphicState;
+ }
+ GSCanonicalEntry newEntry(new SkPDFGraphicState(paint));
+ CanonicalPaints().push(newEntry);
+ return newEntry.fGraphicState;
+}
+
+// static
+SkPDFObject* SkPDFGraphicState::GetInvertFunction() {
+ // This assumes that canonicalPaintsMutex is held.
+ static SkPDFStream* invertFunction = NULL;
+ if (!invertFunction) {
+ // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
+ // a type 2 function, so we use a type 4 function.
+ SkAutoTUnref<SkPDFArray> domainAndRange(new SkPDFArray);
+ domainAndRange->reserve(2);
+ domainAndRange->appendInt(0);
+ domainAndRange->appendInt(1);
+
+ static const char psInvert[] = "{1 exch sub}";
+ SkAutoTUnref<SkMemoryStream> psInvertStream(
+ new SkMemoryStream(&psInvert, strlen(psInvert), true));
+
+ invertFunction = new SkPDFStream(psInvertStream.get());
+ invertFunction->insertInt("FunctionType", 4);
+ invertFunction->insert("Domain", domainAndRange.get());
+ invertFunction->insert("Range", domainAndRange.get());
+ }
+ return invertFunction;
+}
+
+// static
+SkPDFGraphicState* SkPDFGraphicState::GetSMaskGraphicState(
+ SkPDFFormXObject* sMask, bool invert, SkPDFSMaskMode sMaskMode) {
+ // The practical chances of using the same mask more than once are unlikely
+ // enough that it's not worth canonicalizing.
+ SkAutoMutexAcquire lock(CanonicalPaintsMutex());
+
+ SkAutoTUnref<SkPDFDict> sMaskDict(new SkPDFDict("Mask"));
+ if (sMaskMode == kAlpha_SMaskMode) {
+ sMaskDict->insertName("S", "Alpha");
+ } else if (sMaskMode == kLuminosity_SMaskMode) {
+ sMaskDict->insertName("S", "Luminosity");
+ }
+ sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
+
+ SkPDFGraphicState* result = new SkPDFGraphicState;
+ result->fPopulated = true;
+ result->fSMask = true;
+ result->insertName("Type", "ExtGState");
+ result->insert("SMask", sMaskDict.get());
+ result->fResources.push(sMask);
+ sMask->ref();
+
+ if (invert) {
+ SkPDFObject* invertFunction = GetInvertFunction();
+ result->fResources.push(invertFunction);
+ invertFunction->ref();
+ sMaskDict->insert("TR", new SkPDFObjRef(invertFunction))->unref();
+ }
+
+ return result;
+}
+
+// static
+SkPDFGraphicState* SkPDFGraphicState::GetNoSMaskGraphicState() {
+ SkAutoMutexAcquire lock(CanonicalPaintsMutex());
+ static SkPDFGraphicState* noSMaskGS = NULL;
+ if (!noSMaskGS) {
+ noSMaskGS = new SkPDFGraphicState;
+ noSMaskGS->fPopulated = true;
+ noSMaskGS->fSMask = true;
+ noSMaskGS->insertName("Type", "ExtGState");
+ noSMaskGS->insertName("SMask", "None");
+ }
+ noSMaskGS->ref();
+ return noSMaskGS;
+}
+
+// static
+int SkPDFGraphicState::Find(const SkPaint& paint) {
+ GSCanonicalEntry search(&paint);
+ return CanonicalPaints().find(search);
+}
+
+SkPDFGraphicState::SkPDFGraphicState()
+ : fPopulated(false),
+ fSMask(false) {
+}
+
+SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
+ : fPaint(paint),
+ fPopulated(false),
+ fSMask(false) {
+}
+
+// populateDict and operator== have to stay in sync with each other.
+void SkPDFGraphicState::populateDict() {
+ if (!fPopulated) {
+ fPopulated = true;
+ insertName("Type", "ExtGState");
+
+ SkAutoTUnref<SkPDFScalar> alpha(
+ new SkPDFScalar(SkScalarDiv(fPaint.getAlpha(), 0xFF)));
+ insert("CA", alpha.get());
+ insert("ca", alpha.get());
+
+ SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch);
+ SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2);
+ insertInt("LC", fPaint.getStrokeCap());
+
+ SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch);
+ SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch);
+ SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2);
+ insertInt("LJ", fPaint.getStrokeJoin());
+
+ insertScalar("LW", fPaint.getStrokeWidth());
+ insertScalar("ML", fPaint.getStrokeMiter());
+ insert("SA", new SkPDFBool(true))->unref(); // Auto stroke adjustment.
+
+ SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
+ // If asMode fails, default to kSrcOver_Mode.
+ if (fPaint.getXfermode())
+ fPaint.getXfermode()->asMode(&xfermode);
+ // If we don't support the mode, just use kSrcOver_Mode.
+ if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
+ blend_mode_from_xfermode(xfermode) == NULL) {
+ xfermode = SkXfermode::kSrcOver_Mode;
+ NOT_IMPLEMENTED("unsupported xfermode", false);
+ }
+ insertName("BM", blend_mode_from_xfermode(xfermode));
+ }
+}
+
+// We're only interested in some fields of the SkPaint, so we have a custom
+// operator== function.
+bool SkPDFGraphicState::GSCanonicalEntry::operator==(
+ const SkPDFGraphicState::GSCanonicalEntry& gs) const {
+ const SkPaint* a = fPaint;
+ const SkPaint* b = gs.fPaint;
+ SkASSERT(a != NULL);
+ SkASSERT(b != NULL);
+
+ if (SkColorGetA(a->getColor()) != SkColorGetA(b->getColor()) ||
+ a->getStrokeCap() != b->getStrokeCap() ||
+ a->getStrokeJoin() != b->getStrokeJoin() ||
+ a->getStrokeWidth() != b->getStrokeWidth() ||
+ a->getStrokeMiter() != b->getStrokeMiter()) {
+ return false;
+ }
+
+ SkXfermode::Mode aXfermodeName = SkXfermode::kSrcOver_Mode;
+ SkXfermode* aXfermode = a->getXfermode();
+ if (aXfermode) {
+ aXfermode->asMode(&aXfermodeName);
+ }
+ if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
+ blend_mode_from_xfermode(aXfermodeName) == NULL) {
+ aXfermodeName = SkXfermode::kSrcOver_Mode;
+ }
+ const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName);
+ SkASSERT(aXfermodeString != NULL);
+
+ SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
+ SkXfermode* bXfermode = b->getXfermode();
+ if (bXfermode) {
+ bXfermode->asMode(&bXfermodeName);
+ }
+ if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
+ blend_mode_from_xfermode(bXfermodeName) == NULL) {
+ bXfermodeName = SkXfermode::kSrcOver_Mode;
+ }
+ const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName);
+ SkASSERT(bXfermodeString != NULL);
+
+ return strcmp(aXfermodeString, bXfermodeString) == 0;
+}
diff --git a/pdf/SkPDFGraphicState.h b/pdf/SkPDFGraphicState.h
new file mode 100644
index 00000000..84c42910
--- /dev/null
+++ b/pdf/SkPDFGraphicState.h
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright 2010 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 SkPDFGraphicState_DEFINED
+#define SkPDFGraphicState_DEFINED
+
+#include "SkPaint.h"
+#include "SkPDFTypes.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+
+class SkPDFFormXObject;
+
+/** \class SkPDFGraphicState
+ SkPaint objects roughly correspond to graphic state dictionaries that can
+ be installed. So that a given dictionary is only output to the pdf file
+ once, we want to canonicalize them. Static methods in this class manage
+ a weakly referenced set of SkPDFGraphicState objects: when the last
+ reference to a SkPDFGraphicState is removed, it removes itself from the
+ static set of objects.
+
+*/
+class SkPDFGraphicState : public SkPDFDict {
+public:
+ enum SkPDFSMaskMode {
+ kAlpha_SMaskMode,
+ kLuminosity_SMaskMode
+ };
+
+ virtual ~SkPDFGraphicState();
+
+ virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+ // Override emitObject and getOutputSize so that we can populate
+ // the dictionary on demand.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+ /** Get the graphic state for the passed SkPaint. The reference count of
+ * the object is incremented and it is the caller's responsibility to
+ * unreference it when done. This is needed to accommodate the weak
+ * reference pattern used when the returned object is new and has no
+ * other references.
+ * @param paint The SkPaint to emulate.
+ */
+ static SkPDFGraphicState* GetGraphicStateForPaint(const SkPaint& paint);
+
+ /** Make a graphic state that only sets the passed soft mask. The
+ * reference count of the object is incremented and it is the caller's
+ * responsibility to unreference it when done.
+ * @param sMask The form xobject to use as a soft mask.
+ * @param invert Indicates if the alpha of the sMask should be inverted.
+ * @param sMaskMode Whether to use alpha or luminosity for the sMask.
+ */
+ static SkPDFGraphicState* GetSMaskGraphicState(SkPDFFormXObject* sMask,
+ bool invert,
+ SkPDFSMaskMode sMaskMode);
+
+ /** Get a graphic state that only unsets the soft mask. The reference
+ * count of the object is incremented and it is the caller's responsibility
+ * to unreference it when done. This is needed to accommodate the weak
+ * reference pattern used when the returned object is new and has no
+ * other references.
+ */
+ static SkPDFGraphicState* GetNoSMaskGraphicState();
+
+private:
+ const SkPaint fPaint;
+ SkTDArray<SkPDFObject*> fResources;
+ bool fPopulated;
+ bool fSMask;
+
+ class GSCanonicalEntry {
+ public:
+ SkPDFGraphicState* fGraphicState;
+ const SkPaint* fPaint;
+
+ bool operator==(const GSCanonicalEntry& b) const;
+ explicit GSCanonicalEntry(SkPDFGraphicState* gs)
+ : fGraphicState(gs),
+ fPaint(&gs->fPaint) {}
+ explicit GSCanonicalEntry(const SkPaint* paint)
+ : fGraphicState(NULL),
+ fPaint(paint) {}
+ };
+
+ // This should be made a hash table if performance is a problem.
+ static SkTDArray<GSCanonicalEntry>& CanonicalPaints();
+ static SkBaseMutex& CanonicalPaintsMutex();
+
+ SkPDFGraphicState();
+ explicit SkPDFGraphicState(const SkPaint& paint);
+
+ void populateDict();
+
+ static SkPDFObject* GetInvertFunction();
+
+ static int Find(const SkPaint& paint);
+};
+
+#endif
diff --git a/pdf/SkPDFImage.cpp b/pdf/SkPDFImage.cpp
new file mode 100644
index 00000000..a5cb4c20
--- /dev/null
+++ b/pdf/SkPDFImage.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2010 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 "SkPDFImage.h"
+
+#include "SkBitmap.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkPDFCatalog.h"
+#include "SkRect.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkUnPreMultiply.h"
+
+namespace {
+
+void extractImageData(const SkBitmap& bitmap, const SkIRect& srcRect,
+ SkStream** imageData, SkStream** alphaData) {
+ SkMemoryStream* image = NULL;
+ SkMemoryStream* alpha = NULL;
+ bool hasAlpha = false;
+ bool isTransparent = false;
+
+ bitmap.lockPixels();
+ switch (bitmap.getConfig()) {
+ case SkBitmap::kIndex8_Config: {
+ const int rowBytes = srcRect.width();
+ image = new SkMemoryStream(rowBytes * srcRect.height());
+ uint8_t* dst = (uint8_t*)image->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes);
+ dst += rowBytes;
+ }
+ break;
+ }
+ case SkBitmap::kARGB_4444_Config: {
+ isTransparent = true;
+ const int rowBytes = (srcRect.width() * 3 + 1) / 2;
+ const int alphaRowBytes = (srcRect.width() + 1) / 2;
+ image = new SkMemoryStream(rowBytes * srcRect.height());
+ alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+ uint8_t* dst = (uint8_t*)image->getMemoryBase();
+ uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint16_t* src = bitmap.getAddr16(0, y);
+ int x;
+ for (x = srcRect.fLeft; x + 1 < srcRect.fRight; x += 2) {
+ dst[0] = (SkGetPackedR4444(src[x]) << 4) |
+ SkGetPackedG4444(src[x]);
+ dst[1] = (SkGetPackedB4444(src[x]) << 4) |
+ SkGetPackedR4444(src[x + 1]);
+ dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) |
+ SkGetPackedB4444(src[x + 1]);
+ dst += 3;
+ alphaDst[0] = (SkGetPackedA4444(src[x]) << 4) |
+ SkGetPackedA4444(src[x + 1]);
+ if (alphaDst[0] != 0xFF) {
+ hasAlpha = true;
+ }
+ if (alphaDst[0]) {
+ isTransparent = false;
+ }
+ alphaDst++;
+ }
+ if (srcRect.width() & 1) {
+ dst[0] = (SkGetPackedR4444(src[x]) << 4) |
+ SkGetPackedG4444(src[x]);
+ dst[1] = (SkGetPackedB4444(src[x]) << 4);
+ dst += 2;
+ alphaDst[0] = (SkGetPackedA4444(src[x]) << 4);
+ if (alphaDst[0] != 0xF0) {
+ hasAlpha = true;
+ }
+ if (alphaDst[0] & 0xF0) {
+ isTransparent = false;
+ }
+ alphaDst++;
+ }
+ }
+ break;
+ }
+ case SkBitmap::kRGB_565_Config: {
+ const int rowBytes = srcRect.width() * 3;
+ image = new SkMemoryStream(rowBytes * srcRect.height());
+ uint8_t* dst = (uint8_t*)image->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint16_t* src = bitmap.getAddr16(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ dst[0] = SkGetPackedR16(src[x]);
+ dst[1] = SkGetPackedG16(src[x]);
+ dst[2] = SkGetPackedB16(src[x]);
+ dst += 3;
+ }
+ }
+ break;
+ }
+ case SkBitmap::kARGB_8888_Config: {
+ isTransparent = true;
+ const int rowBytes = srcRect.width() * 3;
+ image = new SkMemoryStream(rowBytes * srcRect.height());
+ alpha = new SkMemoryStream(srcRect.width() * srcRect.height());
+ uint8_t* dst = (uint8_t*)image->getMemoryBase();
+ uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint32_t* src = bitmap.getAddr32(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ dst[0] = SkGetPackedR32(src[x]);
+ dst[1] = SkGetPackedG32(src[x]);
+ dst[2] = SkGetPackedB32(src[x]);
+ dst += 3;
+ alphaDst[0] = SkGetPackedA32(src[x]);
+ if (alphaDst[0] != 0xFF) {
+ hasAlpha = true;
+ }
+ if (alphaDst[0]) {
+ isTransparent = false;
+ }
+ alphaDst++;
+ }
+ }
+ break;
+ }
+ case SkBitmap::kA1_Config: {
+ isTransparent = true;
+ image = new SkMemoryStream(1);
+ ((uint8_t*)image->getMemoryBase())[0] = 0;
+
+ const int alphaRowBytes = (srcRect.width() + 7) / 8;
+ alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+ uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
+ int offset1 = srcRect.fLeft % 8;
+ int offset2 = 8 - offset1;
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint8_t* src = bitmap.getAddr1(0, y);
+ // This may read up to one byte after src, but the potentially
+ // invalid bits are never used for computation.
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x += 8) {
+ if (offset1) {
+ alphaDst[0] = src[x / 8] << offset1 |
+ src[x / 8 + 1] >> offset2;
+ } else {
+ alphaDst[0] = src[x / 8];
+ }
+ if (x + 7 < srcRect.fRight && alphaDst[0] != 0xFF) {
+ hasAlpha = true;
+ }
+ if (x + 7 < srcRect.fRight && alphaDst[0]) {
+ isTransparent = false;
+ }
+ alphaDst++;
+ }
+ // Calculate the mask of bits we're interested in within the
+ // last byte of alphaDst.
+ // width mod 8 == 1 -> 0x80 ... width mod 8 == 7 -> 0xFE
+ uint8_t mask = ~((1 << (8 - (srcRect.width() % 8))) - 1);
+ if (srcRect.width() % 8 && (alphaDst[-1] & mask) != mask) {
+ hasAlpha = true;
+ }
+ if (srcRect.width() % 8 && (alphaDst[-1] & mask)) {
+ isTransparent = false;
+ }
+ }
+ break;
+ }
+ case SkBitmap::kA8_Config: {
+ isTransparent = true;
+ image = new SkMemoryStream(1);
+ ((uint8_t*)image->getMemoryBase())[0] = 0;
+
+ const int alphaRowBytes = srcRect.width();
+ alpha = new SkMemoryStream(alphaRowBytes * srcRect.height());
+ uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase();
+ for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
+ uint8_t* src = bitmap.getAddr8(0, y);
+ for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
+ alphaDst[0] = src[x];
+ if (alphaDst[0] != 0xFF) {
+ hasAlpha = true;
+ }
+ if (alphaDst[0]) {
+ isTransparent = false;
+ }
+ alphaDst++;
+ }
+ }
+ break;
+ }
+ default:
+ SkASSERT(false);
+ }
+ bitmap.unlockPixels();
+
+ if (isTransparent) {
+ SkSafeUnref(image);
+ } else {
+ *imageData = image;
+ }
+
+ if (isTransparent || !hasAlpha) {
+ SkSafeUnref(alpha);
+ } else {
+ *alphaData = alpha;
+ }
+}
+
+SkPDFArray* makeIndexedColorSpace(SkColorTable* table) {
+ SkPDFArray* result = new SkPDFArray();
+ result->reserve(4);
+ result->appendName("Indexed");
+ result->appendName("DeviceRGB");
+ result->appendInt(table->count() - 1);
+
+ // Potentially, this could be represented in fewer bytes with a stream.
+ // Max size as a string is 1.5k.
+ SkString index;
+ for (int i = 0; i < table->count(); i++) {
+ char buf[3];
+ SkColor color = SkUnPreMultiply::PMColorToColor((*table)[i]);
+ buf[0] = SkGetPackedR32(color);
+ buf[1] = SkGetPackedG32(color);
+ buf[2] = SkGetPackedB32(color);
+ index.append(buf, 3);
+ }
+ result->append(new SkPDFString(index))->unref();
+ return result;
+}
+
+}; // namespace
+
+// static
+SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ EncodeToDCTStream encoder) {
+ if (bitmap.getConfig() == SkBitmap::kNo_Config) {
+ return NULL;
+ }
+
+ SkStream* imageData = NULL;
+ SkStream* alphaData = NULL;
+ extractImageData(bitmap, srcRect, &imageData, &alphaData);
+ SkAutoUnref unrefImageData(imageData);
+ SkAutoUnref unrefAlphaData(alphaData);
+ if (!imageData) {
+ SkASSERT(!alphaData);
+ return NULL;
+ }
+
+ SkPDFImage* image =
+ SkNEW_ARGS(SkPDFImage, (imageData, bitmap, srcRect, false, encoder));
+
+ if (alphaData != NULL) {
+ // Don't try to use DCT compression with alpha because alpha is small
+ // anyway and it could lead to artifacts.
+ image->addSMask(SkNEW_ARGS(SkPDFImage, (alphaData, bitmap, srcRect, true, NULL)))->unref();
+ }
+ return image;
+}
+
+SkPDFImage::~SkPDFImage() {
+ fResources.unrefAll();
+}
+
+SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) {
+ fResources.push(mask);
+ mask->ref();
+ insert("SMask", new SkPDFObjRef(mask))->unref();
+ return mask;
+}
+
+void SkPDFImage::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
+}
+
+SkPDFImage::SkPDFImage(SkStream* imageData,
+ const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ bool doingAlpha,
+ EncodeToDCTStream encoder)
+ : SkPDFImageStream(imageData, bitmap, srcRect, encoder) {
+ SkBitmap::Config config = bitmap.getConfig();
+ bool alphaOnly = (config == SkBitmap::kA1_Config ||
+ config == SkBitmap::kA8_Config);
+
+ insertName("Type", "XObject");
+ insertName("Subtype", "Image");
+
+ if (!doingAlpha && alphaOnly) {
+ // For alpha only images, we stretch a single pixel of black for
+ // the color/shape part.
+ SkAutoTUnref<SkPDFInt> one(new SkPDFInt(1));
+ insert("Width", one.get());
+ insert("Height", one.get());
+ } else {
+ insertInt("Width", srcRect.width());
+ insertInt("Height", srcRect.height());
+ }
+
+ // if (!image mask) {
+ if (doingAlpha || alphaOnly) {
+ insertName("ColorSpace", "DeviceGray");
+ } else if (config == SkBitmap::kIndex8_Config) {
+ SkAutoLockPixels alp(bitmap);
+ insert("ColorSpace",
+ makeIndexedColorSpace(bitmap.getColorTable()))->unref();
+ } else {
+ insertName("ColorSpace", "DeviceRGB");
+ }
+ // }
+
+ int bitsPerComp = 8;
+ if (config == SkBitmap::kARGB_4444_Config) {
+ bitsPerComp = 4;
+ } else if (doingAlpha && config == SkBitmap::kA1_Config) {
+ bitsPerComp = 1;
+ }
+ insertInt("BitsPerComponent", bitsPerComp);
+
+ if (config == SkBitmap::kRGB_565_Config) {
+ SkAutoTUnref<SkPDFInt> zeroVal(new SkPDFInt(0));
+ SkAutoTUnref<SkPDFScalar> scale5Val(
+ new SkPDFScalar(SkFloatToScalar(8.2258f))); // 255/2^5-1
+ SkAutoTUnref<SkPDFScalar> scale6Val(
+ new SkPDFScalar(SkFloatToScalar(4.0476f))); // 255/2^6-1
+ SkAutoTUnref<SkPDFArray> decodeValue(new SkPDFArray());
+ decodeValue->reserve(6);
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale5Val.get());
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale6Val.get());
+ decodeValue->append(zeroVal.get());
+ decodeValue->append(scale5Val.get());
+ insert("Decode", decodeValue.get());
+ }
+}
diff --git a/pdf/SkPDFImage.h b/pdf/SkPDFImage.h
new file mode 100644
index 00000000..31f89408
--- /dev/null
+++ b/pdf/SkPDFImage.h
@@ -0,0 +1,71 @@
+
+/*
+ * Copyright 2010 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 SkPDFImage_DEFINED
+#define SkPDFImage_DEFINED
+
+#include "SkPDFDevice.h"
+#include "SkPDFImageStream.h"
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+
+class SkBitmap;
+class SkPDFCatalog;
+struct SkIRect;
+
+/** \class SkPDFImage
+
+ An image XObject.
+*/
+
+// We could play the same trick here as is done in SkPDFGraphicState, storing
+// a copy of the Bitmap object (not the pixels), the pixel generation number,
+// and settings used from the paint to canonicalize image objects.
+class SkPDFImage : public SkPDFImageStream {
+public:
+ /** Create a new Image XObject to represent the passed bitmap.
+ * @param bitmap The image to encode.
+ * @param srcRect The rectangle to cut out of bitmap.
+ * @param paint Used to calculate alpha, masks, etc.
+ * @return The image XObject or NUll if there is nothing to draw for
+ * the given parameters.
+ */
+ static SkPDFImage* CreateImage(const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ EncodeToDCTStream encoder);
+
+ virtual ~SkPDFImage();
+
+ /** Add a Soft Mask (alpha or shape channel) to the image. Refs mask.
+ * @param mask A gray scale image representing the mask.
+ * @return The mask argument is returned.
+ */
+ SkPDFImage* addSMask(SkPDFImage* mask);
+
+ // The SkPDFObject interface.
+ virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+private:
+ SkTDArray<SkPDFObject*> fResources;
+
+ /** Create a PDF image XObject. Entries for the image properties are
+ * automatically added to the stream dictionary.
+ * @param imageData The final raw bits representing the image.
+ * @param bitmap The image parameters to use (Config, etc).
+ * @param srcRect The clipping applied to bitmap before generating
+ * imageData.
+ * @param alpha Is this the alpha channel of the bitmap.
+ * @param paint Used to calculate alpha, masks, etc.
+ */
+ SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
+ const SkIRect& srcRect, bool alpha, EncodeToDCTStream encoder);
+};
+
+#endif
diff --git a/pdf/SkPDFImageStream.cpp b/pdf/SkPDFImageStream.cpp
new file mode 100644
index 00000000..7130f2b7
--- /dev/null
+++ b/pdf/SkPDFImageStream.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 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 "SkFlate.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFImageStream.h"
+#include "SkStream.h"
+
+#define kNoColorTransform 0
+
+static bool skip_compression(SkPDFCatalog* catalog) {
+ return SkToBool(catalog->getDocumentFlags() &
+ SkPDFDocument::kFavorSpeedOverSize_Flags);
+}
+
+// TODO(edisonn): Use SkData (after removing deprecated constructor in SkPDFStream).
+SkPDFImageStream::SkPDFImageStream(SkStream* stream,
+ const SkBitmap& bitmap,
+ const SkIRect& srcRect,
+ EncodeToDCTStream encoder)
+ : SkPDFStream(stream),
+ fBitmap(bitmap),
+ fSrcRect(srcRect),
+ fEncoder(encoder) {
+}
+
+SkPDFImageStream::SkPDFImageStream(const SkPDFImageStream& pdfStream)
+ : SkPDFStream(pdfStream),
+ fBitmap(pdfStream.fBitmap),
+ fSrcRect(pdfStream.fSrcRect),
+ fEncoder(pdfStream.fEncoder) {
+}
+
+SkPDFImageStream::~SkPDFImageStream() {}
+
+bool SkPDFImageStream::populate(SkPDFCatalog* catalog) {
+ if (getState() == kUnused_State) {
+ if (!skip_compression(catalog)) {
+ SkDynamicMemoryWStream dctCompressedWStream;
+ if (!fEncoder || !fEncoder(&dctCompressedWStream, fBitmap, fSrcRect)) {
+ return INHERITED::populate(catalog);
+ }
+
+ if (dctCompressedWStream.getOffset() < getData()->getLength()) {
+ SkData* data = dctCompressedWStream.copyToData();
+ SkMemoryStream* stream = SkNEW_ARGS(SkMemoryStream, (data));
+ setData(stream);
+ stream->unref();
+ if (data) {
+ // copyToData and new SkMemoryStream both call ref(), supress one.
+ data->unref();
+ }
+
+ insertName("Filter", "DCTDecode");
+ insertInt("ColorTransform", kNoColorTransform);
+ setState(kCompressed_State);
+ }
+ }
+ setState(kNoCompression_State);
+ insertInt("Length", getData()->getLength());
+ } else if (getState() == kNoCompression_State && !skip_compression(catalog) &&
+ (SkFlate::HaveFlate() || fEncoder)) {
+ // Compression has not been requested when the stream was first created.
+ // But a new Catalog would want it compressed.
+ if (!getSubstitute()) {
+ SkPDFImageStream* substitute = SkNEW_ARGS(SkPDFImageStream, (*this));
+ setSubstitute(substitute);
+ catalog->setSubstitute(this, substitute);
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/pdf/SkPDFImageStream.h b/pdf/SkPDFImageStream.h
new file mode 100644
index 00000000..c5180815
--- /dev/null
+++ b/pdf/SkPDFImageStream.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPDFImageStream_DEFINED
+#define SkPDFImageStream_DEFINED
+
+#include "SkBitmap.h"
+#include "SkPDFDevice.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+class SkPDFCatalog;
+
+/** \class SkPDFImageStream
+
+ An image stream object in a PDF. Note, all streams must be indirect objects
+ (via SkObjRef).
+ This class is similar to SkPDFStream, but it is also able to use image
+ specific compression. Currently we support DCT(jpeg) and flate(zip).
+*/
+class SkPDFImageStream : public SkPDFStream {
+public:
+ /** Create a PDF stream with the same content and dictionary entries
+ * as the passed one.
+ */
+ explicit SkPDFImageStream(const SkPDFImageStream& pdfStream);
+ virtual ~SkPDFImageStream();
+
+protected:
+ SkPDFImageStream(SkStream* stream, const SkBitmap& bitmap,
+ const SkIRect& srcRect, EncodeToDCTStream encoder);
+
+ // Populate the stream dictionary. This method returns false if
+ // fSubstitute should be used.
+ virtual bool populate(SkPDFCatalog* catalog);
+
+private:
+ const SkBitmap fBitmap;
+ const SkIRect fSrcRect;
+ EncodeToDCTStream fEncoder;
+
+ typedef SkPDFStream INHERITED;
+};
+
+#endif
diff --git a/pdf/SkPDFPage.cpp b/pdf/SkPDFPage.cpp
new file mode 100644
index 00000000..8961d2f3
--- /dev/null
+++ b/pdf/SkPDFPage.cpp
@@ -0,0 +1,158 @@
+
+/*
+ * Copyright 2010 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 "SkPDFCatalog.h"
+#include "SkPDFDevice.h"
+#include "SkPDFPage.h"
+#include "SkPDFResourceDict.h"
+#include "SkStream.h"
+
+SkPDFPage::SkPDFPage(SkPDFDevice* content)
+ : SkPDFDict("Page"),
+ fDevice(content) {
+ SkSafeRef(content);
+}
+
+SkPDFPage::~SkPDFPage() {}
+
+void SkPDFPage::finalizePage(SkPDFCatalog* catalog, bool firstPage,
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ SkPDFResourceDict* resourceDict = fDevice->getResourceDict();
+ if (fContentStream.get() == NULL) {
+ insert("Resources", resourceDict);
+ SkSafeUnref(this->insert("MediaBox", fDevice->copyMediaBox()));
+ if (!SkToBool(catalog->getDocumentFlags() &
+ SkPDFDocument::kNoLinks_Flags)) {
+ SkPDFArray* annots = fDevice->getAnnotations();
+ if (annots && annots->size() > 0) {
+ insert("Annots", annots);
+ }
+ }
+
+ SkAutoTUnref<SkStream> content(fDevice->content());
+ fContentStream.reset(new SkPDFStream(content.get()));
+ insert("Contents", new SkPDFObjRef(fContentStream.get()))->unref();
+ }
+ catalog->addObject(fContentStream.get(), firstPage);
+ resourceDict->getReferencedResources(knownResourceObjects,
+ newResourceObjects,
+ true);
+}
+
+off_t SkPDFPage::getPageSize(SkPDFCatalog* catalog, off_t fileOffset) {
+ SkASSERT(fContentStream.get() != NULL);
+ catalog->setFileOffset(fContentStream.get(), fileOffset);
+ return fContentStream->getOutputSize(catalog, true);
+}
+
+void SkPDFPage::emitPage(SkWStream* stream, SkPDFCatalog* catalog) {
+ SkASSERT(fContentStream.get() != NULL);
+ fContentStream->emitObject(stream, catalog, true);
+}
+
+// static
+void SkPDFPage::GeneratePageTree(const SkTDArray<SkPDFPage*>& pages,
+ SkPDFCatalog* catalog,
+ SkTDArray<SkPDFDict*>* pageTree,
+ SkPDFDict** rootNode) {
+ // PDF wants a tree describing all the pages in the document. We arbitrary
+ // choose 8 (kNodeSize) as the number of allowed children. The internal
+ // nodes have type "Pages" with an array of children, a parent pointer, and
+ // the number of leaves below the node as "Count." The leaves are passed
+ // into the method, have type "Page" and need a parent pointer. This method
+ // builds the tree bottom up, skipping internal nodes that would have only
+ // one child.
+ static const int kNodeSize = 8;
+
+ SkAutoTUnref<SkPDFName> kidsName(new SkPDFName("Kids"));
+ SkAutoTUnref<SkPDFName> countName(new SkPDFName("Count"));
+ SkAutoTUnref<SkPDFName> parentName(new SkPDFName("Parent"));
+
+ // curNodes takes a reference to its items, which it passes to pageTree.
+ SkTDArray<SkPDFDict*> curNodes;
+ curNodes.setReserve(pages.count());
+ for (int i = 0; i < pages.count(); i++) {
+ SkSafeRef(pages[i]);
+ curNodes.push(pages[i]);
+ }
+
+ // nextRoundNodes passes its references to nodes on to curNodes.
+ SkTDArray<SkPDFDict*> nextRoundNodes;
+ nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
+
+ int treeCapacity = kNodeSize;
+ do {
+ for (int i = 0; i < curNodes.count(); ) {
+ if (i > 0 && i + 1 == curNodes.count()) {
+ nextRoundNodes.push(curNodes[i]);
+ break;
+ }
+
+ SkPDFDict* newNode = new SkPDFDict("Pages");
+ SkAutoTUnref<SkPDFObjRef> newNodeRef(new SkPDFObjRef(newNode));
+
+ SkAutoTUnref<SkPDFArray> kids(new SkPDFArray);
+ kids->reserve(kNodeSize);
+
+ int count = 0;
+ for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
+ curNodes[i]->insert(parentName.get(), newNodeRef.get());
+ kids->append(new SkPDFObjRef(curNodes[i]))->unref();
+
+ // TODO(vandebo): put the objects in strict access order.
+ // Probably doesn't matter because they are so small.
+ if (curNodes[i] != pages[0]) {
+ pageTree->push(curNodes[i]); // Transfer reference.
+ catalog->addObject(curNodes[i], false);
+ } else {
+ SkSafeUnref(curNodes[i]);
+ catalog->addObject(curNodes[i], true);
+ }
+ }
+
+ // treeCapacity is the number of leaf nodes possible for the
+ // current set of subtrees being generated. (i.e. 8, 64, 512, ...).
+ // It is hard to count the number of leaf nodes in the current
+ // subtree. However, by construction, we know that unless it's the
+ // last subtree for the current depth, the leaf count will be
+ // treeCapacity, otherwise it's what ever is left over after
+ // consuming treeCapacity chunks.
+ int pageCount = treeCapacity;
+ if (i == curNodes.count()) {
+ pageCount = ((pages.count() - 1) % treeCapacity) + 1;
+ }
+ newNode->insert(countName.get(), new SkPDFInt(pageCount))->unref();
+ newNode->insert(kidsName.get(), kids.get());
+ nextRoundNodes.push(newNode); // Transfer reference.
+ }
+
+ curNodes = nextRoundNodes;
+ nextRoundNodes.rewind();
+ treeCapacity *= kNodeSize;
+ } while (curNodes.count() > 1);
+
+ pageTree->push(curNodes[0]); // Transfer reference.
+ catalog->addObject(curNodes[0], false);
+ if (rootNode) {
+ *rootNode = curNodes[0];
+ }
+}
+
+const SkTDArray<SkPDFFont*>& SkPDFPage::getFontResources() const {
+ return fDevice->getFontResources();
+}
+
+const SkPDFGlyphSetMap& SkPDFPage::getFontGlyphUsage() const {
+ return fDevice->getFontGlyphUsage();
+}
+
+void SkPDFPage::appendDestinations(SkPDFDict* dict) {
+ fDevice->appendDestinations(dict, this);
+}
diff --git a/pdf/SkPDFPage.h b/pdf/SkPDFPage.h
new file mode 100644
index 00000000..2ce773c0
--- /dev/null
+++ b/pdf/SkPDFPage.h
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright 2010 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 SkPDFPage_DEFINED
+#define SkPDFPage_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkPDFStream.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+class SkPDFCatalog;
+class SkPDFDevice;
+class SkWStream;
+
+/** \class SkPDFPage
+
+ A SkPDFPage contains meta information about a page, is used in the page
+ tree and points to the content of the page.
+*/
+class SkPDFPage : public SkPDFDict {
+public:
+ /** Create a PDF page with the passed PDF device. The device need not
+ * have content on it yet.
+ * @param content The page content.
+ */
+ explicit SkPDFPage(SkPDFDevice* content);
+ ~SkPDFPage();
+
+ /** Before a page and its contents can be sized and emitted, it must
+ * be finalized. No changes to the PDFDevice will be honored after
+ * finalizePage has been called. This function adds the page content
+ * to the passed catalog, so it must be called for each document
+ * that the page is part of.
+ * @param catalog The catalog to add page content objects to.
+ * @param firstPage Indicate if this is the first page of a document.
+ * @param newResourceObjects All the resource objects (recursively) used on
+ * the page are added to this array. This gives
+ * the caller a chance to deduplicate resources
+ * across pages.
+ * @param knownResourceObjects The set of resources to be ignored.
+ */
+ void finalizePage(SkPDFCatalog* catalog, bool firstPage,
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+ /** Add destinations for this page to the supplied dictionary.
+ * @param dict Dictionary to add destinations to.
+ */
+ void appendDestinations(SkPDFDict* dict);
+
+ /** Determine the size of the page content and store to the catalog
+ * the offsets of all nonresource-indirect objects that make up the page
+ * content. This must be called before emitPage(), but after finalizePage.
+ * @param catalog The catalog to add the object offsets to.
+ * @param fileOffset The file offset where the page content will be
+ * emitted.
+ */
+ off_t getPageSize(SkPDFCatalog* catalog, off_t fileOffset);
+
+ /** Output the page content to the passed stream.
+ * @param stream The writable output stream to send the content to.
+ * @param catalog The active object catalog.
+ */
+ void emitPage(SkWStream* stream, SkPDFCatalog* catalog);
+
+ /** Generate a page tree for the passed vector of pages. New objects are
+ * added to the catalog. The pageTree vector is populated with all of
+ * the 'Pages' dictionaries as well as the 'Page' objects. Page trees
+ * have both parent and children links, creating reference cycles, so
+ * it must be torn down explicitly. The first page is not added to
+ * the pageTree dictionary array so the caller can handle it specially.
+ * @param pages The ordered vector of page objects.
+ * @param catalog The catalog to add new objects into.
+ * @param pageTree An output vector with all of the internal and leaf
+ * nodes of the pageTree.
+ * @param rootNode An output parameter set to the root node.
+ */
+ static void GeneratePageTree(const SkTDArray<SkPDFPage*>& pages,
+ SkPDFCatalog* catalog,
+ SkTDArray<SkPDFDict*>* pageTree,
+ SkPDFDict** rootNode);
+
+ /** Get the fonts used on this page.
+ */
+ const SkTDArray<SkPDFFont*>& getFontResources() const;
+
+ /** Returns a SkPDFGlyphSetMap which represents glyph usage of every font
+ * that shows on this page.
+ */
+ const SkPDFGlyphSetMap& getFontGlyphUsage() const;
+
+private:
+ // Multiple pages may reference the content.
+ SkAutoTUnref<SkPDFDevice> fDevice;
+
+ // Once the content is finalized, put it into a stream for output.
+ SkAutoTUnref<SkPDFStream> fContentStream;
+};
+
+#endif
diff --git a/pdf/SkPDFResourceDict.cpp b/pdf/SkPDFResourceDict.cpp
new file mode 100644
index 00000000..0102b3c4
--- /dev/null
+++ b/pdf/SkPDFResourceDict.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFResourceDict.h"
+#include "SkPostConfig.h"
+
+SK_DEFINE_INST_COUNT(SkPDFResourceDict)
+
+// Sanity check that the values of enum SkPDFResourceType correspond to the
+// expected values as defined in the arrays below.
+// If these are failing, you may need to update the resource_type_prefixes
+// and resource_type_names arrays below.
+SK_COMPILE_ASSERT(SkPDFResourceDict::kExtGState_ResourceType == 0,
+ resource_type_mismatch);
+SK_COMPILE_ASSERT(SkPDFResourceDict::kPattern_ResourceType == 1,
+ resource_type_mismatch);
+SK_COMPILE_ASSERT(SkPDFResourceDict::kXObject_ResourceType == 2,
+ resource_type_mismatch);
+SK_COMPILE_ASSERT(SkPDFResourceDict::kFont_ResourceType == 3,
+ resource_type_mismatch);
+
+static const char resource_type_prefixes[] = {
+ 'G',
+ 'P',
+ 'X',
+ 'F'
+};
+
+static const char* resource_type_names[] = {
+ "ExtGState",
+ "Pattern",
+ "XObject",
+ "Font"
+};
+
+static char get_resource_type_prefix(
+ SkPDFResourceDict::SkPDFResourceType type) {
+ SkASSERT(type >= 0);
+ SkASSERT(type < SkPDFResourceDict::kResourceTypeCount);
+
+ return resource_type_prefixes[type];
+}
+
+static const char* get_resource_type_name(
+ SkPDFResourceDict::SkPDFResourceType type) {
+ SkASSERT(type >= 0);
+ SkASSERT(type < SkPDFResourceDict::kResourceTypeCount);
+
+ return resource_type_names[type];
+}
+
+SkPDFResourceDict::SkPDFResourceDict() : SkPDFDict() {
+ const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
+ SkPDFArray* procSets = SkNEW(SkPDFArray());
+
+ procSets->reserve(SK_ARRAY_COUNT(procs));
+ for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++) {
+ procSets->appendName(procs[i]);
+ }
+ insert("ProcSets", procSets)->unref();
+
+ // Actual sub-dicts will be lazily added later
+ fTypes.setCount(kResourceTypeCount);
+ for (size_t i=0; i < kResourceTypeCount; i++) {
+ fTypes[i] = NULL;
+ }
+}
+
+SkPDFObject* SkPDFResourceDict::insertResourceAsReference(
+ SkPDFResourceType type, int key, SkPDFObject* value) {
+ SkAutoTUnref<SkPDFObjRef> ref(SkNEW_ARGS(SkPDFObjRef, (value)));
+ insertResource(type, key, ref);
+ fResources.add(value);
+
+ return value;
+}
+
+void SkPDFResourceDict::getReferencedResources(
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects,
+ bool recursive) const {
+ // TODO: reserve not correct if we need to recursively explore.
+ newResourceObjects->setReserve(newResourceObjects->count() +
+ fResources.count());
+
+ for (int i = 0; i < fResources.count(); i++) {
+ if (!knownResourceObjects.contains(fResources[i]) &&
+ !newResourceObjects->contains(fResources[i])) {
+ newResourceObjects->add(fResources[i]);
+ fResources[i]->ref();
+ if (recursive) {
+ fResources[i]->getResources(knownResourceObjects,
+ newResourceObjects);
+ }
+ }
+ }
+}
+
+SkString SkPDFResourceDict::getResourceName(
+ SkPDFResourceType type, int key) {
+ SkString keyString;
+ keyString.printf("%c%d", get_resource_type_prefix(type), key);
+ return keyString;
+}
+
+SkPDFObject* SkPDFResourceDict::insertResource(
+ SkPDFResourceType type, int key, SkPDFObject* value) {
+ SkPDFDict* typeDict = fTypes[type];
+ if (NULL == typeDict) {
+ SkAutoTUnref<SkPDFDict> newDict(SkNEW(SkPDFDict()));
+ SkAutoTUnref<SkPDFName> typeName(
+ SkNEW_ARGS(SkPDFName, (get_resource_type_name(type))));
+ insert(typeName, newDict); // ref counting handled here
+ fTypes[type] = newDict;
+ typeDict = newDict.get();
+ }
+
+ SkAutoTUnref<SkPDFName> keyName(
+ SkNEW_ARGS(SkPDFName, (getResourceName(type, key))));
+ typeDict->insert(keyName, value);
+ return value;
+}
diff --git a/pdf/SkPDFResourceDict.h b/pdf/SkPDFResourceDict.h
new file mode 100644
index 00000000..ab25b4a4
--- /dev/null
+++ b/pdf/SkPDFResourceDict.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPDFResourceDict_DEFINED
+#define SkPDFResourceDict_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkTDArray.h"
+#include "SkTSet.h"
+#include "SkTypes.h"
+
+/** \class SkPDFResourceDict
+
+ A resource dictionary, which maintains the relevant sub-dicts and
+ allows generation of a list of referenced SkPDFObjects inserted with
+ insertResourceAsRef.
+*/
+class SkPDFResourceDict : public SkPDFDict {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFResourceDict)
+
+ enum SkPDFResourceType{
+ kExtGState_ResourceType,
+ kPattern_ResourceType,
+ kXObject_ResourceType,
+ kFont_ResourceType,
+ // These additional types are defined by the spec, but not
+ // currently used by Skia: ColorSpace, Shading, Properties
+ kResourceTypeCount
+ };
+
+ /** Create a PDF resource dictionary.
+ * The full set of ProcSet entries is automatically created for backwards
+ * compatibility, as recommended by the PDF spec.
+ */
+ SkPDFResourceDict();
+
+ /** Add the value SkPDFObject as a reference to the resource dictionary
+ * with the give type and key.
+ * The relevant sub-dicts will be automatically generated, and the
+ * resource will be named by concatenating a type-specific prefix and
+ * the input key.
+ * This object will be part of the resource list when requested later.
+ * @param type The type of resource being entered, like
+ * kPattern_ResourceType or kExtGState_ResourceType.
+ * @param key The resource key, should be unique within its type.
+ * @param value The resource itself.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* insertResourceAsReference(SkPDFResourceType type, int key,
+ SkPDFObject* value);
+
+ /**
+ * Gets resources inserted into this dictionary as a reference.
+ *
+ * @param knownResourceObjects Set containing currently known resources.
+ * Resources in the dict and this set will not be added to the output.
+ * @param newResourceObjects Output set to which non-preexisting resources
+ * will be added.
+ * @param recursive Whether or not to add resources of resources.
+ */
+ void getReferencedResources(
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects,
+ bool recursive) const;
+
+ /**
+ * Returns the name for the resource that will be generated by the resource
+ * dict.
+ *
+ * @param type The type of resource being entered, like
+ * kPattern_ResourceType or kExtGState_ResourceType.
+ * @param key The resource key, should be unique within its type.
+ */
+ static SkString getResourceName(SkPDFResourceType type, int key);
+
+private:
+ /** Add the value to the dictionary with the given key. Refs value.
+ * The relevant sub-dicts will be automatically generated, and the
+ * resource will be named by concatenating a type-specific prefix and
+ * the input key.
+ * The object will NOT be part of the resource list when requested later.
+ * @param type The type of resource being entered.
+ * @param key The resource key, should be unique within its type.
+ * @param value The resource itself.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* insertResource(SkPDFResourceType type, int key,
+ SkPDFObject* value);
+
+ SkTSet<SkPDFObject*> fResources;
+
+ SkTDArray<SkPDFDict*> fTypes;
+};
+
+#endif
diff --git a/pdf/SkPDFShader.cpp b/pdf/SkPDFShader.cpp
new file mode 100644
index 00000000..9394f1b9
--- /dev/null
+++ b/pdf/SkPDFShader.cpp
@@ -0,0 +1,1192 @@
+
+/*
+ * 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 "SkPDFShader.h"
+
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFDevice.h"
+#include "SkPDFFormXObject.h"
+#include "SkPDFGraphicState.h"
+#include "SkPDFResourceDict.h"
+#include "SkPDFUtils.h"
+#include "SkScalar.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+#include "SkTSet.h"
+#include "SkTypes.h"
+
+static bool transformBBox(const SkMatrix& matrix, SkRect* bbox) {
+ SkMatrix inverse;
+ if (!matrix.invert(&inverse)) {
+ return false;
+ }
+ inverse.mapRect(bbox);
+ return true;
+}
+
+static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) {
+ SkVector vec = pts[1] - pts[0];
+ SkScalar mag = vec.length();
+ SkScalar inv = mag ? SkScalarInvert(mag) : 0;
+
+ vec.scale(inv);
+ matrix->setSinCos(vec.fY, vec.fX);
+ matrix->preScale(mag, mag);
+ matrix->postTranslate(pts[0].fX, pts[0].fY);
+}
+
+/* Assumes t + startOffset is on the stack and does a linear interpolation on t
+ between startOffset and endOffset from prevColor to curColor (for each color
+ component), leaving the result in component order on the stack. It assumes
+ there are always 3 components per color.
+ @param range endOffset - startOffset
+ @param curColor[components] The current color components.
+ @param prevColor[components] The previous color components.
+ @param result The result ps function.
+ */
+static void interpolateColorCode(SkScalar range, SkScalar* curColor,
+ SkScalar* prevColor, SkString* result) {
+ static const int kColorComponents = 3;
+
+ // Figure out how to scale each color component.
+ SkScalar multiplier[kColorComponents];
+ for (int i = 0; i < kColorComponents; i++) {
+ multiplier[i] = SkScalarDiv(curColor[i] - prevColor[i], range);
+ }
+
+ // Calculate when we no longer need to keep a copy of the input parameter t.
+ // If the last component to use t is i, then dupInput[0..i - 1] = true
+ // and dupInput[i .. components] = false.
+ bool dupInput[kColorComponents];
+ dupInput[kColorComponents - 1] = false;
+ for (int i = kColorComponents - 2; i >= 0; i--) {
+ dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
+ }
+
+ if (!dupInput[0] && multiplier[0] == 0) {
+ result->append("pop ");
+ }
+
+ for (int i = 0; i < kColorComponents; 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 ");
+ }
+
+ if (multiplier[i] == 0) {
+ result->appendScalar(prevColor[i]);
+ result->append(" ");
+ } else {
+ if (multiplier[i] != 1) {
+ result->appendScalar(multiplier[i]);
+ result->append(" mul ");
+ }
+ if (prevColor[i] != 0) {
+ result->appendScalar(prevColor[i]);
+ result->append(" add ");
+ }
+ }
+
+ if (dupInput[i]) {
+ result->append("exch\n");
+ }
+ }
+}
+
+/* Generate Type 4 function code to map t=[0,1) to the passed gradient,
+ clamping at the edges of the range. The generated code will be of the form:
+ if (t < 0) {
+ return colorData[0][r,g,b];
+ } else {
+ if (t < info.fColorOffsets[1]) {
+ return linearinterpolation(colorData[0][r,g,b],
+ colorData[1][r,g,b]);
+ } else {
+ if (t < info.fColorOffsets[2]) {
+ return linearinterpolation(colorData[1][r,g,b],
+ colorData[2][r,g,b]);
+ } else {
+
+ ... } else {
+ return colorData[info.fColorCount - 1][r,g,b];
+ }
+ ...
+ }
+ }
+ */
+static void gradientFunctionCode(const SkShader::GradientInfo& info,
+ SkString* result) {
+ /* We want to linearly interpolate from the previous color to the next.
+ Scale the colors from 0..255 to 0..1 and determine the multipliers
+ for interpolation.
+ C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
+ */
+ static const int kColorComponents = 3;
+ typedef SkScalar ColorTuple[kColorComponents];
+ SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
+ ColorTuple *colorData = colorDataAlloc.get();
+ const SkScalar scale = SkScalarInvert(SkIntToScalar(255));
+ for (int i = 0; i < info.fColorCount; i++) {
+ colorData[i][0] = SkScalarMul(SkColorGetR(info.fColors[i]), scale);
+ colorData[i][1] = SkScalarMul(SkColorGetG(info.fColors[i]), scale);
+ colorData[i][2] = SkScalarMul(SkColorGetB(info.fColors[i]), scale);
+ }
+
+ // Clamp the initial color.
+ result->append("dup 0 le {pop ");
+ result->appendScalar(colorData[0][0]);
+ result->append(" ");
+ result->appendScalar(colorData[0][1]);
+ result->append(" ");
+ result->appendScalar(colorData[0][2]);
+ result->append(" }\n");
+
+ // The gradient colors.
+ for (int i = 1 ; i < info.fColorCount; i++) {
+ result->append("{dup ");
+ result->appendScalar(info.fColorOffsets[i]);
+ result->append(" le {");
+ if (info.fColorOffsets[i - 1] != 0) {
+ result->appendScalar(info.fColorOffsets[i - 1]);
+ result->append(" sub\n");
+ }
+
+ interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
+ colorData[i], colorData[i - 1], result);
+ result->append("}\n");
+ }
+
+ // Clamp the final color.
+ result->append("{pop ");
+ result->appendScalar(colorData[info.fColorCount - 1][0]);
+ result->append(" ");
+ result->appendScalar(colorData[info.fColorCount - 1][1]);
+ result->append(" ");
+ result->appendScalar(colorData[info.fColorCount - 1][2]);
+
+ for (int i = 0 ; i < info.fColorCount; i++) {
+ result->append("} ifelse\n");
+ }
+}
+
+/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
+static void tileModeCode(SkShader::TileMode mode, SkString* result) {
+ if (mode == SkShader::kRepeat_TileMode) {
+ result->append("dup truncate sub\n"); // Get the fractional part.
+ result->append("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1)
+ return;
+ }
+
+ if (mode == SkShader::kMirror_TileMode) {
+ // Map t mod 2 into [0, 1, 1, 0].
+ // Code Stack
+ result->append("abs " // Map negative to positive.
+ "dup " // t.s t.s
+ "truncate " // t.s t
+ "dup " // t.s t t
+ "cvi " // t.s t T
+ "2 mod " // t.s t (i mod 2)
+ "1 eq " // t.s t true|false
+ "3 1 roll " // true|false t.s t
+ "sub " // true|false 0.s
+ "exch " // 0.s true|false
+ "{1 exch sub} if\n"); // 1 - 0.s|0.s
+ }
+}
+
+static SkString linearCode(const SkShader::GradientInfo& info) {
+ SkString function("{pop\n"); // Just ditch the y value.
+ tileModeCode(info.fTileMode, &function);
+ gradientFunctionCode(info, &function);
+ function.append("}");
+ return function;
+}
+
+static SkString radialCode(const SkShader::GradientInfo& info) {
+ SkString function("{");
+ // Find the distance from the origin.
+ function.append("dup " // x y y
+ "mul " // x y^2
+ "exch " // y^2 x
+ "dup " // y^2 x x
+ "mul " // y^2 x^2
+ "add " // y^2+x^2
+ "sqrt\n"); // sqrt(y^2+x^2)
+
+ tileModeCode(info.fTileMode, &function);
+ gradientFunctionCode(info, &function);
+ function.append("}");
+ return function;
+}
+
+/* The math here is all based on the description in Two_Point_Radial_Gradient,
+ with one simplification, the coordinate space has been scaled so that
+ Dr = 1. This means we don't need to scale the entire equation by 1/Dr^2.
+ */
+static SkString twoPointRadialCode(const SkShader::GradientInfo& info) {
+ SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX;
+ SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY;
+ SkScalar sr = info.fRadius[0];
+ SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - SK_Scalar1;
+ bool posRoot = info.fRadius[1] > info.fRadius[0];
+
+ // We start with a stack of (x y), copy it and then consume one copy in
+ // order to calculate b and the other to calculate c.
+ SkString function("{");
+ function.append("2 copy ");
+
+ // Calculate -b and b^2.
+ function.appendScalar(dy);
+ function.append(" mul exch ");
+ function.appendScalar(dx);
+ function.append(" mul add ");
+ function.appendScalar(sr);
+ function.append(" sub 2 mul neg dup dup mul\n");
+
+ // Calculate c
+ function.append("4 2 roll dup mul exch dup mul add ");
+ function.appendScalar(SkScalarMul(sr, sr));
+ function.append(" sub\n");
+
+ // Calculate the determinate
+ function.appendScalar(SkScalarMul(SkIntToScalar(4), a));
+ function.append(" mul sub abs sqrt\n");
+
+ // And then the final value of t.
+ if (posRoot) {
+ function.append("sub ");
+ } else {
+ function.append("add ");
+ }
+ function.appendScalar(SkScalarMul(SkIntToScalar(2), a));
+ function.append(" div\n");
+
+ tileModeCode(info.fTileMode, &function);
+ gradientFunctionCode(info, &function);
+ function.append("}");
+ return function;
+}
+
+/* Conical gradient shader, based on the Canvas spec for radial gradients
+ See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
+ */
+static SkString twoPointConicalCode(const SkShader::GradientInfo& info) {
+ SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
+ SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
+ SkScalar r0 = info.fRadius[0];
+ SkScalar dr = info.fRadius[1] - info.fRadius[0];
+ SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) -
+ SkScalarMul(dr, dr);
+
+ // First compute t, if the pixel falls outside the cone, then we'll end
+ // with 'false' on the stack, otherwise we'll push 'true' with t below it
+
+ // We start with a stack of (x y), copy it and then consume one copy in
+ // order to calculate b and the other to calculate c.
+ SkString function("{");
+ function.append("2 copy ");
+
+ // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
+ function.appendScalar(dy);
+ function.append(" mul exch ");
+ function.appendScalar(dx);
+ function.append(" mul add ");
+ function.appendScalar(SkScalarMul(r0, dr));
+ function.append(" add -2 mul dup dup mul\n");
+
+ // c = x^2 + y^2 + radius0^2
+ function.append("4 2 roll dup mul exch dup mul add ");
+ function.appendScalar(SkScalarMul(r0, r0));
+ function.append(" sub dup 4 1 roll\n");
+
+ // Contents of the stack at this point: c, b, b^2, c
+
+ // if a = 0, then we collapse to a simpler linear case
+ if (a == 0) {
+
+ // t = -c/b
+ function.append("pop pop div neg dup ");
+
+ // compute radius(t)
+ function.appendScalar(dr);
+ function.append(" mul ");
+ function.appendScalar(r0);
+ function.append(" add\n");
+
+ // if r(t) < 0, then it's outside the cone
+ function.append("0 lt {pop false} {true} ifelse\n");
+
+ } else {
+
+ // quadratic case: the Canvas spec wants the largest
+ // root t for which radius(t) > 0
+
+ // compute the discriminant (b^2 - 4ac)
+ function.appendScalar(SkScalarMul(SkIntToScalar(4), a));
+ function.append(" mul sub dup\n");
+
+ // if d >= 0, proceed
+ function.append("0 ge {\n");
+
+ // an intermediate value we'll use to compute the roots:
+ // q = -0.5 * (b +/- sqrt(d))
+ function.append("sqrt exch dup 0 lt {exch -1 mul} if");
+ function.append(" add -0.5 mul dup\n");
+
+ // first root = q / a
+ function.appendScalar(a);
+ function.append(" div\n");
+
+ // second root = c / q
+ function.append("3 1 roll div\n");
+
+ // put the larger root on top of the stack
+ function.append("2 copy gt {exch} if\n");
+
+ // compute radius(t) for larger root
+ function.append("dup ");
+ function.appendScalar(dr);
+ function.append(" mul ");
+ function.appendScalar(r0);
+ function.append(" add\n");
+
+ // if r(t) > 0, we have our t, pop off the smaller root and we're done
+ function.append(" 0 gt {exch pop true}\n");
+
+ // otherwise, throw out the larger one and try the smaller root
+ function.append("{pop dup\n");
+ function.appendScalar(dr);
+ function.append(" mul ");
+ function.appendScalar(r0);
+ function.append(" add\n");
+
+ // if r(t) < 0, push false, otherwise the smaller root is our t
+ function.append("0 le {pop false} {true} ifelse\n");
+ function.append("} ifelse\n");
+
+ // d < 0, clear the stack and push false
+ function.append("} {pop pop pop false} ifelse\n");
+ }
+
+ // if the pixel is in the cone, proceed to compute a color
+ function.append("{");
+ tileModeCode(info.fTileMode, &function);
+ gradientFunctionCode(info, &function);
+
+ // otherwise, just write black
+ function.append("} {0 0 0} ifelse }");
+
+ return function;
+}
+
+static SkString sweepCode(const SkShader::GradientInfo& info) {
+ SkString function("{exch atan 360 div\n");
+ tileModeCode(info.fTileMode, &function);
+ gradientFunctionCode(info, &function);
+ function.append("}");
+ return function;
+}
+
+class SkPDFShader::State {
+public:
+ SkShader::GradientType fType;
+ SkShader::GradientInfo fInfo;
+ SkAutoFree fColorData; // This provides storage for arrays in fInfo.
+ SkMatrix fCanvasTransform;
+ SkMatrix fShaderTransform;
+ SkIRect fBBox;
+
+ SkBitmap fImage;
+ uint32_t fPixelGeneration;
+ SkShader::TileMode fImageTileModes[2];
+
+ State(const SkShader& shader, const SkMatrix& canvasTransform,
+ const SkIRect& bbox);
+
+ bool operator==(const State& b) const;
+
+ SkPDFShader::State* CreateAlphaToLuminosityState() const;
+ SkPDFShader::State* CreateOpaqueState() const;
+
+ bool GradientHasAlpha() const;
+
+private:
+ State(const State& other);
+ State operator=(const State& rhs);
+ void AllocateGradientInfoStorage();
+};
+
+class SkPDFFunctionShader : public SkPDFDict, public SkPDFShader {
+public:
+ explicit SkPDFFunctionShader(SkPDFShader::State* state);
+ virtual ~SkPDFFunctionShader() {
+ if (isValid()) {
+ RemoveShader(this);
+ }
+ fResources.unrefAll();
+ }
+
+ virtual bool isValid() { return fResources.count() > 0; }
+
+ void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources,
+ knownResourceObjects,
+ newResourceObjects);
+ }
+
+private:
+ static SkPDFObject* RangeObject();
+
+ SkTDArray<SkPDFObject*> fResources;
+ SkAutoTDelete<const SkPDFShader::State> fState;
+
+ SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
+};
+
+/**
+ * A shader for PDF gradients. This encapsulates the function shader
+ * inside a tiling pattern while providing a common pattern interface.
+ * The encapsulation allows the use of a SMask for transparency gradients.
+ */
+class SkPDFAlphaFunctionShader : public SkPDFStream, public SkPDFShader {
+public:
+ explicit SkPDFAlphaFunctionShader(SkPDFShader::State* state);
+ virtual ~SkPDFAlphaFunctionShader() {
+ if (isValid()) {
+ RemoveShader(this);
+ }
+ }
+
+ virtual bool isValid() {
+ return fColorShader.get() != NULL;
+ }
+
+private:
+ SkAutoTDelete<const SkPDFShader::State> fState;
+
+ SkPDFGraphicState* CreateSMaskGraphicState();
+
+ void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ fResourceDict->getReferencedResources(knownResourceObjects,
+ newResourceObjects,
+ true);
+ }
+
+ SkAutoTUnref<SkPDFObject> fColorShader;
+ SkAutoTUnref<SkPDFResourceDict> fResourceDict;
+};
+
+class SkPDFImageShader : public SkPDFStream, public SkPDFShader {
+public:
+ explicit SkPDFImageShader(SkPDFShader::State* state);
+ virtual ~SkPDFImageShader() {
+ if (isValid()) {
+ RemoveShader(this);
+ }
+ fResources.unrefAll();
+ }
+
+ virtual bool isValid() { return size() > 0; }
+
+ void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ GetResourcesHelper(&fResources.toArray(),
+ knownResourceObjects,
+ newResourceObjects);
+ }
+
+private:
+ SkTSet<SkPDFObject*> fResources;
+ SkAutoTDelete<const SkPDFShader::State> fState;
+};
+
+SkPDFShader::SkPDFShader() {}
+
+// static
+SkPDFObject* SkPDFShader::GetPDFShaderByState(State* inState) {
+ SkPDFObject* result;
+
+ SkAutoTDelete<State> shaderState(inState);
+ if (shaderState.get()->fType == SkShader::kNone_GradientType &&
+ shaderState.get()->fImage.isNull()) {
+ // TODO(vandebo) This drops SKComposeShader on the floor. We could
+ // handle compose shader by pulling things up to a layer, drawing with
+ // the first shader, applying the xfer mode and drawing again with the
+ // second shader, then applying the layer to the original drawing.
+ return NULL;
+ }
+
+ ShaderCanonicalEntry entry(NULL, shaderState.get());
+ int index = CanonicalShaders().find(entry);
+ if (index >= 0) {
+ result = CanonicalShaders()[index].fPDFShader;
+ result->ref();
+ return result;
+ }
+
+ bool valid = false;
+ // The PDFShader takes ownership of the shaderSate.
+ if (shaderState.get()->fType == SkShader::kNone_GradientType) {
+ SkPDFImageShader* imageShader =
+ new SkPDFImageShader(shaderState.detach());
+ valid = imageShader->isValid();
+ result = imageShader;
+ } else {
+ if (shaderState.get()->GradientHasAlpha()) {
+ SkPDFAlphaFunctionShader* gradientShader =
+ SkNEW_ARGS(SkPDFAlphaFunctionShader, (shaderState.detach()));
+ valid = gradientShader->isValid();
+ result = gradientShader;
+ } else {
+ SkPDFFunctionShader* functionShader =
+ SkNEW_ARGS(SkPDFFunctionShader, (shaderState.detach()));
+ valid = functionShader->isValid();
+ result = functionShader;
+ }
+ }
+ if (!valid) {
+ delete result;
+ return NULL;
+ }
+ entry.fPDFShader = result;
+ CanonicalShaders().push(entry);
+ return result; // return the reference that came from new.
+}
+
+// static
+void SkPDFShader::RemoveShader(SkPDFObject* shader) {
+ SkAutoMutexAcquire lock(CanonicalShadersMutex());
+ ShaderCanonicalEntry entry(shader, NULL);
+ int index = CanonicalShaders().find(entry);
+ SkASSERT(index >= 0);
+ CanonicalShaders().removeShuffle(index);
+}
+
+// static
+SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader,
+ const SkMatrix& matrix,
+ const SkIRect& surfaceBBox) {
+ SkAutoMutexAcquire lock(CanonicalShadersMutex());
+ return GetPDFShaderByState(
+ SkNEW_ARGS(State, (shader, matrix, surfaceBBox)));
+}
+
+// static
+SkTDArray<SkPDFShader::ShaderCanonicalEntry>& SkPDFShader::CanonicalShaders() {
+ // This initialization is only thread safe with gcc.
+ static SkTDArray<ShaderCanonicalEntry> gCanonicalShaders;
+ return gCanonicalShaders;
+}
+
+// static
+SkBaseMutex& SkPDFShader::CanonicalShadersMutex() {
+ // This initialization is only thread safe with gcc or when
+ // POD-style mutex initialization is used.
+ SK_DECLARE_STATIC_MUTEX(gCanonicalShadersMutex);
+ return gCanonicalShadersMutex;
+}
+
+// static
+SkPDFObject* SkPDFFunctionShader::RangeObject() {
+ // This initialization is only thread safe with gcc.
+ static SkPDFArray* range = NULL;
+ // This method is only used with CanonicalShadersMutex, so it's safe to
+ // populate domain.
+ if (range == NULL) {
+ range = new SkPDFArray;
+ range->reserve(6);
+ range->appendInt(0);
+ range->appendInt(1);
+ range->appendInt(0);
+ range->appendInt(1);
+ range->appendInt(0);
+ range->appendInt(1);
+ }
+ return range;
+}
+
+static SkPDFResourceDict* get_gradient_resource_dict(
+ SkPDFObject* functionShader,
+ SkPDFObject* gState) {
+ SkPDFResourceDict* dict = new SkPDFResourceDict();
+
+ if (functionShader != NULL) {
+ dict->insertResourceAsReference(
+ SkPDFResourceDict::kPattern_ResourceType, 0, functionShader);
+ }
+ if (gState != NULL) {
+ dict->insertResourceAsReference(
+ SkPDFResourceDict::kExtGState_ResourceType, 0, gState);
+ }
+
+ return dict;
+}
+
+static void populate_tiling_pattern_dict(SkPDFDict* pattern,
+ SkRect& bbox, SkPDFDict* resources,
+ const SkMatrix& matrix) {
+ const int kTiling_PatternType = 1;
+ const int kColoredTilingPattern_PaintType = 1;
+ const int kConstantSpacing_TilingType = 1;
+
+ pattern->insertName("Type", "Pattern");
+ pattern->insertInt("PatternType", kTiling_PatternType);
+ pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
+ pattern->insertInt("TilingType", kConstantSpacing_TilingType);
+ pattern->insert("BBox", SkPDFUtils::RectToArray(bbox))->unref();
+ pattern->insertScalar("XStep", bbox.width());
+ pattern->insertScalar("YStep", bbox.height());
+ pattern->insert("Resources", resources);
+ if (!matrix.isIdentity()) {
+ pattern->insert("Matrix", SkPDFUtils::MatrixToArray(matrix))->unref();
+ }
+}
+
+/**
+ * Creates a content stream which fills the pattern P0 across bounds.
+ * @param gsIndex A graphics state resource index to apply, or <0 if no
+ * graphics state to apply.
+ */
+static SkStream* create_pattern_fill_content(int gsIndex, SkRect& bounds) {
+ SkDynamicMemoryWStream content;
+ if (gsIndex >= 0) {
+ SkPDFUtils::ApplyGraphicState(gsIndex, &content);
+ }
+ SkPDFUtils::ApplyPattern(0, &content);
+ SkPDFUtils::AppendRectangle(bounds, &content);
+ SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType,
+ &content);
+
+ return content.detachAsStream();
+}
+
+/**
+ * Creates a ExtGState with the SMask set to the luminosityShader in
+ * luminosity mode. The shader pattern extends to the bbox.
+ */
+SkPDFGraphicState* SkPDFAlphaFunctionShader::CreateSMaskGraphicState() {
+ SkRect bbox;
+ bbox.set(fState.get()->fBBox);
+
+ SkAutoTUnref<SkPDFObject> luminosityShader(
+ SkPDFShader::GetPDFShaderByState(
+ fState->CreateAlphaToLuminosityState()));
+
+ SkAutoTUnref<SkStream> alphaStream(create_pattern_fill_content(-1, bbox));
+
+ SkAutoTUnref<SkPDFResourceDict>
+ resources(get_gradient_resource_dict(luminosityShader, NULL));
+
+ SkAutoTUnref<SkPDFFormXObject> alphaMask(
+ new SkPDFFormXObject(alphaStream.get(), bbox, resources.get()));
+
+ return SkPDFGraphicState::GetSMaskGraphicState(
+ alphaMask.get(), false,
+ SkPDFGraphicState::kLuminosity_SMaskMode);
+}
+
+SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state)
+ : fState(state) {
+ SkRect bbox;
+ bbox.set(fState.get()->fBBox);
+
+ fColorShader.reset(
+ SkPDFShader::GetPDFShaderByState(state->CreateOpaqueState()));
+
+ // Create resource dict with alpha graphics state as G0 and
+ // pattern shader as P0, then write content stream.
+ SkAutoTUnref<SkPDFGraphicState> alphaGs(CreateSMaskGraphicState());
+ fResourceDict.reset(
+ get_gradient_resource_dict(fColorShader.get(), alphaGs.get()));
+
+ SkAutoTUnref<SkStream> colorStream(
+ create_pattern_fill_content(0, bbox));
+ setData(colorStream.get());
+
+ populate_tiling_pattern_dict(this, bbox, fResourceDict.get(),
+ SkMatrix::I());
+}
+
+SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
+ : SkPDFDict("Pattern"),
+ fState(state) {
+ SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
+ SkPoint transformPoints[2];
+
+ // Depending on the type of the gradient, we want to transform the
+ // coordinate space in different ways.
+ const SkShader::GradientInfo* info = &fState.get()->fInfo;
+ transformPoints[0] = info->fPoint[0];
+ transformPoints[1] = info->fPoint[1];
+ switch (fState.get()->fType) {
+ case SkShader::kLinear_GradientType:
+ codeFunction = &linearCode;
+ break;
+ case SkShader::kRadial_GradientType:
+ transformPoints[1] = transformPoints[0];
+ transformPoints[1].fX += info->fRadius[0];
+ codeFunction = &radialCode;
+ break;
+ case SkShader::kRadial2_GradientType: {
+ // Bail out if the radii are the same. Empty fResources signals
+ // an error and isValid will return false.
+ if (info->fRadius[0] == info->fRadius[1]) {
+ return;
+ }
+ transformPoints[1] = transformPoints[0];
+ SkScalar dr = info->fRadius[1] - info->fRadius[0];
+ transformPoints[1].fX += dr;
+ codeFunction = &twoPointRadialCode;
+ break;
+ }
+ case SkShader::kConical_GradientType: {
+ transformPoints[1] = transformPoints[0];
+ transformPoints[1].fX += SK_Scalar1;
+ codeFunction = &twoPointConicalCode;
+ break;
+ }
+ case SkShader::kSweep_GradientType:
+ transformPoints[1] = transformPoints[0];
+ transformPoints[1].fX += SK_Scalar1;
+ codeFunction = &sweepCode;
+ break;
+ case SkShader::kColor_GradientType:
+ case SkShader::kNone_GradientType:
+ default:
+ return;
+ }
+
+ // Move any scaling (assuming a unit gradient) or translation
+ // (and rotation for linear gradient), of the final gradient from
+ // info->fPoints to the matrix (updating bbox appropriately). Now
+ // the gradient can be drawn on on the unit segment.
+ SkMatrix mapperMatrix;
+ unitToPointsMatrix(transformPoints, &mapperMatrix);
+ SkMatrix finalMatrix = fState.get()->fCanvasTransform;
+ finalMatrix.preConcat(fState.get()->fShaderTransform);
+ finalMatrix.preConcat(mapperMatrix);
+
+ SkRect bbox;
+ bbox.set(fState.get()->fBBox);
+ if (!transformBBox(finalMatrix, &bbox)) {
+ return;
+ }
+
+ SkAutoTUnref<SkPDFArray> domain(new SkPDFArray);
+ domain->reserve(4);
+ domain->appendScalar(bbox.fLeft);
+ domain->appendScalar(bbox.fRight);
+ domain->appendScalar(bbox.fTop);
+ domain->appendScalar(bbox.fBottom);
+
+ SkString functionCode;
+ // The two point radial gradient further references fState.get()->fInfo
+ // in translating from x, y coordinates to the t parameter. So, we have
+ // to transform the points and radii according to the calculated matrix.
+ if (fState.get()->fType == SkShader::kRadial2_GradientType) {
+ SkShader::GradientInfo twoPointRadialInfo = *info;
+ SkMatrix inverseMapperMatrix;
+ if (!mapperMatrix.invert(&inverseMapperMatrix)) {
+ return;
+ }
+ inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
+ twoPointRadialInfo.fRadius[0] =
+ inverseMapperMatrix.mapRadius(info->fRadius[0]);
+ twoPointRadialInfo.fRadius[1] =
+ inverseMapperMatrix.mapRadius(info->fRadius[1]);
+ functionCode = codeFunction(twoPointRadialInfo);
+ } else {
+ functionCode = codeFunction(*info);
+ }
+
+ SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict);
+ pdfShader->insertInt("ShadingType", 1);
+ pdfShader->insertName("ColorSpace", "DeviceRGB");
+ pdfShader->insert("Domain", domain.get());
+
+ SkPDFStream* function = makePSFunction(functionCode, domain.get());
+ pdfShader->insert("Function", new SkPDFObjRef(function))->unref();
+ fResources.push(function); // Pass ownership to resource list.
+
+ insertInt("PatternType", 2);
+ insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
+ insert("Shading", pdfShader.get());
+}
+
+SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) {
+ fState.get()->fImage.lockPixels();
+
+ SkMatrix finalMatrix = fState.get()->fCanvasTransform;
+ finalMatrix.preConcat(fState.get()->fShaderTransform);
+ SkRect surfaceBBox;
+ surfaceBBox.set(fState.get()->fBBox);
+ if (!transformBBox(finalMatrix, &surfaceBBox)) {
+ return;
+ }
+
+ SkMatrix unflip;
+ unflip.setTranslate(0, SkScalarRoundToScalar(surfaceBBox.height()));
+ unflip.preScale(SK_Scalar1, -SK_Scalar1);
+ SkISize size = SkISize::Make(SkScalarRound(surfaceBBox.width()),
+ SkScalarRound(surfaceBBox.height()));
+ SkPDFDevice pattern(size, size, unflip);
+ SkCanvas canvas(&pattern);
+ canvas.translate(-surfaceBBox.fLeft, -surfaceBBox.fTop);
+ finalMatrix.preTranslate(surfaceBBox.fLeft, surfaceBBox.fTop);
+
+ const SkBitmap* image = &fState.get()->fImage;
+ SkScalar width = SkIntToScalar(image->width());
+ SkScalar height = SkIntToScalar(image->height());
+ SkShader::TileMode tileModes[2];
+ tileModes[0] = fState.get()->fImageTileModes[0];
+ tileModes[1] = fState.get()->fImageTileModes[1];
+
+ canvas.drawBitmap(*image, 0, 0);
+ SkRect patternBBox = SkRect::MakeXYWH(-surfaceBBox.fLeft, -surfaceBBox.fTop,
+ width, height);
+
+ // Tiling is implied. First we handle mirroring.
+ if (tileModes[0] == SkShader::kMirror_TileMode) {
+ SkMatrix xMirror;
+ xMirror.setScale(-1, 1);
+ xMirror.postTranslate(2 * width, 0);
+ canvas.drawBitmapMatrix(*image, xMirror);
+ patternBBox.fRight += width;
+ }
+ if (tileModes[1] == SkShader::kMirror_TileMode) {
+ SkMatrix yMirror;
+ yMirror.setScale(SK_Scalar1, -SK_Scalar1);
+ yMirror.postTranslate(0, 2 * height);
+ canvas.drawBitmapMatrix(*image, yMirror);
+ patternBBox.fBottom += height;
+ }
+ if (tileModes[0] == SkShader::kMirror_TileMode &&
+ tileModes[1] == SkShader::kMirror_TileMode) {
+ SkMatrix mirror;
+ mirror.setScale(-1, -1);
+ mirror.postTranslate(2 * width, 2 * height);
+ canvas.drawBitmapMatrix(*image, mirror);
+ }
+
+ // Then handle Clamping, which requires expanding the pattern canvas to
+ // cover the entire surfaceBBox.
+
+ // If both x and y are in clamp mode, we start by filling in the corners.
+ // (Which are just a rectangles of the corner colors.)
+ if (tileModes[0] == SkShader::kClamp_TileMode &&
+ tileModes[1] == SkShader::kClamp_TileMode) {
+ SkPaint paint;
+ SkRect rect;
+ rect = SkRect::MakeLTRB(surfaceBBox.fLeft, surfaceBBox.fTop, 0, 0);
+ if (!rect.isEmpty()) {
+ paint.setColor(image->getColor(0, 0));
+ canvas.drawRect(rect, paint);
+ }
+
+ rect = SkRect::MakeLTRB(width, surfaceBBox.fTop, surfaceBBox.fRight, 0);
+ if (!rect.isEmpty()) {
+ paint.setColor(image->getColor(image->width() - 1, 0));
+ canvas.drawRect(rect, paint);
+ }
+
+ rect = SkRect::MakeLTRB(width, height, surfaceBBox.fRight,
+ surfaceBBox.fBottom);
+ if (!rect.isEmpty()) {
+ paint.setColor(image->getColor(image->width() - 1,
+ image->height() - 1));
+ canvas.drawRect(rect, paint);
+ }
+
+ rect = SkRect::MakeLTRB(surfaceBBox.fLeft, height, 0,
+ surfaceBBox.fBottom);
+ if (!rect.isEmpty()) {
+ paint.setColor(image->getColor(0, image->height() - 1));
+ canvas.drawRect(rect, paint);
+ }
+ }
+
+ // Then expand the left, right, top, then bottom.
+ if (tileModes[0] == SkShader::kClamp_TileMode) {
+ SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height());
+ if (surfaceBBox.fLeft < 0) {
+ SkBitmap left;
+ SkAssertResult(image->extractSubset(&left, subset));
+
+ SkMatrix leftMatrix;
+ leftMatrix.setScale(-surfaceBBox.fLeft, 1);
+ leftMatrix.postTranslate(surfaceBBox.fLeft, 0);
+ canvas.drawBitmapMatrix(left, leftMatrix);
+
+ if (tileModes[1] == SkShader::kMirror_TileMode) {
+ leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
+ leftMatrix.postTranslate(0, 2 * height);
+ canvas.drawBitmapMatrix(left, leftMatrix);
+ }
+ patternBBox.fLeft = 0;
+ }
+
+ if (surfaceBBox.fRight > width) {
+ SkBitmap right;
+ subset.offset(image->width() - 1, 0);
+ SkAssertResult(image->extractSubset(&right, subset));
+
+ SkMatrix rightMatrix;
+ rightMatrix.setScale(surfaceBBox.fRight - width, 1);
+ rightMatrix.postTranslate(width, 0);
+ canvas.drawBitmapMatrix(right, rightMatrix);
+
+ if (tileModes[1] == SkShader::kMirror_TileMode) {
+ rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
+ rightMatrix.postTranslate(0, 2 * height);
+ canvas.drawBitmapMatrix(right, rightMatrix);
+ }
+ patternBBox.fRight = surfaceBBox.width();
+ }
+ }
+
+ if (tileModes[1] == SkShader::kClamp_TileMode) {
+ SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1);
+ if (surfaceBBox.fTop < 0) {
+ SkBitmap top;
+ SkAssertResult(image->extractSubset(&top, subset));
+
+ SkMatrix topMatrix;
+ topMatrix.setScale(SK_Scalar1, -surfaceBBox.fTop);
+ topMatrix.postTranslate(0, surfaceBBox.fTop);
+ canvas.drawBitmapMatrix(top, topMatrix);
+
+ if (tileModes[0] == SkShader::kMirror_TileMode) {
+ topMatrix.postScale(-1, 1);
+ topMatrix.postTranslate(2 * width, 0);
+ canvas.drawBitmapMatrix(top, topMatrix);
+ }
+ patternBBox.fTop = 0;
+ }
+
+ if (surfaceBBox.fBottom > height) {
+ SkBitmap bottom;
+ subset.offset(0, image->height() - 1);
+ SkAssertResult(image->extractSubset(&bottom, subset));
+
+ SkMatrix bottomMatrix;
+ bottomMatrix.setScale(SK_Scalar1, surfaceBBox.fBottom - height);
+ bottomMatrix.postTranslate(0, height);
+ canvas.drawBitmapMatrix(bottom, bottomMatrix);
+
+ if (tileModes[0] == SkShader::kMirror_TileMode) {
+ bottomMatrix.postScale(-1, 1);
+ bottomMatrix.postTranslate(2 * width, 0);
+ canvas.drawBitmapMatrix(bottom, bottomMatrix);
+ }
+ patternBBox.fBottom = surfaceBBox.height();
+ }
+ }
+
+ // Put the canvas into the pattern stream (fContent).
+ SkAutoTUnref<SkStream> content(pattern.content());
+ setData(content.get());
+ SkPDFResourceDict* resourceDict = pattern.getResourceDict();
+ resourceDict->getReferencedResources(fResources, &fResources, false);
+
+ populate_tiling_pattern_dict(this, patternBBox,
+ pattern.getResourceDict(), finalMatrix);
+
+ fState.get()->fImage.unlockPixels();
+}
+
+SkPDFStream* SkPDFFunctionShader::makePSFunction(const SkString& psCode,
+ SkPDFArray* domain) {
+ SkAutoDataUnref funcData(SkData::NewWithCopy(psCode.c_str(),
+ psCode.size()));
+ SkPDFStream* result = new SkPDFStream(funcData.get());
+ result->insertInt("FunctionType", 4);
+ result->insert("Domain", domain);
+ result->insert("Range", RangeObject());
+ return result;
+}
+
+SkPDFShader::ShaderCanonicalEntry::ShaderCanonicalEntry(SkPDFObject* pdfShader,
+ const State* state)
+ : fPDFShader(pdfShader),
+ fState(state) {
+}
+
+bool SkPDFShader::ShaderCanonicalEntry::operator==(
+ const ShaderCanonicalEntry& b) const {
+ return fPDFShader == b.fPDFShader ||
+ (fState != NULL && b.fState != NULL && *fState == *b.fState);
+}
+
+bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
+ if (fType != b.fType ||
+ fCanvasTransform != b.fCanvasTransform ||
+ fShaderTransform != b.fShaderTransform ||
+ fBBox != b.fBBox) {
+ return false;
+ }
+
+ if (fType == SkShader::kNone_GradientType) {
+ if (fPixelGeneration != b.fPixelGeneration ||
+ fPixelGeneration == 0 ||
+ fImageTileModes[0] != b.fImageTileModes[0] ||
+ fImageTileModes[1] != b.fImageTileModes[1]) {
+ return false;
+ }
+ } else {
+ if (fInfo.fColorCount != b.fInfo.fColorCount ||
+ memcmp(fInfo.fColors, b.fInfo.fColors,
+ sizeof(SkColor) * fInfo.fColorCount) != 0 ||
+ memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
+ sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
+ fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
+ fInfo.fTileMode != b.fInfo.fTileMode) {
+ return false;
+ }
+
+ switch (fType) {
+ case SkShader::kLinear_GradientType:
+ if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
+ return false;
+ }
+ break;
+ case SkShader::kRadial_GradientType:
+ if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
+ return false;
+ }
+ break;
+ case SkShader::kRadial2_GradientType:
+ case SkShader::kConical_GradientType:
+ if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
+ fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
+ fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
+ return false;
+ }
+ break;
+ case SkShader::kSweep_GradientType:
+ case SkShader::kNone_GradientType:
+ case SkShader::kColor_GradientType:
+ break;
+ }
+ }
+ return true;
+}
+
+SkPDFShader::State::State(const SkShader& shader,
+ const SkMatrix& canvasTransform, const SkIRect& bbox)
+ : fCanvasTransform(canvasTransform),
+ fBBox(bbox),
+ fPixelGeneration(0) {
+ fInfo.fColorCount = 0;
+ fInfo.fColors = NULL;
+ fInfo.fColorOffsets = NULL;
+ fShaderTransform = shader.getLocalMatrix();
+ fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
+
+ fType = shader.asAGradient(&fInfo);
+
+ if (fType == SkShader::kNone_GradientType) {
+ SkShader::BitmapType bitmapType;
+ SkMatrix matrix;
+ bitmapType = shader.asABitmap(&fImage, &matrix, fImageTileModes);
+ if (bitmapType != SkShader::kDefault_BitmapType) {
+ fImage.reset();
+ return;
+ }
+ SkASSERT(matrix.isIdentity());
+ fPixelGeneration = fImage.getGenerationID();
+ } else {
+ AllocateGradientInfoStorage();
+ shader.asAGradient(&fInfo);
+ }
+}
+
+SkPDFShader::State::State(const SkPDFShader::State& other)
+ : fType(other.fType),
+ fCanvasTransform(other.fCanvasTransform),
+ fShaderTransform(other.fShaderTransform),
+ fBBox(other.fBBox)
+{
+ // Only gradients supported for now, since that is all that is used.
+ // If needed, image state copy constructor can be added here later.
+ SkASSERT(fType != SkShader::kNone_GradientType);
+
+ if (fType != SkShader::kNone_GradientType) {
+ fInfo = other.fInfo;
+
+ AllocateGradientInfoStorage();
+ for (int i = 0; i < fInfo.fColorCount; i++) {
+ fInfo.fColors[i] = other.fInfo.fColors[i];
+ fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
+ }
+ }
+}
+
+/**
+ * Create a copy of this gradient state with alpha assigned to RGB luminousity.
+ * Only valid for gradient states.
+ */
+SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const {
+ SkASSERT(fType != SkShader::kNone_GradientType);
+
+ SkPDFShader::State* newState = new SkPDFShader::State(*this);
+
+ for (int i = 0; i < fInfo.fColorCount; i++) {
+ SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
+ newState->fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
+ }
+
+ return newState;
+}
+
+/**
+ * Create a copy of this gradient state with alpha set to fully opaque
+ * Only valid for gradient states.
+ */
+SkPDFShader::State* SkPDFShader::State::CreateOpaqueState() const {
+ SkASSERT(fType != SkShader::kNone_GradientType);
+
+ SkPDFShader::State* newState = new SkPDFShader::State(*this);
+ for (int i = 0; i < fInfo.fColorCount; i++) {
+ newState->fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i],
+ SK_AlphaOPAQUE);
+ }
+
+ return newState;
+}
+
+/**
+ * Returns true if state is a gradient and the gradient has alpha.
+ */
+bool SkPDFShader::State::GradientHasAlpha() const {
+ if (fType == SkShader::kNone_GradientType) {
+ return false;
+ }
+
+ for (int i = 0; i < fInfo.fColorCount; i++) {
+ SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
+ if (alpha != SK_AlphaOPAQUE) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SkPDFShader::State::AllocateGradientInfoStorage() {
+ fColorData.set(sk_malloc_throw(
+ fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar))));
+ fInfo.fColors = reinterpret_cast<SkColor*>(fColorData.get());
+ fInfo.fColorOffsets =
+ reinterpret_cast<SkScalar*>(fInfo.fColors + fInfo.fColorCount);
+}
diff --git a/pdf/SkPDFShader.h b/pdf/SkPDFShader.h
new file mode 100644
index 00000000..0b292e93
--- /dev/null
+++ b/pdf/SkPDFShader.h
@@ -0,0 +1,73 @@
+
+/*
+ * 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 SkPDFShader_DEFINED
+#define SkPDFShader_DEFINED
+
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkMatrix.h"
+#include "SkRefCnt.h"
+#include "SkShader.h"
+
+class SkObjRef;
+class SkPDFCatalog;
+
+/** \class SkPDFShader
+
+ In PDF parlance, this is a pattern, used in place of a color when the
+ pattern color space is selected.
+*/
+
+class SkPDFShader {
+public:
+ /** Get the PDF shader for the passed SkShader. If the SkShader is
+ * invalid in some way, returns NULL. The reference count of
+ * the object is incremented and it is the caller's responsibility to
+ * unreference it when done. This is needed to accommodate the weak
+ * reference pattern used when the returned object is new and has no
+ * other references.
+ * @param shader The SkShader to emulate.
+ * @param matrix The current transform. (PDF shaders are absolutely
+ * positioned, relative to where the page is drawn.)
+ * @param surfceBBox The bounding box of the drawing surface (with matrix
+ * already applied).
+ */
+ static SkPDFObject* GetPDFShader(const SkShader& shader,
+ const SkMatrix& matrix,
+ const SkIRect& surfaceBBox);
+
+protected:
+ class State;
+
+ class ShaderCanonicalEntry {
+ public:
+ ShaderCanonicalEntry(SkPDFObject* pdfShader, const State* state);
+ bool operator==(const ShaderCanonicalEntry& b) const;
+
+ SkPDFObject* fPDFShader;
+ const State* fState;
+ };
+ // This should be made a hash table if performance is a problem.
+ static SkTDArray<ShaderCanonicalEntry>& CanonicalShaders();
+ static SkBaseMutex& CanonicalShadersMutex();
+
+ // This is an internal method.
+ // CanonicalShadersMutex() should already be acquired.
+ // This also takes ownership of shaderState.
+ static SkPDFObject* GetPDFShaderByState(State* shaderState);
+ static void RemoveShader(SkPDFObject* shader);
+
+ SkPDFShader();
+ virtual ~SkPDFShader() {};
+
+ virtual bool isValid() = 0;
+};
+
+#endif
diff --git a/pdf/SkPDFStream.cpp b/pdf/SkPDFStream.cpp
new file mode 100644
index 00000000..e5709764
--- /dev/null
+++ b/pdf/SkPDFStream.cpp
@@ -0,0 +1,125 @@
+
+/*
+ * 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 "SkData.h"
+#include "SkFlate.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFStream.h"
+#include "SkStream.h"
+
+static bool skip_compression(SkPDFCatalog* catalog) {
+ return SkToBool(catalog->getDocumentFlags() &
+ SkPDFDocument::kFavorSpeedOverSize_Flags);
+}
+
+SkPDFStream::SkPDFStream(SkStream* stream) : fState(kUnused_State) {
+ setData(stream);
+}
+
+SkPDFStream::SkPDFStream(SkData* data) : fState(kUnused_State) {
+ setData(data);
+}
+
+SkPDFStream::SkPDFStream(const SkPDFStream& pdfStream)
+ : SkPDFDict(),
+ fState(kUnused_State) {
+ setData(pdfStream.fData.get());
+ bool removeLength = true;
+ // Don't uncompress an already compressed stream, but we could.
+ if (pdfStream.fState == kCompressed_State) {
+ fState = kCompressed_State;
+ removeLength = false;
+ }
+ SkPDFDict::Iter dict(pdfStream);
+ SkPDFName* key;
+ SkPDFObject* value;
+ SkPDFName lengthName("Length");
+ for (key = dict.next(&value); key != NULL; key = dict.next(&value)) {
+ if (removeLength && *key == lengthName) {
+ continue;
+ }
+ this->insert(key, value);
+ }
+}
+
+SkPDFStream::~SkPDFStream() {}
+
+void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect) {
+ return emitIndirectObject(stream, catalog);
+ }
+ if (!this->populate(catalog)) {
+ return fSubstitute->emitObject(stream, catalog, indirect);
+ }
+
+ this->INHERITED::emitObject(stream, catalog, false);
+ stream->writeText(" stream\n");
+ stream->writeStream(fData.get(), fData->getLength());
+ fData->rewind();
+ stream->writeText("\nendstream");
+}
+
+size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ if (indirect) {
+ return getIndirectOutputSize(catalog);
+ }
+ if (!this->populate(catalog)) {
+ return fSubstitute->getOutputSize(catalog, indirect);
+ }
+
+ return this->INHERITED::getOutputSize(catalog, false) +
+ strlen(" stream\n\nendstream") + fData->getLength();
+}
+
+SkPDFStream::SkPDFStream() : fState(kUnused_State) {}
+
+void SkPDFStream::setData(SkData* data) {
+ SkMemoryStream* stream = new SkMemoryStream;
+ stream->setData(data);
+ fData.reset(stream); // Transfer ownership.
+}
+
+void SkPDFStream::setData(SkStream* stream) {
+ // Code assumes that the stream starts at the beginning and is rewindable.
+ if (stream) {
+ SkASSERT(stream->getPosition() == 0);
+ SkASSERT(stream->rewind());
+ }
+ fData.reset(stream);
+ SkSafeRef(stream);
+}
+
+bool SkPDFStream::populate(SkPDFCatalog* catalog) {
+ if (fState == kUnused_State) {
+ if (!skip_compression(catalog) && SkFlate::HaveFlate()) {
+ SkDynamicMemoryWStream compressedData;
+
+ SkAssertResult(SkFlate::Deflate(fData.get(), &compressedData));
+ if (compressedData.getOffset() < fData->getLength()) {
+ SkMemoryStream* stream = new SkMemoryStream;
+ stream->setData(compressedData.copyToData())->unref();
+ fData.reset(stream); // Transfer ownership.
+ insertName("Filter", "FlateDecode");
+ }
+ fState = kCompressed_State;
+ } else {
+ fState = kNoCompression_State;
+ }
+ insertInt("Length", fData->getLength());
+ } else if (fState == kNoCompression_State && !skip_compression(catalog) &&
+ SkFlate::HaveFlate()) {
+ if (!fSubstitute.get()) {
+ fSubstitute.reset(new SkPDFStream(*this));
+ catalog->setSubstitute(this, fSubstitute.get());
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/pdf/SkPDFStream.h b/pdf/SkPDFStream.h
new file mode 100644
index 00000000..d7ff115b
--- /dev/null
+++ b/pdf/SkPDFStream.h
@@ -0,0 +1,99 @@
+
+/*
+ * 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 SkPDFStream_DEFINED
+#define SkPDFStream_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+
+class SkPDFCatalog;
+
+/** \class SkPDFStream
+
+ A stream object in a PDF. Note, all streams must be indirect objects (via
+ SkObjRef).
+ TODO(vandebo): SkStream should be replaced by SkStreamRewindable when that
+ is feasible.
+*/
+class SkPDFStream : public SkPDFDict {
+public:
+ /** Create a PDF stream. A Length entry is automatically added to the
+ * stream dictionary. The stream may be retained (stream->ref() may be
+ * called) so its contents must not be changed after calling this.
+ * @param data The data part of the stream.
+ */
+ explicit SkPDFStream(SkData* data);
+ /** Deprecated constructor. */
+ explicit SkPDFStream(SkStream* stream);
+ /** Create a PDF stream with the same content and dictionary entries
+ * as the passed one.
+ */
+ explicit SkPDFStream(const SkPDFStream& pdfStream);
+ virtual ~SkPDFStream();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+protected:
+ enum State {
+ kUnused_State, //!< The stream hasn't been requested yet.
+ kNoCompression_State, //!< The stream's been requested in an
+ // uncompressed form.
+ kCompressed_State, //!< The stream's already been compressed.
+ };
+
+ /* Create a PDF stream with no data. The setData method must be called to
+ * set the data.
+ */
+ SkPDFStream();
+
+ // Populate the stream dictionary. This method returns false if
+ // fSubstitute should be used.
+ virtual bool populate(SkPDFCatalog* catalog);
+
+ void setSubstitute(SkPDFStream* stream) {
+ fSubstitute.reset(stream);
+ }
+
+ SkPDFStream* getSubstitute() {
+ return fSubstitute.get();
+ }
+
+ void setData(SkData* data);
+ void setData(SkStream* stream);
+
+ SkStream* getData() {
+ return fData.get();
+ }
+
+ void setState(State state) {
+ fState = state;
+ }
+
+ State getState() {
+ return fState;
+ }
+
+private:
+ // Indicates what form (or if) the stream has been requested.
+ State fState;
+
+ // TODO(vandebo): Use SkData (after removing deprecated constructor).
+ SkAutoTUnref<SkStream> fData;
+ SkAutoTUnref<SkPDFStream> fSubstitute;
+
+ typedef SkPDFDict INHERITED;
+};
+
+#endif
diff --git a/pdf/SkPDFTypes.cpp b/pdf/SkPDFTypes.cpp
new file mode 100644
index 00000000..ed02d2bd
--- /dev/null
+++ b/pdf/SkPDFTypes.cpp
@@ -0,0 +1,501 @@
+
+/*
+ * 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 "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+
+#ifdef SK_BUILD_FOR_WIN
+ #define SNPRINTF _snprintf
+#else
+ #define SNPRINTF snprintf
+#endif
+
+SK_DEFINE_INST_COUNT(SkPDFArray)
+SK_DEFINE_INST_COUNT(SkPDFBool)
+SK_DEFINE_INST_COUNT(SkPDFDict)
+SK_DEFINE_INST_COUNT(SkPDFInt)
+SK_DEFINE_INST_COUNT(SkPDFName)
+SK_DEFINE_INST_COUNT(SkPDFObject)
+SK_DEFINE_INST_COUNT(SkPDFObjRef)
+SK_DEFINE_INST_COUNT(SkPDFScalar)
+SK_DEFINE_INST_COUNT(SkPDFString)
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkPDFObject::emit(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ SkPDFObject* realObject = catalog->getSubstituteObject(this);
+ return realObject->emitObject(stream, catalog, indirect);
+}
+
+size_t SkPDFObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ SkDynamicMemoryWStream buffer;
+ emit(&buffer, catalog, indirect);
+ return buffer.getOffset();
+}
+
+void SkPDFObject::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {}
+
+void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) {
+ catalog->emitObjectNumber(stream, this);
+ stream->writeText(" obj\n");
+ emit(stream, catalog, false);
+ stream->writeText("\nendobj\n");
+}
+
+size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) {
+ return catalog->getObjectNumberSize(this) + strlen(" obj\n") +
+ this->getOutputSize(catalog, false) + strlen("\nendobj\n");
+}
+
+void SkPDFObject::AddResourceHelper(SkPDFObject* resource,
+ SkTDArray<SkPDFObject*>* list) {
+ list->push(resource);
+ resource->ref();
+}
+
+void SkPDFObject::GetResourcesHelper(
+ const SkTDArray<SkPDFObject*>* resources,
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects) {
+ if (resources->count()) {
+ newResourceObjects->setReserve(
+ newResourceObjects->count() + resources->count());
+ for (int i = 0; i < resources->count(); i++) {
+ if (!knownResourceObjects.contains((*resources)[i]) &&
+ !newResourceObjects->contains((*resources)[i])) {
+ newResourceObjects->add((*resources)[i]);
+ (*resources)[i]->ref();
+ (*resources)[i]->getResources(knownResourceObjects,
+ newResourceObjects);
+ }
+ }
+ }
+}
+
+SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {
+ SkSafeRef(obj);
+}
+
+SkPDFObjRef::~SkPDFObjRef() {}
+
+void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ SkASSERT(!indirect);
+ catalog->emitObjectNumber(stream, fObj.get());
+ stream->writeText(" R");
+}
+
+size_t SkPDFObjRef::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ SkASSERT(!indirect);
+ return catalog->getObjectNumberSize(fObj.get()) + strlen(" R");
+}
+
+SkPDFInt::SkPDFInt(int32_t value) : fValue(value) {}
+SkPDFInt::~SkPDFInt() {}
+
+void SkPDFInt::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect) {
+ return emitIndirectObject(stream, catalog);
+ }
+ stream->writeDecAsText(fValue);
+}
+
+SkPDFBool::SkPDFBool(bool value) : fValue(value) {}
+SkPDFBool::~SkPDFBool() {}
+
+void SkPDFBool::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ SkASSERT(!indirect);
+ if (fValue) {
+ stream->writeText("true");
+ } else {
+ stream->writeText("false");
+ }
+}
+
+size_t SkPDFBool::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ SkASSERT(!indirect);
+ if (fValue) {
+ return strlen("true");
+ }
+ return strlen("false");
+}
+
+SkPDFScalar::SkPDFScalar(SkScalar value) : fValue(value) {}
+SkPDFScalar::~SkPDFScalar() {}
+
+void SkPDFScalar::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect) {
+ return emitIndirectObject(stream, catalog);
+ }
+
+ Append(fValue, stream);
+}
+
+// static
+void SkPDFScalar::Append(SkScalar value, SkWStream* stream) {
+ // The range of reals in PDF/A is the same as SkFixed: +/- 32,767 and
+ // +/- 1/65,536 (though integers can range from 2^31 - 1 to -2^31).
+ // When using floats that are outside the whole value range, we can use
+ // integers instead.
+
+
+#if defined(SK_SCALAR_IS_FIXED)
+ stream->writeScalarAsText(value);
+ return;
+#endif // SK_SCALAR_IS_FIXED
+
+#if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
+ if (value > 32767 || value < -32767) {
+ stream->writeDecAsText(SkScalarRound(value));
+ return;
+ }
+
+ char buffer[SkStrAppendScalar_MaxSize];
+ char* end = SkStrAppendFixed(buffer, SkScalarToFixed(value));
+ stream->write(buffer, end - buffer);
+ return;
+#endif // !SK_ALLOW_LARGE_PDF_SCALARS
+
+#if defined(SK_SCALAR_IS_FLOAT) && defined(SK_ALLOW_LARGE_PDF_SCALARS)
+ // Floats have 24bits of significance, so anything outside that range is
+ // no more precise than an int. (Plus PDF doesn't support scientific
+ // notation, so this clamps to SK_Max/MinS32).
+ if (value > (1 << 24) || value < -(1 << 24)) {
+ stream->writeDecAsText(value);
+ return;
+ }
+ // Continue to enforce the PDF limits for small floats.
+ if (value < 1.0f/65536 && value > -1.0f/65536) {
+ stream->writeDecAsText(0);
+ return;
+ }
+ // SkStrAppendFloat might still use scientific notation, so use snprintf
+ // directly..
+ static const int kFloat_MaxSize = 19;
+ char buffer[kFloat_MaxSize];
+ int len = SNPRINTF(buffer, kFloat_MaxSize, "%#.8f", value);
+ // %f always prints trailing 0s, so strip them.
+ for (; buffer[len - 1] == '0' && len > 0; len--) {
+ buffer[len - 1] = '\0';
+ }
+ if (buffer[len - 1] == '.') {
+ buffer[len - 1] = '\0';
+ }
+ stream->writeText(buffer);
+ return;
+#endif // SK_SCALAR_IS_FLOAT && SK_ALLOW_LARGE_PDF_SCALARS
+}
+
+SkPDFString::SkPDFString(const char value[])
+ : fValue(FormatString(value, strlen(value))) {
+}
+
+SkPDFString::SkPDFString(const SkString& value)
+ : fValue(FormatString(value.c_str(), value.size())) {
+}
+
+SkPDFString::SkPDFString(const uint16_t* value, size_t len, bool wideChars)
+ : fValue(FormatString(value, len, wideChars)) {
+}
+
+SkPDFString::~SkPDFString() {}
+
+void SkPDFString::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect)
+ return emitIndirectObject(stream, catalog);
+ stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFString::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ if (indirect)
+ return getIndirectOutputSize(catalog);
+ return fValue.size();
+}
+
+// static
+SkString SkPDFString::FormatString(const char* input, size_t len) {
+ return DoFormatString(input, len, false, false);
+}
+
+SkString SkPDFString::FormatString(const uint16_t* input, size_t len,
+ bool wideChars) {
+ return DoFormatString(input, len, true, wideChars);
+}
+
+// static
+SkString SkPDFString::DoFormatString(const void* input, size_t len,
+ bool wideInput, bool wideOutput) {
+ SkASSERT(len <= kMaxLen);
+ const uint16_t* win = (const uint16_t*) input;
+ const char* cin = (const char*) input;
+
+ if (wideOutput) {
+ SkASSERT(wideInput);
+ SkString result;
+ result.append("<");
+ for (size_t i = 0; i < len; i++) {
+ result.appendHex(win[i], 4);
+ }
+ result.append(">");
+ return result;
+ }
+
+ // 7-bit clean is a heuristic to decide what string format to use;
+ // a 7-bit clean string should require little escaping.
+ bool sevenBitClean = true;
+ for (size_t i = 0; i < len; i++) {
+ SkASSERT(!wideInput || !(win[i] & ~0xFF));
+ char val = wideInput ? win[i] : cin[i];
+ if (val > '~' || val < ' ') {
+ sevenBitClean = false;
+ break;
+ }
+ }
+
+ SkString result;
+ if (sevenBitClean) {
+ result.append("(");
+ for (size_t i = 0; i < len; i++) {
+ SkASSERT(!wideInput || !(win[i] & ~0xFF));
+ char val = wideInput ? win[i] : cin[i];
+ if (val == '\\' || val == '(' || val == ')') {
+ result.append("\\");
+ }
+ result.append(&val, 1);
+ }
+ result.append(")");
+ } else {
+ result.append("<");
+ for (size_t i = 0; i < len; i++) {
+ SkASSERT(!wideInput || !(win[i] & ~0xFF));
+ unsigned char val = wideInput ? win[i] : cin[i];
+ result.appendHex(val, 2);
+ }
+ result.append(">");
+ }
+
+ return result;
+}
+
+SkPDFName::SkPDFName(const char name[]) : fValue(FormatName(SkString(name))) {}
+SkPDFName::SkPDFName(const SkString& name) : fValue(FormatName(name)) {}
+SkPDFName::~SkPDFName() {}
+
+bool SkPDFName::operator==(const SkPDFName& b) const {
+ return fValue == b.fValue;
+}
+
+void SkPDFName::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ SkASSERT(!indirect);
+ stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFName::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ SkASSERT(!indirect);
+ return fValue.size();
+}
+
+// static
+SkString SkPDFName::FormatName(const SkString& input) {
+ SkASSERT(input.size() <= kMaxLen);
+ // TODO(vandebo) If more escaping is needed, improve the linear scan.
+ static const char escaped[] = "#/%()<>[]{}";
+
+ SkString result("/");
+ for (size_t i = 0; i < input.size(); i++) {
+ if (input[i] & 0x80 || input[i] < '!' || strchr(escaped, input[i])) {
+ result.append("#");
+ // Mask with 0xFF to avoid sign extension. i.e. #FFFFFF81
+ result.appendHex(input[i] & 0xFF, 2);
+ } else {
+ result.append(input.c_str() + i, 1);
+ }
+ }
+
+ return result;
+}
+
+SkPDFArray::SkPDFArray() {}
+SkPDFArray::~SkPDFArray() {
+ fValue.unrefAll();
+}
+
+void SkPDFArray::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect) {
+ return emitIndirectObject(stream, catalog);
+ }
+
+ stream->writeText("[");
+ for (int i = 0; i < fValue.count(); i++) {
+ fValue[i]->emit(stream, catalog, false);
+ if (i + 1 < fValue.count()) {
+ stream->writeText(" ");
+ }
+ }
+ stream->writeText("]");
+}
+
+size_t SkPDFArray::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ if (indirect) {
+ return getIndirectOutputSize(catalog);
+ }
+
+ size_t result = strlen("[]");
+ if (fValue.count()) {
+ result += fValue.count() - 1;
+ }
+ for (int i = 0; i < fValue.count(); i++) {
+ result += fValue[i]->getOutputSize(catalog, false);
+ }
+ return result;
+}
+
+void SkPDFArray::reserve(int length) {
+ SkASSERT(length <= kMaxLen);
+ fValue.setReserve(length);
+}
+
+SkPDFObject* SkPDFArray::setAt(int offset, SkPDFObject* value) {
+ SkASSERT(offset < fValue.count());
+ value->ref();
+ fValue[offset]->unref();
+ fValue[offset] = value;
+ return value;
+}
+
+SkPDFObject* SkPDFArray::append(SkPDFObject* value) {
+ SkASSERT(fValue.count() < kMaxLen);
+ value->ref();
+ fValue.push(value);
+ return value;
+}
+
+void SkPDFArray::appendInt(int32_t value) {
+ SkASSERT(fValue.count() < kMaxLen);
+ fValue.push(new SkPDFInt(value));
+}
+
+void SkPDFArray::appendScalar(SkScalar value) {
+ SkASSERT(fValue.count() < kMaxLen);
+ fValue.push(new SkPDFScalar(value));
+}
+
+void SkPDFArray::appendName(const char name[]) {
+ SkASSERT(fValue.count() < kMaxLen);
+ fValue.push(new SkPDFName(name));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPDFDict::SkPDFDict() {}
+
+SkPDFDict::SkPDFDict(const char type[]) {
+ insertName("Type", type);
+}
+
+SkPDFDict::~SkPDFDict() {
+ clear();
+}
+
+void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) {
+ if (indirect) {
+ return emitIndirectObject(stream, catalog);
+ }
+
+ stream->writeText("<<");
+ for (int i = 0; i < fValue.count(); i++) {
+ fValue[i].key->emitObject(stream, catalog, false);
+ stream->writeText(" ");
+ fValue[i].value->emit(stream, catalog, false);
+ stream->writeText("\n");
+ }
+ stream->writeText(">>");
+}
+
+size_t SkPDFDict::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+ if (indirect) {
+ return getIndirectOutputSize(catalog);
+ }
+
+ size_t result = strlen("<<>>") + (fValue.count() * 2);
+ for (int i = 0; i < fValue.count(); i++) {
+ result += fValue[i].key->getOutputSize(catalog, false);
+ result += fValue[i].value->getOutputSize(catalog, false);
+ }
+ return result;
+}
+
+SkPDFObject* SkPDFDict::insert(SkPDFName* key, SkPDFObject* value) {
+ key->ref();
+ value->ref();
+ struct Rec* newEntry = fValue.append();
+ newEntry->key = key;
+ newEntry->value = value;
+ return value;
+}
+
+SkPDFObject* SkPDFDict::insert(const char key[], SkPDFObject* value) {
+ value->ref();
+ struct Rec* newEntry = fValue.append();
+ newEntry->key = new SkPDFName(key);
+ newEntry->value = value;
+ return value;
+}
+
+void SkPDFDict::insertInt(const char key[], int32_t value) {
+ struct Rec* newEntry = fValue.append();
+ newEntry->key = new SkPDFName(key);
+ newEntry->value = new SkPDFInt(value);
+}
+
+void SkPDFDict::insertScalar(const char key[], SkScalar value) {
+ struct Rec* newEntry = fValue.append();
+ newEntry->key = new SkPDFName(key);
+ newEntry->value = new SkPDFScalar(value);
+}
+
+void SkPDFDict::insertName(const char key[], const char name[]) {
+ struct Rec* newEntry = fValue.append();
+ newEntry->key = new SkPDFName(key);
+ newEntry->value = new SkPDFName(name);
+}
+
+void SkPDFDict::clear() {
+ for (int i = 0; i < fValue.count(); i++) {
+ fValue[i].key->unref();
+ fValue[i].value->unref();
+ }
+ fValue.reset();
+}
+
+SkPDFDict::Iter::Iter(const SkPDFDict& dict)
+ : fIter(dict.fValue.begin()),
+ fStop(dict.fValue.end()) {
+}
+
+SkPDFName* SkPDFDict::Iter::next(SkPDFObject** value) {
+ if (fIter != fStop) {
+ const Rec* cur = fIter;
+ fIter++;
+ *value = cur->value;
+ return cur->key;
+ }
+ *value = NULL;
+ return NULL;
+}
diff --git a/pdf/SkPDFTypes.h b/pdf/SkPDFTypes.h
new file mode 100644
index 00000000..5ed6386b
--- /dev/null
+++ b/pdf/SkPDFTypes.h
@@ -0,0 +1,444 @@
+
+/*
+ * Copyright 2010 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 SkPDFTypes_DEFINED
+#define SkPDFTypes_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+#include "SkTSet.h"
+#include "SkTypes.h"
+
+class SkPDFCatalog;
+class SkWStream;
+
+/** \class SkPDFObject
+
+ A PDF Object is the base class for primitive elements in a PDF file. A
+ common subtype is used to ease the use of indirect object references,
+ which are common in the PDF format.
+*/
+class SkPDFObject : public SkRefCnt {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFObject)
+
+ /** Return the size (number of bytes) of this object in the final output
+ * file. Compound objects or objects that are computationally intensive
+ * to output should override this method.
+ * @param catalog The object catalog to use.
+ * @param indirect If true, output an object identifier with the object.
+ */
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+ /** For non-primitive objects (i.e. objects defined outside this file),
+ * this method will add to newResourceObjects any objects that this method
+ * depends on, but not already in knownResourceObjects. This operates
+ * recursively so if this object depends on another object and that object
+ * depends on two more, all three objects will be added.
+ *
+ * @param knownResourceObjects The set of resources to be ignored.
+ * @param newResourceObjects The set to append dependant resources to.
+ */
+ virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+ /** Emit this object unless the catalog has a substitute object, in which
+ * case emit that.
+ * @see emitObject
+ */
+ void emit(SkWStream* stream, SkPDFCatalog* catalog, bool indirect);
+
+ /** Helper function to output an indirect object.
+ * @param catalog The object catalog to use.
+ * @param stream The writable output stream to send the output to.
+ */
+ void emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog);
+
+ /** Helper function to find the size of an indirect object.
+ * @param catalog The object catalog to use.
+ */
+ size_t getIndirectOutputSize(SkPDFCatalog* catalog);
+
+ /** Static helper function to add a resource to a list. The list takes
+ * a reference.
+ * @param resource The resource to add.
+ * @param list The list to add the resource to.
+ */
+ static void AddResourceHelper(SkPDFObject* resource,
+ SkTDArray<SkPDFObject*>* list);
+
+ /** Static helper function to copy and reference the resources (and all
+ * their subresources) into a new list.
+ * @param resources The resource list.
+ * @param newResourceObjects All the resource objects (recursively) used on
+ * the page are added to this array. This gives
+ * the caller a chance to deduplicate resources
+ * across pages.
+ * @param knownResourceObjects The set of resources to be ignored.
+ */
+ static void GetResourcesHelper(
+ const SkTDArray<SkPDFObject*>* resources,
+ const SkTSet<SkPDFObject*>& knownResourceObjects,
+ SkTSet<SkPDFObject*>* newResourceObjects);
+
+protected:
+ /** Subclasses must implement this method to print the object to the
+ * PDF file.
+ * @param catalog The object catalog to use.
+ * @param indirect If true, output an object identifier with the object.
+ * @param stream The writable output stream to send the output to.
+ */
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect) = 0;
+
+ typedef SkRefCnt INHERITED;
+};
+
+/** \class SkPDFObjRef
+
+ An indirect reference to a PDF object.
+*/
+class SkPDFObjRef : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFObjRef)
+
+ /** Create a reference to an existing SkPDFObject.
+ * @param obj The object to reference.
+ */
+ explicit SkPDFObjRef(SkPDFObject* obj);
+ virtual ~SkPDFObjRef();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+ SkAutoTUnref<SkPDFObject> fObj;
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFInt
+
+ An integer object in a PDF.
+*/
+class SkPDFInt : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFInt)
+
+ /** Create a PDF integer (usually for indirect reference purposes).
+ * @param value An integer value between 2^31 - 1 and -2^31.
+ */
+ explicit SkPDFInt(int32_t value);
+ virtual ~SkPDFInt();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+
+private:
+ int32_t fValue;
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFBool
+
+ An boolean value in a PDF.
+*/
+class SkPDFBool : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFBool)
+
+ /** Create a PDF boolean.
+ * @param value true or false.
+ */
+ explicit SkPDFBool(bool value);
+ virtual ~SkPDFBool();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+ bool fValue;
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFScalar
+
+ A real number object in a PDF.
+*/
+class SkPDFScalar : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFScalar)
+
+ /** Create a PDF real number.
+ * @param value A real value.
+ */
+ explicit SkPDFScalar(SkScalar value);
+ virtual ~SkPDFScalar();
+
+ static void Append(SkScalar value, SkWStream* stream);
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+
+private:
+ SkScalar fValue;
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFString
+
+ A string object in a PDF.
+*/
+class SkPDFString : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFString)
+
+ /** Create a PDF string. Maximum length (in bytes) is 65,535.
+ * @param value A string value.
+ */
+ explicit SkPDFString(const char value[]);
+ explicit SkPDFString(const SkString& value);
+
+ /** Create a PDF string. Maximum length (in bytes) is 65,535.
+ * @param value A string value.
+ * @param len The length of value.
+ * @param wideChars Indicates if the top byte in value is significant and
+ * should be encoded (true) or not (false).
+ */
+ SkPDFString(const uint16_t* value, size_t len, bool wideChars);
+ virtual ~SkPDFString();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+ static SkString FormatString(const char* input, size_t len);
+ static SkString FormatString(const uint16_t* input, size_t len,
+ bool wideChars);
+private:
+ static const size_t kMaxLen = 65535;
+
+ const SkString fValue;
+
+ static SkString DoFormatString(const void* input, size_t len,
+ bool wideInput, bool wideOutput);
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFName
+
+ A name object in a PDF.
+*/
+class SkPDFName : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFName)
+
+ /** Create a PDF name object. Maximum length is 127 bytes.
+ * @param value The name.
+ */
+ explicit SkPDFName(const char name[]);
+ explicit SkPDFName(const SkString& name);
+ virtual ~SkPDFName();
+
+ bool operator==(const SkPDFName& b) const;
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+ static const size_t kMaxLen = 127;
+
+ const SkString fValue;
+
+ static SkString FormatName(const SkString& input);
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFArray
+
+ An array object in a PDF.
+*/
+class SkPDFArray : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFArray)
+
+ /** Create a PDF array. Maximum length is 8191.
+ */
+ SkPDFArray();
+ virtual ~SkPDFArray();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+ /** The size of the array.
+ */
+ int size() { return fValue.count(); }
+
+ /** Preallocate space for the given number of entries.
+ * @param length The number of array slots to preallocate.
+ */
+ void reserve(int length);
+
+ /** Returns the object at the given offset in the array.
+ * @param index The index into the array to retrieve.
+ */
+ SkPDFObject* getAt(int index) { return fValue[index]; }
+
+ /** Set the object at the given offset in the array. Ref's value.
+ * @param index The index into the array to set.
+ * @param value The value to add to the array.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* setAt(int index, SkPDFObject* value);
+
+ /** Append the object to the end of the array and increments its ref count.
+ * @param value The value to add to the array.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* append(SkPDFObject* value);
+
+ /** Creates a SkPDFInt object and appends it to the array.
+ * @param value The value to add to the array.
+ */
+ void appendInt(int32_t value);
+
+ /** Creates a SkPDFScalar object and appends it to the array.
+ * @param value The value to add to the array.
+ */
+ void appendScalar(SkScalar value);
+
+ /** Creates a SkPDFName object and appends it to the array.
+ * @param value The value to add to the array.
+ */
+ void appendName(const char name[]);
+
+private:
+ static const int kMaxLen = 8191;
+ SkTDArray<SkPDFObject*> fValue;
+
+ typedef SkPDFObject INHERITED;
+};
+
+/** \class SkPDFDict
+
+ A dictionary object in a PDF.
+*/
+class SkPDFDict : public SkPDFObject {
+public:
+ SK_DECLARE_INST_COUNT(SkPDFDict)
+
+ /** Create a PDF dictionary. Maximum number of entries is 4095.
+ */
+ SkPDFDict();
+
+ /** Create a PDF dictionary with a Type entry.
+ * @param type The value of the Type entry.
+ */
+ explicit SkPDFDict(const char type[]);
+
+ virtual ~SkPDFDict();
+
+ // The SkPDFObject interface.
+ virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+ bool indirect);
+ virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+ /** The size of the dictionary.
+ */
+ int size() { return fValue.count(); }
+
+ /** Add the value to the dictionary with the given key. Refs value.
+ * @param key The key for this dictionary entry.
+ * @param value The value for this dictionary entry.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* insert(SkPDFName* key, SkPDFObject* value);
+
+ /** Add the value to the dictionary with the given key. Refs value. The
+ * method will create the SkPDFName object.
+ * @param key The text of the key for this dictionary entry.
+ * @param value The value for this dictionary entry.
+ * @return The value argument is returned.
+ */
+ SkPDFObject* insert(const char key[], SkPDFObject* value);
+
+ /** Add the int to the dictionary with the given key.
+ * @param key The text of the key for this dictionary entry.
+ * @param value The int value for this dictionary entry.
+ */
+ void insertInt(const char key[], int32_t value);
+
+ /** Add the scalar to the dictionary with the given key.
+ * @param key The text of the key for this dictionary entry.
+ * @param value The scalar value for this dictionary entry.
+ */
+ void insertScalar(const char key[], SkScalar value);
+
+ /** Add the name to the dictionary with the given key.
+ * @param key The text of the key for this dictionary entry.
+ * @param name The name for this dictionary entry.
+ */
+ void insertName(const char key[], const char name[]);
+
+ /** Add the name to the dictionary with the given key.
+ * @param key The text of the key for this dictionary entry.
+ * @param name The name for this dictionary entry.
+ */
+ void insertName(const char key[], const SkString& name) {
+ this->insertName(key, name.c_str());
+ }
+
+ /** Remove all entries from the dictionary.
+ */
+ void clear();
+
+private:
+ struct Rec {
+ SkPDFName* key;
+ SkPDFObject* value;
+ };
+
+public:
+ class Iter {
+ public:
+ explicit Iter(const SkPDFDict& dict);
+ SkPDFName* next(SkPDFObject** value);
+
+ private:
+ const Rec* fIter;
+ const Rec* fStop;
+ };
+
+private:
+ static const int kMaxLen = 4095;
+
+ SkTDArray<struct Rec> fValue;
+
+ typedef SkPDFObject INHERITED;
+};
+
+#endif
diff --git a/pdf/SkPDFUtils.cpp b/pdf/SkPDFUtils.cpp
new file mode 100644
index 00000000..d034270e
--- /dev/null
+++ b/pdf/SkPDFUtils.cpp
@@ -0,0 +1,249 @@
+
+/*
+ * 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 "SkData.h"
+#include "SkGeometry.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPDFResourceDict.h"
+#include "SkPDFUtils.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkPDFTypes.h"
+
+//static
+SkPDFArray* SkPDFUtils::RectToArray(const SkRect& rect) {
+ SkPDFArray* result = new SkPDFArray();
+ result->reserve(4);
+ result->appendScalar(rect.fLeft);
+ result->appendScalar(rect.fTop);
+ result->appendScalar(rect.fRight);
+ result->appendScalar(rect.fBottom);
+ return result;
+}
+
+// static
+SkPDFArray* SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
+ SkScalar values[6];
+ if (!matrix.asAffine(values)) {
+ SkMatrix::SetAffineIdentity(values);
+ }
+
+ SkPDFArray* result = new SkPDFArray;
+ result->reserve(6);
+ for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
+ result->appendScalar(values[i]);
+ }
+ return result;
+}
+
+// static
+void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
+ SkScalar values[6];
+ if (!matrix.asAffine(values)) {
+ SkMatrix::SetAffineIdentity(values);
+ }
+ for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
+ SkPDFScalar::Append(values[i], content);
+ content->writeText(" ");
+ }
+ content->writeText("cm\n");
+}
+
+// static
+void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) {
+ SkPDFScalar::Append(x, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(y, content);
+ content->writeText(" m\n");
+}
+
+// static
+void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) {
+ SkPDFScalar::Append(x, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(y, content);
+ content->writeText(" l\n");
+}
+
+// static
+void SkPDFUtils::AppendCubic(SkScalar ctl1X, SkScalar ctl1Y,
+ SkScalar ctl2X, SkScalar ctl2Y,
+ SkScalar dstX, SkScalar dstY, SkWStream* content) {
+ SkString cmd("y\n");
+ SkPDFScalar::Append(ctl1X, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(ctl1Y, content);
+ content->writeText(" ");
+ if (ctl2X != dstX || ctl2Y != dstY) {
+ cmd.set("c\n");
+ SkPDFScalar::Append(ctl2X, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(ctl2Y, content);
+ content->writeText(" ");
+ }
+ SkPDFScalar::Append(dstX, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(dstY, content);
+ content->writeText(" ");
+ content->writeText(cmd.c_str());
+}
+
+// static
+void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
+ // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
+ SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop);
+
+ SkPDFScalar::Append(rect.fLeft, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(bottom, content);
+ content->writeText(" ");
+ SkPDFScalar::Append(rect.width(), content);
+ content->writeText(" ");
+ SkPDFScalar::Append(rect.height(), content);
+ content->writeText(" re\n");
+}
+
+// static
+void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
+ SkWStream* content) {
+ // Filling a path with no area results in a drawing in PDF renderers but
+ // Chrome expects to be able to draw some such entities with no visible
+ // result, so we detect those cases and discard the drawing for them.
+ // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
+ enum SkipFillState {
+ kEmpty_SkipFillState = 0,
+ kSingleLine_SkipFillState = 1,
+ kNonSingleLine_SkipFillState = 2,
+ };
+ SkipFillState fillState = kEmpty_SkipFillState;
+ if (paintStyle != SkPaint::kFill_Style) {
+ fillState = kNonSingleLine_SkipFillState;
+ }
+ SkPoint lastMovePt = SkPoint::Make(0,0);
+ SkDynamicMemoryWStream currentSegment;
+ SkPoint args[4];
+ SkPath::Iter iter(path, false);
+ for (SkPath::Verb verb = iter.next(args);
+ verb != SkPath::kDone_Verb;
+ verb = iter.next(args)) {
+ // args gets all the points, even the implicit first point.
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ MoveTo(args[0].fX, args[0].fY, &currentSegment);
+ lastMovePt = args[0];
+ fillState = kEmpty_SkipFillState;
+ break;
+ case SkPath::kLine_Verb:
+ AppendLine(args[1].fX, args[1].fY, &currentSegment);
+ if (fillState == kEmpty_SkipFillState) {
+ if (args[0] != lastMovePt) {
+ fillState = kSingleLine_SkipFillState;
+ }
+ } else if (fillState == kSingleLine_SkipFillState) {
+ fillState = kNonSingleLine_SkipFillState;
+ }
+ break;
+ case SkPath::kQuad_Verb: {
+ SkPoint cubic[4];
+ SkConvertQuadToCubic(args, cubic);
+ AppendCubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
+ cubic[3].fX, cubic[3].fY, &currentSegment);
+ fillState = kNonSingleLine_SkipFillState;
+ break;
+ }
+ case SkPath::kCubic_Verb:
+ AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
+ args[3].fX, args[3].fY, &currentSegment);
+ fillState = kNonSingleLine_SkipFillState;
+ break;
+ case SkPath::kClose_Verb:
+ if (fillState != kSingleLine_SkipFillState) {
+ ClosePath(&currentSegment);
+ SkData* data = currentSegment.copyToData();
+ content->write(data->data(), data->size());
+ data->unref();
+ }
+ currentSegment.reset();
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+ }
+ if (currentSegment.bytesWritten() > 0) {
+ SkData* data = currentSegment.copyToData();
+ content->write(data->data(), data->size());
+ data->unref();
+ }
+}
+
+// static
+void SkPDFUtils::ClosePath(SkWStream* content) {
+ content->writeText("h\n");
+}
+
+// static
+void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill,
+ SkWStream* content) {
+ if (style == SkPaint::kFill_Style) {
+ content->writeText("f");
+ } else if (style == SkPaint::kStrokeAndFill_Style) {
+ content->writeText("B");
+ } else if (style == SkPaint::kStroke_Style) {
+ content->writeText("S");
+ }
+
+ if (style != SkPaint::kStroke_Style) {
+ NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
+ NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
+ if (fill == SkPath::kEvenOdd_FillType) {
+ content->writeText("*");
+ }
+ }
+ content->writeText("\n");
+}
+
+// static
+void SkPDFUtils::StrokePath(SkWStream* content) {
+ SkPDFUtils::PaintPath(
+ SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
+}
+
+// static
+void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
+ content->writeText("/");
+ content->writeText(SkPDFResourceDict::getResourceName(
+ SkPDFResourceDict::kXObject_ResourceType,
+ objectIndex).c_str());
+ content->writeText(" Do\n");
+}
+
+// static
+void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
+ content->writeText("/");
+ content->writeText(SkPDFResourceDict::getResourceName(
+ SkPDFResourceDict::kExtGState_ResourceType,
+ objectIndex).c_str());
+ content->writeText(" gs\n");
+}
+
+// static
+void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
+ // Select Pattern color space (CS, cs) and set pattern object as current
+ // color (SCN, scn)
+ SkString resourceName = SkPDFResourceDict::getResourceName(
+ SkPDFResourceDict::kPattern_ResourceType,
+ objectIndex);
+ content->writeText("/Pattern CS/Pattern cs/");
+ content->writeText(resourceName.c_str());
+ content->writeText(" SCN/");
+ content->writeText(resourceName.c_str());
+ content->writeText(" scn\n");
+}
diff --git a/pdf/SkPDFUtils.h b/pdf/SkPDFUtils.h
new file mode 100644
index 00000000..4f54db66
--- /dev/null
+++ b/pdf/SkPDFUtils.h
@@ -0,0 +1,59 @@
+
+/*
+ * 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 SkPDFUtils_DEFINED
+#define SkPDFUtils_DEFINED
+
+#include "SkPaint.h"
+#include "SkPath.h"
+
+class SkMatrix;
+class SkPath;
+class SkPDFArray;
+struct SkRect;
+class SkWStream;
+
+#if 0
+#define PRINT_NOT_IMPL(str) fprintf(stderr, str)
+#else
+#define PRINT_NOT_IMPL(str)
+#endif
+
+#define NOT_IMPLEMENTED(condition, assert) \
+ do { \
+ if (condition) { \
+ PRINT_NOT_IMPL("NOT_IMPLEMENTED: " #condition "\n"); \
+ SkDEBUGCODE(SkASSERT(!assert);) \
+ } \
+ } while (0)
+
+class SkPDFUtils {
+public:
+ static SkPDFArray* RectToArray(const SkRect& rect);
+ static SkPDFArray* MatrixToArray(const SkMatrix& matrix);
+ static void AppendTransform(const SkMatrix& matrix, SkWStream* content);
+
+ static void MoveTo(SkScalar x, SkScalar y, SkWStream* content);
+ static void AppendLine(SkScalar x, SkScalar y, SkWStream* content);
+ static void AppendCubic(SkScalar ctl1X, SkScalar ctl1Y,
+ SkScalar ctl2X, SkScalar ctl2Y,
+ SkScalar dstX, SkScalar dstY, SkWStream* content);
+ static void AppendRectangle(const SkRect& rect, SkWStream* content);
+ static void EmitPath(const SkPath& path, SkPaint::Style paintStyle,
+ SkWStream* content);
+ static void ClosePath(SkWStream* content);
+ static void PaintPath(SkPaint::Style style, SkPath::FillType fill,
+ SkWStream* content);
+ static void StrokePath(SkWStream* content);
+ static void DrawFormXObject(int objectIndex, SkWStream* content);
+ static void ApplyGraphicState(int objectIndex, SkWStream* content);
+ static void ApplyPattern(int objectIndex, SkWStream* content);
+};
+
+#endif
diff --git a/pdf/SkTSet.h b/pdf/SkTSet.h
new file mode 100644
index 00000000..c2d2785f
--- /dev/null
+++ b/pdf/SkTSet.h
@@ -0,0 +1,356 @@
+/*
+ * 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 SkTSet_DEFINED
+#define SkTSet_DEFINED
+
+#include "SkTSort.h"
+#include "SkTDArray.h"
+#include "SkTypes.h"
+
+/** \class SkTSet<T>
+
+ The SkTSet template class defines a set. Elements are additionally
+ guaranteed to be sorted by their insertion order.
+ Main operations supported now are: add, merge, find and contains.
+
+ TSet<T> is mutable.
+*/
+
+// TODO: Add remove, intersect and difference operations.
+// TODO: Add bench tests.
+template <typename T> class SkTSet {
+public:
+ SkTSet() {
+ fSetArray = SkNEW(SkTDArray<T>);
+ fOrderedArray = SkNEW(SkTDArray<T>);
+ }
+
+ ~SkTSet() {
+ SkASSERT(fSetArray);
+ SkDELETE(fSetArray);
+ SkASSERT(fOrderedArray);
+ SkDELETE(fOrderedArray);
+ }
+
+ SkTSet(const SkTSet<T>& src) {
+ this->fSetArray = SkNEW_ARGS(SkTDArray<T>, (*src.fSetArray));
+ this->fOrderedArray = SkNEW_ARGS(SkTDArray<T>, (*src.fOrderedArray));
+#ifdef SK_DEBUG
+ validate();
+#endif
+ }
+
+ SkTSet<T>& operator=(const SkTSet<T>& src) {
+ *this->fSetArray = *src.fSetArray;
+ *this->fOrderedArray = *src.fOrderedArray;
+#ifdef SK_DEBUG
+ validate();
+#endif
+ return *this;
+ }
+
+ /** Merges src elements into this, and returns the number of duplicates
+ * found. Elements from src will retain their ordering and will be ordered
+ * after the elements currently in this set.
+ *
+ * Implementation note: this uses a 2-stage merge to obtain O(n log n) time.
+ * The first stage goes through src.fOrderedArray, checking if
+ * this->contains() is false before adding to this.fOrderedArray.
+ * The second stage does a standard sorted list merge on the fSetArrays.
+ */
+ int mergeInto(const SkTSet<T>& src) {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+
+ // Do fOrderedArray merge.
+ for (int i = 0; i < src.count(); ++i) {
+ if (!contains((*src.fOrderedArray)[i])) {
+ fOrderedArray->push((*src.fOrderedArray)[i]);
+ }
+ }
+
+ // Do fSetArray merge.
+ int duplicates = 0;
+
+ SkTDArray<T>* fArrayNew = new SkTDArray<T>();
+ fArrayNew->setReserve(fOrderedArray->count());
+ int i = 0;
+ int j = 0;
+
+ while (i < fSetArray->count() && j < src.count()) {
+ if ((*fSetArray)[i] < (*src.fSetArray)[j]) {
+ fArrayNew->push((*fSetArray)[i]);
+ i++;
+ } else if ((*fSetArray)[i] > (*src.fSetArray)[j]) {
+ fArrayNew->push((*src.fSetArray)[j]);
+ j++;
+ } else {
+ duplicates++;
+ j++; // Skip one of the duplicates.
+ }
+ }
+
+ while (i < fSetArray->count()) {
+ fArrayNew->push((*fSetArray)[i]);
+ i++;
+ }
+
+ while (j < src.count()) {
+ fArrayNew->push((*src.fSetArray)[j]);
+ j++;
+ }
+ SkDELETE(fSetArray);
+ fSetArray = fArrayNew;
+ fArrayNew = NULL;
+
+#ifdef SK_DEBUG
+ validate();
+#endif
+ return duplicates;
+ }
+
+ /** Adds a new element into set and returns false if the element is already
+ * in this set.
+ */
+ bool add(const T& elem) {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+
+ int pos = 0;
+ int i = find(elem, &pos);
+ if (i >= 0) {
+ return false;
+ }
+ *fSetArray->insert(pos) = elem;
+ fOrderedArray->push(elem);
+#ifdef SK_DEBUG
+ validate();
+#endif
+ return true;
+ }
+
+ /** Returns true if this set is empty.
+ */
+ bool isEmpty() const {
+ SkASSERT(fOrderedArray);
+ SkASSERT(fSetArray);
+ SkASSERT(fSetArray->isEmpty() == fOrderedArray->isEmpty());
+ return fOrderedArray->isEmpty();
+ }
+
+ /** Return the number of elements in the set.
+ */
+ int count() const {
+ SkASSERT(fOrderedArray);
+ SkASSERT(fSetArray);
+ SkASSERT(fSetArray->count() == fOrderedArray->count());
+ return fOrderedArray->count();
+ }
+
+ /** Return the number of bytes in the set: count * sizeof(T).
+ */
+ size_t bytes() const {
+ SkASSERT(fOrderedArray);
+ return fOrderedArray->bytes();
+ }
+
+ /** Return the beginning of a set iterator.
+ * Elements in the iterator will be sorted ascending.
+ */
+ const T* begin() const {
+ SkASSERT(fOrderedArray);
+ return fOrderedArray->begin();
+ }
+
+ /** Return the end of a set iterator.
+ */
+ const T* end() const {
+ SkASSERT(fOrderedArray);
+ return fOrderedArray->end();
+ }
+
+ const T& operator[](int index) const {
+ SkASSERT(fOrderedArray);
+ return (*fOrderedArray)[index];
+ }
+
+ /** Resets the set (deletes memory and initiates an empty set).
+ */
+ void reset() {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fSetArray->reset();
+ fOrderedArray->reset();
+ }
+
+ /** Rewinds the set (preserves memory and initiates an empty set).
+ */
+ void rewind() {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fSetArray->rewind();
+ fOrderedArray->rewind();
+ }
+
+ /** Reserves memory for the set.
+ */
+ void setReserve(size_t reserve) {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fSetArray->setReserve(reserve);
+ fOrderedArray->setReserve(reserve);
+ }
+
+ /** Returns true if the array contains this element.
+ */
+ bool contains(const T& elem) const {
+ SkASSERT(fSetArray);
+ return (this->find(elem) >= 0);
+ }
+
+ /** Copies internal array to destination.
+ */
+ void copy(T* dst) const {
+ SkASSERT(fOrderedArray);
+ fOrderedArray->copyRange(dst, 0, fOrderedArray->count());
+ }
+
+ /** Returns a const reference to the internal vector.
+ */
+ const SkTDArray<T>& toArray() {
+ SkASSERT(fOrderedArray);
+ return *fOrderedArray;
+ }
+
+ /** Unref all elements in the set.
+ */
+ void unrefAll() {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fOrderedArray->unrefAll();
+ // Also reset the other array, as SkTDArray::unrefAll does an
+ // implcit reset
+ fSetArray->reset();
+ }
+
+ /** safeUnref all elements in the set.
+ */
+ void safeUnrefAll() {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fOrderedArray->safeUnrefAll();
+ // Also reset the other array, as SkTDArray::safeUnrefAll does an
+ // implcit reset
+ fSetArray->reset();
+ }
+
+#ifdef SK_DEBUG
+ void validate() const {
+ SkASSERT(fSetArray);
+ SkASSERT(fOrderedArray);
+ fSetArray->validate();
+ fOrderedArray->validate();
+ SkASSERT(isSorted() && !hasDuplicates() && arraysConsistent());
+ }
+
+ bool hasDuplicates() const {
+ for (int i = 0; i < fSetArray->count() - 1; ++i) {
+ if ((*fSetArray)[i] == (*fSetArray)[i + 1]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool isSorted() const {
+ for (int i = 0; i < fSetArray->count() - 1; ++i) {
+ // Use only < operator
+ if (!((*fSetArray)[i] < (*fSetArray)[i + 1])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Checks if fSetArray is consistent with fOrderedArray
+ */
+ bool arraysConsistent() const {
+ if (fSetArray->count() != fOrderedArray->count()) {
+ return false;
+ }
+ if (fOrderedArray->count() == 0) {
+ return true;
+ }
+
+ // Copy and sort fOrderedArray, then compare to fSetArray.
+ // A O(n log n) algorithm is necessary as O(n^2) will choke some GMs.
+ SkAutoMalloc sortedArray(fOrderedArray->bytes());
+ T* sortedBase = reinterpret_cast<T*>(sortedArray.get());
+ size_t count = fOrderedArray->count();
+ fOrderedArray->copyRange(sortedBase, 0, count);
+
+ SkTQSort<T>(sortedBase, sortedBase + count - 1);
+
+ for (size_t i = 0; i < count; ++i) {
+ if (sortedBase[i] != (*fSetArray)[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+#endif
+
+private:
+ SkTDArray<T>* fSetArray; // Sorted by pointer address for fast
+ // lookup.
+ SkTDArray<T>* fOrderedArray; // Sorted by insertion order for
+ // deterministic output.
+
+ /** Returns the index in fSetArray where an element was found.
+ * Returns -1 if the element was not found, and it fills *posToInsertSorted
+ * with the index of the place where elem should be inserted to preserve the
+ * internal array sorted.
+ * If element was found, *posToInsertSorted is undefined.
+ */
+ int find(const T& elem, int* posToInsertSorted = NULL) const {
+ SkASSERT(fSetArray);
+
+ if (fSetArray->count() == 0) {
+ if (posToInsertSorted) {
+ *posToInsertSorted = 0;
+ }
+ return -1;
+ }
+ int iMin = 0;
+ int iMax = fSetArray->count();
+
+ while (iMin < iMax - 1) {
+ int iMid = (iMin + iMax) / 2;
+ if (elem < (*fSetArray)[iMid]) {
+ iMax = iMid;
+ } else {
+ iMin = iMid;
+ }
+ }
+ if (elem == (*fSetArray)[iMin]) {
+ return iMin;
+ }
+ if (posToInsertSorted) {
+ if (elem < (*fSetArray)[iMin]) {
+ *posToInsertSorted = iMin;
+ } else {
+ *posToInsertSorted = iMin + 1;
+ }
+ }
+
+ return -1;
+ }
+};
+
+#endif
diff --git a/pipe/SkGPipePriv.h b/pipe/SkGPipePriv.h
new file mode 100644
index 00000000..f5f98f2b
--- /dev/null
+++ b/pipe/SkGPipePriv.h
@@ -0,0 +1,282 @@
+
+/*
+ * 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 SkGPipePriv_DEFINED
+#define SkGPipePriv_DEFINED
+
+#include "SkTypes.h"
+
+#define UNIMPLEMENTED
+
+// these must be contiguous, 0...N-1
+enum PaintFlats {
+ kColorFilter_PaintFlat,
+ kDrawLooper_PaintFlat,
+ kMaskFilter_PaintFlat,
+ kPathEffect_PaintFlat,
+ kRasterizer_PaintFlat,
+ kShader_PaintFlat,
+ kImageFilter_PaintFlat,
+ kXfermode_PaintFlat,
+ kAnnotation_PaintFlat,
+
+ kLast_PaintFlat = kAnnotation_PaintFlat
+};
+#define kCount_PaintFlats (kLast_PaintFlat + 1)
+
+enum DrawOps {
+ kSkip_DrawOp, // skip an addition N bytes (N == data)
+
+ // these match Canvas apis
+ kClipPath_DrawOp,
+ kClipRegion_DrawOp,
+ kClipRect_DrawOp,
+ kClipRRect_DrawOp,
+ kConcat_DrawOp,
+ kDrawBitmap_DrawOp,
+ kDrawBitmapMatrix_DrawOp,
+ kDrawBitmapNine_DrawOp,
+ kDrawBitmapRectToRect_DrawOp,
+ kDrawClear_DrawOp,
+ kDrawData_DrawOp,
+ kDrawOval_DrawOp,
+ kDrawPaint_DrawOp,
+ kDrawPath_DrawOp,
+ kDrawPicture_DrawOp,
+ kDrawPoints_DrawOp,
+ kDrawPosText_DrawOp,
+ kDrawPosTextH_DrawOp,
+ kDrawRect_DrawOp,
+ kDrawRRect_DrawOp,
+ kDrawSprite_DrawOp,
+ kDrawText_DrawOp,
+ kDrawTextOnPath_DrawOp,
+ kDrawVertices_DrawOp,
+ kRestore_DrawOp,
+ kRotate_DrawOp,
+ kSave_DrawOp,
+ kSaveLayer_DrawOp,
+ kScale_DrawOp,
+ kSetMatrix_DrawOp,
+ kSkew_DrawOp,
+ kTranslate_DrawOp,
+
+ kPaintOp_DrawOp,
+ kSetTypeface_DrawOp,
+
+ kDef_Typeface_DrawOp,
+ kDef_Flattenable_DrawOp,
+ kDef_Bitmap_DrawOp,
+ kDef_Factory_DrawOp,
+
+ // these are signals to playback, not drawing verbs
+ kReportFlags_DrawOp,
+ kShareBitmapHeap_DrawOp,
+ kDone_DrawOp,
+};
+
+/**
+ * DrawOp packs into a 32bit int as follows
+ *
+ * DrawOp:8 - Flags:4 - Data:20
+ *
+ * Flags and Data are called out separately, so we can reuse Data between
+ * different Ops that might have different Flags. e.g. Data might be a Paint
+ * index for both drawRect (no flags) and saveLayer (does have flags).
+ *
+ * All Ops that take a SkPaint use their Data field to store the index to
+ * the paint (previously defined with kPaintOp_DrawOp).
+ */
+
+#define DRAWOPS_OP_BITS 8
+#define DRAWOPS_FLAG_BITS 4
+#define DRAWOPS_DATA_BITS 20
+
+#define DRAWOPS_OP_MASK ((1 << DRAWOPS_OP_BITS) - 1)
+#define DRAWOPS_FLAG_MASK ((1 << DRAWOPS_FLAG_BITS) - 1)
+#define DRAWOPS_DATA_MASK ((1 << DRAWOPS_DATA_BITS) - 1)
+
+static inline unsigned DrawOp_unpackOp(uint32_t op32) {
+ return (op32 >> (DRAWOPS_FLAG_BITS + DRAWOPS_DATA_BITS));
+}
+
+static inline unsigned DrawOp_unpackFlags(uint32_t op32) {
+ return (op32 >> DRAWOPS_DATA_BITS) & DRAWOPS_FLAG_MASK;
+}
+
+static inline unsigned DrawOp_unpackData(uint32_t op32) {
+ return op32 & DRAWOPS_DATA_MASK;
+}
+
+static inline uint32_t DrawOp_packOpFlagData(DrawOps op, unsigned flags, unsigned data) {
+ SkASSERT(0 == (op & ~DRAWOPS_OP_MASK));
+ SkASSERT(0 == (flags & ~DRAWOPS_FLAG_MASK));
+ SkASSERT(0 == (data & ~DRAWOPS_DATA_MASK));
+
+ return (op << (DRAWOPS_FLAG_BITS + DRAWOPS_DATA_BITS)) |
+ (flags << DRAWOPS_DATA_BITS) |
+ data;
+}
+
+/** DrawOp specific flag bits
+ */
+
+enum {
+ kSaveLayer_HasBounds_DrawOpFlag = 1 << 0,
+ kSaveLayer_HasPaint_DrawOpFlag = 1 << 1,
+};
+enum {
+ kClear_HasColor_DrawOpFlag = 1 << 0
+};
+enum {
+ kDrawTextOnPath_HasMatrix_DrawOpFlag = 1 << 0
+};
+enum {
+ kDrawVertices_HasTexs_DrawOpFlag = 1 << 0,
+ kDrawVertices_HasColors_DrawOpFlag = 1 << 1,
+ kDrawVertices_HasIndices_DrawOpFlag = 1 << 2,
+};
+enum {
+ kDrawBitmap_HasPaint_DrawOpFlag = 1 << 0,
+ // Specific to drawBitmapRect, but needs to be different from HasPaint,
+ // which is used for all drawBitmap calls, so include it here.
+ kDrawBitmap_HasSrcRect_DrawOpFlag = 1 << 1,
+};
+enum {
+ kClip_HasAntiAlias_DrawOpFlag = 1 << 0,
+};
+///////////////////////////////////////////////////////////////////////////////
+
+class BitmapInfo : SkNoncopyable {
+public:
+ BitmapInfo(SkBitmap* bitmap, uint32_t genID, int toBeDrawnCount)
+ : fBitmap(bitmap)
+ , fGenID(genID)
+ , fBytesAllocated(0)
+ , fMoreRecentlyUsed(NULL)
+ , fLessRecentlyUsed(NULL)
+ , fToBeDrawnCount(toBeDrawnCount)
+ {}
+
+ ~BitmapInfo() {
+ SkASSERT(0 == fToBeDrawnCount);
+ SkDELETE(fBitmap);
+ }
+
+ void addDraws(int drawsToAdd) {
+ if (0 == fToBeDrawnCount) {
+ // The readers will only ever decrement the count, so once the
+ // count is zero, the writer will be the only one modifying it,
+ // so it does not need to be an atomic operation.
+ fToBeDrawnCount = drawsToAdd;
+ } else {
+ sk_atomic_add(&fToBeDrawnCount, drawsToAdd);
+ }
+ }
+
+ void decDraws() {
+ sk_atomic_dec(&fToBeDrawnCount);
+ }
+
+ int drawCount() const {
+ return fToBeDrawnCount;
+ }
+
+ SkBitmap* fBitmap;
+ // Store the generation ID of the original bitmap, since copying does
+ // not copy this field, so fBitmap's generation ID will not be useful
+ // for comparing.
+ // FIXME: Is it reasonable to make copying a bitmap/pixelref copy the
+ // generation ID?
+ uint32_t fGenID;
+ // Keep track of the bytes allocated for this bitmap. When replacing the
+ // bitmap or removing this BitmapInfo we know how much memory has been
+ // reclaimed.
+ size_t fBytesAllocated;
+ // TODO: Generalize the LRU caching mechanism
+ BitmapInfo* fMoreRecentlyUsed;
+ BitmapInfo* fLessRecentlyUsed;
+private:
+ int fToBeDrawnCount;
+};
+
+static inline bool shouldFlattenBitmaps(uint32_t flags) {
+ return SkToBool(flags & SkGPipeWriter::kCrossProcess_Flag
+ && !(flags & SkGPipeWriter::kSharedAddressSpace_Flag));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum PaintOps {
+ kReset_PaintOp, // no arg
+
+ kFlags_PaintOp, // arg inline
+ kColor_PaintOp, // arg 32
+ kStyle_PaintOp, // arg inline
+ kJoin_PaintOp, // arg inline
+ kCap_PaintOp, // arg inline
+ kWidth_PaintOp, // arg scalar
+ kMiter_PaintOp,// arg scalar
+
+ kEncoding_PaintOp, // arg inline - text
+ kHinting_PaintOp, // arg inline - text
+ kAlign_PaintOp, // arg inline - text
+ kTextSize_PaintOp, // arg scalar - text
+ kTextScaleX_PaintOp,// arg scalar - text
+ kTextSkewX_PaintOp, // arg scalar - text
+ kTypeface_PaintOp, // arg inline (index) - text
+
+ kFlatIndex_PaintOp, // flags=paintflat, data=index
+};
+
+#define PAINTOPS_OP_BITS 8
+#define PAINTOPS_FLAG_BITS 4
+#define PAINTOPS_DATA_BITS 20
+
+#define PAINTOPS_OP_MASK ((1 << PAINTOPS_OP_BITS) - 1)
+#define PAINTOPS_FLAG_MASK ((1 << PAINTOPS_FLAG_BITS) - 1)
+#define PAINTOPS_DATA_MASK ((1 << PAINTOPS_DATA_BITS) - 1)
+
+static inline unsigned PaintOp_unpackOp(uint32_t op32) {
+ return (op32 >> (PAINTOPS_FLAG_BITS + PAINTOPS_DATA_BITS));
+}
+
+static inline unsigned PaintOp_unpackFlags(uint32_t op32) {
+ return (op32 >> PAINTOPS_DATA_BITS) & PAINTOPS_FLAG_MASK;
+}
+
+static inline unsigned PaintOp_unpackData(uint32_t op32) {
+ return op32 & PAINTOPS_DATA_MASK;
+}
+
+static inline uint32_t PaintOp_packOp(PaintOps op) {
+ SkASSERT(0 == (op & ~PAINTOPS_OP_MASK));
+
+ return op << (PAINTOPS_FLAG_BITS + PAINTOPS_DATA_BITS);
+}
+
+static inline uint32_t PaintOp_packOpData(PaintOps op, unsigned data) {
+ SkASSERT(0 == (op & ~PAINTOPS_OP_MASK));
+ SkASSERT(0 == (data & ~PAINTOPS_DATA_MASK));
+
+ return (op << (PAINTOPS_FLAG_BITS + PAINTOPS_DATA_BITS)) | data;
+}
+
+static inline uint32_t PaintOp_packOpFlagData(PaintOps op, unsigned flags, unsigned data) {
+ SkASSERT(0 == (op & ~PAINTOPS_OP_MASK));
+ SkASSERT(0 == (flags & ~PAINTOPS_FLAG_MASK));
+ SkASSERT(0 == (data & ~PAINTOPS_DATA_MASK));
+
+ return (op << (PAINTOPS_FLAG_BITS + PAINTOPS_DATA_BITS)) |
+ (flags << PAINTOPS_DATA_BITS) |
+ data;
+}
+
+#endif
diff --git a/pipe/SkGPipeRead.cpp b/pipe/SkGPipeRead.cpp
new file mode 100644
index 00000000..3f9ce12d
--- /dev/null
+++ b/pipe/SkGPipeRead.cpp
@@ -0,0 +1,857 @@
+
+/*
+ * 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 "SkBitmapHeap.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkGPipe.h"
+#include "SkGPipePriv.h"
+#include "SkReader32.h"
+#include "SkStream.h"
+
+#include "SkColorFilter.h"
+#include "SkDrawLooper.h"
+#include "SkMaskFilter.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkPathEffect.h"
+#include "SkRasterizer.h"
+#include "SkRRect.h"
+#include "SkShader.h"
+#include "SkTypeface.h"
+#include "SkXfermode.h"
+
+static void set_paintflat(SkPaint* paint, SkFlattenable* obj, unsigned paintFlat) {
+ SkASSERT(paintFlat < kCount_PaintFlats);
+ switch (paintFlat) {
+ case kColorFilter_PaintFlat:
+ paint->setColorFilter((SkColorFilter*)obj);
+ break;
+ case kDrawLooper_PaintFlat:
+ paint->setLooper((SkDrawLooper*)obj);
+ break;
+ case kMaskFilter_PaintFlat:
+ paint->setMaskFilter((SkMaskFilter*)obj);
+ break;
+ case kPathEffect_PaintFlat:
+ paint->setPathEffect((SkPathEffect*)obj);
+ break;
+ case kRasterizer_PaintFlat:
+ paint->setRasterizer((SkRasterizer*)obj);
+ break;
+ case kShader_PaintFlat:
+ paint->setShader((SkShader*)obj);
+ break;
+ case kImageFilter_PaintFlat:
+ paint->setImageFilter((SkImageFilter*)obj);
+ break;
+ case kXfermode_PaintFlat:
+ paint->setXfermode((SkXfermode*)obj);
+ break;
+ case kAnnotation_PaintFlat:
+ paint->setAnnotation((SkAnnotation*)obj);
+ break;
+ default:
+ SkDEBUGFAIL("never gets here");
+ }
+}
+
+template <typename T> class SkRefCntTDArray : public SkTDArray<T> {
+public:
+ ~SkRefCntTDArray() { this->unrefAll(); }
+};
+
+class SkGPipeState : public SkBitmapHeapReader {
+public:
+ SkGPipeState();
+ ~SkGPipeState();
+
+ void setSilent(bool silent) {
+ fSilent = silent;
+ }
+
+ bool shouldDraw() {
+ return !fSilent;
+ }
+
+ void setFlags(unsigned flags) {
+ if (fFlags != flags) {
+ fFlags = flags;
+ this->updateReader();
+ }
+ }
+
+ unsigned getFlags() const {
+ return fFlags;
+ }
+
+ void setReader(SkOrderedReadBuffer* reader) {
+ fReader = reader;
+ this->updateReader();
+ }
+
+ const SkPaint& paint() const { return fPaint; }
+ SkPaint* editPaint() { return &fPaint; }
+
+ SkFlattenable* getFlat(unsigned index) const {
+ if (0 == index) {
+ return NULL;
+ }
+ return fFlatArray[index - 1];
+ }
+
+ void defFlattenable(PaintFlats pf, int index) {
+ index--;
+ SkFlattenable* obj = fReader->readFlattenable();
+ if (fFlatArray.count() == index) {
+ *fFlatArray.append() = obj;
+ } else {
+ SkSafeUnref(fFlatArray[index]);
+ fFlatArray[index] = obj;
+ }
+ }
+
+ void defFactory(const char* name) {
+ SkFlattenable::Factory factory = SkFlattenable::NameToFactory(name);
+ if (factory) {
+ SkASSERT(fFactoryArray.find(factory) < 0);
+ *fFactoryArray.append() = factory;
+ }
+ }
+
+ /**
+ * Add a bitmap to the array of bitmaps, or replace an existing one.
+ * This is only used when in cross process mode without a shared heap.
+ */
+ void addBitmap(int index) {
+ SkASSERT(shouldFlattenBitmaps(fFlags));
+ SkBitmap* bm;
+ if(fBitmaps.count() == index) {
+ bm = SkNEW(SkBitmap);
+ *fBitmaps.append() = bm;
+ } else {
+ bm = fBitmaps[index];
+ }
+ fReader->readBitmap(bm);
+ }
+
+ /**
+ * Override of SkBitmapHeapReader, so that SkOrderedReadBuffer can use
+ * these SkBitmaps for bitmap shaders. Used only in cross process mode
+ * without a shared heap.
+ */
+ virtual SkBitmap* getBitmap(int32_t index) const SK_OVERRIDE {
+ SkASSERT(shouldFlattenBitmaps(fFlags));
+ return fBitmaps[index];
+ }
+
+ /**
+ * Needed to be a non-abstract subclass of SkBitmapHeapReader.
+ */
+ virtual void releaseRef(int32_t) SK_OVERRIDE {}
+
+ void setSharedHeap(SkBitmapHeap* heap) {
+ SkASSERT(!shouldFlattenBitmaps(fFlags) || NULL == heap);
+ SkRefCnt_SafeAssign(fSharedHeap, heap);
+ this->updateReader();
+ }
+
+ /**
+ * Access the shared heap. Only used in the case when bitmaps are not
+ * flattened.
+ */
+ SkBitmapHeap* getSharedHeap() const {
+ SkASSERT(!shouldFlattenBitmaps(fFlags));
+ return fSharedHeap;
+ }
+
+ void addTypeface() {
+ size_t size = fReader->read32();
+ const void* data = fReader->skip(SkAlign4(size));
+ SkMemoryStream stream(data, size, false);
+ *fTypefaces.append() = SkTypeface::Deserialize(&stream);
+ }
+
+ void setTypeface(SkPaint* paint, unsigned id) {
+ paint->setTypeface(id ? fTypefaces[id - 1] : NULL);
+ }
+
+private:
+ void updateReader() {
+ if (NULL == fReader) {
+ return;
+ }
+ bool crossProcess = SkToBool(fFlags & SkGPipeWriter::kCrossProcess_Flag);
+ fReader->setFlags(SkSetClearMask(fReader->getFlags(), crossProcess,
+ SkFlattenableReadBuffer::kCrossProcess_Flag));
+ if (crossProcess) {
+ fReader->setFactoryArray(&fFactoryArray);
+ } else {
+ fReader->setFactoryArray(NULL);
+ }
+
+ if (shouldFlattenBitmaps(fFlags)) {
+ fReader->setBitmapStorage(this);
+ } else {
+ fReader->setBitmapStorage(fSharedHeap);
+ }
+ }
+ SkOrderedReadBuffer* fReader;
+ SkPaint fPaint;
+ SkTDArray<SkFlattenable*> fFlatArray;
+ SkTDArray<SkTypeface*> fTypefaces;
+ SkTDArray<SkFlattenable::Factory> fFactoryArray;
+ SkTDArray<SkBitmap*> fBitmaps;
+ bool fSilent;
+ // Only used when sharing bitmaps with the writer.
+ SkBitmapHeap* fSharedHeap;
+ unsigned fFlags;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> const T* skip(SkReader32* reader, int count = 1) {
+ SkASSERT(count >= 0);
+ size_t size = sizeof(T) * count;
+ SkASSERT(SkAlign4(size) == size);
+ return reinterpret_cast<const T*>(reader->skip(size));
+}
+
+template <typename T> const T* skipAlign(SkReader32* reader, int count = 1) {
+ SkASSERT(count >= 0);
+ size_t size = SkAlign4(sizeof(T) * count);
+ return reinterpret_cast<const T*>(reader->skip(size));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void clipPath_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkPath path;
+ reader->readPath(&path);
+ bool doAA = SkToBool(DrawOp_unpackFlags(op32) & kClip_HasAntiAlias_DrawOpFlag);
+ canvas->clipPath(path, (SkRegion::Op)DrawOp_unpackData(op32), doAA);
+}
+
+static void clipRegion_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkRegion rgn;
+ reader->readRegion(&rgn);
+ canvas->clipRegion(rgn, (SkRegion::Op)DrawOp_unpackData(op32));
+}
+
+static void clipRect_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const SkRect* rect = skip<SkRect>(reader);
+ bool doAA = SkToBool(DrawOp_unpackFlags(op32) & kClip_HasAntiAlias_DrawOpFlag);
+ 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,
+ SkGPipeState* state) {
+ SkMatrix matrix;
+ reader->readMatrix(&matrix);
+ canvas->setMatrix(matrix);
+}
+
+static void concat_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkMatrix matrix;
+ reader->readMatrix(&matrix);
+ canvas->concat(matrix);
+}
+
+static void scale_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const SkScalar* param = skip<SkScalar>(reader, 2);
+ canvas->scale(param[0], param[1]);
+}
+
+static void skew_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const SkScalar* param = skip<SkScalar>(reader, 2);
+ canvas->skew(param[0], param[1]);
+}
+
+static void rotate_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ canvas->rotate(reader->readScalar());
+}
+
+static void translate_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const SkScalar* param = skip<SkScalar>(reader, 2);
+ canvas->translate(param[0], param[1]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void save_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ canvas->save((SkCanvas::SaveFlags)DrawOp_unpackData(op32));
+}
+
+static void saveLayer_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ unsigned flags = DrawOp_unpackFlags(op32);
+ SkCanvas::SaveFlags saveFlags = (SkCanvas::SaveFlags)DrawOp_unpackData(op32);
+
+ const SkRect* bounds = NULL;
+ if (flags & kSaveLayer_HasBounds_DrawOpFlag) {
+ bounds = skip<SkRect>(reader);
+ }
+ const SkPaint* paint = NULL;
+ if (flags & kSaveLayer_HasPaint_DrawOpFlag) {
+ paint = &state->paint();
+ }
+ canvas->saveLayer(bounds, paint, saveFlags);
+}
+
+static void restore_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ canvas->restore();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void drawClear_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkColor color = 0;
+ if (DrawOp_unpackFlags(op32) & kClear_HasColor_DrawOpFlag) {
+ color = reader->readU32();
+ }
+ canvas->clear(color);
+}
+
+static void drawPaint_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ if (state->shouldDraw()) {
+ canvas->drawPaint(state->paint());
+ }
+}
+
+static void drawPoints_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ SkCanvas::PointMode mode = (SkCanvas::PointMode)DrawOp_unpackFlags(op32);
+ size_t count = reader->readU32();
+ const SkPoint* pts = skip<SkPoint>(reader, count);
+ if (state->shouldDraw()) {
+ canvas->drawPoints(mode, count, pts, state->paint());
+ }
+}
+
+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);
+ if (state->shouldDraw()) {
+ canvas->drawRect(*rect, state->paint());
+ }
+}
+
+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;
+ reader->readPath(&path);
+ if (state->shouldDraw()) {
+ canvas->drawPath(path, state->paint());
+ }
+}
+
+static void drawVertices_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ unsigned flags = DrawOp_unpackFlags(op32);
+
+ SkCanvas::VertexMode mode = (SkCanvas::VertexMode)reader->readU32();
+ int vertexCount = reader->readU32();
+ const SkPoint* verts = skip<SkPoint>(reader, vertexCount);
+
+ const SkPoint* texs = NULL;
+ if (flags & kDrawVertices_HasTexs_DrawOpFlag) {
+ texs = skip<SkPoint>(reader, vertexCount);
+ }
+
+ const SkColor* colors = NULL;
+ if (flags & kDrawVertices_HasColors_DrawOpFlag) {
+ colors = skip<SkColor>(reader, vertexCount);
+ }
+
+ // TODO: flatten/unflatten xfermodes
+ SkXfermode* xfer = NULL;
+
+ int indexCount = 0;
+ const uint16_t* indices = NULL;
+ if (flags & kDrawVertices_HasIndices_DrawOpFlag) {
+ indexCount = reader->readU32();
+ indices = skipAlign<uint16_t>(reader, indexCount);
+ }
+ if (state->shouldDraw()) {
+ canvas->drawVertices(mode, vertexCount, verts, texs, colors, xfer,
+ indices, indexCount, state->paint());
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void drawText_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ size_t len = reader->readU32();
+ const void* text = reader->skip(SkAlign4(len));
+ const SkScalar* xy = skip<SkScalar>(reader, 2);
+ if (state->shouldDraw()) {
+ canvas->drawText(text, len, xy[0], xy[1], state->paint());
+ }
+}
+
+static void drawPosText_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ size_t len = reader->readU32();
+ const void* text = reader->skip(SkAlign4(len));
+ size_t posCount = reader->readU32(); // compute by our writer
+ const SkPoint* pos = skip<SkPoint>(reader, posCount);
+ if (state->shouldDraw()) {
+ canvas->drawPosText(text, len, pos, state->paint());
+ }
+}
+
+static void drawPosTextH_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ size_t len = reader->readU32();
+ const void* text = reader->skip(SkAlign4(len));
+ size_t posCount = reader->readU32(); // compute by our writer
+ const SkScalar* xpos = skip<SkScalar>(reader, posCount);
+ SkScalar constY = reader->readScalar();
+ if (state->shouldDraw()) {
+ canvas->drawPosTextH(text, len, xpos, constY, state->paint());
+ }
+}
+
+static void drawTextOnPath_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ size_t len = reader->readU32();
+ const void* text = reader->skip(SkAlign4(len));
+
+ SkPath path;
+ reader->readPath(&path);
+
+ SkMatrix matrixStorage;
+ const SkMatrix* matrix = NULL;
+ if (DrawOp_unpackFlags(op32) & kDrawTextOnPath_HasMatrix_DrawOpFlag) {
+ reader->readMatrix(&matrixStorage);
+ matrix = &matrixStorage;
+ }
+ if (state->shouldDraw()) {
+ canvas->drawTextOnPath(text, len, path, matrix, state->paint());
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class BitmapHolder : SkNoncopyable {
+public:
+ BitmapHolder(SkReader32* reader, uint32_t op32, SkGPipeState* state);
+ ~BitmapHolder() {
+ if (fHeapEntry != NULL) {
+ fHeapEntry->releaseRef();
+ }
+ }
+ const SkBitmap* getBitmap() {
+ return fBitmap;
+ }
+private:
+ SkBitmapHeapEntry* fHeapEntry;
+ const SkBitmap* fBitmap;
+ SkBitmap fBitmapStorage;
+};
+
+BitmapHolder::BitmapHolder(SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ const unsigned flags = state->getFlags();
+ const unsigned index = DrawOp_unpackData(op32);
+ if (shouldFlattenBitmaps(flags)) {
+ fHeapEntry = NULL;
+ fBitmap = state->getBitmap(index);
+ } else {
+ SkBitmapHeapEntry* entry = state->getSharedHeap()->getEntry(index);
+ if (SkToBool(flags & SkGPipeWriter::kSimultaneousReaders_Flag)) {
+ // Make a shallow copy for thread safety. Each thread will point to the same SkPixelRef,
+ // which is thread safe.
+ fBitmapStorage = *entry->getBitmap();
+ fBitmap = &fBitmapStorage;
+ // Release the ref on the bitmap now, since we made our own copy.
+ entry->releaseRef();
+ fHeapEntry = NULL;
+ } else {
+ SkASSERT(!shouldFlattenBitmaps(flags));
+ SkASSERT(!SkToBool(flags & SkGPipeWriter::kSimultaneousReaders_Flag));
+ fHeapEntry = entry;
+ fBitmap = fHeapEntry->getBitmap();
+ }
+ }
+}
+
+static void drawBitmap_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ BitmapHolder holder(reader, op32, state);
+ bool hasPaint = SkToBool(DrawOp_unpackFlags(op32) & kDrawBitmap_HasPaint_DrawOpFlag);
+ SkScalar left = reader->readScalar();
+ SkScalar top = reader->readScalar();
+ const SkBitmap* bitmap = holder.getBitmap();
+ if (state->shouldDraw()) {
+ canvas->drawBitmap(*bitmap, left, top, hasPaint ? &state->paint() : NULL);
+ }
+}
+
+static void drawBitmapMatrix_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ BitmapHolder holder(reader, op32, state);
+ bool hasPaint = SkToBool(DrawOp_unpackFlags(op32) & kDrawBitmap_HasPaint_DrawOpFlag);
+ SkMatrix matrix;
+ reader->readMatrix(&matrix);
+ const SkBitmap* bitmap = holder.getBitmap();
+ if (state->shouldDraw()) {
+ canvas->drawBitmapMatrix(*bitmap, matrix,
+ hasPaint ? &state->paint() : NULL);
+ }
+}
+
+static void drawBitmapNine_rp(SkCanvas* canvas, SkReader32* reader,
+ uint32_t op32, SkGPipeState* state) {
+ BitmapHolder holder(reader, op32, state);
+ bool hasPaint = SkToBool(DrawOp_unpackFlags(op32) & kDrawBitmap_HasPaint_DrawOpFlag);
+ const SkIRect* center = skip<SkIRect>(reader);
+ const SkRect* dst = skip<SkRect>(reader);
+ const SkBitmap* bitmap = holder.getBitmap();
+ if (state->shouldDraw()) {
+ canvas->drawBitmapNine(*bitmap, *center, *dst,
+ hasPaint ? &state->paint() : NULL);
+ }
+}
+
+static void drawBitmapRect_rp(SkCanvas* canvas, SkReader32* reader,
+ uint32_t op32, SkGPipeState* state) {
+ BitmapHolder holder(reader, op32, state);
+ unsigned flags = DrawOp_unpackFlags(op32);
+ bool hasPaint = SkToBool(flags & kDrawBitmap_HasPaint_DrawOpFlag);
+ bool hasSrc = SkToBool(flags & kDrawBitmap_HasSrcRect_DrawOpFlag);
+ const SkRect* src;
+ if (hasSrc) {
+ src = skip<SkRect>(reader);
+ } else {
+ src = NULL;
+ }
+ const SkRect* dst = skip<SkRect>(reader);
+ const SkBitmap* bitmap = holder.getBitmap();
+ if (state->shouldDraw()) {
+ canvas->drawBitmapRectToRect(*bitmap, src, *dst, hasPaint ? &state->paint() : NULL);
+ }
+}
+
+static void drawSprite_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ BitmapHolder holder(reader, op32, state);
+ bool hasPaint = SkToBool(DrawOp_unpackFlags(op32) & kDrawBitmap_HasPaint_DrawOpFlag);
+ const SkIPoint* point = skip<SkIPoint>(reader);
+ const SkBitmap* bitmap = holder.getBitmap();
+ if (state->shouldDraw()) {
+ canvas->drawSprite(*bitmap, point->fX, point->fY, hasPaint ? &state->paint() : NULL);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void drawData_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ // since we don't have a paint, we can use data for our (small) sizes
+ size_t size = DrawOp_unpackData(op32);
+ if (0 == size) {
+ size = reader->readU32();
+ }
+ const void* data = reader->skip(SkAlign4(size));
+ if (state->shouldDraw()) {
+ canvas->drawData(data, size);
+ }
+}
+
+static void drawPicture_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ UNIMPLEMENTED
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void paintOp_rp(SkCanvas*, SkReader32* reader, uint32_t op32,
+ SkGPipeState* state) {
+ size_t offset = reader->offset();
+ size_t stop = offset + PaintOp_unpackData(op32);
+ SkPaint* p = state->editPaint();
+
+ do {
+ uint32_t p32 = reader->readU32();
+ unsigned op = PaintOp_unpackOp(p32);
+ unsigned data = PaintOp_unpackData(p32);
+
+// SkDebugf(" read %08X op=%d flags=%d data=%d\n", p32, op, done, data);
+
+ switch (op) {
+ case kReset_PaintOp: p->reset(); break;
+ case kFlags_PaintOp: p->setFlags(data); break;
+ case kColor_PaintOp: p->setColor(reader->readU32()); break;
+ case kStyle_PaintOp: p->setStyle((SkPaint::Style)data); break;
+ case kJoin_PaintOp: p->setStrokeJoin((SkPaint::Join)data); break;
+ case kCap_PaintOp: p->setStrokeCap((SkPaint::Cap)data); break;
+ case kWidth_PaintOp: p->setStrokeWidth(reader->readScalar()); break;
+ case kMiter_PaintOp: p->setStrokeMiter(reader->readScalar()); break;
+ case kEncoding_PaintOp:
+ p->setTextEncoding((SkPaint::TextEncoding)data);
+ break;
+ case kHinting_PaintOp: p->setHinting((SkPaint::Hinting)data); break;
+ case kAlign_PaintOp: p->setTextAlign((SkPaint::Align)data); break;
+ case kTextSize_PaintOp: p->setTextSize(reader->readScalar()); break;
+ case kTextScaleX_PaintOp: p->setTextScaleX(reader->readScalar()); break;
+ case kTextSkewX_PaintOp: p->setTextSkewX(reader->readScalar()); break;
+
+ case kFlatIndex_PaintOp: {
+ PaintFlats pf = (PaintFlats)PaintOp_unpackFlags(p32);
+ unsigned index = data;
+ set_paintflat(p, state->getFlat(index), pf);
+ break;
+ }
+
+ case kTypeface_PaintOp:
+ SkASSERT(SkToBool(state->getFlags() &
+ SkGPipeWriter::kCrossProcess_Flag));
+ state->setTypeface(p, data); break;
+ default: SkDEBUGFAIL("bad paintop"); return;
+ }
+ SkASSERT(reader->offset() <= stop);
+ } while (reader->offset() < stop);
+}
+
+static void typeface_rp(SkCanvas*, SkReader32* reader, uint32_t,
+ SkGPipeState* state) {
+ SkASSERT(!SkToBool(state->getFlags() & SkGPipeWriter::kCrossProcess_Flag));
+ SkPaint* p = state->editPaint();
+ p->setTypeface(static_cast<SkTypeface*>(reader->readPtr()));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void def_Typeface_rp(SkCanvas*, SkReader32*, uint32_t, SkGPipeState* state) {
+ state->addTypeface();
+}
+
+static void def_PaintFlat_rp(SkCanvas*, SkReader32*, uint32_t op32,
+ SkGPipeState* state) {
+ PaintFlats pf = (PaintFlats)DrawOp_unpackFlags(op32);
+ unsigned index = DrawOp_unpackData(op32);
+ state->defFlattenable(pf, index);
+}
+
+static void def_Bitmap_rp(SkCanvas*, SkReader32*, uint32_t op32,
+ SkGPipeState* state) {
+ unsigned index = DrawOp_unpackData(op32);
+ state->addBitmap(index);
+}
+
+static void def_Factory_rp(SkCanvas*, SkReader32* reader, uint32_t,
+ SkGPipeState* state) {
+ state->defFactory(reader->readString());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void skip_rp(SkCanvas*, SkReader32* reader, uint32_t op32, SkGPipeState*) {
+ size_t bytes = DrawOp_unpackData(op32);
+ (void)reader->skip(bytes);
+}
+
+static void reportFlags_rp(SkCanvas*, SkReader32*, uint32_t op32,
+ SkGPipeState* state) {
+ unsigned flags = DrawOp_unpackFlags(op32);
+ state->setFlags(flags);
+}
+
+static void shareBitmapHeap_rp(SkCanvas*, SkReader32* reader, uint32_t,
+ SkGPipeState* state) {
+ state->setSharedHeap(static_cast<SkBitmapHeap*>(reader->readPtr()));
+}
+
+static void done_rp(SkCanvas*, SkReader32*, uint32_t, SkGPipeState*) {}
+
+typedef void (*ReadProc)(SkCanvas*, SkReader32*, uint32_t op32, SkGPipeState*);
+
+static const ReadProc gReadTable[] = {
+ skip_rp,
+ clipPath_rp,
+ clipRegion_rp,
+ clipRect_rp,
+ clipRRect_rp,
+ concat_rp,
+ drawBitmap_rp,
+ drawBitmapMatrix_rp,
+ drawBitmapNine_rp,
+ drawBitmapRect_rp,
+ drawClear_rp,
+ drawData_rp,
+ drawOval_rp,
+ drawPaint_rp,
+ drawPath_rp,
+ drawPicture_rp,
+ drawPoints_rp,
+ drawPosText_rp,
+ drawPosTextH_rp,
+ drawRect_rp,
+ drawRRect_rp,
+ drawSprite_rp,
+ drawText_rp,
+ drawTextOnPath_rp,
+ drawVertices_rp,
+ restore_rp,
+ rotate_rp,
+ save_rp,
+ saveLayer_rp,
+ scale_rp,
+ setMatrix_rp,
+ skew_rp,
+ translate_rp,
+
+ paintOp_rp,
+ typeface_rp,
+ def_Typeface_rp,
+ def_PaintFlat_rp,
+ def_Bitmap_rp,
+ def_Factory_rp,
+
+ reportFlags_rp,
+ shareBitmapHeap_rp,
+ done_rp
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGPipeState::SkGPipeState()
+ : fReader(0)
+ , fSilent(false)
+ , fSharedHeap(NULL)
+ , fFlags(0) {
+
+}
+
+SkGPipeState::~SkGPipeState() {
+ fTypefaces.safeUnrefAll();
+ fFlatArray.safeUnrefAll();
+ fBitmaps.deleteAll();
+ SkSafeUnref(fSharedHeap);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGPipe.h"
+
+SkGPipeReader::SkGPipeReader() {
+ fCanvas = NULL;
+ fState = NULL;
+ fProc = NULL;
+}
+
+SkGPipeReader::SkGPipeReader(SkCanvas* target) {
+ fCanvas = NULL;
+ this->setCanvas(target);
+ fState = NULL;
+ fProc = NULL;
+}
+
+void SkGPipeReader::setCanvas(SkCanvas *target) {
+ SkRefCnt_SafeAssign(fCanvas, target);
+}
+
+SkGPipeReader::~SkGPipeReader() {
+ SkSafeUnref(fCanvas);
+ delete fState;
+}
+
+SkGPipeReader::Status SkGPipeReader::playback(const void* data, size_t length,
+ uint32_t playbackFlags, size_t* bytesRead) {
+ if (NULL == fCanvas) {
+ return kError_Status;
+ }
+
+ if (NULL == fState) {
+ fState = new SkGPipeState;
+ }
+
+ fState->setSilent(playbackFlags & kSilent_PlaybackFlag);
+
+ SkASSERT(SK_ARRAY_COUNT(gReadTable) == (kDone_DrawOp + 1));
+
+ const ReadProc* table = gReadTable;
+ SkOrderedReadBuffer reader(data, length);
+ reader.setBitmapDecoder(fProc);
+ SkCanvas* canvas = fCanvas;
+ Status status = kEOF_Status;
+
+ fState->setReader(&reader);
+ while (!reader.eof()) {
+ uint32_t op32 = reader.readUInt();
+ unsigned op = DrawOp_unpackOp(op32);
+ // SkDEBUGCODE(DrawOps drawOp = (DrawOps)op;)
+
+ if (op >= SK_ARRAY_COUNT(gReadTable)) {
+ SkDebugf("---- bad op during GPipeState::playback\n");
+ status = kError_Status;
+ break;
+ }
+ if (kDone_DrawOp == op) {
+ status = kDone_Status;
+ break;
+ }
+ table[op](canvas, reader.getReader32(), op32, fState);
+ if ((playbackFlags & kReadAtom_PlaybackFlag) &&
+ (table[op] != paintOp_rp &&
+ table[op] != def_Typeface_rp &&
+ table[op] != def_PaintFlat_rp &&
+ table[op] != def_Bitmap_rp
+ )) {
+ status = kReadAtom_Status;
+ break;
+ }
+ }
+
+ if (bytesRead) {
+ *bytesRead = reader.offset();
+ }
+ return status;
+}
diff --git a/pipe/SkGPipeWrite.cpp b/pipe/SkGPipeWrite.cpp
new file mode 100644
index 00000000..3b22a5e1
--- /dev/null
+++ b/pipe/SkGPipeWrite.cpp
@@ -0,0 +1,1195 @@
+
+/*
+ * 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 "SkAnnotation.h"
+#include "SkBitmapHeap.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkData.h"
+#include "SkDrawLooper.h"
+#include "SkDevice.h"
+#include "SkGPipe.h"
+#include "SkGPipePriv.h"
+#include "SkImageFilter.h"
+#include "SkMaskFilter.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkPaint.h"
+#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);
+}
+
+static SkFlattenable* get_paintflat(const SkPaint& paint, unsigned paintFlat) {
+ SkASSERT(paintFlat < kCount_PaintFlats);
+ switch (paintFlat) {
+ case kColorFilter_PaintFlat: return paint.getColorFilter();
+ case kDrawLooper_PaintFlat: return paint.getLooper();
+ case kMaskFilter_PaintFlat: return paint.getMaskFilter();
+ case kPathEffect_PaintFlat: return paint.getPathEffect();
+ case kRasterizer_PaintFlat: return paint.getRasterizer();
+ case kShader_PaintFlat: return paint.getShader();
+ case kImageFilter_PaintFlat: return paint.getImageFilter();
+ case kXfermode_PaintFlat: return paint.getXfermode();
+ case kAnnotation_PaintFlat: return paint.getAnnotation();
+ }
+ SkDEBUGFAIL("never gets here");
+ return NULL;
+}
+
+static size_t writeTypeface(SkWriter32* writer, SkTypeface* typeface) {
+ SkASSERT(typeface);
+ SkDynamicMemoryWStream stream;
+ typeface->serialize(&stream);
+ size_t size = stream.getOffset();
+ if (writer) {
+ writer->write32(size);
+ SkAutoDataUnref data(stream.copyToData());
+ writer->writePad(data->data(), size);
+ }
+ return 4 + SkAlign4(size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class FlattenableHeap : public SkFlatController {
+public:
+ FlattenableHeap(int numFlatsToKeep, SkNamedFactorySet* fset, bool isCrossProcess)
+ : fNumFlatsToKeep(numFlatsToKeep) {
+ SkASSERT((isCrossProcess && fset != NULL) || (!isCrossProcess && NULL == fset));
+ if (isCrossProcess) {
+ this->setNamedFactorySet(fset);
+ this->setWriteBufferFlags(SkFlattenableWriteBuffer::kCrossProcess_Flag);
+ }
+ }
+
+ ~FlattenableHeap() {
+ fPointers.freeAll();
+ }
+
+ virtual void* allocThrow(size_t bytes) SK_OVERRIDE;
+
+ virtual void unalloc(void* ptr) SK_OVERRIDE;
+
+ void setBitmapStorage(SkBitmapHeap* heap) {
+ this->setBitmapHeap(heap);
+ }
+
+ const SkFlatData* flatToReplace() const;
+
+ // Mark an SkFlatData as one that should not be returned by flatToReplace.
+ // Takes the result of SkFlatData::index() as its parameter.
+ void markFlatForKeeping(int index) {
+ *fFlatsThatMustBeKept.append() = index;
+ }
+
+ void markAllFlatsSafeToDelete() {
+ fFlatsThatMustBeKept.reset();
+ }
+
+private:
+ // Keep track of the indices (i.e. the result of SkFlatData::index()) of
+ // flats that must be kept, since they are on the current paint.
+ SkTDArray<int> fFlatsThatMustBeKept;
+ SkTDArray<void*> fPointers;
+ const int fNumFlatsToKeep;
+};
+
+void FlattenableHeap::unalloc(void* ptr) {
+ int indexToRemove = fPointers.rfind(ptr);
+ if (indexToRemove >= 0) {
+ sk_free(ptr);
+ fPointers.remove(indexToRemove);
+ }
+}
+
+void* FlattenableHeap::allocThrow(size_t bytes) {
+ void* ptr = sk_malloc_throw(bytes);
+ *fPointers.append() = ptr;
+ return ptr;
+}
+
+const SkFlatData* FlattenableHeap::flatToReplace() const {
+ // First, determine whether we should replace one.
+ if (fPointers.count() > fNumFlatsToKeep) {
+ // Look through the flattenable heap.
+ // TODO: Return the LRU flat.
+ for (int i = 0; i < fPointers.count(); i++) {
+ SkFlatData* potential = (SkFlatData*)fPointers[i];
+ // Make sure that it is not one that must be kept.
+ bool mustKeep = false;
+ for (int j = 0; j < fFlatsThatMustBeKept.count(); j++) {
+ if (potential->index() == fFlatsThatMustBeKept[j]) {
+ mustKeep = true;
+ break;
+ }
+ }
+ if (!mustKeep) {
+ return potential;
+ }
+ }
+ }
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class FlatDictionary : public SkFlatDictionary<SkFlattenable> {
+public:
+ FlatDictionary(FlattenableHeap* heap)
+ : SkFlatDictionary<SkFlattenable>(heap) {
+ fFlattenProc = &flattenFlattenableProc;
+ // No need to define fUnflattenProc since the writer will never
+ // unflatten the data.
+ }
+ static void flattenFlattenableProc(SkOrderedWriteBuffer& buffer,
+ const void* obj) {
+ buffer.writeFlattenable((SkFlattenable*)obj);
+ }
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkGPipeCanvas : public SkCanvas {
+public:
+ SkGPipeCanvas(SkGPipeController*, SkWriter32*, uint32_t flags,
+ uint32_t width, uint32_t height);
+ virtual ~SkGPipeCanvas();
+
+ void finish() {
+ if (!fDone) {
+ if (this->needOpBytes()) {
+ this->writeOp(kDone_DrawOp);
+ this->doNotify();
+ if (shouldFlattenBitmaps(fFlags)) {
+ // In this case, a BitmapShuttle is reffed by the SkBitmapHeap
+ // and refs this canvas. Unref the SkBitmapHeap to end the
+ // circular reference. When shouldFlattenBitmaps is false,
+ // there is no circular reference, so the SkBitmapHeap can be
+ // safely unreffed in the destructor.
+ fBitmapHeap->unref();
+ // This eliminates a similar circular reference (Canvas owns
+ // the FlattenableHeap which holds a ref to the SkBitmapHeap).
+ fFlattenableHeap.setBitmapStorage(NULL);
+ fBitmapHeap = NULL;
+ }
+ }
+ fDone = true;
+ }
+ }
+
+ void flushRecording(bool detachCurrentBlock);
+ size_t freeMemoryIfPossible(size_t bytesToFree);
+
+ size_t storageAllocatedForRecording() {
+ return (NULL == fBitmapHeap) ? 0 : fBitmapHeap->bytesAllocated();
+ }
+
+ // overrides from SkCanvas
+ virtual int save(SaveFlags) SK_OVERRIDE;
+ virtual int saveLayer(const SkRect* bounds, const SkPaint*,
+ SaveFlags) SK_OVERRIDE;
+ virtual void restore() SK_OVERRIDE;
+ virtual bool isDrawingToLayer() const SK_OVERRIDE;
+ virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE;
+ virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE;
+ virtual bool rotate(SkScalar degrees) SK_OVERRIDE;
+ 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&, 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;
+ 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 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;
+ virtual void drawBitmapRectToRect(const SkBitmap&, const SkRect* src,
+ const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+ virtual void drawBitmapMatrix(const SkBitmap&, const SkMatrix&,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint = NULL) SK_OVERRIDE;
+ virtual void drawSprite(const SkBitmap&, int left, int top,
+ const SkPaint*) SK_OVERRIDE;
+ virtual void drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint&) SK_OVERRIDE;
+ virtual void drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint&) SK_OVERRIDE;
+ virtual void drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint&) SK_OVERRIDE;
+ virtual void drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint&) SK_OVERRIDE;
+ virtual void drawPicture(SkPicture& picture) SK_OVERRIDE;
+ virtual void drawVertices(VertexMode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode*,
+ const uint16_t indices[], int indexCount,
+ const SkPaint&) SK_OVERRIDE;
+ virtual void drawData(const void*, size_t) SK_OVERRIDE;
+ virtual void beginCommentGroup(const char* description) SK_OVERRIDE;
+ virtual void addComment(const char* kywd, const char* value) SK_OVERRIDE;
+ virtual void endCommentGroup() SK_OVERRIDE;
+
+ /**
+ * Flatten an SkBitmap to send to the reader, where it will be referenced
+ * according to slot.
+ */
+ bool shuttleBitmap(const SkBitmap&, int32_t slot);
+private:
+ enum {
+ kNoSaveLayer = -1,
+ };
+ SkNamedFactorySet* fFactorySet;
+ int fFirstSaveLayerStackLevel;
+ SkBitmapHeap* fBitmapHeap;
+ SkGPipeController* fController;
+ SkWriter32& fWriter;
+ size_t fBlockSize; // amount allocated for writer
+ size_t fBytesNotified;
+ bool fDone;
+ const uint32_t fFlags;
+
+ SkRefCntSet fTypefaceSet;
+
+ uint32_t getTypefaceID(SkTypeface*);
+
+ inline void writeOp(DrawOps op, unsigned flags, unsigned data) {
+ fWriter.write32(DrawOp_packOpFlagData(op, flags, data));
+ }
+
+ inline void writeOp(DrawOps op) {
+ fWriter.write32(DrawOp_packOpFlagData(op, 0, 0));
+ }
+
+ bool needOpBytes(size_t size = 0);
+
+ inline void doNotify() {
+ if (!fDone) {
+ size_t bytes = fWriter.size() - fBytesNotified;
+ if (bytes > 0) {
+ fController->notifyWritten(bytes);
+ fBytesNotified += bytes;
+ }
+ }
+ }
+
+ // Should be called after any calls to an SkFlatDictionary::findAndReplace
+ // if a new SkFlatData was added when in cross process mode
+ void flattenFactoryNames();
+
+ FlattenableHeap fFlattenableHeap;
+ FlatDictionary fFlatDictionary;
+ int fCurrFlatIndex[kCount_PaintFlats];
+ int flattenToIndex(SkFlattenable* obj, PaintFlats);
+
+ // Common code used by drawBitmap*. Behaves differently depending on the
+ // type of SkBitmapHeap being used, which is determined by the flags used.
+ bool commonDrawBitmap(const SkBitmap& bm, DrawOps op, unsigned flags,
+ size_t opBytesNeeded, const SkPaint* paint);
+
+ SkPaint fPaint;
+ void writePaint(const SkPaint&);
+
+ class AutoPipeNotify {
+ public:
+ AutoPipeNotify(SkGPipeCanvas* canvas) : fCanvas(canvas) {}
+ ~AutoPipeNotify() { fCanvas->doNotify(); }
+ private:
+ SkGPipeCanvas* fCanvas;
+ };
+ friend class AutoPipeNotify;
+
+ typedef SkCanvas INHERITED;
+};
+
+void SkGPipeCanvas::flattenFactoryNames() {
+ const char* name;
+ while ((name = fFactorySet->getNextAddedFactoryName()) != NULL) {
+ size_t len = strlen(name);
+ if (this->needOpBytes(len)) {
+ this->writeOp(kDef_Factory_DrawOp);
+ fWriter.writeString(name, len);
+ }
+ }
+}
+
+bool SkGPipeCanvas::shuttleBitmap(const SkBitmap& bm, int32_t slot) {
+ SkASSERT(shouldFlattenBitmaps(fFlags));
+ SkOrderedWriteBuffer buffer(1024);
+ buffer.setNamedFactoryRecorder(fFactorySet);
+ buffer.writeBitmap(bm);
+ this->flattenFactoryNames();
+ uint32_t size = buffer.size();
+ if (this->needOpBytes(size)) {
+ this->writeOp(kDef_Bitmap_DrawOp, 0, slot);
+ void* dst = static_cast<void*>(fWriter.reserve(size));
+ buffer.writeToMemory(dst);
+ return true;
+ }
+ return false;
+}
+
+// return 0 for NULL (or unflattenable obj), or index-base-1
+// return ~(index-base-1) if an old flattenable was replaced
+int SkGPipeCanvas::flattenToIndex(SkFlattenable* obj, PaintFlats paintflat) {
+ SkASSERT(!fDone && fBitmapHeap != NULL);
+ if (NULL == obj) {
+ return 0;
+ }
+
+ fBitmapHeap->deferAddingOwners();
+ bool added, replaced;
+ const SkFlatData* flat = fFlatDictionary.findAndReplace(*obj, fFlattenableHeap.flatToReplace(),
+ &added, &replaced);
+ fBitmapHeap->endAddingOwnersDeferral(added);
+ int index = flat->index();
+ if (added) {
+ if (isCrossProcess(fFlags)) {
+ this->flattenFactoryNames();
+ }
+ size_t flatSize = flat->flatSize();
+ if (this->needOpBytes(flatSize)) {
+ this->writeOp(kDef_Flattenable_DrawOp, paintflat, index);
+ fWriter.write(flat->data(), flatSize);
+ }
+ }
+ if (replaced) {
+ index = ~index;
+ }
+ return index;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * If SkBitmaps are to be flattened to send to the reader, this class is
+ * provided to the SkBitmapHeap to tell the SkGPipeCanvas to do so.
+ */
+class BitmapShuttle : public SkBitmapHeap::ExternalStorage {
+public:
+ BitmapShuttle(SkGPipeCanvas*);
+
+ ~BitmapShuttle();
+
+ virtual bool insert(const SkBitmap& bitmap, int32_t slot) SK_OVERRIDE;
+
+private:
+ SkGPipeCanvas* fCanvas;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define MIN_BLOCK_SIZE (16 * 1024)
+#define BITMAPS_TO_KEEP 5
+#define FLATTENABLES_TO_KEEP 10
+
+SkGPipeCanvas::SkGPipeCanvas(SkGPipeController* controller,
+ SkWriter32* writer, uint32_t flags,
+ uint32_t width, uint32_t height)
+: fFactorySet(isCrossProcess(flags) ? SkNEW(SkNamedFactorySet) : NULL)
+, fWriter(*writer)
+, fFlags(flags)
+, fFlattenableHeap(FLATTENABLES_TO_KEEP, fFactorySet, isCrossProcess(flags))
+, fFlatDictionary(&fFlattenableHeap) {
+ fController = controller;
+ fDone = false;
+ fBlockSize = 0; // need first block from controller
+ fBytesNotified = 0;
+ fFirstSaveLayerStackLevel = kNoSaveLayer;
+ sk_bzero(fCurrFlatIndex, sizeof(fCurrFlatIndex));
+
+ // we need a device to limit our clip
+ // We don't allocate pixels for the bitmap
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ SkDevice* device = SkNEW_ARGS(SkDevice, (bitmap));
+ this->setDevice(device)->unref();
+
+ // Tell the reader the appropriate flags to use.
+ if (this->needOpBytes()) {
+ this->writeOp(kReportFlags_DrawOp, fFlags, 0);
+ }
+
+ if (shouldFlattenBitmaps(flags)) {
+ BitmapShuttle* shuttle = SkNEW_ARGS(BitmapShuttle, (this));
+ fBitmapHeap = SkNEW_ARGS(SkBitmapHeap, (shuttle, BITMAPS_TO_KEEP));
+ shuttle->unref();
+ } else {
+ fBitmapHeap = SkNEW_ARGS(SkBitmapHeap,
+ (BITMAPS_TO_KEEP, controller->numberOfReaders()));
+ if (this->needOpBytes(sizeof(void*))) {
+ this->writeOp(kShareBitmapHeap_DrawOp);
+ fWriter.writePtr(static_cast<void*>(fBitmapHeap));
+ }
+ }
+ fFlattenableHeap.setBitmapStorage(fBitmapHeap);
+ this->doNotify();
+}
+
+SkGPipeCanvas::~SkGPipeCanvas() {
+ this->finish();
+ SkSafeUnref(fFactorySet);
+ SkSafeUnref(fBitmapHeap);
+}
+
+bool SkGPipeCanvas::needOpBytes(size_t needed) {
+ if (fDone) {
+ return false;
+ }
+
+ needed += 4; // size of DrawOp atom
+ if (fWriter.size() + needed > fBlockSize) {
+ // Before we wipe out any data that has already been written, read it
+ // out.
+ this->doNotify();
+ size_t blockSize = SkMax32(MIN_BLOCK_SIZE, needed);
+ void* block = fController->requestBlock(blockSize, &fBlockSize);
+ if (NULL == block) {
+ fDone = true;
+ return false;
+ }
+ SkASSERT(SkIsAlign4(fBlockSize));
+ fWriter.reset(block, fBlockSize);
+ fBytesNotified = 0;
+ }
+ return true;
+}
+
+uint32_t SkGPipeCanvas::getTypefaceID(SkTypeface* face) {
+ uint32_t id = 0; // 0 means default/null typeface
+ if (face) {
+ id = fTypefaceSet.find(face);
+ if (0 == id) {
+ id = fTypefaceSet.add(face);
+ size_t size = writeTypeface(NULL, face);
+ if (this->needOpBytes(size)) {
+ this->writeOp(kDef_Typeface_DrawOp);
+ writeTypeface(&fWriter, face);
+ }
+ }
+ }
+ return id;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define NOTIFY_SETUP(canvas) \
+ AutoPipeNotify apn(canvas)
+
+int SkGPipeCanvas::save(SaveFlags flags) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes()) {
+ this->writeOp(kSave_DrawOp, 0, flags);
+ }
+ return this->INHERITED::save(flags);
+}
+
+int SkGPipeCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags saveFlags) {
+ NOTIFY_SETUP(this);
+ size_t size = 0;
+ unsigned opFlags = 0;
+
+ if (bounds) {
+ opFlags |= kSaveLayer_HasBounds_DrawOpFlag;
+ size += sizeof(SkRect);
+ }
+ if (paint) {
+ opFlags |= kSaveLayer_HasPaint_DrawOpFlag;
+ this->writePaint(*paint);
+ }
+
+ if (this->needOpBytes(size)) {
+ this->writeOp(kSaveLayer_DrawOp, opFlags, saveFlags);
+ if (bounds) {
+ fWriter.writeRect(*bounds);
+ }
+ }
+
+ if (kNoSaveLayer == fFirstSaveLayerStackLevel){
+ fFirstSaveLayerStackLevel = this->getSaveCount();
+ }
+ // we just pass on the save, so we don't create a layer
+ return this->INHERITED::save(saveFlags);
+}
+
+void SkGPipeCanvas::restore() {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes()) {
+ this->writeOp(kRestore_DrawOp);
+ }
+
+ this->INHERITED::restore();
+
+ if (this->getSaveCount() == fFirstSaveLayerStackLevel){
+ fFirstSaveLayerStackLevel = kNoSaveLayer;
+ }
+}
+
+bool SkGPipeCanvas::isDrawingToLayer() const {
+ return kNoSaveLayer != fFirstSaveLayerStackLevel;
+}
+
+bool SkGPipeCanvas::translate(SkScalar dx, SkScalar dy) {
+ if (dx || dy) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(2 * sizeof(SkScalar))) {
+ this->writeOp(kTranslate_DrawOp);
+ fWriter.writeScalar(dx);
+ fWriter.writeScalar(dy);
+ }
+ }
+ return this->INHERITED::translate(dx, dy);
+}
+
+bool SkGPipeCanvas::scale(SkScalar sx, SkScalar sy) {
+ if (sx || sy) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(2 * sizeof(SkScalar))) {
+ this->writeOp(kScale_DrawOp);
+ fWriter.writeScalar(sx);
+ fWriter.writeScalar(sy);
+ }
+ }
+ return this->INHERITED::scale(sx, sy);
+}
+
+bool SkGPipeCanvas::rotate(SkScalar degrees) {
+ if (degrees) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(sizeof(SkScalar))) {
+ this->writeOp(kRotate_DrawOp);
+ fWriter.writeScalar(degrees);
+ }
+ }
+ return this->INHERITED::rotate(degrees);
+}
+
+bool SkGPipeCanvas::skew(SkScalar sx, SkScalar sy) {
+ if (sx || sy) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(2 * sizeof(SkScalar))) {
+ this->writeOp(kSkew_DrawOp);
+ fWriter.writeScalar(sx);
+ fWriter.writeScalar(sy);
+ }
+ }
+ return this->INHERITED::skew(sx, sy);
+}
+
+bool SkGPipeCanvas::concat(const SkMatrix& matrix) {
+ if (!matrix.isIdentity()) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(matrix.writeToMemory(NULL))) {
+ this->writeOp(kConcat_DrawOp);
+ fWriter.writeMatrix(matrix);
+ }
+ }
+ return this->INHERITED::concat(matrix);
+}
+
+void SkGPipeCanvas::setMatrix(const SkMatrix& matrix) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(matrix.writeToMemory(NULL))) {
+ this->writeOp(kSetMatrix_DrawOp);
+ fWriter.writeMatrix(matrix);
+ }
+ this->INHERITED::setMatrix(matrix);
+}
+
+bool SkGPipeCanvas::clipRect(const SkRect& rect, SkRegion::Op rgnOp,
+ bool doAntiAlias) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(sizeof(SkRect))) {
+ unsigned flags = doAntiAlias & kClip_HasAntiAlias_DrawOpFlag;
+ this->writeOp(kClipRect_DrawOp, flags, rgnOp);
+ fWriter.writeRect(rect);
+ }
+ 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);
+ if (this->needOpBytes(path.writeToMemory(NULL))) {
+ unsigned flags = doAntiAlias & kClip_HasAntiAlias_DrawOpFlag;
+ this->writeOp(kClipPath_DrawOp, flags, rgnOp);
+ fWriter.writePath(path);
+ }
+ // we just pass on the bounds of the path
+ return this->INHERITED::clipRect(path.getBounds(), rgnOp, doAntiAlias);
+}
+
+bool SkGPipeCanvas::clipRegion(const SkRegion& region, SkRegion::Op rgnOp) {
+ NOTIFY_SETUP(this);
+ if (this->needOpBytes(region.writeToMemory(NULL))) {
+ this->writeOp(kClipRegion_DrawOp, 0, rgnOp);
+ fWriter.writeRegion(region);
+ }
+ return this->INHERITED::clipRegion(region, rgnOp);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkGPipeCanvas::clear(SkColor color) {
+ NOTIFY_SETUP(this);
+ unsigned flags = 0;
+ if (color) {
+ flags |= kClear_HasColor_DrawOpFlag;
+ }
+ if (this->needOpBytes(sizeof(SkColor))) {
+ this->writeOp(kDrawClear_DrawOp, flags, 0);
+ if (color) {
+ fWriter.write32(color);
+ }
+ }
+}
+
+void SkGPipeCanvas::drawPaint(const SkPaint& paint) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ if (this->needOpBytes()) {
+ this->writeOp(kDrawPaint_DrawOp);
+ }
+}
+
+void SkGPipeCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ if (count) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ if (this->needOpBytes(4 + count * sizeof(SkPoint))) {
+ this->writeOp(kDrawPoints_DrawOp, mode, 0);
+ fWriter.write32(count);
+ fWriter.write(pts, count * sizeof(SkPoint));
+ }
+ }
+}
+
+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);
+ if (this->needOpBytes(sizeof(SkRect))) {
+ this->writeOp(kDrawRect_DrawOp);
+ fWriter.writeRect(rect);
+ }
+}
+
+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);
+ if (this->needOpBytes(path.writeToMemory(NULL))) {
+ this->writeOp(kDrawPath_DrawOp);
+ fWriter.writePath(path);
+ }
+}
+
+bool SkGPipeCanvas::commonDrawBitmap(const SkBitmap& bm, DrawOps op,
+ unsigned flags,
+ size_t opBytesNeeded,
+ const SkPaint* paint) {
+ if (paint != NULL) {
+ flags |= kDrawBitmap_HasPaint_DrawOpFlag;
+ this->writePaint(*paint);
+ }
+ if (this->needOpBytes(opBytesNeeded)) {
+ SkASSERT(fBitmapHeap != NULL);
+ int32_t bitmapIndex = fBitmapHeap->insert(bm);
+ if (SkBitmapHeap::INVALID_SLOT == bitmapIndex) {
+ return false;
+ }
+ this->writeOp(op, flags, bitmapIndex);
+ return true;
+ }
+ return false;
+}
+
+void SkGPipeCanvas::drawBitmap(const SkBitmap& bm, SkScalar left, SkScalar top,
+ const SkPaint* paint) {
+ NOTIFY_SETUP(this);
+ size_t opBytesNeeded = sizeof(SkScalar) * 2;
+
+ if (this->commonDrawBitmap(bm, kDrawBitmap_DrawOp, 0, opBytesNeeded, paint)) {
+ fWriter.writeScalar(left);
+ fWriter.writeScalar(top);
+ }
+}
+
+void SkGPipeCanvas::drawBitmapRectToRect(const SkBitmap& bm, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ NOTIFY_SETUP(this);
+ size_t opBytesNeeded = sizeof(SkRect);
+ bool hasSrc = src != NULL;
+ unsigned flags;
+ if (hasSrc) {
+ flags = kDrawBitmap_HasSrcRect_DrawOpFlag;
+ opBytesNeeded += sizeof(int32_t) * 4;
+ } else {
+ flags = 0;
+ }
+
+ if (this->commonDrawBitmap(bm, kDrawBitmapRectToRect_DrawOp, flags, opBytesNeeded, paint)) {
+ if (hasSrc) {
+ fWriter.writeRect(*src);
+ }
+ fWriter.writeRect(dst);
+ }
+}
+
+void SkGPipeCanvas::drawBitmapMatrix(const SkBitmap& bm, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ NOTIFY_SETUP(this);
+ size_t opBytesNeeded = matrix.writeToMemory(NULL);
+
+ if (this->commonDrawBitmap(bm, kDrawBitmapMatrix_DrawOp, 0, opBytesNeeded, paint)) {
+ fWriter.writeMatrix(matrix);
+ }
+}
+
+void SkGPipeCanvas::drawBitmapNine(const SkBitmap& bm, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) {
+ NOTIFY_SETUP(this);
+ size_t opBytesNeeded = sizeof(int32_t) * 4 + sizeof(SkRect);
+
+ if (this->commonDrawBitmap(bm, kDrawBitmapNine_DrawOp, 0, opBytesNeeded, paint)) {
+ fWriter.write32(center.fLeft);
+ fWriter.write32(center.fTop);
+ fWriter.write32(center.fRight);
+ fWriter.write32(center.fBottom);
+ fWriter.writeRect(dst);
+ }
+}
+
+void SkGPipeCanvas::drawSprite(const SkBitmap& bm, int left, int top,
+ const SkPaint* paint) {
+ NOTIFY_SETUP(this);
+ size_t opBytesNeeded = sizeof(int32_t) * 2;
+
+ if (this->commonDrawBitmap(bm, kDrawSprite_DrawOp, 0, opBytesNeeded, paint)) {
+ fWriter.write32(left);
+ fWriter.write32(top);
+ }
+}
+
+void SkGPipeCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ if (byteLength) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ if (this->needOpBytes(4 + SkAlign4(byteLength) + 2 * sizeof(SkScalar))) {
+ this->writeOp(kDrawText_DrawOp);
+ fWriter.write32(byteLength);
+ fWriter.writePad(text, byteLength);
+ fWriter.writeScalar(x);
+ fWriter.writeScalar(y);
+ }
+ }
+}
+
+void SkGPipeCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ if (byteLength) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ int count = paint.textToGlyphs(text, byteLength, NULL);
+ if (this->needOpBytes(4 + SkAlign4(byteLength) + 4 + count * sizeof(SkPoint))) {
+ this->writeOp(kDrawPosText_DrawOp);
+ fWriter.write32(byteLength);
+ fWriter.writePad(text, byteLength);
+ fWriter.write32(count);
+ fWriter.write(pos, count * sizeof(SkPoint));
+ }
+ }
+}
+
+void SkGPipeCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ if (byteLength) {
+ NOTIFY_SETUP(this);
+ this->writePaint(paint);
+ int count = paint.textToGlyphs(text, byteLength, NULL);
+ if (this->needOpBytes(4 + SkAlign4(byteLength) + 4 + count * sizeof(SkScalar) + 4)) {
+ this->writeOp(kDrawPosTextH_DrawOp);
+ fWriter.write32(byteLength);
+ fWriter.writePad(text, byteLength);
+ fWriter.write32(count);
+ fWriter.write(xpos, count * sizeof(SkScalar));
+ fWriter.writeScalar(constY);
+ }
+ }
+}
+
+void SkGPipeCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ if (byteLength) {
+ NOTIFY_SETUP(this);
+ unsigned flags = 0;
+ size_t size = 4 + SkAlign4(byteLength) + path.writeToMemory(NULL);
+ if (matrix) {
+ flags |= kDrawTextOnPath_HasMatrix_DrawOpFlag;
+ size += matrix->writeToMemory(NULL);
+ }
+ this->writePaint(paint);
+ if (this->needOpBytes(size)) {
+ this->writeOp(kDrawTextOnPath_DrawOp, flags, 0);
+
+ fWriter.write32(byteLength);
+ fWriter.writePad(text, byteLength);
+
+ fWriter.writePath(path);
+ if (matrix) {
+ fWriter.writeMatrix(*matrix);
+ }
+ }
+ }
+}
+
+void SkGPipeCanvas::drawPicture(SkPicture& picture) {
+ // we want to playback the picture into individual draw calls
+ this->INHERITED::drawPicture(picture);
+}
+
+void SkGPipeCanvas::drawVertices(VertexMode mode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode*,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ if (0 == vertexCount) {
+ return;
+ }
+
+ NOTIFY_SETUP(this);
+ size_t size = 4 + vertexCount * sizeof(SkPoint);
+ this->writePaint(paint);
+ unsigned flags = 0;
+ if (texs) {
+ flags |= kDrawVertices_HasTexs_DrawOpFlag;
+ size += vertexCount * sizeof(SkPoint);
+ }
+ if (colors) {
+ flags |= kDrawVertices_HasColors_DrawOpFlag;
+ size += vertexCount * sizeof(SkColor);
+ }
+ if (indices && indexCount > 0) {
+ flags |= kDrawVertices_HasIndices_DrawOpFlag;
+ size += 4 + SkAlign4(indexCount * sizeof(uint16_t));
+ }
+
+ if (this->needOpBytes(size)) {
+ this->writeOp(kDrawVertices_DrawOp, flags, 0);
+ fWriter.write32(mode);
+ fWriter.write32(vertexCount);
+ fWriter.write(vertices, vertexCount * sizeof(SkPoint));
+ if (texs) {
+ fWriter.write(texs, vertexCount * sizeof(SkPoint));
+ }
+ if (colors) {
+ fWriter.write(colors, vertexCount * sizeof(SkColor));
+ }
+
+ // TODO: flatten xfermode
+
+ if (indices && indexCount > 0) {
+ fWriter.write32(indexCount);
+ fWriter.writePad(indices, indexCount * sizeof(uint16_t));
+ }
+ }
+}
+
+void SkGPipeCanvas::drawData(const void* ptr, size_t size) {
+ if (size && ptr) {
+ NOTIFY_SETUP(this);
+ unsigned data = 0;
+ if (size < (1 << DRAWOPS_DATA_BITS)) {
+ data = (unsigned)size;
+ }
+ if (this->needOpBytes(4 + SkAlign4(size))) {
+ this->writeOp(kDrawData_DrawOp, 0, data);
+ if (0 == data) {
+ fWriter.write32(size);
+ }
+ fWriter.writePad(ptr, size);
+ }
+ }
+}
+
+void SkGPipeCanvas::beginCommentGroup(const char* description) {
+ // ignore for now
+}
+
+void SkGPipeCanvas::addComment(const char* kywd, const char* value) {
+ // ignore for now
+}
+
+void SkGPipeCanvas::endCommentGroup() {
+ // ignore for now
+}
+
+void SkGPipeCanvas::flushRecording(bool detachCurrentBlock) {
+ doNotify();
+ if (detachCurrentBlock) {
+ // force a new block to be requested for the next recorded command
+ fBlockSize = 0;
+ }
+}
+
+size_t SkGPipeCanvas::freeMemoryIfPossible(size_t bytesToFree) {
+ return (NULL == fBitmapHeap) ? 0 : fBitmapHeap->freeMemoryIfPossible(bytesToFree);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> uint32_t castToU32(T value) {
+ union {
+ T fSrc;
+ uint32_t fDst;
+ } data;
+ data.fSrc = value;
+ return data.fDst;
+}
+
+void SkGPipeCanvas::writePaint(const SkPaint& paint) {
+ if (fDone) {
+ return;
+ }
+ SkPaint& base = fPaint;
+ uint32_t storage[32];
+ uint32_t* ptr = storage;
+
+ if (base.getFlags() != paint.getFlags()) {
+ *ptr++ = PaintOp_packOpData(kFlags_PaintOp, paint.getFlags());
+ base.setFlags(paint.getFlags());
+ }
+ if (base.getColor() != paint.getColor()) {
+ *ptr++ = PaintOp_packOp(kColor_PaintOp);
+ *ptr++ = paint.getColor();
+ base.setColor(paint.getColor());
+ }
+ if (base.getStyle() != paint.getStyle()) {
+ *ptr++ = PaintOp_packOpData(kStyle_PaintOp, paint.getStyle());
+ base.setStyle(paint.getStyle());
+ }
+ if (base.getStrokeJoin() != paint.getStrokeJoin()) {
+ *ptr++ = PaintOp_packOpData(kJoin_PaintOp, paint.getStrokeJoin());
+ base.setStrokeJoin(paint.getStrokeJoin());
+ }
+ if (base.getStrokeCap() != paint.getStrokeCap()) {
+ *ptr++ = PaintOp_packOpData(kCap_PaintOp, paint.getStrokeCap());
+ base.setStrokeCap(paint.getStrokeCap());
+ }
+ if (base.getStrokeWidth() != paint.getStrokeWidth()) {
+ *ptr++ = PaintOp_packOp(kWidth_PaintOp);
+ *ptr++ = castToU32(paint.getStrokeWidth());
+ base.setStrokeWidth(paint.getStrokeWidth());
+ }
+ if (base.getStrokeMiter() != paint.getStrokeMiter()) {
+ *ptr++ = PaintOp_packOp(kMiter_PaintOp);
+ *ptr++ = castToU32(paint.getStrokeMiter());
+ base.setStrokeMiter(paint.getStrokeMiter());
+ }
+ if (base.getTextEncoding() != paint.getTextEncoding()) {
+ *ptr++ = PaintOp_packOpData(kEncoding_PaintOp, paint.getTextEncoding());
+ base.setTextEncoding(paint.getTextEncoding());
+ }
+ if (base.getHinting() != paint.getHinting()) {
+ *ptr++ = PaintOp_packOpData(kHinting_PaintOp, paint.getHinting());
+ base.setHinting(paint.getHinting());
+ }
+ if (base.getTextAlign() != paint.getTextAlign()) {
+ *ptr++ = PaintOp_packOpData(kAlign_PaintOp, paint.getTextAlign());
+ base.setTextAlign(paint.getTextAlign());
+ }
+ if (base.getTextSize() != paint.getTextSize()) {
+ *ptr++ = PaintOp_packOp(kTextSize_PaintOp);
+ *ptr++ = castToU32(paint.getTextSize());
+ base.setTextSize(paint.getTextSize());
+ }
+ if (base.getTextScaleX() != paint.getTextScaleX()) {
+ *ptr++ = PaintOp_packOp(kTextScaleX_PaintOp);
+ *ptr++ = castToU32(paint.getTextScaleX());
+ base.setTextScaleX(paint.getTextScaleX());
+ }
+ if (base.getTextSkewX() != paint.getTextSkewX()) {
+ *ptr++ = PaintOp_packOp(kTextSkewX_PaintOp);
+ *ptr++ = castToU32(paint.getTextSkewX());
+ base.setTextSkewX(paint.getTextSkewX());
+ }
+
+ if (!SkTypeface::Equal(base.getTypeface(), paint.getTypeface())) {
+ if (isCrossProcess(fFlags)) {
+ uint32_t id = this->getTypefaceID(paint.getTypeface());
+ *ptr++ = PaintOp_packOpData(kTypeface_PaintOp, id);
+ } else if (this->needOpBytes(sizeof(void*))) {
+ // Add to the set for ref counting.
+ fTypefaceSet.add(paint.getTypeface());
+ // It is safe to write the typeface to the stream before the rest
+ // of the paint unless we ever send a kReset_PaintOp, which we
+ // currently never do.
+ this->writeOp(kSetTypeface_DrawOp);
+ fWriter.writePtr(paint.getTypeface());
+ }
+ base.setTypeface(paint.getTypeface());
+ }
+
+ // This is a new paint, so all old flats can be safely purged, if necessary.
+ fFlattenableHeap.markAllFlatsSafeToDelete();
+ for (int i = 0; i < kCount_PaintFlats; i++) {
+ int index = this->flattenToIndex(get_paintflat(paint, i), (PaintFlats)i);
+ bool replaced = index < 0;
+ if (replaced) {
+ index = ~index;
+ }
+ // Store the index of any flat that needs to be kept. 0 means no flat.
+ if (index > 0) {
+ fFlattenableHeap.markFlatForKeeping(index);
+ }
+ SkASSERT(index >= 0 && index <= fFlatDictionary.count());
+ if (index != fCurrFlatIndex[i] || replaced) {
+ *ptr++ = PaintOp_packOpFlagData(kFlatIndex_PaintOp, i, index);
+ fCurrFlatIndex[i] = index;
+ }
+ }
+
+ size_t size = (char*)ptr - (char*)storage;
+ if (size && this->needOpBytes(size)) {
+ this->writeOp(kPaintOp_DrawOp, 0, size);
+ fWriter.write(storage, size);
+ for (size_t i = 0; i < size/4; i++) {
+// SkDebugf("[%d] %08X\n", i, storage[i]);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGPipe.h"
+
+SkGPipeController::~SkGPipeController() {
+ SkSafeUnref(fCanvas);
+}
+
+void SkGPipeController::setCanvas(SkGPipeCanvas* canvas) {
+ SkRefCnt_SafeAssign(fCanvas, canvas);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkGPipeWriter::SkGPipeWriter()
+: fWriter(0) {
+ fCanvas = NULL;
+}
+
+SkGPipeWriter::~SkGPipeWriter() {
+ this->endRecording();
+}
+
+SkCanvas* SkGPipeWriter::startRecording(SkGPipeController* controller, uint32_t flags,
+ uint32_t width, uint32_t height) {
+ if (NULL == fCanvas) {
+ fWriter.reset(NULL, 0);
+ fCanvas = SkNEW_ARGS(SkGPipeCanvas, (controller, &fWriter, flags, width, height));
+ }
+ controller->setCanvas(fCanvas);
+ return fCanvas;
+}
+
+void SkGPipeWriter::endRecording() {
+ if (fCanvas) {
+ fCanvas->finish();
+ fCanvas->unref();
+ fCanvas = NULL;
+ }
+}
+
+void SkGPipeWriter::flushRecording(bool detachCurrentBlock) {
+ if (fCanvas) {
+ fCanvas->flushRecording(detachCurrentBlock);
+ }
+}
+
+size_t SkGPipeWriter::freeMemoryIfPossible(size_t bytesToFree) {
+ if (fCanvas) {
+ return fCanvas->freeMemoryIfPossible(bytesToFree);
+ }
+ return 0;
+}
+
+size_t SkGPipeWriter::storageAllocatedForRecording() const {
+ return NULL == fCanvas ? 0 : fCanvas->storageAllocatedForRecording();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+BitmapShuttle::BitmapShuttle(SkGPipeCanvas* canvas) {
+ SkASSERT(canvas != NULL);
+ fCanvas = canvas;
+ fCanvas->ref();
+}
+
+BitmapShuttle::~BitmapShuttle() {
+ fCanvas->unref();
+}
+
+bool BitmapShuttle::insert(const SkBitmap& bitmap, int32_t slot) {
+ return fCanvas->shuttleBitmap(bitmap, slot);
+}
diff --git a/pipe/utils/SamplePipeControllers.cpp b/pipe/utils/SamplePipeControllers.cpp
new file mode 100644
index 00000000..a23d7753
--- /dev/null
+++ b/pipe/utils/SamplePipeControllers.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "SamplePipeControllers.h"
+
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkGPipe.h"
+#include "SkMatrix.h"
+
+PipeController::PipeController(SkCanvas* target, SkPicture::InstallPixelRefProc proc)
+:fReader(target) {
+ fBlock = NULL;
+ fBlockSize = fBytesWritten = 0;
+ fReader.setBitmapDecoder(proc);
+}
+
+PipeController::~PipeController() {
+ sk_free(fBlock);
+}
+
+void* PipeController::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 PipeController::notifyWritten(size_t bytes) {
+ fStatus = fReader.playback(this->getData(), bytes);
+ SkASSERT(SkGPipeReader::kError_Status != fStatus);
+ fBytesWritten += bytes;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TiledPipeController::TiledPipeController(const SkBitmap& bitmap,
+ SkPicture::InstallPixelRefProc proc,
+ const SkMatrix* initial)
+: INHERITED(NULL, proc) {
+ int32_t top = 0;
+ int32_t bottom;
+ int32_t height = bitmap.height() / NumberOfTiles;
+ SkIRect rect;
+ for (int i = 0; i < NumberOfTiles; i++) {
+ bottom = i + 1 == NumberOfTiles ? bitmap.height() : top + height;
+ rect.setLTRB(0, top, bitmap.width(), bottom);
+ top = bottom;
+
+ SkDEBUGCODE(bool extracted = )bitmap.extractSubset(&fBitmaps[i], rect);
+ SkASSERT(extracted);
+ SkDevice* device = new SkDevice(fBitmaps[i]);
+ SkCanvas* canvas = new SkCanvas(device);
+ device->unref();
+ if (initial != NULL) {
+ canvas->setMatrix(*initial);
+ }
+ canvas->translate(SkIntToScalar(-rect.left()),
+ SkIntToScalar(-rect.top()));
+ if (0 == i) {
+ fReader.setCanvas(canvas);
+ } else {
+ fReaders[i - 1].setCanvas(canvas);
+ fReaders[i - 1].setBitmapDecoder(proc);
+ }
+ canvas->unref();
+ }
+}
+
+void TiledPipeController::notifyWritten(size_t bytes) {
+ for (int i = 0; i < NumberOfTiles - 1; i++) {
+ fReaders[i].playback(this->getData(), bytes);
+ }
+ this->INHERITED::notifyWritten(bytes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ThreadSafePipeController::ThreadSafePipeController(int numberOfReaders)
+: fAllocator(kMinBlockSize)
+, fNumberOfReaders(numberOfReaders) {
+ fBlock = NULL;
+ fBytesWritten = 0;
+}
+
+void* ThreadSafePipeController::requestBlock(size_t minRequest, size_t *actual) {
+ if (fBlock) {
+ // Save the previous block for later
+ PipeBlock previousBloc(fBlock, fBytesWritten);
+ fBlockList.push(previousBloc);
+ }
+ int32_t blockSize = SkMax32(SkToS32(minRequest), kMinBlockSize);
+ fBlock = fAllocator.allocThrow(blockSize);
+ fBytesWritten = 0;
+ *actual = blockSize;
+ return fBlock;
+}
+
+void ThreadSafePipeController::notifyWritten(size_t bytes) {
+ fBytesWritten += bytes;
+}
+
+void ThreadSafePipeController::draw(SkCanvas* target) {
+ SkGPipeReader reader(target);
+ for (int currentBlock = 0; currentBlock < fBlockList.count(); currentBlock++ ) {
+ reader.playback(fBlockList[currentBlock].fBlock, fBlockList[currentBlock].fBytes);
+ }
+
+ if (fBlock) {
+ reader.playback(fBlock, fBytesWritten);
+ }
+}
diff --git a/pipe/utils/SamplePipeControllers.h b/pipe/utils/SamplePipeControllers.h
new file mode 100644
index 00000000..35cfba73
--- /dev/null
+++ b/pipe/utils/SamplePipeControllers.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.
+ */
+
+#include "SkBitmap.h"
+#include "SkChunkAlloc.h"
+#include "SkGPipe.h"
+#include "SkPicture.h"
+#include "SkTDArray.h"
+
+class SkCanvas;
+class SkMatrix;
+
+class PipeController : public SkGPipeController {
+public:
+ PipeController(SkCanvas* target, SkPicture::InstallPixelRefProc proc = NULL);
+ virtual ~PipeController();
+ virtual void* requestBlock(size_t minRequest, size_t* actual) SK_OVERRIDE;
+ virtual void notifyWritten(size_t bytes) SK_OVERRIDE;
+protected:
+ const void* getData() { return (const char*) fBlock + fBytesWritten; }
+ SkGPipeReader fReader;
+private:
+ void* fBlock;
+ size_t fBlockSize;
+ size_t fBytesWritten;
+ SkGPipeReader::Status fStatus;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TiledPipeController : public PipeController {
+public:
+ TiledPipeController(const SkBitmap&, SkPicture::InstallPixelRefProc proc = NULL,
+ const SkMatrix* initialMatrix = NULL);
+ virtual ~TiledPipeController() {};
+ virtual void notifyWritten(size_t bytes) SK_OVERRIDE;
+ virtual int numberOfReaders() const SK_OVERRIDE { return NumberOfTiles; }
+private:
+ enum {
+ NumberOfTiles = 10
+ };
+ SkGPipeReader fReaders[NumberOfTiles - 1];
+ SkBitmap fBitmaps[NumberOfTiles];
+ typedef PipeController INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Borrowed (and modified) from SkDeferredCanvas.cpp::DeferredPipeController.
+ * Allows playing back from multiple threads, but does not do the threading itself.
+ */
+class ThreadSafePipeController : public SkGPipeController {
+public:
+ ThreadSafePipeController(int numberOfReaders);
+ virtual void* requestBlock(size_t minRequest, size_t* actual) SK_OVERRIDE;
+ virtual void notifyWritten(size_t bytes) SK_OVERRIDE;
+ virtual int numberOfReaders() const SK_OVERRIDE { return fNumberOfReaders; }
+
+ /**
+ * Play the stored drawing commands to the specified canvas. If SkGPipeWriter::startRecording
+ * used the flag SkGPipeWriter::kSimultaneousReaders_Flag, this can be called from different
+ * threads simultaneously.
+ */
+ void draw(SkCanvas*);
+private:
+ enum {
+ kMinBlockSize = 4096
+ };
+ struct PipeBlock {
+ PipeBlock(void* block, size_t bytes) { fBlock = block, fBytes = bytes; }
+ // Stream of draw commands written by the SkGPipeWriter. Allocated by fAllocator, which will
+ // handle freeing it.
+ void* fBlock;
+ // Number of bytes that were written to fBlock.
+ size_t fBytes;
+ };
+ void* fBlock;
+ size_t fBytesWritten;
+ SkChunkAlloc fAllocator;
+ SkTDArray<PipeBlock> fBlockList;
+ int fNumberOfReaders;
+};
diff --git a/ports/SkDebug_android.cpp b/ports/SkDebug_android.cpp
new file mode 100644
index 00000000..b9b5665a
--- /dev/null
+++ b/ports/SkDebug_android.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * 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 "SkTypes.h"
+
+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/ports/SkDebug_nacl.cpp b/ports/SkDebug_nacl.cpp
new file mode 100644
index 00000000..6e35f096
--- /dev/null
+++ b/ports/SkDebug_nacl.cpp
@@ -0,0 +1,38 @@
+
+/*
+ * 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/ports/SkDebug_stdio.cpp b/ports/SkDebug_stdio.cpp
new file mode 100644
index 00000000..8ff27ade
--- /dev/null
+++ b/ports/SkDebug_stdio.cpp
@@ -0,0 +1,20 @@
+
+/*
+ * 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 "SkTypes.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+void SkDebugf(const char format[], ...) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
diff --git a/ports/SkDebug_win.cpp b/ports/SkDebug_win.cpp
new file mode 100644
index 00000000..e368c971
--- /dev/null
+++ b/ports/SkDebug_win.cpp
@@ -0,0 +1,32 @@
+
+/*
+ * 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 "SkTypes.h"
+
+static const size_t kBufferSize = 2048;
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <Windows.h>
+
+void SkDebugf(const char format[], ...) {
+ char buffer[kBufferSize + 1];
+ va_list args;
+
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+
+ va_start(args, format);
+ vsnprintf(buffer, kBufferSize, format, args);
+ va_end(args);
+
+ OutputDebugStringA(buffer);
+}
diff --git a/ports/SkFontConfigInterface_android.cpp b/ports/SkFontConfigInterface_android.cpp
new file mode 100644
index 00000000..c9dc944f
--- /dev/null
+++ b/ports/SkFontConfigInterface_android.cpp
@@ -0,0 +1,825 @@
+
+/*
+ * Copyright 2013 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 "SkFontConfigInterface.h"
+#include "SkTypeface_android.h"
+
+#include "SkFontConfigParser_android.h"
+#include "SkFontConfigTypeface.h"
+#include "SkFontMgr.h"
+#include "SkGlyphCache.h"
+#include "SkPaint.h"
+#include "SkString.h"
+#include "SkStream.h"
+#include "SkThread.h"
+#include "SkTypefaceCache.h"
+#include "SkTArray.h"
+#include "SkTDict.h"
+#include "SkTSearch.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef SK_DEBUG_FONTS
+ #define SK_DEBUG_FONTS 0
+#endif
+
+#if SK_DEBUG_FONTS
+ #define DEBUG_FONT(args) SkDebugf args
+#else
+ #define DEBUG_FONT(args)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// For test only.
+static const char* gTestMainConfigFile = NULL;
+static const char* gTestFallbackConfigFile = NULL;
+static const char* gTestFontFilePrefix = NULL;
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef int32_t FontRecID;
+#define INVALID_FONT_REC_ID -1
+
+typedef int32_t FamilyRecID;
+#define INVALID_FAMILY_REC_ID -1
+
+// used to record our notion of the pre-existing fonts
+struct FontRec {
+ SkRefPtr<SkTypeface> fTypeface;
+ SkString fFileName;
+ SkTypeface::Style fStyle;
+ SkPaintOptionsAndroid fPaintOptions;
+ bool fIsFallbackFont;
+ bool fIsValid;
+ FamilyRecID fFamilyRecID;
+};
+
+struct FamilyRec {
+ FamilyRec() {
+ memset(fFontRecID, INVALID_FONT_REC_ID, sizeof(fFontRecID));
+ }
+
+ static const int FONT_STYLE_COUNT = 4;
+ FontRecID fFontRecID[FONT_STYLE_COUNT];
+};
+
+
+typedef SkTDArray<FontRecID> FallbackFontList;
+
+class SkFontConfigInterfaceAndroid : public SkFontConfigInterface {
+public:
+ SkFontConfigInterfaceAndroid(SkTDArray<FontFamily*>& fontFamilies);
+ virtual ~SkFontConfigInterfaceAndroid();
+
+ virtual bool matchFamilyName(const char familyName[],
+ SkTypeface::Style requested,
+ FontIdentity* outFontIdentifier,
+ SkString* outFamilyName,
+ SkTypeface::Style* outStyle) SK_OVERRIDE;
+ virtual SkStream* openStream(const FontIdentity&) SK_OVERRIDE;
+
+ // new APIs
+ virtual SkDataTable* getFamilyNames() SK_OVERRIDE;
+ virtual bool matchFamilySet(const char inFamilyName[],
+ SkString* outFamilyName,
+ SkTArray<FontIdentity>*) SK_OVERRIDE;
+
+ /**
+ * Get the family name of the font in the default fallback font list that
+ * contains the specified chararacter. if no font is found, returns false.
+ */
+ bool getFallbackFamilyNameForChar(SkUnichar uni, SkString* name);
+ /**
+ *
+ */
+ SkTypeface* getTypefaceForChar(SkUnichar uni, SkTypeface::Style style,
+ SkPaintOptionsAndroid::FontVariant fontVariant);
+ SkTypeface* nextLogicalTypeface(SkFontID currFontID, SkFontID origFontID,
+ const SkPaintOptionsAndroid& options);
+
+private:
+ void addFallbackFont(FontRecID fontRecID);
+ SkTypeface* getTypefaceForFontRec(FontRecID fontRecID);
+ FallbackFontList* getCurrentLocaleFallbackFontList();
+ FallbackFontList* findFallbackFontList(const SkLanguage& lang, bool isOriginal = true);
+
+ SkTArray<FontRec> fFonts;
+ SkTArray<FamilyRec> fFontFamilies;
+ SkTDict<FamilyRecID> fFamilyNameDict;
+ FamilyRecID fDefaultFamilyRecID;
+
+ // (SkLanguage)<->(fallback chain index) translation
+ SkTDict<FallbackFontList*> fFallbackFontDict;
+ SkTDict<FallbackFontList*> fFallbackFontAliasDict;
+ FallbackFontList fDefaultFallbackList;
+
+ // fallback info for current locale
+ SkString fCachedLocale;
+ FallbackFontList* fLocaleFallbackFontList;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkFontConfigInterfaceAndroid* getSingletonInterface() {
+ SK_DECLARE_STATIC_MUTEX(gMutex);
+ static SkFontConfigInterfaceAndroid* gFontConfigInterface;
+
+ SkAutoMutexAcquire ac(gMutex);
+ if (NULL == gFontConfigInterface) {
+ // load info from a configuration file that we can use to populate the
+ // system/fallback font structures
+ SkTDArray<FontFamily*> fontFamilies;
+ if (!gTestMainConfigFile) {
+ SkFontConfigParser::GetFontFamilies(fontFamilies);
+ } else {
+ SkFontConfigParser::GetTestFontFamilies(fontFamilies, gTestMainConfigFile,
+ gTestFallbackConfigFile);
+ }
+
+ gFontConfigInterface = new SkFontConfigInterfaceAndroid(fontFamilies);
+
+ // cleanup the data we received from the parser
+ fontFamilies.deleteAll();
+ }
+ return gFontConfigInterface;
+}
+
+SkFontConfigInterface* SkFontConfigInterface::GetSingletonDirectInterface() {
+ return getSingletonInterface();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool has_font(const SkTArray<FontRec>& array, const SkString& filename) {
+ for (int i = 0; i < array.count(); i++) {
+ if (array[i].fFileName == filename) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifndef SK_FONT_FILE_PREFIX
+ #define SK_FONT_FILE_PREFIX "/fonts/"
+#endif
+
+static void get_path_for_sys_fonts(SkString* full, const char name[]) {
+ if (gTestFontFilePrefix) {
+ full->set(gTestFontFilePrefix);
+ } else {
+ full->set(getenv("ANDROID_ROOT"));
+ full->append(SK_FONT_FILE_PREFIX);
+ }
+ full->append(name);
+}
+
+static void insert_into_name_dict(SkTDict<FamilyRecID>& familyNameDict,
+ const char* name, FamilyRecID familyRecID) {
+ SkAutoAsciiToLC tolc(name);
+ if (familyNameDict.find(tolc.lc())) {
+ SkDebugf("---- system font attempting to use a the same name [%s] for"
+ "multiple families. skipping subsequent occurrences", tolc.lc());
+ } else {
+ familyNameDict.set(tolc.lc(), familyRecID);
+ }
+}
+
+// Defined in SkFontHost_FreeType.cpp
+bool find_name_and_attributes(SkStream* stream, SkString* name,
+ SkTypeface::Style* style, bool* isFixedWidth);
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFontConfigInterfaceAndroid::SkFontConfigInterfaceAndroid(SkTDArray<FontFamily*>& fontFamilies) :
+ fFonts(fontFamilies.count()),
+ fFontFamilies(fontFamilies.count() / FamilyRec::FONT_STYLE_COUNT),
+ fFamilyNameDict(1024),
+ fDefaultFamilyRecID(INVALID_FAMILY_REC_ID),
+ fFallbackFontDict(128),
+ fFallbackFontAliasDict(128),
+ fLocaleFallbackFontList(NULL) {
+
+ for (int i = 0; i < fontFamilies.count(); ++i) {
+ FontFamily* family = fontFamilies[i];
+
+ // defer initializing the familyRec until we can be sure that at least
+ // one of it's children contains a valid font file
+ FamilyRec* familyRec = NULL;
+ FamilyRecID familyRecID = INVALID_FAMILY_REC_ID;
+
+ for (int j = 0; j < family->fFontFiles.count(); ++j) {
+ SkString filename;
+ get_path_for_sys_fonts(&filename, family->fFontFiles[j]->fFileName);
+
+ if (has_font(fFonts, filename)) {
+ SkDebugf("---- system font and fallback font files specify a duplicate "
+ "font %s, skipping the second occurrence", filename.c_str());
+ continue;
+ }
+
+ FontRec& fontRec = fFonts.push_back();
+ fontRec.fFileName = filename;
+ fontRec.fStyle = SkTypeface::kNormal;
+ fontRec.fPaintOptions = family->fFontFiles[j]->fPaintOptions;
+ fontRec.fIsFallbackFont = family->fIsFallbackFont;
+ fontRec.fIsValid = false;
+ fontRec.fFamilyRecID = familyRecID;
+
+ const FontRecID fontRecID = fFonts.count() - 1;
+
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(filename.c_str()));
+ if (stream.get() != NULL) {
+ bool isFixedWidth;
+ SkString name;
+ fontRec.fIsValid = find_name_and_attributes(stream.get(), &name,
+ &fontRec.fStyle, &isFixedWidth);
+ } else {
+ if (!fontRec.fIsFallbackFont) {
+ SkDebugf("---- failed to open <%s> as a font\n", filename.c_str());
+ }
+ }
+
+ if (fontRec.fIsValid) {
+ DEBUG_FONT(("---- SystemFonts[%d][%d] fallback=%d file=%s",
+ i, fFonts.count() - 1, fontRec.fIsFallbackFont, filename.c_str()));
+ } else {
+ DEBUG_FONT(("---- SystemFonts[%d][%d] fallback=%d file=%s (INVALID)",
+ i, fFonts.count() - 1, fontRec.fIsFallbackFont, filename.c_str()));
+ continue;
+ }
+
+ // create a familyRec now that we know that at least one font in
+ // the family is valid
+ if (familyRec == NULL) {
+ familyRec = &fFontFamilies.push_back();
+ familyRecID = fFontFamilies.count() - 1;
+ fontRec.fFamilyRecID = familyRecID;
+ }
+
+ // add this font to the current familyRec
+ if (INVALID_FONT_REC_ID != familyRec->fFontRecID[fontRec.fStyle]) {
+ DEBUG_FONT(("Overwriting familyRec for style[%d] old,new:(%d,%d)",
+ fontRec.fStyle, familyRec->fFontRecID[fontRec.fStyle],
+ fontRecID));
+ }
+ familyRec->fFontRecID[fontRec.fStyle] = fontRecID;
+
+ // if this is a fallback font then add it to the appropriate fallback chains
+ if (fontRec.fIsFallbackFont) {
+ addFallbackFont(fontRecID);
+ }
+
+ // add the fallback file name to the name dictionary. This is needed
+ // by getFallbackFamilyNameForChar() so that fallback families can be
+ // requested by the filenames of the fonts they contain.
+ if (family->fIsFallbackFont && familyRec) {
+ insert_into_name_dict(fFamilyNameDict, fontRec.fFileName.c_str(), familyRecID);
+ }
+ }
+
+ // add the names that map to this family to the dictionary for easy lookup
+ if (familyRec && !family->fIsFallbackFont) {
+ SkTDArray<const char*> names = family->fNames;
+ if (names.isEmpty()) {
+ SkDEBUGFAIL("ERROR: non-fallback font with no name");
+ continue;
+ }
+
+ for (int i = 0; i < names.count(); i++) {
+ insert_into_name_dict(fFamilyNameDict, names[i], familyRecID);
+ }
+ }
+
+ }
+
+ DEBUG_FONT(("---- We have %d system fonts", fFonts.count()));
+
+ if (fFontFamilies.count() > 0) {
+ fDefaultFamilyRecID = 0;
+ }
+
+ // scans the default fallback font chain, adding every entry to every other
+ // fallback font chain to which it does not belong. this results in every
+ // language-specific fallback font chain having all of its fallback fonts at
+ // the front of the chain, and everything else at the end.
+ FallbackFontList* fallbackList;
+ SkTDict<FallbackFontList*>::Iter iter(fFallbackFontDict);
+ const char* fallbackLang = iter.next(&fallbackList);
+ while(fallbackLang != NULL) {
+ for (int i = 0; i < fDefaultFallbackList.count(); i++) {
+ FontRecID fontRecID = fDefaultFallbackList[i];
+ const SkString& fontLang = fFonts[fontRecID].fPaintOptions.getLanguage().getTag();
+ if (strcmp(fallbackLang, fontLang.c_str()) != 0) {
+ fallbackList->push(fontRecID);
+ }
+ }
+ // move to the next fallback list in the dictionary
+ fallbackLang = iter.next(&fallbackList);
+ }
+}
+
+SkFontConfigInterfaceAndroid::~SkFontConfigInterfaceAndroid() {
+ // iterate through and cleanup fFallbackFontDict
+ SkTDict<FallbackFontList*>::Iter iter(fFallbackFontDict);
+ FallbackFontList* fallbackList;
+ while(iter.next(&fallbackList) != NULL) {
+ SkDELETE(fallbackList);
+ }
+}
+
+void SkFontConfigInterfaceAndroid::addFallbackFont(FontRecID fontRecID) {
+ SkASSERT(fontRecID < fFonts.count());
+ const FontRec& fontRec = fFonts[fontRecID];
+ SkASSERT(fontRec.fIsFallbackFont);
+
+ // add to the default fallback list
+ fDefaultFallbackList.push(fontRecID);
+
+ // stop here if it is the default language tag
+ const SkString& languageTag = fontRec.fPaintOptions.getLanguage().getTag();
+ if (languageTag.isEmpty()) {
+ return;
+ }
+
+ // add to the appropriate language's custom fallback list
+ FallbackFontList* customList = NULL;
+ if (!fFallbackFontDict.find(languageTag.c_str(), &customList)) {
+ DEBUG_FONT(("---- Created fallback list for \"%s\"", languageTag.c_str()));
+ customList = SkNEW(FallbackFontList);
+ fFallbackFontDict.set(languageTag.c_str(), customList);
+ }
+ SkASSERT(customList != NULL);
+ customList->push(fontRecID);
+}
+
+
+static FontRecID find_best_style(const FamilyRec& family, SkTypeface::Style style) {
+
+ const FontRecID* fontRecIDs = family.fFontRecID;
+
+ if (fontRecIDs[style] != INVALID_FONT_REC_ID) { // exact match
+ return fontRecIDs[style];
+ }
+ // look for a matching bold
+ style = (SkTypeface::Style)(style ^ SkTypeface::kItalic);
+ if (fontRecIDs[style] != INVALID_FONT_REC_ID) {
+ return fontRecIDs[style];
+ }
+ // look for the plain
+ if (fontRecIDs[SkTypeface::kNormal] != INVALID_FONT_REC_ID) {
+ return fontRecIDs[SkTypeface::kNormal];
+ }
+ // look for anything
+ for (int i = 0; i < FamilyRec::FONT_STYLE_COUNT; i++) {
+ if (fontRecIDs[i] != INVALID_FONT_REC_ID) {
+ return fontRecIDs[i];
+ }
+ }
+ // should never get here, since the fontRecID list should not be empty
+ SkDEBUGFAIL("No valid fonts exist for this family");
+ return -1;
+}
+
+bool SkFontConfigInterfaceAndroid::matchFamilyName(const char familyName[],
+ SkTypeface::Style style,
+ FontIdentity* outFontIdentifier,
+ SkString* outFamilyName,
+ SkTypeface::Style* outStyle) {
+ // clip to legal style bits
+ style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic);
+
+ bool exactNameMatch = false;
+
+ FamilyRecID familyRecID = INVALID_FAMILY_REC_ID;
+ if (NULL != familyName) {
+ SkAutoAsciiToLC tolc(familyName);
+ if (fFamilyNameDict.find(tolc.lc(), &familyRecID)) {
+ exactNameMatch = true;
+ }
+ } else {
+ familyRecID = fDefaultFamilyRecID;
+
+ }
+
+ if (INVALID_FAMILY_REC_ID == familyRecID) {
+ //TODO this ensures that we always return something
+ familyRecID = fDefaultFamilyRecID;
+ //return false;
+ }
+
+ FontRecID fontRecID = find_best_style(fFontFamilies[familyRecID], style);
+ FontRec& fontRec = fFonts[fontRecID];
+
+ if (NULL != outFontIdentifier) {
+ outFontIdentifier->fID = fontRecID;
+ outFontIdentifier->fTTCIndex = 0;
+ outFontIdentifier->fString.set(fontRec.fFileName);
+// outFontIdentifier->fStyle = fontRec.fStyle;
+ }
+
+ if (NULL != outFamilyName) {
+ if (exactNameMatch) {
+ outFamilyName->set(familyName);
+ } else {
+ // find familyName from list of names
+ const char* familyName = NULL;
+ SkAssertResult(fFamilyNameDict.findKey(familyRecID, &familyName));
+ SkASSERT(familyName);
+ outFamilyName->set(familyName);
+ }
+ }
+
+ if (NULL != outStyle) {
+ *outStyle = fontRec.fStyle;
+ }
+
+ return true;
+}
+
+SkStream* SkFontConfigInterfaceAndroid::openStream(const FontIdentity& identity) {
+ return SkStream::NewFromFile(identity.fString.c_str());
+}
+
+SkDataTable* SkFontConfigInterfaceAndroid::getFamilyNames() {
+ SkTDArray<const char*> names;
+ SkTDArray<size_t> sizes;
+
+ SkTDict<FamilyRecID>::Iter iter(fFamilyNameDict);
+ const char* familyName = iter.next(NULL);
+ while(familyName != NULL) {
+ *names.append() = familyName;
+ *sizes.append() = strlen(familyName) + 1;
+
+ // move to the next familyName in the dictionary
+ familyName = iter.next(NULL);
+ }
+
+ return SkDataTable::NewCopyArrays((const void*const*)names.begin(),
+ sizes.begin(), names.count());
+}
+
+bool SkFontConfigInterfaceAndroid::matchFamilySet(const char inFamilyName[],
+ SkString* outFamilyName,
+ SkTArray<FontIdentity>*) {
+ return false;
+}
+
+static bool find_proc(SkTypeface* face, SkTypeface::Style style, void* ctx) {
+ const FontRecID* fontRecID = (const FontRecID*)ctx;
+ FontRecID currFontRecID = ((FontConfigTypeface*)face)->getIdentity().fID;
+ return currFontRecID == *fontRecID;
+}
+
+SkTypeface* SkFontConfigInterfaceAndroid::getTypefaceForFontRec(FontRecID fontRecID) {
+ FontRec& fontRec = fFonts[fontRecID];
+ SkTypeface* face = fontRec.fTypeface.get();
+ if (!face) {
+ // look for it in the typeface cache
+ face = SkTypefaceCache::FindByProcAndRef(find_proc, &fontRecID);
+
+ // if it is not in the cache then create it
+ if (!face) {
+ const char* familyName = NULL;
+ SkAssertResult(fFamilyNameDict.findKey(fontRec.fFamilyRecID, &familyName));
+ SkASSERT(familyName);
+ face = SkTypeface::CreateFromName(familyName, fontRec.fStyle);
+ }
+
+ // store the result for subsequent lookups
+ fontRec.fTypeface = face;
+ }
+ SkASSERT(face);
+ return face;
+}
+
+bool SkFontConfigInterfaceAndroid::getFallbackFamilyNameForChar(SkUnichar uni, SkString* name) {
+ FallbackFontList* fallbackFontList = this->getCurrentLocaleFallbackFontList();
+ for (int i = 0; i < fallbackFontList->count(); i++) {
+ FontRecID fontRecID = fallbackFontList->getAt(i);
+ SkTypeface* face = this->getTypefaceForFontRec(fontRecID);
+
+ SkPaint paint;
+ paint.setTypeface(face);
+ paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+
+ uint16_t glyphID;
+ paint.textToGlyphs(&uni, sizeof(uni), &glyphID);
+ if (glyphID != 0) {
+ name->set(fFonts[fontRecID].fFileName);
+ return true;
+ }
+ }
+ return false;
+}
+
+SkTypeface* SkFontConfigInterfaceAndroid::getTypefaceForChar(SkUnichar uni,
+ SkTypeface::Style style,
+ SkPaintOptionsAndroid::FontVariant fontVariant) {
+ FontRecID fontRecID = find_best_style(fFontFamilies[fDefaultFamilyRecID], style);
+ SkTypeface* face = this->getTypefaceForFontRec(fontRecID);
+
+ SkPaintOptionsAndroid paintOptions;
+ paintOptions.setFontVariant(fontVariant);
+ paintOptions.setUseFontFallbacks(true);
+
+ SkPaint paint;
+ paint.setTypeface(face);
+ paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ paint.setPaintOptionsAndroid(paintOptions);
+
+ SkAutoGlyphCache autoCache(paint, NULL, NULL);
+ SkGlyphCache* cache = autoCache.getCache();
+
+ SkScalerContext* ctx = cache->getScalerContext();
+ if (ctx) {
+ SkFontID fontID = ctx->findTypefaceIdForChar(uni);
+ return SkTypefaceCache::FindByID(fontID);
+ }
+ return NULL;
+}
+
+FallbackFontList* SkFontConfigInterfaceAndroid::getCurrentLocaleFallbackFontList() {
+ SkString locale = SkFontConfigParser::GetLocale();
+ if (NULL == fLocaleFallbackFontList || locale != fCachedLocale) {
+ fCachedLocale = locale;
+ fLocaleFallbackFontList = this->findFallbackFontList(locale);
+ }
+ return fLocaleFallbackFontList;
+}
+
+FallbackFontList* SkFontConfigInterfaceAndroid::findFallbackFontList(const SkLanguage& lang,
+ bool isOriginal) {
+ const SkString& langTag = lang.getTag();
+ if (langTag.isEmpty()) {
+ return &fDefaultFallbackList;
+ }
+
+ FallbackFontList* fallbackFontList;
+ if (fFallbackFontDict.find(langTag.c_str(), langTag.size(), &fallbackFontList) ||
+ fFallbackFontAliasDict.find(langTag.c_str(), langTag.size(), &fallbackFontList)) {
+ return fallbackFontList;
+ }
+
+ // attempt a recursive fuzzy match
+ SkLanguage parent = lang.getParent();
+ fallbackFontList = findFallbackFontList(parent, false);
+
+ // cache the original lang so we don't have to do the recursion again.
+ if (isOriginal) {
+ DEBUG_FONT(("---- Created fallback list alias for \"%s\"", langTag.c_str()));
+ fFallbackFontAliasDict.set(langTag.c_str(), fallbackFontList);
+ }
+ return fallbackFontList;
+}
+
+SkTypeface* SkFontConfigInterfaceAndroid::nextLogicalTypeface(SkFontID currFontID,
+ SkFontID origFontID,
+ const SkPaintOptionsAndroid& opts) {
+ // Skia does not support font fallback by default. This enables clients such
+ // as WebKit to customize their font selection. In any case, clients can use
+ // GetFallbackFamilyNameForChar() to get the fallback font for individual
+ // characters.
+ if (!opts.isUsingFontFallbacks()) {
+ return NULL;
+ }
+
+ FallbackFontList* currentFallbackList = findFallbackFontList(opts.getLanguage());
+ SkASSERT(currentFallbackList);
+
+ // we must convert currTypeface into a FontRecID
+ FontRecID currFontRecID = INVALID_FONT_REC_ID;
+ const SkTypeface* currTypeface = SkTypefaceCache::FindByID(currFontID);
+ // non-system fonts are not in the font cache so if we are asked to fallback
+ // for a non-system font we will start at the front of the chain.
+ if (NULL != currTypeface && currFontID != origFontID) {
+ currFontRecID = ((FontConfigTypeface*)currTypeface)->getIdentity().fID;
+ SkASSERT(INVALID_FONT_REC_ID != currFontRecID);
+ }
+
+ // lookup the index next font in the chain
+ int currFallbackFontIndex = currentFallbackList->find(currFontRecID);
+ // We add 1 to the returned index for 2 reasons: (1) if find succeeds it moves
+ // our index to the next entry in the list; (2) if find() fails it returns
+ // -1 and incrementing it will set our starting index to 0 (the head of the list)
+ int nextFallbackFontIndex = currFallbackFontIndex + 1;
+
+ if(nextFallbackFontIndex >= currentFallbackList->count()) {
+ return NULL;
+ }
+
+ // If a rec object is set to prefer "kDefault_Variant" it means they have no preference
+ // In this case, we set the value to "kCompact_Variant"
+ SkPaintOptionsAndroid::FontVariant variant = opts.getFontVariant();
+ if (variant == SkPaintOptionsAndroid::kDefault_Variant) {
+ variant = SkPaintOptionsAndroid::kCompact_Variant;
+ }
+
+ int32_t acceptedVariants = SkPaintOptionsAndroid::kDefault_Variant | variant;
+
+ SkTypeface* nextLogicalTypeface = 0;
+ while (nextFallbackFontIndex < currentFallbackList->count()) {
+ FontRecID fontRecID = currentFallbackList->getAt(nextFallbackFontIndex);
+ if ((fFonts[fontRecID].fPaintOptions.getFontVariant() & acceptedVariants) != 0) {
+ nextLogicalTypeface = this->getTypefaceForFontRec(fontRecID);
+ break;
+ }
+ nextFallbackFontIndex++;
+ }
+
+ DEBUG_FONT(("---- nextLogicalFont: currFontID=%d, origFontID=%d, currRecID=%d, "
+ "lang=%s, variant=%d, nextFallbackIndex[%d,%d] => nextLogicalTypeface=%d",
+ currFontID, origFontID, currFontRecID, opts.getLanguage().getTag().c_str(),
+ variant, nextFallbackFontIndex, currentFallbackList->getAt(nextFallbackFontIndex),
+ (nextLogicalTypeface) ? nextLogicalTypeface->uniqueID() : 0));
+ return SkSafeRef(nextLogicalTypeface);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkGetFallbackFamilyNameForChar(SkUnichar uni, SkString* name) {
+ SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface();
+ return fontConfig->getFallbackFamilyNameForChar(uni, name);
+}
+
+void SkUseTestFontConfigFile(const char* mainconf, const char* fallbackconf,
+ const char* fontsdir) {
+ gTestMainConfigFile = mainconf;
+ gTestFallbackConfigFile = fallbackconf;
+ gTestFontFilePrefix = fontsdir;
+ SkASSERT(gTestMainConfigFile);
+ SkASSERT(gTestFallbackConfigFile);
+ SkASSERT(gTestFontFilePrefix);
+ SkDEBUGF(("Use Test Config File Main %s, Fallback %s, Font Dir %s",
+ gTestMainConfigFile, gTestFallbackConfigFile, gTestFontFilePrefix));
+}
+
+SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID, SkFontID origFontID,
+ const SkPaintOptionsAndroid& options) {
+ SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface();
+ return fontConfig->nextLogicalTypeface(currFontID, origFontID, options);
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+
+struct HB_UnicodeMapping {
+ // TODO: when the WebView no longer needs harfbuzz_old, remove
+ HB_Script script_old;
+ hb_script_t script;
+ const SkUnichar unicode;
+};
+
+/*
+ * The following scripts are not complex fonts and we do not expect them to be parsed by this table
+ * HB_SCRIPT_COMMON,
+ * HB_SCRIPT_GREEK,
+ * HB_SCRIPT_CYRILLIC,
+ * HB_SCRIPT_HANGUL
+ * HB_SCRIPT_INHERITED
+ */
+
+/* Harfbuzz (old) is missing a number of scripts in its table. For these,
+ * we include a value which can never happen. We won't get complex script
+ * shaping in these cases, but the library wouldn't know how to shape
+ * them anyway. */
+#define HB_Script_Unknown HB_ScriptCount
+
+static HB_UnicodeMapping HB_UnicodeMappingArray[] = {
+ {HB_Script_Armenian, HB_SCRIPT_ARMENIAN, 0x0531},
+ {HB_Script_Hebrew, HB_SCRIPT_HEBREW, 0x0591},
+ {HB_Script_Arabic, HB_SCRIPT_ARABIC, 0x0600},
+ {HB_Script_Syriac, HB_SCRIPT_SYRIAC, 0x0710},
+ {HB_Script_Thaana, HB_SCRIPT_THAANA, 0x0780},
+ {HB_Script_Nko, HB_SCRIPT_NKO, 0x07C0},
+ {HB_Script_Devanagari, HB_SCRIPT_DEVANAGARI, 0x0901},
+ {HB_Script_Bengali, HB_SCRIPT_BENGALI, 0x0981},
+ {HB_Script_Gurmukhi, HB_SCRIPT_GURMUKHI, 0x0A10},
+ {HB_Script_Gujarati, HB_SCRIPT_GUJARATI, 0x0A90},
+ {HB_Script_Oriya, HB_SCRIPT_ORIYA, 0x0B10},
+ {HB_Script_Tamil, HB_SCRIPT_TAMIL, 0x0B82},
+ {HB_Script_Telugu, HB_SCRIPT_TELUGU, 0x0C10},
+ {HB_Script_Kannada, HB_SCRIPT_KANNADA, 0x0C90},
+ {HB_Script_Malayalam, HB_SCRIPT_MALAYALAM, 0x0D10},
+ {HB_Script_Sinhala, HB_SCRIPT_SINHALA, 0x0D90},
+ {HB_Script_Thai, HB_SCRIPT_THAI, 0x0E01},
+ {HB_Script_Lao, HB_SCRIPT_LAO, 0x0E81},
+ {HB_Script_Tibetan, HB_SCRIPT_TIBETAN, 0x0F00},
+ {HB_Script_Myanmar, HB_SCRIPT_MYANMAR, 0x1000},
+ {HB_Script_Georgian, HB_SCRIPT_GEORGIAN, 0x10A0},
+ {HB_Script_Unknown, HB_SCRIPT_ETHIOPIC, 0x1200},
+ {HB_Script_Unknown, HB_SCRIPT_CHEROKEE, 0x13A0},
+ {HB_Script_Ogham, HB_SCRIPT_OGHAM, 0x1680},
+ {HB_Script_Runic, HB_SCRIPT_RUNIC, 0x16A0},
+ {HB_Script_Khmer, HB_SCRIPT_KHMER, 0x1780},
+ {HB_Script_Unknown, HB_SCRIPT_TAI_LE, 0x1950},
+ {HB_Script_Unknown, HB_SCRIPT_NEW_TAI_LUE, 0x1980},
+ {HB_Script_Unknown, HB_SCRIPT_TAI_THAM, 0x1A20},
+ {HB_Script_Unknown, HB_SCRIPT_CHAM, 0xAA00},
+};
+
+static hb_script_t getHBScriptFromHBScriptOld(HB_Script script_old) {
+ hb_script_t script = HB_SCRIPT_INVALID;
+ int numSupportedFonts = sizeof(HB_UnicodeMappingArray) / sizeof(HB_UnicodeMapping);
+ for (int i = 0; i < numSupportedFonts; i++) {
+ if (script_old == HB_UnicodeMappingArray[i].script_old) {
+ script = HB_UnicodeMappingArray[i].script;
+ break;
+ }
+ }
+ return script;
+}
+
+// returns 0 for "Not Found"
+static SkUnichar getUnicodeFromHBScript(hb_script_t script) {
+ SkUnichar unichar = 0;
+ int numSupportedFonts = sizeof(HB_UnicodeMappingArray) / sizeof(HB_UnicodeMapping);
+ for (int i = 0; i < numSupportedFonts; i++) {
+ if (script == HB_UnicodeMappingArray[i].script) {
+ unichar = HB_UnicodeMappingArray[i].unicode;
+ break;
+ }
+ }
+ return unichar;
+}
+
+struct TypefaceLookupStruct {
+ hb_script_t script;
+ SkTypeface::Style style;
+ SkPaintOptionsAndroid::FontVariant fontVariant;
+ SkTypeface* typeface;
+};
+
+SK_DECLARE_STATIC_MUTEX(gTypefaceTableMutex); // This is the mutex for gTypefaceTable
+static SkTDArray<TypefaceLookupStruct> gTypefaceTable; // This is protected by gTypefaceTableMutex
+
+static int typefaceLookupCompare(const TypefaceLookupStruct& first,
+ const TypefaceLookupStruct& second) {
+ if (first.script != second.script) {
+ return (first.script > second.script) ? 1 : -1;
+ }
+ if (first.style != second.style) {
+ return (first.style > second.style) ? 1 : -1;
+ }
+ if (first.fontVariant != second.fontVariant) {
+ return (first.fontVariant > second.fontVariant) ? 1 : -1;
+ }
+ return 0;
+}
+
+SkTypeface* SkCreateTypefaceForScriptNG(hb_script_t script, SkTypeface::Style style,
+ SkPaintOptionsAndroid::FontVariant fontVariant) {
+ SkAutoMutexAcquire ac(gTypefaceTableMutex);
+
+ TypefaceLookupStruct key;
+ key.script = script;
+ key.style = style;
+ key.fontVariant = fontVariant;
+
+ int index = SkTSearch<TypefaceLookupStruct>(
+ (const TypefaceLookupStruct*) gTypefaceTable.begin(),
+ gTypefaceTable.count(), key, sizeof(TypefaceLookupStruct),
+ typefaceLookupCompare);
+
+ SkTypeface* retTypeface = NULL;
+ if (index >= 0) {
+ retTypeface = gTypefaceTable[index].typeface;
+ }
+ else {
+ SkUnichar unichar = getUnicodeFromHBScript(script);
+ if (!unichar) {
+ return NULL;
+ }
+
+ SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface();
+ retTypeface = fontConfig->getTypefaceForChar(unichar, style, fontVariant);
+
+ // add to the lookup table
+ key.typeface = retTypeface;
+ *gTypefaceTable.insert(~index) = key;
+ }
+
+ // we ref(), the caller is expected to unref when they are done
+ return SkSafeRef(retTypeface);
+}
+
+SkTypeface* SkCreateTypefaceForScript(HB_Script script, SkTypeface::Style style,
+ SkPaintOptionsAndroid::FontVariant fontVariant) {
+ return SkCreateTypefaceForScriptNG(getHBScriptFromHBScriptOld(script), style, fontVariant);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkFontMgr* SkFontMgr::Factory() {
+ return NULL;
+}
diff --git a/ports/SkFontConfigInterface_direct.cpp b/ports/SkFontConfigInterface_direct.cpp
new file mode 100644
index 00000000..af704d03
--- /dev/null
+++ b/ports/SkFontConfigInterface_direct.cpp
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */
+
+#include <string>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include "SkBuffer.h"
+#include "SkFontConfigInterface.h"
+#include "SkStream.h"
+
+size_t SkFontConfigInterface::FontIdentity::writeToMemory(void* addr) const {
+ size_t size = sizeof(fID) + sizeof(fTTCIndex);
+ size += sizeof(int32_t) + sizeof(int32_t) + sizeof(uint8_t); // weight, width, italic
+ size += sizeof(int32_t) + fString.size(); // store length+data
+ if (addr) {
+ SkWBuffer buffer(addr, size);
+
+ buffer.write32(fID);
+ buffer.write32(fTTCIndex);
+ buffer.write32(fString.size());
+ buffer.write32(fStyle.weight());
+ buffer.write32(fStyle.width());
+ buffer.write8(fStyle.slant());
+ buffer.write(fString.c_str(), fString.size());
+ buffer.padToAlign4();
+
+ SkASSERT(buffer.pos() == size);
+ }
+ return size;
+}
+
+size_t SkFontConfigInterface::FontIdentity::readFromMemory(const void* addr,
+ size_t size) {
+ SkRBuffer buffer(addr, size);
+
+ fID = buffer.readU32();
+ fTTCIndex = buffer.readU32();
+ size_t strLen = buffer.readU32();
+ int weight = buffer.readU32();
+ int width = buffer.readU32();
+ SkFontStyle::Slant slant = (SkFontStyle::Slant)buffer.readU8();
+ fStyle = SkFontStyle(weight, width, slant);
+ fString.resize(strLen);
+ buffer.read(fString.writable_str(), strLen);
+ buffer.skipToAlign4();
+
+ return buffer.pos(); // the actual number of bytes read
+}
+
+#ifdef SK_DEBUG
+static void make_iden(SkFontConfigInterface::FontIdentity* iden) {
+ iden->fID = 10;
+ iden->fTTCIndex = 2;
+ iden->fString.set("Hello world");
+ iden->fStyle = SkFontStyle(300, 6, SkFontStyle::kItalic_Slant);
+}
+
+static void test_writeToMemory(const SkFontConfigInterface::FontIdentity& iden0,
+ int initValue) {
+ SkFontConfigInterface::FontIdentity iden1;
+
+ size_t size0 = iden0.writeToMemory(NULL);
+
+ SkAutoMalloc storage(size0);
+ memset(storage.get(), initValue, size0);
+
+ size_t size1 = iden0.writeToMemory(storage.get());
+ SkASSERT(size0 == size1);
+
+ SkASSERT(iden0 != iden1);
+ size_t size2 = iden1.readFromMemory(storage.get(), size1);
+ SkASSERT(size2 == size1);
+ SkASSERT(iden0 == iden1);
+}
+
+static void fontconfiginterface_unittest() {
+ SkFontConfigInterface::FontIdentity iden0, iden1;
+
+ SkASSERT(iden0 == iden1);
+
+ make_iden(&iden0);
+ SkASSERT(iden0 != iden1);
+
+ make_iden(&iden1);
+ SkASSERT(iden0 == iden1);
+
+ test_writeToMemory(iden0, 0);
+ test_writeToMemory(iden0, 0);
+}
+#endif
+
+class SkFontConfigInterfaceDirect : public SkFontConfigInterface {
+public:
+ SkFontConfigInterfaceDirect();
+ virtual ~SkFontConfigInterfaceDirect();
+
+ virtual bool matchFamilyName(const char familyName[],
+ SkTypeface::Style requested,
+ FontIdentity* outFontIdentifier,
+ SkString* outFamilyName,
+ SkTypeface::Style* outStyle) SK_OVERRIDE;
+ virtual SkStream* openStream(const FontIdentity&) SK_OVERRIDE;
+
+ // new APIs
+ virtual SkDataTable* getFamilyNames() SK_OVERRIDE;
+ virtual bool matchFamilySet(const char inFamilyName[],
+ SkString* outFamilyName,
+ SkTArray<FontIdentity>*) SK_OVERRIDE;
+
+private:
+ SkMutex mutex_;
+};
+
+SkFontConfigInterface* SkFontConfigInterface::GetSingletonDirectInterface() {
+ static SkFontConfigInterface* gDirect;
+ if (NULL == gDirect) {
+ static SkMutex gMutex;
+ SkAutoMutexAcquire ac(gMutex);
+
+ if (NULL == gDirect) {
+ gDirect = new SkFontConfigInterfaceDirect;
+ }
+ }
+ return gDirect;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Returns the string from the pattern, or NULL
+static const char* get_name(FcPattern* pattern, const char field[],
+ int index = 0) {
+ const char* name;
+ if (FcPatternGetString(pattern, field, index,
+ (FcChar8**)&name) != FcResultMatch) {
+ name = NULL;
+ }
+ return name;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// Equivalence classes, used to match the Liberation and other fonts
+// with their metric-compatible replacements. See the discussion in
+// GetFontEquivClass().
+enum FontEquivClass
+{
+ OTHER,
+ SANS,
+ SERIF,
+ MONO,
+ SYMBOL,
+ PGOTHIC,
+ GOTHIC,
+ PMINCHO,
+ MINCHO,
+ SIMSUN,
+ NSIMSUN,
+ SIMHEI,
+ PMINGLIU,
+ MINGLIU,
+ PMINGLIUHK,
+ MINGLIUHK,
+ CAMBRIA,
+};
+
+// Match the font name against a whilelist of fonts, returning the equivalence
+// class.
+FontEquivClass GetFontEquivClass(const char* fontname)
+{
+ // It would be nice for fontconfig to tell us whether a given suggested
+ // replacement is a "strong" match (that is, an equivalent font) or
+ // a "weak" match (that is, fontconfig's next-best attempt at finding a
+ // substitute). However, I played around with the fontconfig API for
+ // a good few hours and could not make it reveal this information.
+ //
+ // So instead, we hardcode. Initially this function emulated
+ // /etc/fonts/conf.d/30-metric-aliases.conf
+ // from my Ubuntu system, but we're better off being very conservative.
+
+ // Arimo, Tinos and Cousine are a set of fonts metric-compatible with
+ // Arial, Times New Roman and Courier New with a character repertoire
+ // much larger than Liberation. Note that Cousine is metrically
+ // compatible with Courier New, but the former is sans-serif while
+ // the latter is serif.
+
+
+ struct FontEquivMap {
+ FontEquivClass clazz;
+ const char name[40];
+ };
+
+ static const FontEquivMap kFontEquivMap[] = {
+ { SANS, "Arial" },
+ { SANS, "Arimo" },
+ { SANS, "Liberation Sans" },
+
+ { SERIF, "Times New Roman" },
+ { SERIF, "Tinos" },
+ { SERIF, "Liberation Serif" },
+
+ { MONO, "Courier New" },
+ { MONO, "Cousine" },
+ { MONO, "Liberation Mono" },
+
+ { SYMBOL, "Symbol" },
+ { SYMBOL, "Symbol Neu" },
+
+ // MS Pゴシック
+ { PGOTHIC, "MS PGothic" },
+ { PGOTHIC, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
+ "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
+ { PGOTHIC, "IPAPGothic" },
+ { PGOTHIC, "MotoyaG04Gothic" },
+
+ // MS ゴシック
+ { GOTHIC, "MS Gothic" },
+ { GOTHIC, "\xef\xbc\xad\xef\xbc\xb3 "
+ "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
+ { GOTHIC, "IPAGothic" },
+ { GOTHIC, "MotoyaG04GothicMono" },
+
+ // ï¼­ï¼³ P明æœ
+ { PMINCHO, "MS PMincho" },
+ { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
+ "\xe6\x98\x8e\xe6\x9c\x9d"},
+ { PMINCHO, "IPAPMincho" },
+ { PMINCHO, "MotoyaG04Mincho" },
+
+ // ï¼­ï¼³ 明æœ
+ { MINCHO, "MS Mincho" },
+ { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" },
+ { MINCHO, "IPAMincho" },
+ { MINCHO, "MotoyaG04MinchoMono" },
+
+ // 宋体
+ { SIMSUN, "Simsun" },
+ { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" },
+ { SIMSUN, "MSung GB18030" },
+ { SIMSUN, "Song ASC" },
+
+ // 新宋体
+ { NSIMSUN, "NSimsun" },
+ { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" },
+ { NSIMSUN, "MSung GB18030" },
+ { NSIMSUN, "N Song ASC" },
+
+ // 黑体
+ { SIMHEI, "Simhei" },
+ { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" },
+ { SIMHEI, "MYingHeiGB18030" },
+ { SIMHEI, "MYingHeiB5HK" },
+
+ // 新細明體
+ { PMINGLIU, "PMingLiU"},
+ { PMINGLIU, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
+ { PMINGLIU, "MSung B5HK"},
+
+ // 細明體
+ { MINGLIU, "MingLiU"},
+ { MINGLIU, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
+ { MINGLIU, "MSung B5HK"},
+
+ // 新細明體
+ { PMINGLIUHK, "PMingLiU_HKSCS"},
+ { PMINGLIUHK, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
+ { PMINGLIUHK, "MSung B5HK"},
+
+ // 細明體
+ { MINGLIUHK, "MingLiU_HKSCS"},
+ { MINGLIUHK, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
+ { MINGLIUHK, "MSung B5HK"},
+
+ // Cambria
+ { CAMBRIA, "Cambria" },
+ { CAMBRIA, "Caladea" },
+ };
+
+ static const size_t kFontCount =
+ sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]);
+
+ // TODO(jungshik): If this loop turns out to be hot, turn
+ // the array to a static (hash)map to speed it up.
+ for (size_t i = 0; i < kFontCount; ++i) {
+ if (strcasecmp(kFontEquivMap[i].name, fontname) == 0)
+ return kFontEquivMap[i].clazz;
+ }
+ return OTHER;
+}
+
+
+// Return true if |font_a| and |font_b| are visually and at the metrics
+// level interchangeable.
+bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b)
+{
+ FontEquivClass class_a = GetFontEquivClass(font_a);
+ FontEquivClass class_b = GetFontEquivClass(font_b);
+
+ return class_a != OTHER && class_a == class_b;
+}
+
+// Normally we only return exactly the font asked for. In last-resort
+// cases, the request either doesn't specify a font or is one of the
+// basic font names like "Sans", "Serif" or "Monospace". This function
+// tells you whether a given request is for such a fallback.
+bool IsFallbackFontAllowed(const std::string& family) {
+ const char* family_cstr = family.c_str();
+ return family.empty() ||
+ strcasecmp(family_cstr, "sans") == 0 ||
+ strcasecmp(family_cstr, "serif") == 0 ||
+ strcasecmp(family_cstr, "monospace") == 0;
+}
+
+static bool valid_pattern(FcPattern* pattern) {
+ FcBool is_scalable;
+ if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &is_scalable) != FcResultMatch
+ || !is_scalable) {
+ return false;
+ }
+
+ // fontconfig can also return fonts which are unreadable
+ const char* c_filename = get_name(pattern, FC_FILE);
+ if (!c_filename) {
+ return false;
+ }
+ if (access(c_filename, R_OK) != 0) {
+ return false;
+ }
+ return true;
+}
+
+// Find matching font from |font_set| for the given font family.
+FcPattern* MatchFont(FcFontSet* font_set,
+ const char* post_config_family,
+ const std::string& family) {
+ // Older versions of fontconfig have a bug where they cannot select
+ // only scalable fonts so we have to manually filter the results.
+ FcPattern* match = NULL;
+ for (int i = 0; i < font_set->nfont; ++i) {
+ FcPattern* current = font_set->fonts[i];
+ if (valid_pattern(current)) {
+ match = current;
+ break;
+ }
+ }
+
+ if (match && !IsFallbackFontAllowed(family)) {
+ bool acceptable_substitute = false;
+ for (int id = 0; id < 255; ++id) {
+ const char* post_match_family = get_name(match, FC_FAMILY, id);
+ if (!post_match_family)
+ break;
+ acceptable_substitute =
+ (strcasecmp(post_config_family, post_match_family) == 0 ||
+ // Workaround for Issue 12530:
+ // requested family: "Bitstream Vera Sans"
+ // post_config_family: "Arial"
+ // post_match_family: "Bitstream Vera Sans"
+ // -> We should treat this case as a good match.
+ strcasecmp(family.c_str(), post_match_family) == 0) ||
+ IsMetricCompatibleReplacement(family.c_str(), post_match_family);
+ if (acceptable_substitute)
+ break;
+ }
+ if (!acceptable_substitute)
+ return NULL;
+ }
+
+ return match;
+}
+
+// Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|.
+SkTypeface::Style GetFontStyle(FcPattern* font) {
+ int resulting_bold;
+ if (FcPatternGetInteger(font, FC_WEIGHT, 0, &resulting_bold))
+ resulting_bold = FC_WEIGHT_NORMAL;
+
+ int resulting_italic;
+ if (FcPatternGetInteger(font, FC_SLANT, 0, &resulting_italic))
+ resulting_italic = FC_SLANT_ROMAN;
+
+ // If we ask for an italic font, fontconfig might take a roman font and set
+ // the undocumented property FC_MATRIX to a skew matrix. It'll then say
+ // that the font is italic or oblique. So, if we see a matrix, we don't
+ // believe that it's italic.
+ FcValue matrix;
+ const bool have_matrix = FcPatternGet(font, FC_MATRIX, 0, &matrix) == 0;
+
+ // If we ask for an italic font, fontconfig might take a roman font and set
+ // FC_EMBOLDEN.
+ FcValue embolden;
+ const bool have_embolden = FcPatternGet(font, FC_EMBOLDEN, 0, &embolden) == 0;
+
+ int styleBits = 0;
+ if (resulting_bold > FC_WEIGHT_MEDIUM && !have_embolden) {
+ styleBits |= SkTypeface::kBold;
+ }
+ if (resulting_italic > FC_SLANT_ROMAN && !have_matrix) {
+ styleBits |= SkTypeface::kItalic;
+ }
+
+ return (SkTypeface::Style)styleBits;
+}
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define kMaxFontFamilyLength 2048
+
+SkFontConfigInterfaceDirect::SkFontConfigInterfaceDirect() {
+ SkAutoMutexAcquire ac(mutex_);
+
+ FcInit();
+
+ SkDEBUGCODE(fontconfiginterface_unittest();)
+}
+
+SkFontConfigInterfaceDirect::~SkFontConfigInterfaceDirect() {
+}
+
+bool SkFontConfigInterfaceDirect::matchFamilyName(const char familyName[],
+ SkTypeface::Style style,
+ FontIdentity* outIdentity,
+ SkString* outFamilyName,
+ SkTypeface::Style* outStyle) {
+ std::string familyStr(familyName ? familyName : "");
+ if (familyStr.length() > kMaxFontFamilyLength) {
+ return false;
+ }
+
+ SkAutoMutexAcquire ac(mutex_);
+
+ FcPattern* pattern = FcPatternCreate();
+
+ if (familyName) {
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
+ }
+ FcPatternAddInteger(pattern, FC_WEIGHT,
+ (style & SkTypeface::kBold) ? FC_WEIGHT_BOLD
+ : FC_WEIGHT_NORMAL);
+ FcPatternAddInteger(pattern, FC_SLANT,
+ (style & SkTypeface::kItalic) ? FC_SLANT_ITALIC
+ : FC_SLANT_ROMAN);
+ FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
+
+ 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().
+
+ const char* post_config_family = get_name(pattern, FC_FAMILY);
+ if (!post_config_family) {
+ // we can just continue with an empty name, e.g. default font
+ post_config_family = "";
+ }
+
+ FcResult result;
+ FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
+ if (!font_set) {
+ FcPatternDestroy(pattern);
+ return false;
+ }
+
+ FcPattern* match = MatchFont(font_set, post_config_family, familyStr);
+ if (!match) {
+ FcPatternDestroy(pattern);
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ FcPatternDestroy(pattern);
+
+ // From here out we just extract our results from 'match'
+
+ post_config_family = get_name(match, FC_FAMILY);
+ if (!post_config_family) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ const char* c_filename = get_name(match, FC_FILE);
+ if (!c_filename) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ int face_index;
+ if (FcPatternGetInteger(match, FC_INDEX, 0, &face_index) != FcResultMatch) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ FcFontSetDestroy(font_set);
+
+ if (outIdentity) {
+ outIdentity->fTTCIndex = face_index;
+ outIdentity->fString.set(c_filename);
+ }
+ if (outFamilyName) {
+ outFamilyName->set(post_config_family);
+ }
+ if (outStyle) {
+ *outStyle = GetFontStyle(match);
+ }
+ return true;
+}
+
+SkStream* SkFontConfigInterfaceDirect::openStream(const FontIdentity& identity) {
+ return SkStream::NewFromFile(identity.fString.c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool find_name(const SkTDArray<const char*>& list, const char* str) {
+ int count = list.count();
+ for (int i = 0; i < count; ++i) {
+ if (!strcmp(list[i], str)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+SkDataTable* SkFontConfigInterfaceDirect::getFamilyNames() {
+ SkAutoMutexAcquire ac(mutex_);
+
+ FcPattern* pat = FcPatternCreate();
+ SkAutoTCallVProc<FcPattern, FcPatternDestroy> autoDestroyPat(pat);
+ if (NULL == pat) {
+ return NULL;
+ }
+
+ FcObjectSet* os = FcObjectSetBuild(FC_FAMILY, (char *)0);
+ SkAutoTCallVProc<FcObjectSet, FcObjectSetDestroy> autoDestroyOs(os);
+ if (NULL == os) {
+ return NULL;
+ }
+
+ FcFontSet* fs = FcFontList(NULL, pat, os);
+ SkAutoTCallVProc<FcFontSet, FcFontSetDestroy> autoDestroyFs(fs);
+ if (NULL == fs) {
+ return NULL;
+ }
+
+ SkTDArray<const char*> names;
+ SkTDArray<size_t> sizes;
+ for (int i = 0; i < fs->nfont; ++i) {
+ FcPattern* match = fs->fonts[i];
+ const char* famName = get_name(match, FC_FAMILY);
+ if (famName && !find_name(names, famName)) {
+ *names.append() = famName;
+ *sizes.append() = strlen(famName) + 1;
+ }
+ }
+
+ return SkDataTable::NewCopyArrays((const void*const*)names.begin(),
+ sizes.begin(), names.count());
+}
+
+bool SkFontConfigInterfaceDirect::matchFamilySet(const char inFamilyName[],
+ SkString* outFamilyName,
+ SkTArray<FontIdentity>* ids) {
+ SkAutoMutexAcquire ac(mutex_);
+
+#if 0
+ std::string familyStr(familyName ? familyName : "");
+ if (familyStr.length() > kMaxFontFamilyLength) {
+ return false;
+ }
+
+ SkAutoMutexAcquire ac(mutex_);
+
+ FcPattern* pattern = FcPatternCreate();
+
+ if (familyName) {
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
+ }
+ FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
+
+ 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().
+
+ const char* post_config_family = get_name(pattern, FC_FAMILY);
+
+ FcResult result;
+ FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
+ if (!font_set) {
+ FcPatternDestroy(pattern);
+ return false;
+ }
+
+ FcPattern* match = MatchFont(font_set, post_config_family, familyStr);
+ if (!match) {
+ FcPatternDestroy(pattern);
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ FcPatternDestroy(pattern);
+
+ // From here out we just extract our results from 'match'
+
+ if (FcPatternGetString(match, FC_FAMILY, 0, &post_config_family) != FcResultMatch) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ FcChar8* c_filename;
+ if (FcPatternGetString(match, FC_FILE, 0, &c_filename) != FcResultMatch) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ int face_index;
+ if (FcPatternGetInteger(match, FC_INDEX, 0, &face_index) != FcResultMatch) {
+ FcFontSetDestroy(font_set);
+ return false;
+ }
+
+ FcFontSetDestroy(font_set);
+
+ if (outIdentity) {
+ outIdentity->fTTCIndex = face_index;
+ outIdentity->fString.set((const char*)c_filename);
+ }
+ if (outFamilyName) {
+ outFamilyName->set((const char*)post_config_family);
+ }
+ if (outStyle) {
+ *outStyle = GetFontStyle(match);
+ }
+ return true;
+
+////////////////////
+
+ int count;
+ FcPattern** match = MatchFont(font_set, post_config_family, &count);
+ if (!match) {
+ FcPatternDestroy(pattern);
+ FcFontSetDestroy(font_set);
+ return NULL;
+ }
+
+ FcPatternDestroy(pattern);
+
+ SkTDArray<FcPattern*> trimmedMatches;
+ for (int i = 0; i < count; ++i) {
+ const char* justName = find_just_name(get_name(match[i], FC_FILE));
+ if (!is_lower(*justName)) {
+ *trimmedMatches.append() = match[i];
+ }
+ }
+
+ SkFontStyleSet_FC* sset = SkNEW_ARGS(SkFontStyleSet_FC,
+ (trimmedMatches.begin(),
+ trimmedMatches.count()));
+#endif
+ return false;
+}
diff --git a/ports/SkFontConfigParser_android.cpp b/ports/SkFontConfigParser_android.cpp
new file mode 100644
index 00000000..e8692e91
--- /dev/null
+++ b/ports/SkFontConfigParser_android.cpp
@@ -0,0 +1,318 @@
+/*
+ * 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 "SkFontConfigParser_android.h"
+#include "SkTDArray.h"
+#include "SkTypeface.h"
+
+#include <expat.h>
+#include <sys/system_properties.h>
+
+#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
+#define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
+#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
+
+// These defines are used to determine the kind of tag that we're currently
+// populating with data. We only care about the sibling tags nameset and fileset
+// for now.
+#define NO_TAG 0
+#define NAMESET_TAG 1
+#define FILESET_TAG 2
+
+/**
+ * The FamilyData structure is passed around by the parser so that each handler
+ * can read these variables that are relevant to the current parsing.
+ */
+struct FamilyData {
+ FamilyData(XML_Parser *parserRef, SkTDArray<FontFamily*> &familiesRef) :
+ parser(parserRef),
+ families(familiesRef),
+ currentFamily(NULL),
+ currentFontInfo(NULL),
+ currentTag(NO_TAG) {};
+
+ XML_Parser *parser; // The expat parser doing the work
+ SkTDArray<FontFamily*> &families; // The array that each family is put into as it is parsed
+ FontFamily *currentFamily; // The current family being created
+ FontFileInfo *currentFontInfo; // The current fontInfo being created
+ int currentTag; // A flag to indicate whether we're in nameset/fileset tags
+};
+
+/**
+ * Handler for arbitrary text. This is used to parse the text inside each name
+ * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays.
+ */
+static void textHandler(void *data, const char *s, int len) {
+ FamilyData *familyData = (FamilyData*) data;
+ // Make sure we're in the right state to store this name information
+ if (familyData->currentFamily &&
+ (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) {
+ // Malloc new buffer to store the string
+ char *buff;
+ buff = (char*) malloc((len + 1) * sizeof(char));
+ strncpy(buff, s, len);
+ buff[len] = '\0';
+ switch (familyData->currentTag) {
+ case NAMESET_TAG:
+ *(familyData->currentFamily->fNames.append()) = buff;
+ break;
+ case FILESET_TAG:
+ if (familyData->currentFontInfo) {
+ familyData->currentFontInfo->fFileName = buff;
+ }
+ break;
+ default:
+ // Noop - don't care about any text that's not in the Fonts or Names list
+ break;
+ }
+ }
+}
+
+/**
+ * Handler for font files. This processes the attributes for language and
+ * variants then lets textHandler handle the actual file name
+ */
+static void fontFileElementHandler(FamilyData *familyData, const char **attributes) {
+ FontFileInfo* newFileInfo = new FontFileInfo();
+ if (attributes) {
+ int currentAttributeIndex = 0;
+ while (attributes[currentAttributeIndex]) {
+ const char* attributeName = attributes[currentAttributeIndex];
+ const char* attributeValue = attributes[currentAttributeIndex+1];
+ int nameLength = strlen(attributeName);
+ int valueLength = strlen(attributeValue);
+ if (strncmp(attributeName, "variant", nameLength) == 0) {
+ if (strncmp(attributeValue, "elegant", valueLength) == 0) {
+ newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kElegant_Variant);
+ } else if (strncmp(attributeValue, "compact", valueLength) == 0) {
+ newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kCompact_Variant);
+ }
+ } else if (strncmp(attributeName, "lang", nameLength) == 0) {
+ newFileInfo->fPaintOptions.setLanguage(attributeValue);
+ }
+ //each element is a pair of attributeName/attributeValue string pairs
+ currentAttributeIndex += 2;
+ }
+ }
+ *(familyData->currentFamily->fFontFiles.append()) = newFileInfo;
+ familyData->currentFontInfo = newFileInfo;
+ XML_SetCharacterDataHandler(*familyData->parser, textHandler);
+}
+
+/**
+ * Handler for the start of a tag. The only tags we expect are family, nameset,
+ * fileset, name, and file.
+ */
+static void startElementHandler(void *data, const char *tag, const char **atts) {
+ FamilyData *familyData = (FamilyData*) data;
+ int len = strlen(tag);
+ if (strncmp(tag, "family", len)== 0) {
+ familyData->currentFamily = new FontFamily();
+ familyData->currentFamily->order = -1;
+ // The Family tag has an optional "order" attribute with an integer value >= 0
+ // If this attribute does not exist, the default value is -1
+ for (int i = 0; atts[i] != NULL; i += 2) {
+ const char* valueString = atts[i+1];
+ int value;
+ int len = sscanf(valueString, "%d", &value);
+ if (len > 0) {
+ familyData->currentFamily->order = value;
+ }
+ }
+ } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
+ familyData->currentTag = NAMESET_TAG;
+ } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
+ familyData->currentTag = FILESET_TAG;
+ } else if (strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) {
+ // If it's a Name, parse the text inside
+ XML_SetCharacterDataHandler(*familyData->parser, textHandler);
+ } else if (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) {
+ // If it's a file, parse the attributes, then parse the text inside
+ fontFileElementHandler(familyData, atts);
+ }
+}
+
+/**
+ * Handler for the end of tags. We only care about family, nameset, fileset,
+ * name, and file.
+ */
+static void endElementHandler(void *data, const char *tag) {
+ FamilyData *familyData = (FamilyData*) data;
+ int len = strlen(tag);
+ if (strncmp(tag, "family", len)== 0) {
+ // Done parsing a Family - store the created currentFamily in the families array
+ *familyData->families.append() = familyData->currentFamily;
+ familyData->currentFamily = NULL;
+ } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
+ familyData->currentTag = NO_TAG;
+ } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
+ familyData->currentTag = NO_TAG;
+ } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) ||
+ (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) {
+ // Disable the arbitrary text handler installed to load Name data
+ XML_SetCharacterDataHandler(*familyData->parser, NULL);
+ }
+}
+
+/**
+ * This function parses the given filename and stores the results in the given
+ * families array.
+ */
+static void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) {
+
+ FILE* file = NULL;
+
+#if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
+ // if we are using a version of Android prior to Android 4.2 (JellyBean MR1
+ // at API Level 17) then we need to look for files with a different suffix.
+ char sdkVersion[PROP_VALUE_MAX];
+ __system_property_get("ro.build.version.sdk", sdkVersion);
+ const int sdkVersionInt = atoi(sdkVersion);
+
+ if (0 != *sdkVersion && sdkVersionInt < 17) {
+ SkString basename;
+ SkString updatedFilename;
+ SkString locale = SkFontConfigParser::GetLocale();
+
+ basename.set(filename);
+ // Remove the .xml suffix. We'll add it back in a moment.
+ if (basename.endsWith(".xml")) {
+ basename.resize(basename.size()-4);
+ }
+ // Try first with language and region
+ updatedFilename.printf("%s-%s.xml", basename.c_str(), locale.c_str());
+ file = fopen(updatedFilename.c_str(), "r");
+ if (!file) {
+ // If not found, try next with just language
+ updatedFilename.printf("%s-%.2s.xml", basename.c_str(), locale.c_str());
+ file = fopen(updatedFilename.c_str(), "r");
+ }
+ }
+#endif
+
+ if (NULL == file) {
+ file = fopen(filename, "r");
+ }
+
+ // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
+ // are optional - failure here is okay because one of these optional files may not exist.
+ if (NULL == file) {
+ return;
+ }
+
+ XML_Parser parser = XML_ParserCreate(NULL);
+ FamilyData *familyData = new FamilyData(&parser, families);
+ XML_SetUserData(parser, familyData);
+ XML_SetElementHandler(parser, startElementHandler, endElementHandler);
+
+ char buffer[512];
+ bool done = false;
+ while (!done) {
+ fgets(buffer, sizeof(buffer), file);
+ int len = strlen(buffer);
+ if (feof(file) != 0) {
+ done = true;
+ }
+ XML_Parse(parser, buffer, len, done);
+ }
+ fclose(file);
+}
+
+static void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
+ parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
+}
+
+static void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
+ SkTDArray<FontFamily*> vendorFonts;
+ parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
+ parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
+
+ // This loop inserts the vendor fallback fonts in the correct order in the
+ // overall fallbacks list.
+ int currentOrder = -1;
+ for (int i = 0; i < vendorFonts.count(); ++i) {
+ FontFamily* family = vendorFonts[i];
+ int order = family->order;
+ if (order < 0) {
+ if (currentOrder < 0) {
+ // Default case - just add it to the end of the fallback list
+ *fallbackFonts.append() = family;
+ } else {
+ // no order specified on this font, but we're incrementing the order
+ // based on an earlier order insertion request
+ *fallbackFonts.insert(currentOrder++) = family;
+ }
+ } else {
+ // Add the font into the fallback list in the specified order. Set
+ // currentOrder for correct placement of other fonts in the vendor list.
+ *fallbackFonts.insert(order) = family;
+ currentOrder = order + 1;
+ }
+ }
+}
+
+/**
+ * Loads data on font families from various expected configuration files. The
+ * resulting data is returned in the given fontFamilies array.
+ */
+void SkFontConfigParser::GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
+
+ getSystemFontFamilies(fontFamilies);
+
+ // Append all the fallback fonts to system fonts
+ SkTDArray<FontFamily*> fallbackFonts;
+ getFallbackFontFamilies(fallbackFonts);
+ for (int i = 0; i < fallbackFonts.count(); ++i) {
+ fallbackFonts[i]->fIsFallbackFont = true;
+ *fontFamilies.append() = fallbackFonts[i];
+ }
+}
+
+void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
+ const char* testMainConfigFile,
+ const char* testFallbackConfigFile) {
+ parseConfigFile(testMainConfigFile, fontFamilies);
+
+ SkTDArray<FontFamily*> fallbackFonts;
+ parseConfigFile(testFallbackConfigFile, fallbackFonts);
+
+ // Append all fallback fonts to system fonts
+ for (int i = 0; i < fallbackFonts.count(); ++i) {
+ fallbackFonts[i]->fIsFallbackFont = true;
+ *fontFamilies.append() = fallbackFonts[i];
+ }
+}
+
+/**
+ * Read the persistent locale.
+ */
+SkString SkFontConfigParser::GetLocale()
+{
+ char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX];
+ __system_property_get("persist.sys.language", propLang);
+ __system_property_get("persist.sys.country", propRegn);
+
+ if (*propLang == 0 && *propRegn == 0) {
+ /* Set to ro properties, default is en_US */
+ __system_property_get("ro.product.locale.language", propLang);
+ __system_property_get("ro.product.locale.region", propRegn);
+ if (*propLang == 0 && *propRegn == 0) {
+ strcpy(propLang, "en");
+ strcpy(propRegn, "US");
+ }
+ }
+
+ SkString locale(6);
+ char* localeCStr = locale.writable_str();
+
+ strncpy(localeCStr, propLang, 2);
+ localeCStr[2] = '-';
+ strncpy(&localeCStr[3], propRegn, 2);
+ localeCStr[5] = '\0';
+
+ return locale;
+}
diff --git a/ports/SkFontConfigParser_android.h b/ports/SkFontConfigParser_android.h
new file mode 100644
index 00000000..fd64496f
--- /dev/null
+++ b/ports/SkFontConfigParser_android.h
@@ -0,0 +1,61 @@
+/*
+ * 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 SKFONTCONFIGPARSER_ANDROID_H_
+#define SKFONTCONFIGPARSER_ANDROID_H_
+
+#include "SkTypes.h"
+
+#include "SkPaintOptionsAndroid.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+
+struct FontFileInfo {
+ FontFileInfo() : fFileName(NULL) {}
+
+ const char* fFileName;
+ SkPaintOptionsAndroid fPaintOptions;
+};
+
+/**
+ * The FontFamily data structure is created during parsing and handed back to
+ * Skia to fold into its representation of font families. fNames is the list of
+ * font names that alias to a font family. fontFileArray is the list of information
+ * about each file. Order is the priority order for the font. This is
+ * used internally to determine the order in which to place fallback fonts as
+ * they are read from the configuration files.
+ */
+struct FontFamily {
+ FontFamily() : fIsFallbackFont(false), order(-1) {}
+
+ SkTDArray<const char*> fNames;
+ SkTDArray<FontFileInfo*> fFontFiles;
+ bool fIsFallbackFont;
+ int order; // only used internally by SkFontConfigParser
+};
+
+namespace SkFontConfigParser {
+
+/**
+ * Parses all system font configuration files and returns the results in an
+ * array of FontFamily structures.
+ */
+void GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies);
+
+/**
+ * Parses all test font configuration files and returns the results in an
+ * array of FontFamily structures.
+ */
+void GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
+ const char* testMainConfigFile,
+ const char* testFallbackConfigFile);
+
+SkString GetLocale();
+
+} // SkFontConfigParser namespace
+
+#endif /* SKFONTCONFIGPARSER_ANDROID_H_ */
diff --git a/ports/SkFontConfigTypeface.h b/ports/SkFontConfigTypeface.h
new file mode 100644
index 00000000..e1b6c182
--- /dev/null
+++ b/ports/SkFontConfigTypeface.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFontConfigInterface.h"
+#include "SkFontHost_FreeType_common.h"
+#include "SkStream.h"
+#include "SkTypefaceCache.h"
+
+class SkFontDescriptor;
+
+class FontConfigTypeface : public SkTypeface_FreeType {
+ SkFontConfigInterface::FontIdentity fIdentity;
+ SkString fFamilyName;
+ SkStream* fLocalStream;
+
+public:
+ FontConfigTypeface(Style style,
+ const SkFontConfigInterface::FontIdentity& fi,
+ const SkString& familyName)
+ : INHERITED(style, SkTypefaceCache::NewFontID(), false)
+ , fIdentity(fi)
+ , fFamilyName(familyName)
+ , fLocalStream(NULL) {}
+
+ FontConfigTypeface(Style style, SkStream* localStream)
+ : INHERITED(style, SkTypefaceCache::NewFontID(), false) {
+ // we default to empty fFamilyName and fIdentity
+ fLocalStream = localStream;
+ SkSafeRef(localStream);
+ }
+
+ virtual ~FontConfigTypeface() {
+ SkSafeUnref(fLocalStream);
+ }
+
+ const SkFontConfigInterface::FontIdentity& getIdentity() const {
+ return fIdentity;
+ }
+
+ const char* getFamilyName() const { return fFamilyName.c_str(); }
+ SkStream* getLocalStream() const { return fLocalStream; }
+
+ bool isFamilyName(const char* name) const {
+ return fFamilyName.equals(name);
+ }
+
+ static SkTypeface* LegacyCreateTypeface(const SkTypeface* family,
+ const char familyName[],
+ SkTypeface::Style);
+
+protected:
+ friend class SkFontHost; // hack until we can make public versions
+
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE;
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style) const SK_OVERRIDE;
+
+private:
+ typedef SkTypeface_FreeType INHERITED;
+};
diff --git a/ports/SkFontHost_FreeType.cpp b/ports/SkFontHost_FreeType.cpp
new file mode 100644
index 00000000..3a8a1ae2
--- /dev/null
+++ b/ports/SkFontHost_FreeType.cpp
@@ -0,0 +1,1535 @@
+
+/*
+ * 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 "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkDescriptor.h"
+#include "SkFDot6.h"
+#include "SkFloatingPoint.h"
+#include "SkFontHost.h"
+#include "SkFontHost_FreeType_common.h"
+#include "SkGlyph.h"
+#include "SkMask.h"
+#include "SkMaskGamma.h"
+#include "SkOTUtils.h"
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkScalerContext.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+
+#if defined(SK_CAN_USE_DLOPEN)
+#include <dlfcn.h>
+#endif
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+#include FT_SIZES_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_TYPE1_TABLES_H
+#include FT_BITMAP_H
+// In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file.
+#include FT_SYNTHESIS_H
+#include FT_XFREE86_H
+#ifdef FT_LCD_FILTER_H
+#include FT_LCD_FILTER_H
+#endif
+
+#ifdef FT_ADVANCES_H
+#include FT_ADVANCES_H
+#endif
+
+#if 0
+// Also include the files by name for build tools which require this.
+#include <freetype/freetype.h>
+#include <freetype/ftoutln.h>
+#include <freetype/ftsizes.h>
+#include <freetype/tttables.h>
+#include <freetype/ftadvanc.h>
+#include <freetype/ftlcdfil.h>
+#include <freetype/ftbitmap.h>
+#include <freetype/ftsynth.h>
+#endif
+
+//#define ENABLE_GLYPH_SPEW // for tracing calls
+//#define DUMP_STRIKE_CREATION
+
+//#define SK_GAMMA_APPLY_TO_A8
+
+using namespace skia_advanced_typeface_metrics_utils;
+
+static bool isLCD(const SkScalerContext::Rec& rec) {
+ switch (rec.fMaskFormat) {
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format:
+ return true;
+ default:
+ return false;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+struct SkFaceRec;
+
+SK_DECLARE_STATIC_MUTEX(gFTMutex);
+static int gFTCount;
+static FT_Library gFTLibrary;
+static SkFaceRec* gFaceRecHead;
+static bool gLCDSupportValid; // true iff |gLCDSupport| has been set.
+static bool gLCDSupport; // true iff LCD is supported by the runtime.
+static int gLCDExtra; // number of extra pixels for filtering.
+
+/////////////////////////////////////////////////////////////////////////
+
+// FT_Library_SetLcdFilterWeights was introduced in FreeType 2.4.0.
+// The following platforms provide FreeType of at least 2.4.0.
+// Ubuntu >= 11.04 (previous deprecated April 2013)
+// Debian >= 6.0 (good)
+// OpenSuse >= 11.4 (previous deprecated January 2012 / Nov 2013 for Evergreen 11.2)
+// Fedora >= 14 (good)
+// Android >= Gingerbread (good)
+typedef FT_Error (*FT_Library_SetLcdFilterWeightsProc)(FT_Library, unsigned char*);
+
+// Caller must lock gFTMutex before calling this function.
+static bool InitFreetype() {
+ FT_Error err = FT_Init_FreeType(&gFTLibrary);
+ if (err) {
+ return false;
+ }
+
+ // Setup LCD filtering. This reduces color fringes for LCD smoothed glyphs.
+#ifdef FT_LCD_FILTER_H
+ // Use default { 0x10, 0x40, 0x70, 0x40, 0x10 }, as it adds up to 0x110, simulating ink spread.
+ // SetLcdFilter must be called before SetLcdFilterWeights.
+ err = FT_Library_SetLcdFilter(gFTLibrary, FT_LCD_FILTER_DEFAULT);
+ if (0 == err) {
+ gLCDSupport = true;
+ gLCDExtra = 2; //Using a filter adds one full pixel to each side.
+
+#ifdef SK_FONTHOST_FREETYPE_USE_NORMAL_LCD_FILTER
+ // This also adds to 0x110 simulating ink spread, but provides better results than default.
+ static unsigned char gGaussianLikeHeavyWeights[] = { 0x1A, 0x43, 0x56, 0x43, 0x1A, };
+
+#if defined(SK_FONTHOST_FREETYPE_RUNTIME_VERSION) && \
+ SK_FONTHOST_FREETYPE_RUNTIME_VERSION > 0x020400
+ err = FT_Library_SetLcdFilterWeights(gFTLibrary, gGaussianLikeHeavyWeights);
+#elif defined(SK_CAN_USE_DLOPEN) && SK_CAN_USE_DLOPEN == 1
+ //The FreeType library is already loaded, so symbols are available in process.
+ void* self = dlopen(NULL, RTLD_LAZY);
+ if (NULL != self) {
+ FT_Library_SetLcdFilterWeightsProc setLcdFilterWeights;
+ //The following cast is non-standard, but safe for POSIX.
+ *reinterpret_cast<void**>(&setLcdFilterWeights) = dlsym(self, "FT_Library_SetLcdFilterWeights");
+ dlclose(self);
+
+ if (NULL != setLcdFilterWeights) {
+ err = setLcdFilterWeights(gFTLibrary, gGaussianLikeHeavyWeights);
+ }
+ }
+#endif
+#endif
+ }
+#else
+ gLCDSupport = false;
+#endif
+ gLCDSupportValid = true;
+
+ 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(SkTypeface*, const SkDescriptor* desc);
+ virtual ~SkScalerContext_FreeType();
+
+ bool success() const {
+ return fFaceRec != NULL &&
+ fFTSize != NULL &&
+ fFace != NULL;
+ }
+
+protected:
+ 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) SK_OVERRIDE;
+ virtual SkUnichar generateGlyphToChar(uint16_t glyph) SK_OVERRIDE;
+
+private:
+ SkFaceRec* fFaceRec;
+ FT_Face fFace; // reference to shared face in gFaceRecHead
+ FT_Size fFTSize; // our own copy
+ SkFixed fScaleX, fScaleY;
+ FT_Matrix fMatrix22;
+ uint32_t fLoadGlyphFlags;
+ bool fDoLinearMetrics;
+ bool fLCDIsVert;
+
+ // Need scalar versions for generateFontMetrics
+ SkVector fScale;
+ SkMatrix fMatrix22Scalar;
+
+ FT_Error setupSize();
+ void getBBoxForCurrentGlyph(SkGlyph* glyph, FT_BBox* bbox,
+ bool snapToPixelBoundary = false);
+ // Caller must lock gFTMutex before calling this function.
+ void updateGlyphIfLCD(SkGlyph* glyph);
+};
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+
+struct SkFaceRec {
+ SkFaceRec* fNext;
+ FT_Face fFace;
+ FT_StreamRec fFTStream;
+ SkStream* fSkStream;
+ uint32_t fRefCnt;
+ uint32_t fFontID;
+
+ // assumes ownership of the stream, will call unref() when its done
+ SkFaceRec(SkStream* strm, uint32_t fontID);
+ ~SkFaceRec() {
+ fSkStream->unref();
+ }
+};
+
+extern "C" {
+ static unsigned long sk_stream_read(FT_Stream stream,
+ unsigned long offset,
+ unsigned char* buffer,
+ unsigned long count ) {
+ SkStream* str = (SkStream*)stream->descriptor.pointer;
+
+ if (count) {
+ if (!str->rewind()) {
+ return 0;
+ } else {
+ unsigned long ret;
+ if (offset) {
+ ret = str->read(NULL, offset);
+ if (ret != offset) {
+ return 0;
+ }
+ }
+ ret = str->read(buffer, count);
+ if (ret != count) {
+ return 0;
+ }
+ count = ret;
+ }
+ }
+ return count;
+ }
+
+ static void sk_stream_close(FT_Stream) {}
+}
+
+SkFaceRec::SkFaceRec(SkStream* strm, uint32_t fontID)
+ : fNext(NULL), fSkStream(strm), fRefCnt(1), fFontID(fontID) {
+// SkDEBUGF(("SkFaceRec: opening %s (%p)\n", key.c_str(), strm));
+
+ sk_bzero(&fFTStream, sizeof(fFTStream));
+ fFTStream.size = fSkStream->getLength();
+ fFTStream.descriptor.pointer = fSkStream;
+ fFTStream.read = sk_stream_read;
+ fFTStream.close = sk_stream_close;
+}
+
+// Will return 0 on failure
+// Caller must lock gFTMutex before calling this function.
+static SkFaceRec* ref_ft_face(const SkTypeface* typeface) {
+ const SkFontID fontID = typeface->uniqueID();
+ SkFaceRec* rec = gFaceRecHead;
+ while (rec) {
+ if (rec->fFontID == fontID) {
+ SkASSERT(rec->fFace);
+ rec->fRefCnt += 1;
+ return rec;
+ }
+ rec = rec->fNext;
+ }
+
+ int face_index;
+ SkStream* strm = typeface->openStream(&face_index);
+ if (NULL == strm) {
+ return NULL;
+ }
+
+ // this passes ownership of strm to the rec
+ rec = SkNEW_ARGS(SkFaceRec, (strm, fontID));
+
+ FT_Open_Args args;
+ memset(&args, 0, sizeof(args));
+ const void* memoryBase = strm->getMemoryBase();
+
+ if (NULL != memoryBase) {
+//printf("mmap(%s)\n", keyString.c_str());
+ args.flags = FT_OPEN_MEMORY;
+ args.memory_base = (const FT_Byte*)memoryBase;
+ args.memory_size = strm->getLength();
+ } else {
+//printf("fopen(%s)\n", keyString.c_str());
+ args.flags = FT_OPEN_STREAM;
+ args.stream = &rec->fFTStream;
+ }
+
+ FT_Error err = FT_Open_Face(gFTLibrary, &args, face_index, &rec->fFace);
+ if (err) { // bad filename, try the default font
+ fprintf(stderr, "ERROR: unable to open font '%x'\n", fontID);
+ SkDELETE(rec);
+ return NULL;
+ } else {
+ SkASSERT(rec->fFace);
+ //fprintf(stderr, "Opened font '%s'\n", filename.c_str());
+ rec->fNext = gFaceRecHead;
+ gFaceRecHead = rec;
+ return rec;
+ }
+}
+
+// Caller must lock gFTMutex before calling this function.
+static void unref_ft_face(FT_Face face) {
+ SkFaceRec* rec = gFaceRecHead;
+ SkFaceRec* prev = NULL;
+ while (rec) {
+ SkFaceRec* next = rec->fNext;
+ if (rec->fFace == face) {
+ if (--rec->fRefCnt == 0) {
+ if (prev) {
+ prev->fNext = next;
+ } else {
+ gFaceRecHead = next;
+ }
+ FT_Done_Face(face);
+ SkDELETE(rec);
+ }
+ return;
+ }
+ prev = rec;
+ rec = next;
+ }
+ SkDEBUGFAIL("shouldn't get here, face not in list");
+}
+
+class AutoFTAccess {
+public:
+ AutoFTAccess(const SkTypeface* tf) : fRec(NULL), fFace(NULL) {
+ gFTMutex.acquire();
+ if (1 == ++gFTCount) {
+ if (!InitFreetype()) {
+ sk_throw();
+ }
+ }
+ fRec = ref_ft_face(tf);
+ if (fRec) {
+ fFace = fRec->fFace;
+ }
+ }
+
+ ~AutoFTAccess() {
+ if (fFace) {
+ unref_ft_face(fFace);
+ }
+ if (0 == --gFTCount) {
+ FT_Done_FreeType(gFTLibrary);
+ }
+ gFTMutex.release();
+ }
+
+ SkFaceRec* rec() { return fRec; }
+ FT_Face face() { return fFace; }
+
+private:
+ SkFaceRec* fRec;
+ FT_Face fFace;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+// Work around for old versions of freetype.
+static FT_Error getAdvances(FT_Face face, FT_UInt start, FT_UInt count,
+ FT_Int32 loadFlags, FT_Fixed* advances) {
+#ifdef FT_ADVANCES_H
+ return FT_Get_Advances(face, start, count, loadFlags, advances);
+#else
+ if (!face || start >= face->num_glyphs ||
+ start + count > face->num_glyphs || loadFlags != FT_LOAD_NO_SCALE) {
+ return 6; // "Invalid argument."
+ }
+ if (count == 0)
+ return 0;
+
+ for (int i = 0; i < count; i++) {
+ FT_Error err = FT_Load_Glyph(face, start + i, FT_LOAD_NO_SCALE);
+ if (err)
+ return err;
+ advances[i] = face->glyph->advance.x;
+ }
+
+ return 0;
+#endif
+}
+
+static bool canEmbed(FT_Face face) {
+#ifdef FT_FSTYPE_RESTRICTED_LICENSE_EMBEDDING
+ FT_UShort fsType = FT_Get_FSType_Flags(face);
+ return (fsType & (FT_FSTYPE_RESTRICTED_LICENSE_EMBEDDING |
+ FT_FSTYPE_BITMAP_EMBEDDING_ONLY)) == 0;
+#else
+ // No embedding is 0x2 and bitmap embedding only is 0x200.
+ TT_OS2* os2_table;
+ if ((os2_table = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2)) != NULL) {
+ return (os2_table->fsType & 0x202) == 0;
+ }
+ return false; // We tried, fail safe.
+#endif
+}
+
+static bool GetLetterCBox(FT_Face face, char letter, FT_BBox* bbox) {
+ const FT_UInt glyph_id = FT_Get_Char_Index(face, letter);
+ if (!glyph_id)
+ return false;
+ FT_Load_Glyph(face, glyph_id, FT_LOAD_NO_SCALE);
+ FT_Outline_Get_CBox(&face->glyph->outline, bbox);
+ return true;
+}
+
+static bool getWidthAdvance(FT_Face face, int gId, int16_t* data) {
+ FT_Fixed advance = 0;
+ if (getAdvances(face, gId, 1, FT_LOAD_NO_SCALE, &advance)) {
+ return false;
+ }
+ SkASSERT(data);
+ *data = advance;
+ return true;
+}
+
+static void populate_glyph_to_unicode(FT_Face& face,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ // Check and see if we have Unicode cmaps.
+ for (int i = 0; i < face->num_charmaps; ++i) {
+ // CMaps known to support Unicode:
+ // Platform ID Encoding ID Name
+ // ----------- ----------- -----------------------------------
+ // 0 0,1 Apple Unicode
+ // 0 3 Apple Unicode 2.0 (preferred)
+ // 3 1 Microsoft Unicode UCS-2
+ // 3 10 Microsoft Unicode UCS-4 (preferred)
+ //
+ // See Apple TrueType Reference Manual
+ // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6cmap.html
+ // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html#ID
+ // Microsoft OpenType Specification
+ // http://www.microsoft.com/typography/otspec/cmap.htm
+
+ FT_UShort platformId = face->charmaps[i]->platform_id;
+ FT_UShort encodingId = face->charmaps[i]->encoding_id;
+
+ if (platformId != 0 && platformId != 3) {
+ continue;
+ }
+ if (platformId == 3 && encodingId != 1 && encodingId != 10) {
+ continue;
+ }
+ bool preferredMap = ((platformId == 3 && encodingId == 10) ||
+ (platformId == 0 && encodingId == 3));
+
+ FT_Set_Charmap(face, face->charmaps[i]);
+ if (glyphToUnicode->isEmpty()) {
+ glyphToUnicode->setCount(face->num_glyphs);
+ memset(glyphToUnicode->begin(), 0,
+ sizeof(SkUnichar) * face->num_glyphs);
+ }
+
+ // Iterate through each cmap entry.
+ FT_UInt glyphIndex;
+ for (SkUnichar charCode = FT_Get_First_Char(face, &glyphIndex);
+ glyphIndex != 0;
+ charCode = FT_Get_Next_Char(face, charCode, &glyphIndex)) {
+ if (charCode &&
+ ((*glyphToUnicode)[glyphIndex] == 0 || preferredMap)) {
+ (*glyphToUnicode)[glyphIndex] = charCode;
+ }
+ }
+ }
+}
+
+SkAdvancedTypefaceMetrics* SkTypeface_FreeType::onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+#if defined(SK_BUILD_FOR_MAC)
+ return NULL;
+#else
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+ if (!face) {
+ return NULL;
+ }
+
+ SkAdvancedTypefaceMetrics* info = new SkAdvancedTypefaceMetrics;
+ info->fFontName.set(FT_Get_Postscript_Name(face));
+ info->fMultiMaster = FT_HAS_MULTIPLE_MASTERS(face);
+ info->fLastGlyphID = face->num_glyphs - 1;
+ info->fEmSize = 1000;
+
+ bool cid = false;
+ const char* fontType = FT_Get_X11_Font_Format(face);
+ if (strcmp(fontType, "Type 1") == 0) {
+ info->fType = SkAdvancedTypefaceMetrics::kType1_Font;
+ } else if (strcmp(fontType, "CID Type 1") == 0) {
+ info->fType = SkAdvancedTypefaceMetrics::kType1CID_Font;
+ cid = true;
+ } else if (strcmp(fontType, "CFF") == 0) {
+ info->fType = SkAdvancedTypefaceMetrics::kCFF_Font;
+ } else if (strcmp(fontType, "TrueType") == 0) {
+ info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font;
+ cid = true;
+ TT_Header* ttHeader;
+ if ((ttHeader = (TT_Header*)FT_Get_Sfnt_Table(face,
+ ft_sfnt_head)) != NULL) {
+ info->fEmSize = ttHeader->Units_Per_EM;
+ }
+ }
+
+ info->fStyle = 0;
+ if (FT_IS_FIXED_WIDTH(face))
+ info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style;
+ if (face->style_flags & FT_STYLE_FLAG_ITALIC)
+ info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
+
+ PS_FontInfoRec ps_info;
+ TT_Postscript* tt_info;
+ if (FT_Get_PS_Font_Info(face, &ps_info) == 0) {
+ info->fItalicAngle = ps_info.italic_angle;
+ } else if ((tt_info =
+ (TT_Postscript*)FT_Get_Sfnt_Table(face,
+ ft_sfnt_post)) != NULL) {
+ info->fItalicAngle = SkFixedToScalar(tt_info->italicAngle);
+ } else {
+ info->fItalicAngle = 0;
+ }
+
+ info->fAscent = face->ascender;
+ info->fDescent = face->descender;
+
+ // Figure out a good guess for StemV - Min width of i, I, !, 1.
+ // This probably isn't very good with an italic font.
+ int16_t min_width = SHRT_MAX;
+ info->fStemV = 0;
+ char stem_chars[] = {'i', 'I', '!', '1'};
+ for (size_t i = 0; i < SK_ARRAY_COUNT(stem_chars); i++) {
+ FT_BBox bbox;
+ if (GetLetterCBox(face, stem_chars[i], &bbox)) {
+ int16_t width = bbox.xMax - bbox.xMin;
+ if (width > 0 && width < min_width) {
+ min_width = width;
+ info->fStemV = min_width;
+ }
+ }
+ }
+
+ TT_PCLT* pclt_info;
+ TT_OS2* os2_table;
+ if ((pclt_info = (TT_PCLT*)FT_Get_Sfnt_Table(face, ft_sfnt_pclt)) != NULL) {
+ info->fCapHeight = pclt_info->CapHeight;
+ uint8_t serif_style = pclt_info->SerifStyle & 0x3F;
+ if (serif_style >= 2 && serif_style <= 6)
+ info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
+ else if (serif_style >= 9 && serif_style <= 12)
+ info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
+ } else if ((os2_table =
+ (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2)) != NULL) {
+ info->fCapHeight = os2_table->sCapHeight;
+ } else {
+ // Figure out a good guess for CapHeight: average the height of M and X.
+ FT_BBox m_bbox, x_bbox;
+ bool got_m, got_x;
+ got_m = GetLetterCBox(face, 'M', &m_bbox);
+ got_x = GetLetterCBox(face, 'X', &x_bbox);
+ if (got_m && got_x) {
+ info->fCapHeight = (m_bbox.yMax - m_bbox.yMin + x_bbox.yMax -
+ x_bbox.yMin) / 2;
+ } else if (got_m && !got_x) {
+ info->fCapHeight = m_bbox.yMax - m_bbox.yMin;
+ } else if (!got_m && got_x) {
+ info->fCapHeight = x_bbox.yMax - x_bbox.yMin;
+ }
+ }
+
+ info->fBBox = SkIRect::MakeLTRB(face->bbox.xMin, face->bbox.yMax,
+ face->bbox.xMax, face->bbox.yMin);
+
+ if (!canEmbed(face) || !FT_IS_SCALABLE(face) ||
+ info->fType == SkAdvancedTypefaceMetrics::kOther_Font) {
+ perGlyphInfo = SkAdvancedTypefaceMetrics::kNo_PerGlyphInfo;
+ }
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) {
+ if (FT_IS_FIXED_WIDTH(face)) {
+ appendRange(&info->fGlyphWidths, 0);
+ int16_t advance = face->max_advance_width;
+ info->fGlyphWidths->fAdvance.append(1, &advance);
+ finishRange(info->fGlyphWidths.get(), 0,
+ SkAdvancedTypefaceMetrics::WidthRange::kDefault);
+ } else if (!cid) {
+ appendRange(&info->fGlyphWidths, 0);
+ // So as to not blow out the stack, get advances in batches.
+ for (int gID = 0; gID < face->num_glyphs; gID += 128) {
+ FT_Fixed advances[128];
+ int advanceCount = 128;
+ if (gID + advanceCount > face->num_glyphs)
+ advanceCount = face->num_glyphs - gID + 1;
+ getAdvances(face, gID, advanceCount, FT_LOAD_NO_SCALE,
+ advances);
+ for (int i = 0; i < advanceCount; i++) {
+ int16_t advance = advances[i];
+ info->fGlyphWidths->fAdvance.append(1, &advance);
+ }
+ }
+ finishRange(info->fGlyphWidths.get(), face->num_glyphs - 1,
+ SkAdvancedTypefaceMetrics::WidthRange::kRange);
+ } else {
+ info->fGlyphWidths.reset(
+ getAdvanceData(face,
+ face->num_glyphs,
+ glyphIDs,
+ glyphIDsCount,
+ &getWidthAdvance));
+ }
+ }
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kVAdvance_PerGlyphInfo &&
+ FT_HAS_VERTICAL(face)) {
+ SkASSERT(false); // Not implemented yet.
+ }
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kGlyphNames_PerGlyphInfo &&
+ info->fType == SkAdvancedTypefaceMetrics::kType1_Font) {
+ // Postscript fonts may contain more than 255 glyphs, so we end up
+ // using multiple font descriptions with a glyph ordering. Record
+ // the name of each glyph.
+ info->fGlyphNames.reset(
+ new SkAutoTArray<SkString>(face->num_glyphs));
+ for (int gID = 0; gID < face->num_glyphs; gID++) {
+ char glyphName[128]; // PS limit for names is 127 bytes.
+ FT_Get_Glyph_Name(face, gID, glyphName, 128);
+ info->fGlyphNames->get()[gID].set(glyphName);
+ }
+ }
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo &&
+ info->fType != SkAdvancedTypefaceMetrics::kType1_Font &&
+ face->num_charmaps) {
+ populate_glyph_to_unicode(face, &(info->fGlyphToUnicode));
+ }
+
+ if (!canEmbed(face))
+ info->fType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
+
+ return info;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#define BLACK_LUMINANCE_LIMIT 0x40
+#define WHITE_LUMINANCE_LIMIT 0xA0
+
+static bool bothZero(SkScalar a, SkScalar b) {
+ return 0 == a && 0 == b;
+}
+
+// returns false if there is any non-90-rotation or skew
+static bool isAxisAligned(const SkScalerContext::Rec& rec) {
+ return 0 == rec.fPreSkewX &&
+ (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
+ bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
+}
+
+SkScalerContext* SkTypeface_FreeType::onCreateScalerContext(
+ const SkDescriptor* desc) const {
+ SkScalerContext_FreeType* c = SkNEW_ARGS(SkScalerContext_FreeType,
+ (const_cast<SkTypeface_FreeType*>(this),
+ desc));
+ if (!c->success()) {
+ SkDELETE(c);
+ c = NULL;
+ }
+ return c;
+}
+
+void SkTypeface_FreeType::onFilterRec(SkScalerContextRec* rec) const {
+ //BOGUS: http://code.google.com/p/chromium/issues/detail?id=121119
+ //Cap the requested size as larger sizes give bogus values.
+ //Remove when http://code.google.com/p/skia/issues/detail?id=554 is fixed.
+ if (rec->fTextSize > SkIntToScalar(1 << 14)) {
+ rec->fTextSize = SkIntToScalar(1 << 14);
+ }
+
+ 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;
+ }
+
+ SkPaint::Hinting h = rec->getHinting();
+ if (SkPaint::kFull_Hinting == h && !isLCD(*rec)) {
+ // collapse full->normal hinting if we're not doing LCD
+ h = SkPaint::kNormal_Hinting;
+ }
+ if ((rec->fFlags & SkScalerContext::kSubpixelPositioning_Flag)) {
+ if (SkPaint::kNo_Hinting != h) {
+ h = SkPaint::kSlight_Hinting;
+ }
+ }
+
+ // rotated text looks bad with hinting, so we disable it as needed
+ if (!isAxisAligned(*rec)) {
+ h = SkPaint::kNo_Hinting;
+ }
+ rec->setHinting(h);
+
+#ifndef SK_GAMMA_APPLY_TO_A8
+ if (!isLCD(*rec)) {
+ rec->ignorePreBlend();
+ }
+#endif
+}
+
+int SkTypeface_FreeType::onGetUPEM() const {
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+ return face ? face->units_per_EM : 0;
+}
+
+SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface,
+ const SkDescriptor* desc)
+ : SkScalerContext_FreeType_Base(typeface, desc) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ if (gFTCount == 0) {
+ if (!InitFreetype()) {
+ sk_throw();
+ }
+ }
+ ++gFTCount;
+
+ // load the font file
+ fFTSize = NULL;
+ fFace = NULL;
+ fFaceRec = ref_ft_face(typeface);
+ if (NULL == fFaceRec) {
+ return;
+ }
+ fFace = fFaceRec->fFace;
+
+ // compute our factors from the record
+
+ SkMatrix m;
+
+ fRec.getSingleMatrix(&m);
+
+#ifdef DUMP_STRIKE_CREATION
+ SkString keyString;
+ SkFontHost::GetDescriptorKeyString(desc, &keyString);
+ printf("========== strike [%g %g %g] [%g %g %g %g] hints %d format %d %s\n", SkScalarToFloat(fRec.fTextSize),
+ SkScalarToFloat(fRec.fPreScaleX), SkScalarToFloat(fRec.fPreSkewX),
+ SkScalarToFloat(fRec.fPost2x2[0][0]), SkScalarToFloat(fRec.fPost2x2[0][1]),
+ SkScalarToFloat(fRec.fPost2x2[1][0]), SkScalarToFloat(fRec.fPost2x2[1][1]),
+ fRec.getHinting(), fRec.fMaskFormat, keyString.c_str());
+#endif
+
+ // now compute our scale factors
+ SkScalar sx = m.getScaleX();
+ SkScalar sy = m.getScaleY();
+
+ fMatrix22Scalar.reset();
+
+ if (m.getSkewX() || m.getSkewY() || sx < 0 || sy < 0) {
+ // sort of give up on hinting
+ sx = SkMaxScalar(SkScalarAbs(sx), SkScalarAbs(m.getSkewX()));
+ sy = SkMaxScalar(SkScalarAbs(m.getSkewY()), SkScalarAbs(sy));
+ sx = sy = SkScalarAve(sx, sy);
+
+ SkScalar inv = SkScalarInvert(sx);
+
+ // flip the skew elements to go from our Y-down system to FreeType's
+ fMatrix22.xx = SkScalarToFixed(SkScalarMul(m.getScaleX(), inv));
+ fMatrix22.xy = -SkScalarToFixed(SkScalarMul(m.getSkewX(), inv));
+ fMatrix22.yx = -SkScalarToFixed(SkScalarMul(m.getSkewY(), inv));
+ fMatrix22.yy = SkScalarToFixed(SkScalarMul(m.getScaleY(), inv));
+
+ fMatrix22Scalar.setScaleX(SkScalarMul(m.getScaleX(), inv));
+ fMatrix22Scalar.setSkewX(-SkScalarMul(m.getSkewX(), inv));
+ fMatrix22Scalar.setSkewY(-SkScalarMul(m.getSkewY(), inv));
+ fMatrix22Scalar.setScaleY(SkScalarMul(m.getScaleY(), inv));
+ } else {
+ fMatrix22.xx = fMatrix22.yy = SK_Fixed1;
+ fMatrix22.xy = fMatrix22.yx = 0;
+ }
+
+#ifdef SK_SUPPORT_HINTING_SCALE_FACTOR
+ if (fRec.getHinting() == SkPaint::kNo_Hinting) {
+ fScale.set(sx, sy);
+ fScaleX = SkScalarToFixed(sx);
+ fScaleY = SkScalarToFixed(sy);
+ } else {
+ SkScalar hintingScaleFactor = fRec.fHintingScaleFactor;
+
+ fScale.set(sx / hintingScaleFactor, sy / hintingScaleFactor);
+ fScaleX = SkScalarToFixed(fScale.fX);
+ fScaleY = SkScalarToFixed(fScale.fY);
+
+ fMatrix22.xx *= hintingScaleFactor;
+ fMatrix22.xy *= hintingScaleFactor;
+ fMatrix22.yx *= hintingScaleFactor;
+ fMatrix22.yy *= hintingScaleFactor;
+
+ fMatrix22Scalar.setScaleX(fMatrix22Scalar.getScaleX() * hintingScaleFactor);
+ fMatrix22Scalar.setSkewX(fMatrix22Scalar..getSkewX() * hintingScaleFactor);
+ fMatrix22Scalar.setSkewY(fMatrix22Scalar..getSkewY() * hintingScaleFactor);
+ fMatrix22Scalar.setScaleY(fMatrix22Scalar..getScaleY() * hintingScaleFactor);
+ }
+#else
+ fScale.set(sx, sy);
+ fScaleX = SkScalarToFixed(sx);
+ fScaleY = SkScalarToFixed(sy);
+#endif
+
+ fLCDIsVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
+
+ // compute the flags we send to Load_Glyph
+ {
+ FT_Int32 loadFlags = FT_LOAD_DEFAULT;
+ bool linearMetrics = SkToBool(fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag);
+
+ if (SkMask::kBW_Format == fRec.fMaskFormat) {
+ // See http://code.google.com/p/chromium/issues/detail?id=43252#c24
+ loadFlags = FT_LOAD_TARGET_MONO;
+ if (fRec.getHinting() == SkPaint::kNo_Hinting) {
+ loadFlags = FT_LOAD_NO_HINTING;
+ linearMetrics = true;
+ }
+ } else {
+ switch (fRec.getHinting()) {
+ case SkPaint::kNo_Hinting:
+ loadFlags = FT_LOAD_NO_HINTING;
+ linearMetrics = true;
+ break;
+ case SkPaint::kSlight_Hinting:
+ loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT
+ break;
+ case SkPaint::kNormal_Hinting:
+ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag)
+ loadFlags = FT_LOAD_FORCE_AUTOHINT;
+ else
+ loadFlags = FT_LOAD_NO_AUTOHINT;
+ break;
+ case SkPaint::kFull_Hinting:
+ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) {
+ loadFlags = FT_LOAD_FORCE_AUTOHINT;
+ break;
+ }
+ loadFlags = FT_LOAD_TARGET_NORMAL;
+ if (isLCD(fRec)) {
+ if (fLCDIsVert) {
+ loadFlags = FT_LOAD_TARGET_LCD_V;
+ } else {
+ loadFlags = FT_LOAD_TARGET_LCD;
+ }
+ }
+ break;
+ default:
+ SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting());
+ break;
+ }
+ }
+
+ if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) {
+ loadFlags |= FT_LOAD_NO_BITMAP;
+ }
+
+ // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct
+ // advances, as fontconfig and cairo do.
+ // See http://code.google.com/p/skia/issues/detail?id=222.
+ loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
+
+ // Use vertical layout if requested.
+ if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
+ loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
+ }
+
+ fLoadGlyphFlags = loadFlags;
+ fDoLinearMetrics = linearMetrics;
+ }
+
+ // now create the FT_Size
+
+ {
+ FT_Error err;
+
+ err = FT_New_Size(fFace, &fFTSize);
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::FT_New_Size(%x): FT_Set_Char_Size(0x%x, 0x%x) returned 0x%x\n",
+ fFaceRec->fFontID, fScaleX, fScaleY, err));
+ fFace = NULL;
+ return;
+ }
+
+ err = FT_Activate_Size(fFTSize);
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n",
+ fFaceRec->fFontID, fScaleX, fScaleY, err));
+ fFTSize = NULL;
+ }
+
+ err = FT_Set_Char_Size( fFace,
+ SkFixedToFDot6(fScaleX), SkFixedToFDot6(fScaleY),
+ 72, 72);
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::FT_Set_Char_Size(%x, 0x%x, 0x%x) returned 0x%x\n",
+ fFaceRec->fFontID, fScaleX, fScaleY, err));
+ fFace = NULL;
+ return;
+ }
+
+ FT_Set_Transform( fFace, &fMatrix22, NULL);
+ }
+}
+
+SkScalerContext_FreeType::~SkScalerContext_FreeType() {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ if (fFTSize != NULL) {
+ FT_Done_Size(fFTSize);
+ }
+
+ if (fFace != NULL) {
+ unref_ft_face(fFace);
+ }
+ if (--gFTCount == 0) {
+// SkDEBUGF(("FT_Done_FreeType\n"));
+ FT_Done_FreeType(gFTLibrary);
+ SkDEBUGCODE(gFTLibrary = NULL;)
+ }
+}
+
+/* We call this before each use of the fFace, since we may be sharing
+ this face with other context (at different sizes).
+*/
+FT_Error SkScalerContext_FreeType::setupSize() {
+ FT_Error err = FT_Activate_Size(fFTSize);
+
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n",
+ fFaceRec->fFontID, fScaleX, fScaleY, err));
+ fFTSize = NULL;
+ } else {
+ // seems we need to reset this every time (not sure why, but without it
+ // I get random italics from some other fFTSize)
+ FT_Set_Transform( fFace, &fMatrix22, NULL);
+ }
+ return err;
+}
+
+unsigned SkScalerContext_FreeType::generateGlyphCount() {
+ return fFace->num_glyphs;
+}
+
+uint16_t SkScalerContext_FreeType::generateCharToGlyph(SkUnichar uni) {
+ return SkToU16(FT_Get_Char_Index( fFace, uni ));
+}
+
+SkUnichar SkScalerContext_FreeType::generateGlyphToChar(uint16_t glyph) {
+ // iterate through each cmap entry, looking for matching glyph indices
+ FT_UInt glyphIndex;
+ SkUnichar charCode = FT_Get_First_Char( fFace, &glyphIndex );
+
+ while (glyphIndex != 0) {
+ if (glyphIndex == glyph) {
+ return charCode;
+ }
+ charCode = FT_Get_Next_Char( fFace, charCode, &glyphIndex );
+ }
+
+ return 0;
+}
+
+void SkScalerContext_FreeType::generateAdvance(SkGlyph* glyph) {
+#ifdef FT_ADVANCES_H
+ /* unhinted and light hinted text have linearly scaled advances
+ * which are very cheap to compute with some font formats...
+ */
+ if (fDoLinearMetrics) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ if (this->setupSize()) {
+ glyph->zeroMetrics();
+ return;
+ }
+
+ FT_Error error;
+ FT_Fixed advance;
+
+ error = FT_Get_Advance( fFace, glyph->getGlyphID(fBaseGlyphCount),
+ fLoadGlyphFlags | FT_ADVANCE_FLAG_FAST_ONLY,
+ &advance );
+ if (0 == error) {
+ glyph->fRsbDelta = 0;
+ glyph->fLsbDelta = 0;
+ glyph->fAdvanceX = SkFixedMul(fMatrix22.xx, advance);
+ glyph->fAdvanceY = - SkFixedMul(fMatrix22.yx, advance);
+ return;
+ }
+ }
+#endif /* FT_ADVANCES_H */
+ /* otherwise, we need to load/hint the glyph, which is slower */
+ this->generateMetrics(glyph);
+ return;
+}
+
+void SkScalerContext_FreeType::getBBoxForCurrentGlyph(SkGlyph* glyph,
+ FT_BBox* bbox,
+ bool snapToPixelBoundary) {
+
+ FT_Outline_Get_CBox(&fFace->glyph->outline, bbox);
+
+ if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
+ int dx = SkFixedToFDot6(glyph->getSubXFixed());
+ int dy = SkFixedToFDot6(glyph->getSubYFixed());
+ // negate dy since freetype-y-goes-up and skia-y-goes-down
+ bbox->xMin += dx;
+ bbox->yMin -= dy;
+ bbox->xMax += dx;
+ bbox->yMax -= dy;
+ }
+
+ // outset the box to integral boundaries
+ if (snapToPixelBoundary) {
+ bbox->xMin &= ~63;
+ bbox->yMin &= ~63;
+ bbox->xMax = (bbox->xMax + 63) & ~63;
+ bbox->yMax = (bbox->yMax + 63) & ~63;
+ }
+
+ // Must come after snapToPixelBoundary so that the width and height are
+ // consistent. Otherwise asserts will fire later on when generating the
+ // glyph image.
+ if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
+ FT_Vector vector;
+ vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX;
+ vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY;
+ FT_Vector_Transform(&vector, &fMatrix22);
+ bbox->xMin += vector.x;
+ bbox->xMax += vector.x;
+ bbox->yMin += vector.y;
+ bbox->yMax += vector.y;
+ }
+}
+
+void SkScalerContext_FreeType::updateGlyphIfLCD(SkGlyph* glyph) {
+ if (isLCD(fRec)) {
+ if (fLCDIsVert) {
+ glyph->fHeight += gLCDExtra;
+ glyph->fTop -= gLCDExtra >> 1;
+ } else {
+ glyph->fWidth += gLCDExtra;
+ glyph->fLeft -= gLCDExtra >> 1;
+ }
+ }
+}
+
+void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ glyph->fRsbDelta = 0;
+ glyph->fLsbDelta = 0;
+
+ FT_Error err;
+
+ if (this->setupSize()) {
+ goto ERROR;
+ }
+
+ err = FT_Load_Glyph( fFace, glyph->getGlyphID(fBaseGlyphCount), fLoadGlyphFlags );
+ if (err != 0) {
+#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;
+ }
+
+ switch ( fFace->glyph->format ) {
+ case FT_GLYPH_FORMAT_OUTLINE: {
+ FT_BBox bbox;
+
+ if (0 == fFace->glyph->outline.n_contours) {
+ glyph->fWidth = 0;
+ glyph->fHeight = 0;
+ glyph->fTop = 0;
+ glyph->fLeft = 0;
+ break;
+ }
+
+ if (fRec.fFlags & kEmbolden_Flag) {
+ emboldenOutline(fFace, &fFace->glyph->outline);
+ }
+
+ getBBoxForCurrentGlyph(glyph, &bbox, true);
+
+ glyph->fWidth = SkToU16(SkFDot6Floor(bbox.xMax - bbox.xMin));
+ glyph->fHeight = SkToU16(SkFDot6Floor(bbox.yMax - bbox.yMin));
+ glyph->fTop = -SkToS16(SkFDot6Floor(bbox.yMax));
+ glyph->fLeft = SkToS16(SkFDot6Floor(bbox.xMin));
+
+ updateGlyphIfLCD(glyph);
+
+ break;
+ }
+
+ case FT_GLYPH_FORMAT_BITMAP:
+ if (fRec.fFlags & kEmbolden_Flag) {
+ FT_GlyphSlot_Own_Bitmap(fFace->glyph);
+ FT_Bitmap_Embolden(gFTLibrary, &fFace->glyph->bitmap, kBitmapEmboldenStrength, 0);
+ }
+
+ if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
+ FT_Vector vector;
+ vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX;
+ vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY;
+ FT_Vector_Transform(&vector, &fMatrix22);
+ fFace->glyph->bitmap_left += SkFDot6Floor(vector.x);
+ fFace->glyph->bitmap_top += SkFDot6Floor(vector.y);
+ }
+
+ glyph->fWidth = SkToU16(fFace->glyph->bitmap.width);
+ glyph->fHeight = SkToU16(fFace->glyph->bitmap.rows);
+ glyph->fTop = -SkToS16(fFace->glyph->bitmap_top);
+ glyph->fLeft = SkToS16(fFace->glyph->bitmap_left);
+ break;
+
+ default:
+ SkDEBUGFAIL("unknown glyph format");
+ goto ERROR;
+ }
+
+ if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
+ if (fDoLinearMetrics) {
+ glyph->fAdvanceX = -SkFixedMul(fMatrix22.xy, fFace->glyph->linearVertAdvance);
+ glyph->fAdvanceY = SkFixedMul(fMatrix22.yy, fFace->glyph->linearVertAdvance);
+ } else {
+ glyph->fAdvanceX = -SkFDot6ToFixed(fFace->glyph->advance.x);
+ glyph->fAdvanceY = SkFDot6ToFixed(fFace->glyph->advance.y);
+ }
+ } else {
+ if (fDoLinearMetrics) {
+ glyph->fAdvanceX = SkFixedMul(fMatrix22.xx, fFace->glyph->linearHoriAdvance);
+ glyph->fAdvanceY = -SkFixedMul(fMatrix22.yx, fFace->glyph->linearHoriAdvance);
+ } else {
+ glyph->fAdvanceX = SkFDot6ToFixed(fFace->glyph->advance.x);
+ glyph->fAdvanceY = -SkFDot6ToFixed(fFace->glyph->advance.y);
+
+ if (fRec.fFlags & kDevKernText_Flag) {
+ glyph->fRsbDelta = SkToS8(fFace->glyph->rsb_delta);
+ glyph->fLsbDelta = SkToS8(fFace->glyph->lsb_delta);
+ }
+ }
+ }
+
+
+#ifdef ENABLE_GLYPH_SPEW
+ SkDEBUGF(("FT_Set_Char_Size(this:%p sx:%x sy:%x ", this, fScaleX, fScaleY));
+ SkDEBUGF(("Metrics(glyph:%d flags:0x%x) w:%d\n", glyph->getGlyphID(fBaseGlyphCount), fLoadGlyphFlags, glyph->fWidth));
+#endif
+}
+
+
+void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ FT_Error err;
+
+ if (this->setupSize()) {
+ goto ERROR;
+ }
+
+ err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), fLoadGlyphFlags);
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::generateImage: FT_Load_Glyph(glyph:%d width:%d height:%d rb:%d flags:%d) returned 0x%x\n",
+ glyph.getGlyphID(fBaseGlyphCount), glyph.fWidth, glyph.fHeight, glyph.rowBytes(), fLoadGlyphFlags, err));
+ ERROR:
+ memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
+ return;
+ }
+
+ generateGlyphImage(fFace, glyph);
+}
+
+
+void SkScalerContext_FreeType::generatePath(const SkGlyph& glyph,
+ SkPath* path) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ SkASSERT(&glyph && path);
+
+ if (this->setupSize()) {
+ path->reset();
+ return;
+ }
+
+ uint32_t flags = fLoadGlyphFlags;
+ flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
+ flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline)
+
+ FT_Error err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), flags);
+
+ if (err != 0) {
+ SkDEBUGF(("SkScalerContext_FreeType::generatePath: FT_Load_Glyph(glyph:%d flags:%d) returned 0x%x\n",
+ glyph.getGlyphID(fBaseGlyphCount), flags, err));
+ path->reset();
+ return;
+ }
+
+ generateGlyphPath(fFace, path);
+
+ // The path's origin from FreeType is always the horizontal layout origin.
+ // Offset the path so that it is relative to the vertical origin if needed.
+ if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
+ FT_Vector vector;
+ vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX;
+ vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY;
+ FT_Vector_Transform(&vector, &fMatrix22);
+ path->offset(SkFDot6ToScalar(vector.x), -SkFDot6ToScalar(vector.y));
+ }
+}
+
+void SkScalerContext_FreeType::generateFontMetrics(SkPaint::FontMetrics* mx,
+ SkPaint::FontMetrics* my) {
+ if (NULL == mx && NULL == my) {
+ return;
+ }
+
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ if (this->setupSize()) {
+ ERROR:
+ if (mx) {
+ sk_bzero(mx, sizeof(SkPaint::FontMetrics));
+ }
+ if (my) {
+ sk_bzero(my, sizeof(SkPaint::FontMetrics));
+ }
+ return;
+ }
+
+ FT_Face face = fFace;
+ int upem = face->units_per_EM;
+ if (upem <= 0) {
+ goto ERROR;
+ }
+
+ SkPoint pts[6];
+ SkFixed ys[6];
+ SkScalar scaleY = fScale.y();
+ SkScalar mxy = fMatrix22Scalar.getSkewX();
+ SkScalar myy = fMatrix22Scalar.getScaleY();
+ SkScalar xmin = SkIntToScalar(face->bbox.xMin) / upem;
+ SkScalar xmax = SkIntToScalar(face->bbox.xMax) / upem;
+
+ int leading = face->height - (face->ascender + -face->descender);
+ if (leading < 0) {
+ leading = 0;
+ }
+
+ // Try to get the OS/2 table from the font. This contains the specific
+ // average font width metrics which Windows uses.
+ TT_OS2* os2 = (TT_OS2*) FT_Get_Sfnt_Table(face, ft_sfnt_os2);
+
+ ys[0] = -face->bbox.yMax;
+ ys[1] = -face->ascender;
+ ys[2] = -face->descender;
+ ys[3] = -face->bbox.yMin;
+ ys[4] = leading;
+ ys[5] = os2 ? os2->xAvgCharWidth : 0;
+
+ SkScalar x_height;
+ if (os2 && os2->sxHeight) {
+ x_height = fScale.x() * os2->sxHeight / upem;
+ } else {
+ const FT_UInt x_glyph = FT_Get_Char_Index(fFace, 'x');
+ if (x_glyph) {
+ FT_BBox bbox;
+ FT_Load_Glyph(fFace, x_glyph, fLoadGlyphFlags);
+ if (fRec.fFlags & kEmbolden_Flag) {
+ emboldenOutline(fFace, &fFace->glyph->outline);
+ }
+ FT_Outline_Get_CBox(&fFace->glyph->outline, &bbox);
+ x_height = bbox.yMax / 64.0f;
+ } else {
+ x_height = 0;
+ }
+ }
+
+ // convert upem-y values into scalar points
+ for (int i = 0; i < 6; i++) {
+ SkScalar y = scaleY * ys[i] / upem;
+ pts[i].set(y * mxy, y * myy);
+ }
+
+ if (mx) {
+ mx->fTop = pts[0].fX;
+ mx->fAscent = pts[1].fX;
+ mx->fDescent = pts[2].fX;
+ mx->fBottom = pts[3].fX;
+ mx->fLeading = pts[4].fX;
+ mx->fAvgCharWidth = pts[5].fX;
+ mx->fXMin = xmin;
+ mx->fXMax = xmax;
+ mx->fXHeight = x_height;
+ }
+ if (my) {
+ my->fTop = pts[0].fY;
+ my->fAscent = pts[1].fY;
+ my->fDescent = pts[2].fY;
+ my->fBottom = pts[3].fY;
+ my->fLeading = pts[4].fY;
+ my->fAvgCharWidth = pts[5].fY;
+ my->fXMin = xmin;
+ my->fXMax = xmax;
+ my->fXHeight = x_height;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkUtils.h"
+
+static SkUnichar next_utf8(const void** chars) {
+ return SkUTF8_NextUnichar((const char**)chars);
+}
+
+static SkUnichar next_utf16(const void** chars) {
+ return SkUTF16_NextUnichar((const uint16_t**)chars);
+}
+
+static SkUnichar next_utf32(const void** chars) {
+ const SkUnichar** uniChars = (const SkUnichar**)chars;
+ SkUnichar uni = **uniChars;
+ *uniChars += 1;
+ return uni;
+}
+
+typedef SkUnichar (*EncodingProc)(const void**);
+
+static EncodingProc find_encoding_proc(SkTypeface::Encoding enc) {
+ static const EncodingProc gProcs[] = {
+ next_utf8, next_utf16, next_utf32
+ };
+ SkASSERT((size_t)enc < SK_ARRAY_COUNT(gProcs));
+ return gProcs[enc];
+}
+
+int SkTypeface_FreeType::onCharsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const {
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+ if (!face) {
+ if (glyphs) {
+ sk_bzero(glyphs, glyphCount * sizeof(glyphs[0]));
+ }
+ return 0;
+ }
+
+ EncodingProc next_uni_proc = find_encoding_proc(encoding);
+
+ if (NULL == glyphs) {
+ for (int i = 0; i < glyphCount; ++i) {
+ if (0 == FT_Get_Char_Index(face, next_uni_proc(&chars))) {
+ return i;
+ }
+ }
+ return glyphCount;
+ } else {
+ int first = glyphCount;
+ for (int i = 0; i < glyphCount; ++i) {
+ unsigned id = FT_Get_Char_Index(face, next_uni_proc(&chars));
+ glyphs[i] = SkToU16(id);
+ if (0 == id && i < first) {
+ first = i;
+ }
+ }
+ return first;
+ }
+}
+
+int SkTypeface_FreeType::onCountGlyphs() const {
+ // we cache this value, using -1 as a sentinel for "not computed"
+ if (fGlyphCount < 0) {
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+ // if the face failed, we still assign a non-negative value
+ fGlyphCount = face ? face->num_glyphs : 0;
+ }
+ return fGlyphCount;
+}
+
+SkTypeface::LocalizedStrings* SkTypeface_FreeType::onCreateFamilyNameIterator() const {
+ SkTypeface::LocalizedStrings* nameIter =
+ SkOTUtils::LocalizedStrings_NameTable::CreateForFamilyNames(*this);
+ if (NULL == nameIter) {
+ SkString familyName;
+ this->getFamilyName(&familyName);
+ SkString language("und"); //undetermined
+ nameIter = new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
+ }
+ return nameIter;
+}
+
+int SkTypeface_FreeType::onGetTableTags(SkFontTableTag tags[]) const {
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+
+ FT_ULong tableCount = 0;
+ FT_Error error;
+
+ // When 'tag' is NULL, returns number of tables in 'length'.
+ error = FT_Sfnt_Table_Info(face, 0, NULL, &tableCount);
+ if (error) {
+ return 0;
+ }
+
+ if (tags) {
+ for (FT_ULong tableIndex = 0; tableIndex < tableCount; ++tableIndex) {
+ FT_ULong tableTag;
+ FT_ULong tablelength;
+ error = FT_Sfnt_Table_Info(face, tableIndex, &tableTag, &tablelength);
+ if (error) {
+ return 0;
+ }
+ tags[tableIndex] = static_cast<SkFontTableTag>(tableTag);
+ }
+ }
+ return tableCount;
+}
+
+size_t SkTypeface_FreeType::onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* data) const
+{
+ AutoFTAccess fta(this);
+ FT_Face face = fta.face();
+
+ FT_ULong tableLength = 0;
+ FT_Error error;
+
+ // When 'length' is 0 it is overwritten with the full table length; 'offset' is ignored.
+ error = FT_Load_Sfnt_Table(face, tag, 0, NULL, &tableLength);
+ if (error) {
+ return 0;
+ }
+
+ if (offset > tableLength) {
+ return 0;
+ }
+ FT_ULong size = SkTMin((FT_ULong)length, tableLength - (FT_ULong)offset);
+ if (NULL != data) {
+ error = FT_Load_Sfnt_Table(face, tag, offset, reinterpret_cast<FT_Byte*>(data), &size);
+ if (error) {
+ return 0;
+ }
+ }
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+/* Export this so that other parts of our FonttHost port can make use of our
+ ability to extract the name+style from a stream, using FreeType's api.
+*/
+bool find_name_and_attributes(SkStream* stream, SkString* name,
+ SkTypeface::Style* style, bool* isFixedPitch) {
+ FT_Library library;
+ if (FT_Init_FreeType(&library)) {
+ return false;
+ }
+
+ FT_Open_Args args;
+ memset(&args, 0, sizeof(args));
+
+ const void* memoryBase = stream->getMemoryBase();
+ FT_StreamRec streamRec;
+
+ if (NULL != memoryBase) {
+ args.flags = FT_OPEN_MEMORY;
+ args.memory_base = (const FT_Byte*)memoryBase;
+ args.memory_size = stream->getLength();
+ } else {
+ memset(&streamRec, 0, sizeof(streamRec));
+ streamRec.size = stream->getLength();
+ streamRec.descriptor.pointer = stream;
+ streamRec.read = sk_stream_read;
+ streamRec.close = sk_stream_close;
+
+ args.flags = FT_OPEN_STREAM;
+ args.stream = &streamRec;
+ }
+
+ FT_Face face;
+ if (FT_Open_Face(library, &args, 0, &face)) {
+ FT_Done_FreeType(library);
+ return false;
+ }
+
+ int tempStyle = SkTypeface::kNormal;
+ if (face->style_flags & FT_STYLE_FLAG_BOLD) {
+ tempStyle |= SkTypeface::kBold;
+ }
+ if (face->style_flags & FT_STYLE_FLAG_ITALIC) {
+ tempStyle |= SkTypeface::kItalic;
+ }
+
+ if (name) {
+ name->set(face->family_name);
+ }
+ if (style) {
+ *style = (SkTypeface::Style) tempStyle;
+ }
+ if (isFixedPitch) {
+ *isFixedPitch = FT_IS_FIXED_WIDTH(face);
+ }
+
+ FT_Done_Face(face);
+ FT_Done_FreeType(library);
+ return true;
+}
diff --git a/ports/SkFontHost_FreeType_common.cpp b/ports/SkFontHost_FreeType_common.cpp
new file mode 100644
index 00000000..2c486847
--- /dev/null
+++ b/ports/SkFontHost_FreeType_common.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2006-2012 The Android Open Source Project
+ * Copyright 2012 Mozilla Foundation
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorPriv.h"
+#include "SkFDot6.h"
+#include "SkFontHost_FreeType_common.h"
+#include "SkPath.h"
+
+#include <ft2build.h>
+#include FT_OUTLINE_H
+#include FT_BITMAP_H
+// In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file.
+#include FT_SYNTHESIS_H
+
+static FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) {
+ switch (format) {
+ case SkMask::kBW_Format:
+ return FT_PIXEL_MODE_MONO;
+ case SkMask::kA8_Format:
+ default:
+ return FT_PIXEL_MODE_GRAY;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static uint16_t packTriple(unsigned r, unsigned g, unsigned b) {
+ return SkPackRGB16(r >> 3, g >> 2, b >> 3);
+}
+
+static uint16_t grayToRGB16(U8CPU gray) {
+ SkASSERT(gray <= 255);
+ return SkPackRGB16(gray >> 3, gray >> 2, gray >> 3);
+}
+
+static int bittst(const uint8_t data[], int bitOffset) {
+ SkASSERT(bitOffset >= 0);
+ int lowBit = data[bitOffset >> 3] >> (~bitOffset & 7);
+ return lowBit & 1;
+}
+
+template<bool APPLY_PREBLEND>
+static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap,
+ int lcdIsBGR, bool lcdIsVert, const uint8_t* tableR,
+ const uint8_t* tableG, const uint8_t* tableB) {
+ if (lcdIsVert) {
+ SkASSERT(3 * glyph.fHeight == bitmap.rows);
+ } else {
+ SkASSERT(glyph.fHeight == bitmap.rows);
+ }
+
+ uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage);
+ const size_t dstRB = glyph.rowBytes();
+ const int width = glyph.fWidth;
+ const uint8_t* src = bitmap.buffer;
+
+ switch (bitmap.pixel_mode) {
+ case FT_PIXEL_MODE_MONO: {
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ for (int x = 0; x < width; ++x) {
+ dst[x] = -bittst(src, x);
+ }
+ dst = (uint16_t*)((char*)dst + dstRB);
+ src += bitmap.pitch;
+ }
+ } break;
+ case FT_PIXEL_MODE_GRAY: {
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ for (int x = 0; x < width; ++x) {
+ dst[x] = grayToRGB16(src[x]);
+ }
+ dst = (uint16_t*)((char*)dst + dstRB);
+ src += bitmap.pitch;
+ }
+ } break;
+ default: {
+ SkASSERT(lcdIsVert || (glyph.fWidth * 3 == bitmap.width));
+ for (int y = 0; y < glyph.fHeight; y++) {
+ if (lcdIsVert) { // vertical stripes
+ const uint8_t* srcR = src;
+ const uint8_t* srcG = srcR + bitmap.pitch;
+ const uint8_t* srcB = srcG + bitmap.pitch;
+ if (lcdIsBGR) {
+ SkTSwap(srcR, srcB);
+ }
+ for (int x = 0; x < width; x++) {
+ dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(*srcR++, tableR),
+ sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG),
+ sk_apply_lut_if<APPLY_PREBLEND>(*srcB++, tableB));
+ }
+ src += 3 * bitmap.pitch;
+ } else { // horizontal stripes
+ const uint8_t* triple = src;
+ if (lcdIsBGR) {
+ for (int x = 0; x < width; x++) {
+ dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableR),
+ sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
+ sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableB));
+ triple += 3;
+ }
+ } else {
+ for (int x = 0; x < width; x++) {
+ dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableR),
+ sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
+ sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableB));
+ triple += 3;
+ }
+ }
+ src += bitmap.pitch;
+ }
+ dst = (uint16_t*)((char*)dst + dstRB);
+ }
+ } break;
+ }
+}
+
+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);
+
+ switch ( face->glyph->format ) {
+ case FT_GLYPH_FORMAT_OUTLINE: {
+ FT_Outline* outline = &face->glyph->outline;
+ FT_BBox bbox;
+ FT_Bitmap target;
+
+ if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) {
+ emboldenOutline(face, outline);
+ }
+
+ int dx = 0, dy = 0;
+ if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
+ dx = SkFixedToFDot6(glyph.getSubXFixed());
+ dy = SkFixedToFDot6(glyph.getSubYFixed());
+ // negate dy since freetype-y-goes-up and skia-y-goes-down
+ dy = -dy;
+ }
+ FT_Outline_Get_CBox(outline, &bbox);
+ /*
+ what we really want to do for subpixel is
+ offset(dx, dy)
+ compute_bounds
+ offset(bbox & !63)
+ but that is two calls to offset, so we do the following, which
+ achieves the same thing with only one offset call.
+ */
+ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63),
+ dy - ((bbox.yMin + dy) & ~63));
+
+ if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
+ FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD);
+ if (fPreBlend.isApplicable()) {
+ copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } else {
+ target.width = glyph.fWidth;
+ target.rows = glyph.fHeight;
+ target.pitch = glyph.rowBytes();
+ target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage);
+ target.pixel_mode = compute_pixel_mode(
+ (SkMask::Format)fRec.fMaskFormat);
+ target.num_grays = 256;
+
+ memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
+ FT_Outline_Get_Bitmap(face->glyph->library, outline, &target);
+ }
+ } break;
+
+ case FT_GLYPH_FORMAT_BITMAP: {
+ if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) {
+ FT_GlyphSlot_Own_Bitmap(face->glyph);
+ FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, kBitmapEmboldenStrength, 0);
+ }
+ SkASSERT_CONTINUE(glyph.fWidth == face->glyph->bitmap.width);
+ SkASSERT_CONTINUE(glyph.fHeight == face->glyph->bitmap.rows);
+ SkASSERT_CONTINUE(glyph.fTop == -face->glyph->bitmap_top);
+ SkASSERT_CONTINUE(glyph.fLeft == face->glyph->bitmap_left);
+
+ const uint8_t* src = (const uint8_t*)face->glyph->bitmap.buffer;
+ uint8_t* dst = (uint8_t*)glyph.fImage;
+
+ if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY ||
+ (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO &&
+ glyph.fMaskFormat == SkMask::kBW_Format)) {
+ unsigned srcRowBytes = face->glyph->bitmap.pitch;
+ unsigned dstRowBytes = glyph.rowBytes();
+ unsigned minRowBytes = SkMin32(srcRowBytes, dstRowBytes);
+ unsigned extraRowBytes = dstRowBytes - minRowBytes;
+
+ for (int y = face->glyph->bitmap.rows - 1; y >= 0; --y) {
+ memcpy(dst, src, minRowBytes);
+ memset(dst + minRowBytes, 0, extraRowBytes);
+ src += srcRowBytes;
+ dst += dstRowBytes;
+ }
+ } else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO &&
+ glyph.fMaskFormat == SkMask::kA8_Format) {
+ for (int y = 0; y < face->glyph->bitmap.rows; ++y) {
+ uint8_t byte = 0;
+ int bits = 0;
+ const uint8_t* src_row = src;
+ uint8_t* dst_row = dst;
+
+ for (int x = 0; x < face->glyph->bitmap.width; ++x) {
+ if (!bits) {
+ byte = *src_row++;
+ bits = 8;
+ }
+
+ *dst_row++ = byte & 0x80 ? 0xff : 0;
+ bits--;
+ byte <<= 1;
+ }
+
+ src += face->glyph->bitmap.pitch;
+ dst += glyph.rowBytes();
+ }
+ } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
+ if (fPreBlend.isApplicable()) {
+ copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } else {
+ SkDEBUGFAIL("unknown glyph bitmap transform needed");
+ }
+ } break;
+
+ default:
+ SkDEBUGFAIL("unknown glyph format");
+ memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
+ return;
+ }
+
+// 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 && 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] = fPreBlend.fG[dst[x]];
+ }
+ dst += rowBytes;
+ }
+ }
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int move_proc(const FT_Vector* pt, void* ctx) {
+ SkPath* path = (SkPath*)ctx;
+ path->close(); // to close the previous contour (if any)
+ path->moveTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
+ return 0;
+}
+
+static int line_proc(const FT_Vector* pt, void* ctx) {
+ SkPath* path = (SkPath*)ctx;
+ path->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
+ return 0;
+}
+
+static int quad_proc(const FT_Vector* pt0, const FT_Vector* pt1,
+ void* ctx) {
+ SkPath* path = (SkPath*)ctx;
+ path->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
+ SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y));
+ return 0;
+}
+
+static int cubic_proc(const FT_Vector* pt0, const FT_Vector* pt1,
+ const FT_Vector* pt2, void* ctx) {
+ SkPath* path = (SkPath*)ctx;
+ path->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
+ SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y),
+ SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y));
+ return 0;
+}
+
+void SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face,
+ SkPath* path)
+{
+ if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) {
+ emboldenOutline(face, &face->glyph->outline);
+ }
+
+ FT_Outline_Funcs funcs;
+
+ funcs.move_to = move_proc;
+ funcs.line_to = line_proc;
+ funcs.conic_to = quad_proc;
+ funcs.cubic_to = cubic_proc;
+ funcs.shift = 0;
+ funcs.delta = 0;
+
+ FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &funcs, path);
+
+ if (err != 0) {
+ path->reset();
+ return;
+ }
+
+ path->close();
+}
+
+void SkScalerContext_FreeType_Base::emboldenOutline(FT_Face face, FT_Outline* outline)
+{
+ FT_Pos strength;
+ strength = FT_MulFix(face->units_per_EM, face->size->metrics.y_scale)
+ / 24;
+ FT_Outline_Embolden(outline, strength);
+}
diff --git a/ports/SkFontHost_FreeType_common.h b/ports/SkFontHost_FreeType_common.h
new file mode 100644
index 00000000..c0f2dc75
--- /dev/null
+++ b/ports/SkFontHost_FreeType_common.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2006-2012 The Android Open Source Project
+ * Copyright 2012 Mozilla Foundation
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKFONTHOST_FREETYPE_COMMON_H_
+#define SKFONTHOST_FREETYPE_COMMON_H_
+
+#include "SkGlyph.h"
+#include "SkScalerContext.h"
+#include "SkTypeface.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#ifdef SK_DEBUG
+ #define SkASSERT_CONTINUE(pred) \
+ do { \
+ if (!(pred)) \
+ SkDebugf("file %s:%d: assert failed '" #pred "'\n", __FILE__, __LINE__); \
+ } while (false)
+#else
+ #define SkASSERT_CONTINUE(pred)
+#endif
+
+
+class SkScalerContext_FreeType_Base : public SkScalerContext {
+protected:
+ // See http://freetype.sourceforge.net/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
+ // This value was chosen by eyeballing the result in Firefox and trying to match it.
+ static const FT_Pos kBitmapEmboldenStrength = 1 << 6;
+
+ SkScalerContext_FreeType_Base(SkTypeface* typeface, const SkDescriptor *desc)
+ : INHERITED(typeface, desc)
+ {}
+
+ void generateGlyphImage(FT_Face face, const SkGlyph& glyph);
+ void generateGlyphPath(FT_Face face, SkPath* path);
+ void emboldenOutline(FT_Face face, FT_Outline* outline);
+
+private:
+ typedef SkScalerContext INHERITED;
+};
+
+class SkTypeface_FreeType : public SkTypeface {
+protected:
+ SkTypeface_FreeType(Style style, SkFontID uniqueID, bool isFixedPitch)
+ : INHERITED(style, uniqueID, isFixedPitch)
+ , fGlyphCount(-1)
+ {}
+
+ virtual SkScalerContext* onCreateScalerContext(
+ const SkDescriptor*) const SK_OVERRIDE;
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE;
+ virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo,
+ const uint32_t*, uint32_t) const SK_OVERRIDE;
+ virtual int onGetUPEM() const SK_OVERRIDE;
+
+ virtual int onCharsToGlyphs(const void* chars, Encoding, uint16_t glyphs[],
+ int glyphCount) const SK_OVERRIDE;
+ virtual int onCountGlyphs() const SK_OVERRIDE;
+
+ virtual LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE;
+
+ virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE;
+ virtual size_t onGetTableData(SkFontTableTag, size_t offset,
+ size_t length, void* data) const SK_OVERRIDE;
+
+private:
+ mutable int fGlyphCount;
+
+ typedef SkTypeface INHERITED;
+};
+
+#endif // SKFONTHOST_FREETYPE_COMMON_H_
diff --git a/ports/SkFontHost_fontconfig.cpp b/ports/SkFontHost_fontconfig.cpp
new file mode 100644
index 00000000..423b85eb
--- /dev/null
+++ b/ports/SkFontHost_fontconfig.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFontConfigInterface.h"
+#include "SkFontConfigTypeface.h"
+#include "SkFontDescriptor.h"
+#include "SkFontHost.h"
+#include "SkFontHost_FreeType_common.h"
+#include "SkFontStream.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkTypefaceCache.h"
+
+SK_DECLARE_STATIC_MUTEX(gFontConfigInterfaceMutex);
+static SkFontConfigInterface* gFontConfigInterface;
+
+SkFontConfigInterface* SkFontConfigInterface::RefGlobal() {
+ SkAutoMutexAcquire ac(gFontConfigInterfaceMutex);
+
+ return SkSafeRef(gFontConfigInterface);
+}
+
+SkFontConfigInterface* SkFontConfigInterface::SetGlobal(SkFontConfigInterface* fc) {
+ SkAutoMutexAcquire ac(gFontConfigInterfaceMutex);
+
+ SkRefCnt_SafeAssign(gFontConfigInterface, fc);
+ return fc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// convenience function to create the direct interface if none is installed.
+extern SkFontConfigInterface* SkCreateDirectFontConfigInterface();
+
+static SkFontConfigInterface* RefFCI() {
+ for (;;) {
+ SkFontConfigInterface* fci = SkFontConfigInterface::RefGlobal();
+ if (fci) {
+ return fci;
+ }
+ fci = SkFontConfigInterface::GetSingletonDirectInterface();
+ SkFontConfigInterface::SetGlobal(fci);
+ }
+}
+
+// export this to SkFontMgr_fontconfig.cpp until this file just goes away.
+SkFontConfigInterface* SkFontHost_fontconfig_ref_global();
+SkFontConfigInterface* SkFontHost_fontconfig_ref_global() {
+ return RefFCI();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct FindRec {
+ FindRec(const char* name, SkTypeface::Style style)
+ : fFamilyName(name) // don't need to make a deep copy
+ , fStyle(style) {}
+
+ const char* fFamilyName;
+ SkTypeface::Style fStyle;
+};
+
+static bool find_proc(SkTypeface* face, SkTypeface::Style style, void* ctx) {
+ FontConfigTypeface* fci = (FontConfigTypeface*)face;
+ const FindRec* rec = (const FindRec*)ctx;
+
+ return rec->fStyle == style && fci->isFamilyName(rec->fFamilyName);
+}
+
+SkTypeface* FontConfigTypeface::LegacyCreateTypeface(
+ const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ SkAutoTUnref<SkFontConfigInterface> fci(RefFCI());
+ if (NULL == fci.get()) {
+ return NULL;
+ }
+
+ if (familyFace) {
+ FontConfigTypeface* fct = (FontConfigTypeface*)familyFace;
+ familyName = fct->getFamilyName();
+ }
+
+ FindRec rec(familyName, style);
+ SkTypeface* face = SkTypefaceCache::FindByProcAndRef(find_proc, &rec);
+ if (face) {
+// SkDebugf("found cached face <%s> <%s> %p [%d]\n", familyName, ((FontConfigTypeface*)face)->getFamilyName(), face, face->getRefCnt());
+ return face;
+ }
+
+ SkFontConfigInterface::FontIdentity indentity;
+ SkString outFamilyName;
+ SkTypeface::Style outStyle;
+
+ if (!fci->matchFamilyName(familyName, style,
+ &indentity, &outFamilyName, &outStyle)) {
+ return NULL;
+ }
+
+ // check if we, in fact, already have this. perhaps fontconfig aliased the
+ // requested name to some other name we actually have...
+ rec.fFamilyName = outFamilyName.c_str();
+ rec.fStyle = outStyle;
+ face = SkTypefaceCache::FindByProcAndRef(find_proc, &rec);
+ if (face) {
+ return face;
+ }
+
+ face = SkNEW_ARGS(FontConfigTypeface, (outStyle, indentity, outFamilyName));
+ SkTypefaceCache::Add(face, style);
+// SkDebugf("add face <%s> <%s> %p [%d]\n", familyName, outFamilyName.c_str(), face, face->getRefCnt());
+ return face;
+}
+
+#ifndef SK_FONTHOST_USES_FONTMGR
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ return FontConfigTypeface::LegacyCreateTypeface(familyFace, familyName,
+ style);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ if (!stream) {
+ return NULL;
+ }
+ const size_t length = stream->getLength();
+ if (!length) {
+ return NULL;
+ }
+ if (length >= 1024 * 1024 * 1024) {
+ return NULL; // don't accept too large fonts (>= 1GB) for safety.
+ }
+
+ // TODO should the caller give us the style?
+ SkTypeface::Style style = SkTypeface::kNormal;
+ SkTypeface* face = SkNEW_ARGS(FontConfigTypeface, (style, stream));
+ return face;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return stream.get() ? CreateTypefaceFromStream(stream) : NULL;
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkStream* FontConfigTypeface::onOpenStream(int* ttcIndex) const {
+ SkStream* stream = this->getLocalStream();
+ if (stream) {
+ // should have been provided by CreateFromStream()
+ *ttcIndex = 0;
+
+ SkAutoTUnref<SkStream> dupStream(stream->duplicate());
+ if (dupStream) {
+ return dupStream.detach();
+ }
+
+ // TODO: update interface use, remove the following code in this block.
+ size_t length = stream->getLength();
+
+ const void* memory = stream->getMemoryBase();
+ if (NULL != memory) {
+ return new SkMemoryStream(memory, length, true);
+ }
+
+ SkAutoTMalloc<uint8_t> allocMemory(length);
+ stream->rewind();
+ if (length == stream->read(allocMemory.get(), length)) {
+ SkAutoTUnref<SkMemoryStream> copyStream(new SkMemoryStream());
+ copyStream->setMemoryOwned(allocMemory.detach(), length);
+ return copyStream.detach();
+ }
+
+ stream->rewind();
+ stream->ref();
+ } else {
+ SkAutoTUnref<SkFontConfigInterface> fci(RefFCI());
+ if (NULL == fci.get()) {
+ return NULL;
+ }
+ stream = fci->openStream(this->getIdentity());
+ *ttcIndex = this->getIdentity().fTTCIndex;
+ }
+ return stream;
+}
+
+void FontConfigTypeface::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocalStream) const {
+ desc->setFamilyName(this->getFamilyName());
+ *isLocalStream = SkToBool(this->getLocalStream());
+}
+
+SkTypeface* FontConfigTypeface::onRefMatchingStyle(Style style) const {
+ return LegacyCreateTypeface(this, NULL, style);
+}
diff --git a/ports/SkFontHost_linux.cpp b/ports/SkFontHost_linux.cpp
new file mode 100644
index 00000000..2487976b
--- /dev/null
+++ b/ports/SkFontHost_linux.cpp
@@ -0,0 +1,527 @@
+
+/*
+ * 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 "SkFontHost_FreeType_common.h"
+#include "SkFontDescriptor.h"
+#include "SkDescriptor.h"
+#include "SkOSFile.h"
+#include "SkPaint.h"
+#include "SkString.h"
+#include "SkStream.h"
+#include "SkThread.h"
+#include "SkTSearch.h"
+
+#ifndef SK_FONT_FILE_PREFIX
+ #define SK_FONT_FILE_PREFIX "/usr/share/fonts/truetype/"
+#endif
+#ifndef SK_FONT_FILE_DIR_SEPERATOR
+ #define SK_FONT_FILE_DIR_SEPERATOR "/"
+#endif
+
+bool find_name_and_attributes(SkStream* stream, SkString* name,
+ SkTypeface::Style* style, bool* isFixedPitch);
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct FamilyRec;
+
+/* This guy holds a mapping of a name -> family, used for looking up fonts.
+ Since it is stored in a stretchy array that doesn't preserve object
+ semantics, we don't use constructor/destructors, but just have explicit
+ helpers to manage our internal bookkeeping.
+ */
+struct NameFamilyPair {
+ const char* fName; // we own this
+ FamilyRec* fFamily; // we don't own this, we just reference it
+
+ void construct(const char name[], FamilyRec* family)
+ {
+ fName = strdup(name);
+ fFamily = family; // we don't own this, so just record the referene
+ }
+ void destruct()
+ {
+ free((char*)fName);
+ // we don't own family, so just ignore our reference
+ }
+};
+
+// we use atomic_inc to grow this for each typeface we create
+static int32_t gUniqueFontID;
+
+// this is the mutex that protects these globals
+SK_DECLARE_STATIC_MUTEX(gFamilyMutex);
+static FamilyRec* gFamilyHead;
+static SkTDArray<NameFamilyPair> gNameList;
+
+struct FamilyRec {
+ FamilyRec* fNext;
+ SkTypeface* fFaces[4];
+
+ FamilyRec()
+ {
+ fNext = gFamilyHead;
+ memset(fFaces, 0, sizeof(fFaces));
+ gFamilyHead = this;
+ }
+};
+
+static SkTypeface* find_best_face(const FamilyRec* family,
+ SkTypeface::Style style) {
+ SkTypeface* const* faces = family->fFaces;
+
+ if (faces[style] != NULL) { // exact match
+ return faces[style];
+ }
+ // look for a matching bold
+ style = (SkTypeface::Style)(style ^ SkTypeface::kItalic);
+ if (faces[style] != NULL) {
+ return faces[style];
+ }
+ // look for the plain
+ if (faces[SkTypeface::kNormal] != NULL) {
+ return faces[SkTypeface::kNormal];
+ }
+ // look for anything
+ for (int i = 0; i < 4; i++) {
+ if (faces[i] != NULL) {
+ return faces[i];
+ }
+ }
+ // should never get here, since the faces list should not be empty
+ SkDEBUGFAIL("faces list is empty");
+ return NULL;
+}
+
+static FamilyRec* find_family(const SkTypeface* member) {
+ FamilyRec* curr = gFamilyHead;
+ while (curr != NULL) {
+ for (int i = 0; i < 4; i++) {
+ if (curr->fFaces[i] == member) {
+ return curr;
+ }
+ }
+ curr = curr->fNext;
+ }
+ return NULL;
+}
+
+/* Remove reference to this face from its family. If the resulting family
+ is empty (has no faces), return that family, otherwise return NULL
+ */
+static FamilyRec* remove_from_family(const SkTypeface* face) {
+ FamilyRec* family = find_family(face);
+ SkASSERT(family->fFaces[face->style()] == face);
+ family->fFaces[face->style()] = NULL;
+
+ for (int i = 0; i < 4; i++) {
+ if (family->fFaces[i] != NULL) { // family is non-empty
+ return NULL;
+ }
+ }
+ return family; // return the empty family
+}
+
+// maybe we should make FamilyRec be doubly-linked
+static void detach_and_delete_family(FamilyRec* family) {
+ FamilyRec* curr = gFamilyHead;
+ FamilyRec* prev = NULL;
+
+ while (curr != NULL) {
+ FamilyRec* next = curr->fNext;
+ if (curr == family) {
+ if (prev == NULL) {
+ gFamilyHead = next;
+ } else {
+ prev->fNext = next;
+ }
+ SkDELETE(family);
+ return;
+ }
+ prev = curr;
+ curr = next;
+ }
+ SkDEBUGFAIL("Yikes, couldn't find family in our list to remove/delete");
+}
+
+static const char* find_family_name(const SkTypeface* familyMember) {
+ const FamilyRec* familyRec = find_family(familyMember);
+ for (int i = 0; i < gNameList.count(); i++) {
+ if (gNameList[i].fFamily == familyRec) {
+ return gNameList[i].fName;
+ }
+ }
+ return NULL;
+}
+
+static FamilyRec* find_familyrec(const char name[]) {
+ const NameFamilyPair* list = gNameList.begin();
+ int index = SkStrLCSearch(&list[0].fName, gNameList.count(), name,
+ sizeof(list[0]));
+ return index >= 0 ? list[index].fFamily : NULL;
+}
+
+static SkTypeface* find_typeface(const char name[], SkTypeface::Style style) {
+ FamilyRec* rec = find_familyrec(name);
+ return rec ? find_best_face(rec, style) : NULL;
+}
+
+static SkTypeface* find_typeface(const SkTypeface* familyMember,
+ SkTypeface::Style style) {
+ const FamilyRec* family = find_family(familyMember);
+ return family ? find_best_face(family, style) : NULL;
+}
+
+static void add_name(const char name[], FamilyRec* family) {
+ SkAutoAsciiToLC tolc(name);
+ name = tolc.lc();
+
+ NameFamilyPair* list = gNameList.begin();
+ int count = gNameList.count();
+
+ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0]));
+
+ if (index < 0) {
+ list = gNameList.insert(~index);
+ list->construct(name, family);
+ }
+}
+
+static void remove_from_names(FamilyRec* emptyFamily) {
+#ifdef SK_DEBUG
+ for (int i = 0; i < 4; i++) {
+ SkASSERT(emptyFamily->fFaces[i] == NULL);
+ }
+#endif
+
+ SkTDArray<NameFamilyPair>& list = gNameList;
+
+ // must go backwards when removing
+ for (int i = list.count() - 1; i >= 0; --i) {
+ NameFamilyPair* pair = &list[i];
+ if (pair->fFamily == emptyFamily) {
+ pair->destruct();
+ list.remove(i);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class FamilyTypeface : public SkTypeface_FreeType {
+public:
+ FamilyTypeface(Style style, bool sysFont, FamilyRec* family, bool isFixedPitch)
+ : INHERITED(style, sk_atomic_inc(&gUniqueFontID) + 1, isFixedPitch) {
+ fIsSysFont = sysFont;
+
+ SkAutoMutexAcquire ac(gFamilyMutex);
+
+ if (NULL == family) {
+ family = SkNEW(FamilyRec);
+ }
+ family->fFaces[style] = this;
+ fFamilyRec = family; // just record it so we can return it if asked
+ }
+
+ virtual ~FamilyTypeface() {
+ SkAutoMutexAcquire ac(gFamilyMutex);
+
+ // remove us from our family. If the family is now empty, we return
+ // that and then remove that family from the name list
+ FamilyRec* family = remove_from_family(this);
+ if (NULL != family) {
+ remove_from_names(family);
+ detach_and_delete_family(family);
+ }
+ }
+
+ bool isSysFont() const { return fIsSysFont; }
+ FamilyRec* getFamily() const { return fFamilyRec; }
+
+ virtual const char* getUniqueString() const = 0;
+
+protected:
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style styleBits) const SK_OVERRIDE;
+
+private:
+ FamilyRec* fFamilyRec; // we don't own this, just point to it
+ bool fIsSysFont;
+
+ typedef SkTypeface_FreeType INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* This subclass is just a place holder for when we have no fonts available.
+ It exists so that our globals (e.g. gFamilyHead) that expect *something*
+ will not be null.
+ */
+class EmptyTypeface : public FamilyTypeface {
+public:
+ EmptyTypeface() : INHERITED(SkTypeface::kNormal, true, NULL, false) {}
+
+ virtual const char* getUniqueString() SK_OVERRIDE const { return NULL; }
+
+protected:
+ virtual SkStream* onOpenStream(int*) const SK_OVERRIDE { return NULL; }
+
+private:
+ typedef FamilyTypeface INHERITED;
+};
+
+class StreamTypeface : public FamilyTypeface {
+public:
+ StreamTypeface(Style style, bool sysFont, FamilyRec* family,
+ SkStream* stream, bool isFixedPitch)
+ : INHERITED(style, sysFont, family, isFixedPitch) {
+ stream->ref();
+ fStream = stream;
+ }
+ virtual ~StreamTypeface() {
+ fStream->unref();
+ }
+
+ virtual const char* getUniqueString() const SK_OVERRIDE { return NULL; }
+
+protected:
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE {
+ *ttcIndex = 0;
+ fStream->ref();
+ return fStream;
+ }
+
+private:
+ SkStream* fStream;
+
+ typedef FamilyTypeface INHERITED;
+};
+
+class FileTypeface : public FamilyTypeface {
+public:
+ FileTypeface(Style style, bool sysFont, FamilyRec* family,
+ const char path[], bool isFixedPitch)
+ : INHERITED(style, sysFont, family, isFixedPitch) {
+ fPath.set(path);
+ }
+
+ virtual const char* getUniqueString() const SK_OVERRIDE {
+ const char* str = strrchr(fPath.c_str(), '/');
+ if (str) {
+ str += 1; // skip the '/'
+ }
+ return str;
+ }
+
+protected:
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE {
+ *ttcIndex = 0;
+ return SkStream::NewFromFile(fPath.c_str());
+ }
+
+private:
+ SkString fPath;
+
+ typedef FamilyTypeface INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static bool get_name_and_style(const char path[], SkString* name,
+ SkTypeface::Style* style, bool* isFixedPitch) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ if (stream.get()) {
+ return find_name_and_attributes(stream, name, style, isFixedPitch);
+ } else {
+ SkDebugf("---- failed to open <%s> as a font\n", path);
+ return false;
+ }
+}
+
+// these globals are assigned (once) by load_system_fonts()
+static SkTypeface* gFallBackTypeface;
+static FamilyRec* gDefaultFamily;
+static SkTypeface* gDefaultNormal;
+
+static void load_directory_fonts(const SkString& directory, unsigned int* count) {
+ SkOSFile::Iter iter(directory.c_str(), ".ttf");
+ SkString name;
+
+ while (iter.next(&name, false)) {
+ SkString filename(directory);
+ filename.append(name);
+
+ bool isFixedPitch;
+ SkString realname;
+ SkTypeface::Style style = SkTypeface::kNormal; // avoid uninitialized warning
+
+ if (!get_name_and_style(filename.c_str(), &realname, &style, &isFixedPitch)) {
+ SkDebugf("------ can't load <%s> as a font\n", filename.c_str());
+ continue;
+ }
+
+ FamilyRec* family = find_familyrec(realname.c_str());
+ if (family && family->fFaces[style]) {
+ continue;
+ }
+
+ // this constructor puts us into the global gFamilyHead llist
+ FamilyTypeface* tf = SkNEW_ARGS(FileTypeface,
+ (style,
+ true, // system-font (cannot delete)
+ family, // what family to join
+ filename.c_str(),
+ isFixedPitch) // filename
+ );
+
+ if (NULL == family) {
+ add_name(realname.c_str(), tf->getFamily());
+ }
+ *count += 1;
+ }
+
+ SkOSFile::Iter dirIter(directory.c_str());
+ while (dirIter.next(&name, true)) {
+ if (name.startsWith(".")) {
+ continue;
+ }
+ SkString dirname(directory);
+ dirname.append(name);
+ dirname.append(SK_FONT_FILE_DIR_SEPERATOR);
+ load_directory_fonts(dirname, count);
+ }
+}
+
+static void load_system_fonts() {
+ // check if we've already be called
+ if (NULL != gDefaultNormal) {
+ return;
+ }
+
+ SkString baseDirectory(SK_FONT_FILE_PREFIX);
+ unsigned int count = 0;
+ load_directory_fonts(baseDirectory, &count);
+
+ if (0 == count) {
+ SkNEW(EmptyTypeface);
+ }
+
+ // do this after all fonts are loaded. This is our default font, and it
+ // acts as a sentinel so we only execute load_system_fonts() once
+ static const char* gDefaultNames[] = {
+ "Arial", "Verdana", "Times New Roman", NULL
+ };
+ const char** names = gDefaultNames;
+ while (*names) {
+ SkTypeface* tf = find_typeface(*names++, SkTypeface::kNormal);
+ if (tf) {
+ gDefaultNormal = tf;
+ break;
+ }
+ }
+ // check if we found *something*
+ if (NULL == gDefaultNormal) {
+ if (NULL == gFamilyHead) {
+ sk_throw();
+ }
+ for (int i = 0; i < 4; i++) {
+ if ((gDefaultNormal = gFamilyHead->fFaces[i]) != NULL) {
+ break;
+ }
+ }
+ }
+ if (NULL == gDefaultNormal) {
+ sk_throw();
+ }
+ gFallBackTypeface = gDefaultNormal;
+ gDefaultFamily = find_family(gDefaultNormal);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FamilyTypeface::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocalStream) const {
+ desc->setFamilyName(find_family_name(this));
+ desc->setFontFileName(this->getUniqueString());
+ *isLocalStream = !this->isSysFont();
+}
+
+static SkTypeface* create_typeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ load_system_fonts();
+
+ SkAutoMutexAcquire ac(gFamilyMutex);
+
+ // clip to legal style bits
+ style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic);
+
+ SkTypeface* tf = NULL;
+
+ if (NULL != familyFace) {
+ tf = find_typeface(familyFace, style);
+ } else if (NULL != familyName) {
+ // SkDebugf("======= familyName <%s>\n", familyName);
+ tf = find_typeface(familyName, style);
+ }
+
+ if (NULL == tf) {
+ tf = find_best_face(gDefaultFamily, style);
+ }
+
+ SkSafeRef(tf);
+ return tf;
+}
+
+SkTypeface* FamilyTypeface::onRefMatchingStyle(Style style) const {
+ return create_typeface(this, NULL, style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_FONTHOST_USES_FONTMGR
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ return create_typeface(familyFace, NULL, style);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ if (NULL == stream || stream->getLength() <= 0) {
+ SkDELETE(stream);
+ return NULL;
+ }
+
+ bool isFixedPitch;
+ SkTypeface::Style style;
+ if (find_name_and_attributes(stream, NULL, &style, &isFixedPitch)) {
+ return SkNEW_ARGS(StreamTypeface, (style, false, NULL, stream, isFixedPitch));
+ } else {
+ return NULL;
+ }
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return stream.get() ? CreateTypefaceFromStream(stream) : NULL;
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontMgr.h"
+
+SkFontMgr* SkFontMgr::Factory() {
+ // todo
+ return NULL;
+}
diff --git a/ports/SkFontHost_mac.cpp b/ports/SkFontHost_mac.cpp
new file mode 100755
index 00000000..987f1851
--- /dev/null
+++ b/ports/SkFontHost_mac.cpp
@@ -0,0 +1,2314 @@
+
+/*
+ * 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 <vector>
+#ifdef SK_BUILD_FOR_MAC
+#import <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef SK_BUILD_FOR_IOS
+#include <CoreText/CoreText.h>
+#include <CoreText/CTFontManager.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#include "SkFontHost.h"
+#include "SkCGUtils.h"
+#include "SkColorPriv.h"
+#include "SkDescriptor.h"
+#include "SkEndian.h"
+#include "SkFontDescriptor.h"
+#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"
+#include "SkStream.h"
+#include "SkThread.h"
+#include "SkTypeface_mac.h"
+#include "SkUtils.h"
+#include "SkTypefaceCache.h"
+#include "SkFontMgr.h"
+#include "SkUtils.h"
+
+//#define HACK_COLORGLYPHS
+
+class SkScalerContext_Mac;
+
+// CTFontManagerCopyAvailableFontFamilyNames() is not always available, so we
+// provide a wrapper here that will return an empty array if need be.
+static CFArrayRef SkCTFontManagerCopyAvailableFontFamilyNames() {
+#ifdef SK_BUILD_FOR_IOS
+ return CFArrayCreate(NULL, NULL, 0, NULL);
+#else
+ return CTFontManagerCopyAvailableFontFamilyNames();
+#endif
+}
+
+
+// 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);
+ }
+}
+
+// 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:
+ 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; }
+
+ CFRef* operator&() { SkASSERT(fCFRef == NULL); return &fCFRef; }
+private:
+ CFRef fCFRef;
+};
+
+static CFStringRef make_CFString(const char str[]) {
+ return CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8);
+}
+
+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
+
+static bool CGRectIsEmpty_inline(const CGRect& rect) {
+ return rect.size.width <= 0 || rect.size.height <= 0;
+}
+
+static CGFloat CGRectGetMinX_inline(const CGRect& rect) {
+ return rect.origin.x;
+}
+
+static CGFloat CGRectGetMaxX_inline(const CGRect& rect) {
+ return rect.origin.x + rect.size.width;
+}
+
+static CGFloat CGRectGetMinY_inline(const CGRect& rect) {
+ return rect.origin.y;
+}
+
+static CGFloat CGRectGetMaxY_inline(const CGRect& rect) {
+ return rect.origin.y + rect.size.height;
+}
+
+static CGFloat CGRectGetWidth_inline(const CGRect& rect) {
+ return rect.size.width;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+
+ if (width >= 32) {
+ while (height) {
+ sk_memset32(ptr, value, width);
+ ptr = (uint32_t*)((char*)ptr + rowBytes);
+ height -= 1;
+ }
+ return;
+ }
+
+ rowBytes -= width * sizeof(uint32_t);
+
+ if (width >= 8) {
+ while (height) {
+ int w = width;
+ do {
+ *ptr++ = value; *ptr++ = value;
+ *ptr++ = value; *ptr++ = value;
+ *ptr++ = value; *ptr++ = value;
+ *ptr++ = value; *ptr++ = value;
+ w -= 8;
+ } while (w >= 8);
+ while (--w >= 0) {
+ *ptr++ = value;
+ }
+ ptr = (uint32_t*)((char*)ptr + rowBytes);
+ height -= 1;
+ }
+ } else {
+ while (height) {
+ int w = width;
+ do {
+ *ptr++ = value;
+ } while (--w > 0);
+ ptr = (uint32_t*)((char*)ptr + rowBytes);
+ height -= 1;
+ }
+ }
+}
+
+#include <sys/utsname.h>
+
+typedef uint32_t CGRGBPixel;
+
+static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
+ return pixel & 0xFF;
+}
+
+// The calls to support subpixel are present in 10.5, but are not included in
+// the 10.5 SDK. The needed calls have been extracted from the 10.6 SDK and are
+// included below. To verify that CGContextSetShouldSubpixelQuantizeFonts, for
+// instance, is present in the 10.5 CoreGraphics libary, use:
+// cd /Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/
+// 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 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";
+
+// See Source/WebKit/chromium/base/mac/mac_util.mm DarwinMajorVersionInternal for original source.
+static int readVersion() {
+ struct utsname info;
+ if (uname(&info) != 0) {
+ SkDebugf("uname failed\n");
+ return 0;
+ }
+ if (strcmp(info.sysname, "Darwin") != 0) {
+ SkDebugf("unexpected uname sysname %s\n", info.sysname);
+ return 0;
+ }
+ char* dot = strchr(info.release, '.');
+ if (!dot) {
+ SkDebugf("expected dot in uname release %s\n", info.release);
+ return 0;
+ }
+ int version = atoi(info.release);
+ if (version == 0) {
+ SkDebugf("could not parse uname release %s\n", info.release);
+ }
+ return version;
+}
+
+static int darwinVersion() {
+ static int darwin_version = readVersion();
+ return darwin_version;
+}
+
+static bool isSnowLeopard() {
+ return darwinVersion() == 10;
+}
+
+static bool isLion() {
+ return darwinVersion() == 11;
+}
+
+static bool isMountainLion() {
+ return darwinVersion() == 12;
+}
+
+static bool isLCDFormat(unsigned format) {
+ return SkMask::kLCD16_Format == format || SkMask::kLCD32_Format == format;
+}
+
+static CGFloat ScalarToCG(SkScalar scalar) {
+ if (sizeof(CGFloat) == sizeof(float)) {
+ return SkScalarToFloat(scalar);
+ } else {
+ SkASSERT(sizeof(CGFloat) == sizeof(double));
+ return (CGFloat) SkScalarToDouble(scalar);
+ }
+}
+
+static SkScalar CGToScalar(CGFloat cgFloat) {
+ if (sizeof(CGFloat) == sizeof(float)) {
+ return SkFloatToScalar(cgFloat);
+ } else {
+ SkASSERT(sizeof(CGFloat) == sizeof(double));
+ return SkDoubleToScalar(cgFloat);
+ }
+}
+
+static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix,
+ 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));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#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
+ * font smoothing will be applied if we request it. The main issue is that if
+ * smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0.
+ */
+static bool supports_LCD() {
+ static int gSupportsLCD = -1;
+ if (gSupportsLCD >= 0) {
+ return (bool) gSupportsLCD;
+ }
+ 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);
+ CGContextShowTextAtPoint(cgContext, -1, 0, "|", 1);
+ 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();
+
+ CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
+ CGGlyph glyphID, size_t* rowBytesPtr,
+ bool generateA8FromLCD);
+
+private:
+ enum {
+ kSize = 32 * 32 * sizeof(CGRGBPixel)
+ };
+ SkAutoSMalloc<kSize> fImageStorage;
+ AutoCFRelease<CGColorSpaceRef> fRGBSpace;
+
+ // cached state
+ AutoCFRelease<CGContextRef> fCG;
+ SkISize fSize;
+ bool fDoAA;
+ bool fDoLCD;
+
+ static int RoundSize(int dimension) {
+ return SkNextPow2(dimension);
+ }
+};
+
+Offscreen::Offscreen() : fRGBSpace(NULL), fCG(NULL),
+ fDoAA(false), fDoLCD(false) {
+ fSize.set(0, 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkTypeface::Style computeStyleBits(CTFontRef font, bool* isFixedPitch) {
+ unsigned style = SkTypeface::kNormal;
+ CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font);
+
+ if (traits & kCTFontBoldTrait) {
+ style |= SkTypeface::kBold;
+ }
+ if (traits & kCTFontItalicTrait) {
+ style |= SkTypeface::kItalic;
+ }
+ if (isFixedPitch) {
+ *isFixedPitch = (traits & kCTFontMonoSpaceTrait) != 0;
+ }
+ return (SkTypeface::Style)style;
+}
+
+static SkFontID CTFontRef_to_SkFontID(CTFontRef fontRef) {
+ SkFontID id = 0;
+// CTFontGetPlatformFont and ATSFontRef are not supported on iOS, so we have to
+// bracket this to be Mac only.
+#ifdef SK_BUILD_FOR_MAC
+ ATSFontRef ats = CTFontGetPlatformFont(fontRef, NULL);
+ id = (SkFontID)ats;
+ if (id != 0) {
+ id &= 0x3FFFFFFF; // make top two bits 00
+ return id;
+ }
+#endif
+ // CTFontGetPlatformFont returns NULL if the font is local
+ // (e.g., was created by a CSS3 @font-face rule).
+ 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.
+ if (id == 0) {
+ id = (SkFontID) (uintptr_t) fontRef;
+ id = (id & 0x3FFFFFFF) | 0x80000000; // make top two bits 10
+ }
+ return id;
+}
+
+static SkFontStyle stylebits2fontstyle(SkTypeface::Style styleBits) {
+ return SkFontStyle((styleBits & SkTypeface::kBold)
+ ? SkFontStyle::kBold_Weight
+ : SkFontStyle::kNormal_Weight,
+ SkFontStyle::kNormal_Width,
+ (styleBits & SkTypeface::kItalic)
+ ? SkFontStyle::kItalic_Slant
+ : SkFontStyle::kUpright_Slant);
+}
+
+#define WEIGHT_THRESHOLD ((SkFontStyle::kNormal_Weight + SkFontStyle::kBold_Weight)/2)
+
+static SkTypeface::Style fontstyle2stylebits(const SkFontStyle& fs) {
+ unsigned style = 0;
+ if (fs.width() >= WEIGHT_THRESHOLD) {
+ style |= SkTypeface::kBold;
+ }
+ if (fs.isItalic()) {
+ style |= SkTypeface::kItalic;
+ }
+ return (SkTypeface::Style)style;
+}
+
+class SkTypeface_Mac : public SkTypeface {
+public:
+ SkTypeface_Mac(SkTypeface::Style style, SkFontID fontID, bool isFixedPitch,
+ CTFontRef fontRef, const char name[])
+ : SkTypeface(style, fontID, isFixedPitch)
+ , fName(name)
+ , fFontRef(fontRef) // caller has already called CFRetain for us
+ , fFontStyle(stylebits2fontstyle(style))
+ {
+ SkASSERT(fontRef);
+ }
+
+ SkTypeface_Mac(const SkFontStyle& fs, SkFontID fontID, bool isFixedPitch,
+ CTFontRef fontRef, const char name[])
+ : SkTypeface(fontstyle2stylebits(fs), fontID, isFixedPitch)
+ , fName(name)
+ , fFontRef(fontRef) // caller has already called CFRetain for us
+ , fFontStyle(fs)
+ {
+ SkASSERT(fontRef);
+ }
+
+ SkString fName;
+ AutoCFRelease<CTFontRef> fFontRef;
+ SkFontStyle fFontStyle;
+
+protected:
+ friend class SkFontHost; // to access our protected members for deprecated methods
+
+ virtual int onGetUPEM() const SK_OVERRIDE;
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE;
+ virtual SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE;
+ virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE;
+ virtual size_t onGetTableData(SkFontTableTag, size_t offset,
+ size_t length, void* data) const SK_OVERRIDE;
+ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor*) const SK_OVERRIDE;
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE;
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE;
+ virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo,
+ const uint32_t*, uint32_t) const SK_OVERRIDE;
+ virtual int onCharsToGlyphs(const void* chars, Encoding, uint16_t glyphs[],
+ int glyphCount) const SK_OVERRIDE;
+ virtual int onCountGlyphs() const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style) const SK_OVERRIDE;
+
+private:
+
+ typedef SkTypeface INHERITED;
+};
+
+static SkTypeface* NewFromFontRef(CTFontRef fontRef, const char name[]) {
+ SkASSERT(fontRef);
+ bool isFixedPitch;
+ SkTypeface::Style style = computeStyleBits(fontRef, &isFixedPitch);
+ SkFontID fontID = CTFontRef_to_SkFontID(fontRef);
+
+ return new SkTypeface_Mac(style, fontID, isFixedPitch, fontRef, name);
+}
+
+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
+ AutoCFRelease<CFStringRef> cfFontName(make_CFString(familyName));
+
+ 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);
+
+ AutoCFRelease<CTFontDescriptorRef> ctFontDesc(
+ CTFontDescriptorCreateWithAttributes(cfAttributes));
+
+ if (ctFontDesc != NULL) {
+ ctFont = CTFontCreateWithFontDescriptor(ctFontDesc, 0, NULL);
+ }
+ }
+
+ return ctFont ? NewFromFontRef(ctFont, familyName) : NULL;
+}
+
+static SkTypeface* GetDefaultFace() {
+ SK_DECLARE_STATIC_MUTEX(gMutex);
+ SkAutoMutexAcquire ma(gMutex);
+
+ static SkTypeface* gDefaultFace;
+
+ if (NULL == gDefaultFace) {
+ gDefaultFace = NewFromName(FONT_DEFAULT_NAME, SkTypeface::kNormal);
+ SkTypefaceCache::Add(gDefaultFace, SkTypeface::kNormal);
+ }
+ return gDefaultFace;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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.get() : NULL;
+}
+
+/* This function is visible on the outside. It first searches the cache, and if
+ * not found, returns a new entry (after adding it to the cache).
+ */
+SkTypeface* SkCreateTypefaceFromCTFont(CTFontRef fontRef) {
+ SkFontID fontID = CTFontRef_to_SkFontID(fontRef);
+ SkTypeface* face = SkTypefaceCache::FindByID(fontID);
+ if (face) {
+ face->ref();
+ } else {
+ face = NewFromFontRef(fontRef, NULL);
+ SkTypefaceCache::Add(face, face->style());
+ // NewFromFontRef doesn't retain the parameter, but the typeface it
+ // creates does release it in its destructor, so we balance that with
+ // a retain call here.
+ CFRetain(fontRef);
+ }
+ SkASSERT(face->getRefCnt() > 1);
+ return face;
+}
+
+struct NameStyleRec {
+ const char* fName;
+ SkTypeface::Style fStyle;
+};
+
+static bool FindByNameStyle(SkTypeface* face, SkTypeface::Style style,
+ void* ctx) {
+ const SkTypeface_Mac* mface = reinterpret_cast<SkTypeface_Mac*>(face);
+ const NameStyleRec* rec = reinterpret_cast<const NameStyleRec*>(ctx);
+
+ return rec->fStyle == style && mface->fName.equals(rec->fName);
+}
+
+static const char* map_css_names(const char* name) {
+ static const struct {
+ const char* fFrom; // name the caller specified
+ const char* fTo; // "canonical" name we map to
+ } gPairs[] = {
+ { "sans-serif", "Helvetica" },
+ { "serif", "Times" },
+ { "monospace", "Courier" }
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
+ if (strcmp(name, gPairs[i].fFrom) == 0) {
+ return gPairs[i].fTo;
+ }
+ }
+ return name; // no change
+}
+
+static SkTypeface* create_typeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ if (familyName) {
+ familyName = map_css_names(familyName);
+ }
+
+ // Clone an existing typeface
+ // TODO: only clone if style matches the familyFace's style...
+ if (familyName == NULL && familyFace != NULL) {
+ familyFace->ref();
+ return const_cast<SkTypeface*>(familyFace);
+ }
+
+ if (!familyName || !*familyName) {
+ familyName = FONT_DEFAULT_NAME;
+ }
+
+ NameStyleRec rec = { familyName, style };
+ SkTypeface* face = SkTypefaceCache::FindByProcAndRef(FindByNameStyle, &rec);
+
+ if (NULL == face) {
+ face = NewFromName(familyName, style);
+ if (face) {
+ SkTypefaceCache::Add(face, style);
+ } else {
+ face = GetDefaultFace();
+ face->ref();
+ }
+ }
+ return face;
+}
+
+SkTypeface* SkTypeface_Mac::onRefMatchingStyle(Style styleBits) const {
+ return create_typeface(this, NULL, styleBits);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** GlyphRect is in FUnits (em space, y up). */
+struct GlyphRect {
+ int16_t fMinX;
+ int16_t fMinY;
+ int16_t fMaxX;
+ int16_t fMaxY;
+};
+
+class SkScalerContext_Mac : public SkScalerContext {
+public:
+ SkScalerContext_Mac(SkTypeface_Mac*, const SkDescriptor*);
+
+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) 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);
+
+ /** Returns the offset from the horizontal origin to the vertical origin in SkGlyph units. */
+ void getVerticalOffset(CGGlyph glyphID, SkPoint* offset) const;
+
+ /** Initializes and returns the value of fFBoundingBoxesGlyphOffset.
+ *
+ * For use with (and must be called before) generateBBoxes.
+ */
+ uint16_t getFBoundingBoxesGlyphOffset();
+
+ /** Initializes fFBoundingBoxes and returns true on success.
+ *
+ * On Lion and Mountain Lion, CTFontGetBoundingRectsForGlyphs has a bug which causes it to
+ * return a bad value in bounds.origin.x for SFNT fonts whose hhea::numberOfHMetrics is
+ * less than its maxp::numGlyphs. When this is the case we try to read the bounds from the
+ * font directly.
+ *
+ * This routine initializes fFBoundingBoxes to an array of
+ * fGlyphCount - fFBoundingBoxesGlyphOffset GlyphRects which contain the bounds in FUnits
+ * (em space, y up) of glyphs with ids in the range [fFBoundingBoxesGlyphOffset, fGlyphCount).
+ *
+ * Returns true if fFBoundingBoxes is properly initialized. The table can only be properly
+ * initialized for a TrueType font with 'head', 'loca', and 'glyf' tables.
+ *
+ * TODO: A future optimization will compute fFBoundingBoxes once per fCTFont.
+ */
+ bool generateBBoxes();
+
+ /** Converts from FUnits (em space, y up) to SkGlyph units (pixels, y down).
+ *
+ * Used on Snow Leopard to correct CTFontGetVerticalTranslationsForGlyphs.
+ * Used on Lion to correct CTFontGetBoundingRectsForGlyphs.
+ */
+ SkMatrix fFUnitMatrix;
+
+ Offscreen fOffscreen;
+ AutoCFRelease<CTFontRef> fCTFont;
+
+ /** Vertical variant of fCTFont.
+ *
+ * CT vertical metrics are pre-rotated (in em space, before transform) 90deg clock-wise.
+ * This makes kCTFontDefaultOrientation dangerous, because the metrics from
+ * kCTFontHorizontalOrientation are in a different space from kCTFontVerticalOrientation.
+ * Use fCTVerticalFont with kCTFontVerticalOrientation to get metrics in the same space.
+ */
+ AutoCFRelease<CTFontRef> fCTVerticalFont;
+
+ AutoCFRelease<CGFontRef> fCGFont;
+ SkAutoTMalloc<GlyphRect> fFBoundingBoxes;
+ uint16_t fFBoundingBoxesGlyphOffset;
+ uint16_t fGlyphCount;
+ bool fGeneratedFBoundingBoxes;
+ const bool fDoSubPosition;
+ const bool fVertical;
+
+ friend class Offscreen;
+
+ typedef SkScalerContext INHERITED;
+};
+
+SkScalerContext_Mac::SkScalerContext_Mac(SkTypeface_Mac* typeface,
+ const SkDescriptor* desc)
+ : INHERITED(typeface, desc)
+ , fFBoundingBoxes()
+ , fFBoundingBoxesGlyphOffset(0)
+ , fGeneratedFBoundingBoxes(false)
+ , fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag))
+ , fVertical(SkToBool(fRec.fFlags & kVertical_Flag))
+
+{
+ CTFontRef ctFont = typeface->fFontRef.get();
+ CFIndex numGlyphs = CTFontGetGlyphCount(ctFont);
+ SkASSERT(numGlyphs >= 1 && numGlyphs <= 0xFFFF);
+ fGlyphCount = SkToU16(numGlyphs);
+
+ fRec.getSingleMatrix(&fFUnitMatrix);
+ CGAffineTransform transform = MatrixToCGAffineTransform(fFUnitMatrix);
+
+ AutoCFRelease<CTFontDescriptorRef> ctFontDesc;
+ if (fVertical) {
+ AutoCFRelease<CFMutableDictionaryRef> cfAttributes(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ if (cfAttributes) {
+ CTFontOrientation ctOrientation = kCTFontVerticalOrientation;
+ AutoCFRelease<CFNumberRef> cfVertical(CFNumberCreate(
+ kCFAllocatorDefault, kCFNumberSInt32Type, &ctOrientation));
+ CFDictionaryAddValue(cfAttributes, kCTFontOrientationAttribute, cfVertical);
+ ctFontDesc = CTFontDescriptorCreateWithAttributes(cfAttributes);
+ }
+ }
+ // Since our matrix includes everything, we pass 1 for size.
+ fCTFont = CTFontCreateCopyWithAttributes(ctFont, 1, &transform, ctFontDesc);
+ fCGFont = CTFontCopyGraphicsFont(fCTFont, NULL);
+ if (fVertical) {
+ CGAffineTransform rotateLeft = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
+ transform = CGAffineTransformConcat(rotateLeft, transform);
+ fCTVerticalFont = CTFontCreateCopyWithAttributes(ctFont, 1, &transform, NULL);
+ }
+
+ SkScalar emPerFUnit = SkScalarInvert(SkIntToScalar(CGFontGetUnitsPerEm(fCGFont)));
+ fFUnitMatrix.preScale(emPerFUnit, -emPerFUnit);
+}
+
+CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
+ CGGlyph glyphID, size_t* rowBytesPtr,
+ bool generateA8FromLCD) {
+ if (!fRGBSpace) {
+ //It doesn't appear to matter what color space is specified.
+ //Regular blends and antialiased text are always (s*a + d*(1-a))
+ //and smoothed text is always g=2.0.
+ fRGBSpace = CGColorSpaceCreateDeviceRGB();
+ }
+
+ // default to kBW_Format
+ bool doAA = false;
+ bool doLCD = false;
+
+ if (SkMask::kBW_Format != glyph.fMaskFormat) {
+ doLCD = true;
+ doAA = true;
+ }
+
+ // FIXME: lcd smoothed un-hinted rasterization unsupported.
+ if (!generateA8FromLCD && SkMask::kA8_Format == glyph.fMaskFormat) {
+ doLCD = false;
+ doAA = true;
+ }
+
+ size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
+ if (!fCG || fSize.fWidth < glyph.fWidth || fSize.fHeight < glyph.fHeight) {
+ if (fSize.fWidth < glyph.fWidth) {
+ fSize.fWidth = RoundSize(glyph.fWidth);
+ }
+ if (fSize.fHeight < glyph.fHeight) {
+ fSize.fHeight = RoundSize(glyph.fHeight);
+ }
+
+ rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
+ void* image = fImageStorage.reset(rowBytes * fSize.fHeight);
+ fCG = CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8,
+ rowBytes, fRGBSpace, BITMAP_INFO_RGB);
+
+ // skia handles quantization itself, so we disable this for cg to get
+ // full fractional data from them.
+ CGContextSetAllowsFontSubpixelQuantization(fCG, false);
+ CGContextSetShouldSubpixelQuantizeFonts(fCG, false);
+
+ CGContextSetTextDrawingMode(fCG, kCGTextFill);
+ CGContextSetFont(fCG, context.fCGFont);
+ CGContextSetFontSize(fCG, 1 /*CTFontGetSize(context.fCTFont)*/);
+ CGContextSetTextMatrix(fCG, CTFontGetMatrix(context.fCTFont));
+
+ // Because CG always draws from the horizontal baseline,
+ // if there is a non-integral translation from the horizontal origin to the vertical origin,
+ // then CG cannot draw the glyph in the correct location without subpixel positioning.
+ CGContextSetAllowsFontSubpixelPositioning(fCG, context.fDoSubPosition || context.fVertical);
+ CGContextSetShouldSubpixelPositionFonts(fCG, context.fDoSubPosition || context.fVertical);
+
+ // Draw white on black to create mask.
+ // TODO: Draw black on white and invert, CG has a special case codepath.
+ CGContextSetGrayFillColor(fCG, 1.0f, 1.0f);
+
+ // force our checks below to happen
+ fDoAA = !doAA;
+ fDoLCD = !doLCD;
+ }
+
+ if (fDoAA != doAA) {
+ CGContextSetShouldAntialias(fCG, doAA);
+ fDoAA = doAA;
+ }
+ if (fDoLCD != doLCD) {
+ CGContextSetShouldSmoothFonts(fCG, doLCD);
+ fDoLCD = doLCD;
+ }
+
+ CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get();
+ // skip rows based on the glyph's height
+ image += (fSize.fHeight - glyph.fHeight) * fSize.fWidth;
+
+ // erase to black
+ sk_memset_rect32(image, 0, glyph.fWidth, glyph.fHeight, rowBytes);
+
+ float subX = 0;
+ float subY = 0;
+ if (context.fDoSubPosition) {
+ subX = SkFixedToFloat(glyph.getSubXFixed());
+ subY = SkFixedToFloat(glyph.getSubYFixed());
+ }
+
+ // CGContextShowGlyphsAtPoint always draws using the horizontal baseline origin.
+ if (context.fVertical) {
+ SkPoint offset;
+ context.getVerticalOffset(glyphID, &offset);
+ subX += offset.fX;
+ subY += offset.fY;
+ }
+
+ CGContextShowGlyphsAtPoint(fCG, -glyph.fLeft + subX,
+ glyph.fTop + glyph.fHeight - subY,
+ &glyphID, 1);
+
+ SkASSERT(rowBytesPtr);
+ *rowBytesPtr = rowBytes;
+ return image;
+}
+
+void SkScalerContext_Mac::getVerticalOffset(CGGlyph glyphID, SkPoint* offset) const {
+ // Snow Leopard returns cgVertOffset in completely un-transformed FUnits (em space, y up).
+ // Lion and Leopard return cgVertOffset in CG units (pixels, y up).
+ CGSize cgVertOffset;
+ CTFontGetVerticalTranslationsForGlyphs(fCTFont, &glyphID, &cgVertOffset, 1);
+
+ SkPoint skVertOffset = { CGToScalar(cgVertOffset.width), CGToScalar(cgVertOffset.height) };
+ if (isSnowLeopard()) {
+ // From FUnits (em space, y up) to SkGlyph units (pixels, y down).
+ fFUnitMatrix.mapPoints(&skVertOffset, 1);
+ } else {
+ // From CG units (pixels, y up) to SkGlyph units (pixels, y down).
+ skVertOffset.fY = -skVertOffset.fY;
+ }
+
+ *offset = skVertOffset;
+}
+
+uint16_t SkScalerContext_Mac::getFBoundingBoxesGlyphOffset() {
+ if (fFBoundingBoxesGlyphOffset) {
+ return fFBoundingBoxesGlyphOffset;
+ }
+ fFBoundingBoxesGlyphOffset = fGlyphCount; // fallback for all fonts
+ AutoCGTable<SkOTTableHorizontalHeader> hheaTable(fCGFont);
+ if (hheaTable.fData) {
+ fFBoundingBoxesGlyphOffset = SkEndian_SwapBE16(hheaTable->numberOfHMetrics);
+ }
+ return fFBoundingBoxesGlyphOffset;
+}
+
+bool SkScalerContext_Mac::generateBBoxes() {
+ if (fGeneratedFBoundingBoxes) {
+ return NULL != fFBoundingBoxes.get();
+ }
+ fGeneratedFBoundingBoxes = true;
+
+ AutoCGTable<SkOTTableHead> headTable(fCGFont);
+ if (!headTable.fData) {
+ return false;
+ }
+
+ AutoCGTable<SkOTTableIndexToLocation> locaTable(fCGFont);
+ if (!locaTable.fData) {
+ return false;
+ }
+
+ AutoCGTable<SkOTTableGlyph> glyfTable(fCGFont);
+ if (!glyfTable.fData) {
+ return false;
+ }
+
+ uint16_t entries = fGlyphCount - fFBoundingBoxesGlyphOffset;
+ fFBoundingBoxes.reset(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);
+ }
+
+ return true;
+}
+
+unsigned SkScalerContext_Mac::generateGlyphCount(void) {
+ return fGlyphCount;
+}
+
+uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni) {
+ CGGlyph cgGlyph;
+ UniChar theChar;
+
+ // Validate our parameters and state
+ SkASSERT(uni <= 0x0000FFFF);
+ SkASSERT(sizeof(CGGlyph) <= sizeof(uint16_t));
+
+ // Get the glyph
+ theChar = (UniChar) uni;
+
+ if (!CTFontGetGlyphsForCharacters(fCTFont, &theChar, &cgGlyph, 1)) {
+ cgGlyph = 0;
+ }
+
+ return cgGlyph;
+}
+
+void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
+ this->generateMetrics(glyph);
+}
+
+void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
+ const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID(fBaseGlyphCount);
+ glyph->zeroMetrics();
+
+ // The following block produces cgAdvance in CG units (pixels, y up).
+ CGSize cgAdvance;
+ if (fVertical) {
+ CTFontGetAdvancesForGlyphs(fCTVerticalFont, kCTFontVerticalOrientation,
+ &cgGlyph, &cgAdvance, 1);
+ } else {
+ CTFontGetAdvancesForGlyphs(fCTFont, kCTFontHorizontalOrientation,
+ &cgGlyph, &cgAdvance, 1);
+ }
+ glyph->fAdvanceX = SkFloatToFixed_Check(cgAdvance.width);
+ glyph->fAdvanceY = -SkFloatToFixed_Check(cgAdvance.height);
+
+ // The following produces skBounds in SkGlyph units (pixels, y down),
+ // or returns early if skBounds would be empty.
+ SkRect skBounds;
+
+ // On Mountain Lion, CTFontGetBoundingRectsForGlyphs with kCTFontVerticalOrientation and
+ // CTFontGetVerticalTranslationsForGlyphs do not agree when using OTF CFF fonts.
+ // For TTF fonts these two do agree and we can use CTFontGetBoundingRectsForGlyphs to get
+ // the bounding box and CTFontGetVerticalTranslationsForGlyphs to then draw the glyph
+ // inside that bounding box. However, with OTF CFF fonts this does not work. It appears that
+ // CTFontGetBoundingRectsForGlyphs with kCTFontVerticalOrientation on OTF CFF fonts tries
+ // to center the glyph along the vertical baseline and also perform some mysterious shift
+ // along the baseline. CTFontGetVerticalTranslationsForGlyphs does not appear to perform
+ // these steps.
+ //
+ // It is not known which is correct (or if either is correct). However, we must always draw
+ // from the horizontal origin and must use CTFontGetVerticalTranslationsForGlyphs to draw.
+ // As a result, we do not call CTFontGetBoundingRectsForGlyphs for vertical glyphs.
+
+ // On Snow Leopard, CTFontGetBoundingRectsForGlyphs ignores kCTFontVerticalOrientation and
+ // returns horizontal bounds.
+
+ // On Lion and Mountain Lion, CTFontGetBoundingRectsForGlyphs has a bug which causes it to
+ // return a bad value in cgBounds.origin.x for SFNT fonts whose hhea::numberOfHMetrics is
+ // less than its maxp::numGlyphs. When this is the case we try to read the bounds from the
+ // font directly.
+ if ((isLion() || isMountainLion()) &&
+ (cgGlyph < fGlyphCount && cgGlyph >= getFBoundingBoxesGlyphOffset() && generateBBoxes()))
+ {
+ const GlyphRect& gRect = fFBoundingBoxes[cgGlyph - fFBoundingBoxesGlyphOffset];
+ if (gRect.fMinX >= gRect.fMaxX || gRect.fMinY >= gRect.fMaxY) {
+ return;
+ }
+ skBounds = SkRect::MakeLTRB(gRect.fMinX, gRect.fMinY, gRect.fMaxX, gRect.fMaxY);
+ // From FUnits (em space, y up) to SkGlyph units (pixels, y down).
+ fFUnitMatrix.mapRect(&skBounds);
+
+ } else {
+ // CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up).
+ CGRect cgBounds;
+ CTFontGetBoundingRectsForGlyphs(fCTFont, kCTFontHorizontalOrientation,
+ &cgGlyph, &cgBounds, 1);
+
+ // BUG?
+ // 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when
+ // 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 == cgAdvance.width && 0 == cgAdvance.height) {
+ AutoCFRelease<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont, cgGlyph, NULL));
+ if (NULL == path || CGPathIsEmpty(path)) {
+ return;
+ }
+ }
+
+ if (CGRectIsEmpty_inline(cgBounds)) {
+ return;
+ }
+
+ // Convert cgBounds to SkGlyph units (pixels, y down).
+ skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height,
+ cgBounds.size.width, cgBounds.size.height);
+ }
+
+ if (fVertical) {
+ // Due to all of the vertical bounds bugs, skBounds is always the horizontal bounds.
+ // Convert these horizontal bounds into vertical bounds.
+ SkPoint offset;
+ getVerticalOffset(cgGlyph, &offset);
+ skBounds.offset(offset);
+ }
+
+ // Currently the bounds are based on being rendered at (0,0).
+ // The top left must not move, since that is the base from which subpixel positioning is offset.
+ if (fDoSubPosition) {
+ skBounds.fRight += SkFixedToFloat(glyph->getSubXFixed());
+ skBounds.fBottom += SkFixedToFloat(glyph->getSubYFixed());
+ }
+
+ SkIRect skIBounds;
+ skBounds.roundOut(&skIBounds);
+ // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
+ // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
+ // is not currently known, as CG dilates the outlines by some percentage.
+ // Note that if this context is A8 and not back-forming from LCD, there is no need to outset.
+ skIBounds.outset(1, 1);
+ glyph->fLeft = SkToS16(skIBounds.fLeft);
+ glyph->fTop = SkToS16(skIBounds.fTop);
+ glyph->fWidth = SkToU16(skIBounds.width());
+ glyph->fHeight = SkToU16(skIBounds.height());
+
+#ifdef HACK_COLORGLYPHS
+ glyph->fMaskFormat = SkMask::kARGB32_Format;
+#endif
+}
+
+#include "SkColorPriv.h"
+
+static void build_power_table(uint8_t table[], float ee) {
+ for (int i = 0; i < 256; i++) {
+ float x = i / 255.f;
+ x = sk_float_pow(x, ee);
+ int xx = SkScalarRoundToInt(SkFloatToScalar(x * 255));
+ table[i] = SkToU8(xx);
+ }
+}
+
+/**
+ * This will invert the gamma applied by CoreGraphics, so we can get linear
+ * values.
+ *
+ * CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value.
+ * The color space used does not appear to affect this choice.
+ */
+static const uint8_t* getInverseGammaTableCoreGraphicSmoothing() {
+ static bool gInited;
+ static uint8_t gTableCoreGraphicsSmoothing[256];
+ if (!gInited) {
+ build_power_table(gTableCoreGraphicsSmoothing, 2.0f);
+ gInited = true;
+ }
+ return gTableCoreGraphicsSmoothing;
+}
+
+static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) {
+ while (count > 0) {
+ uint8_t mask = 0;
+ for (int i = 7; i >= 0; --i) {
+ mask |= (CGRGBPixel_getAlpha(*src++) >> 7) << i;
+ if (0 == --count) {
+ break;
+ }
+ }
+ *dst++ = mask;
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) {
+ U8CPU r = (rgb >> 16) & 0xFF;
+ U8CPU g = (rgb >> 8) & 0xFF;
+ U8CPU b = (rgb >> 0) & 0xFF;
+ return sk_apply_lut_if<APPLY_PREBLEND>(SkComputeLuminance(r, g, b), table8);
+}
+template<bool APPLY_PREBLEND>
+static void rgb_to_a8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
+ const SkGlyph& glyph, const uint8_t* table8) {
+ const int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
+
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; ++i) {
+ dst[i] = rgb_to_a8<APPLY_PREBLEND>(cgPixels[i], table8);
+ }
+ cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes);
+ dst += dstRB;
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static inline uint16_t rgb_to_lcd16(CGRGBPixel rgb, const uint8_t* tableR,
+ const uint8_t* tableG,
+ const uint8_t* tableB) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+ return SkPack888ToRGB16(r, g, b);
+}
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ uint16_t* SK_RESTRICT dst = (uint16_t*)glyph.fImage;
+
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; i++) {
+ dst[i] = rgb_to_lcd16<APPLY_PREBLEND>(cgPixels[i], tableR, tableG, tableB);
+ }
+ cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes);
+ dst = (uint16_t*)((char*)dst + dstRB);
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static inline uint32_t rgb_to_lcd32(CGRGBPixel rgb, const uint8_t* tableR,
+ const uint8_t* tableG,
+ const uint8_t* tableB) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+ return SkPackARGB32(0xFF, r, g, b);
+}
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd32(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ uint32_t* SK_RESTRICT dst = (uint32_t*)glyph.fImage;
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; i++) {
+ dst[i] = rgb_to_lcd32<APPLY_PREBLEND>(cgPixels[i], tableR, tableG, tableB);
+ }
+ cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes);
+ dst = (uint32_t*)((char*)dst + dstRB);
+ }
+}
+
+#ifdef HACK_COLORGLYPHS
+// hack to colorize the output for testing kARGB32_Format
+static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb, const SkGlyph& glyph,
+ int x, int y) {
+ U8CPU r = (rgb >> 16) & 0xFF;
+ U8CPU g = (rgb >> 8) & 0xFF;
+ U8CPU b = (rgb >> 0) & 0xFF;
+ unsigned a = SkComputeLuminance(r, g, b);
+
+ // compute gradient from x,y
+ r = x * 255 / glyph.fWidth;
+ g = 0;
+ b = (glyph.fHeight - y) * 255 / glyph.fHeight;
+ return SkPreMultiplyARGB(a, r, g, b); // red
+}
+#endif
+
+template <typename T> T* SkTAddByteOffset(T* ptr, size_t byteOffset) {
+ return (T*)((char*)ptr + byteOffset);
+}
+
+void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) {
+ CGGlyph cgGlyph = (CGGlyph) glyph.getGlyphID(fBaseGlyphCount);
+
+ // FIXME: lcd smoothed un-hinted rasterization unsupported.
+ bool generateA8FromLCD = fRec.getHinting() != SkPaint::kNo_Hinting;
+
+ // Draw the glyph
+ size_t cgRowBytes;
+ CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, generateA8FromLCD);
+ if (cgPixels == NULL) {
+ return;
+ }
+
+ //TODO: see if drawing black on white and inverting is faster (at least in
+ //lcd case) as core graphics appears to have special case code for drawing
+ //black text.
+
+ // Fix the glyph
+ const bool isLCD = isLCDFormat(glyph.fMaskFormat);
+ if (isLCD || (glyph.fMaskFormat == SkMask::kA8_Format && supports_LCD() && generateA8FromLCD)) {
+ const uint8_t* table = getInverseGammaTableCoreGraphicSmoothing();
+
+ //Note that the following cannot really be integrated into the
+ //pre-blend, since we may not be applying the pre-blend; when we aren't
+ //applying the pre-blend it means that a filter wants linear anyway.
+ //Other code may also be applying the pre-blend, so we'd need another
+ //one with this and one without.
+ CGRGBPixel* addr = cgPixels;
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ for (int x = 0; x < glyph.fWidth; ++x) {
+ int r = (addr[x] >> 16) & 0xFF;
+ int g = (addr[x] >> 8) & 0xFF;
+ int b = (addr[x] >> 0) & 0xFF;
+ addr[x] = (table[r] << 16) | (table[g] << 8) | table[b];
+ }
+ addr = SkTAddByteOffset(addr, cgRowBytes);
+ }
+ }
+
+ // Convert glyph to mask
+ switch (glyph.fMaskFormat) {
+ case SkMask::kLCD32_Format: {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd32<false>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } break;
+ case SkMask::kLCD16_Format: {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd16<false>(cgPixels, cgRowBytes, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } break;
+ case SkMask::kA8_Format: {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(cgPixels, cgRowBytes, glyph, fPreBlend.fG);
+ } else {
+ rgb_to_a8<false>(cgPixels, cgRowBytes, glyph, fPreBlend.fG);
+ }
+ } break;
+ case SkMask::kBW_Format: {
+ const int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ uint8_t* dst = (uint8_t*)glyph.fImage;
+ for (int y = 0; y < glyph.fHeight; y++) {
+ cgpixels_to_bits(dst, cgPixels, width);
+ cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes);
+ dst += dstRB;
+ }
+ } break;
+#ifdef HACK_COLORGLYPHS
+ case SkMask::kARGB32_Format: {
+ const int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ SkPMColor* dst = (SkPMColor*)glyph.fImage;
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int x = 0; x < width; ++x) {
+ dst[x] = cgpixels_to_pmcolor(cgPixels[x], glyph, x, y);
+ }
+ cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes);
+ dst = (SkPMColor*)((char*)dst + dstRB);
+ }
+ } break;
+#endif
+ default:
+ SkDEBUGFAIL("unexpected mask format");
+ break;
+ }
+}
+
+/*
+ * Our subpixel resolution is only 2 bits in each direction, so a scale of 4
+ * seems sufficient, and possibly even correct, to allow the hinted outline
+ * to be subpixel positioned.
+ */
+#define kScaleForSubPixelPositionHinting (4.0f)
+
+void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
+ CTFontRef font = fCTFont;
+ SkScalar scaleX = SK_Scalar1;
+ SkScalar scaleY = SK_Scalar1;
+
+ /*
+ * For subpixel positioning, we want to return an unhinted outline, so it
+ * can be positioned nicely at fractional offsets. However, we special-case
+ * if the baseline of the (horizontal) text is axis-aligned. In those cases
+ * we want to retain hinting in the direction orthogonal to the baseline.
+ * e.g. for horizontal baseline, we want to retain hinting in Y.
+ * The way we remove hinting is to scale the font by some value (4) in that
+ * direction, ask for the path, and then scale the path back down.
+ */
+ if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
+ SkMatrix m;
+ fRec.getSingleMatrix(&m);
+
+ // start out by assuming that we want no hining in X and Y
+ scaleX = scaleY = SkFloatToScalar(kScaleForSubPixelPositionHinting);
+ // now see if we need to restore hinting for axis-aligned baselines
+ switch (SkComputeAxisAlignmentForHText(m)) {
+ case kX_SkAxisAlignment:
+ scaleY = SK_Scalar1; // want hinting in the Y direction
+ break;
+ case kY_SkAxisAlignment:
+ scaleX = SK_Scalar1; // want hinting in the X direction
+ break;
+ default:
+ break;
+ }
+
+ CGAffineTransform xform = MatrixToCGAffineTransform(m, scaleX, scaleY);
+ // need to release font when we're done
+ font = CTFontCreateCopyWithAttributes(fCTFont, 1, &xform, 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);
+ }
+
+ if (fDoSubPosition) {
+ SkMatrix m;
+ m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
+ path->transform(m);
+ // balance the call to CTFontCreateCopyWithAttributes
+ CFSafeRelease(font);
+ }
+ if (fVertical) {
+ SkPoint offset;
+ getVerticalOffset(cgGlyph, &offset);
+ path->offset(offset.fX, offset.fY);
+ }
+}
+
+void SkScalerContext_Mac::generateFontMetrics(SkPaint::FontMetrics* mx,
+ SkPaint::FontMetrics* my) {
+ CGRect theBounds = CTFontGetBoundingBox(fCTFont);
+
+ SkPaint::FontMetrics theMetrics;
+ theMetrics.fTop = CGToScalar(-CGRectGetMaxY_inline(theBounds));
+ theMetrics.fAscent = CGToScalar(-CTFontGetAscent(fCTFont));
+ theMetrics.fDescent = CGToScalar( CTFontGetDescent(fCTFont));
+ theMetrics.fBottom = CGToScalar(-CGRectGetMinY_inline(theBounds));
+ theMetrics.fLeading = CGToScalar( CTFontGetLeading(fCTFont));
+ theMetrics.fAvgCharWidth = CGToScalar( CGRectGetWidth_inline(theBounds));
+ theMetrics.fXMin = CGToScalar( CGRectGetMinX_inline(theBounds));
+ theMetrics.fXMax = CGToScalar( CGRectGetMaxX_inline(theBounds));
+ theMetrics.fXHeight = CGToScalar( CTFontGetXHeight(fCTFont));
+
+ if (mx != NULL) {
+ *mx = theMetrics;
+ }
+ if (my != NULL) {
+ *my = theMetrics;
+ }
+}
+
+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);
+ break;
+
+ case kCGPathElementAddLineToPoint:
+ 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);
+ break;
+
+ case kCGPathElementAddCurveToPoint:
+ skPath->cubicTo(element->points[0].x, -element->points[0].y,
+ element->points[1].x, -element->points[1].y,
+ element->points[2].x, -element->points[2].y);
+ break;
+
+ case kCGPathElementCloseSubpath:
+ skPath->close();
+ break;
+
+ default:
+ SkDEBUGFAIL("Unknown path element!");
+ break;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Returns NULL on failure
+// Call must still manage its ownership of provider
+static SkTypeface* create_from_dataProvider(CGDataProviderRef provider) {
+ AutoCFRelease<CGFontRef> cg(CGFontCreateWithDataProvider(provider));
+ if (NULL == cg) {
+ return NULL;
+ }
+ CTFontRef ct = CTFontCreateWithGraphicsFont(cg, 0, NULL, NULL);
+ return cg ? SkCreateTypefaceFromCTFont(ct) : NULL;
+}
+
+// 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, CFIndex glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ glyphToUnicode->setCount(glyphCount);
+ SkUnichar* out = glyphToUnicode->begin();
+ sk_bzero(out, glyphCount * sizeof(SkUnichar));
+ UniChar unichar = 0;
+ while (glyphCount > 0) {
+ CGGlyph glyph;
+ if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) {
+ out[glyph] = unichar;
+ --glyphCount;
+ }
+ if (++unichar == 0) {
+ break;
+ }
+ }
+}
+
+// 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, CFIndex glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ AutoCFRelease<CFCharacterSetRef> charSet(CTFontCopyCharacterSet(ctFont));
+ if (!charSet) {
+ populate_glyph_to_unicode_slow(ctFont, glyphCount, glyphToUnicode);
+ return;
+ }
+
+ AutoCFRelease<CFDataRef> bitmap(CFCharacterSetCreateBitmapRepresentation(kCFAllocatorDefault,
+ charSet));
+ if (!bitmap) {
+ return;
+ }
+ CFIndex length = CFDataGetLength(bitmap);
+ if (!length) {
+ return;
+ }
+ if (length > 8192) {
+ // TODO: Add support for Unicode above 0xFFFF
+ // Consider only the BMP portion of the Unicode character points.
+ // The bitmap may contain other planes, up to plane 16.
+ // See http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFCharacterSetRef/Reference/reference.html
+ length = 8192;
+ }
+ const UInt8* bits = CFDataGetBytePtr(bitmap);
+ glyphToUnicode->setCount(glyphCount);
+ SkUnichar* out = glyphToUnicode->begin();
+ sk_bzero(out, glyphCount * sizeof(SkUnichar));
+ for (int i = 0; i < length; i++) {
+ int mask = bits[i];
+ if (!mask) {
+ continue;
+ }
+ 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)) {
+ out[glyph] = unichar;
+ }
+ }
+ }
+}
+
+static bool getWidthAdvance(CTFontRef ctFont, int gId, int16_t* data) {
+ CGSize advance;
+ advance.width = 0;
+ CGGlyph glyph = gId;
+ CTFontGetAdvancesForGlyphs(ctFont, kCTFontHorizontalOrientation, &glyph, &advance, 1);
+ *data = sk_float_round2int(advance.width);
+ return true;
+}
+
+// we might move this into our CGUtils...
+static void CFStringToSkString(CFStringRef src, SkString* dst) {
+ // Reserve enough room for the worst-case string,
+ // plus 1 byte for the trailing null.
+ CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(src),
+ kCFStringEncodingUTF8) + 1;
+ dst->resize(length);
+ 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()));
+}
+
+SkAdvancedTypefaceMetrics* SkTypeface_Mac::onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+
+ CTFontRef originalCTFont = fFontRef.get();
+ AutoCFRelease<CTFontRef> ctFont(CTFontCreateCopyWithAttributes(
+ originalCTFont, CTFontGetUnitsPerEm(originalCTFont), NULL, NULL));
+ SkAdvancedTypefaceMetrics* info = new SkAdvancedTypefaceMetrics;
+
+ {
+ AutoCFRelease<CFStringRef> fontName(CTFontCopyPostScriptName(ctFont));
+ CFStringToSkString(fontName, &info->fFontName);
+ }
+
+ info->fMultiMaster = false;
+ CFIndex glyphCount = CTFontGetGlyphCount(ctFont);
+ info->fLastGlyphID = SkToU16(glyphCount - 1);
+ info->fEmSize = CTFontGetUnitsPerEm(ctFont);
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo) {
+ populate_glyph_to_unicode(ctFont, glyphCount, &info->fGlyphToUnicode);
+ }
+
+ info->fStyle = 0;
+
+ // If it's not a truetype font, mark it as 'other'. Assume that TrueType
+ // fonts always have both glyf and loca tables. At the least, this is what
+ // sfntly needs to subset the font. CTFontCopyAttribute() does not always
+ // succeed in determining this directly.
+ if (!this->getTableSize('glyf') || !this->getTableSize('loca')) {
+ info->fType = SkAdvancedTypefaceMetrics::kOther_Font;
+ info->fItalicAngle = 0;
+ info->fAscent = 0;
+ info->fDescent = 0;
+ info->fStemV = 0;
+ info->fCapHeight = 0;
+ info->fBBox = SkIRect::MakeEmpty();
+ return info;
+ }
+
+ info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font;
+ CTFontSymbolicTraits symbolicTraits = CTFontGetSymbolicTraits(ctFont);
+ if (symbolicTraits & kCTFontMonoSpaceTrait) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style;
+ }
+ if (symbolicTraits & kCTFontItalicTrait) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
+ }
+ CTFontStylisticClass stylisticClass = symbolicTraits & kCTFontClassMaskTrait;
+ if (stylisticClass >= kCTFontOldStyleSerifsClass && stylisticClass <= kCTFontSlabSerifsClass) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
+ } else if (stylisticClass & kCTFontScriptsClass) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
+ }
+ info->fItalicAngle = (int16_t) CTFontGetSlantAngle(ctFont);
+ info->fAscent = (int16_t) CTFontGetAscent(ctFont);
+ info->fDescent = (int16_t) CTFontGetDescent(ctFont);
+ info->fCapHeight = (int16_t) CTFontGetCapHeight(ctFont);
+ CGRect bbox = CTFontGetBoundingBox(ctFont);
+
+ SkRect r;
+ r.set( CGToScalar(CGRectGetMinX_inline(bbox)), // Left
+ CGToScalar(CGRectGetMaxY_inline(bbox)), // Top
+ CGToScalar(CGRectGetMaxX_inline(bbox)), // Right
+ CGToScalar(CGRectGetMinY_inline(bbox))); // Bottom
+
+ r.roundOut(&(info->fBBox));
+
+ // Figure out a good guess for StemV - Min width of i, I, !, 1.
+ // This probably isn't very good with an italic font.
+ int16_t min_width = SHRT_MAX;
+ info->fStemV = 0;
+ static const UniChar stem_chars[] = {'i', 'I', '!', '1'};
+ const size_t count = sizeof(stem_chars) / sizeof(stem_chars[0]);
+ CGGlyph glyphs[count];
+ CGRect boundingRects[count];
+ if (CTFontGetGlyphsForCharacters(ctFont, stem_chars, glyphs, count)) {
+ CTFontGetBoundingRectsForGlyphs(ctFont, kCTFontHorizontalOrientation,
+ 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) {
+ min_width = width;
+ info->fStemV = min_width;
+ }
+ }
+ }
+
+ 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) {
+ if (info->fStyle & SkAdvancedTypefaceMetrics::kFixedPitch_Style) {
+ skia_advanced_typeface_metrics_utils::appendRange(&info->fGlyphWidths, 0);
+ info->fGlyphWidths->fAdvance.append(1, &min_width);
+ skia_advanced_typeface_metrics_utils::finishRange(info->fGlyphWidths.get(), 0,
+ SkAdvancedTypefaceMetrics::WidthRange::kDefault);
+ } else {
+ info->fGlyphWidths.reset(
+ skia_advanced_typeface_metrics_utils::getAdvanceData(ctFont.get(),
+ glyphCount,
+ glyphIDs,
+ glyphIDsCount,
+ &getWidthAdvance));
+ }
+ }
+ return info;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SK_SFNT_ULONG get_font_type_tag(const SkTypeface_Mac* typeface) {
+ CTFontRef ctFont = typeface->fFontRef.get();
+ AutoCFRelease<CFNumberRef> fontFormatRef(
+ static_cast<CFNumberRef>(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute)));
+ if (!fontFormatRef) {
+ return 0;
+ }
+
+ SInt32 fontFormatValue;
+ if (!CFNumberGetValue(fontFormatRef, kCFNumberSInt32Type, &fontFormatValue)) {
+ return 0;
+ }
+
+ 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;
+ }
+}
+
+SkStream* SkTypeface_Mac::onOpenStream(int* ttcIndex) const {
+ SK_SFNT_ULONG fontType = get_font_type_tag(this);
+ if (0 == fontType) {
+ return NULL;
+ }
+
+ // get table tags
+ int numTables = this->countTables();
+ SkTDArray<SkFontTableTag> tableTags;
+ tableTags.setCount(numTables);
+ this->getTableTags(tableTags.begin());
+
+ // calc total size for font, save sizes
+ SkTDArray<size_t> tableSizes;
+ size_t totalSize = sizeof(SkSFNTHeader) + sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables;
+ for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ size_t tableSize = this->getTableSize(tableTags[tableIndex]);
+ totalSize += (tableSize + 3) & ~3;
+ *tableSizes.append() = tableSize;
+ }
+
+ // reserve memory for stream, and zero it (tables must be zero padded)
+ SkMemoryStream* stream = new SkMemoryStream(totalSize);
+ char* dataStart = (char*)stream->getMemoryBase();
+ sk_bzero(dataStart, totalSize);
+ char* dataPtr = dataStart;
+
+ // compute font header entries
+ uint16_t entrySelector = 0;
+ uint16_t searchRange = 1;
+ while (searchRange < numTables >> 1) {
+ entrySelector++;
+ searchRange <<= 1;
+ }
+ searchRange <<= 4;
+ uint16_t rangeShift = (numTables << 4) - searchRange;
+
+ // 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
+ SkSFNTHeader::TableDirectoryEntry* entry = (SkSFNTHeader::TableDirectoryEntry*)dataPtr;
+ dataPtr += sizeof(SkSFNTHeader::TableDirectoryEntry) * numTables;
+ for (int tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ size_t tableSize = tableSizes[tableIndex];
+ this->getTableData(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;
+ }
+
+ return stream;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int SkTypeface_Mac::onGetUPEM() const {
+ AutoCFRelease<CGFontRef> cgFont(CTFontCopyGraphicsFont(fFontRef, NULL));
+ return CGFontGetUnitsPerEm(cgFont);
+}
+
+SkTypeface::LocalizedStrings* SkTypeface_Mac::onCreateFamilyNameIterator() const {
+ SkTypeface::LocalizedStrings* nameIter =
+ SkOTUtils::LocalizedStrings_NameTable::CreateForFamilyNames(*this);
+ if (NULL == nameIter) {
+ AutoCFRelease<CFStringRef> cfLanguage;
+ AutoCFRelease<CFStringRef> cfFamilyName(
+ CTFontCopyLocalizedName(fFontRef, kCTFontFamilyNameKey, &cfLanguage));
+
+ SkString skLanguage;
+ SkString skFamilyName;
+ if (cfLanguage.get()) {
+ CFStringToSkString(cfLanguage.get(), &skLanguage);
+ } else {
+ skLanguage = "und"; //undetermined
+ }
+ if (cfFamilyName.get()) {
+ CFStringToSkString(cfFamilyName.get(), &skFamilyName);
+ }
+
+ nameIter = new SkOTUtils::LocalizedStrings_SingleName(skFamilyName, skLanguage);
+ }
+ return nameIter;
+}
+
+// If, as is the case with web fonts, the CTFont data isn't available,
+// 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);
+ if (NULL == data) {
+ AutoCFRelease<CGFontRef> cgFont(CTFontCopyGraphicsFont(ctFont, NULL));
+ data = CGFontCopyTableForTag(cgFont, tag);
+ }
+ return data;
+}
+
+int SkTypeface_Mac::onGetTableTags(SkFontTableTag tags[]) const {
+ AutoCFRelease<CFArrayRef> cfArray(CTFontCopyAvailableTables(fFontRef,
+ kCTFontTableOptionNoOptions));
+ if (NULL == cfArray) {
+ return 0;
+ }
+ int count = CFArrayGetCount(cfArray);
+ if (tags) {
+ for (int i = 0; i < count; ++i) {
+ uintptr_t fontTag = reinterpret_cast<uintptr_t>(CFArrayGetValueAtIndex(cfArray, i));
+ tags[i] = static_cast<SkFontTableTag>(fontTag);
+ }
+ }
+ return count;
+}
+
+size_t SkTypeface_Mac::onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* dstData) const {
+ AutoCFRelease<CFDataRef> srcData(copyTableFromFont(fFontRef, tag));
+ if (NULL == srcData) {
+ return 0;
+ }
+
+ size_t srcSize = CFDataGetLength(srcData);
+ if (offset >= srcSize) {
+ return 0;
+ }
+ if (length > srcSize - offset) {
+ length = srcSize - offset;
+ }
+ if (dstData) {
+ memcpy(dstData, CFDataGetBytePtr(srcData) + offset, length);
+ }
+ return length;
+}
+
+SkScalerContext* SkTypeface_Mac::onCreateScalerContext(const SkDescriptor* desc) const {
+ return new SkScalerContext_Mac(const_cast<SkTypeface_Mac*>(this), desc);
+}
+
+void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const {
+ if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag ||
+ rec->fFlags & SkScalerContext::kLCD_Vertical_Flag)
+ {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ // Render the glyphs as close as possible to what was requested.
+ // The above turns off subpixel rendering, but the user requested it.
+ // Normal hinting will cause the A8 masks to be generated from CoreGraphics subpixel masks.
+ // See comments below for more details.
+ rec->setHinting(SkPaint::kNormal_Hinting);
+ }
+
+ unsigned flagsWeDontSupport = SkScalerContext::kDevKernText_Flag |
+ SkScalerContext::kAutohinting_Flag |
+ SkScalerContext::kLCD_BGROrder_Flag |
+ SkScalerContext::kLCD_Vertical_Flag;
+
+ 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.
+ // 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(hinting);
+
+ // FIXME: lcd smoothed un-hinted rasterization unsupported.
+ // Tracked by http://code.google.com/p/skia/issues/detail?id=915 .
+ // There is no current means to honor a request for unhinted lcd,
+ // so arbitrarilly ignore the hinting request and honor lcd.
+
+ // Hinting and smoothing should be orthogonal, but currently they are not.
+ // CoreGraphics has no API to influence hinting. However, its lcd smoothed
+ // output is drawn from auto-dilated outlines (the amount of which is
+ // determined by AppleFontSmoothing). Its regular anti-aliased output is
+ // drawn from un-dilated outlines.
+
+ // The behavior of Skia is as follows:
+ // [AA][no-hint]: generate AA using CoreGraphic's AA output.
+ // [AA][yes-hint]: use CoreGraphic's LCD output and reduce it to a single
+ // channel. This matches [LCD][yes-hint] in weight.
+ // [LCD][no-hint]: curently unable to honor, and must pick which to respect.
+ // Currenly side with LCD, effectively ignoring the hinting setting.
+ // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output.
+
+ if (isLCDFormat(rec->fMaskFormat)) {
+ if (lcdSupport) {
+ //CoreGraphics creates 555 masks for smoothed text anyway.
+ rec->fMaskFormat = SkMask::kLCD16_Format;
+ rec->setHinting(SkPaint::kNormal_Hinting);
+ } else {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ }
+ }
+
+ // 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);
+ }
+}
+
+// we take ownership of the ref
+static const char* get_str(CFStringRef ref, SkString* str) {
+ CFStringToSkString(ref, str);
+ CFSafeRelease(ref);
+ return str->c_str();
+}
+
+void SkTypeface_Mac::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocalStream) const {
+ SkString tmpStr;
+
+ desc->setFamilyName(get_str(CTFontCopyFamilyName(fFontRef), &tmpStr));
+ desc->setFullName(get_str(CTFontCopyFullName(fFontRef), &tmpStr));
+ desc->setPostscriptName(get_str(CTFontCopyPostScriptName(fFontRef), &tmpStr));
+ // TODO: need to add support for local-streams (here and openStream)
+ *isLocalStream = false;
+}
+
+int SkTypeface_Mac::onCharsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const {
+ // UniChar is utf16
+ SkAutoSTMalloc<1024, UniChar> charStorage;
+ const UniChar* src;
+ switch (encoding) {
+ case kUTF8_Encoding: {
+ const char* u8 = (const char*)chars;
+ const UniChar* u16 = src = charStorage.reset(2 * glyphCount);
+ for (int i = 0; i < glyphCount; ++i) {
+ SkUnichar uni = SkUTF8_NextUnichar(&u8);
+ int n = SkUTF16_FromUnichar(uni, (uint16_t*)u16);
+ u16 += n;
+ }
+ break;
+ }
+ case kUTF16_Encoding:
+ src = (const UniChar*)chars;
+ break;
+ case kUTF32_Encoding: {
+ const SkUnichar* u32 = (const SkUnichar*)chars;
+ const UniChar* u16 = src = charStorage.reset(2 * glyphCount);
+ for (int i = 0; i < glyphCount; ++i) {
+ int n = SkUTF16_FromUnichar(u32[i], (uint16_t*)u16);
+ u16 += n;
+ }
+ break;
+ }
+ }
+
+ // Our caller may not want glyphs for output, but we need to give that
+ // storage to CT, so we can walk it looking for the first non-zero.
+ SkAutoSTMalloc<1024, uint16_t> glyphStorage;
+ uint16_t* macGlyphs = glyphs;
+ if (NULL == macGlyphs) {
+ macGlyphs = glyphStorage.reset(glyphCount);
+ }
+
+ if (CTFontGetGlyphsForCharacters(fFontRef, src, macGlyphs, glyphCount)) {
+ return glyphCount;
+ }
+ // If we got false, then we need to manually look for first failure
+ for (int i = 0; i < glyphCount; ++i) {
+ if (0 == macGlyphs[i]) {
+ return i;
+ }
+ }
+ // odd to get here, as we expected CT to have returned true up front.
+ return glyphCount;
+}
+
+int SkTypeface_Mac::onCountGlyphs() const {
+ return CTFontGetGlyphCount(fFontRef);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+#if 1
+
+static bool find_desc_str(CTFontDescriptorRef desc, CFStringRef name, SkString* value) {
+ AutoCFRelease<CFStringRef> ref((CFStringRef)CTFontDescriptorCopyAttribute(desc, name));
+ if (NULL == ref.get()) {
+ return false;
+ }
+ CFStringToSkString(ref, value);
+ return true;
+}
+
+static bool find_dict_float(CFDictionaryRef dict, CFStringRef name, float* value) {
+ CFNumberRef num;
+ return CFDictionaryGetValueIfPresent(dict, name, (const void**)&num)
+ && CFNumberIsFloatType(num)
+ && CFNumberGetValue(num, kCFNumberFloatType, value);
+}
+
+#include "SkFontMgr.h"
+
+static int unit_weight_to_fontstyle(float unit) {
+ float value;
+ if (unit < 0) {
+ value = 100 + (1 + unit) * 300;
+ } else {
+ value = 400 + unit * 500;
+ }
+ return sk_float_round2int(value);
+}
+
+static int unit_width_to_fontstyle(float unit) {
+ float value;
+ if (unit < 0) {
+ value = 1 + (1 + unit) * 4;
+ } else {
+ value = 5 + unit * 4;
+ }
+ return sk_float_round2int(value);
+}
+
+static inline int sqr(int value) {
+ SkASSERT(SkAbs32(value) < 0x7FFF); // check for overflow
+ return value * value;
+}
+
+// We normalize each axis (weight, width, italic) to be base-900
+static int compute_metric(const SkFontStyle& a, const SkFontStyle& b) {
+ return sqr(a.weight() - b.weight()) +
+ sqr((a.width() - b.width()) * 100) +
+ sqr((a.isItalic() != b.isItalic()) * 900);
+}
+
+static SkFontStyle desc2fontstyle(CTFontDescriptorRef desc) {
+ AutoCFRelease<CFDictionaryRef> dict(
+ (CFDictionaryRef)CTFontDescriptorCopyAttribute(desc,
+ kCTFontTraitsAttribute));
+ if (NULL == dict.get()) {
+ return SkFontStyle();
+ }
+
+ float weight, width, slant;
+ if (!find_dict_float(dict, kCTFontWeightTrait, &weight)) {
+ weight = 0;
+ }
+ if (!find_dict_float(dict, kCTFontWidthTrait, &width)) {
+ width = 0;
+ }
+ if (!find_dict_float(dict, kCTFontSlantTrait, &slant)) {
+ slant = 0;
+ }
+
+ return SkFontStyle(unit_weight_to_fontstyle(weight),
+ unit_width_to_fontstyle(width),
+ slant ? SkFontStyle::kItalic_Slant
+ : SkFontStyle::kUpright_Slant);
+}
+
+struct NameFontStyleRec {
+ SkString fFamilyName;
+ SkFontStyle fFontStyle;
+};
+
+static bool nameFontStyleProc(SkTypeface* face, SkTypeface::Style,
+ void* ctx) {
+ SkTypeface_Mac* macFace = (SkTypeface_Mac*)face;
+ const NameFontStyleRec* rec = (const NameFontStyleRec*)ctx;
+
+ return macFace->fFontStyle == rec->fFontStyle &&
+ macFace->fName == rec->fFamilyName;
+}
+
+static SkTypeface* createFromDesc(CFStringRef cfFamilyName,
+ CTFontDescriptorRef desc) {
+ NameFontStyleRec rec;
+ CFStringToSkString(cfFamilyName, &rec.fFamilyName);
+ rec.fFontStyle = desc2fontstyle(desc);
+
+ SkTypeface* face = SkTypefaceCache::FindByProcAndRef(nameFontStyleProc,
+ &rec);
+ if (face) {
+ return face;
+ }
+
+ AutoCFRelease<CTFontRef> ctNamed(CTFontCreateWithName(cfFamilyName, 1, NULL));
+ CTFontRef ctFont = CTFontCreateCopyWithAttributes(ctNamed, 1, NULL, desc);
+ if (NULL == ctFont) {
+ return NULL;
+ }
+
+ SkString str;
+ CFStringToSkString(cfFamilyName, &str);
+
+ bool isFixedPitch;
+ (void)computeStyleBits(ctFont, &isFixedPitch);
+ SkFontID fontID = CTFontRef_to_SkFontID(ctFont);
+
+ face = SkNEW_ARGS(SkTypeface_Mac, (rec.fFontStyle, fontID, isFixedPitch,
+ ctFont, str.c_str()));
+ SkTypefaceCache::Add(face, face->style());
+ return face;
+}
+
+class SkFontStyleSet_Mac : public SkFontStyleSet {
+public:
+ SkFontStyleSet_Mac(CFStringRef familyName, CTFontDescriptorRef desc)
+ : fArray(CTFontDescriptorCreateMatchingFontDescriptors(desc, NULL))
+ , fFamilyName(familyName)
+ , fCount(0) {
+ CFRetain(familyName);
+ if (NULL == fArray) {
+ fArray = CFArrayCreate(NULL, NULL, 0, NULL);
+ }
+ fCount = CFArrayGetCount(fArray);
+ }
+
+ virtual ~SkFontStyleSet_Mac() {
+ CFRelease(fArray);
+ CFRelease(fFamilyName);
+ }
+
+ virtual int count() SK_OVERRIDE {
+ return fCount;
+ }
+
+ virtual void getStyle(int index, SkFontStyle* style,
+ SkString* name) SK_OVERRIDE {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, index);
+ if (style) {
+ *style = desc2fontstyle(desc);
+ }
+ if (name) {
+ if (!find_desc_str(desc, kCTFontStyleNameAttribute, name)) {
+ name->reset();
+ }
+ }
+ }
+
+ virtual SkTypeface* createTypeface(int index) SK_OVERRIDE {
+ SkASSERT((unsigned)index < (unsigned)CFArrayGetCount(fArray));
+ CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, index);
+
+ return createFromDesc(fFamilyName, desc);
+ }
+
+ virtual SkTypeface* matchStyle(const SkFontStyle& pattern) SK_OVERRIDE {
+ if (0 == fCount) {
+ return NULL;
+ }
+ return createFromDesc(fFamilyName, findMatchingDesc(pattern));
+ }
+
+private:
+ CFArrayRef fArray;
+ CFStringRef fFamilyName;
+ int fCount;
+
+ CTFontDescriptorRef findMatchingDesc(const SkFontStyle& pattern) const {
+ int bestMetric = SK_MaxS32;
+ CTFontDescriptorRef bestDesc = NULL;
+
+ for (int i = 0; i < fCount; ++i) {
+ CTFontDescriptorRef desc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fArray, i);
+ int metric = compute_metric(pattern, desc2fontstyle(desc));
+ if (0 == metric) {
+ return desc;
+ }
+ if (metric < bestMetric) {
+ bestMetric = metric;
+ bestDesc = desc;
+ }
+ }
+ SkASSERT(bestDesc);
+ return bestDesc;
+ }
+};
+
+class SkFontMgr_Mac : public SkFontMgr {
+ int fCount;
+ CFArrayRef fNames;
+
+ CFStringRef stringAt(int index) const {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ return (CFStringRef)CFArrayGetValueAtIndex(fNames, index);
+ }
+
+ void lazyInit() {
+ if (NULL == fNames) {
+ fNames = SkCTFontManagerCopyAvailableFontFamilyNames();
+ fCount = fNames ? CFArrayGetCount(fNames) : 0;
+ }
+ }
+
+ static SkFontStyleSet* CreateSet(CFStringRef cfFamilyName) {
+ AutoCFRelease<CFMutableDictionaryRef> cfAttr(
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ CFDictionaryAddValue(cfAttr, kCTFontFamilyNameAttribute, cfFamilyName);
+
+ AutoCFRelease<CTFontDescriptorRef> desc(
+ CTFontDescriptorCreateWithAttributes(cfAttr));
+ return SkNEW_ARGS(SkFontStyleSet_Mac, (cfFamilyName, desc));
+ }
+
+public:
+ SkFontMgr_Mac() : fCount(0), fNames(NULL) {}
+
+ virtual ~SkFontMgr_Mac() {
+ CFSafeRelease(fNames);
+ }
+
+protected:
+ virtual int onCountFamilies() SK_OVERRIDE {
+ this->lazyInit();
+ return fCount;
+ }
+
+ virtual void onGetFamilyName(int index, SkString* familyName) SK_OVERRIDE {
+ this->lazyInit();
+ if ((unsigned)index < (unsigned)fCount) {
+ CFStringToSkString(this->stringAt(index), familyName);
+ } else {
+ familyName->reset();
+ }
+ }
+
+ virtual SkFontStyleSet* onCreateStyleSet(int index) SK_OVERRIDE {
+ this->lazyInit();
+ if ((unsigned)index >= (unsigned)fCount) {
+ return NULL;
+ }
+ return CreateSet(this->stringAt(index));
+ }
+
+ virtual SkFontStyleSet* onMatchFamily(const char familyName[]) SK_OVERRIDE {
+ AutoCFRelease<CFStringRef> cfName(make_CFString(familyName));
+ return CreateSet(cfName);
+ }
+
+ virtual SkTypeface* onMatchFamilyStyle(const char familyName[],
+ const SkFontStyle&) SK_OVERRIDE {
+ return NULL;
+ }
+
+ virtual SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember,
+ const SkFontStyle&) SK_OVERRIDE {
+ return NULL;
+ }
+
+ virtual SkTypeface* onCreateFromData(SkData* data,
+ int ttcIndex) SK_OVERRIDE {
+ AutoCFRelease<CGDataProviderRef> pr(SkCreateDataProviderFromData(data));
+ if (NULL == pr) {
+ return NULL;
+ }
+ return create_from_dataProvider(pr);
+ }
+
+ virtual SkTypeface* onCreateFromStream(SkStream* stream,
+ int ttcIndex) SK_OVERRIDE {
+ AutoCFRelease<CGDataProviderRef> pr(SkCreateDataProviderFromStream(stream));
+ if (NULL == pr) {
+ return NULL;
+ }
+ return create_from_dataProvider(pr);
+ }
+
+ virtual SkTypeface* onCreateFromFile(const char path[],
+ int ttcIndex) SK_OVERRIDE {
+ AutoCFRelease<CGDataProviderRef> pr(CGDataProviderCreateWithFilename(path));
+ if (NULL == pr) {
+ return NULL;
+ }
+ return create_from_dataProvider(pr);
+ }
+
+ virtual SkTypeface* onLegacyCreateTypeface(const char familyName[],
+ unsigned styleBits) SK_OVERRIDE {
+ return create_typeface(NULL, familyName, (SkTypeface::Style)styleBits);
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_FONTHOST_USES_FONTMGR
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ return create_typeface(familyFace, familyName, style);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ AutoCFRelease<CGDataProviderRef> provider(SkCreateDataProviderFromStream(stream));
+ if (NULL == provider) {
+ return NULL;
+ }
+ return create_from_dataProvider(provider);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ AutoCFRelease<CGDataProviderRef> provider(CGDataProviderCreateWithFilename(path));
+ if (NULL == provider) {
+ return NULL;
+ }
+ return create_from_dataProvider(provider);
+}
+
+#endif
+
+SkFontMgr* SkFontMgr::Factory() {
+ return SkNEW(SkFontMgr_Mac);
+}
+#endif
diff --git a/ports/SkFontHost_none.cpp b/ports/SkFontHost_none.cpp
new file mode 100644
index 00000000..c5267096
--- /dev/null
+++ b/ports/SkFontHost_none.cpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkFontHost.h"
+#include "SkScalerContext.h"
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char famillyName[],
+ SkTypeface::Style style) {
+ SkDEBUGFAIL("SkFontHost::FindTypeface unimplemented");
+ return NULL;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream*) {
+ SkDEBUGFAIL("SkFontHost::CreateTypeface unimplemented");
+ return NULL;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(char const*) {
+ SkDEBUGFAIL("SkFontHost::CreateTypefaceFromFile unimplemented");
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontMgr.h"
+
+SkFontMgr* SkFontMgr::Factory() {
+ // todo
+ return NULL;
+}
diff --git a/ports/SkFontHost_sandbox_none.cpp b/ports/SkFontHost_sandbox_none.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ports/SkFontHost_sandbox_none.cpp
diff --git a/ports/SkFontHost_win.cpp b/ports/SkFontHost_win.cpp
new file mode 100755
index 00000000..708c66fd
--- /dev/null
+++ b/ports/SkFontHost_win.cpp
@@ -0,0 +1,2385 @@
+
+/*
+ * 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 "SkAdvancedTypefaceMetrics.h"
+#include "SkBase64.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkDescriptor.h"
+#include "SkFontDescriptor.h"
+#include "SkFontHost.h"
+#include "SkGlyph.h"
+#include "SkMaskGamma.h"
+#include "SkOTTable_maxp.h"
+#include "SkOTTable_name.h"
+#include "SkOTUtils.h"
+#include "SkPath.h"
+#include "SkSFNTHeader.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+#include "SkTypeface_win.h"
+#include "SkTypefaceCache.h"
+#include "SkUtils.h"
+
+#include "SkTypes.h"
+#include <tchar.h>
+#include <usp10.h>
+#include <objbase.h>
+
+static void (*gEnsureLOGFONTAccessibleProc)(const LOGFONT&);
+
+void SkTypeface_SetEnsureLOGFONTAccessibleProc(void (*proc)(const LOGFONT&)) {
+ gEnsureLOGFONTAccessibleProc = proc;
+}
+
+static void call_ensure_accessible(const LOGFONT& lf) {
+ if (gEnsureLOGFONTAccessibleProc) {
+ gEnsureLOGFONTAccessibleProc(lf);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// always packed xxRRGGBB
+typedef uint32_t SkGdiRGB;
+
+// define this in your Makefile or .gyp to enforce AA requests
+// which GDI ignores at small sizes. This flag guarantees AA
+// for rotated text, regardless of GDI's notions.
+//#define SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS
+
+static bool isLCD(const SkScalerContext::Rec& rec) {
+ return SkMask::kLCD16_Format == rec.fMaskFormat ||
+ SkMask::kLCD32_Format == rec.fMaskFormat;
+}
+
+static bool bothZero(SkScalar a, SkScalar b) {
+ return 0 == a && 0 == b;
+}
+
+// returns false if there is any non-90-rotation or skew
+static bool isAxisAligned(const SkScalerContext::Rec& rec) {
+ return 0 == rec.fPreSkewX &&
+ (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
+ bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
+}
+
+static bool needToRenderWithSkia(const SkScalerContext::Rec& rec) {
+#ifdef SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS
+ // What we really want to catch is when GDI will ignore the AA request and give
+ // us BW instead. Smallish rotated text is one heuristic, so this code is just
+ // an approximation. We shouldn't need to do this for larger sizes, but at those
+ // sizes, the quality difference gets less and less between our general
+ // scanconverter and GDI's.
+ if (SkMask::kA8_Format == rec.fMaskFormat && !isAxisAligned(rec)) {
+ return true;
+ }
+#endif
+ return rec.getHinting() == SkPaint::kNo_Hinting || rec.getHinting() == SkPaint::kSlight_Hinting;
+}
+
+using namespace skia_advanced_typeface_metrics_utils;
+
+static void tchar_to_skstring(const TCHAR t[], SkString* s) {
+#ifdef UNICODE
+ 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(t);
+#endif
+}
+
+static void dcfontname_to_skstring(HDC deviceContext, const LOGFONT& lf, SkString* familyName) {
+ int fontNameLen; //length of fontName in TCHARS.
+ if (0 == (fontNameLen = GetTextFace(deviceContext, 0, NULL))) {
+ call_ensure_accessible(lf);
+ if (0 == (fontNameLen = GetTextFace(deviceContext, 0, NULL))) {
+ fontNameLen = 0;
+ }
+ }
+
+ SkAutoSTArray<LF_FULLFACESIZE, TCHAR> fontName(fontNameLen+1);
+ if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) {
+ call_ensure_accessible(lf);
+ if (0 == GetTextFace(deviceContext, fontNameLen, fontName.get())) {
+ fontName[0] = 0;
+ }
+ }
+
+ tchar_to_skstring(fontName.get(), familyName);
+}
+
+static void make_canonical(LOGFONT* lf) {
+ lf->lfHeight = -2048;
+ lf->lfQuality = CLEARTYPE_QUALITY;//PROOF_QUALITY;
+ lf->lfCharSet = DEFAULT_CHARSET;
+// lf->lfClipPrecision = 64;
+}
+
+static SkTypeface::Style get_style(const LOGFONT& lf) {
+ unsigned style = 0;
+ if (lf.lfWeight >= FW_BOLD) {
+ style |= SkTypeface::kBold;
+ }
+ if (lf.lfItalic) {
+ style |= SkTypeface::kItalic;
+ }
+ return static_cast<SkTypeface::Style>(style);
+}
+
+static void setStyle(LOGFONT* lf, SkTypeface::Style style) {
+ lf->lfWeight = (style & SkTypeface::kBold) != 0 ? FW_BOLD : FW_NORMAL ;
+ lf->lfItalic = ((style & SkTypeface::kItalic) != 0);
+}
+
+static inline FIXED SkFixedToFIXED(SkFixed x) {
+ return *(FIXED*)(&x);
+}
+static inline SkFixed SkFIXEDToFixed(FIXED x) {
+ return *(SkFixed*)(&x);
+}
+
+static inline FIXED SkScalarToFIXED(SkScalar x) {
+ return SkFixedToFIXED(SkScalarToFixed(x));
+}
+
+static unsigned calculateGlyphCount(HDC hdc, const LOGFONT& lf) {
+ TEXTMETRIC textMetric;
+ if (0 == GetTextMetrics(hdc, &textMetric)) {
+ textMetric.tmPitchAndFamily = TMPF_VECTOR;
+ call_ensure_accessible(lf);
+ GetTextMetrics(hdc, &textMetric);
+ }
+
+ if (!(textMetric.tmPitchAndFamily & TMPF_VECTOR)) {
+ return textMetric.tmLastChar;
+ }
+
+ // The 'maxp' table stores the number of glyphs at offset 4, in 2 bytes.
+ uint16_t glyphs;
+ if (GDI_ERROR != GetFontData(hdc, SkOTTableMaximumProfile::TAG, 4, &glyphs, sizeof(glyphs))) {
+ return SkEndian_SwapBE16(glyphs);
+ }
+
+ // Binary search for glyph count.
+ static const MAT2 mat2 = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
+ int32_t max = SK_MaxU16 + 1;
+ int32_t min = 0;
+ GLYPHMETRICS gm;
+ while (min < max) {
+ int32_t mid = min + ((max - min) / 2);
+ if (GetGlyphOutlineW(hdc, mid, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0,
+ NULL, &mat2) == GDI_ERROR) {
+ max = mid;
+ } else {
+ min = mid + 1;
+ }
+ }
+ SkASSERT(min == max);
+ return min;
+}
+
+static unsigned calculateUPEM(HDC hdc, const LOGFONT& lf) {
+ TEXTMETRIC textMetric;
+ if (0 == GetTextMetrics(hdc, &textMetric)) {
+ textMetric.tmPitchAndFamily = TMPF_VECTOR;
+ call_ensure_accessible(lf);
+ GetTextMetrics(hdc, &textMetric);
+ }
+
+ if (!(textMetric.tmPitchAndFamily & TMPF_VECTOR)) {
+ return textMetric.tmMaxCharWidth;
+ }
+
+ OUTLINETEXTMETRIC otm;
+ unsigned int otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm);
+ if (0 == otmRet) {
+ call_ensure_accessible(lf);
+ otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm);
+ }
+
+ return (0 == otmRet) ? 0 : otm.otmEMSquare;
+}
+
+class LogFontTypeface : public SkTypeface {
+public:
+ LogFontTypeface(SkTypeface::Style style, SkFontID fontID, const LOGFONT& lf, bool serializeAsStream = false) :
+ SkTypeface(style, fontID, false), fLogFont(lf), fSerializeAsStream(serializeAsStream) {
+
+ // If the font has cubic outlines, it will not be rendered with ClearType.
+ HFONT font = CreateFontIndirect(&lf);
+
+ HDC deviceContext = ::CreateCompatibleDC(NULL);
+ HFONT savefont = (HFONT)SelectObject(deviceContext, font);
+
+ TEXTMETRIC textMetric;
+ if (0 == GetTextMetrics(deviceContext, &textMetric)) {
+ call_ensure_accessible(lf);
+ if (0 == GetTextMetrics(deviceContext, &textMetric)) {
+ textMetric.tmPitchAndFamily = TMPF_TRUETYPE;
+ }
+ }
+ if (deviceContext) {
+ ::SelectObject(deviceContext, savefont);
+ ::DeleteDC(deviceContext);
+ }
+ if (font) {
+ ::DeleteObject(font);
+ }
+
+ // The fixed pitch bit is set if the font is *not* fixed pitch.
+ this->setIsFixedPitch((textMetric.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0);
+
+ // Used a logfont on a memory context, should never get a device font.
+ // Therefore all TMPF_DEVICE will be PostScript (cubic) fonts.
+ fCanBeLCD = !((textMetric.tmPitchAndFamily & TMPF_VECTOR) &&
+ (textMetric.tmPitchAndFamily & TMPF_DEVICE));
+ }
+
+ LOGFONT fLogFont;
+ bool fSerializeAsStream;
+ bool fCanBeLCD;
+
+ static LogFontTypeface* Create(const LOGFONT& lf) {
+ SkTypeface::Style style = get_style(lf);
+ SkFontID fontID = SkTypefaceCache::NewFontID();
+ return new LogFontTypeface(style, fontID, lf);
+ }
+
+ static void EnsureAccessible(const SkTypeface* face) {
+ call_ensure_accessible(static_cast<const LogFontTypeface*>(face)->fLogFont);
+ }
+
+protected:
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE;
+ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor*) const SK_OVERRIDE;
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE;
+ virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo,
+ const uint32_t*, uint32_t) const SK_OVERRIDE;
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE;
+ virtual int onCountGlyphs() const SK_OVERRIDE;
+ virtual int onGetUPEM() const SK_OVERRIDE;
+ virtual SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE;
+ virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE;
+ virtual size_t onGetTableData(SkFontTableTag, size_t offset,
+ size_t length, void* data) const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style) const SK_OVERRIDE;
+};
+
+class FontMemResourceTypeface : public LogFontTypeface {
+public:
+ /**
+ * Takes ownership of fontMemResource.
+ */
+ FontMemResourceTypeface(SkTypeface::Style style, SkFontID fontID, const LOGFONT& lf, HANDLE fontMemResource) :
+ LogFontTypeface(style, fontID, lf, true), fFontMemResource(fontMemResource) {
+ }
+
+ HANDLE fFontMemResource;
+
+ /**
+ * The created FontMemResourceTypeface takes ownership of fontMemResource.
+ */
+ static FontMemResourceTypeface* Create(const LOGFONT& lf, HANDLE fontMemResource) {
+ SkTypeface::Style style = get_style(lf);
+ SkFontID fontID = SkTypefaceCache::NewFontID();
+ return new FontMemResourceTypeface(style, fontID, lf, fontMemResource);
+ }
+
+protected:
+ virtual void weak_dispose() const SK_OVERRIDE {
+ RemoveFontMemResourceEx(fFontMemResource);
+ //SkTypefaceCache::Remove(this);
+ INHERITED::weak_dispose();
+ }
+
+private:
+ typedef LogFontTypeface INHERITED;
+};
+
+static const LOGFONT& get_default_font() {
+ static LOGFONT gDefaultFont;
+ return gDefaultFont;
+}
+
+static bool FindByLogFont(SkTypeface* face, SkTypeface::Style requestedStyle, void* ctx) {
+ LogFontTypeface* lface = static_cast<LogFontTypeface*>(face);
+ const LOGFONT* lf = reinterpret_cast<const LOGFONT*>(ctx);
+
+ return lface &&
+ get_style(lface->fLogFont) == requestedStyle &&
+ !memcmp(&lface->fLogFont, lf, sizeof(LOGFONT));
+}
+
+/**
+ * This guy is public. It first searches the cache, and if a match is not found,
+ * it creates a new face.
+ */
+SkTypeface* SkCreateTypefaceFromLOGFONT(const LOGFONT& origLF) {
+ LOGFONT lf = origLF;
+ make_canonical(&lf);
+ SkTypeface* face = SkTypefaceCache::FindByProcAndRef(FindByLogFont, &lf);
+ if (NULL == face) {
+ face = LogFontTypeface::Create(lf);
+ SkTypefaceCache::Add(face, get_style(lf));
+ }
+ return face;
+}
+
+/**
+ * The created SkTypeface takes ownership of fontMemResource.
+ */
+SkTypeface* SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) {
+ LOGFONT lf = origLF;
+ make_canonical(&lf);
+ FontMemResourceTypeface* face = FontMemResourceTypeface::Create(lf, fontMemResource);
+ SkTypefaceCache::Add(face, get_style(lf), false);
+ return face;
+}
+
+/**
+ * This guy is public
+ */
+void SkLOGFONTFromTypeface(const SkTypeface* face, LOGFONT* lf) {
+ if (NULL == face) {
+ *lf = get_default_font();
+ } else {
+ *lf = static_cast<const LogFontTypeface*>(face)->fLogFont;
+ }
+}
+
+static void GetLogFontByID(SkFontID fontID, LOGFONT* lf) {
+ LogFontTypeface* face = static_cast<LogFontTypeface*>(SkTypefaceCache::FindByID(fontID));
+ if (face) {
+ *lf = face->fLogFont;
+ } else {
+ sk_bzero(lf, sizeof(LOGFONT));
+ }
+}
+
+// Construct Glyph to Unicode table.
+// Unicode code points that require conjugate pairs in utf16 are not
+// supported.
+// TODO(arthurhsu): Add support for conjugate pairs. It looks like that may
+// require parsing the TTF cmap table (platform 4, encoding 12) directly instead
+// of calling GetFontUnicodeRange().
+static void populate_glyph_to_unicode(HDC fontHdc, const unsigned glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ DWORD glyphSetBufferSize = GetFontUnicodeRanges(fontHdc, NULL);
+ if (!glyphSetBufferSize) {
+ return;
+ }
+
+ SkAutoTDeleteArray<BYTE> glyphSetBuffer(new BYTE[glyphSetBufferSize]);
+ GLYPHSET* glyphSet =
+ reinterpret_cast<LPGLYPHSET>(glyphSetBuffer.get());
+ if (GetFontUnicodeRanges(fontHdc, glyphSet) != glyphSetBufferSize) {
+ return;
+ }
+
+ glyphToUnicode->setCount(glyphCount);
+ memset(glyphToUnicode->begin(), 0, glyphCount * sizeof(SkUnichar));
+ for (DWORD i = 0; i < glyphSet->cRanges; ++i) {
+ // There is no guarantee that within a Unicode range, the corresponding
+ // glyph id in a font file are continuous. So, even if we have ranges,
+ // we can't just use the first and last entry of the range to compute
+ // result. We need to enumerate them one by one.
+ int count = glyphSet->ranges[i].cGlyphs;
+ SkAutoTArray<WCHAR> chars(count + 1);
+ chars[count] = 0; // termintate string
+ SkAutoTArray<WORD> glyph(count);
+ for (USHORT j = 0; j < count; ++j) {
+ chars[j] = glyphSet->ranges[i].wcLow + j;
+ }
+ GetGlyphIndicesW(fontHdc, chars.get(), count, glyph.get(),
+ GGI_MARK_NONEXISTING_GLYPHS);
+ // If the glyph ID is valid, and the glyph is not mapped, then we will
+ // fill in the char id into the vector. If the glyph is mapped already,
+ // skip it.
+ // TODO(arthurhsu): better improve this. e.g. Get all used char ids from
+ // font cache, then generate this mapping table from there. It's
+ // unlikely to have collisions since glyph reuse happens mostly for
+ // different Unicode pages.
+ for (USHORT j = 0; j < count; ++j) {
+ if (glyph[j] != 0xffff && glyph[j] < glyphCount &&
+ (*glyphToUnicode)[glyph[j]] == 0) {
+ (*glyphToUnicode)[glyph[j]] = chars[j];
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+static int alignTo32(int n) {
+ return (n + 31) & ~31;
+}
+
+struct MyBitmapInfo : public BITMAPINFO {
+ RGBQUAD fMoreSpaceForColors[1];
+};
+
+class HDCOffscreen {
+public:
+ HDCOffscreen() {
+ fFont = 0;
+ fDC = 0;
+ fBM = 0;
+ fBits = NULL;
+ fWidth = fHeight = 0;
+ fIsBW = false;
+ }
+
+ ~HDCOffscreen() {
+ if (fDC) {
+ DeleteDC(fDC);
+ }
+ if (fBM) {
+ DeleteObject(fBM);
+ }
+ }
+
+ void init(HFONT font, const XFORM& xform) {
+ fFont = font;
+ fXform = xform;
+ }
+
+ const void* draw(const SkGlyph&, bool isBW, size_t* srcRBPtr);
+
+private:
+ HDC fDC;
+ HBITMAP fBM;
+ HFONT fFont;
+ XFORM fXform;
+ void* fBits; // points into fBM
+ int fWidth;
+ int fHeight;
+ bool fIsBW;
+};
+
+const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW,
+ size_t* srcRBPtr) {
+ // Can we share the scalercontext's fDDC, so we don't need to create
+ // a separate fDC here?
+ if (0 == fDC) {
+ fDC = CreateCompatibleDC(0);
+ if (0 == fDC) {
+ return NULL;
+ }
+ SetGraphicsMode(fDC, GM_ADVANCED);
+ SetBkMode(fDC, TRANSPARENT);
+ SetTextAlign(fDC, TA_LEFT | TA_BASELINE);
+ SelectObject(fDC, fFont);
+
+ COLORREF color = 0x00FFFFFF;
+ SkDEBUGCODE(COLORREF prev =) SetTextColor(fDC, color);
+ SkASSERT(prev != CLR_INVALID);
+ }
+
+ if (fBM && (fIsBW != isBW || fWidth < glyph.fWidth || fHeight < glyph.fHeight)) {
+ DeleteObject(fBM);
+ fBM = 0;
+ }
+ fIsBW = isBW;
+
+ fWidth = SkMax32(fWidth, glyph.fWidth);
+ fHeight = SkMax32(fHeight, glyph.fHeight);
+
+ int biWidth = isBW ? alignTo32(fWidth) : fWidth;
+
+ if (0 == fBM) {
+ MyBitmapInfo info;
+ sk_bzero(&info, sizeof(info));
+ if (isBW) {
+ RGBQUAD blackQuad = { 0, 0, 0, 0 };
+ RGBQUAD whiteQuad = { 0xFF, 0xFF, 0xFF, 0 };
+ info.bmiColors[0] = blackQuad;
+ info.bmiColors[1] = whiteQuad;
+ }
+ info.bmiHeader.biSize = sizeof(info.bmiHeader);
+ info.bmiHeader.biWidth = biWidth;
+ info.bmiHeader.biHeight = fHeight;
+ info.bmiHeader.biPlanes = 1;
+ info.bmiHeader.biBitCount = isBW ? 1 : 32;
+ info.bmiHeader.biCompression = BI_RGB;
+ if (isBW) {
+ info.bmiHeader.biClrUsed = 2;
+ }
+ fBM = CreateDIBSection(fDC, &info, DIB_RGB_COLORS, &fBits, 0, 0);
+ if (0 == fBM) {
+ return NULL;
+ }
+ SelectObject(fDC, fBM);
+ }
+
+ // erase
+ size_t srcRB = isBW ? (biWidth >> 3) : (fWidth << 2);
+ size_t size = fHeight * srcRB;
+ memset(fBits, 0, size);
+
+ XFORM xform = fXform;
+ xform.eDx = (float)-glyph.fLeft;
+ xform.eDy = (float)-glyph.fTop;
+ SetWorldTransform(fDC, &xform);
+
+ uint16_t glyphID = glyph.getGlyphID();
+ BOOL ret = ExtTextOutW(fDC, 0, 0, ETO_GLYPH_INDEX, NULL, reinterpret_cast<LPCWSTR>(&glyphID), 1, NULL);
+ GdiFlush();
+ if (0 == ret) {
+ return NULL;
+ }
+ *srcRBPtr = srcRB;
+ // offset to the start of the image
+ return (const char*)fBits + (fHeight - glyph.fHeight) * srcRB;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+#define BUFFERSIZE (1 << 13)
+
+class SkScalerContext_GDI : public SkScalerContext {
+public:
+ SkScalerContext_GDI(SkTypeface*, const SkDescriptor* desc);
+ virtual ~SkScalerContext_GDI();
+
+ // Returns true if the constructor was able to complete all of its
+ // initializations (which may include calling GDI).
+ bool isValid() const;
+
+protected:
+ 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) SK_OVERRIDE;
+
+private:
+ DWORD getGDIGlyphPath(const SkGlyph& glyph, UINT flags,
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t>* glyphbuf);
+
+ HDCOffscreen fOffscreen;
+ /** fGsA is the non-rotational part of total matrix without the text height scale.
+ * Used to find the magnitude of advances.
+ */
+ MAT2 fGsA;
+ /** The total matrix without the textSize. */
+ MAT2 fMat22;
+ /** Scales font to EM size. */
+ MAT2 fHighResMat22;
+ HDC fDDC;
+ HFONT fSavefont;
+ HFONT fFont;
+ SCRIPT_CACHE fSC;
+ int fGlyphCount;
+
+ /** The total matrix which also removes EM scale. */
+ SkMatrix fHiResMatrix;
+ /** fG_inv is the inverse of the rotational part of the total matrix.
+ * Used to set the direction of advances.
+ */
+ SkMatrix fG_inv;
+ enum Type {
+ kTrueType_Type, kBitmap_Type,
+ } fType;
+ TEXTMETRIC fTM;
+};
+
+static FIXED float2FIXED(float x) {
+ return SkFixedToFIXED(SkFloatToFixed(x));
+}
+
+static BYTE compute_quality(const SkScalerContext::Rec& rec) {
+ switch (rec.fMaskFormat) {
+ case SkMask::kBW_Format:
+ return NONANTIALIASED_QUALITY;
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format:
+ return CLEARTYPE_QUALITY;
+ default:
+ if (rec.fFlags & SkScalerContext::kGenA8FromLCD_Flag) {
+ return CLEARTYPE_QUALITY;
+ } else {
+ return ANTIALIASED_QUALITY;
+ }
+ }
+}
+
+SkScalerContext_GDI::SkScalerContext_GDI(SkTypeface* rawTypeface,
+ const SkDescriptor* desc)
+ : SkScalerContext(rawTypeface, desc)
+ , fDDC(0)
+ , fSavefont(0)
+ , fFont(0)
+ , fSC(0)
+ , fGlyphCount(-1)
+{
+ LogFontTypeface* typeface = reinterpret_cast<LogFontTypeface*>(rawTypeface);
+
+ fDDC = ::CreateCompatibleDC(NULL);
+ if (!fDDC) {
+ return;
+ }
+ SetGraphicsMode(fDDC, GM_ADVANCED);
+ SetBkMode(fDDC, TRANSPARENT);
+
+ SkPoint h = SkPoint::Make(SK_Scalar1, 0);
+ // A is the total matrix.
+ SkMatrix A;
+ fRec.getSingleMatrix(&A);
+ A.mapPoints(&h, 1);
+
+ // Find the Given's matrix [[c, -s],[s, c]] which rotates the baseline vector h
+ // (where the baseline is mapped to) to the positive horizontal axis.
+ const SkScalar& a = h.fX;
+ const SkScalar& b = h.fY;
+ SkScalar c, s;
+ if (0 == b) {
+ c = SkDoubleToScalar(_copysign(SK_Scalar1, a));
+ s = 0;
+ } else if (0 == a) {
+ c = 0;
+ s = SkDoubleToScalar(-_copysign(SK_Scalar1, b));
+ } else if (SkScalarAbs(b) > SkScalarAbs(a)) {
+ SkScalar t = a / b;
+ SkScalar u = SkDoubleToScalar(_copysign(SkScalarSqrt(SK_Scalar1 + t*t), b));
+ s = -1 / u;
+ c = -s * t;
+ } else {
+ SkScalar t = b / a;
+ SkScalar u = SkDoubleToScalar(_copysign(SkScalarSqrt(SK_Scalar1 + t*t), a));
+ c = 1 / u;
+ s = -c * t;
+ }
+
+ // G is the Given's Matrix for A (rotational matrix such that GA[0][1] == 0).
+ SkMatrix G;
+ G.setAll(c, -s, 0,
+ s, c, 0,
+ 0, 0, SkScalarToPersp(SK_Scalar1));
+
+ // GA is the matrix A with rotation removed.
+ SkMatrix GA(G);
+ GA.preConcat(A);
+
+ // textSize is the actual device size we want (as opposed to the size the user requested).
+ // If the scale is negative, this means the matrix will do the flip anyway.
+ SkScalar textSize = SkScalarAbs(SkScalarRoundToScalar(GA.get(SkMatrix::kMScaleY)));
+ if (textSize == 0) {
+ textSize = SK_Scalar1;
+ }
+
+ // sA is the total matrix A without the textSize (so GDI knows the text size separately).
+ // When this matrix is used with GetGlyphOutline, no further processing is needed.
+ SkMatrix sA(A);
+ SkScalar scale = SkScalarInvert(textSize);
+ sA.preScale(scale, scale); //remove text size
+
+ // GsA is the non-rotational part of A without the text height scale.
+ // This is what is used to find the magnitude of advances.
+ SkMatrix GsA(GA);
+ GsA.preScale(scale, scale); //remove text size, G is rotational so reorders with the scale.
+
+ fGsA.eM11 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleX));
+ fGsA.eM12 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
+ fGsA.eM21 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewX));
+ fGsA.eM22 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleY));
+
+ // fG_inv is G inverse, which is fairly simple since G is 2x2 rotational.
+ fG_inv.setAll(G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
+ -G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
+ G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
+
+ LOGFONT lf = typeface->fLogFont;
+ lf.lfHeight = -SkScalarTruncToInt(textSize);
+ lf.lfQuality = compute_quality(fRec);
+ fFont = CreateFontIndirect(&lf);
+ if (!fFont) {
+ return;
+ }
+
+ fSavefont = (HFONT)SelectObject(fDDC, fFont);
+
+ if (0 == GetTextMetrics(fDDC, &fTM)) {
+ call_ensure_accessible(lf);
+ if (0 == GetTextMetrics(fDDC, &fTM)) {
+ fTM.tmPitchAndFamily = TMPF_TRUETYPE;
+ }
+ }
+ // Used a logfont on a memory context, should never get a device font.
+ // Therefore all TMPF_DEVICE will be PostScript fonts.
+
+ // If TMPF_VECTOR is set, one of TMPF_TRUETYPE or TMPF_DEVICE must be set,
+ // otherwise we have a vector FON, which we don't support.
+ // This was determined by testing with Type1 PFM/PFB and OpenTypeCFF OTF,
+ // as well as looking at Wine bugs and sources.
+ SkASSERT(!(fTM.tmPitchAndFamily & TMPF_VECTOR) ||
+ (fTM.tmPitchAndFamily & (TMPF_TRUETYPE | TMPF_DEVICE)));
+
+ XFORM xform;
+ if (fTM.tmPitchAndFamily & TMPF_VECTOR) {
+ // Truetype or PostScript.
+ // Stroked FON also gets here (TMPF_VECTOR), but we don't handle it.
+ fType = SkScalerContext_GDI::kTrueType_Type;
+
+ // fPost2x2 is column-major, left handed (y down).
+ // XFORM 2x2 is row-major, left handed (y down).
+ xform.eM11 = SkScalarToFloat(sA.get(SkMatrix::kMScaleX));
+ xform.eM12 = SkScalarToFloat(sA.get(SkMatrix::kMSkewY));
+ xform.eM21 = SkScalarToFloat(sA.get(SkMatrix::kMSkewX));
+ xform.eM22 = SkScalarToFloat(sA.get(SkMatrix::kMScaleY));
+ xform.eDx = 0;
+ xform.eDy = 0;
+
+ // MAT2 is row major, right handed (y up).
+ fMat22.eM11 = float2FIXED(xform.eM11);
+ fMat22.eM12 = float2FIXED(-xform.eM12);
+ fMat22.eM21 = float2FIXED(-xform.eM21);
+ fMat22.eM22 = float2FIXED(xform.eM22);
+
+ if (needToRenderWithSkia(fRec)) {
+ this->forceGenerateImageFromPath();
+ }
+
+ // Create a hires font if we need linear metrics.
+ if (this->isSubpixel()) {
+ OUTLINETEXTMETRIC otm;
+ UINT success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ if (0 == success) {
+ call_ensure_accessible(lf);
+ success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ }
+ if (0 != success) {
+ SkScalar scale = SkIntToScalar(otm.otmEMSquare);
+
+ SkScalar textScale = scale / textSize;
+ fHighResMat22.eM11 = float2FIXED(textScale);
+ fHighResMat22.eM12 = float2FIXED(0);
+ fHighResMat22.eM21 = float2FIXED(0);
+ fHighResMat22.eM22 = float2FIXED(textScale);
+
+ SkScalar invScale = SkScalarInvert(scale);
+ fHiResMatrix = A;
+ fHiResMatrix.preScale(invScale, invScale);
+ }
+ }
+
+ } else {
+ // Assume bitmap
+ fType = SkScalerContext_GDI::kBitmap_Type;
+
+ xform.eM11 = 1.0f;
+ xform.eM12 = 0.0f;
+ xform.eM21 = 0.0f;
+ xform.eM22 = 1.0f;
+ xform.eDx = 0.0f;
+ xform.eDy = 0.0f;
+
+ // fPost2x2 is column-major, left handed (y down).
+ // MAT2 is row major, right handed (y up).
+ fMat22.eM11 = SkScalarToFIXED(fRec.fPost2x2[0][0]);
+ fMat22.eM12 = SkScalarToFIXED(-fRec.fPost2x2[1][0]);
+ fMat22.eM21 = SkScalarToFIXED(-fRec.fPost2x2[0][1]);
+ fMat22.eM22 = SkScalarToFIXED(fRec.fPost2x2[1][1]);
+ }
+
+ fOffscreen.init(fFont, xform);
+}
+
+SkScalerContext_GDI::~SkScalerContext_GDI() {
+ if (fDDC) {
+ ::SelectObject(fDDC, fSavefont);
+ ::DeleteDC(fDDC);
+ }
+ if (fFont) {
+ ::DeleteObject(fFont);
+ }
+ if (fSC) {
+ ::ScriptFreeCache(&fSC);
+ }
+}
+
+bool SkScalerContext_GDI::isValid() const {
+ return fDDC && fFont;
+}
+
+unsigned SkScalerContext_GDI::generateGlyphCount() {
+ if (fGlyphCount < 0) {
+ fGlyphCount = calculateGlyphCount(
+ fDDC, static_cast<const LogFontTypeface*>(this->getTypeface())->fLogFont);
+ }
+ return fGlyphCount;
+}
+
+uint16_t SkScalerContext_GDI::generateCharToGlyph(SkUnichar uni) {
+ uint16_t index = 0;
+ WCHAR c[2];
+ // TODO(ctguil): Support characters that generate more than one glyph.
+ if (SkUTF16_FromUnichar(uni, (uint16_t*)c) == 1) {
+ // Type1 fonts fail with uniscribe API. Use GetGlyphIndices for plane 0.
+ SkAssertResult(GetGlyphIndicesW(fDDC, c, 1, &index, 0));
+ } else {
+ // Use uniscribe to detemine glyph index for non-BMP characters.
+ // Need to add extra item to SCRIPT_ITEM to work around a bug in older
+ // windows versions. https://bugzilla.mozilla.org/show_bug.cgi?id=366643
+ SCRIPT_ITEM si[2 + 1];
+ int items;
+ SkAssertResult(
+ SUCCEEDED(ScriptItemize(c, 2, 2, NULL, NULL, si, &items)));
+
+ WORD log[2];
+ SCRIPT_VISATTR vsa;
+ int glyphs;
+ SkAssertResult(SUCCEEDED(ScriptShape(
+ fDDC, &fSC, c, 2, 1, &si[0].a, &index, log, &vsa, &glyphs)));
+ }
+ return index;
+}
+
+void SkScalerContext_GDI::generateAdvance(SkGlyph* glyph) {
+ this->generateMetrics(glyph);
+}
+
+void SkScalerContext_GDI::generateMetrics(SkGlyph* glyph) {
+ SkASSERT(fDDC);
+
+ if (fType == SkScalerContext_GDI::kBitmap_Type) {
+ SIZE size;
+ WORD glyphs = glyph->getGlyphID(0);
+ if (0 == GetTextExtentPointI(fDDC, &glyphs, 1, &size)) {
+ glyph->fWidth = SkToS16(fTM.tmMaxCharWidth);
+ } else {
+ glyph->fWidth = SkToS16(size.cx);
+ }
+ glyph->fHeight = SkToS16(size.cy);
+
+ glyph->fTop = SkToS16(-fTM.tmAscent);
+ glyph->fLeft = SkToS16(0);
+ glyph->fAdvanceX = SkIntToFixed(glyph->fWidth);
+ glyph->fAdvanceY = 0;
+
+ // Apply matrix to advance.
+ glyph->fAdvanceY = SkFixedMul(SkFIXEDToFixed(fMat22.eM21), glyph->fAdvanceX);
+ glyph->fAdvanceX = SkFixedMul(SkFIXEDToFixed(fMat22.eM11), glyph->fAdvanceX);
+
+ return;
+ }
+
+ UINT glyphId = glyph->getGlyphID(0);
+
+ GLYPHMETRICS gm;
+ sk_bzero(&gm, sizeof(gm));
+
+ DWORD status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == status) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == status) {
+ glyph->zeroMetrics();
+ return;
+ }
+ }
+
+ bool empty = false;
+ // The black box is either the embedded bitmap size or the outline extent.
+ // It is 1x1 if nothing is to be drawn, but will also be 1x1 if something very small
+ // is to be drawn, like a '.'. We need to outset '.' but do not wish to outset ' '.
+ if (1 == gm.gmBlackBoxX && 1 == gm.gmBlackBoxY) {
+ // If GetGlyphOutline with GGO_NATIVE returns 0, we know there was no outline.
+ DWORD bufferSize = GetGlyphOutlineW(fDDC, glyphId, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, NULL, &fMat22);
+ empty = (0 == bufferSize);
+ }
+
+ glyph->fTop = SkToS16(-gm.gmptGlyphOrigin.y);
+ glyph->fLeft = SkToS16(gm.gmptGlyphOrigin.x);
+ if (empty) {
+ glyph->fWidth = 0;
+ glyph->fHeight = 0;
+ } else {
+ // Outset, since the image may bleed out of the black box.
+ // For embedded bitmaps the black box should be exact.
+ // For outlines we need to outset by 1 in all directions for bleed.
+ // For ClearType we need to outset by 2 for bleed.
+ glyph->fWidth = gm.gmBlackBoxX + 4;
+ glyph->fHeight = gm.gmBlackBoxY + 4;
+ glyph->fTop -= 2;
+ glyph->fLeft -= 2;
+ }
+ glyph->fAdvanceX = SkIntToFixed(gm.gmCellIncX);
+ glyph->fAdvanceY = SkIntToFixed(gm.gmCellIncY);
+ glyph->fRsbDelta = 0;
+ glyph->fLsbDelta = 0;
+
+ if (this->isSubpixel()) {
+ sk_bzero(&gm, sizeof(gm));
+ status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fHighResMat22);
+ if (GDI_ERROR != status) {
+ SkPoint advance;
+ fHiResMatrix.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance);
+ glyph->fAdvanceX = SkScalarToFixed(advance.fX);
+ glyph->fAdvanceY = SkScalarToFixed(advance.fY);
+ }
+ } else if (!isAxisAligned(this->fRec)) {
+ status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fGsA);
+ if (GDI_ERROR != status) {
+ SkPoint advance;
+ fG_inv.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance);
+ glyph->fAdvanceX = SkScalarToFixed(advance.fX);
+ glyph->fAdvanceY = SkScalarToFixed(advance.fY);
+ }
+ }
+}
+
+static const MAT2 gMat2Identity = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
+void SkScalerContext_GDI::generateFontMetrics(SkPaint::FontMetrics* mx, SkPaint::FontMetrics* my) {
+ if (!(mx || my)) {
+ return;
+ }
+
+ if (mx) {
+ sk_bzero(mx, sizeof(*mx));
+ }
+ if (my) {
+ sk_bzero(my, sizeof(*my));
+ }
+
+ SkASSERT(fDDC);
+
+#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS
+ if (fType == SkScalerContext_GDI::kBitmap_Type) {
+#endif
+ if (mx) {
+ mx->fTop = SkIntToScalar(-fTM.tmAscent);
+ mx->fAscent = SkIntToScalar(-fTM.tmAscent);
+ mx->fDescent = SkIntToScalar(fTM.tmDescent);
+ mx->fBottom = SkIntToScalar(fTM.tmDescent);
+ mx->fLeading = SkIntToScalar(fTM.tmExternalLeading);
+ }
+
+ if (my) {
+ my->fTop = SkIntToScalar(-fTM.tmAscent);
+ my->fAscent = SkIntToScalar(-fTM.tmAscent);
+ my->fDescent = SkIntToScalar(fTM.tmDescent);
+ my->fBottom = SkIntToScalar(fTM.tmDescent);
+ my->fLeading = SkIntToScalar(fTM.tmExternalLeading);
+ my->fAvgCharWidth = SkIntToScalar(fTM.tmAveCharWidth);
+ my->fMaxCharWidth = SkIntToScalar(fTM.tmMaxCharWidth);
+ my->fXMin = 0;
+ my->fXMax = my->fMaxCharWidth;
+ //my->fXHeight = 0;
+ }
+#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS
+ return;
+ }
+#endif
+
+ OUTLINETEXTMETRIC otm;
+
+ uint32_t ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ if (0 == ret) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ }
+ if (0 == ret) {
+ return;
+ }
+
+ if (mx) {
+ mx->fTop = SkIntToScalar(-otm.otmrcFontBox.left);
+ mx->fAscent = SkIntToScalar(-otm.otmAscent);
+ mx->fDescent = SkIntToScalar(-otm.otmDescent);
+ mx->fBottom = SkIntToScalar(otm.otmrcFontBox.right);
+ mx->fLeading = SkIntToScalar(otm.otmLineGap);
+ }
+
+ if (my) {
+#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS
+ my->fTop = SkIntToScalar(-otm.otmrcFontBox.top);
+ my->fAscent = SkIntToScalar(-otm.otmAscent);
+ my->fDescent = SkIntToScalar(-otm.otmDescent);
+ my->fBottom = SkIntToScalar(-otm.otmrcFontBox.bottom);
+ my->fLeading = SkIntToScalar(otm.otmLineGap);
+ my->fAvgCharWidth = SkIntToScalar(otm.otmTextMetrics.tmAveCharWidth);
+ my->fMaxCharWidth = SkIntToScalar(otm.otmTextMetrics.tmMaxCharWidth);
+ my->fXMin = SkIntToScalar(otm.otmrcFontBox.left);
+ my->fXMax = SkIntToScalar(otm.otmrcFontBox.right);
+#endif
+ my->fXHeight = SkIntToScalar(otm.otmsXHeight);
+
+ GLYPHMETRICS gm;
+ sk_bzero(&gm, sizeof(gm));
+ DWORD len = GetGlyphOutlineW(fDDC, 'x', GGO_METRICS, &gm, 0, 0, &gMat2Identity);
+ if (len != GDI_ERROR && gm.gmBlackBoxY > 0) {
+ my->fXHeight = SkIntToScalar(gm.gmBlackBoxY);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+#define SK_SHOW_TEXT_BLIT_COVERAGE 0
+
+static void build_power_table(uint8_t table[], float ee) {
+ for (int i = 0; i < 256; i++) {
+ float x = i / 255.f;
+ x = sk_float_pow(x, ee);
+ int xx = SkScalarRound(SkFloatToScalar(x * 255));
+ table[i] = SkToU8(xx);
+ }
+}
+
+/**
+ * This will invert the gamma applied by GDI (gray-scale antialiased), so we
+ * can get linear values.
+ *
+ * GDI grayscale appears to use a hard-coded gamma of 2.3.
+ *
+ * GDI grayscale appears to draw using the black and white rasterizer at four
+ * times the size and then downsamples to compute the coverage mask. As a
+ * result there are only seventeen total grays. This lack of fidelity means
+ * that shifting into other color spaces is imprecise.
+ */
+static const uint8_t* getInverseGammaTableGDI() {
+ // Since build_power_table is idempotent, many threads can build gTableGdi
+ // simultaneously.
+
+ // Microsoft Specific:
+ // Making gInited volatile provides read-aquire and write-release in vc++.
+ // In VS2012, see compiler option /volatile:(ms|iso).
+ // Replace with C++11 atomics when possible.
+ static volatile bool gInited;
+ static uint8_t gTableGdi[256];
+ if (gInited) {
+ // Need a L/L (read) barrier (full acquire not needed). If gInited is observed
+ // true then gTableGdi is observable, but it must be requested.
+ } else {
+ build_power_table(gTableGdi, 2.3f);
+ // Need a S/S (write) barrier (full release not needed) here so that this
+ // write to gInited becomes observable after gTableGdi.
+ gInited = true;
+ }
+ return gTableGdi;
+}
+
+/**
+ * This will invert the gamma applied by GDI ClearType, so we can get linear
+ * values.
+ *
+ * GDI ClearType uses SPI_GETFONTSMOOTHINGCONTRAST / 1000 as the gamma value.
+ * If this value is not specified, the default is a gamma of 1.4.
+ */
+static const uint8_t* getInverseGammaTableClearType() {
+ // We don't expect SPI_GETFONTSMOOTHINGCONTRAST to ever change, so building
+ // gTableClearType with build_power_table is effectively idempotent.
+
+ // Microsoft Specific:
+ // Making gInited volatile provides read-aquire and write-release in vc++.
+ // In VS2012, see compiler option /volatile:(ms|iso).
+ // Replace with C++11 atomics when possible.
+ static volatile bool gInited;
+ static uint8_t gTableClearType[256];
+ if (gInited) {
+ // Need a L/L (read) barrier (acquire not needed). If gInited is observed
+ // true then gTableClearType is observable, but it must be requested.
+ } else {
+ UINT level = 0;
+ if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &level, 0) || !level) {
+ // can't get the data, so use a default
+ level = 1400;
+ }
+ build_power_table(gTableClearType, level / 1000.0f);
+ // Need a S/S (write) barrier (release not needed) here so that this
+ // write to gInited becomes observable after gTableClearType.
+ gInited = true;
+ }
+ return gTableClearType;
+}
+
+#include "SkColorPriv.h"
+
+//Cannot assume that the input rgb is gray due to possible setting of kGenA8FromLCD_Flag.
+template<bool APPLY_PREBLEND>
+static inline uint8_t rgb_to_a8(SkGdiRGB rgb, const uint8_t* table8) {
+ U8CPU r = (rgb >> 16) & 0xFF;
+ U8CPU g = (rgb >> 8) & 0xFF;
+ U8CPU b = (rgb >> 0) & 0xFF;
+ return sk_apply_lut_if<APPLY_PREBLEND>(SkComputeLuminance(r, g, b), table8);
+}
+
+template<bool APPLY_PREBLEND>
+static inline uint16_t rgb_to_lcd16(SkGdiRGB rgb, const uint8_t* tableR,
+ const uint8_t* tableG,
+ const uint8_t* tableB) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
+ return SkPack888ToRGB16(r, g, b);
+}
+
+template<bool APPLY_PREBLEND>
+static inline SkPMColor rgb_to_lcd32(SkGdiRGB rgb, const uint8_t* tableR,
+ const uint8_t* tableG,
+ const uint8_t* tableB) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
+ return SkPackARGB32(0xFF, r, g, b);
+}
+
+// Is this GDI color neither black nor white? If so, we have to keep this
+// image as is, rather than smashing it down to a BW mask.
+//
+// returns int instead of bool, since we don't want/have to pay to convert
+// the zero/non-zero value into a bool
+static int is_not_black_or_white(SkGdiRGB c) {
+ // same as (but faster than)
+ // c &= 0x00FFFFFF;
+ // return 0 == c || 0x00FFFFFF == c;
+ return (c + (c & 1)) & 0x00FFFFFF;
+}
+
+static bool is_rgb_really_bw(const SkGdiRGB* src, int width, int height, size_t srcRB) {
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ if (is_not_black_or_white(src[x])) {
+ return false;
+ }
+ }
+ src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
+ }
+ return true;
+}
+
+// gdi's bitmap is upside-down, so we reverse dst walking in Y
+// whenever we copy it into skia's buffer
+static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB,
+ const SkGlyph& glyph) {
+ const int width = glyph.fWidth;
+ const size_t dstRB = (width + 7) >> 3;
+ uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
+
+ int byteCount = width >> 3;
+ int bitCount = width & 7;
+
+ // adjust srcRB to skip the values in our byteCount loop,
+ // since we increment src locally there
+ srcRB -= byteCount * 8 * sizeof(SkGdiRGB);
+
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ if (byteCount > 0) {
+ for (int i = 0; i < byteCount; ++i) {
+ unsigned byte = 0;
+ byte |= src[0] & (1 << 7);
+ byte |= src[1] & (1 << 6);
+ byte |= src[2] & (1 << 5);
+ byte |= src[3] & (1 << 4);
+ byte |= src[4] & (1 << 3);
+ byte |= src[5] & (1 << 2);
+ byte |= src[6] & (1 << 1);
+ byte |= src[7] & (1 << 0);
+ dst[i] = byte;
+ src += 8;
+ }
+ }
+ if (bitCount > 0) {
+ unsigned byte = 0;
+ unsigned mask = 0x80;
+ for (int i = 0; i < bitCount; i++) {
+ byte |= src[i] & mask;
+ mask >>= 1;
+ }
+ dst[byteCount] = byte;
+ }
+ src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
+ dst -= dstRB;
+ }
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ if (glyph.fWidth > 0 && glyph.fHeight > 0) {
+ uint8_t* first = (uint8_t*)glyph.fImage;
+ uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
+ *first |= 1 << 7;
+ *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
+ }
+#endif
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_a8(const SkGdiRGB* SK_RESTRICT src, size_t srcRB,
+ const SkGlyph& glyph, const uint8_t* table8) {
+ const size_t dstRB = glyph.rowBytes();
+ const int width = glyph.fWidth;
+ uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
+
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; i++) {
+ dst[i] = rgb_to_a8<APPLY_PREBLEND>(src[i], table8);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ dst[i] = SkMax32(dst[i], 10);
+#endif
+ }
+ src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
+ dst -= dstRB;
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd16(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const size_t dstRB = glyph.rowBytes();
+ const int width = glyph.fWidth;
+ uint16_t* SK_RESTRICT dst = (uint16_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
+
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; i++) {
+ dst[i] = rgb_to_lcd16<APPLY_PREBLEND>(src[i], tableR, tableG, tableB);
+ }
+ src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
+ dst = (uint16_t*)((char*)dst - dstRB);
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd32(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const size_t dstRB = glyph.rowBytes();
+ const int width = glyph.fWidth;
+ uint32_t* SK_RESTRICT dst = (uint32_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
+
+ for (int y = 0; y < glyph.fHeight; y++) {
+ for (int i = 0; i < width; i++) {
+ dst[i] = rgb_to_lcd32<APPLY_PREBLEND>(src[i], tableR, tableG, tableB);
+ }
+ src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
+ dst = (uint32_t*)((char*)dst - dstRB);
+ }
+}
+
+static inline unsigned clamp255(unsigned x) {
+ SkASSERT(x <= 256);
+ return x - (x >> 8);
+}
+
+void SkScalerContext_GDI::generateImage(const SkGlyph& glyph) {
+ SkASSERT(fDDC);
+
+ const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat;
+ const bool isAA = !isLCD(fRec);
+
+ size_t srcRB;
+ const void* bits = fOffscreen.draw(glyph, isBW, &srcRB);
+ if (NULL == bits) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ bits = fOffscreen.draw(glyph, isBW, &srcRB);
+ if (NULL == bits) {
+ sk_bzero(glyph.fImage, glyph.computeImageSize());
+ return;
+ }
+ }
+
+ if (!isBW) {
+ const uint8_t* table;
+ //The offscreen contains a GDI blit if isAA and kGenA8FromLCD_Flag is not set.
+ //Otherwise the offscreen contains a ClearType blit.
+ if (isAA && !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag)) {
+ table = getInverseGammaTableGDI();
+ } else {
+ table = getInverseGammaTableClearType();
+ }
+ //Note that the following cannot really be integrated into the
+ //pre-blend, since we may not be applying the pre-blend; when we aren't
+ //applying the pre-blend it means that a filter wants linear anyway.
+ //Other code may also be applying the pre-blend, so we'd need another
+ //one with this and one without.
+ SkGdiRGB* addr = (SkGdiRGB*)bits;
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ for (int x = 0; x < glyph.fWidth; ++x) {
+ int r = (addr[x] >> 16) & 0xFF;
+ int g = (addr[x] >> 8) & 0xFF;
+ int b = (addr[x] >> 0) & 0xFF;
+ addr[x] = (table[r] << 16) | (table[g] << 8) | table[b];
+ }
+ addr = SkTAddOffset<SkGdiRGB>(addr, srcRB);
+ }
+ }
+
+ int width = glyph.fWidth;
+ size_t dstRB = glyph.rowBytes();
+ if (isBW) {
+ const uint8_t* src = (const uint8_t*)bits;
+ uint8_t* dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
+ for (int y = 0; y < glyph.fHeight; y++) {
+ memcpy(dst, src, dstRB);
+ src += srcRB;
+ dst -= dstRB;
+ }
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ if (glyph.fWidth > 0 && glyph.fHeight > 0) {
+ int bitCount = width & 7;
+ uint8_t* first = (uint8_t*)glyph.fImage;
+ uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
+ *first |= 1 << 7;
+ *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
+ }
+#endif
+ } else if (isAA) {
+ // 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 (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(src, srcRB, glyph, fPreBlend.fG);
+ } else {
+ rgb_to_a8<false>(src, srcRB, glyph, fPreBlend.fG);
+ }
+ } else { // LCD16
+ const SkGdiRGB* src = (const SkGdiRGB*)bits;
+ if (is_rgb_really_bw(src, width, glyph.fHeight, srcRB)) {
+ rgb_to_bw(src, srcRB, glyph);
+ ((SkGlyph*)&glyph)->fMaskFormat = SkMask::kBW_Format;
+ } else {
+ if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd16<false>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } else {
+ SkASSERT(SkMask::kLCD32_Format == glyph.fMaskFormat);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd32<false>(src, srcRB, glyph,
+ fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ }
+ }
+ }
+}
+
+class GDIGlyphbufferPointIter {
+public:
+ GDIGlyphbufferPointIter(const uint8_t* glyphbuf, DWORD total_size)
+ : fHeaderIter(glyphbuf, total_size), fCurveIter(), fPointIter()
+ { }
+
+ POINTFX next() {
+nextHeader:
+ if (!fCurveIter.isSet()) {
+ const TTPOLYGONHEADER* header = fHeaderIter.next();
+ SkASSERT(header);
+ fCurveIter.set(header);
+ const TTPOLYCURVE* curve = fCurveIter.next();
+ SkASSERT(curve);
+ fPointIter.set(curve);
+ return header->pfxStart;
+ }
+
+ const POINTFX* nextPoint = fPointIter.next();
+ if (NULL == nextPoint) {
+ const TTPOLYCURVE* curve = fCurveIter.next();
+ if (NULL == curve) {
+ fCurveIter.set();
+ goto nextHeader;
+ } else {
+ fPointIter.set(curve);
+ }
+ nextPoint = fPointIter.next();
+ SkASSERT(nextPoint);
+ }
+ return *nextPoint;
+ }
+
+ WORD currentCurveType() {
+ return fPointIter.fCurveType;
+ }
+
+private:
+ /** Iterates over all of the polygon headers in a glyphbuf. */
+ class GDIPolygonHeaderIter {
+ public:
+ GDIPolygonHeaderIter(const uint8_t* glyphbuf, DWORD total_size)
+ : fCurPolygon(reinterpret_cast<const TTPOLYGONHEADER*>(glyphbuf))
+ , fEndPolygon(SkTAddOffset<const TTPOLYGONHEADER>(glyphbuf, total_size))
+ { }
+
+ const TTPOLYGONHEADER* next() {
+ if (fCurPolygon >= fEndPolygon) {
+ return NULL;
+ }
+ const TTPOLYGONHEADER* thisPolygon = fCurPolygon;
+ fCurPolygon = SkTAddOffset<const TTPOLYGONHEADER>(fCurPolygon, fCurPolygon->cb);
+ return thisPolygon;
+ }
+ private:
+ const TTPOLYGONHEADER* fCurPolygon;
+ const TTPOLYGONHEADER* fEndPolygon;
+ };
+
+ /** Iterates over all of the polygon curves in a polygon header. */
+ class GDIPolygonCurveIter {
+ public:
+ GDIPolygonCurveIter() : fCurCurve(NULL), fEndCurve(NULL) { }
+
+ GDIPolygonCurveIter(const TTPOLYGONHEADER* curPolygon)
+ : fCurCurve(SkTAddOffset<const TTPOLYCURVE>(curPolygon, sizeof(TTPOLYGONHEADER)))
+ , fEndCurve(SkTAddOffset<const TTPOLYCURVE>(curPolygon, curPolygon->cb))
+ { }
+
+ bool isSet() { return fCurCurve != NULL; }
+
+ void set(const TTPOLYGONHEADER* curPolygon) {
+ fCurCurve = SkTAddOffset<const TTPOLYCURVE>(curPolygon, sizeof(TTPOLYGONHEADER));
+ fEndCurve = SkTAddOffset<const TTPOLYCURVE>(curPolygon, curPolygon->cb);
+ }
+ void set() {
+ fCurCurve = NULL;
+ fEndCurve = NULL;
+ }
+
+ const TTPOLYCURVE* next() {
+ if (fCurCurve >= fEndCurve) {
+ return NULL;
+ }
+ const TTPOLYCURVE* thisCurve = fCurCurve;
+ fCurCurve = SkTAddOffset<const TTPOLYCURVE>(fCurCurve, size_of_TTPOLYCURVE(*fCurCurve));
+ return thisCurve;
+ }
+ private:
+ size_t size_of_TTPOLYCURVE(const TTPOLYCURVE& curve) {
+ return 2*sizeof(WORD) + curve.cpfx*sizeof(POINTFX);
+ }
+ const TTPOLYCURVE* fCurCurve;
+ const TTPOLYCURVE* fEndCurve;
+ };
+
+ /** Iterates over all of the polygon points in a polygon curve. */
+ class GDIPolygonCurvePointIter {
+ public:
+ GDIPolygonCurvePointIter() : fCurveType(0), fCurPoint(NULL), fEndPoint(NULL) { }
+
+ GDIPolygonCurvePointIter(const TTPOLYCURVE* curPolygon)
+ : fCurveType(curPolygon->wType)
+ , fCurPoint(&curPolygon->apfx[0])
+ , fEndPoint(&curPolygon->apfx[curPolygon->cpfx])
+ { }
+
+ bool isSet() { return fCurPoint != NULL; }
+
+ void set(const TTPOLYCURVE* curPolygon) {
+ fCurveType = curPolygon->wType;
+ fCurPoint = &curPolygon->apfx[0];
+ fEndPoint = &curPolygon->apfx[curPolygon->cpfx];
+ }
+ void set() {
+ fCurPoint = NULL;
+ fEndPoint = NULL;
+ }
+
+ const POINTFX* next() {
+ if (fCurPoint >= fEndPoint) {
+ return NULL;
+ }
+ const POINTFX* thisPoint = fCurPoint;
+ ++fCurPoint;
+ return thisPoint;
+ }
+
+ WORD fCurveType;
+ private:
+ const POINTFX* fCurPoint;
+ const POINTFX* fEndPoint;
+ };
+
+ GDIPolygonHeaderIter fHeaderIter;
+ GDIPolygonCurveIter fCurveIter;
+ GDIPolygonCurvePointIter fPointIter;
+};
+
+static void sk_path_from_gdi_path(SkPath* path, const uint8_t* glyphbuf, DWORD total_size) {
+ const uint8_t* cur_glyph = glyphbuf;
+ const uint8_t* end_glyph = glyphbuf + total_size;
+
+ while (cur_glyph < end_glyph) {
+ const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph;
+
+ const uint8_t* end_poly = cur_glyph + th->cb;
+ const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER);
+
+ path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(th->pfxStart.y)));
+
+ while (cur_poly < end_poly) {
+ const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly;
+
+ if (pc->wType == TT_PRIM_LINE) {
+ for (uint16_t i = 0; i < pc->cpfx; i++) {
+ path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pc->apfx[i].y)));
+ }
+ }
+
+ if (pc->wType == TT_PRIM_QSPLINE) {
+ for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline
+ POINTFX pnt_b = pc->apfx[u]; // B is always the current point
+ POINTFX pnt_c = pc->apfx[u+1];
+
+ if (u < pc->cpfx - 2) { // If not on last spline, compute C
+ pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
+ SkFIXEDToFixed(pnt_c.x)));
+ pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
+ SkFIXEDToFixed(pnt_c.y)));
+ }
+
+ path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)),
+ SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y)));
+ }
+ }
+ // Advance past this TTPOLYCURVE.
+ cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
+ }
+ cur_glyph += th->cb;
+ path->close();
+ }
+}
+
+static void sk_path_from_gdi_paths(SkPath* path, const uint8_t* glyphbuf, DWORD total_size,
+ GDIGlyphbufferPointIter hintedYs) {
+ const uint8_t* cur_glyph = glyphbuf;
+ const uint8_t* end_glyph = glyphbuf + total_size;
+
+ while (cur_glyph < end_glyph) {
+ const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph;
+
+ const uint8_t* end_poly = cur_glyph + th->cb;
+ const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER);
+
+ path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(hintedYs.next().y)));
+
+ while (cur_poly < end_poly) {
+ const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly;
+
+ if (pc->wType == TT_PRIM_LINE) {
+ for (uint16_t i = 0; i < pc->cpfx; i++) {
+ path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)),
+ SkFixedToScalar(-SkFIXEDToFixed(hintedYs.next().y)));
+ }
+ }
+
+ if (pc->wType == TT_PRIM_QSPLINE) {
+ POINTFX currentPoint = pc->apfx[0];
+ POINTFX hintedY = hintedYs.next();
+ // only take the hinted y if it wasn't flipped
+ if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
+ currentPoint.y = hintedY.y;
+ }
+ for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline
+ POINTFX pnt_b = currentPoint;//pc->apfx[u]; // B is always the current point
+ POINTFX pnt_c = pc->apfx[u+1];
+ POINTFX hintedY = hintedYs.next();
+ // only take the hinted y if it wasn't flipped
+ if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
+ pnt_c.y = hintedY.y;
+ }
+ currentPoint.x = pnt_c.x;
+ currentPoint.y = pnt_c.y;
+
+ if (u < pc->cpfx - 2) { // If not on last spline, compute C
+ pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
+ SkFIXEDToFixed(pnt_c.x)));
+ pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
+ SkFIXEDToFixed(pnt_c.y)));
+ }
+
+ path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)),
+ SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y)));
+ }
+ }
+ // Advance past this TTPOLYCURVE.
+ cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
+ }
+ cur_glyph += th->cb;
+ path->close();
+ }
+}
+
+DWORD SkScalerContext_GDI::getGDIGlyphPath(const SkGlyph& glyph, UINT flags,
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t>* glyphbuf)
+{
+ GLYPHMETRICS gm;
+
+ DWORD total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, BUFFERSIZE, glyphbuf->get(), &fMat22);
+ // Sometimes GetGlyphOutlineW returns a number larger than BUFFERSIZE even if BUFFERSIZE > 0.
+ // It has been verified that this does not involve a buffer overrun.
+ if (GDI_ERROR == total_size || total_size > BUFFERSIZE) {
+ // GDI_ERROR because the BUFFERSIZE was too small, or because the data was not accessible.
+ // When the data is not accessable GetGlyphOutlineW fails rather quickly,
+ // so just try to get the size. If that fails then ensure the data is accessible.
+ total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == total_size) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == total_size) {
+ SkASSERT(false);
+ return 0;
+ }
+ }
+
+ glyphbuf->reset(total_size);
+
+ DWORD ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf->get(), &fMat22);
+ if (GDI_ERROR == ret) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf->get(), &fMat22);
+ if (GDI_ERROR == ret) {
+ SkASSERT(false);
+ return 0;
+ }
+ }
+ }
+ return total_size;
+}
+
+void SkScalerContext_GDI::generatePath(const SkGlyph& glyph, SkPath* path) {
+ SkASSERT(&glyph && path);
+ SkASSERT(fDDC);
+
+ path->reset();
+
+ // Out of all the fonts on a typical Windows box,
+ // 25% of glyphs require more than 2KB.
+ // 1% of glyphs require more than 4KB.
+ // 0.01% of glyphs require more than 8KB.
+ // 8KB is less than 1% of the normal 1MB stack on Windows.
+ // Note that some web fonts glyphs require more than 20KB.
+ //static const DWORD BUFFERSIZE = (1 << 13);
+
+ //GDI only uses hinted outlines when axis aligned.
+ UINT format = GGO_NATIVE | GGO_GLYPH_INDEX;
+ if (fRec.getHinting() == SkPaint::kNo_Hinting || fRec.getHinting() == SkPaint::kSlight_Hinting){
+ format |= GGO_UNHINTED;
+ }
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t> glyphbuf(BUFFERSIZE);
+ DWORD total_size = getGDIGlyphPath(glyph, format, &glyphbuf);
+ if (0 == total_size) {
+ return;
+ }
+
+ if (fRec.getHinting() != SkPaint::kSlight_Hinting) {
+ sk_path_from_gdi_path(path, glyphbuf, total_size);
+ } else {
+ //GDI only uses hinted outlines when axis aligned.
+ UINT format = GGO_NATIVE | GGO_GLYPH_INDEX;
+
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t> hintedGlyphbuf(BUFFERSIZE);
+ DWORD hinted_total_size = getGDIGlyphPath(glyph, format, &hintedGlyphbuf);
+ if (0 == hinted_total_size) {
+ return;
+ }
+
+ sk_path_from_gdi_paths(path, glyphbuf, total_size,
+ GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size));
+ }
+}
+
+static void logfont_for_name(const char* familyName, LOGFONT* lf) {
+ sk_bzero(lf, sizeof(LOGFONT));
+#ifdef UNICODE
+ // Get the buffer size needed first.
+ size_t str_len = ::MultiByteToWideChar(CP_UTF8, 0, familyName,
+ -1, NULL, 0);
+ // Allocate a buffer (str_len already has terminating null
+ // accounted for).
+ wchar_t *wideFamilyName = new wchar_t[str_len];
+ // Now actually convert the string.
+ ::MultiByteToWideChar(CP_UTF8, 0, familyName, -1,
+ wideFamilyName, str_len);
+ ::wcsncpy(lf->lfFaceName, wideFamilyName, LF_FACESIZE - 1);
+ delete [] wideFamilyName;
+ lf->lfFaceName[LF_FACESIZE-1] = L'\0';
+#else
+ ::strncpy(lf->lfFaceName, familyName, LF_FACESIZE - 1);
+ lf->lfFaceName[LF_FACESIZE - 1] = '\0';
+#endif
+}
+
+void LogFontTypeface::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocalStream) const {
+ // Get the actual name of the typeface. The logfont may not know this.
+ HFONT font = CreateFontIndirect(&fLogFont);
+
+ HDC deviceContext = ::CreateCompatibleDC(NULL);
+ HFONT savefont = (HFONT)SelectObject(deviceContext, font);
+
+ SkString familyName;
+ dcfontname_to_skstring(deviceContext, fLogFont, &familyName);
+
+ if (deviceContext) {
+ ::SelectObject(deviceContext, savefont);
+ ::DeleteDC(deviceContext);
+ }
+ if (font) {
+ ::DeleteObject(font);
+ }
+
+ desc->setFamilyName(familyName.c_str());
+ *isLocalStream = this->fSerializeAsStream;
+}
+
+static bool getWidthAdvance(HDC hdc, int gId, int16_t* advance) {
+ // Initialize the MAT2 structure to the identify transformation matrix.
+ static const MAT2 mat2 = {SkScalarToFIXED(1), SkScalarToFIXED(0),
+ SkScalarToFIXED(0), SkScalarToFIXED(1)};
+ int flags = GGO_METRICS | GGO_GLYPH_INDEX;
+ GLYPHMETRICS gm;
+ if (GDI_ERROR == GetGlyphOutline(hdc, gId, flags, &gm, 0, NULL, &mat2)) {
+ return false;
+ }
+ SkASSERT(advance);
+ *advance = gm.gmCellIncX;
+ return true;
+}
+
+SkAdvancedTypefaceMetrics* LogFontTypeface::onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+ LOGFONT lf = fLogFont;
+ SkAdvancedTypefaceMetrics* info = NULL;
+
+ HDC hdc = CreateCompatibleDC(NULL);
+ HFONT font = CreateFontIndirect(&lf);
+ HFONT savefont = (HFONT)SelectObject(hdc, font);
+ HFONT designFont = NULL;
+
+ const char stem_chars[] = {'i', 'I', '!', '1'};
+ int16_t min_width;
+ unsigned glyphCount;
+
+ // To request design units, create a logical font whose height is specified
+ // as unitsPerEm.
+ OUTLINETEXTMETRIC otm;
+ unsigned int otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm);
+ if (0 == otmRet) {
+ call_ensure_accessible(lf);
+ otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm);
+ }
+ if (!otmRet || !GetTextFace(hdc, LF_FACESIZE, lf.lfFaceName)) {
+ goto Error;
+ }
+ lf.lfHeight = -SkToS32(otm.otmEMSquare);
+ designFont = CreateFontIndirect(&lf);
+ SelectObject(hdc, designFont);
+ if (!GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) {
+ goto Error;
+ }
+ glyphCount = calculateGlyphCount(hdc, fLogFont);
+
+ info = new SkAdvancedTypefaceMetrics;
+ info->fEmSize = otm.otmEMSquare;
+ info->fMultiMaster = false;
+ info->fLastGlyphID = SkToU16(glyphCount - 1);
+ info->fStyle = 0;
+ tchar_to_skstring(lf.lfFaceName, &info->fFontName);
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo) {
+ populate_glyph_to_unicode(hdc, glyphCount, &(info->fGlyphToUnicode));
+ }
+
+ if (glyphCount > 0 &&
+ (otm.otmTextMetrics.tmPitchAndFamily & TMPF_TRUETYPE)) {
+ info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font;
+ } else {
+ info->fType = SkAdvancedTypefaceMetrics::kOther_Font;
+ info->fItalicAngle = 0;
+ info->fAscent = 0;
+ info->fDescent = 0;
+ info->fStemV = 0;
+ info->fCapHeight = 0;
+ info->fBBox = SkIRect::MakeEmpty();
+ goto ReturnInfo;
+ }
+
+ // If this bit is clear the font is a fixed pitch font.
+ if (!(otm.otmTextMetrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style;
+ }
+ if (otm.otmTextMetrics.tmItalic) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
+ }
+ if (otm.otmTextMetrics.tmPitchAndFamily & FF_ROMAN) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
+ } else if (otm.otmTextMetrics.tmPitchAndFamily & FF_SCRIPT) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
+ }
+
+ // The main italic angle of the font, in tenths of a degree counterclockwise
+ // from vertical.
+ info->fItalicAngle = otm.otmItalicAngle / 10;
+ info->fAscent = SkToS16(otm.otmTextMetrics.tmAscent);
+ info->fDescent = SkToS16(-otm.otmTextMetrics.tmDescent);
+ // TODO(ctguil): Use alternate cap height calculation.
+ // MSDN says otmsCapEmHeight is not support but it is returning a value on
+ // my Win7 box.
+ info->fCapHeight = otm.otmsCapEmHeight;
+ info->fBBox =
+ SkIRect::MakeLTRB(otm.otmrcFontBox.left, otm.otmrcFontBox.top,
+ otm.otmrcFontBox.right, otm.otmrcFontBox.bottom);
+
+ // Figure out a good guess for StemV - Min width of i, I, !, 1.
+ // This probably isn't very good with an italic font.
+ min_width = SHRT_MAX;
+ info->fStemV = 0;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(stem_chars); i++) {
+ ABC abcWidths;
+ if (GetCharABCWidths(hdc, stem_chars[i], stem_chars[i], &abcWidths)) {
+ int16_t width = abcWidths.abcB;
+ if (width > 0 && width < min_width) {
+ min_width = width;
+ info->fStemV = min_width;
+ }
+ }
+ }
+
+ // If bit 1 is set, the font may not be embedded in a document.
+ // If bit 1 is clear, the font can be embedded.
+ // If bit 2 is set, the embedding is read-only.
+ if (otm.otmfsType & 0x1) {
+ info->fType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
+ } else if (perGlyphInfo &
+ SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) {
+ if (info->fStyle & SkAdvancedTypefaceMetrics::kFixedPitch_Style) {
+ appendRange(&info->fGlyphWidths, 0);
+ info->fGlyphWidths->fAdvance.append(1, &min_width);
+ finishRange(info->fGlyphWidths.get(), 0,
+ SkAdvancedTypefaceMetrics::WidthRange::kDefault);
+ } else {
+ info->fGlyphWidths.reset(
+ getAdvanceData(hdc,
+ glyphCount,
+ glyphIDs,
+ glyphIDsCount,
+ &getWidthAdvance));
+ }
+ }
+
+Error:
+ReturnInfo:
+ SelectObject(hdc, savefont);
+ DeleteObject(designFont);
+ DeleteObject(font);
+ DeleteDC(hdc);
+
+ return info;
+}
+
+//Dummy representation of a Base64 encoded GUID from create_unique_font_name.
+#define BASE64_GUID_ID "XXXXXXXXXXXXXXXXXXXXXXXX"
+//Length of GUID representation from create_id, including NULL terminator.
+#define BASE64_GUID_ID_LEN SK_ARRAY_COUNT(BASE64_GUID_ID)
+
+SK_COMPILE_ASSERT(BASE64_GUID_ID_LEN < LF_FACESIZE, GUID_longer_than_facesize);
+
+/**
+ NameID 6 Postscript names cannot have the character '/'.
+ It would be easier to hex encode the GUID, but that is 32 bytes,
+ and many systems have issues with names longer than 28 bytes.
+ The following need not be any standard base64 encoding.
+ The encoded value is never decoded.
+*/
+static const char postscript_safe_base64_encode[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-_=";
+
+/**
+ Formats a GUID into Base64 and places it into buffer.
+ buffer should have space for at least BASE64_GUID_ID_LEN characters.
+ The string will always be null terminated.
+ XXXXXXXXXXXXXXXXXXXXXXXX0
+ */
+static void format_guid_b64(const GUID& guid, char* buffer, size_t bufferSize) {
+ SkASSERT(bufferSize >= BASE64_GUID_ID_LEN);
+ size_t written = SkBase64::Encode(&guid, sizeof(guid), buffer, postscript_safe_base64_encode);
+ SkASSERT(written < LF_FACESIZE);
+ buffer[written] = '\0';
+}
+
+/**
+ Creates a Base64 encoded GUID and places it into buffer.
+ buffer should have space for at least BASE64_GUID_ID_LEN characters.
+ The string will always be null terminated.
+ XXXXXXXXXXXXXXXXXXXXXXXX0
+ */
+static HRESULT create_unique_font_name(char* buffer, size_t bufferSize) {
+ GUID guid = {};
+ if (FAILED(CoCreateGuid(&guid))) {
+ return E_UNEXPECTED;
+ }
+ format_guid_b64(guid, buffer, bufferSize);
+
+ return S_OK;
+}
+
+/**
+ Introduces a font to GDI. On failure will return NULL. The returned handle
+ should eventually be passed to RemoveFontMemResourceEx.
+*/
+static HANDLE activate_font(SkData* fontData) {
+ DWORD numFonts = 0;
+ //AddFontMemResourceEx just copies the data, but does not specify const.
+ HANDLE fontHandle = AddFontMemResourceEx(const_cast<void*>(fontData->data()),
+ static_cast<DWORD>(fontData->size()),
+ 0,
+ &numFonts);
+
+ if (fontHandle != NULL && numFonts < 1) {
+ RemoveFontMemResourceEx(fontHandle);
+ return NULL;
+ }
+
+ return fontHandle;
+}
+
+static SkTypeface* create_from_stream(SkStream* stream) {
+ // Create a unique and unpredictable font name.
+ // Avoids collisions and access from CSS.
+ char familyName[BASE64_GUID_ID_LEN];
+ const int familyNameSize = SK_ARRAY_COUNT(familyName);
+ if (FAILED(create_unique_font_name(familyName, familyNameSize))) {
+ return NULL;
+ }
+
+ // Change the name of the font.
+ SkAutoTUnref<SkData> rewrittenFontData(SkOTUtils::RenameFont(stream, familyName, familyNameSize-1));
+ if (NULL == rewrittenFontData.get()) {
+ return NULL;
+ }
+
+ // Register the font with GDI.
+ HANDLE fontReference = activate_font(rewrittenFontData.get());
+ if (NULL == fontReference) {
+ return NULL;
+ }
+
+ // Create the typeface.
+ LOGFONT lf;
+ logfont_for_name(familyName, &lf);
+
+ return SkCreateFontMemResourceTypefaceFromLOGFONT(lf, fontReference);
+}
+
+SkStream* LogFontTypeface::onOpenStream(int* ttcIndex) const {
+ *ttcIndex = 0;
+
+ const DWORD kTTCTag =
+ SkEndian_SwapBE32(SkSetFourByteTag('t', 't', 'c', 'f'));
+ LOGFONT lf = fLogFont;
+
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ HFONT font = CreateFontIndirect(&lf);
+ HFONT savefont = (HFONT)SelectObject(hdc, font);
+
+ SkMemoryStream* stream = NULL;
+ DWORD tables[2] = {kTTCTag, 0};
+ for (int i = 0; i < SK_ARRAY_COUNT(tables); i++) {
+ DWORD bufferSize = GetFontData(hdc, tables[i], 0, NULL, 0);
+ if (bufferSize == GDI_ERROR) {
+ call_ensure_accessible(lf);
+ bufferSize = GetFontData(hdc, tables[i], 0, NULL, 0);
+ }
+ if (bufferSize != GDI_ERROR) {
+ stream = new SkMemoryStream(bufferSize);
+ if (GetFontData(hdc, tables[i], 0, (void*)stream->getMemoryBase(), bufferSize)) {
+ break;
+ } else {
+ delete stream;
+ stream = NULL;
+ }
+ }
+ }
+
+ SelectObject(hdc, savefont);
+ DeleteObject(font);
+ DeleteDC(hdc);
+
+ return stream;
+}
+
+int LogFontTypeface::onCountGlyphs() const {
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ HFONT font = CreateFontIndirect(&fLogFont);
+ HFONT savefont = (HFONT)SelectObject(hdc, font);
+
+ unsigned int glyphCount = calculateGlyphCount(hdc, fLogFont);
+
+ SelectObject(hdc, savefont);
+ DeleteObject(font);
+ DeleteDC(hdc);
+
+ return glyphCount;
+}
+
+int LogFontTypeface::onGetUPEM() const {
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ HFONT font = CreateFontIndirect(&fLogFont);
+ HFONT savefont = (HFONT)SelectObject(hdc, font);
+
+ unsigned int upem = calculateUPEM(hdc, fLogFont);
+
+ SelectObject(hdc, savefont);
+ DeleteObject(font);
+ DeleteDC(hdc);
+
+ return upem;
+}
+
+SkTypeface::LocalizedStrings* LogFontTypeface::onCreateFamilyNameIterator() const {
+ SkTypeface::LocalizedStrings* nameIter =
+ SkOTUtils::LocalizedStrings_NameTable::CreateForFamilyNames(*this);
+ if (NULL == nameIter) {
+ SkString familyName;
+ this->getFamilyName(&familyName);
+ SkString language("und"); //undetermined
+ nameIter = new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
+ }
+ return nameIter;
+}
+
+int LogFontTypeface::onGetTableTags(SkFontTableTag tags[]) const {
+ SkSFNTHeader header;
+ if (sizeof(header) != this->onGetTableData(0, 0, sizeof(header), &header)) {
+ return 0;
+ }
+
+ int numTables = SkEndian_SwapBE16(header.numTables);
+
+ if (tags) {
+ size_t size = numTables * sizeof(SkSFNTHeader::TableDirectoryEntry);
+ SkAutoSTMalloc<0x20, SkSFNTHeader::TableDirectoryEntry> dir(numTables);
+ if (size != this->onGetTableData(0, sizeof(header), size, dir.get())) {
+ return 0;
+ }
+
+ for (int i = 0; i < numTables; ++i) {
+ tags[i] = SkEndian_SwapBE32(dir[i].tag);
+ }
+ }
+ return numTables;
+}
+
+size_t LogFontTypeface::onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* data) const
+{
+ LOGFONT lf = fLogFont;
+
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ HFONT font = CreateFontIndirect(&lf);
+ HFONT savefont = (HFONT)SelectObject(hdc, font);
+
+ tag = SkEndian_SwapBE32(tag);
+ if (NULL == data) {
+ length = 0;
+ }
+ DWORD bufferSize = GetFontData(hdc, tag, offset, data, length);
+ if (bufferSize == GDI_ERROR) {
+ call_ensure_accessible(lf);
+ bufferSize = GetFontData(hdc, tag, offset, data, length);
+ }
+
+ SelectObject(hdc, savefont);
+ DeleteObject(font);
+ DeleteDC(hdc);
+
+ return bufferSize == GDI_ERROR ? 0 : bufferSize;
+}
+
+SkScalerContext* LogFontTypeface::onCreateScalerContext(const SkDescriptor* desc) const {
+ SkScalerContext_GDI* ctx = SkNEW_ARGS(SkScalerContext_GDI,
+ (const_cast<LogFontTypeface*>(this), desc));
+ if (!ctx->isValid()) {
+ SkDELETE(ctx);
+ ctx = NULL;
+ }
+ return ctx;
+}
+
+void LogFontTypeface::onFilterRec(SkScalerContextRec* rec) const {
+ if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag ||
+ rec->fFlags & SkScalerContext::kLCD_Vertical_Flag)
+ {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag;
+ }
+
+ unsigned flagsWeDontSupport = SkScalerContext::kDevKernText_Flag |
+ SkScalerContext::kAutohinting_Flag |
+ SkScalerContext::kEmbeddedBitmapText_Flag |
+ SkScalerContext::kEmbolden_Flag |
+ SkScalerContext::kLCD_BGROrder_Flag |
+ SkScalerContext::kLCD_Vertical_Flag;
+ rec->fFlags &= ~flagsWeDontSupport;
+
+ SkPaint::Hinting h = rec->getHinting();
+ switch (h) {
+ case SkPaint::kNo_Hinting:
+ break;
+ case SkPaint::kSlight_Hinting:
+ // Only do slight hinting when axis aligned.
+ // TODO: re-enable slight hinting when FontHostTest can pass.
+ //if (!isAxisAligned(*rec)) {
+ h = SkPaint::kNo_Hinting;
+ //}
+ break;
+ case SkPaint::kNormal_Hinting:
+ case SkPaint::kFull_Hinting:
+ // TODO: need to be able to distinguish subpixel positioned glyphs
+ // and linear metrics.
+ //rec->fFlags &= ~SkScalerContext::kSubpixelPositioning_Flag;
+ h = SkPaint::kNormal_Hinting;
+ break;
+ default:
+ SkDEBUGFAIL("unknown hinting");
+ }
+ //TODO: if this is a bitmap font, squash hinting and subpixel.
+ rec->setHinting(h);
+
+// turn this off since GDI might turn A8 into BW! Need a bigger fix.
+#if 0
+ // Disable LCD when rotated, since GDI's output is ugly
+ if (isLCD(*rec) && !isAxisAligned(*rec)) {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ }
+#endif
+
+ if (!fCanBeLCD && isLCD(*rec)) {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ rec->fFlags &= ~SkScalerContext::kGenA8FromLCD_Flag;
+ }
+}
+
+static SkTypeface* create_typeface(const SkTypeface* familyFace,
+ const char familyName[],
+ unsigned styleBits) {
+ LOGFONT lf;
+ if (NULL == familyFace && NULL == familyName) {
+ lf = get_default_font();
+ } else if (familyFace) {
+ LogFontTypeface* face = (LogFontTypeface*)familyFace;
+ lf = face->fLogFont;
+ } else {
+ logfont_for_name(familyName, &lf);
+ }
+ setStyle(&lf, (SkTypeface::Style)styleBits);
+ return SkCreateTypefaceFromLOGFONT(lf);
+}
+
+SkTypeface* LogFontTypeface::onRefMatchingStyle(Style style) const {
+ return create_typeface(this, NULL, style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontMgr.h"
+#include "SkDataTable.h"
+
+static bool valid_logfont_for_enum(const LOGFONT& lf) {
+ // TODO: Vector FON is unsupported and should not be listed.
+ return
+ // Ignore implicit vertical variants.
+ lf.lfFaceName[0] && lf.lfFaceName[0] != '@'
+
+ // DEFAULT_CHARSET is used to get all fonts, but also implies all
+ // character sets. Filter assuming all fonts support ANSI_CHARSET.
+ && ANSI_CHARSET == lf.lfCharSet
+ ;
+}
+
+/** An EnumFontFamExProc implementation which interprets builderParam as
+ * an SkTDArray<ENUMLOGFONTEX>* and appends logfonts which
+ * pass the valid_logfont_for_enum predicate.
+ */
+static int CALLBACK enum_family_proc(const LOGFONT* lf, const TEXTMETRIC*,
+ DWORD fontType, LPARAM builderParam) {
+ if (valid_logfont_for_enum(*lf)) {
+ SkTDArray<ENUMLOGFONTEX>* array = (SkTDArray<ENUMLOGFONTEX>*)builderParam;
+ *array->append() = *(ENUMLOGFONTEX*)lf;
+ }
+ return 1; // non-zero means continue
+}
+
+static SkFontStyle compute_fontstyle(const LOGFONT& lf) {
+ return SkFontStyle(lf.lfWeight, SkFontStyle::kNormal_Width,
+ lf.lfItalic ? SkFontStyle::kItalic_Slant
+ : SkFontStyle::kUpright_Slant);
+}
+
+class SkFontStyleSetGDI : public SkFontStyleSet {
+public:
+ SkFontStyleSetGDI(const TCHAR familyName[]) {
+ LOGFONT lf;
+ sk_bzero(&lf, sizeof(lf));
+ lf.lfCharSet = DEFAULT_CHARSET;
+ _tcscpy_s(lf.lfFaceName, familyName);
+
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ ::EnumFontFamiliesEx(hdc, &lf, enum_family_proc, (LPARAM)&fArray, 0);
+ ::DeleteDC(hdc);
+ }
+
+ virtual int count() SK_OVERRIDE {
+ return fArray.count();
+ }
+
+ virtual void getStyle(int index, SkFontStyle* fs, SkString* styleName) SK_OVERRIDE {
+ if (fs) {
+ *fs = compute_fontstyle(fArray[index].elfLogFont);
+ }
+ if (styleName) {
+ const ENUMLOGFONTEX& ref = fArray[index];
+ // For some reason, ENUMLOGFONTEX and LOGFONT disagree on their type in the
+ // non-unicode version.
+ // ENUMLOGFONTEX uses BYTE
+ // LOGFONT uses CHAR
+ // Here we assert they that the style name is logically the same (size) as
+ // a TCHAR, so we can use the same converter function.
+ SkASSERT(sizeof(TCHAR) == sizeof(ref.elfStyle[0]));
+ tchar_to_skstring((const TCHAR*)ref.elfStyle, styleName);
+ }
+ }
+
+ virtual SkTypeface* createTypeface(int index) SK_OVERRIDE {
+ return SkCreateTypefaceFromLOGFONT(fArray[index].elfLogFont);
+ }
+
+ virtual SkTypeface* matchStyle(const SkFontStyle& pattern) SK_OVERRIDE {
+ // todo:
+ return SkCreateTypefaceFromLOGFONT(fArray[0].elfLogFont);
+ }
+
+private:
+ SkTDArray<ENUMLOGFONTEX> fArray;
+};
+
+class SkFontMgrGDI : public SkFontMgr {
+public:
+ SkFontMgrGDI() {
+ LOGFONT lf;
+ sk_bzero(&lf, sizeof(lf));
+ lf.lfCharSet = DEFAULT_CHARSET;
+
+ HDC hdc = ::CreateCompatibleDC(NULL);
+ ::EnumFontFamiliesEx(hdc, &lf, enum_family_proc, (LPARAM)&fLogFontArray, 0);
+ ::DeleteDC(hdc);
+ }
+
+protected:
+ virtual int onCountFamilies() SK_OVERRIDE {
+ return fLogFontArray.count();
+ }
+
+ virtual void onGetFamilyName(int index, SkString* familyName) SK_OVERRIDE {
+ SkASSERT((unsigned)index < (unsigned)fLogFontArray.count());
+ tchar_to_skstring(fLogFontArray[index].elfLogFont.lfFaceName, familyName);
+ }
+
+ virtual SkFontStyleSet* onCreateStyleSet(int index) SK_OVERRIDE {
+ SkASSERT((unsigned)index < (unsigned)fLogFontArray.count());
+ return SkNEW_ARGS(SkFontStyleSetGDI, (fLogFontArray[index].elfLogFont.lfFaceName));
+ }
+
+ virtual SkFontStyleSet* onMatchFamily(const char familyName[]) SK_OVERRIDE {
+ if (NULL == familyName) {
+ familyName = ""; // do we need this check???
+ }
+ LOGFONT lf;
+ logfont_for_name(familyName, &lf);
+ return SkNEW_ARGS(SkFontStyleSetGDI, (lf.lfFaceName));
+ }
+
+ virtual SkTypeface* onMatchFamilyStyle(const char familyName[],
+ const SkFontStyle& fontstyle) SK_OVERRIDE {
+ // could be in base impl
+ SkAutoTUnref<SkFontStyleSet> sset(this->matchFamily(familyName));
+ return sset->matchStyle(fontstyle);
+ }
+
+ virtual SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember,
+ const SkFontStyle& fontstyle) SK_OVERRIDE {
+ // could be in base impl
+ SkString familyName;
+ ((LogFontTypeface*)familyMember)->getFamilyName(&familyName);
+ return this->matchFamilyStyle(familyName.c_str(), fontstyle);
+ }
+
+ virtual SkTypeface* onCreateFromStream(SkStream* stream, int ttcIndex) SK_OVERRIDE {
+ return create_from_stream(stream);
+ }
+
+ virtual SkTypeface* onCreateFromData(SkData* data, int ttcIndex) SK_OVERRIDE {
+ // could be in base impl
+ SkAutoTUnref<SkStream> stream(SkNEW_ARGS(SkMemoryStream, (data)));
+ return this->createFromStream(stream);
+ }
+
+ virtual SkTypeface* onCreateFromFile(const char path[], int ttcIndex) SK_OVERRIDE {
+ // could be in base impl
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return this->createFromStream(stream);
+ }
+
+ virtual SkTypeface* onLegacyCreateTypeface(const char familyName[],
+ unsigned styleBits) SK_OVERRIDE {
+ return create_typeface(NULL, familyName, styleBits);
+ }
+
+private:
+ SkTDArray<ENUMLOGFONTEX> fLogFontArray;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_FONTHOST_USES_FONTMGR
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style styleBits) {
+ return create_typeface(familyFace, familyName, styleBits);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return stream.get() ? CreateTypefaceFromStream(stream) : NULL;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ return create_from_stream(stream);
+}
+
+#endif
+
+SkFontMgr* SkFontMgr::Factory() {
+ return SkNEW(SkFontMgrGDI);
+}
diff --git a/ports/SkFontHost_win_dw.cpp b/ports/SkFontHost_win_dw.cpp
new file mode 100644
index 00000000..5df90b47
--- /dev/null
+++ b/ports/SkFontHost_win_dw.cpp
@@ -0,0 +1,1869 @@
+/*
+ * 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 "SkTypes.h"
+#undef GetGlyphIndices
+
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkColorFilter.h"
+#include "SkDWriteFontFileStream.h"
+#include "SkDWriteGeometrySink.h"
+#include "SkDescriptor.h"
+#include "SkEndian.h"
+#include "SkFontDescriptor.h"
+#include "SkFontHost.h"
+#include "SkFontStream.h"
+#include "SkGlyph.h"
+#include "SkHRESULT.h"
+#include "SkMaskGamma.h"
+#include "SkOTTable_head.h"
+#include "SkOTTable_hhea.h"
+#include "SkOTTable_OS_2.h"
+#include "SkOTTable_post.h"
+#include "SkPath.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTScopedComPtr.h"
+#include "SkThread.h"
+#include "SkTypeface_win.h"
+#include "SkTypefaceCache.h"
+#include "SkUtils.h"
+
+#include <dwrite.h>
+
+SK_DECLARE_STATIC_MUTEX(gFTMutex);
+
+static bool isLCD(const SkScalerContext::Rec& rec) {
+ return SkMask::kLCD16_Format == rec.fMaskFormat ||
+ SkMask::kLCD32_Format == rec.fMaskFormat;
+}
+
+/** Prefer to use this type to prevent template proliferation. */
+typedef SkAutoSTMalloc<16, WCHAR> SkSMallocWCHAR;
+
+static HRESULT cstring_to_wchar(const char* skname, SkSMallocWCHAR* name) {
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, skname, -1, NULL, 0);
+ if (0 == wlen) {
+ HRM(HRESULT_FROM_WIN32(GetLastError()),
+ "Could not get length for wchar to utf-8 conversion.");
+ }
+ name->reset(wlen);
+ wlen = MultiByteToWideChar(CP_UTF8, 0, skname, -1, name->get(), wlen);
+ if (0 == wlen) {
+ HRM(HRESULT_FROM_WIN32(GetLastError()), "Could not convert wchar to utf-8.");
+ }
+ return S_OK;
+}
+
+static HRESULT wchar_to_skstring(WCHAR* name, SkString* skname) {
+ int len = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL);
+ if (0 == len) {
+ HRM(HRESULT_FROM_WIN32(GetLastError()),
+ "Could not get length for utf-8 to wchar conversion.");
+ }
+ skname->resize(len - 1);
+ len = WideCharToMultiByte(CP_UTF8, 0, name, -1, skname->writable_str(), len, NULL, NULL);
+ if (0 == len) {
+ HRM(HRESULT_FROM_WIN32(GetLastError()), "Could not convert utf-8 to wchar.");
+ }
+ return S_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class DWriteOffscreen {
+public:
+ DWriteOffscreen() : fWidth(0), fHeight(0) {
+ }
+
+ void init(IDWriteFontFace* fontFace, const DWRITE_MATRIX& xform, FLOAT fontSize) {
+ fFontFace = fontFace;
+ fFontSize = fontSize;
+ fXform = xform;
+ }
+
+ const void* draw(const SkGlyph&, bool isBW);
+
+private:
+ uint16_t fWidth;
+ uint16_t fHeight;
+ IDWriteFontFace* fFontFace;
+ FLOAT fFontSize;
+ DWRITE_MATRIX fXform;
+ SkTDArray<uint8_t> fBits;
+};
+
+typedef HRESULT (WINAPI *DWriteCreateFactoryProc)(
+ __in DWRITE_FACTORY_TYPE factoryType,
+ __in REFIID iid,
+ __out IUnknown **factory
+);
+
+static HRESULT get_dwrite_factory(IDWriteFactory** factory) {
+ static IDWriteFactory* gDWriteFactory = NULL;
+
+ if (gDWriteFactory != NULL) {
+ *factory = gDWriteFactory;
+ return S_OK;
+ }
+
+ DWriteCreateFactoryProc dWriteCreateFactoryProc =
+ reinterpret_cast<DWriteCreateFactoryProc>(
+ GetProcAddress(LoadLibraryW(L"dwrite.dll"), "DWriteCreateFactory")
+ )
+ ;
+
+ if (!dWriteCreateFactoryProc) {
+ return E_UNEXPECTED;
+ }
+
+ HRM(dWriteCreateFactoryProc(DWRITE_FACTORY_TYPE_SHARED,
+ __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(&gDWriteFactory)),
+ "Could not create DirectWrite factory.");
+
+ *factory = gDWriteFactory;
+ return S_OK;
+}
+
+const void* DWriteOffscreen::draw(const SkGlyph& glyph, bool isBW) {
+ IDWriteFactory* factory;
+ HRNM(get_dwrite_factory(&factory), "Could not get factory.");
+
+ if (fWidth < glyph.fWidth || fHeight < glyph.fHeight) {
+ fWidth = SkMax32(fWidth, glyph.fWidth);
+ fHeight = SkMax32(fHeight, glyph.fHeight);
+
+ if (isBW) {
+ fBits.setCount(fWidth * fHeight);
+ } else {
+ fBits.setCount(fWidth * fHeight * 3);
+ }
+ }
+
+ // erase
+ memset(fBits.begin(), 0, fBits.count());
+
+ fXform.dx = SkFixedToFloat(glyph.getSubXFixed());
+ fXform.dy = SkFixedToFloat(glyph.getSubYFixed());
+
+ FLOAT advance = 0.0f;
+
+ UINT16 index = glyph.getGlyphID();
+
+ DWRITE_GLYPH_OFFSET offset;
+ offset.advanceOffset = 0.0f;
+ offset.ascenderOffset = 0.0f;
+
+ DWRITE_GLYPH_RUN run;
+ run.glyphCount = 1;
+ run.glyphAdvances = &advance;
+ run.fontFace = fFontFace;
+ run.fontEmSize = fFontSize;
+ run.bidiLevel = 0;
+ run.glyphIndices = &index;
+ run.isSideways = FALSE;
+ run.glyphOffsets = &offset;
+
+ DWRITE_RENDERING_MODE renderingMode;
+ DWRITE_TEXTURE_TYPE textureType;
+ if (isBW) {
+ renderingMode = DWRITE_RENDERING_MODE_ALIASED;
+ textureType = DWRITE_TEXTURE_ALIASED_1x1;
+ } else {
+ renderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
+ textureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
+ }
+ SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
+ HRNM(factory->CreateGlyphRunAnalysis(&run,
+ 1.0f, // pixelsPerDip,
+ &fXform,
+ renderingMode,
+ DWRITE_MEASURING_MODE_NATURAL,
+ 0.0f, // baselineOriginX,
+ 0.0f, // baselineOriginY,
+ &glyphRunAnalysis),
+ "Could not create glyph run analysis.");
+
+ //NOTE: this assumes that the glyph has already been measured
+ //with an exact same glyph run analysis.
+ RECT bbox;
+ bbox.left = glyph.fLeft;
+ bbox.top = glyph.fTop;
+ bbox.right = glyph.fLeft + glyph.fWidth;
+ bbox.bottom = glyph.fTop + glyph.fHeight;
+ HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType,
+ &bbox,
+ fBits.begin(),
+ fBits.count()),
+ "Could not draw mask.");
+ return fBits.begin();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamFontFileLoader : public IDWriteFontFileLoader {
+public:
+ // IUnknown methods
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ // IDWriteFontFileLoader methods
+ virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey(
+ void const* fontFileReferenceKey,
+ UINT32 fontFileReferenceKeySize,
+ IDWriteFontFileStream** fontFileStream);
+
+ static HRESULT Create(SkStream* stream, StreamFontFileLoader** streamFontFileLoader) {
+ *streamFontFileLoader = new StreamFontFileLoader(stream);
+ if (NULL == streamFontFileLoader) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+ }
+
+ SkAutoTUnref<SkStream> fStream;
+
+private:
+ StreamFontFileLoader(SkStream* stream) : fRefCount(1), fStream(SkRef(stream)) { }
+
+ ULONG fRefCount;
+};
+
+HRESULT StreamFontFileLoader::QueryInterface(REFIID iid, void** ppvObject) {
+ if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) {
+ *ppvObject = this;
+ AddRef();
+ return S_OK;
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG StreamFontFileLoader::AddRef() {
+ return InterlockedIncrement(&fRefCount);
+}
+
+ULONG StreamFontFileLoader::Release() {
+ ULONG newCount = InterlockedDecrement(&fRefCount);
+ if (0 == newCount) {
+ delete this;
+ }
+ return newCount;
+}
+
+HRESULT StreamFontFileLoader::CreateStreamFromKey(
+ void const* fontFileReferenceKey,
+ UINT32 fontFileReferenceKeySize,
+ IDWriteFontFileStream** fontFileStream)
+{
+ SkTScopedComPtr<SkDWriteFontFileStreamWrapper> stream;
+ HR(SkDWriteFontFileStreamWrapper::Create(fStream, &stream));
+ *fontFileStream = stream.release();
+ return S_OK;
+}
+
+class StreamFontFileEnumerator : public IDWriteFontFileEnumerator {
+public:
+ // IUnknown methods
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ // IDWriteFontFileEnumerator methods
+ virtual HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasCurrentFile);
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentFontFile(IDWriteFontFile** fontFile);
+
+ static HRESULT Create(IDWriteFactory* factory, IDWriteFontFileLoader* fontFileLoader,
+ StreamFontFileEnumerator** streamFontFileEnumerator) {
+ *streamFontFileEnumerator = new StreamFontFileEnumerator(factory, fontFileLoader);
+ if (NULL == streamFontFileEnumerator) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+ }
+private:
+ StreamFontFileEnumerator(IDWriteFactory* factory, IDWriteFontFileLoader* fontFileLoader);
+ ULONG fRefCount;
+
+ SkTScopedComPtr<IDWriteFactory> fFactory;
+ SkTScopedComPtr<IDWriteFontFile> fCurrentFile;
+ SkTScopedComPtr<IDWriteFontFileLoader> fFontFileLoader;
+ bool fHasNext;
+};
+
+StreamFontFileEnumerator::StreamFontFileEnumerator(IDWriteFactory* factory,
+ IDWriteFontFileLoader* fontFileLoader)
+ : fRefCount(1)
+ , fFactory(SkRefComPtr(factory))
+ , fCurrentFile()
+ , fFontFileLoader(SkRefComPtr(fontFileLoader))
+ , fHasNext(true)
+{ }
+
+HRESULT StreamFontFileEnumerator::QueryInterface(REFIID iid, void** ppvObject) {
+ if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileEnumerator)) {
+ *ppvObject = this;
+ AddRef();
+ return S_OK;
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG StreamFontFileEnumerator::AddRef() {
+ return InterlockedIncrement(&fRefCount);
+}
+
+ULONG StreamFontFileEnumerator::Release() {
+ ULONG newCount = InterlockedDecrement(&fRefCount);
+ if (0 == newCount) {
+ delete this;
+ }
+ return newCount;
+}
+
+HRESULT StreamFontFileEnumerator::MoveNext(BOOL* hasCurrentFile) {
+ *hasCurrentFile = FALSE;
+
+ if (!fHasNext) {
+ return S_OK;
+ }
+ fHasNext = false;
+
+ UINT32 dummy = 0;
+ HR(fFactory->CreateCustomFontFileReference(
+ &dummy, //cannot be NULL
+ sizeof(dummy), //even if this is 0
+ fFontFileLoader.get(),
+ &fCurrentFile));
+
+ *hasCurrentFile = TRUE;
+ return S_OK;
+}
+
+HRESULT StreamFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** fontFile) {
+ if (fCurrentFile.get() == NULL) {
+ *fontFile = NULL;
+ return E_FAIL;
+ }
+
+ *fontFile = SkRefComPtr(fCurrentFile.get());
+ return S_OK;
+}
+
+class StreamFontCollectionLoader : public IDWriteFontCollectionLoader {
+public:
+ // IUnknown methods
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ // IDWriteFontCollectionLoader methods
+ virtual HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey(
+ IDWriteFactory* factory,
+ void const* collectionKey,
+ UINT32 collectionKeySize,
+ IDWriteFontFileEnumerator** fontFileEnumerator);
+
+ static HRESULT Create(IDWriteFontFileLoader* fontFileLoader,
+ StreamFontCollectionLoader** streamFontCollectionLoader) {
+ *streamFontCollectionLoader = new StreamFontCollectionLoader(fontFileLoader);
+ if (NULL == streamFontCollectionLoader) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+ }
+private:
+ StreamFontCollectionLoader(IDWriteFontFileLoader* fontFileLoader)
+ : fRefCount(1)
+ , fFontFileLoader(SkRefComPtr(fontFileLoader))
+ { }
+
+ ULONG fRefCount;
+ SkTScopedComPtr<IDWriteFontFileLoader> fFontFileLoader;
+};
+
+HRESULT StreamFontCollectionLoader::QueryInterface(REFIID iid, void** ppvObject) {
+ if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontCollectionLoader)) {
+ *ppvObject = this;
+ AddRef();
+ return S_OK;
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG StreamFontCollectionLoader::AddRef() {
+ return InterlockedIncrement(&fRefCount);
+}
+
+ULONG StreamFontCollectionLoader::Release() {
+ ULONG newCount = InterlockedDecrement(&fRefCount);
+ if (0 == newCount) {
+ delete this;
+ }
+ return newCount;
+}
+
+HRESULT StreamFontCollectionLoader::CreateEnumeratorFromKey(
+ IDWriteFactory* factory,
+ void const* collectionKey,
+ UINT32 collectionKeySize,
+ IDWriteFontFileEnumerator** fontFileEnumerator)
+{
+ SkTScopedComPtr<StreamFontFileEnumerator> enumerator;
+ HR(StreamFontFileEnumerator::Create(factory, fFontFileLoader.get(), &enumerator));
+ *fontFileEnumerator = enumerator.release();
+ return S_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkTypeface::Style get_style(IDWriteFont* font) {
+ int style = SkTypeface::kNormal;
+ DWRITE_FONT_WEIGHT weight = font->GetWeight();
+ if (DWRITE_FONT_WEIGHT_DEMI_BOLD <= weight) {
+ style |= SkTypeface::kBold;
+ }
+ DWRITE_FONT_STYLE angle = font->GetStyle();
+ if (DWRITE_FONT_STYLE_OBLIQUE == angle || DWRITE_FONT_STYLE_ITALIC == angle) {
+ style |= SkTypeface::kItalic;
+ }
+ return static_cast<SkTypeface::Style>(style);
+}
+
+class DWriteFontTypeface : public SkTypeface {
+private:
+ DWriteFontTypeface(SkTypeface::Style style, SkFontID fontID,
+ IDWriteFontFace* fontFace,
+ IDWriteFont* font,
+ IDWriteFontFamily* fontFamily,
+ StreamFontFileLoader* fontFileLoader = NULL,
+ IDWriteFontCollectionLoader* fontCollectionLoader = NULL)
+ : SkTypeface(style, fontID, false)
+ , fDWriteFontCollectionLoader(SkSafeRefComPtr(fontCollectionLoader))
+ , fDWriteFontFileLoader(SkSafeRefComPtr(fontFileLoader))
+ , fDWriteFontFamily(SkRefComPtr(fontFamily))
+ , fDWriteFont(SkRefComPtr(font))
+ , fDWriteFontFace(SkRefComPtr(fontFace))
+ { }
+
+public:
+ SkTScopedComPtr<IDWriteFontCollectionLoader> fDWriteFontCollectionLoader;
+ SkTScopedComPtr<StreamFontFileLoader> fDWriteFontFileLoader;
+ SkTScopedComPtr<IDWriteFontFamily> fDWriteFontFamily;
+ SkTScopedComPtr<IDWriteFont> fDWriteFont;
+ SkTScopedComPtr<IDWriteFontFace> fDWriteFontFace;
+
+ static DWriteFontTypeface* Create(IDWriteFontFace* fontFace,
+ IDWriteFont* font,
+ IDWriteFontFamily* fontFamily,
+ StreamFontFileLoader* fontFileLoader = NULL,
+ IDWriteFontCollectionLoader* fontCollectionLoader = NULL) {
+ SkTypeface::Style style = get_style(font);
+ SkFontID fontID = SkTypefaceCache::NewFontID();
+ return SkNEW_ARGS(DWriteFontTypeface, (style, fontID,
+ fontFace, font, fontFamily,
+ fontFileLoader, fontCollectionLoader));
+ }
+
+ ~DWriteFontTypeface() {
+ if (fDWriteFontCollectionLoader.get() == NULL) return;
+
+ IDWriteFactory* factory;
+ HRVM(get_dwrite_factory(&factory), "Could not get factory.");
+ HRV(factory->UnregisterFontCollectionLoader(fDWriteFontCollectionLoader.get()));
+ HRV(factory->UnregisterFontFileLoader(fDWriteFontFileLoader.get()));
+ }
+
+protected:
+ virtual SkStream* onOpenStream(int* ttcIndex) const SK_OVERRIDE;
+ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor*) const SK_OVERRIDE;
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE;
+ virtual SkAdvancedTypefaceMetrics* onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo,
+ const uint32_t*, uint32_t) const SK_OVERRIDE;
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE;
+ virtual int onCountGlyphs() const SK_OVERRIDE;
+ virtual int onGetUPEM() const SK_OVERRIDE;
+ virtual SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const SK_OVERRIDE;
+ virtual int onGetTableTags(SkFontTableTag tags[]) const SK_OVERRIDE;
+ virtual size_t onGetTableData(SkFontTableTag, size_t offset,
+ size_t length, void* data) const SK_OVERRIDE;
+ virtual SkTypeface* onRefMatchingStyle(Style) const SK_OVERRIDE;
+};
+
+class SkScalerContext_DW : public SkScalerContext {
+public:
+ SkScalerContext_DW(DWriteFontTypeface*, const SkDescriptor* desc);
+ virtual ~SkScalerContext_DW();
+
+protected:
+ 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) SK_OVERRIDE;
+
+private:
+ DWriteOffscreen fOffscreen;
+ DWRITE_MATRIX fXform;
+ SkAutoTUnref<DWriteFontTypeface> fTypeface;
+ int fGlyphCount;
+};
+
+#define SK_DWRITE_DEFAULT_FONT_NAMED 1
+#define SK_DWRITE_DEFAULT_FONT_MESSAGE 2
+#define SK_DWRITE_DEFAULT_FONT_THEME 3
+#define SK_DWRITE_DEFAULT_FONT_SHELLDLG 4
+#define SK_DWRITE_DEFAULT_FONT_GDI 5
+#define SK_DWRITE_DEFAULT_FONT_STRATEGY SK_DWRITE_DEFAULT_FONT_MESSAGE
+
+static HRESULT get_default_font(IDWriteFont** font) {
+ IDWriteFactory* factory;
+ HRM(get_dwrite_factory(&factory), "Could not get factory.");
+
+#if SK_DWRITE_DEFAULT_FONT_STRATEGY == SK_DWRITE_DEFAULT_FONT_NAMED
+ SkTScopedComPtr<IDWriteFontCollection> sysFonts;
+ HRM(factory->GetSystemFontCollection(&sysFonts, false),
+ "Could not get system font collection.");
+
+ UINT32 index;
+ BOOL exists;
+ //hr = sysFonts->FindFamilyName(L"Georgia", &index, &exists);
+ HRM(sysFonts->FindFamilyName(L"Microsoft Sans Serif", &index, &exists),
+ "Could not access family names.");
+
+ if (!exists) {
+ SkDEBUGF(("The hard coded font family does not exist."));
+ return E_UNEXPECTED;
+ }
+
+ SkTScopedComPtr<IDWriteFontFamily> fontFamily;
+ HRM(sysFonts->GetFontFamily(index, &fontFamily),
+ "Could not load the requested font family.");
+
+ HRM(fontFamily->GetFont(0, font), "Could not get first font from family.");
+
+#elif SK_DWRITE_DEFAULT_FONT_STRATEGY == SK_DWRITE_DEFAULT_FONT_MESSAGE
+ SkTScopedComPtr<IDWriteGdiInterop> gdi;
+ HRM(factory->GetGdiInterop(&gdi), "Could not get GDI interop.");
+
+ NONCLIENTMETRICSW metrics;
+ metrics.cbSize = sizeof(metrics);
+ if (0 == SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
+ sizeof(metrics),
+ &metrics,
+ 0)) {
+ return E_UNEXPECTED;
+ }
+ HRM(gdi->CreateFontFromLOGFONT(&metrics.lfMessageFont, font),
+ "Could not create DWrite font from LOGFONT.");
+
+#elif SK_DWRITE_DEFAULT_FONT_STRATEGY == SK_DWRITE_DEFAULT_FONT_THEME
+ //Theme body font?
+
+#elif SK_DWRITE_DEFAULT_FONT_STRATEGY == SK_DWRITE_DEFAULT_FONT_SHELLDLG
+ //"MS Shell Dlg" or "MS Shell Dlg 2"?
+
+#elif SK_DWRITE_DEFAULT_FONT_STRATEGY == SK_DWRITE_DEFAULT_FONT_GDI
+ //Never works.
+ SkTScopedComPtr<IDWriteGdiInterop> gdi;
+ HRM(factory->GetGdiInterop(&gdi), "Could not get GDI interop.");
+
+ static LOGFONTW gDefaultFont = {};
+ gDefaultFont.lfFaceName
+ HRM(gdi->CreateFontFromLOGFONT(&gDefaultFont, font),
+ "Could not create DWrite font from LOGFONT.";
+#endif
+ return S_OK;
+}
+
+static bool are_same(IUnknown* a, IUnknown* b) {
+ SkTScopedComPtr<IUnknown> iunkA;
+ if (FAILED(a->QueryInterface(&iunkA))) {
+ return false;
+ }
+
+ SkTScopedComPtr<IUnknown> iunkB;
+ if (FAILED(b->QueryInterface(&iunkB))) {
+ return false;
+ }
+
+ return iunkA.get() == iunkB.get();
+}
+static bool FindByDWriteFont(SkTypeface* face, SkTypeface::Style requestedStyle, void* ctx) {
+ //Check to see if the two fonts are identical.
+ DWriteFontTypeface* dwFace = reinterpret_cast<DWriteFontTypeface*>(face);
+ IDWriteFont* dwFont = reinterpret_cast<IDWriteFont*>(ctx);
+ if (are_same(dwFace->fDWriteFont.get(), dwFont)) {
+ return true;
+ }
+
+ //Check if the two fonts share the same loader and have the same key.
+ SkTScopedComPtr<IDWriteFontFace> dwFaceFontFace;
+ SkTScopedComPtr<IDWriteFontFace> dwFontFace;
+ HRB(dwFace->fDWriteFont->CreateFontFace(&dwFaceFontFace));
+ HRB(dwFont->CreateFontFace(&dwFontFace));
+ if (are_same(dwFaceFontFace.get(), dwFontFace.get())) {
+ return true;
+ }
+
+ UINT32 dwFaceNumFiles;
+ UINT32 dwNumFiles;
+ HRB(dwFaceFontFace->GetFiles(&dwFaceNumFiles, NULL));
+ HRB(dwFontFace->GetFiles(&dwNumFiles, NULL));
+ if (dwFaceNumFiles != dwNumFiles) {
+ return false;
+ }
+
+ SkTScopedComPtr<IDWriteFontFile> dwFaceFontFile;
+ SkTScopedComPtr<IDWriteFontFile> dwFontFile;
+ HRB(dwFaceFontFace->GetFiles(&dwFaceNumFiles, &dwFaceFontFile));
+ HRB(dwFontFace->GetFiles(&dwNumFiles, &dwFontFile));
+
+ //for (each file) { //we currently only admit fonts from one file.
+ SkTScopedComPtr<IDWriteFontFileLoader> dwFaceFontFileLoader;
+ SkTScopedComPtr<IDWriteFontFileLoader> dwFontFileLoader;
+ HRB(dwFaceFontFile->GetLoader(&dwFaceFontFileLoader));
+ HRB(dwFontFile->GetLoader(&dwFontFileLoader));
+ if (!are_same(dwFaceFontFileLoader.get(), dwFontFileLoader.get())) {
+ return false;
+ }
+ //}
+
+ const void* dwFaceFontRefKey;
+ UINT32 dwFaceFontRefKeySize;
+ const void* dwFontRefKey;
+ UINT32 dwFontRefKeySize;
+ HRB(dwFaceFontFile->GetReferenceKey(&dwFaceFontRefKey, &dwFaceFontRefKeySize));
+ HRB(dwFontFile->GetReferenceKey(&dwFontRefKey, &dwFontRefKeySize));
+ if (dwFaceFontRefKeySize != dwFontRefKeySize) {
+ return false;
+ }
+ if (0 != memcmp(dwFaceFontRefKey, dwFontRefKey, dwFontRefKeySize)) {
+ return false;
+ }
+
+ //TODO: better means than comparing name strings?
+ //NOTE: .tfc and fake bold/italic will end up here.
+ SkTScopedComPtr<IDWriteFontFamily> dwFaceFontFamily;
+ SkTScopedComPtr<IDWriteFontFamily> dwFontFamily;
+ HRB(dwFace->fDWriteFont->GetFontFamily(&dwFaceFontFamily));
+ HRB(dwFont->GetFontFamily(&dwFontFamily));
+
+ SkTScopedComPtr<IDWriteLocalizedStrings> dwFaceFontFamilyNames;
+ SkTScopedComPtr<IDWriteLocalizedStrings> dwFaceFontNames;
+ HRB(dwFaceFontFamily->GetFamilyNames(&dwFaceFontFamilyNames));
+ HRB(dwFace->fDWriteFont->GetFaceNames(&dwFaceFontNames));
+
+ SkTScopedComPtr<IDWriteLocalizedStrings> dwFontFamilyNames;
+ SkTScopedComPtr<IDWriteLocalizedStrings> dwFontNames;
+ HRB(dwFontFamily->GetFamilyNames(&dwFontFamilyNames));
+ HRB(dwFont->GetFaceNames(&dwFontNames));
+
+ UINT32 dwFaceFontFamilyNameLength;
+ UINT32 dwFaceFontNameLength;
+ HRB(dwFaceFontFamilyNames->GetStringLength(0, &dwFaceFontFamilyNameLength));
+ HRB(dwFaceFontNames->GetStringLength(0, &dwFaceFontNameLength));
+
+ UINT32 dwFontFamilyNameLength;
+ UINT32 dwFontNameLength;
+ HRB(dwFontFamilyNames->GetStringLength(0, &dwFontFamilyNameLength));
+ HRB(dwFontNames->GetStringLength(0, &dwFontNameLength));
+
+ if (dwFaceFontFamilyNameLength != dwFontFamilyNameLength ||
+ dwFaceFontNameLength != dwFontNameLength)
+ {
+ return false;
+ }
+
+ SkSMallocWCHAR dwFaceFontFamilyNameChar(dwFaceFontFamilyNameLength+1);
+ SkSMallocWCHAR dwFaceFontNameChar(dwFaceFontNameLength+1);
+ HRB(dwFaceFontFamilyNames->GetString(0, dwFaceFontFamilyNameChar.get(), dwFaceFontFamilyNameLength+1));
+ HRB(dwFaceFontNames->GetString(0, dwFaceFontNameChar.get(), dwFaceFontNameLength+1));
+
+ SkSMallocWCHAR dwFontFamilyNameChar(dwFontFamilyNameLength+1);
+ SkSMallocWCHAR dwFontNameChar(dwFontNameLength+1);
+ HRB(dwFontFamilyNames->GetString(0, dwFontFamilyNameChar.get(), dwFontFamilyNameLength+1));
+ HRB(dwFontNames->GetString(0, dwFontNameChar.get(), dwFontNameLength+1));
+
+ return wcscmp(dwFaceFontFamilyNameChar.get(), dwFontFamilyNameChar.get()) == 0 &&
+ wcscmp(dwFaceFontNameChar.get(), dwFontNameChar.get()) == 0;
+}
+
+SkTypeface* SkCreateTypefaceFromDWriteFont(IDWriteFontFace* fontFace,
+ IDWriteFont* font,
+ IDWriteFontFamily* fontFamily,
+ StreamFontFileLoader* fontFileLoader = NULL,
+ IDWriteFontCollectionLoader* fontCollectionLoader = NULL) {
+ SkTypeface* face = SkTypefaceCache::FindByProcAndRef(FindByDWriteFont, font);
+ if (NULL == face) {
+ face = DWriteFontTypeface::Create(fontFace, font, fontFamily,
+ fontFileLoader, fontCollectionLoader);
+ SkTypefaceCache::Add(face, get_style(font), fontCollectionLoader != NULL);
+ }
+ return face;
+}
+
+void SkDWriteFontFromTypeface(const SkTypeface* face, IDWriteFont** font) {
+ if (NULL == face) {
+ HRVM(get_default_font(font), "Could not get default font.");
+ } else {
+ *font = SkRefComPtr(static_cast<const DWriteFontTypeface*>(face)->fDWriteFont.get());
+ }
+}
+static DWriteFontTypeface* GetDWriteFontByID(SkFontID fontID) {
+ return static_cast<DWriteFontTypeface*>(SkTypefaceCache::FindByID(fontID));
+}
+
+SkScalerContext_DW::SkScalerContext_DW(DWriteFontTypeface* typeface,
+ const SkDescriptor* desc)
+ : SkScalerContext(typeface, desc)
+ , fTypeface(SkRef(typeface))
+ , fGlyphCount(-1) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ fXform.m11 = SkScalarToFloat(fRec.fPost2x2[0][0]);
+ fXform.m12 = SkScalarToFloat(fRec.fPost2x2[1][0]);
+ fXform.m21 = SkScalarToFloat(fRec.fPost2x2[0][1]);
+ fXform.m22 = SkScalarToFloat(fRec.fPost2x2[1][1]);
+ fXform.dx = 0;
+ fXform.dy = 0;
+
+ fOffscreen.init(fTypeface->fDWriteFontFace.get(), fXform, SkScalarToFloat(fRec.fTextSize));
+}
+
+SkScalerContext_DW::~SkScalerContext_DW() {
+}
+
+unsigned SkScalerContext_DW::generateGlyphCount() {
+ if (fGlyphCount < 0) {
+ fGlyphCount = fTypeface->fDWriteFontFace->GetGlyphCount();
+ }
+ return fGlyphCount;
+}
+
+uint16_t SkScalerContext_DW::generateCharToGlyph(SkUnichar uni) {
+ uint16_t index = 0;
+ fTypeface->fDWriteFontFace->GetGlyphIndices(reinterpret_cast<UINT32*>(&uni), 1, &index);
+ return index;
+}
+
+void SkScalerContext_DW::generateAdvance(SkGlyph* glyph) {
+ //Delta is the difference between the right/left side bearing metric
+ //and where the right/left side bearing ends up after hinting.
+ //DirectWrite does not provide this information.
+ glyph->fRsbDelta = 0;
+ glyph->fLsbDelta = 0;
+
+ glyph->fAdvanceX = 0;
+ glyph->fAdvanceY = 0;
+
+ uint16_t glyphId = glyph->getGlyphID();
+ DWRITE_GLYPH_METRICS gm;
+ HRVM(fTypeface->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm),
+ "Could not get design metrics.");
+
+ DWRITE_FONT_METRICS dwfm;
+ fTypeface->fDWriteFontFace->GetMetrics(&dwfm);
+
+ SkScalar advanceX = SkScalarMulDiv(fRec.fTextSize,
+ SkIntToScalar(gm.advanceWidth),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+
+ if (!(fRec.fFlags & kSubpixelPositioning_Flag)) {
+ advanceX = SkScalarRoundToScalar(advanceX);
+ }
+
+ SkVector vecs[1] = { { advanceX, 0 } };
+ SkMatrix mat;
+ fRec.getMatrixFrom2x2(&mat);
+ mat.mapVectors(vecs, SK_ARRAY_COUNT(vecs));
+
+ glyph->fAdvanceX = SkScalarToFixed(vecs[0].fX);
+ glyph->fAdvanceY = SkScalarToFixed(vecs[0].fY);
+}
+
+void SkScalerContext_DW::generateMetrics(SkGlyph* glyph) {
+ glyph->fWidth = 0;
+
+ this->generateAdvance(glyph);
+
+ //Measure raster size.
+ fXform.dx = SkFixedToFloat(glyph->getSubXFixed());
+ fXform.dy = SkFixedToFloat(glyph->getSubYFixed());
+
+ FLOAT advance = 0;
+
+ UINT16 glyphId = glyph->getGlyphID();
+
+ DWRITE_GLYPH_OFFSET offset;
+ offset.advanceOffset = 0.0f;
+ offset.ascenderOffset = 0.0f;
+
+ DWRITE_GLYPH_RUN run;
+ run.glyphCount = 1;
+ run.glyphAdvances = &advance;
+ run.fontFace = fTypeface->fDWriteFontFace.get();
+ run.fontEmSize = SkScalarToFloat(fRec.fTextSize);
+ run.bidiLevel = 0;
+ run.glyphIndices = &glyphId;
+ run.isSideways = FALSE;
+ run.glyphOffsets = &offset;
+
+ IDWriteFactory* factory;
+ HRVM(get_dwrite_factory(&factory), "Could not get factory.");
+
+ const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat;
+ DWRITE_RENDERING_MODE renderingMode;
+ DWRITE_TEXTURE_TYPE textureType;
+ if (isBW) {
+ renderingMode = DWRITE_RENDERING_MODE_ALIASED;
+ textureType = DWRITE_TEXTURE_ALIASED_1x1;
+ } else {
+ renderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
+ textureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
+ }
+
+ SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
+ HRVM(factory->CreateGlyphRunAnalysis(&run,
+ 1.0f, // pixelsPerDip,
+ &fXform,
+ renderingMode,
+ DWRITE_MEASURING_MODE_NATURAL,
+ 0.0f, // baselineOriginX,
+ 0.0f, // baselineOriginY,
+ &glyphRunAnalysis),
+ "Could not create glyph run analysis.");
+
+ RECT bbox;
+ HRVM(glyphRunAnalysis->GetAlphaTextureBounds(textureType, &bbox),
+ "Could not get texture bounds.");
+
+ glyph->fWidth = SkToU16(bbox.right - bbox.left);
+ glyph->fHeight = SkToU16(bbox.bottom - bbox.top);
+ glyph->fLeft = SkToS16(bbox.left);
+ glyph->fTop = SkToS16(bbox.top);
+}
+
+void SkScalerContext_DW::generateFontMetrics(SkPaint::FontMetrics* mx,
+ SkPaint::FontMetrics* my) {
+ if (!(mx || my))
+ return;
+
+ DWRITE_FONT_METRICS dwfm;
+ fTypeface->fDWriteFontFace->GetMetrics(&dwfm);
+
+ if (mx) {
+ mx->fTop = SkScalarMulDiv(-fRec.fTextSize,
+ SkIntToScalar(dwfm.ascent),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ mx->fAscent = mx->fTop;
+ mx->fDescent = SkScalarMulDiv(fRec.fTextSize,
+ SkIntToScalar(dwfm.descent),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ mx->fBottom = mx->fDescent;
+ //TODO, can be less than zero
+ mx->fLeading = SkScalarMulDiv(fRec.fTextSize,
+ SkIntToScalar(dwfm.lineGap),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ }
+
+ if (my) {
+ my->fTop = SkScalarMulDiv(-fRec.fTextSize,
+ SkIntToScalar(dwfm.ascent),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ my->fAscent = my->fTop;
+ my->fDescent = SkScalarMulDiv(fRec.fTextSize,
+ SkIntToScalar(dwfm.descent),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ my->fBottom = my->fDescent;
+ //TODO, can be less than zero
+ my->fLeading = SkScalarMulDiv(fRec.fTextSize,
+ SkIntToScalar(dwfm.lineGap),
+ SkIntToScalar(dwfm.designUnitsPerEm));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorPriv.h"
+
+static void bilevel_to_bw(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph) {
+ const int width = glyph.fWidth;
+ const size_t dstRB = (width + 7) >> 3;
+ uint8_t* SK_RESTRICT dst = static_cast<uint8_t*>(glyph.fImage);
+
+ int byteCount = width >> 3;
+ int bitCount = width & 7;
+
+ for (int y = 0; y < glyph.fHeight; ++y) {
+ if (byteCount > 0) {
+ for (int i = 0; i < byteCount; ++i) {
+ unsigned byte = 0;
+ byte |= src[0] & (1 << 7);
+ byte |= src[1] & (1 << 6);
+ byte |= src[2] & (1 << 5);
+ byte |= src[3] & (1 << 4);
+ byte |= src[4] & (1 << 3);
+ byte |= src[5] & (1 << 2);
+ byte |= src[6] & (1 << 1);
+ byte |= src[7] & (1 << 0);
+ dst[i] = byte;
+ src += 8;
+ }
+ }
+ if (bitCount > 0) {
+ unsigned byte = 0;
+ unsigned mask = 0x80;
+ for (int i = 0; i < bitCount; i++) {
+ byte |= (src[i]) & mask;
+ mask >>= 1;
+ }
+ dst[byteCount] = byte;
+ }
+ src += bitCount;
+ dst += dstRB;
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_a8(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, const uint8_t* table8) {
+ const size_t dstRB = glyph.rowBytes();
+ const U16CPU width = glyph.fWidth;
+ uint8_t* SK_RESTRICT dst = static_cast<uint8_t*>(glyph.fImage);
+
+ for (U16CPU y = 0; y < glyph.fHeight; y++) {
+ for (U16CPU i = 0; i < width; i++) {
+ U8CPU r = *(src++);
+ U8CPU g = *(src++);
+ U8CPU b = *(src++);
+ dst[i] = sk_apply_lut_if<APPLY_PREBLEND>((r + g + b) / 3, table8);
+ }
+ dst = (uint8_t*)((char*)dst + dstRB);
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd16(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const size_t dstRB = glyph.rowBytes();
+ const U16CPU width = glyph.fWidth;
+ uint16_t* SK_RESTRICT dst = static_cast<uint16_t*>(glyph.fImage);
+
+ for (U16CPU y = 0; y < glyph.fHeight; y++) {
+ for (U16CPU i = 0; i < width; i++) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableB);
+ dst[i] = SkPack888ToRGB16(r, g, b);
+ }
+ dst = (uint16_t*)((char*)dst + dstRB);
+ }
+}
+
+template<bool APPLY_PREBLEND>
+static void rgb_to_lcd32(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph,
+ const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
+ const size_t dstRB = glyph.rowBytes();
+ const U16CPU width = glyph.fWidth;
+ SkPMColor* SK_RESTRICT dst = static_cast<SkPMColor*>(glyph.fImage);
+
+ for (U16CPU y = 0; y < glyph.fHeight; y++) {
+ for (U16CPU i = 0; i < width; i++) {
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableB);
+ dst[i] = SkPackARGB32(0xFF, r, g, b);
+ }
+ dst = (SkPMColor*)((char*)dst + dstRB);
+ }
+}
+
+void SkScalerContext_DW::generateImage(const SkGlyph& glyph) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat;
+ const bool isAA = !isLCD(fRec);
+
+ //Create the mask.
+ const void* bits = fOffscreen.draw(glyph, isBW);
+ if (!bits) {
+ sk_bzero(glyph.fImage, glyph.computeImageSize());
+ return;
+ }
+
+ //Copy the mask into the glyph.
+ const uint8_t* src = (const uint8_t*)bits;
+ if (isBW) {
+ bilevel_to_bw(src, glyph);
+ } else if (isAA) {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_a8<true>(src, glyph, fPreBlend.fG);
+ } else {
+ rgb_to_a8<false>(src, glyph, fPreBlend.fG);
+ }
+ } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd16<true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd16<false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ } else {
+ SkASSERT(SkMask::kLCD32_Format == glyph.fMaskFormat);
+ if (fPreBlend.isApplicable()) {
+ rgb_to_lcd32<true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ } else {
+ rgb_to_lcd32<false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
+ }
+ }
+}
+
+void SkScalerContext_DW::generatePath(const SkGlyph& glyph, SkPath* path) {
+ SkAutoMutexAcquire ac(gFTMutex);
+
+ SkASSERT(&glyph && path);
+
+ path->reset();
+
+ SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
+ HRVM(SkDWriteGeometrySink::Create(path, &geometryToPath),
+ "Could not create geometry to path converter.");
+ uint16_t glyphId = glyph.getGlyphID();
+ //TODO: convert to<->from DIUs? This would make a difference if hinting.
+ //It may not be needed, it appears that DirectWrite only hints at em size.
+ HRVM(fTypeface->fDWriteFontFace->GetGlyphRunOutline(SkScalarToFloat(fRec.fTextSize),
+ &glyphId,
+ NULL, //advances
+ NULL, //offsets
+ 1, //num glyphs
+ FALSE, //sideways
+ FALSE, //rtl
+ geometryToPath.get()),
+ "Could not create glyph outline.");
+
+ SkMatrix mat;
+ fRec.getMatrixFrom2x2(&mat);
+ path->transform(mat);
+}
+
+void DWriteFontTypeface::onGetFontDescriptor(SkFontDescriptor* desc,
+ bool* isLocalStream) const {
+ // Get the family name.
+ SkTScopedComPtr<IDWriteLocalizedStrings> dwFamilyNames;
+ HRV(fDWriteFontFamily->GetFamilyNames(&dwFamilyNames));
+
+ UINT32 dwFamilyNamesLength;
+ HRV(dwFamilyNames->GetStringLength(0, &dwFamilyNamesLength));
+
+ SkSMallocWCHAR dwFamilyNameChar(dwFamilyNamesLength+1);
+ HRV(dwFamilyNames->GetString(0, dwFamilyNameChar.get(), dwFamilyNamesLength+1));
+
+ SkString utf8FamilyName;
+ HRV(wchar_to_skstring(dwFamilyNameChar.get(), &utf8FamilyName));
+
+ desc->setFamilyName(utf8FamilyName.c_str());
+ *isLocalStream = SkToBool(fDWriteFontFileLoader.get());
+}
+
+int DWriteFontTypeface::onCountGlyphs() const {
+ return fDWriteFontFace->GetGlyphCount();
+}
+
+int DWriteFontTypeface::onGetUPEM() const {
+ DWRITE_FONT_METRICS metrics;
+ fDWriteFontFace->GetMetrics(&metrics);
+ return metrics.designUnitsPerEm;
+}
+
+class LocalizedStrings_IDWriteLocalizedStrings : public SkTypeface::LocalizedStrings {
+public:
+ /** Takes ownership of the IDWriteLocalizedStrings. */
+ explicit LocalizedStrings_IDWriteLocalizedStrings(IDWriteLocalizedStrings* strings)
+ : fIndex(0), fStrings(strings)
+ { }
+
+ virtual bool next(SkTypeface::LocalizedString* localizedString) SK_OVERRIDE {
+ if (fIndex >= fStrings->GetCount()) {
+ return false;
+ }
+
+ // String
+ UINT32 stringLength;
+ HRBM(fStrings->GetStringLength(fIndex, &stringLength), "Could not get string length.");
+ stringLength += 1;
+
+ SkSMallocWCHAR wString(stringLength);
+ HRBM(fStrings->GetString(fIndex, wString.get(), stringLength), "Could not get string.");
+
+ HRB(wchar_to_skstring(wString.get(), &localizedString->fString));
+
+ // Locale
+ UINT32 localeLength;
+ HRBM(fStrings->GetLocaleNameLength(fIndex, &localeLength), "Could not get locale length.");
+ localeLength += 1;
+
+ SkSMallocWCHAR wLocale(localeLength);
+ HRBM(fStrings->GetLocaleName(fIndex, wLocale.get(), localeLength), "Could not get locale.");
+
+ HRB(wchar_to_skstring(wLocale.get(), &localizedString->fLanguage));
+
+ ++fIndex;
+ return true;
+ }
+
+private:
+ UINT32 fIndex;
+ SkTScopedComPtr<IDWriteLocalizedStrings> fStrings;
+};
+
+SkTypeface::LocalizedStrings* DWriteFontTypeface::onCreateFamilyNameIterator() const {
+ SkTScopedComPtr<IDWriteLocalizedStrings> familyNames;
+ HRNM(fDWriteFontFamily->GetFamilyNames(&familyNames), "Could not obtain family names.");
+
+ return new LocalizedStrings_IDWriteLocalizedStrings(familyNames.release());
+}
+
+int DWriteFontTypeface::onGetTableTags(SkFontTableTag tags[]) const {
+ DWRITE_FONT_FACE_TYPE type = fDWriteFontFace->GetType();
+ if (type != DWRITE_FONT_FACE_TYPE_CFF &&
+ type != DWRITE_FONT_FACE_TYPE_TRUETYPE &&
+ type != DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION)
+ {
+ return 0;
+ }
+
+ int ttcIndex;
+ SkAutoTUnref<SkStream> stream(this->openStream(&ttcIndex));
+ return stream.get() ? SkFontStream::GetTableTags(stream, ttcIndex, tags) : 0;
+}
+
+class AutoDWriteTable {
+public:
+ AutoDWriteTable(IDWriteFontFace* fontFace, UINT32 beTag) : fFontFace(fontFace), fExists(FALSE) {
+ // Any errors are ignored, user must check fExists anyway.
+ fontFace->TryGetFontTable(beTag,
+ reinterpret_cast<const void **>(&fData), &fSize, &fLock, &fExists);
+ }
+ ~AutoDWriteTable() {
+ if (fExists) {
+ fFontFace->ReleaseFontTable(fLock);
+ }
+ }
+
+ const uint8_t* fData;
+ UINT32 fSize;
+ BOOL fExists;
+private:
+ // Borrowed reference, the user must ensure the fontFace stays alive.
+ IDWriteFontFace* fFontFace;
+ void* fLock;
+};
+
+size_t DWriteFontTypeface::onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* data) const
+{
+ AutoDWriteTable table(fDWriteFontFace.get(), SkEndian_SwapBE32(tag));
+ if (!table.fExists) {
+ return 0;
+ }
+
+ if (offset > table.fSize) {
+ return 0;
+ }
+ size_t size = SkTMin(length, table.fSize - offset);
+ if (NULL != data) {
+ memcpy(data, table.fData + offset, size);
+ }
+
+ return size;
+}
+
+template <typename T> class SkAutoIDWriteUnregister {
+public:
+ SkAutoIDWriteUnregister(IDWriteFactory* factory, T* unregister)
+ : fFactory(factory), fUnregister(unregister)
+ { }
+
+ ~SkAutoIDWriteUnregister() {
+ if (fUnregister) {
+ unregister(fFactory, fUnregister);
+ }
+ }
+
+ T* detatch() {
+ T* old = fUnregister;
+ fUnregister = NULL;
+ return old;
+ }
+
+private:
+ HRESULT unregister(IDWriteFactory* factory, IDWriteFontFileLoader* unregister) {
+ return factory->UnregisterFontFileLoader(unregister);
+ }
+
+ HRESULT unregister(IDWriteFactory* factory, IDWriteFontCollectionLoader* unregister) {
+ return factory->UnregisterFontCollectionLoader(unregister);
+ }
+
+ IDWriteFactory* fFactory;
+ T* fUnregister;
+};
+
+static SkTypeface* create_from_stream(SkStream* stream, int ttcIndex) {
+ IDWriteFactory* factory;
+ HRN(get_dwrite_factory(&factory));
+
+ SkTScopedComPtr<StreamFontFileLoader> fontFileLoader;
+ HRN(StreamFontFileLoader::Create(stream, &fontFileLoader));
+ HRN(factory->RegisterFontFileLoader(fontFileLoader.get()));
+ SkAutoIDWriteUnregister<StreamFontFileLoader> autoUnregisterFontFileLoader(
+ factory, fontFileLoader.get());
+
+ SkTScopedComPtr<StreamFontCollectionLoader> fontCollectionLoader;
+ HRN(StreamFontCollectionLoader::Create(fontFileLoader.get(), &fontCollectionLoader));
+ HRN(factory->RegisterFontCollectionLoader(fontCollectionLoader.get()));
+ SkAutoIDWriteUnregister<StreamFontCollectionLoader> autoUnregisterFontCollectionLoader(
+ factory, fontCollectionLoader.get());
+
+ SkTScopedComPtr<IDWriteFontCollection> fontCollection;
+ HRN(factory->CreateCustomFontCollection(fontCollectionLoader.get(), NULL, 0, &fontCollection));
+
+ // Find the first non-simulated font which has the given ttc index.
+ UINT32 familyCount = fontCollection->GetFontFamilyCount();
+ for (UINT32 familyIndex = 0; familyIndex < familyCount; ++familyIndex) {
+ SkTScopedComPtr<IDWriteFontFamily> fontFamily;
+ HRN(fontCollection->GetFontFamily(familyIndex, &fontFamily));
+
+ UINT32 fontCount = fontFamily->GetFontCount();
+ for (UINT32 fontIndex = 0; fontIndex < fontCount; ++fontIndex) {
+ SkTScopedComPtr<IDWriteFont> font;
+ HRN(fontFamily->GetFont(fontIndex, &font));
+ if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE) {
+ continue;
+ }
+
+ SkTScopedComPtr<IDWriteFontFace> fontFace;
+ HRN(font->CreateFontFace(&fontFace));
+
+ UINT32 faceIndex = fontFace->GetIndex();
+ if (faceIndex == ttcIndex) {
+ return SkCreateTypefaceFromDWriteFont(fontFace.get(), font.get(), fontFamily.get(),
+ autoUnregisterFontFileLoader.detatch(),
+ autoUnregisterFontCollectionLoader.detatch());
+ }
+ }
+ }
+
+ return NULL;
+}
+
+SkStream* DWriteFontTypeface::onOpenStream(int* ttcIndex) const {
+ *ttcIndex = fDWriteFontFace->GetIndex();
+
+ UINT32 numFiles;
+ HRNM(fDWriteFontFace->GetFiles(&numFiles, NULL),
+ "Could not get number of font files.");
+ if (numFiles != 1) {
+ return NULL;
+ }
+
+ SkTScopedComPtr<IDWriteFontFile> fontFile;
+ HRNM(fDWriteFontFace->GetFiles(&numFiles, &fontFile), "Could not get font files.");
+
+ const void* fontFileKey;
+ UINT32 fontFileKeySize;
+ HRNM(fontFile->GetReferenceKey(&fontFileKey, &fontFileKeySize),
+ "Could not get font file reference key.");
+
+ SkTScopedComPtr<IDWriteFontFileLoader> fontFileLoader;
+ HRNM(fontFile->GetLoader(&fontFileLoader), "Could not get font file loader.");
+
+ SkTScopedComPtr<IDWriteFontFileStream> fontFileStream;
+ HRNM(fontFileLoader->CreateStreamFromKey(fontFileKey, fontFileKeySize,
+ &fontFileStream),
+ "Could not create font file stream.");
+
+ return SkNEW_ARGS(SkDWriteFontFileStream, (fontFileStream.get()));
+}
+
+SkScalerContext* DWriteFontTypeface::onCreateScalerContext(const SkDescriptor* desc) const {
+ return SkNEW_ARGS(SkScalerContext_DW, (const_cast<DWriteFontTypeface*>(this), desc));
+}
+
+static HRESULT get_by_family_name(const char familyName[], IDWriteFontFamily** fontFamily) {
+ IDWriteFactory* factory;
+ HR(get_dwrite_factory(&factory));
+
+ SkTScopedComPtr<IDWriteFontCollection> sysFontCollection;
+ HR(factory->GetSystemFontCollection(&sysFontCollection, FALSE));
+
+ SkSMallocWCHAR wideFamilyName;
+ HR(cstring_to_wchar(familyName, &wideFamilyName));
+
+ UINT32 index;
+ BOOL exists;
+ HR(sysFontCollection->FindFamilyName(wideFamilyName.get(), &index, &exists));
+
+ if (exists) {
+ HR(sysFontCollection->GetFontFamily(index, fontFamily));
+ return S_OK;
+ }
+ return S_FALSE;
+}
+
+void DWriteFontTypeface::onFilterRec(SkScalerContext::Rec* rec) const {
+ if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag ||
+ rec->fFlags & SkScalerContext::kLCD_Vertical_Flag)
+ {
+ rec->fMaskFormat = SkMask::kA8_Format;
+ }
+
+ unsigned flagsWeDontSupport = SkScalerContext::kDevKernText_Flag |
+ SkScalerContext::kAutohinting_Flag |
+ SkScalerContext::kEmbeddedBitmapText_Flag |
+ SkScalerContext::kEmbolden_Flag |
+ SkScalerContext::kLCD_BGROrder_Flag |
+ SkScalerContext::kLCD_Vertical_Flag;
+ rec->fFlags &= ~flagsWeDontSupport;
+
+ SkPaint::Hinting h = rec->getHinting();
+ // DirectWrite does not provide for hinting hints.
+ h = SkPaint::kSlight_Hinting;
+ rec->setHinting(h);
+
+#if SK_FONT_HOST_USE_SYSTEM_SETTINGS
+ IDWriteFactory* factory;
+ if (SUCCEEDED(get_dwrite_factory(&factory))) {
+ SkTScopedComPtr<IDWriteRenderingParams> defaultRenderingParams;
+ if (SUCCEEDED(factory->CreateRenderingParams(&defaultRenderingParams))) {
+ float gamma = defaultRenderingParams->GetGamma();
+ rec->setDeviceGamma(SkFloatToScalar(gamma));
+ rec->setPaintGamma(SkFloatToScalar(gamma));
+
+ rec->setContrast(SkFloatToScalar(defaultRenderingParams->GetEnhancedContrast()));
+ }
+ }
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//PDF Support
+
+using namespace skia_advanced_typeface_metrics_utils;
+
+// Construct Glyph to Unicode table.
+// Unicode code points that require conjugate pairs in utf16 are not
+// supported.
+// TODO(arthurhsu): Add support for conjugate pairs. It looks like that may
+// require parsing the TTF cmap table (platform 4, encoding 12) directly instead
+// of calling GetFontUnicodeRange().
+// TODO(bungeman): This never does what anyone wants.
+// What is really wanted is the text to glyphs mapping
+static void populate_glyph_to_unicode(IDWriteFontFace* fontFace,
+ const unsigned glyphCount,
+ SkTDArray<SkUnichar>* glyphToUnicode) {
+ HRESULT hr = S_OK;
+
+ //Do this like free type instead
+ UINT32 count = 0;
+ for (UINT32 c = 0; c < 0x10FFFF; ++c) {
+ UINT16 glyph;
+ hr = fontFace->GetGlyphIndices(&c, 1, &glyph);
+ if (glyph > 0) {
+ ++count;
+ }
+ }
+
+ SkAutoTArray<UINT32> chars(count);
+ count = 0;
+ for (UINT32 c = 0; c < 0x10FFFF; ++c) {
+ UINT16 glyph;
+ hr = fontFace->GetGlyphIndices(&c, 1, &glyph);
+ if (glyph > 0) {
+ chars[count] = c;
+ ++count;
+ }
+ }
+
+ SkAutoTArray<UINT16> glyph(count);
+ fontFace->GetGlyphIndices(chars.get(), count, glyph.get());
+
+ USHORT maxGlyph = 0;
+ for (USHORT j = 0; j < count; ++j) {
+ if (glyph[j] > maxGlyph) maxGlyph = glyph[j];
+ }
+
+ glyphToUnicode->setCount(maxGlyph+1);
+ for (size_t j = 0; j < maxGlyph+1u; ++j) {
+ (*glyphToUnicode)[j] = 0;
+ }
+
+ //'invert'
+ for (USHORT j = 0; j < count; ++j) {
+ if (glyph[j] < glyphCount && (*glyphToUnicode)[glyph[j]] == 0) {
+ (*glyphToUnicode)[glyph[j]] = chars[j];
+ }
+ }
+}
+
+static bool getWidthAdvance(IDWriteFontFace* fontFace, int gId, int16_t* advance) {
+ SkASSERT(advance);
+
+ UINT16 glyphId = gId;
+ DWRITE_GLYPH_METRICS gm;
+ HRESULT hr = fontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm);
+
+ if (FAILED(hr)) {
+ *advance = 0;
+ return false;
+ }
+
+ *advance = gm.advanceWidth;
+ return true;
+}
+
+template<typename T> class AutoTDWriteTable : public AutoDWriteTable {
+public:
+ static const UINT32 tag = DWRITE_MAKE_OPENTYPE_TAG(T::TAG0, T::TAG1, T::TAG2, T::TAG3);
+ AutoTDWriteTable(IDWriteFontFace* fontFace) : AutoDWriteTable(fontFace, tag) { }
+
+ const T* operator->() const { return reinterpret_cast<const T*>(fData); }
+};
+
+SkAdvancedTypefaceMetrics* DWriteFontTypeface::onGetAdvancedTypefaceMetrics(
+ SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo,
+ const uint32_t* glyphIDs,
+ uint32_t glyphIDsCount) const {
+
+ SkAdvancedTypefaceMetrics* info = NULL;
+
+ HRESULT hr = S_OK;
+
+ const unsigned glyphCount = fDWriteFontFace->GetGlyphCount();
+
+ DWRITE_FONT_METRICS dwfm;
+ fDWriteFontFace->GetMetrics(&dwfm);
+
+ info = new SkAdvancedTypefaceMetrics;
+ info->fEmSize = dwfm.designUnitsPerEm;
+ info->fMultiMaster = false;
+ info->fLastGlyphID = SkToU16(glyphCount - 1);
+ info->fStyle = 0;
+
+
+ SkTScopedComPtr<IDWriteLocalizedStrings> familyNames;
+ SkTScopedComPtr<IDWriteLocalizedStrings> faceNames;
+ hr = fDWriteFontFamily->GetFamilyNames(&familyNames);
+ hr = fDWriteFont->GetFaceNames(&faceNames);
+
+ UINT32 familyNameLength;
+ hr = familyNames->GetStringLength(0, &familyNameLength);
+
+ UINT32 faceNameLength;
+ hr = faceNames->GetStringLength(0, &faceNameLength);
+
+ UINT32 size = familyNameLength+1+faceNameLength+1;
+ SkSMallocWCHAR wFamilyName(size);
+ hr = familyNames->GetString(0, wFamilyName.get(), size);
+ wFamilyName[familyNameLength] = L' ';
+ hr = faceNames->GetString(0, &wFamilyName[familyNameLength+1], size - faceNameLength + 1);
+
+ hr = wchar_to_skstring(wFamilyName.get(), &info->fFontName);
+
+ if (perGlyphInfo & SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo) {
+ populate_glyph_to_unicode(fDWriteFontFace.get(), glyphCount, &(info->fGlyphToUnicode));
+ }
+
+ DWRITE_FONT_FACE_TYPE fontType = fDWriteFontFace->GetType();
+ if (fontType == DWRITE_FONT_FACE_TYPE_TRUETYPE ||
+ fontType == DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION) {
+ info->fType = SkAdvancedTypefaceMetrics::kTrueType_Font;
+ } else {
+ info->fType = SkAdvancedTypefaceMetrics::kOther_Font;
+ info->fItalicAngle = 0;
+ info->fAscent = dwfm.ascent;;
+ info->fDescent = dwfm.descent;
+ info->fStemV = 0;
+ info->fCapHeight = dwfm.capHeight;
+ info->fBBox = SkIRect::MakeEmpty();
+ return info;
+ }
+
+ AutoTDWriteTable<SkOTTableHead> headTable(fDWriteFontFace.get());
+ AutoTDWriteTable<SkOTTablePostScript> postTable(fDWriteFontFace.get());
+ AutoTDWriteTable<SkOTTableHorizontalHeader> hheaTable(fDWriteFontFace.get());
+ AutoTDWriteTable<SkOTTableOS2> os2Table(fDWriteFontFace.get());
+ if (!headTable.fExists || !postTable.fExists || !hheaTable.fExists || !os2Table.fExists) {
+ info->fItalicAngle = 0;
+ info->fAscent = dwfm.ascent;;
+ info->fDescent = dwfm.descent;
+ info->fStemV = 0;
+ info->fCapHeight = dwfm.capHeight;
+ info->fBBox = SkIRect::MakeEmpty();
+ return info;
+ }
+
+ //There exist CJK fonts which set the IsFixedPitch and Monospace bits,
+ //but have full width, latin half-width, and half-width kana.
+ bool fixedWidth = (postTable->isFixedPitch &&
+ (1 == SkEndian_SwapBE16(hheaTable->numberOfHMetrics)));
+ //Monospace
+ if (fixedWidth) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style;
+ }
+ //Italic
+ if (os2Table->version.v0.fsSelection.field.Italic) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
+ }
+ //Script
+ if (SkPanose::FamilyType::Script == os2Table->version.v0.panose.bFamilyType.value) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
+ //Serif
+ } else if (SkPanose::FamilyType::TextAndDisplay == os2Table->version.v0.panose.bFamilyType.value &&
+ SkPanose::Data::TextAndDisplay::SerifStyle::Triangle <= os2Table->version.v0.panose.data.textAndDisplay.bSerifStyle.value &&
+ SkPanose::Data::TextAndDisplay::SerifStyle::NoFit != os2Table->version.v0.panose.data.textAndDisplay.bSerifStyle.value) {
+ info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
+ }
+
+ info->fItalicAngle = SkEndian_SwapBE32(postTable->italicAngle) >> 16;
+
+ info->fAscent = SkToS16(dwfm.ascent);
+ info->fDescent = SkToS16(dwfm.descent);
+ info->fCapHeight = SkToS16(dwfm.capHeight);
+
+ info->fBBox = SkIRect::MakeLTRB((int32_t)SkEndian_SwapBE16((uint16_t)headTable->xMin),
+ (int32_t)SkEndian_SwapBE16((uint16_t)headTable->yMax),
+ (int32_t)SkEndian_SwapBE16((uint16_t)headTable->xMax),
+ (int32_t)SkEndian_SwapBE16((uint16_t)headTable->yMin));
+
+ //TODO: is this even desired? It seems PDF only wants this value for Type1
+ //fonts, and we only get here for TrueType fonts.
+ info->fStemV = 0;
+ /*
+ // Figure out a good guess for StemV - Min width of i, I, !, 1.
+ // This probably isn't very good with an italic font.
+ int16_t min_width = SHRT_MAX;
+ info->fStemV = 0;
+ char stem_chars[] = {'i', 'I', '!', '1'};
+ for (size_t i = 0; i < SK_ARRAY_COUNT(stem_chars); i++) {
+ ABC abcWidths;
+ if (GetCharABCWidths(hdc, stem_chars[i], stem_chars[i], &abcWidths)) {
+ int16_t width = abcWidths.abcB;
+ if (width > 0 && width < min_width) {
+ min_width = width;
+ info->fStemV = min_width;
+ }
+ }
+ }
+ */
+
+ // If Restricted, the font may not be embedded in a document.
+ // If not Restricted, the font can be embedded.
+ // If PreviewPrint, the embedding is read-only.
+ if (os2Table->version.v0.fsType.field.Restricted) {
+ info->fType = SkAdvancedTypefaceMetrics::kNotEmbeddable_Font;
+ } else if (perGlyphInfo & SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo) {
+ if (fixedWidth) {
+ appendRange(&info->fGlyphWidths, 0);
+ int16_t advance;
+ getWidthAdvance(fDWriteFontFace.get(), 1, &advance);
+ info->fGlyphWidths->fAdvance.append(1, &advance);
+ finishRange(info->fGlyphWidths.get(), 0,
+ SkAdvancedTypefaceMetrics::WidthRange::kDefault);
+ } else {
+ info->fGlyphWidths.reset(
+ getAdvanceData(fDWriteFontFace.get(),
+ glyphCount,
+ glyphIDs,
+ glyphIDsCount,
+ getWidthAdvance));
+ }
+ }
+
+ return info;
+}
+
+static SkTypeface* create_typeface(const SkTypeface* familyFace,
+ const char familyName[],
+ unsigned style) {
+ HRESULT hr;
+ SkTScopedComPtr<IDWriteFontFamily> fontFamily;
+ if (familyFace) {
+ const DWriteFontTypeface* face = static_cast<const DWriteFontTypeface*>(familyFace);
+ *(&fontFamily) = SkRefComPtr(face->fDWriteFontFamily.get());
+
+ } else if (familyName) {
+ hr = get_by_family_name(familyName, &fontFamily);
+ }
+
+ if (NULL == fontFamily.get()) {
+ //No good family found, go with default.
+ SkTScopedComPtr<IDWriteFont> font;
+ hr = get_default_font(&font);
+ hr = font->GetFontFamily(&fontFamily);
+ }
+
+ SkTScopedComPtr<IDWriteFont> font;
+ DWRITE_FONT_WEIGHT weight = (style & SkTypeface::kBold)
+ ? DWRITE_FONT_WEIGHT_BOLD
+ : DWRITE_FONT_WEIGHT_NORMAL;
+ DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_UNDEFINED;
+ DWRITE_FONT_STYLE italic = (style & SkTypeface::kItalic)
+ ? DWRITE_FONT_STYLE_ITALIC
+ : DWRITE_FONT_STYLE_NORMAL;
+ hr = fontFamily->GetFirstMatchingFont(weight, stretch, italic, &font);
+
+ SkTScopedComPtr<IDWriteFontFace> fontFace;
+ hr = font->CreateFontFace(&fontFace);
+
+ return SkCreateTypefaceFromDWriteFont(fontFace.get(), font.get(), fontFamily.get());
+}
+
+SkTypeface* DWriteFontTypeface::onRefMatchingStyle(Style style) const {
+ return create_typeface(this, NULL, style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkFontMgr.h"
+
+static void get_locale_string(IDWriteLocalizedStrings* names, const WCHAR* preferedLocale,
+ SkString* skname) {
+ UINT32 nameIndex = 0;
+ if (preferedLocale) {
+ // Ignore any errors and continue with index 0 if there is a problem.
+ BOOL nameExists;
+ names->FindLocaleName(preferedLocale, &nameIndex, &nameExists);
+ if (!nameExists) {
+ nameIndex = 0;
+ }
+ }
+
+ UINT32 nameLength;
+ HRVM(names->GetStringLength(nameIndex, &nameLength), "Could not get name length.");
+ nameLength += 1;
+
+ SkSMallocWCHAR name(nameLength);
+ HRVM(names->GetString(nameIndex, name.get(), nameLength), "Could not get string.");
+
+ HRV(wchar_to_skstring(name.get(), skname));
+}
+
+class SkFontMgr_DirectWrite;
+
+class SkFontStyleSet_DirectWrite : public SkFontStyleSet {
+public:
+ SkFontStyleSet_DirectWrite(const SkFontMgr_DirectWrite* fontMgr, IDWriteFontFamily* fontFamily)
+ : fFontMgr(SkRef(fontMgr))
+ , fFontFamily(SkRefComPtr(fontFamily))
+ { }
+
+ virtual int count() SK_OVERRIDE {
+ return fFontFamily->GetFontCount();
+ }
+
+ virtual void getStyle(int index, SkFontStyle* fs, SkString* styleName);
+
+ virtual SkTypeface* createTypeface(int index) SK_OVERRIDE {
+ SkTScopedComPtr<IDWriteFont> font;
+ HRNM(fFontFamily->GetFont(index, &font), "Could not get font.");
+
+ SkTScopedComPtr<IDWriteFontFace> fontFace;
+ HRNM(font->CreateFontFace(&fontFace), "Could not create font face.");
+
+ return SkCreateTypefaceFromDWriteFont(fontFace.get(), font.get(), fFontFamily.get());
+ }
+
+ virtual SkTypeface* matchStyle(const SkFontStyle& pattern) SK_OVERRIDE {
+ DWRITE_FONT_STYLE slant;
+ switch (pattern.slant()) {
+ case SkFontStyle::kUpright_Slant:
+ slant = DWRITE_FONT_STYLE_NORMAL;
+ break;
+ case SkFontStyle::kItalic_Slant:
+ slant = DWRITE_FONT_STYLE_ITALIC;
+ break;
+ default:
+ SkASSERT(false);
+ }
+
+ DWRITE_FONT_WEIGHT weight = (DWRITE_FONT_WEIGHT)pattern.weight();
+ DWRITE_FONT_STRETCH width = (DWRITE_FONT_STRETCH)pattern.width();
+
+ SkTScopedComPtr<IDWriteFont> font;
+ // TODO: perhaps use GetMatchingFonts and get the least simulated?
+ HRNM(fFontFamily->GetFirstMatchingFont(weight, width, slant, &font),
+ "Could not match font in family.");
+
+ SkTScopedComPtr<IDWriteFontFace> fontFace;
+ HRNM(font->CreateFontFace(&fontFace), "Could not create font face.");
+
+ return SkCreateTypefaceFromDWriteFont(fontFace.get(), font.get(), fFontFamily.get());
+ }
+
+private:
+ SkAutoTUnref<const SkFontMgr_DirectWrite> fFontMgr;
+ SkTScopedComPtr<IDWriteFontFamily> fFontFamily;
+};
+
+class SkFontMgr_DirectWrite : public SkFontMgr {
+public:
+ /** localeNameLength must include the null terminator. */
+ SkFontMgr_DirectWrite(IDWriteFontCollection* fontCollection,
+ WCHAR* localeName, int localeNameLength)
+ : fFontCollection(SkRefComPtr(fontCollection))
+ , fLocaleName(localeNameLength)
+ {
+ memcpy(fLocaleName.get(), localeName, localeNameLength * sizeof(WCHAR));
+ }
+
+private:
+ friend class SkFontStyleSet_DirectWrite;
+ SkTScopedComPtr<IDWriteFontCollection> fFontCollection;
+ SkSMallocWCHAR fLocaleName;
+
+protected:
+ virtual int onCountFamilies() SK_OVERRIDE {
+ return fFontCollection->GetFontFamilyCount();
+ }
+ virtual void onGetFamilyName(int index, SkString* familyName) SK_OVERRIDE {
+ SkTScopedComPtr<IDWriteFontFamily> fontFamily;
+ HRVM(fFontCollection->GetFontFamily(index, &fontFamily), "Could not get requested family.");
+
+ SkTScopedComPtr<IDWriteLocalizedStrings> familyNames;
+ HRVM(fontFamily->GetFamilyNames(&familyNames), "Could not get family names.");
+
+ get_locale_string(familyNames.get(), fLocaleName.get(), familyName);
+ }
+ virtual SkFontStyleSet* onCreateStyleSet(int index) SK_OVERRIDE {
+ SkTScopedComPtr<IDWriteFontFamily> fontFamily;
+ HRNM(fFontCollection->GetFontFamily(index, &fontFamily), "Could not get requested family.");
+
+ return SkNEW_ARGS(SkFontStyleSet_DirectWrite, (this, fontFamily.get()));
+ }
+ virtual SkFontStyleSet* onMatchFamily(const char familyName[]) SK_OVERRIDE {
+ SkSMallocWCHAR dwFamilyName;
+ HRN(cstring_to_wchar(familyName, &dwFamilyName));
+
+ UINT32 index;
+ BOOL exists;
+ HRNM(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists),
+ "Failed while finding family by name.");
+ if (!exists) {
+ return NULL;
+ }
+
+ return this->onCreateStyleSet(index);
+ }
+
+ virtual SkTypeface* onMatchFamilyStyle(const char familyName[],
+ const SkFontStyle& fontstyle) SK_OVERRIDE {
+ SkAutoTUnref<SkFontStyleSet> sset(this->matchFamily(familyName));
+ return sset->matchStyle(fontstyle);
+ }
+ virtual SkTypeface* onMatchFaceStyle(const SkTypeface* familyMember,
+ const SkFontStyle& fontstyle) SK_OVERRIDE {
+ SkString familyName;
+ SkFontStyleSet_DirectWrite sset(
+ this, ((DWriteFontTypeface*)familyMember)->fDWriteFontFamily.get()
+ );
+ return sset.matchStyle(fontstyle);
+ }
+ virtual SkTypeface* onCreateFromStream(SkStream* stream, int ttcIndex) SK_OVERRIDE {
+ return create_from_stream(stream, ttcIndex);
+ }
+ virtual SkTypeface* onCreateFromData(SkData* data, int ttcIndex) SK_OVERRIDE {
+ SkAutoTUnref<SkStream> stream(SkNEW_ARGS(SkMemoryStream, (data)));
+ return this->createFromStream(stream, ttcIndex);
+ }
+ virtual SkTypeface* onCreateFromFile(const char path[], int ttcIndex) SK_OVERRIDE {
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ return this->createFromStream(stream, ttcIndex);
+ }
+
+ virtual SkTypeface* onLegacyCreateTypeface(const char familyName[],
+ unsigned styleBits) SK_OVERRIDE {
+ return create_typeface(NULL, familyName, styleBits);
+ }
+};
+
+void SkFontStyleSet_DirectWrite::getStyle(int index, SkFontStyle* fs, SkString* styleName) {
+ SkTScopedComPtr<IDWriteFont> font;
+ HRVM(fFontFamily->GetFont(index, &font), "Could not get font.");
+
+ SkFontStyle::Slant slant;
+ switch (font->GetStyle()) {
+ case DWRITE_FONT_STYLE_NORMAL:
+ slant = SkFontStyle::kUpright_Slant;
+ break;
+ case DWRITE_FONT_STYLE_OBLIQUE:
+ case DWRITE_FONT_STYLE_ITALIC:
+ slant = SkFontStyle::kItalic_Slant;
+ break;
+ default:
+ SkASSERT(false);
+ }
+
+ int weight = font->GetWeight();
+ int width = font->GetStretch();
+
+ *fs = SkFontStyle(weight, width, slant);
+
+ SkTScopedComPtr<IDWriteLocalizedStrings> faceNames;
+ if (SUCCEEDED(font->GetFaceNames(&faceNames))) {
+ get_locale_string(faceNames.get(), fFontMgr->fLocaleName.get(), styleName);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_FONTHOST_USES_FONTMGR
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ SkTypeface::Style style) {
+ return create_typeface(familyFace, familyName, style);
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
+ printf("SkFontHost::CreateTypefaceFromFile unimplemented");
+ return NULL;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ return create_from_stream(stream, 0);
+}
+
+#endif
+
+SkFontMgr* SkFontMgr::Factory() {
+ IDWriteFactory* factory;
+ HRNM(get_dwrite_factory(&factory), "Could not get factory.");
+
+ SkTScopedComPtr<IDWriteFontCollection> sysFontCollection;
+ HRNM(factory->GetSystemFontCollection(&sysFontCollection, FALSE),
+ "Could not get system font collection.");
+
+ WCHAR localeNameStorage[LOCALE_NAME_MAX_LENGTH];
+ WCHAR* localeName = NULL;
+ int localeNameLen = GetUserDefaultLocaleName(localeNameStorage, LOCALE_NAME_MAX_LENGTH);
+ if (localeNameLen) {
+ localeName = localeNameStorage;
+ };
+
+ return SkNEW_ARGS(SkFontMgr_DirectWrite, (sysFontCollection.get(), localeName, localeNameLen));
+}
diff --git a/ports/SkGlobalInitialization_chromium.cpp b/ports/SkGlobalInitialization_chromium.cpp
new file mode 100644
index 00000000..d05af709
--- /dev/null
+++ b/ports/SkGlobalInitialization_chromium.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkBitmapProcShader.h"
+#include "SkBlurImageFilter.h"
+#include "SkBlurMaskFilter.h"
+#include "SkColorFilter.h"
+#include "SkCornerPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkGradientShader.h"
+#include "SkLayerDrawLooper.h"
+#include "SkMallocPixelRef.h"
+#include "SkXfermode.h"
+#include "SkMagnifierImageFilter.h"
+
+void SkFlattenable::InitializeFlattenables() {
+
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBitmapProcShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkCornerPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDashPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLayerDrawLooper)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMallocPixelRef)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMagnifierImageFilter)
+
+ SkBlurMaskFilter::InitializeFlattenables();
+ SkColorFilter::InitializeFlattenables();
+ SkGradientShader::InitializeFlattenables();
+ SkXfermode::InitializeFlattenables();
+}
diff --git a/ports/SkGlobalInitialization_default.cpp b/ports/SkGlobalInitialization_default.cpp
new file mode 100644
index 00000000..24edd31e
--- /dev/null
+++ b/ports/SkGlobalInitialization_default.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 "SkTypes.h"
+
+#include "SkBitmapProcShader.h"
+#include "SkMallocPixelRef.h"
+#include "SkPathEffect.h"
+#include "SkPixelRef.h"
+#include "SkXfermode.h"
+
+#include "Sk1DPathEffect.h"
+#include "Sk2DPathEffect.h"
+#include "SkAnnotation.h"
+#include "SkArithmeticMode.h"
+#include "SkAvoidXfermode.h"
+#include "SkBicubicImageFilter.h"
+#include "SkBitmapSource.h"
+#include "SkBlurDrawLooper.h"
+#include "SkBlurImageFilter.h"
+#include "SkBlurMaskFilter.h"
+#include "SkColorFilter.h"
+#include "SkColorFilterImageFilter.h"
+#include "SkColorMatrixFilter.h"
+#include "SkColorShader.h"
+#include "SkColorTable.h"
+#include "SkComposeImageFilter.h"
+#include "SkComposeShader.h"
+#include "SkCornerPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkData.h"
+#include "SkDataSet.h"
+#include "SkDiscretePathEffect.h"
+#include "SkDisplacementMapEffect.h"
+#include "SkDropShadowImageFilter.h"
+#include "SkEmptyShader.h"
+#include "SkEmbossMaskFilter.h"
+#include "SkFlattenable.h"
+#include "SkGradientShader.h"
+#include "SkImages.h"
+#include "SkLayerDrawLooper.h"
+#include "SkLayerRasterizer.h"
+#include "SkLerpXfermode.h"
+#include "SkLightingImageFilter.h"
+#include "SkMagnifierImageFilter.h"
+#include "SkMatrixConvolutionImageFilter.h"
+#include "SkMergeImageFilter.h"
+#include "SkMorphologyImageFilter.h"
+#include "SkOffsetImageFilter.h"
+#include "SkPerlinNoiseShader.h"
+#include "SkPixelXorXfermode.h"
+#include "SkStippleMaskFilter.h"
+#include "SkTableColorFilter.h"
+#include "SkTestImageFilters.h"
+#include "SkXfermodeImageFilter.h"
+
+void SkFlattenable::InitializeFlattenables() {
+
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAnnotation)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAvoidXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBicubicImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBitmapProcShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBitmapSource)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurDrawLooper)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkColorMatrixFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkColorShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkColorTable)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkComposePathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkComposeShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkCornerPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDashPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkData)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDataSet)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDilateImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDiscretePathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDisplacementMapEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDropShadowImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkEmbossMaskFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkEmptyShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkErodeImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLayerDrawLooper)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLayerRasterizer)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLerpXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPath1DPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sk2DPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLine2DPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPath2DPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPixelXorXfermode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkStippleMaskFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSumPathEffect)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkXfermodeImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMagnifierImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMatrixConvolutionImageFilter)
+
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkOffsetImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkComposeImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMergeImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkColorFilterImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDownSampleImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkMallocPixelRef)
+
+ SkArithmeticMode::InitializeFlattenables();
+ SkBlurMaskFilter::InitializeFlattenables();
+ SkColorFilter::InitializeFlattenables();
+ SkGradientShader::InitializeFlattenables();
+ SkImages::InitializeFlattenables();
+ SkLightingImageFilter::InitializeFlattenables();
+ SkTableColorFilter::InitializeFlattenables();
+ SkXfermode::InitializeFlattenables();
+}
diff --git a/ports/SkHarfBuzzFont.cpp b/ports/SkHarfBuzzFont.cpp
new file mode 100644
index 00000000..8f483f5d
--- /dev/null
+++ b/ports/SkHarfBuzzFont.cpp
@@ -0,0 +1,187 @@
+
+/*
+ * Copyright 2009 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 "SkHarfBuzzFont.h"
+#include "SkFontHost.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+
+// HB_Fixed is a 26.6 fixed point format.
+static inline HB_Fixed SkScalarToHarfbuzzFixed(SkScalar value) {
+#ifdef SK_SCALAR_IS_FLOAT
+ return static_cast<HB_Fixed>(value * 64);
+#else
+ // convert .16 to .6
+ return value >> (16 - 6);
+#endif
+}
+
+static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters,
+ hb_uint32 length, HB_Glyph* glyphs,
+ hb_uint32* glyphsSize, HB_Bool isRTL) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+
+ paint.setTypeface(font->getTypeface());
+ paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t),
+ reinterpret_cast<uint16_t*>(glyphs));
+
+ // HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our
+ // |glyphs| array needs to be converted.
+ for (int i = numGlyphs - 1; i >= 0; --i) {
+ uint16_t value;
+ // We use a memcpy to avoid breaking strict aliasing rules.
+ memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(uint16_t));
+ glyphs[i] = value;
+ }
+
+ *glyphsSize = numGlyphs;
+ return 1;
+}
+
+static void glyphsToAdvances(HB_Font hbFont, const HB_Glyph* glyphs,
+ hb_uint32 numGlyphs, HB_Fixed* advances, int flags) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkAutoMalloc storage(numGlyphs * (sizeof(SkScalar) + sizeof(uint16_t)));
+ SkScalar* scalarWidths = reinterpret_cast<SkScalar*>(storage.get());
+ uint16_t* glyphs16 = reinterpret_cast<uint16_t*>(scalarWidths + numGlyphs);
+
+ // convert HB 32bit glyphs to skia's 16bit
+ for (hb_uint32 i = 0; i < numGlyphs; ++i) {
+ glyphs16[i] = SkToU16(glyphs[i]);
+ }
+ paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), scalarWidths);
+
+ for (hb_uint32 i = 0; i < numGlyphs; ++i) {
+ advances[i] = SkScalarToHarfbuzzFixed(scalarWidths[i]);
+ }
+}
+
+static HB_Bool canRender(HB_Font hbFont, const HB_UChar16* characters,
+ hb_uint32 length) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+
+ paint.setTypeface(font->getTypeface());
+ paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ return paint.containsText(characters, length * sizeof(uint16_t));
+}
+
+static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags,
+ hb_uint32 index, HB_Fixed* xPos, HB_Fixed* yPos,
+ hb_uint32* resultingNumPoints) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ if (flags & HB_ShaperFlag_UseDesignMetrics) {
+ paint.setHinting(SkPaint::kNo_Hinting);
+ }
+
+ SkPath path;
+ uint16_t glyph16 = SkToU16(glyph);
+ paint.getTextPath(&glyph16, sizeof(glyph16), 0, 0, &path);
+ int numPoints = path.countPoints();
+ if (index >= numPoints) {
+ return HB_Err_Invalid_SubTable;
+ }
+
+ SkPoint pt = path.getPoint(index);
+ *xPos = SkScalarToHarfbuzzFixed(pt.fX);
+ *yPos = SkScalarToHarfbuzzFixed(pt.fY);
+ *resultingNumPoints = numPoints;
+
+ return HB_Err_Ok;
+}
+
+static void getGlyphMetrics(HB_Font hbFont, HB_Glyph glyph,
+ HB_GlyphMetrics* metrics) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ SkScalar width;
+ SkRect bounds;
+ uint16_t glyph16 = SkToU16(glyph);
+ paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds);
+
+ metrics->x = SkScalarToHarfbuzzFixed(bounds.fLeft);
+ metrics->y = SkScalarToHarfbuzzFixed(bounds.fTop);
+ metrics->width = SkScalarToHarfbuzzFixed(bounds.width());
+ metrics->height = SkScalarToHarfbuzzFixed(bounds.height());
+
+ metrics->xOffset = SkScalarToHarfbuzzFixed(width);
+ // We can't actually get the |y| correct because Skia doesn't export
+ // the vertical advance. However, nor we do ever render vertical text at
+ // the moment so it's unimportant.
+ metrics->yOffset = 0;
+}
+
+static HB_Fixed getFontMetric(HB_Font hbFont, HB_FontMetric metric)
+{
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
+ SkPaint paint;
+ SkPaint::FontMetrics skiaMetrics;
+
+ font->setupPaint(&paint);
+ paint.getFontMetrics(&skiaMetrics);
+
+ switch (metric) {
+ case HB_FontAscent:
+ return SkScalarToHarfbuzzFixed(-skiaMetrics.fAscent);
+ default:
+ SkDebugf("--- unknown harfbuzz metric enum %d\n", metric);
+ return 0;
+ }
+}
+
+static HB_FontClass gSkHarfBuzzFontClass = {
+ stringToGlyphs,
+ glyphsToAdvances,
+ canRender,
+ getOutlinePoint,
+ getGlyphMetrics,
+ getFontMetric,
+};
+
+const HB_FontClass& SkHarfBuzzFont::GetFontClass() {
+ return gSkHarfBuzzFontClass;
+}
+
+HB_Error SkHarfBuzzFont::GetFontTableFunc(void* voidface, const HB_Tag tag,
+ HB_Byte* buffer, HB_UInt* len) {
+ SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(voidface);
+ SkTypeface* typeface = font->getTypeface();
+
+ const size_t tableSize = typeface->getTableSize(tag);
+ if (!tableSize) {
+ return HB_Err_Invalid_Argument;
+ }
+ // If Harfbuzz specified a NULL buffer then it's asking for the size.
+ if (!buffer) {
+ *len = tableSize;
+ return HB_Err_Ok;
+ }
+
+ if (*len < tableSize) {
+ // is this right, or should we just copy less than the full table?
+ return HB_Err_Invalid_Argument;
+ }
+ typeface->getTableData(tag, 0, tableSize, buffer);
+ return HB_Err_Ok;
+}
diff --git a/ports/SkImageDecoder_CG.cpp b/ports/SkImageDecoder_CG.cpp
new file mode 100644
index 00000000..7734ea52
--- /dev/null
+++ b/ports/SkImageDecoder_CG.cpp
@@ -0,0 +1,304 @@
+/*
+ * 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.
+ */
+
+#include "SkCGUtils.h"
+#include "SkColorPriv.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkMovie.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUnPreMultiply.h"
+
+#ifdef SK_BUILD_FOR_MAC
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef SK_BUILD_FOR_IOS
+#include <CoreGraphics/CoreGraphics.h>
+#include <ImageIO/ImageIO.h>
+#include <MobileCoreServices/MobileCoreServices.h>
+#endif
+
+static void malloc_release_proc(void* info, const void* data, size_t size) {
+ sk_free(info);
+}
+
+static CGDataProviderRef SkStreamToDataProvider(SkStream* stream) {
+ // TODO: use callbacks, so we don't have to load all the data into RAM
+ size_t len = stream->getLength();
+ void* data = sk_malloc_throw(len);
+ stream->read(data, len);
+
+ return CGDataProviderCreateWithData(data, data, len, malloc_release_proc);
+}
+
+static CGImageSourceRef SkStreamToCGImageSource(SkStream* stream) {
+ CGDataProviderRef data = SkStreamToDataProvider(stream);
+ CGImageSourceRef imageSrc = CGImageSourceCreateWithDataProvider(data, 0);
+ CGDataProviderRelease(data);
+ return imageSrc;
+}
+
+class SkImageDecoder_CG : public SkImageDecoder {
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
+};
+
+// Returns an unpremultiplied version of color. It will have the same ordering and size as an
+// SkPMColor, but the alpha will not be premultiplied.
+static SkPMColor unpremultiply_pmcolor(SkPMColor color) {
+ U8CPU a = SkGetPackedA32(color);
+ const SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(a);
+ return SkPackARGB32NoCheck(a,
+ SkUnPreMultiply::ApplyScale(scale, SkGetPackedR32(color)),
+ SkUnPreMultiply::ApplyScale(scale, SkGetPackedG32(color)),
+ SkUnPreMultiply::ApplyScale(scale, SkGetPackedB32(color)));
+}
+
+#define BITMAP_INFO (kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast)
+
+bool SkImageDecoder_CG::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+ if (NULL == imageSrc) {
+ return false;
+ }
+ SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+
+ CGImageRef image = CGImageSourceCreateImageAtIndex(imageSrc, 0, NULL);
+ if (NULL == image) {
+ return false;
+ }
+ SkAutoTCallVProc<CGImage, CGImageRelease> arimage(image);
+
+ const int width = CGImageGetWidth(image);
+ const int height = CGImageGetHeight(image);
+ bm->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ if (!this->allocPixelRef(bm, NULL)) {
+ return false;
+ }
+
+ bm->lockPixels();
+ bm->eraseColor(SK_ColorTRANSPARENT);
+
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef 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();
+ }
+ if (!bm->isOpaque() && this->getRequireUnpremultipliedColors()) {
+ // CGBitmapContext does not support unpremultiplied, so the image has been premultiplied.
+ // Convert to unpremultiplied.
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ uint32_t* addr = bm->getAddr32(i, j);
+ *addr = unpremultiply_pmcolor(*addr);
+ }
+ }
+ }
+ bm->unlockPixels();
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStream*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) {
+ SkImageDecoder* decoder = image_decoder_from_stream(stream);
+ if (NULL == decoder) {
+ // If no image decoder specific to the stream exists, use SkImageDecoder_CG.
+ return SkNEW(SkImageDecoder_CG);
+ } else {
+ return decoder;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+SkMovie* SkMovie::DecodeStream(SkStream* stream) {
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+static size_t consumer_put(void* info, const void* buffer, size_t count) {
+ SkWStream* stream = reinterpret_cast<SkWStream*>(info);
+ return stream->write(buffer, count) ? count : 0;
+}
+
+static void consumer_release(void* info) {
+ // we do nothing, since by design we don't "own" the stream (i.e. info)
+}
+
+static CGDataConsumerRef SkStreamToCGDataConsumer(SkWStream* stream) {
+ CGDataConsumerCallbacks procs;
+ procs.putBytes = consumer_put;
+ procs.releaseConsumer = consumer_release;
+ // we don't own/reference the stream, so it our consumer must not live
+ // longer that our caller's ownership of the stream
+ return CGDataConsumerCreate(stream, &procs);
+}
+
+static CGImageDestinationRef SkStreamToImageDestination(SkWStream* stream,
+ CFStringRef type) {
+ CGDataConsumerRef consumer = SkStreamToCGDataConsumer(stream);
+ if (NULL == consumer) {
+ return NULL;
+ }
+ SkAutoTCallVProc<const void, CFRelease> arconsumer(consumer);
+
+ return CGImageDestinationCreateWithDataConsumer(consumer, type, 1, NULL);
+}
+
+class SkImageEncoder_CG : public SkImageEncoder {
+public:
+ SkImageEncoder_CG(Type t) : fType(t) {}
+
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
+
+private:
+ Type fType;
+};
+
+/* Encode bitmaps via CGImageDestination. We setup a DataConsumer which writes
+ to our SkWStream. Since we don't reference/own the SkWStream, our consumer
+ must only live for the duration of the onEncode() method.
+ */
+bool SkImageEncoder_CG::onEncode(SkWStream* stream, const SkBitmap& bm,
+ int quality) {
+ // Used for converting a bitmap to 8888.
+ const SkBitmap* bmPtr = &bm;
+ SkBitmap bitmap8888;
+
+ CFStringRef type;
+ switch (fType) {
+ case kICO_Type:
+ type = kUTTypeICO;
+ break;
+ case kBMP_Type:
+ type = kUTTypeBMP;
+ break;
+ case kGIF_Type:
+ type = kUTTypeGIF;
+ break;
+ case kJPEG_Type:
+ type = kUTTypeJPEG;
+ break;
+ case kPNG_Type:
+ // PNG encoding an ARGB_4444 bitmap gives the following errors in GM:
+ // <Error>: CGImageDestinationAddImage image could not be converted to destination
+ // format.
+ // <Error>: CGImageDestinationFinalize image destination does not have enough images
+ // So instead we copy to 8888.
+ if (bm.getConfig() == SkBitmap::kARGB_4444_Config) {
+ bm.copyTo(&bitmap8888, SkBitmap::kARGB_8888_Config);
+ bmPtr = &bitmap8888;
+ }
+ type = kUTTypePNG;
+ break;
+ default:
+ return false;
+ }
+
+ CGImageDestinationRef dst = SkStreamToImageDestination(stream, type);
+ if (NULL == dst) {
+ return false;
+ }
+ SkAutoTCallVProc<const void, CFRelease> ardst(dst);
+
+ CGImageRef image = SkCreateCGImageRef(*bmPtr);
+ if (NULL == image) {
+ return false;
+ }
+ SkAutoTCallVProc<CGImage, CGImageRelease> agimage(image);
+
+ CGImageDestinationAddImage(dst, image, NULL);
+ return CGImageDestinationFinalize(dst);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+static SkImageEncoder* sk_imageencoder_cg_factory(SkImageEncoder::Type t) {
+ switch (t) {
+ case SkImageEncoder::kICO_Type:
+ case SkImageEncoder::kBMP_Type:
+ case SkImageEncoder::kGIF_Type:
+ case SkImageEncoder::kJPEG_Type:
+ case SkImageEncoder::kPNG_Type:
+ break;
+ default:
+ return NULL;
+ }
+ return SkNEW_ARGS(SkImageEncoder_CG, (t));
+}
+
+static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_imageencoder_cg_factory);
+
+struct FormatConversion {
+ CFStringRef fUTType;
+ SkImageDecoder::Format fFormat;
+};
+
+// Array of the types supported by the decoder.
+static const FormatConversion gFormatConversions[] = {
+ { kUTTypeBMP, SkImageDecoder::kBMP_Format },
+ { kUTTypeGIF, SkImageDecoder::kGIF_Format },
+ { kUTTypeICO, SkImageDecoder::kICO_Format },
+ { kUTTypeJPEG, SkImageDecoder::kJPEG_Format },
+ // Also include JPEG2000
+ { kUTTypeJPEG2000, SkImageDecoder::kJPEG_Format },
+ { kUTTypePNG, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format UTType_to_Format(const CFStringRef uttype) {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+ if (CFStringCompare(uttype, gFormatConversions[i].fUTType, 0) == kCFCompareEqualTo) {
+ return gFormatConversions[i].fFormat;
+ }
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder::Format get_format_cg(SkStream *stream) {
+ CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+ if (NULL == imageSrc) {
+ return SkImageDecoder::kUnknown_Format;
+ }
+
+ SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+ const CFStringRef name = CGImageSourceGetType(imageSrc);
+ if (NULL == name) {
+ return SkImageDecoder::kUnknown_Format;
+ }
+ return UTType_to_Format(name);
+}
+
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_cg);
diff --git a/ports/SkImageDecoder_WIC.cpp b/ports/SkImageDecoder_WIC.cpp
new file mode 100644
index 00000000..77f4b952
--- /dev/null
+++ b/ports/SkImageDecoder_WIC.cpp
@@ -0,0 +1,446 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <wincodec.h>
+#include "SkAutoCoInitialize.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkIStream.h"
+#include "SkMovie.h"
+#include "SkStream.h"
+#include "SkTScopedComPtr.h"
+#include "SkUnPreMultiply.h"
+
+//All Windows SDKs back to XPSP2 export the CLSID_WICImagingFactory symbol.
+//In the Windows8 SDK the CLSID_WICImagingFactory symbol is still exported
+//but CLSID_WICImagingFactory is then #defined to CLSID_WICImagingFactory2.
+//Undo this #define if it has been done so that we link against the symbols
+//we intended to link against on all SDKs.
+#if defined(CLSID_WICImagingFactory)
+#undef CLSID_WICImagingFactory
+#endif
+
+class SkImageDecoder_WIC : public SkImageDecoder {
+public:
+ // Decoding modes corresponding to SkImageDecoder::Mode, plus an extra mode for decoding
+ // only the format.
+ enum WICModes {
+ kDecodeFormat_WICMode,
+ kDecodeBounds_WICMode,
+ kDecodePixels_WICMode,
+ };
+
+ /**
+ * Helper function to decode an SkStream.
+ * @param stream SkStream to decode. Must be at the beginning.
+ * @param bm SkBitmap to decode into. Only used if wicMode is kDecodeBounds_WICMode or
+ * kDecodePixels_WICMode, in which case it must not be NULL.
+ * @param format Out parameter for the SkImageDecoder::Format of the SkStream. Only used if
+ * wicMode is kDecodeFormat_WICMode.
+ */
+ bool decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode, Format* format) const;
+
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
+};
+
+struct FormatConversion {
+ GUID fGuidFormat;
+ SkImageDecoder::Format fFormat;
+};
+
+static const FormatConversion gFormatConversions[] = {
+ { GUID_ContainerFormatBmp, SkImageDecoder::kBMP_Format },
+ { GUID_ContainerFormatGif, SkImageDecoder::kGIF_Format },
+ { GUID_ContainerFormatIco, SkImageDecoder::kICO_Format },
+ { GUID_ContainerFormatJpeg, SkImageDecoder::kJPEG_Format },
+ { GUID_ContainerFormatPng, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format GuidContainerFormat_to_Format(REFGUID guid) {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+ if (IsEqualGUID(guid, gFormatConversions[i].fGuidFormat)) {
+ return gFormatConversions[i].fFormat;
+ }
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+bool SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ WICModes wicMode;
+ switch (mode) {
+ case SkImageDecoder::kDecodeBounds_Mode:
+ wicMode = kDecodeBounds_WICMode;
+ break;
+ case SkImageDecoder::kDecodePixels_Mode:
+ wicMode = kDecodePixels_WICMode;
+ break;
+ }
+ return this->decodeStream(stream, bm, wicMode, NULL);
+}
+
+bool SkImageDecoder_WIC::decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode,
+ Format* format) const {
+ //Initialize COM.
+ SkAutoCoInitialize scopedCo;
+ if (!scopedCo.succeeded()) {
+ return false;
+ }
+
+ HRESULT hr = S_OK;
+
+ //Create Windows Imaging Component ImagingFactory.
+ SkTScopedComPtr<IWICImagingFactory> piImagingFactory;
+ if (SUCCEEDED(hr)) {
+ hr = CoCreateInstance(
+ CLSID_WICImagingFactory
+ , NULL
+ , CLSCTX_INPROC_SERVER
+ , IID_PPV_ARGS(&piImagingFactory)
+ );
+ }
+
+ //Convert SkStream to IStream.
+ SkTScopedComPtr<IStream> piStream;
+ if (SUCCEEDED(hr)) {
+ hr = SkIStream::CreateFromSkStream(stream, false, &piStream);
+ }
+
+ //Make sure we're at the beginning of the stream.
+ if (SUCCEEDED(hr)) {
+ LARGE_INTEGER liBeginning = { 0 };
+ hr = piStream->Seek(liBeginning, STREAM_SEEK_SET, NULL);
+ }
+
+ //Create the decoder from the stream content.
+ SkTScopedComPtr<IWICBitmapDecoder> piBitmapDecoder;
+ if (SUCCEEDED(hr)) {
+ hr = piImagingFactory->CreateDecoderFromStream(
+ piStream.get() //Image to be decoded
+ , NULL //No particular vendor
+ , WICDecodeMetadataCacheOnDemand //Cache metadata when needed
+ , &piBitmapDecoder //Pointer to the decoder
+ );
+ }
+
+ if (kDecodeFormat_WICMode == wicMode) {
+ SkASSERT(format != NULL);
+ //Get the format
+ if (SUCCEEDED(hr)) {
+ GUID guidFormat;
+ hr = piBitmapDecoder->GetContainerFormat(&guidFormat);
+ if (SUCCEEDED(hr)) {
+ *format = GuidContainerFormat_to_Format(guidFormat);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //Get the first frame from the decoder.
+ SkTScopedComPtr<IWICBitmapFrameDecode> piBitmapFrameDecode;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode);
+ }
+
+ //Get the BitmapSource interface of the frame.
+ SkTScopedComPtr<IWICBitmapSource> piBitmapSourceOriginal;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameDecode->QueryInterface(
+ IID_PPV_ARGS(&piBitmapSourceOriginal)
+ );
+ }
+
+ //Get the size of the bitmap.
+ UINT width;
+ UINT height;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapSourceOriginal->GetSize(&width, &height);
+ }
+
+ //Exit early if we're only looking for the bitmap bounds.
+ if (SUCCEEDED(hr)) {
+ bm->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ if (kDecodeBounds_WICMode == wicMode) {
+ return true;
+ }
+ if (!this->allocPixelRef(bm, NULL)) {
+ return false;
+ }
+ }
+
+ //Create a format converter.
+ SkTScopedComPtr<IWICFormatConverter> piFormatConverter;
+ if (SUCCEEDED(hr)) {
+ hr = piImagingFactory->CreateFormatConverter(&piFormatConverter);
+ }
+
+ GUID destinationPixelFormat;
+ if (this->getRequireUnpremultipliedColors()) {
+ destinationPixelFormat = GUID_WICPixelFormat32bppBGRA;
+ } else {
+ destinationPixelFormat = GUID_WICPixelFormat32bppPBGRA;
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = piFormatConverter->Initialize(
+ piBitmapSourceOriginal.get() //Input bitmap to convert
+ , destinationPixelFormat //Destination pixel format
+ , WICBitmapDitherTypeNone //Specified dither patterm
+ , NULL //Specify a particular palette
+ , 0.f //Alpha threshold
+ , WICBitmapPaletteTypeCustom //Palette translation type
+ );
+ }
+
+ //Get the BitmapSource interface of the format converter.
+ SkTScopedComPtr<IWICBitmapSource> piBitmapSourceConverted;
+ if (SUCCEEDED(hr)) {
+ hr = piFormatConverter->QueryInterface(
+ IID_PPV_ARGS(&piBitmapSourceConverted)
+ );
+ }
+
+ //Copy the pixels into the bitmap.
+ if (SUCCEEDED(hr)) {
+ SkAutoLockPixels alp(*bm);
+ bm->eraseColor(SK_ColorTRANSPARENT);
+ const UINT stride = bm->rowBytes();
+ hr = piBitmapSourceConverted->CopyPixels(
+ NULL, //Get all the pixels
+ stride,
+ stride * height,
+ reinterpret_cast<BYTE *>(bm->getPixels())
+ );
+
+ // Note: we don't need to premultiply here since we specified PBGRA
+ bm->computeAndSetOpaquePredicate();
+ }
+
+ return SUCCEEDED(hr);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStream*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) {
+ SkImageDecoder* decoder = image_decoder_from_stream(stream);
+ if (NULL == decoder) {
+ // If no image decoder specific to the stream exists, use SkImageDecoder_WIC.
+ return SkNEW(SkImageDecoder_WIC);
+ } else {
+ return decoder;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+SkMovie* SkMovie::DecodeStream(SkStream* stream) {
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+class SkImageEncoder_WIC : public SkImageEncoder {
+public:
+ SkImageEncoder_WIC(Type t) : fType(t) {}
+
+protected:
+ virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
+
+private:
+ Type fType;
+};
+
+bool SkImageEncoder_WIC::onEncode(SkWStream* stream
+ , const SkBitmap& bitmapOrig
+ , int quality)
+{
+ GUID type;
+ switch (fType) {
+ case kBMP_Type:
+ type = GUID_ContainerFormatBmp;
+ break;
+ case kICO_Type:
+ type = GUID_ContainerFormatIco;
+ break;
+ case kJPEG_Type:
+ type = GUID_ContainerFormatJpeg;
+ break;
+ case kPNG_Type:
+ type = GUID_ContainerFormatPng;
+ break;
+ default:
+ return false;
+ }
+
+ //Convert to 8888 if needed.
+ const SkBitmap* bitmap;
+ SkBitmap bitmapCopy;
+ if (SkBitmap::kARGB_8888_Config == bitmapOrig.config() && bitmapOrig.isOpaque()) {
+ bitmap = &bitmapOrig;
+ } else {
+ if (!bitmapOrig.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config)) {
+ return false;
+ }
+ 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()) {
+ return false;
+ }
+
+ HRESULT hr = S_OK;
+
+ //Create Windows Imaging Component ImagingFactory.
+ SkTScopedComPtr<IWICImagingFactory> piImagingFactory;
+ if (SUCCEEDED(hr)) {
+ hr = CoCreateInstance(
+ CLSID_WICImagingFactory
+ , NULL
+ , CLSCTX_INPROC_SERVER
+ , IID_PPV_ARGS(&piImagingFactory)
+ );
+ }
+
+ //Convert the SkWStream to an IStream.
+ SkTScopedComPtr<IStream> piStream;
+ if (SUCCEEDED(hr)) {
+ hr = SkWIStream::CreateFromSkWStream(stream, &piStream);
+ }
+
+ //Create an encode of the appropriate type.
+ SkTScopedComPtr<IWICBitmapEncoder> piEncoder;
+ if (SUCCEEDED(hr)) {
+ hr = piImagingFactory->CreateEncoder(type, NULL, &piEncoder);
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = piEncoder->Initialize(piStream.get(), WICBitmapEncoderNoCache);
+ }
+
+ //Create a the frame.
+ SkTScopedComPtr<IWICBitmapFrameEncode> piBitmapFrameEncode;
+ SkTScopedComPtr<IPropertyBag2> piPropertybag;
+ if (SUCCEEDED(hr)) {
+ hr = piEncoder->CreateNewFrame(&piBitmapFrameEncode, &piPropertybag);
+ }
+
+ if (SUCCEEDED(hr)) {
+ PROPBAG2 name = { 0 };
+ name.dwType = PROPBAG2_TYPE_DATA;
+ name.vt = VT_R4;
+ name.pstrName = L"ImageQuality";
+
+ VARIANT value;
+ VariantInit(&value);
+ value.vt = VT_R4;
+ value.fltVal = (FLOAT)(quality / 100.0);
+
+ //Ignore result code.
+ // This returns E_FAIL if the named property is not in the bag.
+ //TODO(bungeman) enumerate the properties,
+ // write and set hr iff property exists.
+ piPropertybag->Write(1, &name, &value);
+ }
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameEncode->Initialize(piPropertybag.get());
+ }
+
+ //Set the size of the frame.
+ const UINT width = bitmap->width();
+ const UINT height = bitmap->height();
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameEncode->SetSize(width, height);
+ }
+
+ //Set the pixel format of the frame.
+ const WICPixelFormatGUID formatDesired = GUID_WICPixelFormat32bppBGRA;
+ WICPixelFormatGUID formatGUID = formatDesired;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameEncode->SetPixelFormat(&formatGUID);
+ }
+ if (SUCCEEDED(hr)) {
+ //Be sure the image format is the one requested.
+ hr = IsEqualGUID(formatGUID, formatDesired) ? S_OK : E_FAIL;
+ }
+
+ //Write the pixels into the frame.
+ if (SUCCEEDED(hr)) {
+ SkAutoLockPixels alp(*bitmap);
+ const UINT stride = bitmap->rowBytes();
+ hr = piBitmapFrameEncode->WritePixels(
+ height
+ , stride
+ , stride * height
+ , reinterpret_cast<BYTE*>(bitmap->getPixels()));
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameEncode->Commit();
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = piEncoder->Commit();
+ }
+
+ return SUCCEEDED(hr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkTRegistry.h"
+
+static SkImageEncoder* sk_imageencoder_wic_factory(SkImageEncoder::Type t) {
+ switch (t) {
+ case SkImageEncoder::kBMP_Type:
+ case SkImageEncoder::kICO_Type:
+ case SkImageEncoder::kJPEG_Type:
+ case SkImageEncoder::kPNG_Type:
+ break;
+ default:
+ return NULL;
+ }
+ return SkNEW_ARGS(SkImageEncoder_WIC, (t));
+}
+
+static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_imageencoder_wic_factory);
+
+static SkImageDecoder::Format get_format_wic(SkStream* stream) {
+ SkImageDecoder::Format format;
+ SkImageDecoder_WIC codec;
+ if (!codec.decodeStream(stream, NULL, SkImageDecoder_WIC::kDecodeFormat_WICMode, &format)) {
+ format = SkImageDecoder::kUnknown_Format;
+ }
+ return format;
+}
+
+static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_wic);
diff --git a/ports/SkImageDecoder_empty.cpp b/ports/SkImageDecoder_empty.cpp
new file mode 100644
index 00000000..c225bb19
--- /dev/null
+++ b/ports/SkImageDecoder_empty.cpp
@@ -0,0 +1,156 @@
+
+/*
+ * 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 "SkBitmap.h"
+#include "SkBitmapFactory.h"
+#include "SkImage.h"
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkMovie.h"
+
+class SkColorTable;
+class SkStream;
+
+// Empty implementations for SkImageDecoder.
+
+SkImageDecoder* SkImageDecoder::Factory(SkStream*) {
+ return NULL;
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* ) {}
+
+bool SkImageDecoder::DecodeFile(const char[], SkBitmap*, SkBitmap::Config,
+ SkImageDecoder::Mode, SkImageDecoder::Format*) {
+ return false;
+}
+
+bool SkImageDecoder::decode(SkStream*, SkBitmap*, SkBitmap::Config, Mode) {
+ return false;
+}
+
+bool SkImageDecoder::DecodeStream(SkStream*, SkBitmap*, SkBitmap::Config,
+ SkImageDecoder::Mode,
+ SkImageDecoder::Format*) {
+ return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void*, size_t, SkBitmap*,
+ SkBitmap::Config, SkImageDecoder::Mode,
+ SkImageDecoder::Format*) {
+ return false;
+}
+
+bool SkImageDecoder::buildTileIndex(SkStream*, int *width, int *height) {
+ return false;
+}
+
+bool SkImageDecoder::decodeSubset(SkBitmap*, const SkIRect&, SkBitmap::Config) {
+ return false;
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+ return kUnknown_Format;
+}
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStream*) {
+ return kUnknown_Format;
+}
+
+const char* SkImageDecoder::GetFormatName(Format) {
+ return NULL;
+}
+
+SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker*) {
+ return NULL;
+}
+
+SkImageDecoder::Chooser* SkImageDecoder::setChooser(Chooser*) {
+ return NULL;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator*) {
+ return NULL;
+}
+
+void SkImageDecoder::setSampleSize(int) {}
+
+bool SkImageDecoder::DecodeMemoryToTarget(const void*, size_t, SkImage::Info*,
+ const SkBitmapFactory::Target*) {
+ return false;
+}
+
+SkBitmap::Config SkImageDecoder::GetDeviceConfig() {
+ return SkBitmap::kNo_Config;
+}
+
+void SkImageDecoder::SetDeviceConfig(SkBitmap::Config) {}
+
+bool SkImageDecoder::cropBitmap(SkBitmap*, SkBitmap*, int, int, int, int, int,
+ int, int) {
+ return false;
+}
+
+bool SkImageDecoder::chooseFromOneChoice(SkBitmap::Config, int, int) const {
+ return false;
+}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap*, SkColorTable*) const {
+ return false;
+}
+
+SkBitmap::Config SkImageDecoder::getPrefConfig(SrcDepth, bool) const {
+ return SkBitmap::kNo_Config;
+}
+
+
+/////////////////////////////////////////////////////////////////////////
+
+// Empty implementation for SkMovie.
+
+SkMovie* SkMovie::DecodeStream(SkStream* stream) {
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+// Empty implementations for SkImageEncoder.
+
+SkImageEncoder* SkImageEncoder::Create(Type t) {
+ return NULL;
+}
+
+bool SkImageEncoder::EncodeFile(const char file[], const SkBitmap&, Type, int quality) {
+ return false;
+}
+
+bool SkImageEncoder::EncodeStream(SkWStream*, const SkBitmap&, SkImageEncoder::Type, int) {
+ return false;
+}
+
+SkData* SkImageEncoder::EncodeData(const SkBitmap&, Type, int quality) {
+ return NULL;
+}
+
+bool SkImageEncoder::encodeStream(SkWStream*, const SkBitmap&, int) {
+ return false;
+}
+
+SkData* SkImageEncoder::encodeData(const SkBitmap&, int) {
+ return NULL;
+}
+
+bool SkImageEncoder::encodeFile(const char file[], const SkBitmap& bm, int quality) {
+ return false;
+}
+/////////////////////////////////////////////////////////////////////////
+
+// Empty implementation for SkImages.
+
+#include "SkImages.h"
+
+void SkImages::InitializeFlattenables() {}
diff --git a/ports/SkMemory_malloc.cpp b/ports/SkMemory_malloc.cpp
new file mode 100644
index 00000000..73b56079
--- /dev/null
+++ b/ports/SkMemory_malloc.cpp
@@ -0,0 +1,51 @@
+
+/*
+ * 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 "SkTypes.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+void sk_throw() {
+ SkDEBUGFAIL("sk_throw");
+ abort();
+}
+
+void sk_out_of_memory(void) {
+ SkDEBUGFAIL("sk_out_of_memory");
+ abort();
+}
+
+void* sk_malloc_throw(size_t size) {
+ return sk_malloc_flags(size, SK_MALLOC_THROW);
+}
+
+void* sk_realloc_throw(void* addr, size_t size) {
+ void* p = realloc(addr, size);
+ if (size == 0) {
+ return p;
+ }
+ if (p == NULL) {
+ sk_throw();
+ }
+ return p;
+}
+
+void sk_free(void* p) {
+ if (p) {
+ free(p);
+ }
+}
+
+void* sk_malloc_flags(size_t size, unsigned flags) {
+ void* p = malloc(size);
+ if (p == NULL) {
+ if (flags & SK_MALLOC_THROW) {
+ sk_throw();
+ }
+ }
+ return p;
+}
diff --git a/ports/SkMemory_mozalloc.cpp b/ports/SkMemory_mozalloc.cpp
new file mode 100644
index 00000000..2c049b2a
--- /dev/null
+++ b/ports/SkMemory_mozalloc.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 Google Inc.
+ * Copyright 2012 Mozilla Foundation
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#include "mozilla/mozalloc.h"
+#include "mozilla/mozalloc_abort.h"
+#include "mozilla/mozalloc_oom.h"
+
+void sk_throw() {
+ SkDEBUGFAIL("sk_throw");
+ mozalloc_abort("Abort from sk_throw");
+}
+
+void sk_out_of_memory(void) {
+ SkDEBUGFAIL("sk_out_of_memory");
+ mozalloc_handle_oom(0);
+}
+
+void* sk_malloc_throw(size_t size) {
+ return sk_malloc_flags(size, SK_MALLOC_THROW);
+}
+
+void* sk_realloc_throw(void* addr, size_t size) {
+ return moz_xrealloc(addr, size);
+}
+
+void sk_free(void* p) {
+ moz_free(p);
+}
+
+void* sk_malloc_flags(size_t size, unsigned flags) {
+ return (flags & SK_MALLOC_THROW) ? moz_xmalloc(size) : moz_malloc(size);
+}
diff --git a/ports/SkOSFile_none.cpp b/ports/SkOSFile_none.cpp
new file mode 100644
index 00000000..7ed8ab4c
--- /dev/null
+++ b/ports/SkOSFile_none.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOSFile.h"
+
+bool sk_fidentical(SkFILE* a, SkFILE* b) {
+ return false;
+}
+
+int sk_fileno(SkFILE* f) {
+ return -1;
+}
+
+void sk_fmunmap(const void* addr, size_t length) { }
+
+void* sk_fdmmap(int fd, size_t* size) {
+ return NULL;
+}
+
+void* sk_fmmap(SkFILE* f, size_t* size) {
+ return NULL;
+}
diff --git a/ports/SkOSFile_posix.cpp b/ports/SkOSFile_posix.cpp
new file mode 100644
index 00000000..93918b26
--- /dev/null
+++ b/ports/SkOSFile_posix.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOSFile.h"
+
+#include "SkTFitsIn.h"
+
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+typedef struct {
+ dev_t dev;
+ ino_t ino;
+} SkFILEID;
+
+static bool sk_ino(SkFILE* a, SkFILEID* id) {
+ int fd = fileno((FILE*)a);
+ if (fd < 0) {
+ return 0;
+ }
+ struct stat status;
+ if (0 != fstat(fd, &status)) {
+ return 0;
+ }
+ id->dev = status.st_dev;
+ id->ino = status.st_ino;
+ return true;
+}
+
+bool sk_fidentical(SkFILE* a, SkFILE* b) {
+ SkFILEID aID, bID;
+ return sk_ino(a, &aID) && sk_ino(b, &bID)
+ && aID.ino == bID.ino
+ && aID.dev == bID.dev;
+}
+
+void sk_fmunmap(const void* addr, size_t length) {
+ munmap(const_cast<void*>(addr), length);
+}
+
+void* sk_fdmmap(int fd, size_t* size) {
+ struct stat status;
+ if (0 != fstat(fd, &status)) {
+ return NULL;
+ }
+ if (!S_ISREG(status.st_mode)) {
+ return NULL;
+ }
+ if (!SkTFitsIn<size_t>(status.st_size)) {
+ return NULL;
+ }
+ size_t fileSize = static_cast<size_t>(status.st_size);
+
+ void* addr = mmap(NULL, fileSize, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (MAP_FAILED == addr) {
+ return NULL;
+ }
+
+ *size = fileSize;
+ return addr;
+}
+
+int sk_fileno(SkFILE* f) {
+ return fileno((FILE*)f);
+}
+
+void* sk_fmmap(SkFILE* f, size_t* size) {
+ int fd = sk_fileno(f);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ return sk_fdmmap(fd, size);
+}
diff --git a/ports/SkOSFile_stdio.cpp b/ports/SkOSFile_stdio.cpp
new file mode 100644
index 00000000..ad26c807
--- /dev/null
+++ b/ports/SkOSFile_stdio.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 "SkOSFile.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) {
+ char perm[4];
+ char* p = perm;
+
+ if (flags & kRead_SkFILE_Flag) {
+ *p++ = 'r';
+ }
+ if (flags & kWrite_SkFILE_Flag) {
+ *p++ = 'w';
+ }
+ *p++ = 'b';
+ *p = 0;
+
+ //TODO: on Windows fopen is just ASCII or the current code page,
+ //convert to utf16 and use _wfopen
+ return (SkFILE*)::fopen(path, perm);
+}
+
+char* sk_fgets(char* str, int size, SkFILE* f) {
+ return ::fgets(str, size, (FILE *)f);
+}
+
+int sk_feof(SkFILE *f) {
+ // no :: namespace qualifier because it breaks android
+ return feof((FILE *)f);
+}
+
+size_t sk_fgetsize(SkFILE* f) {
+ SkASSERT(f);
+
+ long curr = ::ftell((FILE*)f); // remember where we are
+ if (curr < 0) {
+ return 0;
+ }
+
+ ::fseek((FILE*)f, 0, SEEK_END); // go to the end
+ long size = ::ftell((FILE*)f); // record the size
+ if (size < 0) {
+ size = 0;
+ }
+
+ ::fseek((FILE*)f, curr, SEEK_SET); // go back to our prev location
+ return size;
+}
+
+bool sk_frewind(SkFILE* f) {
+ SkASSERT(f);
+ ::rewind((FILE*)f);
+ return true;
+}
+
+size_t sk_fread(void* buffer, size_t byteCount, SkFILE* f) {
+ SkASSERT(f);
+ if (buffer == NULL) {
+ size_t curr = ::ftell((FILE*)f);
+ if ((long)curr == -1) {
+ SkDEBUGF(("sk_fread: ftell(%p) returned -1 feof:%d ferror:%d\n", f, feof((FILE*)f), ferror((FILE*)f)));
+ return 0;
+ }
+ int err = ::fseek((FILE*)f, (long)byteCount, SEEK_CUR);
+ if (err != 0) {
+ SkDEBUGF(("sk_fread: fseek(%d) tell:%d failed with feof:%d ferror:%d returned:%d\n",
+ byteCount, curr, feof((FILE*)f), ferror((FILE*)f), err));
+ return 0;
+ }
+ return byteCount;
+ }
+ else
+ return ::fread(buffer, 1, byteCount, (FILE*)f);
+}
+
+size_t sk_fwrite(const void* buffer, size_t byteCount, SkFILE* f) {
+ SkASSERT(f);
+ return ::fwrite(buffer, 1, byteCount, (FILE*)f);
+}
+
+void sk_fflush(SkFILE* f) {
+ SkASSERT(f);
+ ::fflush((FILE*)f);
+}
+
+bool sk_fseek(SkFILE* f, size_t byteCount) {
+ int err = ::fseek((FILE*)f, (long)byteCount, SEEK_SET);
+ return err == 0;
+}
+
+bool sk_fmove(SkFILE* f, long byteCount) {
+ int err = ::fseek((FILE*)f, byteCount, SEEK_CUR);
+ return err == 0;
+}
+
+size_t sk_ftell(SkFILE* f) {
+ long curr = ::ftell((FILE*)f);
+ if (curr < 0) {
+ return 0;
+ }
+ return curr;
+}
+
+void sk_fclose(SkFILE* f) {
+ SkASSERT(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/ports/SkOSFile_win.cpp b/ports/SkOSFile_win.cpp
new file mode 100644
index 00000000..7fec5579
--- /dev/null
+++ b/ports/SkOSFile_win.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOSFile.h"
+
+#include "SkTFitsIn.h"
+
+#include <io.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+typedef struct {
+ ULONGLONG fVolume;
+ ULONGLONG fLsbSize;
+ ULONGLONG fMsbSize;
+} SkFILEID;
+
+static bool sk_ino(SkFILE* f, SkFILEID* id) {
+ int fileno = _fileno((FILE*)f);
+ if (fileno < 0) {
+ return false;
+ }
+
+ HANDLE file = (HANDLE)_get_osfhandle(fileno);
+ if (INVALID_HANDLE_VALUE == file) {
+ return false;
+ }
+
+ //TODO: call GetFileInformationByHandleEx on Vista and later with FileIdInfo.
+ BY_HANDLE_FILE_INFORMATION info;
+ if (0 == GetFileInformationByHandle(file, &info)) {
+ return false;
+ }
+ id->fVolume = info.dwVolumeSerialNumber;
+ id->fLsbSize = info.nFileIndexLow + (((ULONGLONG)info.nFileIndexHigh) << 32);
+ id->fMsbSize = 0;
+
+ return true;
+}
+
+bool sk_fidentical(SkFILE* a, SkFILE* b) {
+ SkFILEID aID, bID;
+ return sk_ino(a, &aID) && sk_ino(b, &bID)
+ && aID.fLsbSize == bID.fLsbSize
+ && aID.fMsbSize == bID.fMsbSize
+ && aID.fVolume == bID.fVolume;
+}
+
+template <typename HandleType, HandleType InvalidValue, BOOL (WINAPI * Close)(HandleType)>
+class SkAutoTHandle : SkNoncopyable {
+public:
+ SkAutoTHandle(HandleType handle) : fHandle(handle) { }
+ ~SkAutoTHandle() { Close(fHandle); }
+ operator HandleType() { return fHandle; }
+ bool isValid() { return InvalidValue != fHandle; }
+private:
+ HandleType fHandle;
+};
+typedef SkAutoTHandle<HANDLE, INVALID_HANDLE_VALUE, CloseHandle> SkAutoWinFile;
+typedef SkAutoTHandle<HANDLE, NULL, CloseHandle> SkAutoWinMMap;
+
+void sk_fmunmap(const void* addr, size_t) {
+ UnmapViewOfFile(addr);
+}
+
+void* sk_fdmmap(int fileno, size_t* length) {
+ HANDLE file = (HANDLE)_get_osfhandle(fileno);
+ if (INVALID_HANDLE_VALUE == file) {
+ return NULL;
+ }
+
+ LARGE_INTEGER fileSize;
+ if (0 == GetFileSizeEx(file, &fileSize)) {
+ //TODO: use SK_TRACEHR(GetLastError(), "Could not get file size.") to report.
+ return NULL;
+ }
+ if (!SkTFitsIn<size_t>(fileSize.QuadPart)) {
+ return NULL;
+ }
+
+ SkAutoWinMMap mmap(CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL));
+ if (!mmap.isValid()) {
+ //TODO: use SK_TRACEHR(GetLastError(), "Could not create file mapping.") to report.
+ return NULL;
+ }
+
+ // Eventually call UnmapViewOfFile
+ void* addr = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
+ if (NULL == addr) {
+ //TODO: use SK_TRACEHR(GetLastError(), "Could not map view of file.") to report.
+ return NULL;
+ }
+
+ *length = static_cast<size_t>(fileSize.QuadPart);
+ return addr;
+}
+
+int sk_fileno(SkFILE* f) {
+ return _fileno((FILE*)f);
+}
+
+void* sk_fmmap(SkFILE* f, size_t* length) {
+ int fileno = sk_fileno(f);
+ if (fileno < 0) {
+ return NULL;
+ }
+
+ return sk_fdmmap(fileno, length);
+}
diff --git a/ports/SkPurgeableMemoryBlock_android.cpp b/ports/SkPurgeableMemoryBlock_android.cpp
new file mode 100644
index 00000000..acabb0d8
--- /dev/null
+++ b/ports/SkPurgeableMemoryBlock_android.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPurgeableMemoryBlock.h"
+
+#include "android/ashmem.h"
+#include <sys/mman.h>
+#include <unistd.h>
+
+bool SkPurgeableMemoryBlock::IsSupported() {
+ return true;
+}
+
+#ifdef SK_DEBUG
+bool SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks() {
+ return false;
+}
+
+bool SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks() {
+ return false;
+}
+
+bool SkPurgeableMemoryBlock::purge() {
+ SkASSERT(!fPinned);
+ if (-1 != fFD) {
+ ashmem_purge_all_caches(fFD);
+ return true;
+ } else {
+ return false;
+ }
+}
+#endif
+
+// ashmem likes lengths on page boundaries.
+static size_t round_to_page_size(size_t size) {
+ const size_t mask = getpagesize() - 1;
+ size_t newSize = (size + mask) & ~mask;
+ return newSize;
+}
+
+SkPurgeableMemoryBlock::SkPurgeableMemoryBlock(size_t size)
+ : fAddr(NULL)
+ , fSize(round_to_page_size(size))
+ , fPinned(false)
+ , fFD(-1) {
+}
+
+SkPurgeableMemoryBlock::~SkPurgeableMemoryBlock() {
+ if (-1 != fFD) {
+ munmap(fAddr, fSize);
+ close(fFD);
+ }
+}
+
+void* SkPurgeableMemoryBlock::pin(SkPurgeableMemoryBlock::PinResult* pinResult) {
+ SkASSERT(!fPinned);
+ if (-1 == fFD) {
+ int fd = ashmem_create_region(NULL, fSize);
+ if (-1 == fd) {
+ SkDebugf("ashmem_create_region failed\n");
+ return NULL;
+ }
+
+ int err = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+ if (err != 0) {
+ SkDebugf("ashmem_set_prot_region failed\n");
+ close(fd);
+ return NULL;
+ }
+
+ void* addr = mmap(NULL, fSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (-1 == (long) addr) {
+ SkDebugf("mmap failed\n");
+ close(fd);
+ return NULL;
+ }
+ fAddr = addr;
+ fFD = fd;
+ (void) ashmem_pin_region(fd, 0, 0);
+ *pinResult = kUninitialized_PinResult;
+ fPinned = true;
+ } else {
+ int pin = ashmem_pin_region(fFD, 0, 0);
+ if (ASHMEM_NOT_PURGED == pin) {
+ fPinned = true;
+ *pinResult = kRetained_PinResult;
+ } else if (ASHMEM_WAS_PURGED == pin) {
+ fPinned = true;
+ *pinResult = kUninitialized_PinResult;
+ } else {
+ // Failed.
+ munmap(fAddr, fSize);
+ close(fFD);
+ fFD = -1;
+ fAddr = NULL;
+ }
+ }
+ return fAddr;
+}
+
+void SkPurgeableMemoryBlock::unpin() {
+ if (-1 != fFD) {
+ ashmem_unpin_region(fFD, 0, 0);
+ fPinned = false;
+ }
+}
diff --git a/ports/SkPurgeableMemoryBlock_mac.cpp b/ports/SkPurgeableMemoryBlock_mac.cpp
new file mode 100644
index 00000000..da4cdff1
--- /dev/null
+++ b/ports/SkPurgeableMemoryBlock_mac.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPurgeableMemoryBlock.h"
+
+#include <mach/mach.h>
+
+bool SkPurgeableMemoryBlock::IsSupported() {
+ return true;
+}
+
+#ifdef SK_DEBUG
+bool SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks() {
+ return true;
+}
+
+bool SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks() {
+ // Unused.
+ int state = 0;
+ kern_return_t ret = vm_purgable_control(mach_task_self(), 0, VM_PURGABLE_PURGE_ALL, &state);
+ return ret == KERN_SUCCESS;
+}
+
+bool SkPurgeableMemoryBlock::purge() {
+ return false;
+}
+#endif
+
+static size_t round_to_page_size(size_t size) {
+ const size_t mask = 4096 - 1;
+ return (size + mask) & ~mask;
+}
+
+SkPurgeableMemoryBlock::SkPurgeableMemoryBlock(size_t size)
+ : fAddr(NULL)
+ , fSize(round_to_page_size(size))
+ , fPinned(false) {
+}
+
+SkPurgeableMemoryBlock::~SkPurgeableMemoryBlock() {
+ SkDEBUGCODE(kern_return_t ret =) vm_deallocate(mach_task_self(),
+ reinterpret_cast<vm_address_t>(fAddr),
+ static_cast<vm_size_t>(fSize));
+#ifdef SK_DEBUG
+ if (ret != KERN_SUCCESS) {
+ SkDebugf("SkPurgeableMemoryBlock destructor failed to deallocate.\n");
+ }
+#endif
+}
+
+void* SkPurgeableMemoryBlock::pin(SkPurgeableMemoryBlock::PinResult* pinResult) {
+ SkASSERT(!fPinned);
+ SkASSERT(pinResult != NULL);
+ if (NULL == fAddr) {
+ vm_address_t addr = 0;
+ kern_return_t ret = vm_allocate(mach_task_self(), &addr, static_cast<vm_size_t>(fSize),
+ VM_FLAGS_PURGABLE | VM_FLAGS_ANYWHERE);
+ if (KERN_SUCCESS == ret) {
+ fAddr = reinterpret_cast<void*>(addr);
+ *pinResult = kUninitialized_PinResult;
+ fPinned = true;
+ } else {
+ fAddr = NULL;
+ }
+ } else {
+ int state = VM_PURGABLE_NONVOLATILE;
+ kern_return_t ret = vm_purgable_control(mach_task_self(),
+ reinterpret_cast<vm_address_t>(fAddr),
+ VM_PURGABLE_SET_STATE, &state);
+ if (ret != KERN_SUCCESS) {
+ fAddr = NULL;
+ fPinned = false;
+ return NULL;
+ }
+
+ fPinned = true;
+
+ if (state & VM_PURGABLE_EMPTY) {
+ *pinResult = kUninitialized_PinResult;
+ } else {
+ *pinResult = kRetained_PinResult;
+ }
+ }
+ return fAddr;
+}
+
+void SkPurgeableMemoryBlock::unpin() {
+ SkASSERT(fPinned);
+ int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT;
+ SkDEBUGCODE(kern_return_t ret =) vm_purgable_control(mach_task_self(),
+ reinterpret_cast<vm_address_t>(fAddr),
+ VM_PURGABLE_SET_STATE, &state);
+ fPinned = false;
+
+#ifdef SK_DEBUG
+ if (ret != KERN_SUCCESS) {
+ SkDebugf("SkPurgeableMemoryBlock::unpin() failed.\n");
+ }
+#endif
+}
diff --git a/ports/SkPurgeableMemoryBlock_none.cpp b/ports/SkPurgeableMemoryBlock_none.cpp
new file mode 100644
index 00000000..e10832e9
--- /dev/null
+++ b/ports/SkPurgeableMemoryBlock_none.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPurgeableMemoryBlock.h"
+
+bool SkPurgeableMemoryBlock::IsSupported() {
+ return false;
+}
+
+#ifdef SK_DEBUG
+bool SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks() {
+ return false;
+}
+
+bool SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks() {
+ return false;
+}
+
+bool SkPurgeableMemoryBlock::purge() {
+ return false;
+}
+#endif
+
+SkPurgeableMemoryBlock::SkPurgeableMemoryBlock(size_t size) {
+ SkASSERT(false);
+}
+
+SkPurgeableMemoryBlock::~SkPurgeableMemoryBlock() {
+}
+
+void* SkPurgeableMemoryBlock::pin(SkPurgeableMemoryBlock::PinResult*) {
+ return NULL;
+}
+
+void SkPurgeableMemoryBlock::unpin() {
+}
diff --git a/ports/SkTLS_none.cpp b/ports/SkTLS_none.cpp
new file mode 100644
index 00000000..95f6e370
--- /dev/null
+++ b/ports/SkTLS_none.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTLS.h"
+
+static void* gSpecific = NULL;
+
+void* SkTLS::PlatformGetSpecific(bool) {
+ return gSpecific;
+}
+
+void SkTLS::PlatformSetSpecific(void* ptr) {
+ gSpecific = ptr;
+}
diff --git a/ports/SkTLS_pthread.cpp b/ports/SkTLS_pthread.cpp
new file mode 100644
index 00000000..42648902
--- /dev/null
+++ b/ports/SkTLS_pthread.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTLS.h"
+
+#include <pthread.h>
+
+static pthread_key_t gSkTLSKey;
+static pthread_once_t gSkTLSKey_Once = PTHREAD_ONCE_INIT;
+
+static void sk_tls_make_key() {
+ (void)pthread_key_create(&gSkTLSKey, SkTLS::Destructor);
+}
+
+void* SkTLS::PlatformGetSpecific(bool forceCreateTheSlot) {
+ // should we use forceCreateTheSlot to potentially skip calling pthread_once
+ // and just return NULL if we've never been called with
+ // forceCreateTheSlot==true ?
+
+ (void)pthread_once(&gSkTLSKey_Once, sk_tls_make_key);
+ return pthread_getspecific(gSkTLSKey);
+}
+
+void SkTLS::PlatformSetSpecific(void* ptr) {
+ (void)pthread_setspecific(gSkTLSKey, ptr);
+}
diff --git a/ports/SkTLS_win.cpp b/ports/SkTLS_win.cpp
new file mode 100644
index 00000000..dea8cd4d
--- /dev/null
+++ b/ports/SkTLS_win.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTLS.h"
+#include "SkThread.h"
+
+static bool gOnce = false;
+static DWORD gTlsIndex;
+SK_DECLARE_STATIC_MUTEX(gMutex);
+
+void* SkTLS::PlatformGetSpecific(bool forceCreateTheSlot) {
+ if (!forceCreateTheSlot && !gOnce) {
+ return NULL;
+ }
+
+ if (!gOnce) {
+ SkAutoMutexAcquire tmp(gMutex);
+ if (!gOnce) {
+ gTlsIndex = TlsAlloc();
+ gOnce = true;
+ }
+ }
+ return TlsGetValue(gTlsIndex);
+}
+
+void SkTLS::PlatformSetSpecific(void* ptr) {
+ SkASSERT(gOnce);
+ (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/ports/SkThread_none.cpp b/ports/SkThread_none.cpp
new file mode 100644
index 00000000..638d7d01
--- /dev/null
+++ b/ports/SkThread_none.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "SkThread.h"
+
+int32_t sk_atomic_inc(int32_t* addr) {
+ int32_t value = *addr;
+ *addr = value + 1;
+ return value;
+}
+
+int32_t sk_atomic_add(int32_t* addr, int32_t inc) {
+ int32_t value = *addr;
+ *addr = value + inc;
+ return value;
+}
+
+int32_t sk_atomic_dec(int32_t* addr) {
+ int32_t value = *addr;
+ *addr = value - 1;
+ return value;
+}
+void sk_membar_aquire__after_atomic_dec() { }
+
+int32_t sk_atomic_conditional_inc(int32_t* addr) {
+ int32_t value = *addr;
+ if (value != 0) ++*addr;
+ return value;
+}
+void sk_membar_aquire__after_atomic_conditional_inc() { }
+
+SkMutex::SkMutex() {}
+
+SkMutex::~SkMutex() {}
+
+#ifndef SK_USE_POSIX_THREADS
+void SkMutex::acquire() {}
+void SkMutex::release() {}
+#endif
diff --git a/ports/SkThread_pthread.cpp b/ports/SkThread_pthread.cpp
new file mode 100644
index 00000000..a78c7b2f
--- /dev/null
+++ b/ports/SkThread_pthread.cpp
@@ -0,0 +1,197 @@
+
+/*
+ * 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 "SkThread.h"
+
+#include <pthread.h>
+#include <errno.h>
+
+#ifndef SK_BUILD_FOR_ANDROID
+
+/**
+ We prefer the GCC intrinsic implementation of the atomic operations over the
+ SkMutex-based implementation. The SkMutex version suffers from static
+ destructor ordering problems.
+ Note clang also defines the GCC version macros and implements the intrinsics.
+ TODO: Verify that gcc-style __sync_* intrinsics work on ARM
+ According to this the intrinsics are supported on ARM in LLVM 2.7+
+ http://llvm.org/releases/2.7/docs/ReleaseNotes.html
+*/
+#if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || __GNUC__ > 4
+ #if (defined(__x86_64) || defined(__i386__))
+ #define GCC_INTRINSIC
+ #endif
+#endif
+
+#if defined(GCC_INTRINSIC)
+
+int32_t sk_atomic_inc(int32_t* addr)
+{
+ return __sync_fetch_and_add(addr, 1);
+}
+
+int32_t sk_atomic_add(int32_t* addr, int32_t inc)
+{
+ return __sync_fetch_and_add(addr, inc);
+}
+
+int32_t sk_atomic_dec(int32_t* addr)
+{
+ return __sync_fetch_and_add(addr, -1);
+}
+void sk_membar_aquire__after_atomic_dec() { }
+
+int32_t sk_atomic_conditional_inc(int32_t* addr)
+{
+ int32_t value = *addr;
+
+ while (true) {
+ if (value == 0) {
+ return 0;
+ }
+
+ int32_t before = __sync_val_compare_and_swap(addr, value, value + 1);
+
+ if (before == value) {
+ return value;
+ } else {
+ value = before;
+ }
+ }
+}
+void sk_membar_aquire__after_atomic_conditional_inc() { }
+
+#else
+
+SkMutex gAtomicMutex;
+
+int32_t sk_atomic_inc(int32_t* addr)
+{
+ SkAutoMutexAcquire ac(gAtomicMutex);
+
+ int32_t value = *addr;
+ *addr = value + 1;
+ return value;
+}
+
+int32_t sk_atomic_add(int32_t* addr, int32_t inc)
+{
+ SkAutoMutexAcquire ac(gAtomicMutex);
+
+ int32_t value = *addr;
+ *addr = value + inc;
+ return value;
+}
+
+int32_t sk_atomic_dec(int32_t* addr)
+{
+ SkAutoMutexAcquire ac(gAtomicMutex);
+
+ int32_t value = *addr;
+ *addr = value - 1;
+ return value;
+}
+void sk_membar_aquire__after_atomic_dec() { }
+
+int32_t sk_atomic_conditional_inc(int32_t* addr)
+{
+ SkAutoMutexAcquire ac(gAtomicMutex);
+
+ int32_t value = *addr;
+ if (value != 0) ++*addr;
+ return value;
+}
+void sk_membar_aquire__after_atomic_conditional_inc() { }
+
+#endif
+
+#endif // SK_BUILD_FOR_ANDROID
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void print_pthread_error(int status) {
+ switch (status) {
+ case 0: // success
+ break;
+ case EINVAL:
+ SkDebugf("pthread error [%d] EINVAL\n", status);
+ break;
+ case EBUSY:
+ SkDebugf("pthread error [%d] EBUSY\n", status);
+ break;
+ default:
+ SkDebugf("pthread error [%d] unknown\n", status);
+ break;
+ }
+}
+
+#ifdef SK_USE_POSIX_THREADS
+
+SkMutex::SkMutex() {
+ int status;
+
+ status = pthread_mutex_init(&fMutex, NULL);
+ if (status != 0) {
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+ }
+}
+
+SkMutex::~SkMutex() {
+ int status = pthread_mutex_destroy(&fMutex);
+
+ // only report errors on non-global mutexes
+ if (status != 0) {
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+ }
+}
+
+#else // !SK_USE_POSIX_THREADS
+
+SkMutex::SkMutex() {
+ if (sizeof(pthread_mutex_t) > sizeof(fStorage)) {
+ SkDEBUGF(("pthread mutex size = %d\n", sizeof(pthread_mutex_t)));
+ SkDEBUGFAIL("mutex storage is too small");
+ }
+
+ int status;
+ pthread_mutexattr_t attr;
+
+ status = pthread_mutexattr_init(&attr);
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+
+ status = pthread_mutex_init((pthread_mutex_t*)fStorage, &attr);
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+}
+
+SkMutex::~SkMutex() {
+ int status = pthread_mutex_destroy((pthread_mutex_t*)fStorage);
+#if 0
+ // only report errors on non-global mutexes
+ if (!fIsGlobal) {
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+ }
+#endif
+}
+
+void SkMutex::acquire() {
+ int status = pthread_mutex_lock((pthread_mutex_t*)fStorage);
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+}
+
+void SkMutex::release() {
+ int status = pthread_mutex_unlock((pthread_mutex_t*)fStorage);
+ print_pthread_error(status);
+ SkASSERT(0 == status);
+}
+
+#endif // !SK_USE_POSIX_THREADS
diff --git a/ports/SkThread_win.cpp b/ports/SkThread_win.cpp
new file mode 100644
index 00000000..708db24a
--- /dev/null
+++ b/ports/SkThread_win.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#include <windows.h>
+#include <intrin.h>
+#include "SkThread.h"
+
+//MSDN says in order to declare an interlocked function for use as an
+//intrinsic, include intrin.h and put the function in a #pragma intrinsic
+//directive.
+//The pragma appears to be unnecessary, but doesn't hurt.
+#pragma intrinsic(_InterlockedIncrement, _InterlockedExchangeAdd, _InterlockedDecrement)
+#pragma intrinsic(_InterlockedCompareExchange)
+
+int32_t sk_atomic_inc(int32_t* addr) {
+ // InterlockedIncrement returns the new value, we want to return the old.
+ return _InterlockedIncrement(reinterpret_cast<LONG*>(addr)) - 1;
+}
+
+int32_t sk_atomic_add(int32_t* addr, int32_t inc) {
+ return _InterlockedExchangeAdd(reinterpret_cast<LONG*>(addr),
+ static_cast<LONG>(inc));
+}
+
+int32_t sk_atomic_dec(int32_t* addr) {
+ return _InterlockedDecrement(reinterpret_cast<LONG*>(addr)) + 1;
+}
+void sk_membar_aquire__after_atomic_dec() { }
+
+int32_t sk_atomic_conditional_inc(int32_t* addr) {
+ while (true) {
+ LONG value = static_cast<int32_t const volatile&>(*addr);
+ if (value == 0) {
+ return 0;
+ }
+ if (_InterlockedCompareExchange(reinterpret_cast<LONG*>(addr),
+ value + 1,
+ value) == value) {
+ return value;
+ }
+ }
+}
+void sk_membar_aquire__after_atomic_conditional_inc() { }
+
+SkMutex::SkMutex() {
+ SK_COMPILE_ASSERT(sizeof(fStorage) > sizeof(CRITICAL_SECTION),
+ NotEnoughSizeForCriticalSection);
+ InitializeCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(&fStorage));
+}
+
+SkMutex::~SkMutex() {
+ DeleteCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(&fStorage));
+}
+
+void SkMutex::acquire() {
+ EnterCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(&fStorage));
+}
+
+void SkMutex::release() {
+ LeaveCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(&fStorage));
+}
diff --git a/ports/SkTime_Unix.cpp b/ports/SkTime_Unix.cpp
new file mode 100644
index 00000000..f519a69d
--- /dev/null
+++ b/ports/SkTime_Unix.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * 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 "SkTime.h"
+
+#include <sys/time.h>
+#include <time.h>
+
+void SkTime::GetDateTime(DateTime* dt)
+{
+ if (dt)
+ {
+ time_t m_time;
+ time(&m_time);
+ struct tm* tstruct;
+ tstruct = localtime(&m_time);
+
+ dt->fYear = tstruct->tm_year;
+ dt->fMonth = SkToU8(tstruct->tm_mon + 1);
+ dt->fDayOfWeek = SkToU8(tstruct->tm_wday);
+ dt->fDay = SkToU8(tstruct->tm_mday);
+ dt->fHour = SkToU8(tstruct->tm_hour);
+ dt->fMinute = SkToU8(tstruct->tm_min);
+ dt->fSecond = SkToU8(tstruct->tm_sec);
+ }
+}
+
+SkMSec SkTime::GetMSecs()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (SkMSec) (tv.tv_sec * 1000 + tv.tv_usec / 1000 ); // microseconds to milliseconds
+}
diff --git a/ports/SkTime_win.cpp b/ports/SkTime_win.cpp
new file mode 100644
index 00000000..37af9f29
--- /dev/null
+++ b/ports/SkTime_win.cpp
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright 2009 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 "SkTime.h"
+
+void SkTime::GetDateTime(DateTime* dt)
+{
+ if (dt)
+ {
+ SYSTEMTIME st;
+ GetSystemTime(&st);
+
+ dt->fYear = st.wYear;
+ dt->fMonth = SkToU8(st.wMonth + 1);
+ dt->fDayOfWeek = SkToU8(st.wDayOfWeek);
+ dt->fDay = SkToU8(st.wDay);
+ dt->fHour = SkToU8(st.wHour);
+ dt->fMinute = SkToU8(st.wMinute);
+ dt->fSecond = SkToU8(st.wSecond);
+ }
+}
+
+SkMSec SkTime::GetMSecs()
+{
+ FILETIME ft;
+ LARGE_INTEGER li;
+ GetSystemTimeAsFileTime(&ft);
+ li.LowPart = ft.dwLowDateTime;
+ li.HighPart = ft.dwHighDateTime;
+ __int64 t = li.QuadPart; /* In 100-nanosecond intervals */
+ return (SkMSec)(t / 10000); /* In milliseconds */
+}
diff --git a/ports/SkXMLParser_empty.cpp b/ports/SkXMLParser_empty.cpp
new file mode 100644
index 00000000..09b222e8
--- /dev/null
+++ b/ports/SkXMLParser_empty.cpp
@@ -0,0 +1,23 @@
+/*
+ * 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 "SkXMLParser.h"
+
+bool SkXMLParser::parse(SkStream& docStream)
+{
+ return false;
+}
+
+bool SkXMLParser::parse(const char doc[], size_t len)
+{
+ return false;
+}
+
+void SkXMLParser::GetNativeErrorString(int error, SkString* str)
+{
+
+}
diff --git a/ports/SkXMLParser_expat.cpp b/ports/SkXMLParser_expat.cpp
new file mode 100644
index 00000000..3eba2fe8
--- /dev/null
+++ b/ports/SkXMLParser_expat.cpp
@@ -0,0 +1,140 @@
+
+/*
+ * 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 "SkXMLParser.h"
+#include "SkString.h"
+#include "SkStream.h"
+
+#include "expat.h"
+
+#ifdef SK_BUILD_FOR_PPI
+#define CHAR_16_TO_9
+#endif
+
+#if defined CHAR_16_TO_9
+inline size_t sk_wcslen(const short* char16) {
+ const short* start = char16;
+ while (*char16)
+ char16++;
+ return char16 - start;
+}
+
+inline const char* ConvertUnicodeToChar(const short* ch16, size_t len, SkAutoMalloc& ch8Malloc) {
+ char* ch8 = (char*) ch8Malloc.get();
+ int index;
+ for (index = 0; index < len; index++)
+ ch8[index] = (char) ch16[index];
+ ch8[index] = '\0';
+ return ch8;
+}
+#endif
+
+static void XMLCALL start_proc(void *data, const char *el, const char **attr)
+{
+#if defined CHAR_16_TO_9
+ size_t len = sk_wcslen((const short*) el);
+ SkAutoMalloc el8(len + 1);
+ el = ConvertUnicodeToChar((const short*) el, len, el8);
+#endif
+ if (((SkXMLParser*)data)->startElement(el)) {
+ XML_StopParser((XML_Parser) ((SkXMLParser*)data)->fParser, false);
+ return;
+ }
+ while (*attr)
+ {
+ const char* attr0 = attr[0];
+ const char* attr1 = attr[1];
+#if defined CHAR_16_TO_9
+ size_t len0 = sk_wcslen((const short*) attr0);
+ SkAutoMalloc attr0_8(len0 + 1);
+ attr0 = ConvertUnicodeToChar((const short*) attr0, len0, attr0_8);
+ size_t len1 = sk_wcslen((const short*) attr1);
+ SkAutoMalloc attr1_8(len1 + 1);
+ attr1 = ConvertUnicodeToChar((const short*) attr1, len1, attr1_8);
+#endif
+ if (((SkXMLParser*)data)->addAttribute(attr0, attr1)) {
+ XML_StopParser((XML_Parser) ((SkXMLParser*)data)->fParser, false);
+ return;
+ }
+ attr += 2;
+ }
+}
+
+static void XMLCALL end_proc(void *data, const char *el)
+{
+#if defined CHAR_16_TO_9
+ size_t len = sk_wcslen((const short*) el);
+ SkAutoMalloc el8(len + 1);
+ el = ConvertUnicodeToChar((const short*) el, len, el8);
+#endif
+ if (((SkXMLParser*)data)->endElement(el))
+ XML_StopParser((XML_Parser) ((SkXMLParser*)data)->fParser, false);
+}
+
+static void XMLCALL text_proc(void* data, const char* text, int len)
+{
+#if defined CHAR_16_TO_9
+ SkAutoMalloc text8(len + 1);
+ text = ConvertUnicodeToChar((const short*) text, len, text8);
+#endif
+ if (((SkXMLParser*)data)->text(text, len))
+ XML_StopParser((XML_Parser) ((SkXMLParser*)data)->fParser, false);
+}
+
+bool SkXMLParser::parse(const char doc[], size_t len)
+{
+ if (len == 0) {
+ fError->fCode = SkXMLParserError::kEmptyFile;
+ reportError(NULL);
+ return false;
+ }
+ XML_Parser p = XML_ParserCreate(NULL);
+ SkASSERT(p);
+ fParser = p;
+ XML_SetElementHandler(p, start_proc, end_proc);
+ XML_SetCharacterDataHandler(p, text_proc);
+ XML_SetUserData(p, this);
+
+ bool success = true;
+ int error = XML_Parse(p, doc, len, true);
+ if (error == XML_STATUS_ERROR) {
+ reportError(p);
+ success = false;
+ }
+ XML_ParserFree(p);
+ return success;
+}
+
+bool SkXMLParser::parse(SkStream& input)
+{
+ size_t len = input.getLength();
+ SkAutoMalloc am(len);
+ char* doc = (char*)am.get();
+
+ input.rewind();
+ size_t len2 = input.read(doc, len);
+ SkASSERT(len2 == len);
+
+ return this->parse(doc, len2);
+}
+
+void SkXMLParser::reportError(void* p)
+{
+ XML_Parser parser = (XML_Parser) p;
+ if (fError && parser) {
+ fError->fNativeCode = XML_GetErrorCode(parser);
+ fError->fLineNumber = XML_GetCurrentLineNumber(parser);
+ }
+}
+
+void SkXMLParser::GetNativeErrorString(int error, SkString* str)
+{
+ if (str)
+ str->set(XML_ErrorString((XML_Error) error));
+}
diff --git a/ports/SkXMLParser_tinyxml.cpp b/ports/SkXMLParser_tinyxml.cpp
new file mode 100644
index 00000000..5f9f3a68
--- /dev/null
+++ b/ports/SkXMLParser_tinyxml.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * 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 "SkXMLParser.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "tinyxml.h"
+
+static void walk_elem(SkXMLParser* parser, const TiXmlElement* elem)
+{
+ //printf("walk_elem(%s) ", elem->Value());
+
+ parser->startElement(elem->Value());
+
+ const TiXmlAttribute* attr = elem->FirstAttribute();
+ while (attr)
+ {
+ //printf("walk_elem_attr(%s=\"%s\") ", attr->Name(), attr->Value());
+
+ parser->addAttribute(attr->Name(), attr->Value());
+ attr = attr->Next();
+ }
+ //printf("\n");
+
+ const TiXmlNode* node = elem->FirstChild();
+ while (node)
+ {
+ if (node->ToElement())
+ walk_elem(parser, node->ToElement());
+ else if (node->ToText())
+ parser->text(node->Value(), strlen(node->Value()));
+ node = node->NextSibling();
+ }
+
+ parser->endElement(elem->Value());
+}
+
+static bool load_buf(SkXMLParser* parser, const char buf[])
+{
+ TiXmlDocument doc;
+
+ (void)doc.Parse(buf);
+ if (doc.Error())
+ {
+ printf("tinyxml error: <%s> row[%d] col[%d]\n", doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol());
+ return false;
+ }
+
+ walk_elem(parser, doc.RootElement());
+ return true;
+}
+
+bool SkXMLParser::parse(SkStream& stream)
+{
+ size_t size = stream.getLength();
+
+ SkAutoMalloc buffer(size + 1);
+ char* buf = (char*)buffer.get();
+
+ stream.read(buf, size);
+ buf[size] = 0;
+
+ return load_buf(this, buf);
+}
+
+bool SkXMLParser::parse(const char doc[], size_t len)
+{
+ SkAutoMalloc buffer(len + 1);
+ char* buf = (char*)buffer.get();
+
+ memcpy(buf, doc, len);
+ buf[len] = 0;
+
+ return load_buf(this, buf);
+}
+
+void SkXMLParser::GetNativeErrorString(int error, SkString* str)
+{
+ if (str)
+ str->set("GetNativeErrorString not implemented for TinyXml");
+}
diff --git a/ports/SkXMLPullParser_expat.cpp b/ports/SkXMLPullParser_expat.cpp
new file mode 100644
index 00000000..44a3c7f8
--- /dev/null
+++ b/ports/SkXMLPullParser_expat.cpp
@@ -0,0 +1,213 @@
+
+/*
+ * 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 "SkXMLParser.h"
+#include "SkChunkAlloc.h"
+#include "SkString.h"
+#include "SkStream.h"
+
+#include "expat.h"
+
+static inline char* dupstr(SkChunkAlloc& chunk, const char src[], size_t len)
+{
+ SkASSERT(src);
+ char* dst = (char*)chunk.alloc(len + 1, SkChunkAlloc::kThrow_AllocFailType);
+
+ memcpy(dst, src, len);
+ dst[len] = 0;
+ return dst;
+}
+
+static inline int count_pairs(const char** p)
+{
+ const char** start = p;
+ while (*p)
+ {
+ SkASSERT(p[1] != NULL);
+ p += 2;
+ }
+ return (p - start) >> 1;
+}
+
+struct Data {
+ Data() : fAlloc(2048), fState(NORMAL) {}
+
+ XML_Parser fParser;
+ SkXMLPullParser::Curr* fCurr;
+ SkChunkAlloc fAlloc;
+
+ enum State {
+ NORMAL,
+ MISSED_START_TAG,
+ RETURN_END_TAG
+ };
+ State fState;
+ const char* fEndTag; // if state is RETURN_END_TAG
+};
+
+static void XMLCALL start_proc(void *data, const char *el, const char **attr)
+{
+ Data* p = (Data*)data;
+ SkXMLPullParser::Curr* c = p->fCurr;
+ SkChunkAlloc& alloc = p->fAlloc;
+
+ c->fName = dupstr(alloc, el, strlen(el));
+
+ int n = count_pairs(attr);
+ SkXMLPullParser::AttrInfo* info = (SkXMLPullParser::AttrInfo*)alloc.alloc(n * sizeof(SkXMLPullParser::AttrInfo),
+ SkChunkAlloc::kThrow_AllocFailType);
+ c->fAttrInfoCount = n;
+ c->fAttrInfos = info;
+
+ for (int i = 0; i < n; i++)
+ {
+ info[i].fName = dupstr(alloc, attr[0], strlen(attr[0]));
+ info[i].fValue = dupstr(alloc, attr[1], strlen(attr[1]));
+ attr += 2;
+ }
+
+ c->fEventType = SkXMLPullParser::START_TAG;
+ XML_StopParser(p->fParser, true);
+}
+
+static void XMLCALL end_proc(void *data, const char *el)
+{
+ Data* p = (Data*)data;
+ SkXMLPullParser::Curr* c = p->fCurr;
+
+ if (c->fEventType == SkXMLPullParser::START_TAG)
+ {
+ /* if we get here, we were called with a start_tag immediately
+ followed by this end_tag. The caller will only see the end_tag,
+ so we set a flag to notify them of the missed start_tag
+ */
+ p->fState = Data::MISSED_START_TAG;
+
+ SkASSERT(c->fName != NULL);
+ SkASSERT(strcmp(c->fName, el) == 0);
+ }
+ else
+ c->fName = dupstr(p->fAlloc, el, strlen(el));
+
+ c->fEventType = SkXMLPullParser::END_TAG;
+ XML_StopParser(p->fParser, true);
+}
+
+#include <ctype.h>
+
+static bool isws(const char s[])
+{
+ for (; *s; s++)
+ if (!isspace(*s))
+ return false;
+ return true;
+}
+
+static void XMLCALL text_proc(void* data, const char* text, int len)
+{
+ Data* p = (Data*)data;
+ SkXMLPullParser::Curr* c = p->fCurr;
+
+ c->fName = dupstr(p->fAlloc, text, len);
+ c->fIsWhitespace = isws(c->fName);
+
+ c->fEventType = SkXMLPullParser::TEXT;
+ XML_StopParser(p->fParser, true);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+struct SkXMLPullParser::Impl {
+ Data fData;
+ void* fBuffer;
+ size_t fBufferLen;
+};
+
+static void reportError(XML_Parser parser)
+{
+ XML_Error code = XML_GetErrorCode(parser);
+ int lineNumber = XML_GetCurrentLineNumber(parser);
+ const char* msg = XML_ErrorString(code);
+
+ printf("-------- XML error [%d] on line %d, %s\n", code, lineNumber, msg);
+}
+
+bool SkXMLPullParser::onInit()
+{
+ fImpl = new Impl;
+
+ XML_Parser p = XML_ParserCreate(NULL);
+ SkASSERT(p);
+
+ fImpl->fData.fParser = p;
+ fImpl->fData.fCurr = &fCurr;
+
+ XML_SetElementHandler(p, start_proc, end_proc);
+ XML_SetCharacterDataHandler(p, text_proc);
+ XML_SetUserData(p, &fImpl->fData);
+
+ size_t len = fStream->getLength();
+ fImpl->fBufferLen = len;
+ fImpl->fBuffer = sk_malloc_throw(len);
+ fStream->rewind();
+ size_t len2 = fStream->read(fImpl->fBuffer, len);
+ return len2 == len;
+}
+
+void SkXMLPullParser::onExit()
+{
+ sk_free(fImpl->fBuffer);
+ XML_ParserFree(fImpl->fData.fParser);
+ delete fImpl;
+ fImpl = NULL;
+}
+
+SkXMLPullParser::EventType SkXMLPullParser::onNextToken()
+{
+ if (Data::RETURN_END_TAG == fImpl->fData.fState)
+ {
+ fImpl->fData.fState = Data::NORMAL;
+ fCurr.fName = fImpl->fData.fEndTag; // restore name from (below) save
+ return SkXMLPullParser::END_TAG;
+ }
+
+ fImpl->fData.fAlloc.reset();
+
+ XML_Parser p = fImpl->fData.fParser;
+ XML_Status status;
+
+ status = XML_ResumeParser(p);
+
+CHECK_STATUS:
+ switch (status) {
+ case XML_STATUS_OK:
+ return SkXMLPullParser::END_DOCUMENT;
+
+ case XML_STATUS_ERROR:
+ if (XML_GetErrorCode(p) != XML_ERROR_NOT_SUSPENDED)
+ {
+ reportError(p);
+ return SkXMLPullParser::ERROR;
+ }
+ status = XML_Parse(p, (const char*)fImpl->fBuffer, fImpl->fBufferLen, true);
+ goto CHECK_STATUS;
+
+ case XML_STATUS_SUSPENDED:
+ if (Data::MISSED_START_TAG == fImpl->fData.fState)
+ {
+ // return a start_tag, and clear the flag so we return end_tag next
+ SkASSERT(SkXMLPullParser::END_TAG == fCurr.fEventType);
+ fImpl->fData.fState = Data::RETURN_END_TAG;
+ fImpl->fData.fEndTag = fCurr.fName; // save this pointer
+ return SkXMLPullParser::START_TAG;
+ }
+ break;
+ }
+ return fCurr.fEventType;
+}
diff --git a/sfnt/SkIBMFamilyClass.h b/sfnt/SkIBMFamilyClass.h
new file mode 100644
index 00000000..45d9822f
--- /dev/null
+++ b/sfnt/SkIBMFamilyClass.h
@@ -0,0 +1,174 @@
+/*
+ * 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 SkIBMFamilyClass_DEFINED
+#define SkIBMFamilyClass_DEFINED
+
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkIBMFamilyClass {
+ SK_TYPED_ENUM(Class, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((OldstyleSerifs, 1))
+ ((TransitionalSerifs, 2))
+ ((ModernSerifs, 3))
+ ((ClarendonSerifs, 4))
+ ((SlabSerifs, 5))
+ //6 reserved for future use
+ ((FreeformSerifs, 7))
+ ((SansSerif, 8))
+ ((Ornamentals, 9))
+ ((Scripts, 10))
+ //11 reserved for future use
+ ((Symbolic, 12))
+ //13-15 reserved for future use
+ SK_SEQ_END,
+ (familyClass)SK_SEQ_END)
+ union SubClass {
+ struct OldstyleSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((IBMRoundedLegibility, 1))
+ ((Garalde, 2))
+ ((Venetian, 3))
+ ((ModifiedVenetian, 4))
+ ((DutchModern, 5))
+ ((DutchTraditional, 6))
+ ((Contemporary, 7))
+ ((Calligraphic, 8))
+ //9-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } oldstyleSerifs;
+ struct TransitionalSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((DirectLine, 1))
+ ((Script, 2))
+ //3-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } transitionalSerifs;
+ struct ModernSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Italian, 1))
+ ((Script, 2))
+ //3-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } modernSerifs;
+ struct ClarendonSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Clarendon, 1))
+ ((Modern, 2))
+ ((Traditional, 3))
+ ((Newspaper, 4))
+ ((StubSerif, 5))
+ ((Monotone, 6))
+ ((Typewriter, 7))
+ //8-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } clarendonSerifs;
+ struct SlabSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Monotone, 1))
+ ((Humanist, 2))
+ ((Geometric, 3))
+ ((Swiss, 4))
+ ((Typewriter, 5))
+ //6-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } slabSerifs;
+ struct FreeformSerifs {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Modern, 1))
+ //2-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } freeformSerifs;
+ struct SansSerif {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((IBMNeoGrotesqueGothic, 1))
+ ((Humanist, 2))
+ ((LowXRoundGeometric, 3))
+ ((HighXRoundGeometric, 4))
+ ((NeoGrotesqueGothic, 5))
+ ((ModifiedNeoGrotesqueGothic, 6))
+ //7-8 reserved for future use
+ ((TypewriterGothic, 9))
+ ((Matrix, 10))
+ //11-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } sansSerif;
+ struct Ornamentals {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Engraver, 1))
+ ((BlackLetter, 2))
+ ((Decorative, 3))
+ ((ThreeDimensional, 4))
+ //5-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } ornamentals;
+ struct Scripts {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ ((Uncial, 1))
+ ((Brush_Joined, 2))
+ ((Formal_Joined, 3))
+ ((Monotone_Joined, 4))
+ ((Calligraphic, 5))
+ ((Brush_Unjoined, 6))
+ ((Formal_Unjoined, 7))
+ ((Monotone_Unjoined, 8))
+ //9-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } scripts;
+ struct Symbolic {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoClassification, 0))
+ //1-2 reserved for future use
+ ((MixedSerif, 3))
+ //4-5 reserved for future use
+ ((OldstyleSerif, 6))
+ ((NeoGrotesqueSansSerif, 7))
+ //8-14 reserved for future use
+ ((Miscellaneous, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } symbolic;
+ } familySubClass;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkIBMFamilyClass) == 2, sizeof_SkIBMFamilyClass_not_2);
+
+#endif
diff --git a/sfnt/SkOTTableTypes.h b/sfnt/SkOTTableTypes.h
new file mode 100644
index 00000000..9adec9b8
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2.h b/sfnt/SkOTTable_OS_2.h
new file mode 100644
index 00000000..c4dbc533
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_V0.h b/sfnt/SkOTTable_OS_2_V0.h
new file mode 100644
index 00000000..14d322f7
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_V1.h b/sfnt/SkOTTable_OS_2_V1.h
new file mode 100644
index 00000000..52a60c0a
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_V2.h b/sfnt/SkOTTable_OS_2_V2.h
new file mode 100644
index 00000000..4be2e199
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_V3.h b/sfnt/SkOTTable_OS_2_V3.h
new file mode 100644
index 00000000..637eb378
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_V4.h b/sfnt/SkOTTable_OS_2_V4.h
new file mode 100644
index 00000000..fc6ed5da
--- /dev/null
+++ b/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/sfnt/SkOTTable_OS_2_VA.h b/sfnt/SkOTTable_OS_2_VA.h
new file mode 100644
index 00000000..146e83b6
--- /dev/null
+++ b/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/sfnt/SkOTTable_glyf.h b/sfnt/SkOTTable_glyf.h
new file mode 100644
index 00000000..ac34d7b8
--- /dev/null
+++ b/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)
+ : uint32_t(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/sfnt/SkOTTable_head.h b/sfnt/SkOTTable_head.h
new file mode 100644
index 00000000..3d2c0f11
--- /dev/null
+++ b/sfnt/SkOTTable_head.h
@@ -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.
+ */
+
+#ifndef SkOTTable_head_DEFINED
+#define SkOTTable_head_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableHead {
+ static const SK_OT_CHAR TAG0 = 'h';
+ static const SK_OT_CHAR TAG1 = 'e';
+ static const SK_OT_CHAR TAG2 = 'a';
+ static const SK_OT_CHAR TAG3 = 'd';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableHead>::value;
+
+ SK_OT_Fixed version;
+ static const SK_OT_Fixed version1 = SkTEndian_SwapBE32(0x00010000);
+ SK_OT_Fixed fontRevision;
+ static const uint32_t fontChecksum = 0xB1B0AFBA; //checksum of all TT fonts
+ SK_OT_ULONG checksumAdjustment;
+ SK_OT_ULONG magicNumber;
+ static const SK_OT_ULONG magicNumberConst = SkTEndian_SwapBE32(0x5F0F3CF5);
+ union Flags {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ GXMetamorphosis_Apple,
+ HasStrongRTL_Apple,
+ HasIndicStyleRearrangement,
+ AgfaMicroTypeExpressProcessed,
+ FontConverted,
+ DesignedForClearType,
+ LastResort,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ BaselineAtY0,
+ LeftSidebearingAtX0,
+ InstructionsDependOnPointSize,
+ IntegerScaling,
+ InstructionsAlterAdvanceWidth,
+ VerticalCenteredGlyphs_Apple,
+ Reserved06,
+ RequiresLayout_Apple)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT BaselineAtY0Mask = SkTEndian_SwapBE16(1 << 0);
+ static const SK_OT_USHORT LeftSidebearingAtX0Mask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT InstructionsDependOnPointSizeMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT IntegerScalingMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT InstructionsAlterAdvanceWidthMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT VerticalCenteredGlyphs_AppleMask = SkTEndian_SwapBE16(1 << 5);
+ //Reserved
+ static const SK_OT_USHORT RequiresLayout_AppleMask = SkTEndian_SwapBE16(1 << 7);
+
+ static const SK_OT_USHORT GXMetamorphosis_AppleMask = SkTEndian_SwapBE16(1 << 8);
+ static const SK_OT_USHORT HasStrongRTL_AppleMask = SkTEndian_SwapBE16(1 << 9);
+ static const SK_OT_USHORT HasIndicStyleRearrangementMask = SkTEndian_SwapBE16(1 << 10);
+ static const SK_OT_USHORT AgfaMicroTypeExpressProcessedMask = SkTEndian_SwapBE16(1 << 11);
+ static const SK_OT_USHORT FontConvertedMask = SkTEndian_SwapBE16(1 << 12);
+ static const SK_OT_USHORT DesignedForClearTypeMask = SkTEndian_SwapBE16(1 << 13);
+ static const SK_OT_USHORT LastResortMask = SkTEndian_SwapBE16(1 << 14);
+ //Reserved
+ SK_OT_USHORT value;
+ } raw;
+ } flags;
+ SK_OT_USHORT unitsPerEm;
+ SK_OT_LONGDATETIME created;
+ SK_OT_LONGDATETIME modified;
+ SK_OT_SHORT xMin;
+ SK_OT_SHORT yMin;
+ SK_OT_SHORT xMax;
+ SK_OT_SHORT yMax;
+ union MacStyle {
+ struct Field {
+ //8-15
+ SK_OT_BYTE_BITFIELD(
+ Reserved08,
+ Reserved09,
+ Reserved10,
+ Reserved11,
+ Reserved12,
+ Reserved13,
+ Reserved14,
+ Reserved15)
+ //0-7
+ SK_OT_BYTE_BITFIELD(
+ Bold,
+ Italic,
+ Underline,
+ Outline,
+ Shadow,
+ Condensed,
+ Extended,
+ Reserved07)
+ } field;
+ struct Raw {
+ static const SK_OT_USHORT BoldMask = SkTEndian_SwapBE16(1);
+ static const SK_OT_USHORT ItalicMask = SkTEndian_SwapBE16(1 << 1);
+ static const SK_OT_USHORT UnderlineMask = SkTEndian_SwapBE16(1 << 2);
+ static const SK_OT_USHORT OutlineMask = SkTEndian_SwapBE16(1 << 3);
+ static const SK_OT_USHORT ShadowMask = SkTEndian_SwapBE16(1 << 4);
+ static const SK_OT_USHORT CondensedMask = SkTEndian_SwapBE16(1 << 5);
+ static const SK_OT_USHORT ExtendedMask = SkTEndian_SwapBE16(1 << 6);
+
+ SK_OT_USHORT value;
+ } raw;
+ } macStyle;
+ SK_OT_USHORT lowestRecPPEM;
+ struct FontDirectionHint {
+ SK_TYPED_ENUM(Value, SK_OT_SHORT,
+ ((FullyMixedDirectionalGlyphs, SkTEndian_SwapBE16(0)))
+ ((OnlyStronglyLTR, SkTEndian_SwapBE16(1)))
+ ((StronglyLTR, SkTEndian_SwapBE16(2)))
+ ((OnlyStronglyRTL, static_cast<SK_OT_SHORT>(SkTEndian_SwapBE16((uint16_t)-1))))
+ ((StronglyRTL, static_cast<SK_OT_SHORT>(SkTEndian_SwapBE16((uint16_t)-2))))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } fontDirectionHint;
+ struct IndexToLocFormat {
+ SK_TYPED_ENUM(Value, SK_OT_SHORT,
+ ((ShortOffsets, SkTEndian_SwapBE16(0)))
+ ((LongOffsets, SkTEndian_SwapBE16(1)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } indexToLocFormat;
+ struct GlyphDataFormat {
+ SK_TYPED_ENUM(Value, SK_OT_SHORT,
+ ((CurrentFormat, SkTEndian_SwapBE16(0)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } glyphDataFormat;
+};
+
+#pragma pack(pop)
+
+
+#include <stddef.h>
+SK_COMPILE_ASSERT(offsetof(SkOTTableHead, glyphDataFormat) == 52, SkOTTableHead_glyphDataFormat_not_at_52);
+SK_COMPILE_ASSERT(sizeof(SkOTTableHead) == 54, sizeof_SkOTTableHead_not_54);
+
+#endif
diff --git a/sfnt/SkOTTable_hhea.h b/sfnt/SkOTTable_hhea.h
new file mode 100644
index 00000000..b8a20701
--- /dev/null
+++ b/sfnt/SkOTTable_hhea.h
@@ -0,0 +1,56 @@
+/*
+ * 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_hhea_DEFINED
+#define SkOTTable_hhea_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableHorizontalHeader {
+ static const SK_OT_CHAR TAG0 = 'h';
+ static const SK_OT_CHAR TAG1 = 'h';
+ static const SK_OT_CHAR TAG2 = 'e';
+ static const SK_OT_CHAR TAG3 = 'a';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableHorizontalHeader>::value;
+
+ SK_OT_Fixed version;
+ static const SK_OT_Fixed version1 = SkTEndian_SwapBE32(0x00010000);
+ SK_OT_FWORD Ascender;
+ SK_OT_FWORD Descender;
+ SK_OT_FWORD LineGap;
+ SK_OT_UFWORD advanceWidthMax;
+ SK_OT_FWORD minLeftSideBearing;
+ SK_OT_FWORD minRightSideBearing;
+ SK_OT_FWORD xMaxExtent;
+ SK_OT_SHORT caretSlopeRise;
+ SK_OT_SHORT caretSlopeRun;
+ SK_OT_SHORT caretOffset;
+ SK_OT_SHORT Reserved24;
+ SK_OT_SHORT Reserved26;
+ SK_OT_SHORT Reserved28;
+ SK_OT_SHORT Reserved30;
+ struct MetricDataFormat {
+ SK_TYPED_ENUM(Value, SK_OT_SHORT,
+ ((CurrentFormat, SkTEndian_SwapBE16(0)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } metricDataFormat;
+ SK_OT_USHORT numberOfHMetrics;
+};
+
+#pragma pack(pop)
+
+
+#include <stddef.h>
+SK_COMPILE_ASSERT(offsetof(SkOTTableHorizontalHeader, numberOfHMetrics) == 34, SkOTTableHorizontalHeader_numberOfHMetrics_not_at_34);
+SK_COMPILE_ASSERT(sizeof(SkOTTableHorizontalHeader) == 36, sizeof_SkOTTableHorizontalHeader_not_36);
+
+#endif
diff --git a/sfnt/SkOTTable_loca.h b/sfnt/SkOTTable_loca.h
new file mode 100644
index 00000000..586daf1d
--- /dev/null
+++ b/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/sfnt/SkOTTable_maxp.h b/sfnt/SkOTTable_maxp.h
new file mode 100644
index 00000000..d7feac69
--- /dev/null
+++ b/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/sfnt/SkOTTable_maxp_CFF.h b/sfnt/SkOTTable_maxp_CFF.h
new file mode 100644
index 00000000..873fb66c
--- /dev/null
+++ b/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/sfnt/SkOTTable_maxp_TT.h b/sfnt/SkOTTable_maxp_TT.h
new file mode 100644
index 00000000..ad472a1f
--- /dev/null
+++ b/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/sfnt/SkOTTable_name.cpp b/sfnt/SkOTTable_name.cpp
new file mode 100644
index 00000000..b536c0a1
--- /dev/null
+++ b/sfnt/SkOTTable_name.cpp
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOTTable_name.h"
+
+#include "SkEndian.h"
+#include "SkString.h"
+#include "SkTSearch.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+static SkUnichar SkUTF16BE_NextUnichar(const uint16_t** srcPtr) {
+ SkASSERT(srcPtr && *srcPtr);
+
+ const uint16_t* src = *srcPtr;
+ SkUnichar c = SkEndian_SwapBE16(*src++);
+
+ SkASSERT(!SkUTF16_IsLowSurrogate(c));
+ if (SkUTF16_IsHighSurrogate(c)) {
+ unsigned c2 = SkEndian_SwapBE16(*src++);
+ SkASSERT(SkUTF16_IsLowSurrogate(c2));
+
+ c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00);
+ }
+ *srcPtr = src;
+ return c;
+}
+
+static void SkStringFromUTF16BE(const uint16_t* utf16be, size_t length, SkString& utf8) {
+ SkASSERT(utf16be != NULL);
+
+ utf8.reset();
+ size_t numberOf16BitValues = length / 2;
+ const uint16_t* end = utf16be + numberOf16BitValues;
+ while (utf16be < end) {
+ utf8.appendUnichar(SkUTF16BE_NextUnichar(&utf16be));
+ }
+}
+
+/** UnicodeFromMacRoman[macRomanPoint - 0x80] -> unicodeCodePoint.
+ * Derived from http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT .
+ * In MacRoman the first 128 code points match ASCII code points.
+ * This maps the second 128 MacRoman code points to unicode code points.
+ */
+static uint16_t UnicodeFromMacRoman[0x80] = {
+ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
+ 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
+ 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
+ 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
+ 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
+ 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
+ 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
+ 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
+ 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
+ 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
+ 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
+ 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
+ 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
+ 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
+ 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
+ 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
+};
+
+static void SkStringFromMacRoman(const uint8_t* macRoman, size_t length, SkString& utf8) {
+ utf8.reset();
+ for (size_t i = 0; i < length; ++i) {
+ utf8.appendUnichar(macRoman[i] < 0x80 ? macRoman[i] : UnicodeFromMacRoman[macRoman[i]]);
+ }
+}
+
+static struct BCP47FromLanguageId {
+ uint16_t languageID;
+ const char* bcp47;
+}
+/** The Mac and Windows values do not conflict, so this is currently one single table. */
+BCP47FromLanguageID[] = {
+ /** A mapping from Mac Language Designators to BCP 47 codes.
+ * The following list was constructed more or less manually.
+ * Apple now uses BCP 47 (post OSX10.4), so there will be no new entries.
+ */
+ {0, "en"}, //English
+ {1, "fr"}, //French
+ {2, "de"}, //German
+ {3, "it"}, //Italian
+ {4, "nl"}, //Dutch
+ {5, "sv"}, //Swedish
+ {6, "es"}, //Spanish
+ {7, "da"}, //Danish
+ {8, "pt"}, //Portuguese
+ {9, "nb"}, //Norwegian
+ {10, "he"}, //Hebrew
+ {11, "ja"}, //Japanese
+ {12, "ar"}, //Arabic
+ {13, "fi"}, //Finnish
+ {14, "el"}, //Greek
+ {15, "is"}, //Icelandic
+ {16, "mt"}, //Maltese
+ {17, "tr"}, //Turkish
+ {18, "hr"}, //Croatian
+ {19, "zh-Hant"}, //Chinese (Traditional)
+ {20, "ur"}, //Urdu
+ {21, "hi"}, //Hindi
+ {22, "th"}, //Thai
+ {23, "ko"}, //Korean
+ {24, "lt"}, //Lithuanian
+ {25, "pl"}, //Polish
+ {26, "hu"}, //Hungarian
+ {27, "et"}, //Estonian
+ {28, "lv"}, //Latvian
+ {29, "se"}, //Sami
+ {30, "fo"}, //Faroese
+ {31, "fa"}, //Farsi (Persian)
+ {32, "ru"}, //Russian
+ {33, "zh-Hans"}, //Chinese (Simplified)
+ {34, "nl"}, //Dutch
+ {35, "ga"}, //Irish(Gaelic)
+ {36, "sq"}, //Albanian
+ {37, "ro"}, //Romanian
+ {38, "cs"}, //Czech
+ {39, "sk"}, //Slovak
+ {40, "sl"}, //Slovenian
+ {41, "yi"}, //Yiddish
+ {42, "sr"}, //Serbian
+ {43, "mk"}, //Macedonian
+ {44, "bg"}, //Bulgarian
+ {45, "uk"}, //Ukrainian
+ {46, "be"}, //Byelorussian
+ {47, "uz"}, //Uzbek
+ {48, "kk"}, //Kazakh
+ {49, "az-Cyrl"}, //Azerbaijani (Cyrillic)
+ {50, "az-Arab"}, //Azerbaijani (Arabic)
+ {51, "hy"}, //Armenian
+ {52, "ka"}, //Georgian
+ {53, "mo"}, //Moldavian
+ {54, "ky"}, //Kirghiz
+ {55, "tg"}, //Tajiki
+ {56, "tk"}, //Turkmen
+ {57, "mn-Mong"}, //Mongolian (Traditional)
+ {58, "mn-Cyrl"}, //Mongolian (Cyrillic)
+ {59, "ps"}, //Pashto
+ {60, "ku"}, //Kurdish
+ {61, "ks"}, //Kashmiri
+ {62, "sd"}, //Sindhi
+ {63, "bo"}, //Tibetan
+ {64, "ne"}, //Nepali
+ {65, "sa"}, //Sanskrit
+ {66, "mr"}, //Marathi
+ {67, "bn"}, //Bengali
+ {68, "as"}, //Assamese
+ {69, "gu"}, //Gujarati
+ {70, "pa"}, //Punjabi
+ {71, "or"}, //Oriya
+ {72, "ml"}, //Malayalam
+ {73, "kn"}, //Kannada
+ {74, "ta"}, //Tamil
+ {75, "te"}, //Telugu
+ {76, "si"}, //Sinhalese
+ {77, "my"}, //Burmese
+ {78, "km"}, //Khmer
+ {79, "lo"}, //Lao
+ {80, "vi"}, //Vietnamese
+ {81, "id"}, //Indonesian
+ {82, "tl"}, //Tagalog
+ {83, "ms-Latn"}, //Malay (Roman)
+ {84, "ms-Arab"}, //Malay (Arabic)
+ {85, "am"}, //Amharic
+ {86, "ti"}, //Tigrinya
+ {87, "om"}, //Oromo
+ {88, "so"}, //Somali
+ {89, "sw"}, //Swahili
+ {90, "rw"}, //Kinyarwanda/Ruanda
+ {91, "rn"}, //Rundi
+ {92, "ny"}, //Nyanja/Chewa
+ {93, "mg"}, //Malagasy
+ {94, "eo"}, //Esperanto
+ {128, "cy"}, //Welsh
+ {129, "eu"}, //Basque
+ {130, "ca"}, //Catalan
+ {131, "la"}, //Latin
+ {132, "qu"}, //Quechua
+ {133, "gn"}, //Guarani
+ {134, "ay"}, //Aymara
+ {135, "tt"}, //Tatar
+ {136, "ug"}, //Uighur
+ {137, "dz"}, //Dzongkha
+ {138, "jv-Latn"}, //Javanese (Roman)
+ {139, "su-Latn"}, //Sundanese (Roman)
+ {140, "gl"}, //Galician
+ {141, "af"}, //Afrikaans
+ {142, "br"}, //Breton
+ {143, "iu"}, //Inuktitut
+ {144, "gd"}, //Scottish (Gaelic)
+ {145, "gv"}, //Manx (Gaelic)
+ {146, "ga"}, //Irish (Gaelic with Lenition)
+ {147, "to"}, //Tongan
+ {148, "el"}, //Greek (Polytonic) Note: ISO 15924 does not have an equivalent script name.
+ {149, "kl"}, //Greenlandic
+ {150, "az-Latn"}, //Azerbaijani (Roman)
+ {151, "nn"}, //Nynorsk
+
+ /** A mapping from Windows LCID to BCP 47 codes.
+ * This list is the sorted, curated output of tools/win_lcid.cpp.
+ * Note that these are sorted by value for quick binary lookup, and not logically by lsb.
+ * The 'bare' language ids (e.g. 0x0001 for Arabic) are ommitted
+ * as they do not appear as valid language ids in the OpenType specification.
+ */
+ { 0x0401, "ar-SA" }, //Arabic
+ { 0x0402, "bg-BG" }, //Bulgarian
+ { 0x0403, "ca-ES" }, //Catalan
+ { 0x0404, "zh-TW" }, //Chinese (Traditional)
+ { 0x0405, "cs-CZ" }, //Czech
+ { 0x0406, "da-DK" }, //Danish
+ { 0x0407, "de-DE" }, //German
+ { 0x0408, "el-GR" }, //Greek
+ { 0x0409, "en-US" }, //English
+ { 0x040a, "es-ES_tradnl" }, //Spanish
+ { 0x040b, "fi-FI" }, //Finnish
+ { 0x040c, "fr-FR" }, //French
+ { 0x040d, "he-IL" }, //Hebrew
+ { 0x040d, "he" }, //Hebrew
+ { 0x040e, "hu-HU" }, //Hungarian
+ { 0x040e, "hu" }, //Hungarian
+ { 0x040f, "is-IS" }, //Icelandic
+ { 0x0410, "it-IT" }, //Italian
+ { 0x0411, "ja-JP" }, //Japanese
+ { 0x0412, "ko-KR" }, //Korean
+ { 0x0413, "nl-NL" }, //Dutch
+ { 0x0414, "nb-NO" }, //Norwegian (Bokmål)
+ { 0x0415, "pl-PL" }, //Polish
+ { 0x0416, "pt-BR" }, //Portuguese
+ { 0x0417, "rm-CH" }, //Romansh
+ { 0x0418, "ro-RO" }, //Romanian
+ { 0x0419, "ru-RU" }, //Russian
+ { 0x041a, "hr-HR" }, //Croatian
+ { 0x041b, "sk-SK" }, //Slovak
+ { 0x041c, "sq-AL" }, //Albanian
+ { 0x041d, "sv-SE" }, //Swedish
+ { 0x041e, "th-TH" }, //Thai
+ { 0x041f, "tr-TR" }, //Turkish
+ { 0x0420, "ur-PK" }, //Urdu
+ { 0x0421, "id-ID" }, //Indonesian
+ { 0x0422, "uk-UA" }, //Ukrainian
+ { 0x0423, "be-BY" }, //Belarusian
+ { 0x0424, "sl-SI" }, //Slovenian
+ { 0x0425, "et-EE" }, //Estonian
+ { 0x0426, "lv-LV" }, //Latvian
+ { 0x0427, "lt-LT" }, //Lithuanian
+ { 0x0428, "tg-Cyrl-TJ" }, //Tajik (Cyrillic)
+ { 0x0429, "fa-IR" }, //Persian
+ { 0x042a, "vi-VN" }, //Vietnamese
+ { 0x042b, "hy-AM" }, //Armenian
+ { 0x042c, "az-Latn-AZ" }, //Azeri (Latin)
+ { 0x042d, "eu-ES" }, //Basque
+ { 0x042e, "hsb-DE" }, //Upper Sorbian
+ { 0x042f, "mk-MK" }, //Macedonian (FYROM)
+ { 0x0432, "tn-ZA" }, //Setswana
+ { 0x0434, "xh-ZA" }, //isiXhosa
+ { 0x0435, "zu-ZA" }, //isiZulu
+ { 0x0436, "af-ZA" }, //Afrikaans
+ { 0x0437, "ka-GE" }, //Georgian
+ { 0x0438, "fo-FO" }, //Faroese
+ { 0x0439, "hi-IN" }, //Hindi
+ { 0x043a, "mt-MT" }, //Maltese
+ { 0x043b, "se-NO" }, //Sami (Northern)
+ { 0x043e, "ms-MY" }, //Malay
+ { 0x043f, "kk-KZ" }, //Kazakh
+ { 0x0440, "ky-KG" }, //Kyrgyz
+ { 0x0441, "sw-KE" }, //Kiswahili
+ { 0x0442, "tk-TM" }, //Turkmen
+ { 0x0443, "uz-Latn-UZ" }, //Uzbek (Latin)
+ { 0x0443, "uz" }, //Uzbek
+ { 0x0444, "tt-RU" }, //Tatar
+ { 0x0445, "bn-IN" }, //Bengali
+ { 0x0446, "pa-IN" }, //Punjabi
+ { 0x0447, "gu-IN" }, //Gujarati
+ { 0x0448, "or-IN" }, //Oriya
+ { 0x0449, "ta-IN" }, //Tamil
+ { 0x044a, "te-IN" }, //Telugu
+ { 0x044b, "kn-IN" }, //Kannada
+ { 0x044c, "ml-IN" }, //Malayalam
+ { 0x044d, "as-IN" }, //Assamese
+ { 0x044e, "mr-IN" }, //Marathi
+ { 0x044f, "sa-IN" }, //Sanskrit
+ { 0x0450, "mn-Cyrl" }, //Mongolian (Cyrillic)
+ { 0x0451, "bo-CN" }, //Tibetan
+ { 0x0452, "cy-GB" }, //Welsh
+ { 0x0453, "km-KH" }, //Khmer
+ { 0x0454, "lo-LA" }, //Lao
+ { 0x0456, "gl-ES" }, //Galician
+ { 0x0457, "kok-IN" }, //Konkani
+ { 0x045a, "syr-SY" }, //Syriac
+ { 0x045b, "si-LK" }, //Sinhala
+ { 0x045d, "iu-Cans-CA" }, //Inuktitut (Syllabics)
+ { 0x045e, "am-ET" }, //Amharic
+ { 0x0461, "ne-NP" }, //Nepali
+ { 0x0462, "fy-NL" }, //Frisian
+ { 0x0463, "ps-AF" }, //Pashto
+ { 0x0464, "fil-PH" }, //Filipino
+ { 0x0465, "dv-MV" }, //Divehi
+ { 0x0468, "ha-Latn-NG" }, //Hausa (Latin)
+ { 0x046a, "yo-NG" }, //Yoruba
+ { 0x046b, "quz-BO" }, //Quechua
+ { 0x046c, "nso-ZA" }, //Sesotho sa Leboa
+ { 0x046d, "ba-RU" }, //Bashkir
+ { 0x046e, "lb-LU" }, //Luxembourgish
+ { 0x046f, "kl-GL" }, //Greenlandic
+ { 0x0470, "ig-NG" }, //Igbo
+ { 0x0478, "ii-CN" }, //Yi
+ { 0x047a, "arn-CL" }, //Mapudungun
+ { 0x047c, "moh-CA" }, //Mohawk
+ { 0x047e, "br-FR" }, //Breton
+ { 0x0480, "ug-CN" }, //Uyghur
+ { 0x0481, "mi-NZ" }, //Maori
+ { 0x0482, "oc-FR" }, //Occitan
+ { 0x0483, "co-FR" }, //Corsican
+ { 0x0484, "gsw-FR" }, //Alsatian
+ { 0x0485, "sah-RU" }, //Yakut
+ { 0x0486, "qut-GT" }, //K'iche
+ { 0x0487, "rw-RW" }, //Kinyarwanda
+ { 0x0488, "wo-SN" }, //Wolof
+ { 0x048c, "prs-AF" }, //Dari
+ { 0x0491, "gd-GB" }, //Scottish Gaelic
+ { 0x0801, "ar-IQ" }, //Arabic
+ { 0x0804, "zh-Hans" }, //Chinese (Simplified)
+ { 0x0807, "de-CH" }, //German
+ { 0x0809, "en-GB" }, //English
+ { 0x080a, "es-MX" }, //Spanish
+ { 0x080c, "fr-BE" }, //French
+ { 0x0810, "it-CH" }, //Italian
+ { 0x0813, "nl-BE" }, //Dutch
+ { 0x0814, "nn-NO" }, //Norwegian (Nynorsk)
+ { 0x0816, "pt-PT" }, //Portuguese
+ { 0x081a, "sr-Latn-CS" }, //Serbian (Latin)
+ { 0x081d, "sv-FI" }, //Swedish
+ { 0x082c, "az-Cyrl-AZ" }, //Azeri (Cyrillic)
+ { 0x082e, "dsb-DE" }, //Lower Sorbian
+ { 0x082e, "dsb" }, //Lower Sorbian
+ { 0x083b, "se-SE" }, //Sami (Northern)
+ { 0x083c, "ga-IE" }, //Irish
+ { 0x083e, "ms-BN" }, //Malay
+ { 0x0843, "uz-Cyrl-UZ" }, //Uzbek (Cyrillic)
+ { 0x0845, "bn-BD" }, //Bengali
+ { 0x0850, "mn-Mong-CN" }, //Mongolian (Traditional Mongolian)
+ { 0x085d, "iu-Latn-CA" }, //Inuktitut (Latin)
+ { 0x085f, "tzm-Latn-DZ" }, //Tamazight (Latin)
+ { 0x086b, "quz-EC" }, //Quechua
+ { 0x0c01, "ar-EG" }, //Arabic
+ { 0x0c04, "zh-Hant" }, //Chinese (Traditional)
+ { 0x0c07, "de-AT" }, //German
+ { 0x0c09, "en-AU" }, //English
+ { 0x0c0a, "es-ES" }, //Spanish
+ { 0x0c0c, "fr-CA" }, //French
+ { 0x0c1a, "sr-Cyrl-CS" }, //Serbian (Cyrillic)
+ { 0x0c3b, "se-FI" }, //Sami (Northern)
+ { 0x0c6b, "quz-PE" }, //Quechua
+ { 0x1001, "ar-LY" }, //Arabic
+ { 0x1004, "zh-SG" }, //Chinese (Simplified)
+ { 0x1007, "de-LU" }, //German
+ { 0x1009, "en-CA" }, //English
+ { 0x100a, "es-GT" }, //Spanish
+ { 0x100c, "fr-CH" }, //French
+ { 0x101a, "hr-BA" }, //Croatian (Latin)
+ { 0x103b, "smj-NO" }, //Sami (Lule)
+ { 0x1401, "ar-DZ" }, //Arabic
+ { 0x1404, "zh-MO" }, //Chinese (Traditional)
+ { 0x1407, "de-LI" }, //German
+ { 0x1409, "en-NZ" }, //English
+ { 0x140a, "es-CR" }, //Spanish
+ { 0x140c, "fr-LU" }, //French
+ { 0x141a, "bs-Latn-BA" }, //Bosnian (Latin)
+ { 0x141a, "bs" }, //Bosnian
+ { 0x143b, "smj-SE" }, //Sami (Lule)
+ { 0x143b, "smj" }, //Sami (Lule)
+ { 0x1801, "ar-MA" }, //Arabic
+ { 0x1809, "en-IE" }, //English
+ { 0x180a, "es-PA" }, //Spanish
+ { 0x180c, "fr-MC" }, //French
+ { 0x181a, "sr-Latn-BA" }, //Serbian (Latin)
+ { 0x183b, "sma-NO" }, //Sami (Southern)
+ { 0x1c01, "ar-TN" }, //Arabic
+ { 0x1c09, "en-ZA" }, //English
+ { 0x1c0a, "es-DO" }, //Spanish
+ { 0x1c1a, "sr-Cyrl-BA" }, //Serbian (Cyrillic)
+ { 0x1c3b, "sma-SE" }, //Sami (Southern)
+ { 0x1c3b, "sma" }, //Sami (Southern)
+ { 0x2001, "ar-OM" }, //Arabic
+ { 0x2009, "en-JM" }, //English
+ { 0x200a, "es-VE" }, //Spanish
+ { 0x201a, "bs-Cyrl-BA" }, //Bosnian (Cyrillic)
+ { 0x201a, "bs-Cyrl" }, //Bosnian (Cyrillic)
+ { 0x203b, "sms-FI" }, //Sami (Skolt)
+ { 0x203b, "sms" }, //Sami (Skolt)
+ { 0x2401, "ar-YE" }, //Arabic
+ { 0x2409, "en-029" }, //English
+ { 0x240a, "es-CO" }, //Spanish
+ { 0x241a, "sr-Latn-RS" }, //Serbian (Latin)
+ { 0x243b, "smn-FI" }, //Sami (Inari)
+ { 0x2801, "ar-SY" }, //Arabic
+ { 0x2809, "en-BZ" }, //English
+ { 0x280a, "es-PE" }, //Spanish
+ { 0x281a, "sr-Cyrl-RS" }, //Serbian (Cyrillic)
+ { 0x2c01, "ar-JO" }, //Arabic
+ { 0x2c09, "en-TT" }, //English
+ { 0x2c0a, "es-AR" }, //Spanish
+ { 0x2c1a, "sr-Latn-ME" }, //Serbian (Latin)
+ { 0x3001, "ar-LB" }, //Arabic
+ { 0x3009, "en-ZW" }, //English
+ { 0x300a, "es-EC" }, //Spanish
+ { 0x301a, "sr-Cyrl-ME" }, //Serbian (Cyrillic)
+ { 0x3401, "ar-KW" }, //Arabic
+ { 0x3409, "en-PH" }, //English
+ { 0x340a, "es-CL" }, //Spanish
+ { 0x3801, "ar-AE" }, //Arabic
+ { 0x380a, "es-UY" }, //Spanish
+ { 0x3c01, "ar-BH" }, //Arabic
+ { 0x3c0a, "es-PY" }, //Spanish
+ { 0x4001, "ar-QA" }, //Arabic
+ { 0x4009, "en-IN" }, //English
+ { 0x400a, "es-BO" }, //Spanish
+ { 0x4409, "en-MY" }, //English
+ { 0x440a, "es-SV" }, //Spanish
+ { 0x4809, "en-SG" }, //English
+ { 0x480a, "es-HN" }, //Spanish
+ { 0x4c0a, "es-NI" }, //Spanish
+ { 0x500a, "es-PR" }, //Spanish
+ { 0x540a, "es-US" }, //Spanish
+};
+
+namespace {
+bool BCP47FromLanguageIdLess(const BCP47FromLanguageId& a, const BCP47FromLanguageId& b) {
+ return a.languageID < b.languageID;
+}
+}
+
+bool SkOTTableName::Iterator::next(SkOTTableName::Iterator::Record& record) {
+ const size_t nameRecordsCount = SkEndian_SwapBE16(fName.count);
+ const SkOTTableName::Record* nameRecords = SkTAfter<const SkOTTableName::Record>(&fName);
+ const SkOTTableName::Record* nameRecord;
+
+ // Find the next record which matches the requested type.
+ do {
+ if (fIndex >= nameRecordsCount) {
+ return false;
+ }
+
+ nameRecord = &nameRecords[fIndex];
+ ++fIndex;
+ } while (fType != -1 && nameRecord->nameID.fontSpecific != fType);
+
+ record.type = nameRecord->nameID.fontSpecific;
+
+ const uint16_t stringTableOffset = SkEndian_SwapBE16(fName.stringOffset);
+ const char* stringTable = SkTAddOffset<const char>(&fName, stringTableOffset);
+
+ // Decode the name into UTF-8.
+ const uint16_t nameOffset = SkEndian_SwapBE16(nameRecord->offset);
+ const uint16_t nameLength = SkEndian_SwapBE16(nameRecord->length);
+ const char* nameString = SkTAddOffset<const char>(stringTable, nameOffset);
+ switch (nameRecord->platformID.value) {
+ case SkOTTableName::Record::PlatformID::Windows:
+ if (SkOTTableName::Record::EncodingID::Windows::UnicodeBMPUCS2
+ != nameRecord->encodingID.windows.value
+ && SkOTTableName::Record::EncodingID::Windows::UnicodeUCS4
+ != nameRecord->encodingID.windows.value
+ && SkOTTableName::Record::EncodingID::Windows::Symbol
+ != nameRecord->encodingID.windows.value)
+ {
+ record.name.reset();
+ break;
+ }
+ case SkOTTableName::Record::PlatformID::Unicode:
+ case SkOTTableName::Record::PlatformID::ISO:
+ SkStringFromUTF16BE((const uint16_t*)nameString, nameLength, record.name);
+ break;
+
+ case SkOTTableName::Record::PlatformID::Macintosh:
+ // TODO: need better decoding, especially on Mac.
+ if (SkOTTableName::Record::EncodingID::Macintosh::Roman
+ != nameRecord->encodingID.macintosh.value)
+ {
+ record.name.reset();
+ break;
+ }
+ SkStringFromMacRoman((const uint8_t*)nameString, nameLength, record.name);
+ break;
+
+ case SkOTTableName::Record::PlatformID::Custom:
+ // These should never appear in a 'name' table.
+ default:
+ SkASSERT(false);
+ record.name.reset();
+ break;
+ }
+
+ // Determine the language.
+ const uint16_t languageID = SkEndian_SwapBE16(nameRecord->languageID.languageTagID);
+
+ // Handle format 1 languages.
+ if (SkOTTableName::format_1 == fName.format && languageID >= 0x8000) {
+ const uint16_t languageTagRecordIndex = languageID - 0x8000;
+
+ const SkOTTableName::Format1Ext* format1ext =
+ SkTAfter<const SkOTTableName::Format1Ext>(nameRecords, nameRecordsCount);
+
+ if (languageTagRecordIndex < SkEndian_SwapBE16(format1ext->langTagCount)) {
+ const SkOTTableName::Format1Ext::LangTagRecord* languageTagRecord =
+ SkTAfter<const SkOTTableName::Format1Ext::LangTagRecord>(format1ext);
+
+ uint16_t offset = SkEndian_SwapBE16(languageTagRecord[languageTagRecordIndex].offset);
+ uint16_t length = SkEndian_SwapBE16(languageTagRecord[languageTagRecordIndex].length);
+ const uint16_t* string = SkTAddOffset<const uint16_t>(stringTable, offset);
+ SkStringFromUTF16BE(string, length, record.language);
+ return true;
+ }
+ }
+
+ // Handle format 0 languages, translating them into BCP 47.
+ const BCP47FromLanguageId target = { languageID, "" };
+ int languageIndex = SkTSearch<BCP47FromLanguageId, BCP47FromLanguageIdLess>(
+ BCP47FromLanguageID, SK_ARRAY_COUNT(BCP47FromLanguageID), target, sizeof(target));
+ if (languageIndex >= 0) {
+ record.language = BCP47FromLanguageID[languageIndex].bcp47;
+ return true;
+ }
+
+ // Unknown language, return the BCP 47 code 'und' for 'undetermined'.
+ SkASSERT(false);
+ record.language = "und";
+ return true;
+}
diff --git a/sfnt/SkOTTable_name.h b/sfnt/SkOTTable_name.h
new file mode 100644
index 00000000..f3dae408
--- /dev/null
+++ b/sfnt/SkOTTable_name.h
@@ -0,0 +1,584 @@
+/*
+ * 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_name_DEFINED
+#define SkOTTable_name_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkString.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTableName {
+ static const SK_OT_CHAR TAG0 = 'n';
+ static const SK_OT_CHAR TAG1 = 'a';
+ static const SK_OT_CHAR TAG2 = 'm';
+ static const SK_OT_CHAR TAG3 = 'e';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTableName>::value;
+
+ SK_OT_USHORT format;
+ static const SK_OT_USHORT format_0 = SkTEndian_SwapBE16(0);
+ /** Format 1 was added in OpenType 1.6 (April 2009). */
+ static const SK_OT_USHORT format_1 = SkTEndian_SwapBE16(1);
+
+ /** The number of name records which follow. */
+ SK_OT_USHORT count;
+
+ /** Offset in SK_OT_BYTEs to start of string storage area (from start of table). */
+ SK_OT_USHORT stringOffset;
+
+ struct Record {
+ /** The platform ID specifies how to interpret the encoding and language ID. */
+ struct PlatformID {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Unicode, SkTEndian_SwapBE16(0)))
+ ((Macintosh, SkTEndian_SwapBE16(1)))
+ ((ISO, SkTEndian_SwapBE16(2))) // Deprecated, use Unicode instead.
+ ((Windows, SkTEndian_SwapBE16(3)))
+ ((Custom, SkTEndian_SwapBE16(4)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } platformID;
+
+ union EncodingID {
+ SK_OT_USHORT custom;
+
+ /** Always UTF-16BE. */
+ struct Unicode {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Unicode10, SkTEndian_SwapBE16(0)))
+ ((Unicode11, SkTEndian_SwapBE16(1)))
+ ((ISO10646, SkTEndian_SwapBE16(2))) //deprecated, use Unicode11
+ ((Unicode20BMP, SkTEndian_SwapBE16(3)))
+ ((Unicode20, SkTEndian_SwapBE16(4)))
+ ((UnicodeVariationSequences, SkTEndian_SwapBE16(5)))
+ ((UnicodeFull, SkTEndian_SwapBE16(6)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } unicode;
+
+ /** These are Mac encodings, see http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/
+ * for their mappings to unicode.
+ * Name table strings using PlatformID::Macintosh must use Roman.
+ */
+ struct Macintosh {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Roman, SkTEndian_SwapBE16(0)))
+ ((Japanese, SkTEndian_SwapBE16(1)))
+ ((ChineseTraditional, SkTEndian_SwapBE16(2)))
+ ((Korean, SkTEndian_SwapBE16(3)))
+ ((Arabic, SkTEndian_SwapBE16(4)))
+ ((Hebrew, SkTEndian_SwapBE16(5)))
+ ((Greek, SkTEndian_SwapBE16(6)))
+ ((Russian, SkTEndian_SwapBE16(7)))
+ ((RSymbol, SkTEndian_SwapBE16(8)))
+ ((Devanagari, SkTEndian_SwapBE16(9)))
+ ((Gurmukhi, SkTEndian_SwapBE16(10)))
+ ((Gujarati, SkTEndian_SwapBE16(11)))
+ ((Oriya, SkTEndian_SwapBE16(12)))
+ ((Bengali, SkTEndian_SwapBE16(13)))
+ ((Tamil, SkTEndian_SwapBE16(14)))
+ ((Telugu, SkTEndian_SwapBE16(15)))
+ ((Kannada, SkTEndian_SwapBE16(16)))
+ ((Malayalam, SkTEndian_SwapBE16(17)))
+ ((Sinhalese, SkTEndian_SwapBE16(18)))
+ ((Burmese, SkTEndian_SwapBE16(19)))
+ ((Khmer, SkTEndian_SwapBE16(20)))
+ ((Thai, SkTEndian_SwapBE16(21)))
+ ((Laotian, SkTEndian_SwapBE16(22)))
+ ((Georgian, SkTEndian_SwapBE16(23)))
+ ((Armenian, SkTEndian_SwapBE16(24)))
+ ((ChineseSimplified, SkTEndian_SwapBE16(25)))
+ ((Tibetan, SkTEndian_SwapBE16(26)))
+ ((Mongolian, SkTEndian_SwapBE16(27)))
+ ((Geez, SkTEndian_SwapBE16(28)))
+ ((Slavic, SkTEndian_SwapBE16(29)))
+ ((Vietnamese, SkTEndian_SwapBE16(30)))
+ ((Sindhi, SkTEndian_SwapBE16(31)))
+ ((Uninterpreted, SkTEndian_SwapBE16(32)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } macintosh;
+
+ /** Deprecated, use Unicode instead. */
+ struct ISO {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((ASCII7, SkTEndian_SwapBE16(0)))
+ ((ISO10646, SkTEndian_SwapBE16(1)))
+ ((ISO88591, SkTEndian_SwapBE16(2)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } iso;
+
+ /** Name table strings using PlatformID::Windows must use Symbol, UnicodeBMPUCS2, or
+ * UnicodeUCS4. Symbol and UnicodeBMPUCS2 are both UCS2-BE, UnicodeUCS4 is actually
+ * UTF-16BE.
+ */
+ struct Windows {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Symbol, SkTEndian_SwapBE16(0))) // UCS2-BE, but don't use this font to display it's own name.
+ ((UnicodeBMPUCS2, SkTEndian_SwapBE16(1))) // UCS2-BE, Windows default
+ ((ShiftJIS, SkTEndian_SwapBE16(2)))
+ ((PRC, SkTEndian_SwapBE16(3)))
+ ((Big5, SkTEndian_SwapBE16(4)))
+ ((Wansung, SkTEndian_SwapBE16(5)))
+ ((Johab, SkTEndian_SwapBE16(6)))
+ ((UnicodeUCS4, SkTEndian_SwapBE16(10))) // UTF-16BE. It means UCS4 in charmaps.
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } windows;
+ } encodingID;
+
+ /** LanguageIDs <= 0x7FFF are predefined.
+ * LanguageIDs > 0x7FFF are indexes into the langTagRecord array
+ * (in format 1 name tables, see SkOTTableName::format).
+ */
+ union LanguageID {
+ /** A value greater than 0x7FFF.
+ * languageTagID - 0x8000 is an index into the langTagRecord array.
+ */
+ SK_OT_USHORT languageTagID;
+
+ /** These are known as Language Designators.
+ * Apple now uses BCP 47 (post OSX10.4), so there will be no new entries.
+ */
+ struct Macintosh {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((English, SkTEndian_SwapBE16(0)))
+ ((French, SkTEndian_SwapBE16(1)))
+ ((German, SkTEndian_SwapBE16(2)))
+ ((Italian, SkTEndian_SwapBE16(3)))
+ ((Dutch, SkTEndian_SwapBE16(4)))
+ ((Swedish, SkTEndian_SwapBE16(5)))
+ ((Spanish, SkTEndian_SwapBE16(6)))
+ ((Danish, SkTEndian_SwapBE16(7)))
+ ((Portuguese, SkTEndian_SwapBE16(8)))
+ ((Norwegian, SkTEndian_SwapBE16(9)))
+ ((Hebrew, SkTEndian_SwapBE16(10)))
+ ((Japanese, SkTEndian_SwapBE16(11)))
+ ((Arabic, SkTEndian_SwapBE16(12)))
+ ((Finnish, SkTEndian_SwapBE16(13)))
+ ((Greek, SkTEndian_SwapBE16(14)))
+ ((Icelandic, SkTEndian_SwapBE16(15)))
+ ((Maltese, SkTEndian_SwapBE16(16)))
+ ((Turkish, SkTEndian_SwapBE16(17)))
+ ((Croatian, SkTEndian_SwapBE16(18)))
+ ((ChineseTraditional, SkTEndian_SwapBE16(19)))
+ ((Urdu, SkTEndian_SwapBE16(20)))
+ ((Hindi, SkTEndian_SwapBE16(21)))
+ ((Thai, SkTEndian_SwapBE16(22)))
+ ((Korean, SkTEndian_SwapBE16(23)))
+ ((Lithuanian, SkTEndian_SwapBE16(24)))
+ ((Polish, SkTEndian_SwapBE16(25)))
+ ((Hungarian, SkTEndian_SwapBE16(26)))
+ ((Estonian, SkTEndian_SwapBE16(27)))
+ ((Latvian, SkTEndian_SwapBE16(28)))
+ ((Sami, SkTEndian_SwapBE16(29)))
+ ((Faroese, SkTEndian_SwapBE16(30)))
+ ((Farsi_Persian, SkTEndian_SwapBE16(31)))
+ ((Russian, SkTEndian_SwapBE16(32)))
+ ((ChineseSimplified, SkTEndian_SwapBE16(33)))
+ ((Flemish, SkTEndian_SwapBE16(34)))
+ ((IrishGaelic, SkTEndian_SwapBE16(35)))
+ ((Albanian, SkTEndian_SwapBE16(36)))
+ ((Romanian, SkTEndian_SwapBE16(37)))
+ ((Czech, SkTEndian_SwapBE16(38)))
+ ((Slovak, SkTEndian_SwapBE16(39)))
+ ((Slovenian, SkTEndian_SwapBE16(40)))
+ ((Yiddish, SkTEndian_SwapBE16(41)))
+ ((Serbian, SkTEndian_SwapBE16(42)))
+ ((Macedonian, SkTEndian_SwapBE16(43)))
+ ((Bulgarian, SkTEndian_SwapBE16(44)))
+ ((Ukrainian, SkTEndian_SwapBE16(45)))
+ ((Byelorussian, SkTEndian_SwapBE16(46)))
+ ((Uzbek, SkTEndian_SwapBE16(47)))
+ ((Kazakh, SkTEndian_SwapBE16(48)))
+ ((AzerbaijaniCyrillic, SkTEndian_SwapBE16(49)))
+ ((AzerbaijaniArabic, SkTEndian_SwapBE16(50)))
+ ((Armenian, SkTEndian_SwapBE16(51)))
+ ((Georgian, SkTEndian_SwapBE16(52)))
+ ((Moldavian, SkTEndian_SwapBE16(53)))
+ ((Kirghiz, SkTEndian_SwapBE16(54)))
+ ((Tajiki, SkTEndian_SwapBE16(55)))
+ ((Turkmen, SkTEndian_SwapBE16(56)))
+ ((MongolianTraditional, SkTEndian_SwapBE16(57)))
+ ((MongolianCyrillic, SkTEndian_SwapBE16(58)))
+ ((Pashto, SkTEndian_SwapBE16(59)))
+ ((Kurdish, SkTEndian_SwapBE16(60)))
+ ((Kashmiri, SkTEndian_SwapBE16(61)))
+ ((Sindhi, SkTEndian_SwapBE16(62)))
+ ((Tibetan, SkTEndian_SwapBE16(63)))
+ ((Nepali, SkTEndian_SwapBE16(64)))
+ ((Sanskrit, SkTEndian_SwapBE16(65)))
+ ((Marathi, SkTEndian_SwapBE16(66)))
+ ((Bengali, SkTEndian_SwapBE16(67)))
+ ((Assamese, SkTEndian_SwapBE16(68)))
+ ((Gujarati, SkTEndian_SwapBE16(69)))
+ ((Punjabi, SkTEndian_SwapBE16(70)))
+ ((Oriya, SkTEndian_SwapBE16(71)))
+ ((Malayalam, SkTEndian_SwapBE16(72)))
+ ((Kannada, SkTEndian_SwapBE16(73)))
+ ((Tamil, SkTEndian_SwapBE16(74)))
+ ((Telugu, SkTEndian_SwapBE16(75)))
+ ((Sinhalese, SkTEndian_SwapBE16(76)))
+ ((Burmese, SkTEndian_SwapBE16(77)))
+ ((Khmer, SkTEndian_SwapBE16(78)))
+ ((Lao, SkTEndian_SwapBE16(79)))
+ ((Vietnamese, SkTEndian_SwapBE16(80)))
+ ((Indonesian, SkTEndian_SwapBE16(81)))
+ ((Tagalong, SkTEndian_SwapBE16(82)))
+ ((MalayRoman, SkTEndian_SwapBE16(83)))
+ ((MalayArabic, SkTEndian_SwapBE16(84)))
+ ((Amharic, SkTEndian_SwapBE16(85)))
+ ((Tigrinya, SkTEndian_SwapBE16(86)))
+ ((Galla, SkTEndian_SwapBE16(87)))
+ ((Somali, SkTEndian_SwapBE16(88)))
+ ((Swahili, SkTEndian_SwapBE16(89)))
+ ((Kinyarwanda_Ruanda, SkTEndian_SwapBE16(90)))
+ ((Rundi, SkTEndian_SwapBE16(91)))
+ ((Nyanja_Chewa, SkTEndian_SwapBE16(92)))
+ ((Malagasy, SkTEndian_SwapBE16(93)))
+ ((Esperanto, SkTEndian_SwapBE16(94)))
+ ((Welsh, SkTEndian_SwapBE16(128)))
+ ((Basque, SkTEndian_SwapBE16(129)))
+ ((Catalan, SkTEndian_SwapBE16(130)))
+ ((Latin, SkTEndian_SwapBE16(131)))
+ ((Quenchua, SkTEndian_SwapBE16(132)))
+ ((Guarani, SkTEndian_SwapBE16(133)))
+ ((Aymara, SkTEndian_SwapBE16(134)))
+ ((Tatar, SkTEndian_SwapBE16(135)))
+ ((Uighur, SkTEndian_SwapBE16(136)))
+ ((Dzongkha, SkTEndian_SwapBE16(137)))
+ ((JavaneseRoman, SkTEndian_SwapBE16(138)))
+ ((SundaneseRoman, SkTEndian_SwapBE16(139)))
+ ((Galician, SkTEndian_SwapBE16(140)))
+ ((Afrikaans, SkTEndian_SwapBE16(141)))
+ ((Breton, SkTEndian_SwapBE16(142)))
+ ((Inuktitut, SkTEndian_SwapBE16(143)))
+ ((ScottishGaelic, SkTEndian_SwapBE16(144)))
+ ((ManxGaelic, SkTEndian_SwapBE16(145)))
+ ((IrishGaelicWithLenition, SkTEndian_SwapBE16(146)))
+ ((Tongan, SkTEndian_SwapBE16(147)))
+ ((GreekPolytonic, SkTEndian_SwapBE16(148)))
+ ((Greenlandic, SkTEndian_SwapBE16(149)))
+ ((AzerbaijaniRoman, SkTEndian_SwapBE16(150)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } macintosh;
+
+ /** These are known as LCIDs.
+ * On Windows the current set can be had from EnumSystemLocalesEx and LocaleNameToLCID.
+ */
+ struct Windows {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((Afrikaans_SouthAfrica, SkTEndian_SwapBE16(0x0436)))
+ ((Albanian_Albania, SkTEndian_SwapBE16(0x041C)))
+ ((Alsatian_France, SkTEndian_SwapBE16(0x0484)))
+ ((Amharic_Ethiopia, SkTEndian_SwapBE16(0x045E)))
+ ((Arabic_Algeria, SkTEndian_SwapBE16(0x1401)))
+ ((Arabic_Bahrain, SkTEndian_SwapBE16(0x3C01)))
+ ((Arabic_Egypt, SkTEndian_SwapBE16(0x0C01)))
+ ((Arabic_Iraq, SkTEndian_SwapBE16(0x0801)))
+ ((Arabic_Jordan, SkTEndian_SwapBE16(0x2C01)))
+ ((Arabic_Kuwait, SkTEndian_SwapBE16(0x3401)))
+ ((Arabic_Lebanon, SkTEndian_SwapBE16(0x3001)))
+ ((Arabic_Libya, SkTEndian_SwapBE16(0x1001)))
+ ((Arabic_Morocco, SkTEndian_SwapBE16(0x1801)))
+ ((Arabic_Oman, SkTEndian_SwapBE16(0x2001)))
+ ((Arabic_Qatar, SkTEndian_SwapBE16(0x4001)))
+ ((Arabic_SaudiArabia, SkTEndian_SwapBE16(0x0401)))
+ ((Arabic_Syria, SkTEndian_SwapBE16(0x2801)))
+ ((Arabic_Tunisia, SkTEndian_SwapBE16(0x1C01)))
+ ((Arabic_UAE, SkTEndian_SwapBE16(0x3801)))
+ ((Arabic_Yemen, SkTEndian_SwapBE16(0x2401)))
+ ((Armenian_Armenia, SkTEndian_SwapBE16(0x042B)))
+ ((Assamese_India, SkTEndian_SwapBE16(0x044D)))
+ ((AzeriCyrillic_Azerbaijan, SkTEndian_SwapBE16(0x082C)))
+ ((AzeriLatin_Azerbaijan, SkTEndian_SwapBE16(0x042C)))
+ ((Bashkir_Russia, SkTEndian_SwapBE16(0x046D)))
+ ((Basque_Basque, SkTEndian_SwapBE16(0x042D)))
+ ((Belarusian_Belarus, SkTEndian_SwapBE16(0x0423)))
+ ((Bengali_Bangladesh, SkTEndian_SwapBE16(0x0845)))
+ ((Bengali_India, SkTEndian_SwapBE16(0x0445)))
+ ((BosnianCyrillic_BosniaAndHerzegovina, SkTEndian_SwapBE16(0x201A)))
+ ((BosnianLatin_BosniaAndHerzegovina, SkTEndian_SwapBE16(0x141A)))
+ ((Breton_France, SkTEndian_SwapBE16(0x047E)))
+ ((Bulgarian_Bulgaria, SkTEndian_SwapBE16(0x0402)))
+ ((Catalan_Catalan, SkTEndian_SwapBE16(0x0403)))
+ ((Chinese_HongKongSAR, SkTEndian_SwapBE16(0x0C04)))
+ ((Chinese_MacaoSAR, SkTEndian_SwapBE16(0x1404)))
+ ((Chinese_PeoplesRepublicOfChina, SkTEndian_SwapBE16(0x0804)))
+ ((Chinese_Singapore, SkTEndian_SwapBE16(0x1004)))
+ ((Chinese_Taiwan, SkTEndian_SwapBE16(0x0404)))
+ ((Corsican_France, SkTEndian_SwapBE16(0x0483)))
+ ((Croatian_Croatia, SkTEndian_SwapBE16(0x041A)))
+ ((CroatianLatin_BosniaAndHerzegovina, SkTEndian_SwapBE16(0x101A)))
+ ((Czech_CzechRepublic, SkTEndian_SwapBE16(0x0405)))
+ ((Danish_Denmark, SkTEndian_SwapBE16(0x0406)))
+ ((Dari_Afghanistan, SkTEndian_SwapBE16(0x048C)))
+ ((Divehi_Maldives, SkTEndian_SwapBE16(0x0465)))
+ ((Dutch_Belgium, SkTEndian_SwapBE16(0x0813)))
+ ((Dutch_Netherlands, SkTEndian_SwapBE16(0x0413)))
+ ((English_Australia, SkTEndian_SwapBE16(0x0C09)))
+ ((English_Belize, SkTEndian_SwapBE16(0x2809)))
+ ((English_Canada, SkTEndian_SwapBE16(0x1009)))
+ ((English_Caribbean, SkTEndian_SwapBE16(0x2409)))
+ ((English_India, SkTEndian_SwapBE16(0x4009)))
+ ((English_Ireland, SkTEndian_SwapBE16(0x1809)))
+ ((English_Jamaica, SkTEndian_SwapBE16(0x2009)))
+ ((English_Malaysia, SkTEndian_SwapBE16(0x4409)))
+ ((English_NewZealand, SkTEndian_SwapBE16(0x1409)))
+ ((English_RepublicOfThePhilippines, SkTEndian_SwapBE16(0x3409)))
+ ((English_Singapore, SkTEndian_SwapBE16(0x4809)))
+ ((English_SouthAfrica, SkTEndian_SwapBE16(0x1C09)))
+ ((English_TrinidadAndTobago, SkTEndian_SwapBE16(0x2C09)))
+ ((English_UnitedKingdom, SkTEndian_SwapBE16(0x0809)))
+ ((English_UnitedStates, SkTEndian_SwapBE16(0x0409)))
+ ((English_Zimbabwe, SkTEndian_SwapBE16(0x3009)))
+ ((Estonian_Estonia, SkTEndian_SwapBE16(0x0425)))
+ ((Faroese_FaroeIslands, SkTEndian_SwapBE16(0x0438)))
+ ((Filipino_Philippines, SkTEndian_SwapBE16(0x0464)))
+ ((Finnish_Finland, SkTEndian_SwapBE16(0x040B)))
+ ((French_Belgium, SkTEndian_SwapBE16(0x080C)))
+ ((French_Canada, SkTEndian_SwapBE16(0x0C0C)))
+ ((French_France, SkTEndian_SwapBE16(0x040C)))
+ ((French_Luxembourg, SkTEndian_SwapBE16(0x140c)))
+ ((French_PrincipalityOfMonoco, SkTEndian_SwapBE16(0x180C)))
+ ((French_Switzerland, SkTEndian_SwapBE16(0x100C)))
+ ((Frisian_Netherlands, SkTEndian_SwapBE16(0x0462)))
+ ((Galician_Galician, SkTEndian_SwapBE16(0x0456)))
+ ((Georgian_Georgia, SkTEndian_SwapBE16(0x0437)))
+ ((German_Austria, SkTEndian_SwapBE16(0x0C07)))
+ ((German_Germany, SkTEndian_SwapBE16(0x0407)))
+ ((German_Liechtenstein, SkTEndian_SwapBE16(0x1407)))
+ ((German_Luxembourg, SkTEndian_SwapBE16(0x1007)))
+ ((German_Switzerland, SkTEndian_SwapBE16(0x0807)))
+ ((Greek_Greece, SkTEndian_SwapBE16(0x0408)))
+ ((Greenlandic_Greenland, SkTEndian_SwapBE16(0x046F)))
+ ((Gujarati_India, SkTEndian_SwapBE16(0x0447)))
+ ((HausaLatin_Nigeria, SkTEndian_SwapBE16(0x0468)))
+ ((Hebrew_Israel, SkTEndian_SwapBE16(0x040D)))
+ ((Hindi_India, SkTEndian_SwapBE16(0x0439)))
+ ((Hungarian_Hungary, SkTEndian_SwapBE16(0x040E)))
+ ((Icelandic_Iceland, SkTEndian_SwapBE16(0x040F)))
+ ((Igbo_Nigeria, SkTEndian_SwapBE16(0x0470)))
+ ((Indonesian_Indonesia, SkTEndian_SwapBE16(0x0421)))
+ ((Inuktitut_Canada, SkTEndian_SwapBE16(0x045D)))
+ ((InuktitutLatin_Canada, SkTEndian_SwapBE16(0x085D)))
+ ((Irish_Ireland, SkTEndian_SwapBE16(0x083C)))
+ ((isiXhosa_SouthAfrica, SkTEndian_SwapBE16(0x0434)))
+ ((isiZulu_SouthAfrica, SkTEndian_SwapBE16(0x0435)))
+ ((Italian_Italy, SkTEndian_SwapBE16(0x0410)))
+ ((Italian_Switzerland, SkTEndian_SwapBE16(0x0810)))
+ ((Japanese_Japan, SkTEndian_SwapBE16(0x0411)))
+ ((Kannada_India, SkTEndian_SwapBE16(0x044B)))
+ ((Kazakh_Kazakhstan, SkTEndian_SwapBE16(0x043F)))
+ ((Khmer_Cambodia, SkTEndian_SwapBE16(0x0453)))
+ ((Kiche_Guatemala, SkTEndian_SwapBE16(0x0486)))
+ ((Kinyarwanda_Rwanda, SkTEndian_SwapBE16(0x0487)))
+ ((Kiswahili_Kenya, SkTEndian_SwapBE16(0x0441)))
+ ((Konkani_India, SkTEndian_SwapBE16(0x0457)))
+ ((Korean_Korea, SkTEndian_SwapBE16(0x0412)))
+ ((Kyrgyz_Kyrgyzstan, SkTEndian_SwapBE16(0x0440)))
+ ((Lao_LaoPDR, SkTEndian_SwapBE16(0x0454)))
+ ((Latvian_Latvia, SkTEndian_SwapBE16(0x0426)))
+ ((Lithuanian_Lithuania, SkTEndian_SwapBE16(0x0427)))
+ ((LowerSorbian_Germany, SkTEndian_SwapBE16(0x082E)))
+ ((Luxembourgish_Luxembourg, SkTEndian_SwapBE16(0x046E)))
+ ((MacedonianFYROM_FormerYugoslavRepublicOfMacedonia, SkTEndian_SwapBE16(0x042F)))
+ ((Malay_BruneiDarussalam, SkTEndian_SwapBE16(0x083E)))
+ ((Malay_Malaysia, SkTEndian_SwapBE16(0x043E)))
+ ((Malayalam_India, SkTEndian_SwapBE16(0x044C)))
+ ((Maltese_Malta, SkTEndian_SwapBE16(0x043A)))
+ ((Maori_NewZealand, SkTEndian_SwapBE16(0x0481)))
+ ((Mapudungun_Chile, SkTEndian_SwapBE16(0x047A)))
+ ((Marathi_India, SkTEndian_SwapBE16(0x044E)))
+ ((Mohawk_Mohawk, SkTEndian_SwapBE16(0x047C)))
+ ((MongolianCyrillic_Mongolia, SkTEndian_SwapBE16(0x0450)))
+ ((MongolianTraditional_PeoplesRepublicOfChina, SkTEndian_SwapBE16(0x0850)))
+ ((Nepali_Nepal, SkTEndian_SwapBE16(0x0461)))
+ ((NorwegianBokmal_Norway, SkTEndian_SwapBE16(0x0414)))
+ ((NorwegianNynorsk_Norway, SkTEndian_SwapBE16(0x0814)))
+ ((Occitan_France, SkTEndian_SwapBE16(0x0482)))
+ ((Odia_India, SkTEndian_SwapBE16(0x0448)))
+ ((Pashto_Afghanistan, SkTEndian_SwapBE16(0x0463)))
+ ((Polish_Poland, SkTEndian_SwapBE16(0x0415)))
+ ((Portuguese_Brazil, SkTEndian_SwapBE16(0x0416)))
+ ((Portuguese_Portugal, SkTEndian_SwapBE16(0x0816)))
+ ((Punjabi_India, SkTEndian_SwapBE16(0x0446)))
+ ((Quechua_Bolivia, SkTEndian_SwapBE16(0x046B)))
+ ((Quechua_Ecuador, SkTEndian_SwapBE16(0x086B)))
+ ((Quechua_Peru, SkTEndian_SwapBE16(0x0C6B)))
+ ((Romanian_Romania, SkTEndian_SwapBE16(0x0418)))
+ ((Romansh_Switzerland, SkTEndian_SwapBE16(0x0417)))
+ ((Russian_Russia, SkTEndian_SwapBE16(0x0419)))
+ ((SamiInari_Finland, SkTEndian_SwapBE16(0x243B)))
+ ((SamiLule_Norway, SkTEndian_SwapBE16(0x103B)))
+ ((SamiLule_Sweden, SkTEndian_SwapBE16(0x143B)))
+ ((SamiNorthern_Finland, SkTEndian_SwapBE16(0x0C3B)))
+ ((SamiNorthern_Norway, SkTEndian_SwapBE16(0x043B)))
+ ((SamiNorthern_Sweden, SkTEndian_SwapBE16(0x083B)))
+ ((SamiSkolt_Finland, SkTEndian_SwapBE16(0x203B)))
+ ((SamiSouthern_Norway, SkTEndian_SwapBE16(0x183B)))
+ ((SamiSouthern_Sweden, SkTEndian_SwapBE16(0x1C3B)))
+ ((Sanskrit_India, SkTEndian_SwapBE16(0x044F)))
+ ((SerbianCyrillic_BosniaAndHerzegovina, SkTEndian_SwapBE16(0x1C1A)))
+ ((SerbianCyrillic_Serbia, SkTEndian_SwapBE16(0x0C1A)))
+ ((SerbianLatin_BosniaAndHerzegovina, SkTEndian_SwapBE16(0x181A)))
+ ((SerbianLatin_Serbia, SkTEndian_SwapBE16(0x081A)))
+ ((SesothoSaLeboa_SouthAfrica, SkTEndian_SwapBE16(0x046C)))
+ ((Setswana_SouthAfrica, SkTEndian_SwapBE16(0x0432)))
+ ((Sinhala_SriLanka, SkTEndian_SwapBE16(0x045B)))
+ ((Slovak_Slovakia, SkTEndian_SwapBE16(0x041B)))
+ ((Slovenian_Slovenia, SkTEndian_SwapBE16(0x0424)))
+ ((Spanish_Argentina, SkTEndian_SwapBE16(0x2C0A)))
+ ((Spanish_Bolivia, SkTEndian_SwapBE16(0x400A)))
+ ((Spanish_Chile, SkTEndian_SwapBE16(0x340A)))
+ ((Spanish_Colombia, SkTEndian_SwapBE16(0x240A)))
+ ((Spanish_CostaRica, SkTEndian_SwapBE16(0x140A)))
+ ((Spanish_DominicanRepublic, SkTEndian_SwapBE16(0x1C0A)))
+ ((Spanish_Ecuador, SkTEndian_SwapBE16(0x300A)))
+ ((Spanish_ElSalvador, SkTEndian_SwapBE16(0x440A)))
+ ((Spanish_Guatemala, SkTEndian_SwapBE16(0x100A)))
+ ((Spanish_Honduras, SkTEndian_SwapBE16(0x480A)))
+ ((Spanish_Mexico, SkTEndian_SwapBE16(0x080A)))
+ ((Spanish_Nicaragua, SkTEndian_SwapBE16(0x4C0A)))
+ ((Spanish_Panama, SkTEndian_SwapBE16(0x180A)))
+ ((Spanish_Paraguay, SkTEndian_SwapBE16(0x3C0A)))
+ ((Spanish_Peru, SkTEndian_SwapBE16(0x280A)))
+ ((Spanish_PuertoRico, SkTEndian_SwapBE16(0x500A)))
+ ((SpanishModernSort_Spain, SkTEndian_SwapBE16(0x0C0A)))
+ ((SpanishTraditionalSort_Spain, SkTEndian_SwapBE16(0x040A)))
+ ((Spanish_UnitedStates, SkTEndian_SwapBE16(0x540A)))
+ ((Spanish_Uruguay, SkTEndian_SwapBE16(0x380A)))
+ ((Spanish_Venezuela, SkTEndian_SwapBE16(0x200A)))
+ ((Sweden_Finland, SkTEndian_SwapBE16(0x081D)))
+ ((Swedish_Sweden, SkTEndian_SwapBE16(0x041D)))
+ ((Syriac_Syria, SkTEndian_SwapBE16(0x045A)))
+ ((TajikCyrillic_Tajikistan, SkTEndian_SwapBE16(0x0428)))
+ ((TamazightLatin_Algeria, SkTEndian_SwapBE16(0x085F)))
+ ((Tamil_India, SkTEndian_SwapBE16(0x0449)))
+ ((Tatar_Russia, SkTEndian_SwapBE16(0x0444)))
+ ((Telugu_India, SkTEndian_SwapBE16(0x044A)))
+ ((Thai_Thailand, SkTEndian_SwapBE16(0x041E)))
+ ((Tibetan_PRC, SkTEndian_SwapBE16(0x0451)))
+ ((Turkish_Turkey, SkTEndian_SwapBE16(0x041F)))
+ ((Turkmen_Turkmenistan, SkTEndian_SwapBE16(0x0442)))
+ ((Uighur_PRC, SkTEndian_SwapBE16(0x0480)))
+ ((Ukrainian_Ukraine, SkTEndian_SwapBE16(0x0422)))
+ ((UpperSorbian_Germany, SkTEndian_SwapBE16(0x042E)))
+ ((Urdu_IslamicRepublicOfPakistan, SkTEndian_SwapBE16(0x0420)))
+ ((UzbekCyrillic_Uzbekistan, SkTEndian_SwapBE16(0x0843)))
+ ((UzbekLatin_Uzbekistan, SkTEndian_SwapBE16(0x0443)))
+ ((Vietnamese_Vietnam, SkTEndian_SwapBE16(0x042A)))
+ ((Welsh_UnitedKingdom, SkTEndian_SwapBE16(0x0452)))
+ ((Wolof_Senegal, SkTEndian_SwapBE16(0x0488)))
+ ((Yakut_Russia, SkTEndian_SwapBE16(0x0485)))
+ ((Yi_PRC, SkTEndian_SwapBE16(0x0478)))
+ ((Yoruba_Nigeria, SkTEndian_SwapBE16(0x046A)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } windows;
+ } languageID;
+
+ /** NameIDs <= 0xFF are predefined. Those > 0xFF are font specific. */
+ union NameID {
+ /** A font specific name id which should be greater than 0xFF. */
+ SK_OT_USHORT fontSpecific;
+ struct Predefined {
+ SK_TYPED_ENUM(Value, SK_OT_USHORT,
+ ((CopyrightNotice, SkTEndian_SwapBE16(0)))
+ ((FontFamilyName, SkTEndian_SwapBE16(1)))
+ ((FontSubfamilyName, SkTEndian_SwapBE16(2)))
+ ((UniqueFontIdentifier, SkTEndian_SwapBE16(3)))
+ ((FullFontName, SkTEndian_SwapBE16(4)))
+ ((VersionString, SkTEndian_SwapBE16(5))) //Version <number>.<number>
+ ((PostscriptName, SkTEndian_SwapBE16(6))) //See spec for constraints.
+ ((Trademark, SkTEndian_SwapBE16(7)))
+ ((ManufacturerName, SkTEndian_SwapBE16(8)))
+ ((Designer, SkTEndian_SwapBE16(9)))
+ ((Description, SkTEndian_SwapBE16(10)))
+ ((URLVendor, SkTEndian_SwapBE16(11)))
+ ((URLDesigner, SkTEndian_SwapBE16(12)))
+ ((LicenseDescription, SkTEndian_SwapBE16(13)))
+ ((LicenseInfoURL, SkTEndian_SwapBE16(14)))
+ ((PreferredFamily, SkTEndian_SwapBE16(16)))
+ ((PreferredSubfamily, SkTEndian_SwapBE16(17)))
+ ((CompatibleFullName, SkTEndian_SwapBE16(18)))
+ ((SampleText, SkTEndian_SwapBE16(19)))
+ ((PostscriptCIDFindfontName, SkTEndian_SwapBE16(20)))
+ ((WWSFamilyName, SkTEndian_SwapBE16(21)))
+ ((WWSSubfamilyName, SkTEndian_SwapBE16(22)))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } predefined;
+ } nameID;
+
+ /** The length of the string in SK_OT_BYTEs. */
+ SK_OT_USHORT length;
+
+ /** Offset in SK_OT_BYTEs from start of string storage area
+ * (see SkOTTableName::stringOffset).
+ */
+ SK_OT_USHORT offset;
+ }; //nameRecord[count];
+
+ struct Format1Ext {
+ /** The number of languageTagRecords which follow. */
+ SK_OT_USHORT langTagCount;
+
+ /** The encoding of a langTagRecord string is always UTF-16BE.
+ * The content should follow IETF specification BCP 47.
+ */
+ struct LangTagRecord {
+ /** The length of the string in SK_OT_BYTEs. */
+ SK_OT_USHORT length;
+
+ /** Offset in SK_OT_BYTEs from start of string storage area
+ * (see SkOTTableName::stringOffset).
+ */
+ SK_OT_USHORT offset;
+ }; //langTagRecord[langTagCount]
+ }; //format1ext (if format == format_1)
+
+ class Iterator {
+ public:
+ Iterator(const SkOTTableName& name) : fName(name), fIndex(0), fType(-1) { }
+ Iterator(const SkOTTableName& name, SkOTTableName::Record::NameID::Predefined::Value type)
+ : fName(name), fIndex(0), fType(type)
+ { }
+
+ void reset(SkOTTableName::Record::NameID::Predefined::Value type) {
+ fIndex = 0;
+ fType = type;
+ }
+
+ struct Record {
+ SK_OT_USHORT type;
+ SkString name;
+ SkString language;
+ };
+ bool next(Record&);
+
+ private:
+ const SkOTTableName& fName;
+ size_t fIndex;
+ int fType;
+ };
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkOTTableName) == 6, sizeof_SkOTTableName_not_6);
+SK_COMPILE_ASSERT(sizeof(SkOTTableName::Format1Ext) == 2, sizeof_SkOTTableNameF1_not_2);
+SK_COMPILE_ASSERT(sizeof(SkOTTableName::Format1Ext::LangTagRecord) == 4, sizeof_SkOTTableNameLangTagRecord_not_4);
+SK_COMPILE_ASSERT(sizeof(SkOTTableName::Record) == 12, sizeof_SkOTTableNameRecord_not_12);
+
+#endif
diff --git a/sfnt/SkOTTable_post.h b/sfnt/SkOTTable_post.h
new file mode 100644
index 00000000..f563b08b
--- /dev/null
+++ b/sfnt/SkOTTable_post.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_post_DEFINED
+#define SkOTTable_post_DEFINED
+
+#include "SkEndian.h"
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkOTTablePostScript {
+ static const SK_OT_CHAR TAG0 = 'p';
+ static const SK_OT_CHAR TAG1 = 'o';
+ static const SK_OT_CHAR TAG2 = 's';
+ static const SK_OT_CHAR TAG3 = 't';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkOTTablePostScript>::value;
+
+ struct Format {
+ SK_TYPED_ENUM(Value, SK_OT_Fixed,
+ ((version1, SkTEndian_SwapBE32(0x00010000)))
+ ((version2, SkTEndian_SwapBE32(0x00020000)))
+ ((version2_5, SkTEndian_SwapBE32(0x00025000)))
+ ((version3, SkTEndian_SwapBE32(0x00030000)))
+ ((version4, SkTEndian_SwapBE32(0x00040000)))
+ SK_SEQ_END,
+ SK_SEQ_END)
+ SK_OT_Fixed value;
+ } format;
+ SK_OT_Fixed italicAngle;
+ SK_OT_FWORD underlinePosition;
+ SK_OT_FWORD underlineThickness;
+ SK_OT_ULONG isFixedPitch;
+ SK_OT_ULONG minMemType42;
+ SK_OT_ULONG maxMemType42;
+ SK_OT_ULONG minMemType1;
+ SK_OT_ULONG maxMemType1;
+};
+
+#pragma pack(pop)
+
+
+#include <stddef.h>
+SK_COMPILE_ASSERT(offsetof(SkOTTablePostScript, maxMemType1) == 28, SkOTTablePostScript_maxMemType1_not_at_28);
+SK_COMPILE_ASSERT(sizeof(SkOTTablePostScript) == 32, sizeof_SkOTTablePostScript_not_32);
+
+#endif
diff --git a/sfnt/SkOTUtils.cpp b/sfnt/SkOTUtils.cpp
new file mode 100644
index 00000000..004a8883
--- /dev/null
+++ b/sfnt/SkOTUtils.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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 SkOTTableName::Record::NameID::Predefined::Value namesToCreate[] = {
+ SkOTTableName::Record::NameID::Predefined::FontFamilyName,
+ SkOTTableName::Record::NameID::Predefined::FontSubfamilyName,
+ SkOTTableName::Record::NameID::Predefined::UniqueFontIdentifier,
+ SkOTTableName::Record::NameID::Predefined::FullFontName,
+ SkOTTableName::Record::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(SkOTTableName::Record)) + (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(SkOTTableName::Record));
+ nameTable->format = SkOTTableName::format_0;
+ nameTable->count = SkEndian_SwapBE16(namesCount);
+ nameTable->stringOffset = SkEndian_SwapBE16(stringOffset);
+
+ SkOTTableName::Record* nameRecords = reinterpret_cast<SkOTTableName::Record*>(data + originalDataSize + sizeof(SkOTTableName));
+ for (int i = 0; i < namesCount; ++i) {
+ nameRecords[i].platformID.value = SkOTTableName::Record::PlatformID::Windows;
+ nameRecords[i].encodingID.windows.value = SkOTTableName::Record::EncodingID::Windows::UnicodeBMPUCS2;
+ nameRecords[i].languageID.windows.value = SkOTTableName::Record::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();
+}
+
+
+SkOTUtils::LocalizedStrings_NameTable*
+SkOTUtils::LocalizedStrings_NameTable::CreateForFamilyNames(const SkTypeface& typeface) {
+ static const SkFontTableTag nameTag = SkSetFourByteTag('n','a','m','e');
+ size_t nameTableSize = typeface.getTableSize(nameTag);
+ if (0 == nameTableSize) {
+ return NULL;
+ }
+ SkAutoTDeleteArray<uint8_t> nameTableData(new uint8_t[nameTableSize]);
+ size_t copied = typeface.getTableData(nameTag, 0, nameTableSize, nameTableData.get());
+ if (copied != nameTableSize) {
+ return NULL;
+ }
+
+ return new SkOTUtils::LocalizedStrings_NameTable((SkOTTableName*)nameTableData.detach(),
+ SkOTUtils::LocalizedStrings_NameTable::familyNameTypes,
+ SK_ARRAY_COUNT(SkOTUtils::LocalizedStrings_NameTable::familyNameTypes));
+}
+
+bool SkOTUtils::LocalizedStrings_NameTable::next(SkTypeface::LocalizedString* localizedString) {
+ do {
+ SkOTTableName::Iterator::Record record;
+ if (fFamilyNameIter.next(record)) {
+ localizedString->fString = record.name;
+ localizedString->fLanguage = record.language;
+ return true;
+ }
+ if (fTypesCount == fTypesIndex + 1) {
+ return false;
+ }
+ ++fTypesIndex;
+ fFamilyNameIter.reset(fTypes[fTypesIndex]);
+ } while (true);
+}
+
+SkOTTableName::Record::NameID::Predefined::Value
+SkOTUtils::LocalizedStrings_NameTable::familyNameTypes[3] = {
+ SkOTTableName::Record::NameID::Predefined::FontFamilyName,
+ SkOTTableName::Record::NameID::Predefined::PreferredFamily,
+ SkOTTableName::Record::NameID::Predefined::WWSFamilyName,
+};
diff --git a/sfnt/SkOTUtils.h b/sfnt/SkOTUtils.h
new file mode 100644
index 00000000..4825fbeb
--- /dev/null
+++ b/sfnt/SkOTUtils.h
@@ -0,0 +1,89 @@
+/*
+ * 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"
+#include "SkOTTable_name.h"
+#include "SkTypeface.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);
+
+ /** An implementation of LocalizedStrings which obtains it's data from a 'name' table. */
+ class LocalizedStrings_NameTable : public SkTypeface::LocalizedStrings {
+ public:
+ /** Takes ownership of the nameTableData and will free it with SK_DELETE. */
+ LocalizedStrings_NameTable(SkOTTableName* nameTableData,
+ SkOTTableName::Record::NameID::Predefined::Value types[],
+ int typesCount)
+ : fTypes(types), fTypesCount(typesCount), fTypesIndex(0)
+ , fNameTableData(nameTableData), fFamilyNameIter(*nameTableData, fTypes[fTypesIndex])
+ { }
+
+ /** Creates an iterator over all the family names in the 'name' table of a typeface.
+ * If no valid 'name' table can be found, returns NULL.
+ */
+ static LocalizedStrings_NameTable* CreateForFamilyNames(const SkTypeface& typeface);
+
+ virtual bool next(SkTypeface::LocalizedString* localizedString) SK_OVERRIDE;
+ private:
+ static SkOTTableName::Record::NameID::Predefined::Value familyNameTypes[3];
+
+ SkOTTableName::Record::NameID::Predefined::Value* fTypes;
+ int fTypesCount;
+ int fTypesIndex;
+ SkAutoTDeleteArray<SkOTTableName> fNameTableData;
+ SkOTTableName::Iterator fFamilyNameIter;
+ };
+
+ /** An implementation of LocalizedStrings which has one name. */
+ class LocalizedStrings_SingleName : public SkTypeface::LocalizedStrings {
+ public:
+ LocalizedStrings_SingleName(SkString name, SkString language)
+ : fName(name), fLanguage(language), fHasNext(true)
+ { }
+
+ virtual bool next(SkTypeface::LocalizedString* localizedString) SK_OVERRIDE {
+ localizedString->fString = fName;
+ localizedString->fLanguage = fLanguage;
+
+ bool hadNext = fHasNext;
+ fHasNext = false;
+ return hadNext;
+ }
+
+ private:
+ SkString fName;
+ SkString fLanguage;
+ bool fHasNext;
+ };
+};
+
+#endif
diff --git a/sfnt/SkPanose.h b/sfnt/SkPanose.h
new file mode 100644
index 00000000..873c093f
--- /dev/null
+++ b/sfnt/SkPanose.h
@@ -0,0 +1,639 @@
+/*
+ * 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 SkPanose_DEFINED
+#define SkPanose_DEFINED
+
+#include "SkOTTableTypes.h"
+#include "SkTypedEnum.h"
+
+#pragma pack(push, 1)
+
+struct SkPanose {
+ //This value changes the meaning of the following 9 bytes.
+ struct FamilyType {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((TextAndDisplay, 2))
+ ((Script, 3))
+ ((Decorative, 4))
+ ((Pictoral, 5))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bFamilyType;
+
+ union Data {
+ struct TextAndDisplay {
+ struct SerifStyle {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Cove, 2))
+ ((ObtuseCove, 3))
+ ((SquareCove, 4))
+ ((ObtuseSquareCove, 5))
+ ((Square, 6))
+ ((Thin, 7))
+ ((Bone, 8))
+ ((Exaggerated, 9))
+ ((Triangle, 10))
+ ((NormalSans, 11))
+ ((ObtuseSans, 12))
+ ((PerpSans, 13))
+ ((Flared, 14))
+ ((Rounded, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bSerifStyle;
+
+ struct Weight {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((VeryLight, 2))
+ ((Light, 3))
+ ((Thin, 4))
+ ((Book, 5))
+ ((Medium, 6))
+ ((Demi, 7))
+ ((Bold, 8))
+ ((Heavy, 9))
+ ((Black, 10))
+ ((ExtraBlack, 11))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bWeight;
+
+ struct Proportion {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((OldStyle, 2))
+ ((Modern, 3))
+ ((EvenWidth, 4))
+ ((Expanded, 5))
+ ((Condensed, 6))
+ ((VeryExpanded, 7))
+ ((VeryCondensed, 8))
+ ((Monospaced, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bProportion;
+
+ struct Contrast {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None, 2))
+ ((VeryLow, 3))
+ ((Low, 4))
+ ((MediumLow, 5))
+ ((Medium, 6))
+ ((MediumHigh, 7))
+ ((High, 8))
+ ((VeryHigh, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bContrast;
+
+#ifdef SK_WIN_PANOSE
+ //This is what Windows (and FontForge and Apple TT spec) define.
+ //The Impact font uses 9.
+ struct StrokeVariation {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((GradualDiagonal, 2))
+ ((GradualTransitional, 3))
+ ((GradualVertical, 4))
+ ((GradualHorizontal, 5))
+ ((RapidVertical, 6))
+ ((RapidHorizontal, 7))
+ ((InstantVertical, 8))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bStrokeVariation;
+#else
+ //Stroke variation description in OT OS/2 ver0,ver1 is incorrect.
+ //This is what HP Panose says.
+ struct StrokeVariation {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((NoVariation, 2))
+ ((Gradual_Diagonal, 3))
+ ((Gradual_Transitional, 4))
+ ((Gradual_Vertical, 5))
+ ((Gradual_Horizontal, 6))
+ ((Rapid_Vertical, 7))
+ ((Rapid_Horizontal, 8))
+ ((Instant_Vertical, 9))
+ ((Instant_Horizontal, 10))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bStrokeVariation;
+#endif
+
+ struct ArmStyle {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((StraightArms_Horizontal, 2))
+ ((StraightArms_Wedge, 3))
+ ((StraightArms_Vertical, 4))
+ ((StraightArms_SingleSerif, 5))
+ ((StraightArms_DoubleSerif, 6))
+ ((NonStraightArms_Horizontal, 7))
+ ((NonStraightArms_Wedge, 8))
+ ((NonStraightArms_Vertical, 9))
+ ((NonStraightArms_SingleSerif, 10))
+ ((NonStraightArms_DoubleSerif, 11))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bArmStyle;
+
+ struct Letterform {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Normal_Contact, 2))
+ ((Normal_Weighted, 3))
+ ((Normal_Boxed, 4))
+ ((Normal_Flattened, 5))
+ ((Normal_Rounded, 6))
+ ((Normal_OffCenter, 7))
+ ((Normal_Square, 8))
+ ((Oblique_Contact, 9))
+ ((Oblique_Weighted, 10))
+ ((Oblique_Boxed, 11))
+ ((Oblique_Flattened, 12))
+ ((Oblique_Rounded, 13))
+ ((Oblique_OffCenter, 14))
+ ((Oblique_Square, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bLetterform;
+
+ struct Midline {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Standard_Trimmed, 2))
+ ((Standard_Pointed, 3))
+ ((Standard_Serifed, 4))
+ ((High_Trimmed, 5))
+ ((High_Pointed, 6))
+ ((High_Serifed, 7))
+ ((Constant_Trimmed, 8))
+ ((Constant_Pointed, 9))
+ ((Constant_Serifed, 10))
+ ((Low_Trimmed, 11))
+ ((Low_Pointed, 12))
+ ((Low_Serifed, 13))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bMidline;
+
+ struct XHeight {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Constant_Small, 2))
+ ((Constant_Standard, 3))
+ ((Constant_Large, 4))
+ ((Ducking_Small, 5))
+ ((Ducking_Standard, 6))
+ ((Ducking_Large, 7))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bXHeight;
+ } textAndDisplay;
+
+ struct Script {
+ struct ToolKind {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((FlatNib, 2))
+ ((PressurePoint, 3))
+ ((Engraved, 4))
+ ((Ball, 5))
+ ((Brush, 6))
+ ((Rough, 7))
+ ((FeltPen, 8))
+ ((WildBrush, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bToolKind;
+
+ struct Weight {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((VeryLight, 2))
+ ((Light, 3))
+ ((Thin, 4))
+ ((Book, 5))
+ ((Medium, 6))
+ ((Demi, 7))
+ ((Bold, 8))
+ ((Heavy, 9))
+ ((Black, 10))
+ ((ExtraBlack, 11))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bWeight;
+
+ struct Spacing {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((ProportionalSpaced, 2))
+ ((Monospaced, 3))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bSpacing;
+
+ struct AspectRatio {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((VeryCondensed, 2))
+ ((Condensed, 3))
+ ((Normal, 4))
+ ((Expanded, 5))
+ ((VeryExpanded, 6))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatio;
+
+ struct Contrast {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None, 2))
+ ((VeryLow, 3))
+ ((Low, 4))
+ ((MediumLow, 5))
+ ((Medium, 6))
+ ((MediumHigh, 7))
+ ((High, 8))
+ ((VeryHigh, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bContrast;
+
+ struct Topology {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Roman_Disconnected, 2))
+ ((Roman_Trailing, 3))
+ ((Roman_Connected, 4))
+ ((Cursive_Disconnected, 5))
+ ((Cursive_Trailing, 6))
+ ((Cursive_Connected, 7))
+ ((Blackletter_Disconnected, 8))
+ ((Blackletter_Trailing, 9))
+ ((Blackletter_Connected, 10))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bTopology;
+
+ struct Form {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Upright_NoWrapping, 2))
+ ((Upright_SomeWrapping, 3))
+ ((Upright_MoreWrapping, 4))
+ ((Upright_ExtremeWrapping, 5))
+ ((Oblique_NoWrapping, 6))
+ ((Oblique_SomeWrapping, 7))
+ ((Oblique_MoreWrapping, 8))
+ ((Oblique_ExtremeWrapping, 9))
+ ((Exaggerated_NoWrapping, 10))
+ ((Exaggerated_SomeWrapping, 11))
+ ((Exaggerated_MoreWrapping, 12))
+ ((Exaggerated_ExtremeWrapping, 13))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bForm;
+
+ struct Finials {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None_NoLoops, 2))
+ ((None_ClosedLoops, 3))
+ ((None_OpenLoops, 4))
+ ((Sharp_NoLoops, 5))
+ ((Sharp_ClosedLoops, 6))
+ ((Sharp_OpenLoops, 7))
+ ((Tapered_NoLoops, 8))
+ ((Tapered_ClosedLoops, 9))
+ ((Tapered_OpenLoops, 10))
+ ((Round_NoLoops, 11))
+ ((Round_ClosedLoops, 12))
+ ((Round_OpenLoops, 13))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bFinials;
+
+ struct XAscent {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((VeryLow, 2))
+ ((Low, 3))
+ ((Medium, 4))
+ ((High, 5))
+ ((VeryHigh, 6))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bXAscent;
+ } script;
+
+ struct Decorative {
+ struct Class {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Derivative, 2))
+ ((NonStandard_Topology, 3))
+ ((NonStandard_Elements, 4))
+ ((NonStandard_Aspect, 5))
+ ((Initials, 6))
+ ((Cartoon, 7))
+ ((PictureStems, 8))
+ ((Ornamented, 9))
+ ((TextAndBackground, 10))
+ ((Collage, 11))
+ ((Montage, 12))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bClass;
+
+ struct Weight {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((VeryLight, 2))
+ ((Light, 3))
+ ((Thin, 4))
+ ((Book, 5))
+ ((Medium, 6))
+ ((Demi, 7))
+ ((Bold, 8))
+ ((Heavy, 9))
+ ((Black, 10))
+ ((ExtraBlack, 11))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bWeight;
+
+ struct Aspect {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((SuperCondensed, 2))
+ ((VeryCondensed, 3))
+ ((Condensed, 4))
+ ((Normal, 5))
+ ((Extended, 6))
+ ((VeryExtended, 7))
+ ((SuperExtended, 8))
+ ((Monospaced, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspect;
+
+ struct Contrast {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None, 2))
+ ((VeryLow, 3))
+ ((Low, 4))
+ ((MediumLow, 5))
+ ((Medium, 6))
+ ((MediumHigh, 7))
+ ((High, 8))
+ ((VeryHigh, 9))
+ ((HorizontalLow, 10))
+ ((HorizontalMedium, 11))
+ ((HorizontalHigh, 12))
+ ((Broken, 13))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bContrast;
+
+ struct SerifVariant {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Cove, 2))
+ ((ObtuseCove, 3))
+ ((SquareCove, 4))
+ ((ObtuseSquareCove, 5))
+ ((Square, 6))
+ ((Thin, 7))
+ ((Oval, 8))
+ ((Exaggerated, 9))
+ ((Triangle, 10))
+ ((NormalSans, 11))
+ ((ObtuseSans, 12))
+ ((PerpendicularSans, 13))
+ ((Flared, 14))
+ ((Rounded, 15))
+ ((Script, 16))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bSerifVariant;
+
+ struct Treatment {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None_StandardSolidFill, 2))
+ ((White_NoFill, 3))
+ ((PatternedFill, 4))
+ ((ComplexFill, 5))
+ ((ShapedFill, 6))
+ ((DrawnDistressed, 7))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bTreatment;
+
+ struct Lining {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((None, 2))
+ ((Inline, 3))
+ ((Outline, 4))
+ ((Engraved, 5))
+ ((Shadow, 6))
+ ((Relief, 7))
+ ((Backdrop, 8))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bLining;
+
+ struct Topology {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Standard, 2))
+ ((Square, 3))
+ ((MultipleSegment, 4))
+ ((DecoWacoMidlines, 5))
+ ((UnevenWeighting, 6))
+ ((DiverseArms, 7))
+ ((DiverseForms, 8))
+ ((LombardicForms, 9))
+ ((UpperCaseInLowerCase, 10))
+ ((ImpliedTopology, 11))
+ ((HorseshoeEandA, 12))
+ ((Cursive, 13))
+ ((Blackletter, 14))
+ ((SwashVariance, 15))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bTopology;
+
+ struct RangeOfCharacters {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((ExtendedCollection, 2))
+ ((Litterals, 3))
+ ((NoLowerCase, 4))
+ ((SmallCaps, 5))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bRangeOfCharacters;
+ } decorative;
+
+ struct Pictoral {
+ struct Kind {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((Montages, 2))
+ ((Pictures, 3))
+ ((Shapes, 4))
+ ((Scientific, 5))
+ ((Music, 6))
+ ((Expert, 7))
+ ((Patterns, 8))
+ ((Boarders, 9))
+ ((Icons, 10))
+ ((Logos, 11))
+ ((IndustrySpecific, 12))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bKind;
+
+ struct Weight {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoFit, 1))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bWeight;
+
+ struct Spacing {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((ProportionalSpaced, 2))
+ ((Monospaced, 3))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bSpacing;
+
+ struct AspectRatioAndContrast {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((NoFit, 1))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatioAndContrast;
+
+ struct AspectRatio94 {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((NoWidth, 2))
+ ((ExceptionallyWide, 3))
+ ((SuperWide, 4))
+ ((VeryWide, 5))
+ ((Wide, 6))
+ ((Normal, 7))
+ ((Narrow, 8))
+ ((VeryNarrow, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatio94;
+
+ struct AspectRatio119 {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((NoWidth, 2))
+ ((ExceptionallyWide, 3))
+ ((SuperWide, 4))
+ ((VeryWide, 5))
+ ((Wide, 6))
+ ((Normal, 7))
+ ((Narrow, 8))
+ ((VeryNarrow, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatio119;
+
+ struct AspectRatio157 {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((NoWidth, 2))
+ ((ExceptionallyWide, 3))
+ ((SuperWide, 4))
+ ((VeryWide, 5))
+ ((Wide, 6))
+ ((Normal, 7))
+ ((Narrow, 8))
+ ((VeryNarrow, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatio157;
+
+ struct AspectRatio163 {
+ SK_TYPED_ENUM(Value, SK_OT_BYTE,
+ ((Any, 0))
+ ((NoFit, 1))
+ ((NoWidth, 2))
+ ((ExceptionallyWide, 3))
+ ((SuperWide, 4))
+ ((VeryWide, 5))
+ ((Wide, 6))
+ ((Normal, 7))
+ ((Narrow, 8))
+ ((VeryNarrow, 9))
+ SK_SEQ_END,
+ (value)SK_SEQ_END)
+ } bAspectRatio163;
+ } pictoral;
+ } data;
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkPanose) == 10, sizeof_SkPanose_not_10);
+
+#endif
diff --git a/sfnt/SkPreprocessorSeq.h b/sfnt/SkPreprocessorSeq.h
new file mode 100644
index 00000000..fed24181
--- /dev/null
+++ b/sfnt/SkPreprocessorSeq.h
@@ -0,0 +1,826 @@
+/*
+ * 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 SkPreprocessorSeq_DEFINED
+#define SkPreprocessorSeq_DEFINED
+
+#define SK_CONCAT(a, b) SK_CONCAT_I(a, b) // allow for macro expansion
+#define SK_CONCAT_I(a, b) a ## b
+
+//A preprocessor pair is of the form '(a, b)'
+#define SK_PAIR_FIRST(pair) SK_PAIR_FIRST_I pair
+#define SK_PAIR_FIRST_I(a, b) a
+#define SK_PAIR_SECOND(pair) SK_PAIR_SECOND_I pair
+#define SK_PAIR_SECOND_I(a, b) b
+
+//A preprocessor sequence is of the form (a)(b)(c)SK_SEQ_END
+#if 0
+//The following is what we logically want to do.
+//However, MSVC expands macros at the wrong time, causing an error on use of
+//SK_SEQ_IGNORE_SECOND_I because it formally takes two parameters, but we only
+//pass "one" parameter in SK_SEQ_IGNORE_SECOND.
+
+#define SK_SEQ_HEAD(seq) SK_SEQ_IGNORE_SECOND(SK_SEQ_FIRST seq)
+#define SK_SEQ_IGNORE_SECOND(x) SK_SEQ_IGNORE_SECOND_I(x) // expand x
+#define SK_SEQ_IGNORE_SECOND_I(x, _) x
+#define SK_SEQ_FIRST(x) x, SK_NIL
+
+#else
+
+//This is less obvious, but works on GCC, clang, and MSVC.
+#define SK_SEQ_HEAD(seq) SK_SEQ_HEAD_II((SK_SEQ_FIRST seq))
+#define SK_SEQ_HEAD_II(x) SK_SEQ_IGNORE_SECOND_I x
+#define SK_SEQ_IGNORE_SECOND_I(x, _) x
+#define SK_SEQ_FIRST(x) x, SK_NIL
+
+#endif
+
+#define SK_SEQ_TAIL(seq) SK_SEQ_TAIL_I seq
+#define SK_SEQ_TAIL_I(x)
+
+#define SK_SEQ_SIZE(seq) SK_CONCAT(SK_SEQ_SIZE_, SK_SEQ_SIZE_TERMINATOR seq)
+
+#define SK_SEQ_SIZE_TERMINATOR(_) SK_SEQ_SIZE_0
+#define SK_SEQ_SIZE_0(_) SK_SEQ_SIZE_1
+#define SK_SEQ_SIZE_1(_) SK_SEQ_SIZE_2
+#define SK_SEQ_SIZE_2(_) SK_SEQ_SIZE_3
+#define SK_SEQ_SIZE_3(_) SK_SEQ_SIZE_4
+#define SK_SEQ_SIZE_4(_) SK_SEQ_SIZE_5
+#define SK_SEQ_SIZE_5(_) SK_SEQ_SIZE_6
+#define SK_SEQ_SIZE_6(_) SK_SEQ_SIZE_7
+#define SK_SEQ_SIZE_7(_) SK_SEQ_SIZE_8
+#define SK_SEQ_SIZE_8(_) SK_SEQ_SIZE_9
+#define SK_SEQ_SIZE_9(_) SK_SEQ_SIZE_10
+#define SK_SEQ_SIZE_10(_) SK_SEQ_SIZE_11
+#define SK_SEQ_SIZE_11(_) SK_SEQ_SIZE_12
+#define SK_SEQ_SIZE_12(_) SK_SEQ_SIZE_13
+#define SK_SEQ_SIZE_13(_) SK_SEQ_SIZE_14
+#define SK_SEQ_SIZE_14(_) SK_SEQ_SIZE_15
+#define SK_SEQ_SIZE_15(_) SK_SEQ_SIZE_16
+#define SK_SEQ_SIZE_16(_) SK_SEQ_SIZE_17
+#define SK_SEQ_SIZE_17(_) SK_SEQ_SIZE_18
+#define SK_SEQ_SIZE_18(_) SK_SEQ_SIZE_19
+#define SK_SEQ_SIZE_19(_) SK_SEQ_SIZE_20
+#define SK_SEQ_SIZE_20(_) SK_SEQ_SIZE_21
+#define SK_SEQ_SIZE_21(_) SK_SEQ_SIZE_22
+#define SK_SEQ_SIZE_22(_) SK_SEQ_SIZE_23
+#define SK_SEQ_SIZE_23(_) SK_SEQ_SIZE_24
+#define SK_SEQ_SIZE_24(_) SK_SEQ_SIZE_25
+#define SK_SEQ_SIZE_25(_) SK_SEQ_SIZE_26
+#define SK_SEQ_SIZE_26(_) SK_SEQ_SIZE_27
+#define SK_SEQ_SIZE_27(_) SK_SEQ_SIZE_28
+#define SK_SEQ_SIZE_28(_) SK_SEQ_SIZE_29
+#define SK_SEQ_SIZE_29(_) SK_SEQ_SIZE_30
+#define SK_SEQ_SIZE_30(_) SK_SEQ_SIZE_31
+#define SK_SEQ_SIZE_31(_) SK_SEQ_SIZE_32
+#define SK_SEQ_SIZE_32(_) SK_SEQ_SIZE_33
+#define SK_SEQ_SIZE_33(_) SK_SEQ_SIZE_34
+#define SK_SEQ_SIZE_34(_) SK_SEQ_SIZE_35
+#define SK_SEQ_SIZE_35(_) SK_SEQ_SIZE_36
+#define SK_SEQ_SIZE_36(_) SK_SEQ_SIZE_37
+#define SK_SEQ_SIZE_37(_) SK_SEQ_SIZE_38
+#define SK_SEQ_SIZE_38(_) SK_SEQ_SIZE_39
+#define SK_SEQ_SIZE_39(_) SK_SEQ_SIZE_40
+#define SK_SEQ_SIZE_40(_) SK_SEQ_SIZE_41
+#define SK_SEQ_SIZE_41(_) SK_SEQ_SIZE_42
+#define SK_SEQ_SIZE_42(_) SK_SEQ_SIZE_43
+#define SK_SEQ_SIZE_43(_) SK_SEQ_SIZE_44
+#define SK_SEQ_SIZE_44(_) SK_SEQ_SIZE_45
+#define SK_SEQ_SIZE_45(_) SK_SEQ_SIZE_46
+#define SK_SEQ_SIZE_46(_) SK_SEQ_SIZE_47
+#define SK_SEQ_SIZE_47(_) SK_SEQ_SIZE_48
+#define SK_SEQ_SIZE_48(_) SK_SEQ_SIZE_49
+#define SK_SEQ_SIZE_49(_) SK_SEQ_SIZE_50
+#define SK_SEQ_SIZE_50(_) SK_SEQ_SIZE_51
+#define SK_SEQ_SIZE_51(_) SK_SEQ_SIZE_52
+#define SK_SEQ_SIZE_52(_) SK_SEQ_SIZE_53
+#define SK_SEQ_SIZE_53(_) SK_SEQ_SIZE_54
+#define SK_SEQ_SIZE_54(_) SK_SEQ_SIZE_55
+#define SK_SEQ_SIZE_55(_) SK_SEQ_SIZE_56
+#define SK_SEQ_SIZE_56(_) SK_SEQ_SIZE_57
+#define SK_SEQ_SIZE_57(_) SK_SEQ_SIZE_58
+#define SK_SEQ_SIZE_58(_) SK_SEQ_SIZE_59
+#define SK_SEQ_SIZE_59(_) SK_SEQ_SIZE_60
+#define SK_SEQ_SIZE_60(_) SK_SEQ_SIZE_61
+#define SK_SEQ_SIZE_61(_) SK_SEQ_SIZE_62
+#define SK_SEQ_SIZE_62(_) SK_SEQ_SIZE_63
+#define SK_SEQ_SIZE_63(_) SK_SEQ_SIZE_64
+#define SK_SEQ_SIZE_64(_) SK_SEQ_SIZE_65
+#define SK_SEQ_SIZE_65(_) SK_SEQ_SIZE_66
+#define SK_SEQ_SIZE_66(_) SK_SEQ_SIZE_67
+#define SK_SEQ_SIZE_67(_) SK_SEQ_SIZE_68
+#define SK_SEQ_SIZE_68(_) SK_SEQ_SIZE_69
+#define SK_SEQ_SIZE_69(_) SK_SEQ_SIZE_70
+#define SK_SEQ_SIZE_70(_) SK_SEQ_SIZE_71
+#define SK_SEQ_SIZE_71(_) SK_SEQ_SIZE_72
+#define SK_SEQ_SIZE_72(_) SK_SEQ_SIZE_73
+#define SK_SEQ_SIZE_73(_) SK_SEQ_SIZE_74
+#define SK_SEQ_SIZE_74(_) SK_SEQ_SIZE_75
+#define SK_SEQ_SIZE_75(_) SK_SEQ_SIZE_76
+#define SK_SEQ_SIZE_76(_) SK_SEQ_SIZE_77
+#define SK_SEQ_SIZE_77(_) SK_SEQ_SIZE_78
+#define SK_SEQ_SIZE_78(_) SK_SEQ_SIZE_79
+#define SK_SEQ_SIZE_79(_) SK_SEQ_SIZE_80
+#define SK_SEQ_SIZE_80(_) SK_SEQ_SIZE_81
+#define SK_SEQ_SIZE_81(_) SK_SEQ_SIZE_82
+#define SK_SEQ_SIZE_82(_) SK_SEQ_SIZE_83
+#define SK_SEQ_SIZE_83(_) SK_SEQ_SIZE_84
+#define SK_SEQ_SIZE_84(_) SK_SEQ_SIZE_85
+#define SK_SEQ_SIZE_85(_) SK_SEQ_SIZE_86
+#define SK_SEQ_SIZE_86(_) SK_SEQ_SIZE_87
+#define SK_SEQ_SIZE_87(_) SK_SEQ_SIZE_88
+#define SK_SEQ_SIZE_88(_) SK_SEQ_SIZE_89
+#define SK_SEQ_SIZE_89(_) SK_SEQ_SIZE_90
+#define SK_SEQ_SIZE_90(_) SK_SEQ_SIZE_91
+#define SK_SEQ_SIZE_91(_) SK_SEQ_SIZE_92
+#define SK_SEQ_SIZE_92(_) SK_SEQ_SIZE_93
+#define SK_SEQ_SIZE_93(_) SK_SEQ_SIZE_94
+#define SK_SEQ_SIZE_94(_) SK_SEQ_SIZE_95
+#define SK_SEQ_SIZE_95(_) SK_SEQ_SIZE_96
+#define SK_SEQ_SIZE_96(_) SK_SEQ_SIZE_97
+#define SK_SEQ_SIZE_97(_) SK_SEQ_SIZE_98
+#define SK_SEQ_SIZE_98(_) SK_SEQ_SIZE_99
+#define SK_SEQ_SIZE_99(_) SK_SEQ_SIZE_100
+#define SK_SEQ_SIZE_100(_) SK_SEQ_SIZE_101
+#define SK_SEQ_SIZE_101(_) SK_SEQ_SIZE_102
+#define SK_SEQ_SIZE_102(_) SK_SEQ_SIZE_103
+#define SK_SEQ_SIZE_103(_) SK_SEQ_SIZE_104
+#define SK_SEQ_SIZE_104(_) SK_SEQ_SIZE_105
+#define SK_SEQ_SIZE_105(_) SK_SEQ_SIZE_106
+#define SK_SEQ_SIZE_106(_) SK_SEQ_SIZE_107
+#define SK_SEQ_SIZE_107(_) SK_SEQ_SIZE_108
+#define SK_SEQ_SIZE_108(_) SK_SEQ_SIZE_109
+#define SK_SEQ_SIZE_109(_) SK_SEQ_SIZE_110
+#define SK_SEQ_SIZE_110(_) SK_SEQ_SIZE_111
+#define SK_SEQ_SIZE_111(_) SK_SEQ_SIZE_112
+#define SK_SEQ_SIZE_112(_) SK_SEQ_SIZE_113
+#define SK_SEQ_SIZE_113(_) SK_SEQ_SIZE_114
+#define SK_SEQ_SIZE_114(_) SK_SEQ_SIZE_115
+#define SK_SEQ_SIZE_115(_) SK_SEQ_SIZE_116
+#define SK_SEQ_SIZE_116(_) SK_SEQ_SIZE_117
+#define SK_SEQ_SIZE_117(_) SK_SEQ_SIZE_118
+#define SK_SEQ_SIZE_118(_) SK_SEQ_SIZE_119
+#define SK_SEQ_SIZE_119(_) SK_SEQ_SIZE_120
+#define SK_SEQ_SIZE_120(_) SK_SEQ_SIZE_121
+#define SK_SEQ_SIZE_121(_) SK_SEQ_SIZE_122
+#define SK_SEQ_SIZE_122(_) SK_SEQ_SIZE_123
+#define SK_SEQ_SIZE_123(_) SK_SEQ_SIZE_124
+#define SK_SEQ_SIZE_124(_) SK_SEQ_SIZE_125
+#define SK_SEQ_SIZE_125(_) SK_SEQ_SIZE_126
+#define SK_SEQ_SIZE_126(_) SK_SEQ_SIZE_127
+#define SK_SEQ_SIZE_127(_) SK_SEQ_SIZE_128
+#define SK_SEQ_SIZE_128(_) SK_SEQ_SIZE_129
+#define SK_SEQ_SIZE_129(_) SK_SEQ_SIZE_130
+#define SK_SEQ_SIZE_130(_) SK_SEQ_SIZE_131
+#define SK_SEQ_SIZE_131(_) SK_SEQ_SIZE_132
+#define SK_SEQ_SIZE_132(_) SK_SEQ_SIZE_133
+#define SK_SEQ_SIZE_133(_) SK_SEQ_SIZE_134
+#define SK_SEQ_SIZE_134(_) SK_SEQ_SIZE_135
+#define SK_SEQ_SIZE_135(_) SK_SEQ_SIZE_136
+#define SK_SEQ_SIZE_136(_) SK_SEQ_SIZE_137
+#define SK_SEQ_SIZE_137(_) SK_SEQ_SIZE_138
+#define SK_SEQ_SIZE_138(_) SK_SEQ_SIZE_139
+#define SK_SEQ_SIZE_139(_) SK_SEQ_SIZE_140
+#define SK_SEQ_SIZE_140(_) SK_SEQ_SIZE_141
+#define SK_SEQ_SIZE_141(_) SK_SEQ_SIZE_142
+#define SK_SEQ_SIZE_142(_) SK_SEQ_SIZE_143
+#define SK_SEQ_SIZE_143(_) SK_SEQ_SIZE_144
+#define SK_SEQ_SIZE_144(_) SK_SEQ_SIZE_145
+#define SK_SEQ_SIZE_145(_) SK_SEQ_SIZE_146
+#define SK_SEQ_SIZE_146(_) SK_SEQ_SIZE_147
+#define SK_SEQ_SIZE_147(_) SK_SEQ_SIZE_148
+#define SK_SEQ_SIZE_148(_) SK_SEQ_SIZE_149
+#define SK_SEQ_SIZE_149(_) SK_SEQ_SIZE_150
+#define SK_SEQ_SIZE_150(_) SK_SEQ_SIZE_151
+#define SK_SEQ_SIZE_151(_) SK_SEQ_SIZE_152
+#define SK_SEQ_SIZE_152(_) SK_SEQ_SIZE_153
+#define SK_SEQ_SIZE_153(_) SK_SEQ_SIZE_154
+#define SK_SEQ_SIZE_154(_) SK_SEQ_SIZE_155
+#define SK_SEQ_SIZE_155(_) SK_SEQ_SIZE_156
+#define SK_SEQ_SIZE_156(_) SK_SEQ_SIZE_157
+#define SK_SEQ_SIZE_157(_) SK_SEQ_SIZE_158
+#define SK_SEQ_SIZE_158(_) SK_SEQ_SIZE_159
+#define SK_SEQ_SIZE_159(_) SK_SEQ_SIZE_160
+#define SK_SEQ_SIZE_160(_) SK_SEQ_SIZE_161
+#define SK_SEQ_SIZE_161(_) SK_SEQ_SIZE_162
+#define SK_SEQ_SIZE_162(_) SK_SEQ_SIZE_163
+#define SK_SEQ_SIZE_163(_) SK_SEQ_SIZE_164
+#define SK_SEQ_SIZE_164(_) SK_SEQ_SIZE_165
+#define SK_SEQ_SIZE_165(_) SK_SEQ_SIZE_166
+#define SK_SEQ_SIZE_166(_) SK_SEQ_SIZE_167
+#define SK_SEQ_SIZE_167(_) SK_SEQ_SIZE_168
+#define SK_SEQ_SIZE_168(_) SK_SEQ_SIZE_169
+#define SK_SEQ_SIZE_169(_) SK_SEQ_SIZE_170
+#define SK_SEQ_SIZE_170(_) SK_SEQ_SIZE_171
+#define SK_SEQ_SIZE_171(_) SK_SEQ_SIZE_172
+#define SK_SEQ_SIZE_172(_) SK_SEQ_SIZE_173
+#define SK_SEQ_SIZE_173(_) SK_SEQ_SIZE_174
+#define SK_SEQ_SIZE_174(_) SK_SEQ_SIZE_175
+#define SK_SEQ_SIZE_175(_) SK_SEQ_SIZE_176
+#define SK_SEQ_SIZE_176(_) SK_SEQ_SIZE_177
+#define SK_SEQ_SIZE_177(_) SK_SEQ_SIZE_178
+#define SK_SEQ_SIZE_178(_) SK_SEQ_SIZE_179
+#define SK_SEQ_SIZE_179(_) SK_SEQ_SIZE_180
+#define SK_SEQ_SIZE_180(_) SK_SEQ_SIZE_181
+#define SK_SEQ_SIZE_181(_) SK_SEQ_SIZE_182
+#define SK_SEQ_SIZE_182(_) SK_SEQ_SIZE_183
+#define SK_SEQ_SIZE_183(_) SK_SEQ_SIZE_184
+#define SK_SEQ_SIZE_184(_) SK_SEQ_SIZE_185
+#define SK_SEQ_SIZE_185(_) SK_SEQ_SIZE_186
+#define SK_SEQ_SIZE_186(_) SK_SEQ_SIZE_187
+#define SK_SEQ_SIZE_187(_) SK_SEQ_SIZE_188
+#define SK_SEQ_SIZE_188(_) SK_SEQ_SIZE_189
+#define SK_SEQ_SIZE_189(_) SK_SEQ_SIZE_190
+#define SK_SEQ_SIZE_190(_) SK_SEQ_SIZE_191
+#define SK_SEQ_SIZE_191(_) SK_SEQ_SIZE_192
+#define SK_SEQ_SIZE_192(_) SK_SEQ_SIZE_193
+#define SK_SEQ_SIZE_193(_) SK_SEQ_SIZE_194
+#define SK_SEQ_SIZE_194(_) SK_SEQ_SIZE_195
+#define SK_SEQ_SIZE_195(_) SK_SEQ_SIZE_196
+#define SK_SEQ_SIZE_196(_) SK_SEQ_SIZE_197
+#define SK_SEQ_SIZE_197(_) SK_SEQ_SIZE_198
+#define SK_SEQ_SIZE_198(_) SK_SEQ_SIZE_199
+#define SK_SEQ_SIZE_199(_) SK_SEQ_SIZE_200
+#define SK_SEQ_SIZE_200(_) SK_SEQ_SIZE_201
+#define SK_SEQ_SIZE_201(_) SK_SEQ_SIZE_202
+#define SK_SEQ_SIZE_202(_) SK_SEQ_SIZE_203
+#define SK_SEQ_SIZE_203(_) SK_SEQ_SIZE_204
+#define SK_SEQ_SIZE_204(_) SK_SEQ_SIZE_205
+#define SK_SEQ_SIZE_205(_) SK_SEQ_SIZE_206
+#define SK_SEQ_SIZE_206(_) SK_SEQ_SIZE_207
+#define SK_SEQ_SIZE_207(_) SK_SEQ_SIZE_208
+#define SK_SEQ_SIZE_208(_) SK_SEQ_SIZE_209
+#define SK_SEQ_SIZE_209(_) SK_SEQ_SIZE_210
+#define SK_SEQ_SIZE_210(_) SK_SEQ_SIZE_211
+#define SK_SEQ_SIZE_211(_) SK_SEQ_SIZE_212
+#define SK_SEQ_SIZE_212(_) SK_SEQ_SIZE_213
+#define SK_SEQ_SIZE_213(_) SK_SEQ_SIZE_214
+#define SK_SEQ_SIZE_214(_) SK_SEQ_SIZE_215
+#define SK_SEQ_SIZE_215(_) SK_SEQ_SIZE_216
+#define SK_SEQ_SIZE_216(_) SK_SEQ_SIZE_217
+#define SK_SEQ_SIZE_217(_) SK_SEQ_SIZE_218
+#define SK_SEQ_SIZE_218(_) SK_SEQ_SIZE_219
+#define SK_SEQ_SIZE_219(_) SK_SEQ_SIZE_220
+#define SK_SEQ_SIZE_220(_) SK_SEQ_SIZE_221
+#define SK_SEQ_SIZE_221(_) SK_SEQ_SIZE_222
+#define SK_SEQ_SIZE_222(_) SK_SEQ_SIZE_223
+#define SK_SEQ_SIZE_223(_) SK_SEQ_SIZE_224
+#define SK_SEQ_SIZE_224(_) SK_SEQ_SIZE_225
+#define SK_SEQ_SIZE_225(_) SK_SEQ_SIZE_226
+#define SK_SEQ_SIZE_226(_) SK_SEQ_SIZE_227
+#define SK_SEQ_SIZE_227(_) SK_SEQ_SIZE_228
+#define SK_SEQ_SIZE_228(_) SK_SEQ_SIZE_229
+#define SK_SEQ_SIZE_229(_) SK_SEQ_SIZE_230
+#define SK_SEQ_SIZE_230(_) SK_SEQ_SIZE_231
+#define SK_SEQ_SIZE_231(_) SK_SEQ_SIZE_232
+#define SK_SEQ_SIZE_232(_) SK_SEQ_SIZE_233
+#define SK_SEQ_SIZE_233(_) SK_SEQ_SIZE_234
+#define SK_SEQ_SIZE_234(_) SK_SEQ_SIZE_235
+#define SK_SEQ_SIZE_235(_) SK_SEQ_SIZE_236
+#define SK_SEQ_SIZE_236(_) SK_SEQ_SIZE_237
+#define SK_SEQ_SIZE_237(_) SK_SEQ_SIZE_238
+#define SK_SEQ_SIZE_238(_) SK_SEQ_SIZE_239
+#define SK_SEQ_SIZE_239(_) SK_SEQ_SIZE_240
+#define SK_SEQ_SIZE_240(_) SK_SEQ_SIZE_241
+#define SK_SEQ_SIZE_241(_) SK_SEQ_SIZE_242
+#define SK_SEQ_SIZE_242(_) SK_SEQ_SIZE_243
+#define SK_SEQ_SIZE_243(_) SK_SEQ_SIZE_244
+#define SK_SEQ_SIZE_244(_) SK_SEQ_SIZE_245
+#define SK_SEQ_SIZE_245(_) SK_SEQ_SIZE_246
+#define SK_SEQ_SIZE_246(_) SK_SEQ_SIZE_247
+#define SK_SEQ_SIZE_247(_) SK_SEQ_SIZE_248
+#define SK_SEQ_SIZE_248(_) SK_SEQ_SIZE_249
+#define SK_SEQ_SIZE_249(_) SK_SEQ_SIZE_250
+#define SK_SEQ_SIZE_250(_) SK_SEQ_SIZE_251
+#define SK_SEQ_SIZE_251(_) SK_SEQ_SIZE_252
+#define SK_SEQ_SIZE_252(_) SK_SEQ_SIZE_253
+#define SK_SEQ_SIZE_253(_) SK_SEQ_SIZE_254
+#define SK_SEQ_SIZE_254(_) SK_SEQ_SIZE_255
+#define SK_SEQ_SIZE_255(_) SK_SEQ_SIZE_256
+
+
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_0 0
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_1 1
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_2 2
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_3 3
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_4 4
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_5 5
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_6 6
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_7 7
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_8 8
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_9 9
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_10 10
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_11 11
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_12 12
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_13 13
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_14 14
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_15 15
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_16 16
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_17 17
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_18 18
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_19 19
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_20 20
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_21 21
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_22 22
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_23 23
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_24 24
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_25 25
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_26 26
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_27 27
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_28 28
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_29 29
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_30 30
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_31 31
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_32 32
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_33 33
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_34 34
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_35 35
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_36 36
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_37 37
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_38 38
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_39 39
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_40 40
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_41 41
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_42 42
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_43 43
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_44 44
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_45 45
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_46 46
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_47 47
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_48 48
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_49 49
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_50 50
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_51 51
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_52 52
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_53 53
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_54 54
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_55 55
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_56 56
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_57 57
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_58 58
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_59 59
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_60 60
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_61 61
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_62 62
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_63 63
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_64 64
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_65 65
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_66 66
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_67 67
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_68 68
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_69 69
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_70 70
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_71 71
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_72 72
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_73 73
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_74 74
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_75 75
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_76 76
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_77 77
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_78 78
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_79 79
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_80 80
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_81 81
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_82 82
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_83 83
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_84 84
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_85 85
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_86 86
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_87 87
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_88 88
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_89 89
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_90 90
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_91 91
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_92 92
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_93 93
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_94 94
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_95 95
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_96 96
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_97 97
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_98 98
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_99 99
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_100 100
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_101 101
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_102 102
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_103 103
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_104 104
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_105 105
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_106 106
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_107 107
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_108 108
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_109 109
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_110 110
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_111 111
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_112 112
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_113 113
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_114 114
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_115 115
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_116 116
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_117 117
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_118 118
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_119 119
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_120 120
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_121 121
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_122 122
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_123 123
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_124 124
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_125 125
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_126 126
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_127 127
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_128 128
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_129 129
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_130 130
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_131 131
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_132 132
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_133 133
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_134 134
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_135 135
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_136 136
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_137 137
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_138 138
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_139 139
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_140 140
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_141 141
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_142 142
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_143 143
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_144 144
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_145 145
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_146 146
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_147 147
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_148 148
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_149 149
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_150 150
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_151 151
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_152 152
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_153 153
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_154 154
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_155 155
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_156 156
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_157 157
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_158 158
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_159 159
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_160 160
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_161 161
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_162 162
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_163 163
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_164 164
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_165 165
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_166 166
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_167 167
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_168 168
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_169 169
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_170 170
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_171 171
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_172 172
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_173 173
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_174 174
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_175 175
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_176 176
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_177 177
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_178 178
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_179 179
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_180 180
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_181 181
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_182 182
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_183 183
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_184 184
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_185 185
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_186 186
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_187 187
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_188 188
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_189 189
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_190 190
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_191 191
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_192 192
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_193 193
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_194 194
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_195 195
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_196 196
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_197 197
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_198 198
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_199 199
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_200 200
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_201 201
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_202 202
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_203 203
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_204 204
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_205 205
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_206 206
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_207 207
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_208 208
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_209 209
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_210 210
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_211 211
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_212 212
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_213 213
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_214 214
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_215 215
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_216 216
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_217 217
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_218 218
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_219 219
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_220 220
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_221 221
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_222 222
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_223 223
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_224 224
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_225 225
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_226 226
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_227 227
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_228 228
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_229 229
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_230 230
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_231 231
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_232 232
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_233 233
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_234 234
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_235 235
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_236 236
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_237 237
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_238 238
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_239 239
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_240 240
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_241 241
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_242 242
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_243 243
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_244 244
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_245 245
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_246 246
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_247 247
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_248 248
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_249 249
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_250 250
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_251 251
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_252 252
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_253 253
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_254 254
+#define SK_SEQ_SIZE_SK_SEQ_SIZE_255 255
+
+
+#define SK_SEQ_FOREACH(op, data, seq) SK_SEQ_FOREACH_L(op, op, data, seq)
+#define SK_SEQ_FOREACH_L(op, lop, data, seq) SK_CONCAT(SK_SEQ_FOREACH_, SK_SEQ_SIZE(seq)) (op, lop, data, SK_SEQ_HEAD(seq), SK_SEQ_TAIL(seq))
+
+#define SK_SEQ_FOREACH_0(op,lop,d,x,t)
+#define SK_SEQ_FOREACH_1(op,lop,d,x,t) lop(d,x)
+#define SK_SEQ_FOREACH_2(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_1(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_3(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_2(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_4(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_3(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_5(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_4(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_6(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_5(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_7(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_6(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_8(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_7(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_9(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_8(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_10(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_9(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_11(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_10(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_12(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_11(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_13(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_12(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_14(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_13(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_15(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_14(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_16(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_15(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_17(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_16(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_18(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_17(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_19(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_18(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_20(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_19(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_21(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_20(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_22(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_21(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_23(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_22(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_24(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_23(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_25(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_24(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_26(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_25(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_27(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_26(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_28(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_27(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_29(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_28(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_30(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_29(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_31(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_30(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_32(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_31(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_33(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_32(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_34(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_33(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_35(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_34(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_36(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_35(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_37(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_36(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_38(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_37(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_39(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_38(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_40(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_39(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_41(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_40(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_42(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_41(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_43(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_42(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_44(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_43(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_45(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_44(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_46(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_45(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_47(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_46(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_48(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_47(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_49(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_48(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_50(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_49(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_51(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_50(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_52(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_51(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_53(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_52(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_54(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_53(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_55(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_54(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_56(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_55(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_57(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_56(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_58(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_57(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_59(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_58(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_60(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_59(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_61(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_60(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_62(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_61(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_63(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_62(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_64(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_63(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_65(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_64(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_66(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_65(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_67(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_66(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_68(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_67(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_69(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_68(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_70(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_69(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_71(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_70(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_72(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_71(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_73(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_72(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_74(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_73(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_75(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_74(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_76(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_75(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_77(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_76(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_78(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_77(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_79(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_78(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_80(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_79(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_81(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_80(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_82(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_81(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_83(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_82(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_84(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_83(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_85(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_84(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_86(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_85(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_87(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_86(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_88(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_87(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_89(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_88(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_90(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_89(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_91(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_90(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_92(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_91(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_93(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_92(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_94(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_93(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_95(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_94(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_96(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_95(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_97(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_96(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_98(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_97(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_99(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_98(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_100(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_99(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_101(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_100(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_102(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_101(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_103(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_102(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_104(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_103(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_105(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_104(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_106(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_105(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_107(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_106(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_108(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_107(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_109(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_108(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_110(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_109(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_111(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_110(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_112(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_111(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_113(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_112(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_114(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_113(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_115(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_114(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_116(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_115(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_117(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_116(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_118(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_117(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_119(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_118(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_120(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_119(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_121(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_120(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_122(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_121(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_123(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_122(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_124(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_123(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_125(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_124(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_126(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_125(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_127(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_126(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_128(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_127(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_129(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_128(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_130(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_129(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_131(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_130(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_132(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_131(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_133(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_132(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_134(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_133(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_135(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_134(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_136(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_135(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_137(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_136(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_138(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_137(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_139(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_138(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_140(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_139(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_141(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_140(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_142(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_141(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_143(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_142(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_144(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_143(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_145(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_144(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_146(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_145(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_147(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_146(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_148(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_147(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_149(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_148(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_150(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_149(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_151(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_150(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_152(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_151(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_153(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_152(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_154(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_153(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_155(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_154(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_156(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_155(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_157(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_156(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_158(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_157(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_159(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_158(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_160(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_159(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_161(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_160(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_162(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_161(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_163(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_162(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_164(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_163(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_165(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_164(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_166(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_165(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_167(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_166(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_168(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_167(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_169(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_168(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_170(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_169(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_171(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_170(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_172(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_171(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_173(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_172(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_174(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_173(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_175(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_174(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_176(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_175(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_177(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_176(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_178(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_177(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_179(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_178(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_180(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_179(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_181(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_180(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_182(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_181(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_183(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_182(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_184(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_183(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_185(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_184(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_186(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_185(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_187(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_186(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_188(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_187(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_189(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_188(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_190(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_189(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_191(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_190(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_192(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_191(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_193(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_192(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_194(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_193(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_195(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_194(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_196(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_195(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_197(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_196(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_198(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_197(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_199(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_198(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_200(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_199(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_201(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_200(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_202(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_201(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_203(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_202(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_204(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_203(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_205(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_204(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_206(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_205(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_207(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_206(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_208(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_207(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_209(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_208(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_210(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_209(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_211(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_210(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_212(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_211(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_213(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_212(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_214(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_213(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_215(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_214(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_216(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_215(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_217(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_216(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_218(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_217(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_219(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_218(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_220(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_219(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_221(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_220(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_222(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_221(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_223(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_222(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_224(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_223(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_225(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_224(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_226(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_225(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_227(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_226(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_228(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_227(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_229(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_228(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_230(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_229(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_231(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_230(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_232(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_231(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_233(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_232(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_234(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_233(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_235(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_234(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_236(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_235(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_237(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_236(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_238(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_237(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_239(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_238(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_240(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_239(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_241(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_240(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_242(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_241(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_243(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_242(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_244(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_243(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_245(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_244(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_246(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_245(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_247(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_246(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_248(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_247(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_249(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_248(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_250(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_249(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_251(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_250(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_252(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_251(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_253(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_252(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_254(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_253(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+#define SK_SEQ_FOREACH_255(op,lop,d,x,t) op(d,x) SK_SEQ_FOREACH_254(op, lop, d, SK_SEQ_HEAD(t), SK_SEQ_TAIL(t))
+
+#define SK_SEQ_END (SK_NIL)
+
+#endif
diff --git a/sfnt/SkSFNTHeader.h b/sfnt/SkSFNTHeader.h
new file mode 100644
index 00000000..9071696c
--- /dev/null
+++ b/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/sfnt/SkTTCFHeader.h b/sfnt/SkTTCFHeader.h
new file mode 100644
index 00000000..8f53c032
--- /dev/null
+++ b/sfnt/SkTTCFHeader.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTTCFHeader_DEFINED
+#define SkTTCFHeader_DEFINED
+
+#include "SkOTTableTypes.h"
+
+#pragma pack(push, 1)
+
+struct SkTTCFHeader {
+ SK_SFNT_ULONG ttcTag;
+ static const SK_OT_CHAR TAG0 = 't';
+ static const SK_OT_CHAR TAG1 = 't';
+ static const SK_OT_CHAR TAG2 = 'c';
+ static const SK_OT_CHAR TAG3 = 'f';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<SkTTCFHeader>::value;
+
+ SK_OT_Fixed version;
+ static const SK_OT_Fixed version_1 = SkTEndian_SwapBE32(1 << 16);
+ static const SK_OT_Fixed version_2 = SkTEndian_SwapBE32(2 << 16);
+
+ SK_OT_ULONG numOffsets;
+ //SK_OT_ULONG offset[numOffsets]
+
+ struct Version2Ext {
+ SK_OT_ULONG dsigType;
+ struct dsigType_None {
+ static const SK_OT_CHAR TAG0 = 0;
+ static const SK_OT_CHAR TAG1 = 0;
+ static const SK_OT_CHAR TAG2 = 0;
+ static const SK_OT_CHAR TAG3 = 0;
+ static const SK_OT_ULONG TAG = SkOTTableTAG<dsigType_None>::value;
+ };
+ struct dsigType_Format1 {
+ static const SK_OT_CHAR TAG0 = 'D';
+ static const SK_OT_CHAR TAG1 = 'S';
+ static const SK_OT_CHAR TAG2 = 'I';
+ static const SK_OT_CHAR TAG3 = 'G';
+ static const SK_OT_ULONG TAG = SkOTTableTAG<dsigType_Format1>::value;
+ };
+ SK_OT_ULONG dsigLength; //Length of DSIG table (in bytes).
+ SK_OT_ULONG dsigOffset; //Offset of DSIG table from the beginning of file (in bytes).
+ };// version2ext (if version == version_2)
+};
+
+#pragma pack(pop)
+
+
+SK_COMPILE_ASSERT(sizeof(SkTTCFHeader) == 12, sizeof_SkTTCFHeader_not_12);
+
+#endif
diff --git a/sfnt/SkTypedEnum.h b/sfnt/SkTypedEnum.h
new file mode 100644
index 00000000..73d73148
--- /dev/null
+++ b/sfnt/SkTypedEnum.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 SkTypedEnum_DEFINED
+#define SkTypedEnum_DEFINED
+
+#include "SkPreprocessorSeq.h"
+
+//Compatibility with non-clang compilers.
+#ifndef __has_feature
+ #define __has_feature(x) 0
+#endif
+#ifndef __has_extension
+ #define __has_extension __has_feature
+#endif
+
+//Detect if typed enums are supported.
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+ #define SK_TYPED_ENUMS
+
+#elif defined(__clang__) && __has_extension(cxx_strong_enums)
+ #define SK_TYPED_ENUMS
+
+// Scoped enums are buggy in GCC 4.4.0 through 4.5.1.
+// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38064
+// __cplusplus should actually be accurate now.
+// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=1773
+#elif defined(__GNUC__) && (((__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__ >= 40501) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || __cplusplus >= 201103L)
+ #define SK_TYPED_ENUMS
+#endif
+
+//Define what a typed enum looks like.
+#ifdef SK_TYPED_ENUMS
+
+ #define SK_TYPED_ENUM_VALUES(data, elem) \
+ SK_PAIR_FIRST(elem) = SK_PAIR_SECOND(elem),
+
+ #define SK_TYPED_ENUM_IDS(data, elem) \
+ elem,
+
+ #define SK_TYPED_ENUM_IDS_L(data, elem) \
+ elem
+
+ #define SK_TYPED_ENUM(enumName, enumType, enumSeq, idSeq) \
+ enum enumName : enumType { \
+ SK_SEQ_FOREACH(SK_TYPED_ENUM_VALUES, _, enumSeq) \
+ } SK_SEQ_FOREACH_L(SK_TYPED_ENUM_IDS, SK_TYPED_ENUM_IDS_L, _, idSeq);
+
+#else
+
+ #define SK_TYPED_ENUM_VALUES(enumType, elem) \
+ static const enumType SK_PAIR_FIRST(elem) = SK_PAIR_SECOND(elem);
+
+ #define SK_TYPED_ENUM_IDS(enumType, elem) \
+ enumType elem;
+
+ #define SK_TYPED_ENUM(enumName, enumType, enumSeq, idSeq) \
+ typedef enumType enumName; \
+ SK_SEQ_FOREACH(SK_TYPED_ENUM_VALUES, enumType, enumSeq) \
+ SK_SEQ_FOREACH(SK_TYPED_ENUM_IDS, enumType, idSeq)
+
+#endif
+
+#endif
diff --git a/svg/SkSVG.cpp b/svg/SkSVG.cpp
new file mode 100644
index 00000000..fdfc13a6
--- /dev/null
+++ b/svg/SkSVG.cpp
@@ -0,0 +1,28 @@
+
+/*
+ * 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 "SkSVG.h"
+#include 'SkSVGParser.h"
+
+SkSVG::SkSVG() {
+}
+
+SkSVG::~SkSVG() {
+}
+
+bool SkSVG::decodeStream(SkStream* stream);
+{
+ size_t size = stream->read(nil, 0);
+ SkAutoMalloc storage(size);
+ char* data = (char*)storage.get();
+ size_t actual = stream->read(data, size);
+ SkASSERT(size == actual);
+ SkSVGParser parser(*fMaker);
+ return parser.parse(data, actual, &fErrorCode, &fErrorLineNumber);
+}
diff --git a/svg/SkSVGCircle.cpp b/svg/SkSVGCircle.cpp
new file mode 100644
index 00000000..e34e1795
--- /dev/null
+++ b/svg/SkSVGCircle.cpp
@@ -0,0 +1,45 @@
+
+/*
+ * 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 "SkSVGCircle.h"
+#include "SkSVGParser.h"
+#include "SkParse.h"
+#include <stdio.h>
+
+const SkSVGAttribute SkSVGCircle::gAttributes[] = {
+ SVG_ATTRIBUTE(cx),
+ SVG_ATTRIBUTE(cy),
+ SVG_ATTRIBUTE(r)
+};
+
+DEFINE_SVG_INFO(Circle)
+
+void SkSVGCircle::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("oval");
+ INHERITED::translate(parser, defState);
+ SkScalar cx, cy, r;
+ SkParse::FindScalar(f_cx.c_str(), &cx);
+ SkParse::FindScalar(f_cy.c_str(), &cy);
+ SkParse::FindScalar(f_r.c_str(), &r);
+ SkScalar left, top, right, bottom;
+ left = cx - r;
+ top = cy - r;
+ right = cx + r;
+ bottom = cy + r;
+ char scratch[16];
+ sprintf(scratch, "%g", SkScalarToDouble(left));
+ parser._addAttribute("left", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(top));
+ parser._addAttribute("top", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(right));
+ parser._addAttribute("right", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(bottom));
+ parser._addAttribute("bottom", scratch);
+ parser._endElement();
+}
diff --git a/svg/SkSVGCircle.h b/svg/SkSVGCircle.h
new file mode 100644
index 00000000..11851627
--- /dev/null
+++ b/svg/SkSVGCircle.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGCircle_DEFINED
+#define SkSVGCircle_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGCircle : public SkSVGElement {
+ DECLARE_SVG_INFO(Circle);
+private:
+ SkString f_cx;
+ SkString f_cy;
+ SkString f_r;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGCircle_DEFINED
diff --git a/svg/SkSVGClipPath.cpp b/svg/SkSVGClipPath.cpp
new file mode 100644
index 00000000..fa3a7991
--- /dev/null
+++ b/svg/SkSVGClipPath.cpp
@@ -0,0 +1,40 @@
+
+/*
+ * 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 "SkSVGClipPath.h"
+#include "SkSVGParser.h"
+#include "SkSVGUse.h"
+
+DEFINE_SVG_NO_INFO(ClipPath)
+
+bool SkSVGClipPath::isDef() {
+ return true;
+}
+
+bool SkSVGClipPath::isNotDef() {
+ return false;
+}
+
+void SkSVGClipPath::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("clip");
+ INHERITED::translate(parser, defState);
+ SkASSERT(fChildren.count() == 1);
+ SkSVGElement* child = *fChildren.begin();
+ SkASSERT(child->getType() == SkSVGType_Use);
+ SkSVGUse* use = (SkSVGUse*) child;
+ SkSVGElement* ref = NULL;
+ const char* refStr = &use->f_xlink_href.c_str()[1];
+ SkASSERT(parser.getIDs().find(refStr, &ref));
+ SkASSERT(ref);
+ if (ref->getType() == SkSVGType_Rect)
+ parser._addAttribute("rectangle", refStr);
+ else
+ parser._addAttribute("path", refStr);
+ parser._endElement();
+}
diff --git a/svg/SkSVGClipPath.h b/svg/SkSVGClipPath.h
new file mode 100644
index 00000000..9ba4fbc7
--- /dev/null
+++ b/svg/SkSVGClipPath.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGClipPath_DEFINED
+#define SkSVGClipPath_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGClipPath : public SkSVGElement {
+ DECLARE_SVG_INFO(ClipPath);
+ virtual bool isDef();
+ virtual bool isNotDef();
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGClipPath_DEFINED
diff --git a/svg/SkSVGDefs.cpp b/svg/SkSVGDefs.cpp
new file mode 100644
index 00000000..3b9bc201
--- /dev/null
+++ b/svg/SkSVGDefs.cpp
@@ -0,0 +1,24 @@
+
+/*
+ * 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 "SkSVGDefs.h"
+
+DEFINE_SVG_NO_INFO(Defs)
+
+bool SkSVGDefs::isDef() {
+ return true;
+}
+
+bool SkSVGDefs::isNotDef() {
+ return false;
+}
+
+void SkSVGDefs::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGDefs.h b/svg/SkSVGDefs.h
new file mode 100644
index 00000000..daa68940
--- /dev/null
+++ b/svg/SkSVGDefs.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGDefs_DEFINED
+#define SkSVGDefs_DEFINED
+
+#include "SkSVGGroup.h"
+
+class SkSVGDefs : public SkSVGGroup {
+ DECLARE_SVG_INFO(Defs);
+ virtual bool isDef();
+ virtual bool isNotDef();
+private:
+ typedef SkSVGGroup INHERITED;
+};
+
+#endif // SkSVGDefs_DEFINED
diff --git a/svg/SkSVGElements.cpp b/svg/SkSVGElements.cpp
new file mode 100644
index 00000000..a1bd1005
--- /dev/null
+++ b/svg/SkSVGElements.cpp
@@ -0,0 +1,86 @@
+
+/*
+ * 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 "SkSVGElements.h"
+#include "SkSVGParser.h"
+
+SkSVGBase::~SkSVGBase() {
+}
+
+void SkSVGBase::addAttribute(SkSVGParser& parser, int attrIndex,
+ const char* attrValue, size_t attrLength) {
+ SkString* first = (SkString*) ((char*) this + sizeof(SkSVGElement));
+ first += attrIndex;
+ first->set(attrValue, attrLength);
+}
+
+
+SkSVGElement::SkSVGElement() : fParent(NULL), fIsDef(false), fIsNotDef(true) {
+}
+
+SkSVGElement::~SkSVGElement() {
+}
+
+SkSVGElement* SkSVGElement::getGradient() {
+ return NULL;
+}
+
+bool SkSVGElement::isGroupParent() {
+ SkSVGElement* parent = fParent;
+ while (parent) {
+ if (parent->getType() != SkSVGType_G)
+ return false;
+ parent = parent->fParent;
+ }
+ return true;
+}
+
+bool SkSVGElement::isDef() {
+ return isGroupParent() == false ? fParent->isDef() : fIsDef;
+}
+
+bool SkSVGElement::isFlushable() {
+ return true;
+}
+
+bool SkSVGElement::isGroup() {
+ return false;
+}
+
+bool SkSVGElement::isNotDef() {
+ return isGroupParent() == false ? fParent->isNotDef() : fIsNotDef;
+}
+
+bool SkSVGElement::onEndElement(SkSVGParser& parser) {
+ if (f_id.size() > 0)
+ parser.getIDs().set(f_id.c_str(), f_id.size(), this);
+ return false;
+}
+
+bool SkSVGElement::onStartElement(SkSVGElement* child) {
+ *fChildren.append() = child;
+ return false;
+}
+
+void SkSVGElement::translate(SkSVGParser& parser, bool) {
+ if (f_id.size() > 0)
+ SVG_ADD_ATTRIBUTE(id);
+}
+
+void SkSVGElement::setIsDef() {
+ fIsDef = isDef();
+}
+
+//void SkSVGElement::setIsNotDef() {
+// fIsNotDef = isNotDef();
+//}
+
+void SkSVGElement::write(SkSVGParser& , SkString& ) {
+ SkASSERT(0);
+}
diff --git a/svg/SkSVGElements.h b/svg/SkSVGElements.h
new file mode 100644
index 00000000..d00e4347
--- /dev/null
+++ b/svg/SkSVGElements.h
@@ -0,0 +1,73 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGElements_DEFINED
+#define SkSVGElements_DEFINED
+
+#include "SkSVGPaintState.h"
+#include "SkSVGTypes.h"
+#include "SkTDArray.h"
+
+class SkSVGParser;
+
+#define DECLARE_SVG_INFO(_type) \
+public: \
+ virtual ~SkSVG##_type(); \
+ static const SkSVGAttribute gAttributes[]; \
+ virtual int getAttributes(const SkSVGAttribute** attrPtr); \
+ virtual SkSVGTypes getType() const; \
+ virtual void translate(SkSVGParser& parser, bool defState); \
+ typedef SkSVG##_type BASE_CLASS
+
+#define DEFINE_SVG_INFO(_type) \
+ SkSVG##_type::~SkSVG##_type() {} \
+ int SkSVG##_type::getAttributes(const SkSVGAttribute** attrPtr) { \
+ *attrPtr = gAttributes; \
+ return SK_ARRAY_COUNT(gAttributes); \
+ } \
+ SkSVGTypes SkSVG##_type::getType() const { return SkSVGType_##_type; }
+
+#define DEFINE_SVG_NO_INFO(_type) \
+ SkSVG##_type::~SkSVG##_type() {} \
+ int SkSVG##_type::getAttributes(const SkSVGAttribute** ) { return 0; } \
+ SkSVGTypes SkSVG##_type::getType() const { return SkSVGType_##_type; }
+
+
+struct SkSVGTypeName {
+ const char* fName;
+ SkSVGTypes fType;
+};
+
+class SkSVGElement : public SkSVGBase {
+public:
+ SkSVGElement();
+ virtual ~SkSVGElement();
+ virtual SkSVGElement* getGradient();
+ virtual SkSVGTypes getType() const = 0;
+ virtual bool isDef();
+ virtual bool isFlushable();
+ virtual bool isGroup();
+ virtual bool isNotDef();
+ virtual bool onEndElement(SkSVGParser& parser);
+ virtual bool onStartElement(SkSVGElement* child);
+ void setIsDef();
+// void setIsNotDef();
+ virtual void translate(SkSVGParser& parser, bool defState);
+ virtual void write(SkSVGParser& , SkString& color);
+ SkString f_id;
+ SkSVGPaint fPaintState;
+ SkTDArray<SkSVGElement*> fChildren;
+ SkSVGElement* fParent;
+ bool fIsDef;
+ bool fIsNotDef;
+private:
+ bool isGroupParent();
+};
+
+#endif // SkSVGElements_DEFINED
diff --git a/svg/SkSVGEllipse.cpp b/svg/SkSVGEllipse.cpp
new file mode 100644
index 00000000..281e4e92
--- /dev/null
+++ b/svg/SkSVGEllipse.cpp
@@ -0,0 +1,47 @@
+
+/*
+ * 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 "SkSVGEllipse.h"
+#include "SkSVGParser.h"
+#include "SkParse.h"
+#include <stdio.h>
+
+const SkSVGAttribute SkSVGEllipse::gAttributes[] = {
+ SVG_ATTRIBUTE(cx),
+ SVG_ATTRIBUTE(cy),
+ SVG_ATTRIBUTE(rx),
+ SVG_ATTRIBUTE(ry)
+};
+
+DEFINE_SVG_INFO(Ellipse)
+
+void SkSVGEllipse::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("oval");
+ INHERITED::translate(parser, defState);
+ SkScalar cx, cy, rx, ry;
+ SkParse::FindScalar(f_cx.c_str(), &cx);
+ SkParse::FindScalar(f_cy.c_str(), &cy);
+ SkParse::FindScalar(f_rx.c_str(), &rx);
+ SkParse::FindScalar(f_ry.c_str(), &ry);
+ SkScalar left, top, right, bottom;
+ left = cx - rx;
+ top = cy - ry;
+ right = cx + rx;
+ bottom = cy + ry;
+ char scratch[16];
+ sprintf(scratch, "%g", SkScalarToDouble(left));
+ parser._addAttribute("left", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(top));
+ parser._addAttribute("top", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(right));
+ parser._addAttribute("right", scratch);
+ sprintf(scratch, "%g", SkScalarToDouble(bottom));
+ parser._addAttribute("bottom", scratch);
+ parser._endElement();
+}
diff --git a/svg/SkSVGEllipse.h b/svg/SkSVGEllipse.h
new file mode 100644
index 00000000..c039c830
--- /dev/null
+++ b/svg/SkSVGEllipse.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGEllipse_DEFINED
+#define SkSVGEllipse_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGEllipse : public SkSVGElement {
+ DECLARE_SVG_INFO(Ellipse);
+private:
+ SkString f_cx;
+ SkString f_cy;
+ SkString f_rx;
+ SkString f_ry;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGEllipse_DEFINED
diff --git a/svg/SkSVGFeColorMatrix.cpp b/svg/SkSVGFeColorMatrix.cpp
new file mode 100644
index 00000000..4e2d32a1
--- /dev/null
+++ b/svg/SkSVGFeColorMatrix.cpp
@@ -0,0 +1,24 @@
+
+/*
+ * 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 "SkSVGFeColorMatrix.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGFeColorMatrix::gAttributes[] = {
+ SVG_LITERAL_ATTRIBUTE(color-interpolation-filters, f_color_interpolation_filters),
+ SVG_ATTRIBUTE(result),
+ SVG_ATTRIBUTE(type),
+ SVG_ATTRIBUTE(values)
+};
+
+DEFINE_SVG_INFO(FeColorMatrix)
+
+void SkSVGFeColorMatrix::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGFeColorMatrix.h b/svg/SkSVGFeColorMatrix.h
new file mode 100644
index 00000000..389995ac
--- /dev/null
+++ b/svg/SkSVGFeColorMatrix.h
@@ -0,0 +1,26 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGFeColorMatrix_DEFINED
+#define SkSVGFeColorMatrix_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGFeColorMatrix : public SkSVGElement {
+ DECLARE_SVG_INFO(FeColorMatrix);
+protected:
+ SkString f_color_interpolation_filters;
+ SkString f_result;
+ SkString f_type;
+ SkString f_values;
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGFeColorMatrix_DEFINED
diff --git a/svg/SkSVGFilter.cpp b/svg/SkSVGFilter.cpp
new file mode 100644
index 00000000..207f176b
--- /dev/null
+++ b/svg/SkSVGFilter.cpp
@@ -0,0 +1,25 @@
+
+/*
+ * 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 "SkSVGFilter.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGFilter::gAttributes[] = {
+ SVG_ATTRIBUTE(filterUnits),
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(x),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Filter)
+
+void SkSVGFilter::translate(SkSVGParser& parser, bool defState) {
+// INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGFilter.h b/svg/SkSVGFilter.h
new file mode 100644
index 00000000..f615bd06
--- /dev/null
+++ b/svg/SkSVGFilter.h
@@ -0,0 +1,27 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGFilter_DEFINED
+#define SkSVGFilter_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGFilter : public SkSVGElement {
+ DECLARE_SVG_INFO(Filter);
+protected:
+ SkString f_filterUnits;
+ SkString f_height;
+ SkString f_width;
+ SkString f_x;
+ SkString f_y;
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGFilter_DEFINEDRITED;
diff --git a/svg/SkSVGG.cpp b/svg/SkSVGG.cpp
new file mode 100644
index 00000000..21a7aa86
--- /dev/null
+++ b/svg/SkSVGG.cpp
@@ -0,0 +1,16 @@
+
+/*
+ * 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 "SkSVGG.h"
+
+DEFINE_SVG_NO_INFO(G)
+
+void SkSVGG::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGG.h b/svg/SkSVGG.h
new file mode 100644
index 00000000..4fa27d29
--- /dev/null
+++ b/svg/SkSVGG.h
@@ -0,0 +1,21 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGG_DEFINED
+#define SkSVGG_DEFINED
+
+#include "SkSVGGroup.h"
+
+class SkSVGG : public SkSVGGroup {
+ DECLARE_SVG_INFO(G);
+private:
+ typedef SkSVGGroup INHERITED;
+};
+
+#endif // SkSVGG_DEFINED
diff --git a/svg/SkSVGGradient.cpp b/svg/SkSVGGradient.cpp
new file mode 100644
index 00000000..bbcca18f
--- /dev/null
+++ b/svg/SkSVGGradient.cpp
@@ -0,0 +1,114 @@
+
+/*
+ * 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 "SkSVGGradient.h"
+#include "SkSVGParser.h"
+#include "SkSVGStop.h"
+
+SkSVGGradient::SkSVGGradient() {
+}
+
+SkSVGElement* SkSVGGradient::getGradient() {
+ return this;
+}
+
+bool SkSVGGradient::isDef() {
+ return true;
+}
+
+bool SkSVGGradient::isNotDef() {
+ return false;
+}
+
+void SkSVGGradient::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+ // !!! no support for 'objectBoundingBox' yet
+ bool first = true;
+ bool addedFirst = false;
+ bool addedLast = false;
+ SkString offsets("[");
+ SkString* lastOffset = NULL;
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkASSERT((*ptr)->getType() == SkSVGType_Stop);
+ SkSVGStop* stop = (SkSVGStop*) *ptr;
+ if (first && stop->f_offset.equals("0") == false) {
+ addedFirst = true;
+ offsets.append("0,");
+ }
+ SkString* thisOffset = &stop->f_offset;
+ if (lastOffset && thisOffset->equals(*lastOffset)) {
+ if (thisOffset->equals("1")) {
+ offsets.remove(offsets.size() - 2, 2);
+ offsets.append(".999,");
+ } else {
+ SkASSERT(0); // !!! need to write this case
+ }
+ }
+ offsets.append(*thisOffset);
+ if (ptr == fChildren.end() - 1) { // last
+ if (stop->f_offset.equals("1") == false) {
+ offsets.append(",1");
+ addedLast = true;
+ }
+ } else
+ offsets.appendUnichar(',');
+ first = false;
+ lastOffset = thisOffset;
+ }
+ offsets.appendUnichar(']');
+ parser._addAttribute("offsets", offsets);
+ if (addedFirst)
+ parser.translate(*fChildren.begin(), defState);
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++)
+ parser.translate(*ptr, defState);
+ if (addedLast)
+ parser.translate(*(fChildren.end() - 1), defState);
+}
+
+void SkSVGGradient::translateGradientUnits(SkString& units) {
+ // !!! no support for 'objectBoundingBox' yet
+ SkASSERT(strcmp(units.c_str(), "userSpaceOnUse") == 0);
+}
+
+void SkSVGGradient::write(SkSVGParser& parser, SkString& baseColor) {
+ if (baseColor.c_str()[0] != '#')
+ return;
+ SkSVGPaint* saveHead = parser.fHead;
+ parser.fHead = &fPaintState;
+ parser.fSuppressPaint = true;
+ SkString originalID(f_id);
+ f_id.set("mask"); // write out gradient named given name + color (less initial #)
+ f_id.append(baseColor.c_str() + 1);
+ SkString originalColors;
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkSVGStop* colorElement = (SkSVGStop*) *ptr;
+ SkString& color = colorElement->fPaintState.f_stopColor;
+ originalColors.append(color);
+ originalColors.appendUnichar(',');
+ SkASSERT(color.c_str()[0] == '#');
+ SkString replacement;
+ replacement.set("0x");
+ replacement.append(color.c_str() + 1, 2); // add stop colors using given color, turning existing stop color into alpha
+ SkASSERT(baseColor.c_str()[0] == '#');
+ SkASSERT(baseColor.size() == 7);
+ replacement.append(baseColor.c_str() + 1);
+ color.set(replacement);
+ }
+ translate(parser, true);
+ const char* originalPtr = originalColors.c_str(); // restore original gradient values
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkSVGStop* color = (SkSVGStop*) *ptr;
+ const char* originalEnd = strchr(originalPtr, ',');
+ color->fPaintState.f_stopColor.set(originalPtr, originalEnd - originalPtr);
+ originalPtr = originalEnd + 1;
+ }
+ f_id.set(originalID);
+ parser.fSuppressPaint = false;
+ parser.fHead = saveHead;
+}
diff --git a/svg/SkSVGGradient.h b/svg/SkSVGGradient.h
new file mode 100644
index 00000000..286ff185
--- /dev/null
+++ b/svg/SkSVGGradient.h
@@ -0,0 +1,29 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGGradient_DEFINED
+#define SkSVGGradient_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGGradient : public SkSVGElement {
+public:
+ SkSVGGradient();
+ virtual SkSVGElement* getGradient();
+ virtual bool isDef();
+ virtual bool isNotDef();
+ virtual void write(SkSVGParser& , SkString& color);
+protected:
+ void translate(SkSVGParser& , bool defState);
+ void translateGradientUnits(SkString& units);
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGGradient_DEFINED
diff --git a/svg/SkSVGGroup.cpp b/svg/SkSVGGroup.cpp
new file mode 100644
index 00000000..420179d8
--- /dev/null
+++ b/svg/SkSVGGroup.cpp
@@ -0,0 +1,45 @@
+
+/*
+ * 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 "SkSVGGroup.h"
+#include "SkSVGParser.h"
+
+SkSVGGroup::SkSVGGroup() {
+ fIsNotDef = false;
+}
+
+SkSVGElement* SkSVGGroup::getGradient() {
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkSVGElement* result = (*ptr)->getGradient();
+ if (result != NULL)
+ return result;
+ }
+ return NULL;
+}
+
+bool SkSVGGroup::isDef() {
+ return fParent ? fParent->isDef() : false;
+}
+
+bool SkSVGGroup::isFlushable() {
+ return false;
+}
+
+bool SkSVGGroup::isGroup() {
+ return true;
+}
+
+bool SkSVGGroup::isNotDef() {
+ return fParent ? fParent->isNotDef() : false;
+}
+
+void SkSVGGroup::translate(SkSVGParser& parser, bool defState) {
+ for (SkSVGElement** ptr = fChildren.begin(); ptr < fChildren.end(); ptr++)
+ parser.translate(*ptr, defState);
+}
diff --git a/svg/SkSVGGroup.h b/svg/SkSVGGroup.h
new file mode 100644
index 00000000..f579a980
--- /dev/null
+++ b/svg/SkSVGGroup.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGGroup_DEFINED
+#define SkSVGGroup_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGGroup : public SkSVGElement {
+public:
+ SkSVGGroup();
+ virtual SkSVGElement* getGradient();
+ virtual bool isDef();
+ virtual bool isFlushable();
+ virtual bool isGroup();
+ virtual bool isNotDef();
+ void translate(SkSVGParser& , bool defState);
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGGroup_DEFINED
diff --git a/svg/SkSVGImage.cpp b/svg/SkSVGImage.cpp
new file mode 100644
index 00000000..97b8df86
--- /dev/null
+++ b/svg/SkSVGImage.cpp
@@ -0,0 +1,44 @@
+
+/*
+ * 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 "SkSVGImage.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGImage::gAttributes[] = {
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(x),
+ SVG_LITERAL_ATTRIBUTE(xlink:href, f_xlink_href),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Image)
+
+void SkSVGImage::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("image");
+ INHERITED::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE(x);
+ SVG_ADD_ATTRIBUTE(y);
+// SVG_ADD_ATTRIBUTE(width);
+// SVG_ADD_ATTRIBUTE(height);
+ translateImage(parser);
+ parser._endElement();
+}
+
+void SkSVGImage::translateImage(SkSVGParser& parser) {
+ SkASSERT(f_xlink_href.size() > 0);
+ const char* data = f_xlink_href.c_str();
+ SkASSERT(strncmp(data, "data:image/", 11) == 0);
+ data += 11;
+ SkASSERT(strncmp(data, "png;", 4) == 0 || strncmp(data, "jpeg;", 5) == 0);
+ data = strchr(data, ';');
+ SkASSERT(strncmp(data, ";base64,", 8) == 0);
+ data += 8;
+ parser._addAttribute("base64", data);
+}
diff --git a/svg/SkSVGImage.h b/svg/SkSVGImage.h
new file mode 100644
index 00000000..b63beef5
--- /dev/null
+++ b/svg/SkSVGImage.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGImage_DEFINED
+#define SkSVGImage_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGImage : public SkSVGElement {
+public:
+ DECLARE_SVG_INFO(Image);
+private:
+ void translateImage(SkSVGParser& parser);
+ SkString f_height;
+ SkString f_width;
+ SkString f_x;
+ SkString f_xlink_href;
+ SkString f_y;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGImage_DEFINED
diff --git a/svg/SkSVGLine.cpp b/svg/SkSVGLine.cpp
new file mode 100644
index 00000000..9158c99b
--- /dev/null
+++ b/svg/SkSVGLine.cpp
@@ -0,0 +1,30 @@
+
+/*
+ * 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 "SkSVGLine.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGLine::gAttributes[] = {
+ SVG_ATTRIBUTE(x1),
+ SVG_ATTRIBUTE(x2),
+ SVG_ATTRIBUTE(y1),
+ SVG_ATTRIBUTE(y2)
+};
+
+DEFINE_SVG_INFO(Line)
+
+void SkSVGLine::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("line");
+ INHERITED::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE(x1);
+ SVG_ADD_ATTRIBUTE(y1);
+ SVG_ADD_ATTRIBUTE(x2);
+ SVG_ADD_ATTRIBUTE(y2);
+ parser._endElement();
+}
diff --git a/svg/SkSVGLine.h b/svg/SkSVGLine.h
new file mode 100644
index 00000000..3e437e01
--- /dev/null
+++ b/svg/SkSVGLine.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGLine_DEFINED
+#define SkSVGLine_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGLine : public SkSVGElement {
+ DECLARE_SVG_INFO(Line);
+private:
+ SkString f_x1;
+ SkString f_x2;
+ SkString f_y1;
+ SkString f_y2;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGLine_DEFINED
diff --git a/svg/SkSVGLinearGradient.cpp b/svg/SkSVGLinearGradient.cpp
new file mode 100644
index 00000000..f89ee534
--- /dev/null
+++ b/svg/SkSVGLinearGradient.cpp
@@ -0,0 +1,44 @@
+
+/*
+ * 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 "SkSVGLinearGradient.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGLinearGradient::gAttributes[] = {
+ SVG_ATTRIBUTE(gradientTransform),
+ SVG_ATTRIBUTE(gradientUnits),
+ SVG_ATTRIBUTE(x1),
+ SVG_ATTRIBUTE(x2),
+ SVG_ATTRIBUTE(y1),
+ SVG_ATTRIBUTE(y2)
+};
+
+DEFINE_SVG_INFO(LinearGradient)
+
+void SkSVGLinearGradient::translate(SkSVGParser& parser, bool defState) {
+ if (fMatrixID.size() == 0)
+ parser.translateMatrix(f_gradientTransform, &fMatrixID);
+ parser._startElement("linearGradient");
+ if (fMatrixID.size() > 0)
+ parser._addAttribute("matrix", fMatrixID);
+ INHERITED::translateGradientUnits(f_gradientUnits);
+ SkString points;
+ points.appendUnichar('[');
+ points.append(f_x1);
+ points.appendUnichar(',');
+ points.append(f_y1);
+ points.appendUnichar(',');
+ points.append(f_x2);
+ points.appendUnichar(',');
+ points.append(f_y2);
+ points.appendUnichar(']');
+ parser._addAttribute("points", points.c_str());
+ INHERITED::translate(parser, defState);
+ parser._endElement();
+}
diff --git a/svg/SkSVGLinearGradient.h b/svg/SkSVGLinearGradient.h
new file mode 100644
index 00000000..8eda0650
--- /dev/null
+++ b/svg/SkSVGLinearGradient.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGLinearGradient_DEFINED
+#define SkSVGLinearGradient_DEFINED
+
+#include "SkSVGGradient.h"
+
+class SkSVGLinearGradient : public SkSVGGradient {
+ DECLARE_SVG_INFO(LinearGradient);
+private:
+ SkString f_gradientTransform;
+ SkString f_gradientUnits;
+ SkString f_x1;
+ SkString f_x2;
+ SkString f_y1;
+ SkString f_y2;
+ SkString fMatrixID;
+ typedef SkSVGGradient INHERITED;
+};
+
+#endif // SkSVGLinearGradient_DEFINED
diff --git a/svg/SkSVGMask.cpp b/svg/SkSVGMask.cpp
new file mode 100644
index 00000000..7526d18e
--- /dev/null
+++ b/svg/SkSVGMask.cpp
@@ -0,0 +1,33 @@
+
+/*
+ * 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 "SkSVGMask.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGMask::gAttributes[] = {
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(maskUnits),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(x),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Mask)
+
+bool SkSVGMask::isDef() {
+ return false;
+}
+
+bool SkSVGMask::isNotDef() {
+ return false;
+}
+
+void SkSVGMask::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGMask.h b/svg/SkSVGMask.h
new file mode 100644
index 00000000..2e1fd501
--- /dev/null
+++ b/svg/SkSVGMask.h
@@ -0,0 +1,29 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGMask_DEFINED
+#define SkSVGMask_DEFINED
+
+#include "SkSVGGroup.h"
+
+class SkSVGMask : public SkSVGGroup {
+ DECLARE_SVG_INFO(Mask);
+ virtual bool isDef();
+ virtual bool isNotDef();
+protected:
+ SkString f_height;
+ SkString f_maskUnits;
+ SkString f_width;
+ SkString f_x;
+ SkString f_y;
+private:
+ typedef SkSVGGroup INHERITED;
+};
+
+#endif // SkSVGMask_DEFINED
diff --git a/svg/SkSVGMetadata.cpp b/svg/SkSVGMetadata.cpp
new file mode 100644
index 00000000..0f8850ec
--- /dev/null
+++ b/svg/SkSVGMetadata.cpp
@@ -0,0 +1,24 @@
+
+/*
+ * 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 "SkSVGMetadata.h"
+#include "SkSVGParser.h"
+
+DEFINE_SVG_NO_INFO(Metadata)
+
+bool SkSVGMetadata::isDef() {
+ return false;
+}
+
+bool SkSVGMetadata::isNotDef() {
+ return false;
+}
+
+void SkSVGMetadata::translate(SkSVGParser& parser, bool defState) {
+}
diff --git a/svg/SkSVGMetadata.h b/svg/SkSVGMetadata.h
new file mode 100644
index 00000000..2dcb3a2c
--- /dev/null
+++ b/svg/SkSVGMetadata.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGMetadata_DEFINED
+#define SkSVGMetadata_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGMetadata : public SkSVGElement {
+ DECLARE_SVG_INFO(Metadata);
+ virtual bool isDef();
+ virtual bool isNotDef();
+private:
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGMetadata_DEFINED
diff --git a/svg/SkSVGPaintState.cpp b/svg/SkSVGPaintState.cpp
new file mode 100644
index 00000000..5db624d0
--- /dev/null
+++ b/svg/SkSVGPaintState.cpp
@@ -0,0 +1,454 @@
+
+/*
+ * 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 "SkSVGPaintState.h"
+#include "SkSVGElements.h"
+#include "SkSVGParser.h"
+#include "SkParse.h"
+
+SkSVGAttribute SkSVGPaint::gAttributes[] = {
+ SVG_LITERAL_ATTRIBUTE(clip-path, f_clipPath),
+ SVG_LITERAL_ATTRIBUTE(clip-rule, f_clipRule),
+ SVG_LITERAL_ATTRIBUTE(enable-background, f_enableBackground),
+ SVG_ATTRIBUTE(fill),
+ SVG_LITERAL_ATTRIBUTE(fill-rule, f_fillRule),
+ SVG_ATTRIBUTE(filter),
+ SVG_LITERAL_ATTRIBUTE(font-family, f_fontFamily),
+ SVG_LITERAL_ATTRIBUTE(font-size, f_fontSize),
+ SVG_LITERAL_ATTRIBUTE(letter-spacing, f_letterSpacing),
+ SVG_ATTRIBUTE(mask),
+ SVG_ATTRIBUTE(opacity),
+ SVG_LITERAL_ATTRIBUTE(stop-color, f_stopColor),
+ SVG_LITERAL_ATTRIBUTE(stop-opacity, f_stopOpacity),
+ SVG_ATTRIBUTE(stroke),
+ SVG_LITERAL_ATTRIBUTE(stroke-dasharray, f_strokeDasharray),
+ SVG_LITERAL_ATTRIBUTE(stroke-linecap, f_strokeLinecap),
+ SVG_LITERAL_ATTRIBUTE(stroke-linejoin, f_strokeLinejoin),
+ SVG_LITERAL_ATTRIBUTE(stroke-miterlimit, f_strokeMiterlimit),
+ SVG_LITERAL_ATTRIBUTE(stroke-width, f_strokeWidth),
+ SVG_ATTRIBUTE(style),
+ SVG_ATTRIBUTE(transform)
+};
+
+const int SkSVGPaint::kAttributesSize = SK_ARRAY_COUNT(SkSVGPaint::gAttributes);
+
+SkSVGPaint::SkSVGPaint() : fNext(NULL) {
+}
+
+SkString* SkSVGPaint::operator[](int index) {
+ SkASSERT(index >= 0);
+ SkASSERT(index < &fTerminal - &fInitial);
+ SkASSERT(&fTerminal - &fInitial == kTerminal - kInitial);
+ SkString* result = &fInitial + index + 1;
+ return result;
+}
+
+void SkSVGPaint::addAttribute(SkSVGParser& parser, int attrIndex,
+ const char* attrValue, size_t attrLength) {
+ SkString* attr = (*this)[attrIndex];
+ switch(attrIndex) {
+ case kClipPath:
+ case kClipRule:
+ case kEnableBackground:
+ case kFill:
+ case kFillRule:
+ case kFilter:
+ case kFontFamily:
+ case kFontSize:
+ case kLetterSpacing:
+ case kMask:
+ case kOpacity:
+ case kStopColor:
+ case kStopOpacity:
+ case kStroke:
+ case kStroke_Dasharray:
+ case kStroke_Linecap:
+ case kStroke_Linejoin:
+ case kStroke_Miterlimit:
+ case kStroke_Width:
+ case kTransform:
+ attr->set(attrValue, attrLength);
+ return;
+ case kStyle: {
+ // iterate through colon / semi-colon delimited pairs
+ int pairs = SkParse::Count(attrValue, ';');
+ const char* attrEnd = attrValue + attrLength;
+ do {
+ const char* end = strchr(attrValue, ';');
+ if (end == NULL)
+ end = attrEnd;
+ const char* delimiter = strchr(attrValue, ':');
+ SkASSERT(delimiter != 0 && delimiter < end);
+ int index = parser.findAttribute(this, attrValue, (int) (delimiter - attrValue), true);
+ SkASSERT(index >= 0);
+ delimiter++;
+ addAttribute(parser, index, delimiter, (int) (end - delimiter));
+ attrValue = end + 1;
+ } while (--pairs);
+ return;
+ }
+ default:
+ SkASSERT(0);
+ }
+}
+
+bool SkSVGPaint::flush(SkSVGParser& parser, bool isFlushable, bool isDef) {
+ SkSVGPaint current;
+ SkSVGPaint* walking = parser.fHead;
+ int index;
+ while (walking != NULL) {
+ for (index = kInitial + 1; index < kTerminal; index++) {
+ SkString* lastAttr = (*walking)[index];
+ if (lastAttr->size() == 0)
+ continue;
+ if (current[index]->size() > 0)
+ continue;
+ current[index]->set(*lastAttr);
+ }
+ walking = walking->fNext;
+ }
+ bool paintChanged = false;
+ SkSVGPaint& lastState = parser.fLastFlush;
+ if (isFlushable == false) {
+ if (isDef == true) {
+ if (current.f_mask.size() > 0 && current.f_mask.equals(lastState.f_mask) == false) {
+ SkSVGElement* found;
+ const char* idStart = strchr(current.f_mask.c_str(), '#');
+ SkASSERT(idStart);
+ SkString id(idStart + 1, strlen(idStart) - 2);
+ bool itsFound = parser.fIDs.find(id.c_str(), &found);
+ SkASSERT(itsFound);
+ SkSVGElement* gradient = found->getGradient();
+ if (gradient) {
+ gradient->write(parser, current.f_fill);
+ gradient->write(parser, current.f_stroke);
+ }
+ }
+ }
+ goto setLast;
+ }
+ {
+ bool changed[kTerminal];
+ memset(changed, 0, sizeof(changed));
+ for (index = kInitial + 1; index < kTerminal; index++) {
+ if (index == kTransform || index == kClipPath || index == kStopColor || index == kStopOpacity ||
+ index == kClipRule || index == kFillRule)
+ continue;
+ SkString* lastAttr = lastState[index];
+ SkString* currentAttr = current[index];
+ paintChanged |= changed[index] = lastAttr->equals(*currentAttr) == false;
+ }
+ if (paintChanged) {
+ if (current.f_mask.size() > 0) {
+ if (current.f_fill.equals("none") == false && strncmp(current.f_fill.c_str(), "url(#", 5) != 0) {
+ SkASSERT(current.f_fill.c_str()[0] == '#');
+ SkString replacement("url(#mask");
+ replacement.append(current.f_fill.c_str() + 1);
+ replacement.appendUnichar(')');
+ current.f_fill.set(replacement);
+ }
+ if (current.f_stroke.equals("none") == false && strncmp(current.f_stroke.c_str(), "url(#", 5) != 0) {
+ SkASSERT(current.f_stroke.c_str()[0] == '#');
+ SkString replacement("url(#mask");
+ replacement.append(current.f_stroke.c_str() + 1);
+ replacement.appendUnichar(')');
+ current.f_stroke.set(replacement);
+ }
+ }
+ if (current.f_fill.equals("none") && current.f_stroke.equals("none"))
+ current.f_opacity.set("0");
+ if (parser.fSuppressPaint == false) {
+ parser._startElement("paint");
+ bool success = writeChangedAttributes(parser, current, changed);
+ if (success == false)
+ return paintChanged;
+ success = writeChangedElements(parser, current, changed);
+ if (success == false)
+ return paintChanged;
+ parser._endElement(); // paint
+ }
+ }
+ }
+setLast:
+ for (index = kInitial + 1; index < kTerminal; index++) {
+ SkString* lastAttr = lastState[index];
+ SkString* currentAttr = current[index];
+ lastAttr->set(*currentAttr);
+ }
+ return paintChanged;
+}
+
+int SkSVGPaint::getAttributes(const SkSVGAttribute** attrPtr) {
+ *attrPtr = gAttributes;
+ return kAttributesSize;
+}
+
+void SkSVGPaint::setSave(SkSVGParser& parser) {
+ SkTDArray<SkString*> clips;
+ SkSVGPaint* walking = parser.fHead;
+ int index;
+ SkMatrix sum;
+ sum.reset();
+ while (walking != NULL) {
+ for (index = kInitial + 1; index < kTerminal; index++) {
+ SkString* lastAttr = (*walking)[index];
+ if (lastAttr->size() == 0)
+ continue;
+ if (index == kTransform) {
+ const char* str = lastAttr->c_str();
+ SkASSERT(strncmp(str, "matrix(", 7) == 0);
+ str += 6;
+ const char* strEnd = strrchr(str, ')');
+ SkASSERT(strEnd != NULL);
+ SkString mat(str, strEnd - str);
+ SkSVGParser::ConvertToArray(mat);
+ SkScalar values[6];
+ SkParse::FindScalars(mat.c_str() + 1, values, 6);
+ SkMatrix matrix;
+ matrix.reset();
+ matrix.setScaleX(values[0]);
+ matrix.setSkewY(values[1]);
+ matrix.setSkewX(values[2]);
+ matrix.setScaleY(values[3]);
+ matrix.setTranslateX(values[4]);
+ matrix.setTranslateY(values[5]);
+ sum.setConcat(matrix, sum);
+ continue;
+ }
+ if ( index == kClipPath)
+ *clips.insert(0) = lastAttr;
+ }
+ walking = walking->fNext;
+ }
+ if ((sum == parser.fLastTransform) == false) {
+ SkMatrix inverse;
+ bool success = parser.fLastTransform.invert(&inverse);
+ SkASSERT(success == true);
+ SkMatrix output;
+ output.setConcat(inverse, sum);
+ parser.fLastTransform = sum;
+ SkString outputStr;
+ outputStr.appendUnichar('[');
+ outputStr.appendScalar(output.getScaleX());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getSkewX());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getTranslateX());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getSkewY());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getScaleY());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getTranslateY());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getPerspX());
+ outputStr.appendUnichar(',');
+ outputStr.appendScalar(output.getPerspY());
+ outputStr.append(",1]");
+ parser._startElement("matrix");
+ parser._addAttributeLen("matrix", outputStr.c_str(), outputStr.size());
+ parser._endElement();
+ }
+#if 0 // incomplete
+ if (parser.fTransformClips.size() > 0) {
+ // need to reset the clip when the 'g' scope is ended
+ parser._startElement("add");
+ const char* start = strchr(current->f_clipPath.c_str(), '#') + 1;
+ SkASSERT(start);
+ parser._addAttributeLen("use", start, strlen(start) - 1);
+ parser._endElement(); // clip
+ }
+#endif
+}
+
+bool SkSVGPaint::writeChangedAttributes(SkSVGParser& parser,
+ SkSVGPaint& current, bool* changed) {
+ SkSVGPaint& lastState = parser.fLastFlush;
+ for (int index = kInitial + 1; index < kTerminal; index++) {
+ if (changed[index] == false)
+ continue;
+ SkString* topAttr = current[index];
+ size_t attrLength = topAttr->size();
+ if (attrLength == 0)
+ continue;
+ const char* attrValue = topAttr->c_str();
+ SkString* lastAttr = lastState[index];
+ switch(index) {
+ case kClipPath:
+ case kClipRule:
+ case kEnableBackground:
+ break;
+ case kFill:
+ if (topAttr->equals("none") == false && lastAttr->equals("none") == true)
+ parser._addAttribute("stroke", "false");
+ goto fillStrokeAttrCommon;
+ case kFillRule:
+ case kFilter:
+ case kFontFamily:
+ break;
+ case kFontSize:
+ parser._addAttributeLen("textSize", attrValue, attrLength);
+ break;
+ case kLetterSpacing:
+ parser._addAttributeLen("textTracking", attrValue, attrLength);
+ break;
+ case kMask:
+ break;
+ case kOpacity:
+ break;
+ case kStopColor:
+ break;
+ case kStopOpacity:
+ break;
+ case kStroke:
+ if (topAttr->equals("none") == false && lastAttr->equals("none") == true)
+ parser._addAttribute("stroke", "true");
+fillStrokeAttrCommon:
+ if (strncmp(attrValue, "url(", 4) == 0) {
+ SkASSERT(attrValue[4] == '#');
+ const char* idStart = attrValue + 5;
+ const char* idEnd = strrchr(attrValue, ')');
+ SkASSERT(idStart < idEnd);
+ SkString id(idStart, idEnd - idStart);
+ SkSVGElement* found;
+ if (strncmp(id.c_str(), "mask", 4) != 0) {
+ bool itsFound = parser.fIDs.find(id.c_str(), &found);
+ SkASSERT(itsFound);
+ SkASSERT(found->getType() == SkSVGType_LinearGradient ||
+ found->getType() == SkSVGType_RadialGradient);
+ }
+ parser._addAttribute("shader", id.c_str());
+ }
+ break;
+ case kStroke_Dasharray:
+ break;
+ case kStroke_Linecap:
+ parser._addAttributeLen("strokeCap", attrValue, attrLength);
+ break;
+ case kStroke_Linejoin:
+ parser._addAttributeLen("strokeJoin", attrValue, attrLength);
+ break;
+ case kStroke_Miterlimit:
+ parser._addAttributeLen("strokeMiter", attrValue, attrLength);
+ break;
+ case kStroke_Width:
+ parser._addAttributeLen("strokeWidth", attrValue, attrLength);
+ case kStyle:
+ case kTransform:
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SkSVGPaint::writeChangedElements(SkSVGParser& parser,
+ SkSVGPaint& current, bool* changed) {
+ SkSVGPaint& lastState = parser.fLastFlush;
+ for (int index = kInitial + 1; index < kTerminal; index++) {
+ SkString* topAttr = current[index];
+ size_t attrLength = topAttr->size();
+ if (attrLength == 0)
+ continue;
+ const char* attrValue = topAttr->c_str();
+ SkString* lastAttr = lastState[index];
+ switch(index) {
+ case kClipPath:
+ case kClipRule:
+ // !!! need to add this outside of paint
+ break;
+ case kEnableBackground:
+ // !!! don't know what to do with this
+ break;
+ case kFill:
+ goto addColor;
+ case kFillRule:
+ case kFilter:
+ break;
+ case kFontFamily:
+ parser._startElement("typeface");
+ parser._addAttributeLen("fontName", attrValue, attrLength);
+ parser._endElement(); // typeface
+ break;
+ case kFontSize:
+ case kLetterSpacing:
+ break;
+ case kMask:
+ case kOpacity:
+ if (changed[kStroke] == false && changed[kFill] == false) {
+ parser._startElement("color");
+ SkString& opacity = current.f_opacity;
+ parser._addAttributeLen("color", parser.fLastColor.c_str(), parser.fLastColor.size());
+ parser._addAttributeLen("alpha", opacity.c_str(), opacity.size());
+ parser._endElement(); // color
+ }
+ break;
+ case kStopColor:
+ break;
+ case kStopOpacity:
+ break;
+ case kStroke:
+addColor:
+ if (strncmp(lastAttr->c_str(), "url(", 4) == 0 && strncmp(attrValue, "url(", 4) != 0) {
+ parser._startElement("shader");
+ parser._endElement();
+ }
+ if (topAttr->equals(*lastAttr))
+ continue;
+ {
+ bool urlRef = strncmp(attrValue, "url(", 4) == 0;
+ bool colorNone = strcmp(attrValue, "none") == 0;
+ bool lastEqual = parser.fLastColor.equals(attrValue, attrLength);
+ bool newColor = urlRef == false && colorNone == false && lastEqual == false;
+ if (newColor || changed[kOpacity]) {
+ parser._startElement("color");
+ if (newColor || changed[kOpacity]) {
+ parser._addAttributeLen("color", attrValue, attrLength);
+ parser.fLastColor.set(attrValue, attrLength);
+ }
+ if (changed[kOpacity]) {
+ SkString& opacity = current.f_opacity;
+ parser._addAttributeLen("alpha", opacity.c_str(), opacity.size());
+ }
+ parser._endElement(); // color
+ }
+ }
+ break;
+ case kStroke_Dasharray:
+ parser._startElement("dash");
+ SkSVGParser::ConvertToArray(*topAttr);
+ parser._addAttribute("intervals", topAttr->c_str());
+ parser._endElement(); // dash
+ break;
+ case kStroke_Linecap:
+ case kStroke_Linejoin:
+ case kStroke_Miterlimit:
+ case kStroke_Width:
+ case kStyle:
+ case kTransform:
+ break;
+ default:
+ SkASSERT(0);
+ return false;
+ }
+ }
+ return true;
+}
+
+void SkSVGPaint::Push(SkSVGPaint** head, SkSVGPaint* newRecord) {
+ newRecord->fNext = *head;
+ *head = newRecord;
+}
+
+void SkSVGPaint::Pop(SkSVGPaint** head) {
+ SkSVGPaint* next = (*head)->fNext;
+ *head = next;
+}
diff --git a/svg/SkSVGParser.cpp b/svg/SkSVGParser.cpp
new file mode 100644
index 00000000..74ea0236
--- /dev/null
+++ b/svg/SkSVGParser.cpp
@@ -0,0 +1,441 @@
+
+/*
+ * 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 "SkSVGParser.h"
+#include "SkSVGCircle.h"
+#include "SkSVGClipPath.h"
+#include "SkSVGDefs.h"
+#include "SkSVGEllipse.h"
+#include "SkSVGFeColorMatrix.h"
+#include "SkSVGFilter.h"
+#include "SkSVGG.h"
+#include "SkSVGImage.h"
+#include "SkSVGLine.h"
+#include "SkSVGLinearGradient.h"
+#include "SkSVGMask.h"
+#include "SkSVGMetadata.h"
+#include "SkSVGPath.h"
+#include "SkSVGPolygon.h"
+#include "SkSVGPolyline.h"
+#include "SkSVGRadialGradient.h"
+#include "SkSVGRect.h"
+#include "SkSVGSVG.h"
+#include "SkSVGStop.h"
+#include "SkSVGSymbol.h"
+#include "SkSVGText.h"
+#include "SkSVGUse.h"
+#include "SkTSearch.h"
+#include <stdio.h>
+
+static int gGeneratedMatrixID = 0;
+
+SkSVGParser::SkSVGParser(SkXMLParserError* errHandler) :
+ SkXMLParser(errHandler),
+ fHead(&fEmptyPaint), fIDs(256),
+ fXMLWriter(&fStream), fCurrElement(NULL), fInSVG(false), fSuppressPaint(false) {
+ fLastTransform.reset();
+ fEmptyPaint.f_fill.set("black");
+ fEmptyPaint.f_stroke.set("none");
+ fEmptyPaint.f_strokeMiterlimit.set("4");
+ fEmptyPaint.f_fillRule.set("winding");
+ fEmptyPaint.f_opacity.set("1");
+ fEmptyPaint.fNext = NULL;
+ for (int index = SkSVGPaint::kInitial + 1; index < SkSVGPaint::kTerminal; index++) {
+ SkString* initial = fEmptyPaint[index];
+ if (initial->size() == 0)
+ continue;
+ fLastFlush[index]->set(*initial);
+ }
+}
+
+SkSVGParser::~SkSVGParser() {
+}
+
+void SkSVGParser::Delete(SkTDArray<SkSVGElement*>& fChildren) {
+ SkSVGElement** ptr;
+ for (ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ Delete((*ptr)->fChildren);
+ delete *ptr;
+ }
+}
+
+int SkSVGParser::findAttribute(SkSVGBase* element, const char* attrValue,
+ size_t len, bool isPaint) {
+ const SkSVGAttribute* attributes;
+ size_t count = element->getAttributes(&attributes);
+ size_t result = 0;
+ while (result < count) {
+ if (strncmp(attributes->fName, attrValue, len) == 0 && strlen(attributes->fName) == len) {
+ SkASSERT(result == (attributes->fOffset -
+ (isPaint ? sizeof(SkString) : sizeof(SkSVGElement))) / sizeof(SkString));
+ return result;
+ }
+ attributes++;
+ result++;
+ }
+ return -1;
+}
+
+#if 0
+const char* SkSVGParser::getFinal() {
+ _startElement("screenplay");
+ // generate defs
+ SkSVGElement** ptr;
+ for (ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkSVGElement* element = *ptr;
+ translate(element, true);
+ }
+ // generate onLoad
+ _startElement("event");
+ _addAttribute("kind", "onLoad");
+ _startElement("paint");
+ _addAttribute("antiAlias", "true");
+ _endElement();
+ for (ptr = fChildren.begin(); ptr < fChildren.end(); ptr++) {
+ SkSVGElement* element = *ptr;
+ translate(element, false);
+ }
+ _endElement(); // event
+ _endElement(); // screenplay
+ Delete(fChildren);
+ fStream.write("", 1);
+ return fStream.getStream();
+}
+#endif
+
+SkString& SkSVGParser::getPaintLast(SkSVGPaint::Field field) {
+ SkSVGPaint* state = fHead;
+ do {
+ SkString* attr = (*state)[field];
+ SkASSERT(attr);
+ if (attr->size() > 0)
+ return *attr;
+ state = state->fNext;
+ } while (state);
+ SkASSERT(0);
+ SkASSERT(fEmptyPaint[field]);
+ return *fEmptyPaint[field];
+}
+
+bool SkSVGParser::isStrokeAndFill( SkSVGPaint** strokeState, SkSVGPaint** fillState) {
+ SkSVGPaint* walking = fHead;
+ bool stroke = false;
+ bool fill = false;
+ bool strokeSet = false;
+ bool fillSet = false;
+ while (walking != NULL) {
+ if (strokeSet == false && walking->f_stroke.size() > 0) {
+ stroke = walking->f_stroke.equals("none") == false;
+ *strokeState = walking;
+ strokeSet = true;
+ }
+ if (fillSet == false && walking->f_fill.size() > 0) {
+ fill = walking->f_fill.equals("none") == false;
+ *fillState = walking;
+ fillSet = true;
+ }
+ walking = walking->fNext;
+ }
+ return stroke && fill;
+}
+
+bool SkSVGParser::onAddAttribute(const char name[], const char value[]) {
+ return onAddAttributeLen(name, value, strlen(value));
+}
+
+bool SkSVGParser::onAddAttributeLen(const char name[], const char value[], size_t len) {
+ if (fCurrElement == NULL) // this signals we should ignore attributes for this element
+ return true;
+ if (fCurrElement->fIsDef == false && fCurrElement->fIsNotDef == false)
+ return false; // also an ignored element
+ size_t nameLen = strlen(name);
+ int attrIndex = findAttribute(fCurrElement, name, nameLen, false);
+ if (attrIndex == -1) {
+ attrIndex = findAttribute(&fCurrElement->fPaintState, name, nameLen, true);
+ if (attrIndex >= 0) {
+ fCurrElement->fPaintState.addAttribute(*this, attrIndex, value, len);
+ return false;
+ }
+ if (nameLen == 2 && strncmp("id", name, nameLen) == 0) {
+ fCurrElement->f_id.set(value, len);
+ return false;
+ }
+ if (strchr(name, ':') != 0) // part of a different namespace
+ return false;
+ }
+ SkASSERT(attrIndex >= 0);
+ fCurrElement->addAttribute(*this, attrIndex, value, len);
+ return false;
+}
+
+bool SkSVGParser::onEndElement(const char elem[]) {
+ int parentIndex = fParents.count() - 1;
+ if (parentIndex >= 0) {
+ SkSVGElement* element = fParents[parentIndex];
+ element->onEndElement(*this);
+ fParents.remove(parentIndex);
+ }
+ return false;
+}
+
+bool SkSVGParser::onStartElement(const char name[]) {
+ return onStartElementLen(name, strlen(name));
+}
+
+bool SkSVGParser::onStartElementLen(const char name[], size_t len) {
+ if (strncmp(name, "svg", len) == 0) {
+ fInSVG = true;
+ } else if (fInSVG == false)
+ return false;
+ const char* nextColon = strchr(name, ':');
+ if (nextColon && (size_t)(nextColon - name) < len)
+ return false;
+ SkSVGTypes type = GetType(name, len);
+// SkASSERT(type >= 0);
+ if (type < 0) {
+ type = SkSVGType_G;
+// return true;
+ }
+ SkSVGElement* parent = fParents.count() > 0 ? fParents.top() : NULL;
+ SkSVGElement* element = CreateElement(type, parent);
+ bool result = false;
+ if (parent) {
+ element->fParent = parent;
+ result = fParents.top()->onStartElement(element);
+ } else
+ *fChildren.append() = element;
+ if (strncmp(name, "svg", len) != 0)
+ *fParents.append() = element;
+ fCurrElement = element;
+ return result;
+}
+
+bool SkSVGParser::onText(const char text[], int len) {
+ if (fInSVG == false)
+ return false;
+ SkSVGTypes type = fCurrElement->getType();
+ if (type != SkSVGType_Text && type != SkSVGType_Tspan)
+ return false;
+ SkSVGText* textElement = (SkSVGText*) fCurrElement;
+ textElement->f_text.set(text, len);
+ return false;
+}
+
+static int32_t strokeFillID = 0;
+
+void SkSVGParser::translate(SkSVGElement* element, bool isDef) {
+ SkSVGPaint::Push(&fHead, &element->fPaintState);
+ bool isFlushable = element->isFlushable();
+ if ((element->fIsDef == false && element->fIsNotDef == false) ||
+ (element->fIsDef && isDef == false && element->fIsNotDef == false) ||
+ (element->fIsDef == false && isDef && element->fIsNotDef)) {
+ isFlushable = false;
+ }
+ SkSVGPaint* strokeState = NULL, * fillState = NULL;
+ if (isFlushable)
+ element->fPaintState.setSave(*this);
+ if (isFlushable && isStrokeAndFill(&strokeState, &fillState)) {
+ SkString& elementID = element->f_id;
+ if (elementID.size() == 0) {
+ elementID.set("sf");
+ elementID.appendS32(++strokeFillID);
+ }
+ SkString saveStroke(strokeState->f_stroke);
+ SkString saveFill(fillState->f_fill);
+ strokeState->f_stroke.set("none");
+ element->fPaintState.flush(*this, isFlushable, isDef);
+ element->translate(*this, isDef);
+ strokeState->f_stroke.set(saveStroke);
+ fillState->f_fill.set("none");
+ if (element->fPaintState.flush(*this, isFlushable, isDef)) {
+ _startElement("add");
+ _addAttributeLen("use", elementID.c_str(), elementID.size());
+ _endElement(); // add
+ }
+ fillState->f_fill.set(saveFill);
+ } else {
+ element->fPaintState.flush(*this, isFlushable, isDef);
+ if (isFlushable || element->isGroup())
+ element->translate(*this, isDef);
+ }
+ SkSVGPaint::Pop(&fHead);
+}
+
+void SkSVGParser::translateMatrix(SkString& string, SkString* stringID) {
+ if (string.size() == 0)
+ return;
+ if (stringID->size() > 0) {
+ _startElement("add");
+ _addAttribute("use", stringID->c_str());
+ _endElement(); // add
+ return;
+ }
+ SkASSERT(strncmp(string.c_str(), "matrix", 6) == 0);
+ ++gGeneratedMatrixID;
+ _startElement("matrix");
+ char idStr[24];
+ strcpy(idStr, "sk_matrix");
+ sprintf(idStr + strlen(idStr), "%d", gGeneratedMatrixID);
+ _addAttribute("id", idStr);
+ stringID->set(idStr);
+ const char* str = string.c_str();
+ SkASSERT(strncmp(str, "matrix(", 7) == 0);
+ str += 6;
+ const char* strEnd = strrchr(str, ')');
+ SkASSERT(strEnd != NULL);
+ SkString mat(str, strEnd - str);
+ ConvertToArray(mat);
+ const char* elems[6];
+ static const int order[] = {0, 3, 1, 4, 2, 5};
+ const int* orderPtr = order;
+ str = mat.c_str();
+ strEnd = str + mat.size();
+ while (str < strEnd) {
+ elems[*orderPtr++] = str;
+ while (str < strEnd && *str != ',' )
+ str++;
+ str++;
+ }
+ string.reset();
+ for (int index = 0; index < 6; index++) {
+ const char* end = strchr(elems[index], ',');
+ if (end == NULL)
+ end= strchr(elems[index], ']');
+ string.append(elems[index], end - elems[index] + 1);
+ }
+ string.remove(string.size() - 1, 1);
+ string.append(",0,0,1]");
+ _addAttribute("matrix", string);
+ _endElement(); // matrix
+}
+
+static bool is_whitespace(char ch) {
+ return ch > 0 && ch <= ' ';
+}
+
+void SkSVGParser::ConvertToArray(SkString& vals) {
+ vals.appendUnichar(']');
+ char* valCh = (char*) vals.c_str();
+ valCh[0] = '[';
+ int index = 1;
+ while (valCh[index] != ']') {
+ while (is_whitespace(valCh[index]))
+ index++;
+ bool foundComma = false;
+ char next;
+ do {
+ next = valCh[index++];
+ if (next == ',') {
+ foundComma = true;
+ continue;
+ }
+ if (next == ']') {
+ index--;
+ goto undoLastComma;
+ }
+ if (next == ' ')
+ break;
+ foundComma = false;
+ } while (is_whitespace(next) == false);
+ if (foundComma == false)
+ valCh[index - 1] = ',';
+ }
+undoLastComma:
+ while (is_whitespace(valCh[--index]))
+ ;
+ if (valCh[index] == ',')
+ valCh[index] = ' ';
+}
+
+#define CASE_NEW(type) case SkSVGType_##type : created = new SkSVG##type(); break
+
+SkSVGElement* SkSVGParser::CreateElement(SkSVGTypes type, SkSVGElement* parent) {
+ SkSVGElement* created = NULL;
+ switch (type) {
+ CASE_NEW(Circle);
+ CASE_NEW(ClipPath);
+ CASE_NEW(Defs);
+ CASE_NEW(Ellipse);
+ CASE_NEW(FeColorMatrix);
+ CASE_NEW(Filter);
+ CASE_NEW(G);
+ CASE_NEW(Image);
+ CASE_NEW(Line);
+ CASE_NEW(LinearGradient);
+ CASE_NEW(Mask);
+ CASE_NEW(Metadata);
+ CASE_NEW(Path);
+ CASE_NEW(Polygon);
+ CASE_NEW(Polyline);
+ CASE_NEW(RadialGradient);
+ CASE_NEW(Rect);
+ CASE_NEW(Stop);
+ CASE_NEW(SVG);
+ CASE_NEW(Symbol);
+ CASE_NEW(Text);
+ CASE_NEW(Tspan);
+ CASE_NEW(Use);
+ default:
+ SkASSERT(0);
+ return NULL;
+ }
+ created->fParent = parent;
+ bool isDef = created->fIsDef = created->isDef();
+ bool isNotDef = created->fIsNotDef = created->isNotDef();
+ if (isDef) {
+ SkSVGElement* up = parent;
+ while (up && up->fIsDef == false) {
+ up->fIsDef = true;
+ up = up->fParent;
+ }
+ }
+ if (isNotDef) {
+ SkSVGElement* up = parent;
+ while (up && up->fIsNotDef == false) {
+ up->fIsNotDef = true;
+ up = up->fParent;
+ }
+ }
+ return created;
+}
+
+const SkSVGTypeName gSVGTypeNames[] = {
+ {"circle", SkSVGType_Circle},
+ {"clipPath", SkSVGType_ClipPath},
+ {"defs", SkSVGType_Defs},
+ {"ellipse", SkSVGType_Ellipse},
+ {"feColorMatrix", SkSVGType_FeColorMatrix},
+ {"filter", SkSVGType_Filter},
+ {"g", SkSVGType_G},
+ {"image", SkSVGType_Image},
+ {"line", SkSVGType_Line},
+ {"linearGradient", SkSVGType_LinearGradient},
+ {"mask", SkSVGType_Mask},
+ {"metadata", SkSVGType_Metadata},
+ {"path", SkSVGType_Path},
+ {"polygon", SkSVGType_Polygon},
+ {"polyline", SkSVGType_Polyline},
+ {"radialGradient", SkSVGType_RadialGradient},
+ {"rect", SkSVGType_Rect},
+ {"stop", SkSVGType_Stop},
+ {"svg", SkSVGType_SVG},
+ {"symbol", SkSVGType_Symbol},
+ {"text", SkSVGType_Text},
+ {"tspan", SkSVGType_Tspan},
+ {"use", SkSVGType_Use}
+};
+
+const int kSVGTypeNamesSize = SK_ARRAY_COUNT(gSVGTypeNames);
+
+SkSVGTypes SkSVGParser::GetType(const char match[], size_t len ) {
+ int index = SkStrSearch(&gSVGTypeNames[0].fName, kSVGTypeNamesSize, match,
+ len, sizeof(gSVGTypeNames[0]));
+ return index >= 0 && index < kSVGTypeNamesSize ? gSVGTypeNames[index].fType :
+ (SkSVGTypes) -1;
+}
diff --git a/svg/SkSVGPath.cpp b/svg/SkSVGPath.cpp
new file mode 100644
index 00000000..ab18a65a
--- /dev/null
+++ b/svg/SkSVGPath.cpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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 "SkSVGPath.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGPath::gAttributes[] = {
+ SVG_ATTRIBUTE(d)
+};
+
+DEFINE_SVG_INFO(Path)
+
+void SkSVGPath::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("path");
+ INHERITED::translate(parser, defState);
+ bool hasMultiplePaths = false;
+ const char* firstZ = strchr(f_d.c_str(), 'z');
+ if (firstZ != NULL) {
+ firstZ++; // skip over 'z'
+ while (*firstZ == ' ')
+ firstZ++;
+ hasMultiplePaths = *firstZ != '\0';
+ }
+ if (hasMultiplePaths) {
+ SkString& fillRule = parser.getPaintLast(SkSVGPaint::kFillRule);
+ if (fillRule.size() > 0)
+ parser._addAttribute("fillType", fillRule.equals("evenodd") ? "evenOdd" : "winding");
+ }
+ SVG_ADD_ATTRIBUTE(d);
+ parser._endElement();
+}
diff --git a/svg/SkSVGPath.h b/svg/SkSVGPath.h
new file mode 100644
index 00000000..de325a7b
--- /dev/null
+++ b/svg/SkSVGPath.h
@@ -0,0 +1,22 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGPath_DEFINED
+#define SkSVGPath_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGPath : public SkSVGElement {
+ DECLARE_SVG_INFO(Path);
+private:
+ SkString f_d;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGPath_DEFINED
diff --git a/svg/SkSVGPolygon.cpp b/svg/SkSVGPolygon.cpp
new file mode 100644
index 00000000..4b458db9
--- /dev/null
+++ b/svg/SkSVGPolygon.cpp
@@ -0,0 +1,33 @@
+
+/*
+ * 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 "SkSVGPolygon.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGPolygon::gAttributes[] = {
+ SVG_LITERAL_ATTRIBUTE(clip-rule, f_clipRule),
+ SVG_LITERAL_ATTRIBUTE(fill-rule, f_fillRule),
+ SVG_ATTRIBUTE(points)
+};
+
+DEFINE_SVG_INFO(Polygon)
+
+void SkSVGPolygon::addAttribute(SkSVGParser& parser, int attrIndex,
+ const char* attrValue, size_t attrLength) {
+ INHERITED::addAttribute(parser, attrIndex, attrValue, attrLength);
+}
+
+void SkSVGPolygon::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("polygon");
+ SkSVGElement::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE(points);
+ if (f_fillRule.size() > 0)
+ parser._addAttribute("fillType", f_fillRule.equals("evenodd") ? "evenOdd" : "winding");
+ parser._endElement();
+}
diff --git a/svg/SkSVGPolygon.h b/svg/SkSVGPolygon.h
new file mode 100644
index 00000000..b2848d06
--- /dev/null
+++ b/svg/SkSVGPolygon.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGPolygon_DEFINED
+#define SkSVGPolygon_DEFINED
+
+#include "SkSVGPolyline.h"
+
+class SkSVGPolygon : public SkSVGPolyline {
+ DECLARE_SVG_INFO(Polygon);
+ virtual void addAttribute(SkSVGParser& , int attrIndex,
+ const char* attrValue, size_t attrLength);
+private:
+ typedef SkSVGPolyline INHERITED;
+};
+
+#endif // SkSVGPolygon_DEFINED
diff --git a/svg/SkSVGPolyline.cpp b/svg/SkSVGPolyline.cpp
new file mode 100644
index 00000000..83dad488
--- /dev/null
+++ b/svg/SkSVGPolyline.cpp
@@ -0,0 +1,43 @@
+
+/*
+ * 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 "SkSVGPolyline.h"
+#include "SkSVGParser.h"
+
+enum {
+ kCliipRule,
+ kFillRule,
+ kPoints
+};
+
+const SkSVGAttribute SkSVGPolyline::gAttributes[] = {
+ SVG_LITERAL_ATTRIBUTE(clip-rule, f_clipRule),
+ SVG_LITERAL_ATTRIBUTE(fill-rule, f_fillRule),
+ SVG_ATTRIBUTE(points)
+};
+
+DEFINE_SVG_INFO(Polyline)
+
+void SkSVGPolyline::addAttribute(SkSVGParser& , int attrIndex,
+ const char* attrValue, size_t attrLength) {
+ if (attrIndex != kPoints)
+ return;
+ f_points.set("[");
+ f_points.append(attrValue, attrLength);
+ SkSVGParser::ConvertToArray(f_points);
+}
+
+void SkSVGPolyline::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("polyline");
+ INHERITED::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE(points);
+ if (f_fillRule.size() > 0)
+ parser._addAttribute("fillType", f_fillRule.equals("evenodd") ? "evenOdd" : "winding");
+ parser._endElement();
+}
diff --git a/svg/SkSVGPolyline.h b/svg/SkSVGPolyline.h
new file mode 100644
index 00000000..b8d5af45
--- /dev/null
+++ b/svg/SkSVGPolyline.h
@@ -0,0 +1,27 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGPolyline_DEFINED
+#define SkSVGPolyline_DEFINED
+
+#include "SkSVGElements.h"
+#include "SkString.h"
+
+class SkSVGPolyline : public SkSVGElement {
+ DECLARE_SVG_INFO(Polyline);
+ virtual void addAttribute(SkSVGParser& , int attrIndex,
+ const char* attrValue, size_t attrLength);
+protected:
+ SkString f_clipRule;
+ SkString f_fillRule;
+ SkString f_points;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGPolyline_DEFINED
diff --git a/svg/SkSVGRadialGradient.cpp b/svg/SkSVGRadialGradient.cpp
new file mode 100644
index 00000000..4fdf4322
--- /dev/null
+++ b/svg/SkSVGRadialGradient.cpp
@@ -0,0 +1,42 @@
+
+/*
+ * 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 "SkSVGRadialGradient.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGRadialGradient::gAttributes[] = {
+ SVG_ATTRIBUTE(cx),
+ SVG_ATTRIBUTE(cy),
+ SVG_ATTRIBUTE(fx),
+ SVG_ATTRIBUTE(fy),
+ SVG_ATTRIBUTE(gradientTransform),
+ SVG_ATTRIBUTE(gradientUnits),
+ SVG_ATTRIBUTE(r)
+};
+
+DEFINE_SVG_INFO(RadialGradient)
+
+void SkSVGRadialGradient::translate(SkSVGParser& parser, bool defState) {
+ if (fMatrixID.size() == 0)
+ parser.translateMatrix(f_gradientTransform, &fMatrixID);
+ parser._startElement("radialGradient");
+ if (fMatrixID.size() > 0)
+ parser._addAttribute("matrix", fMatrixID);
+ INHERITED::translateGradientUnits(f_gradientUnits);
+ SkString center;
+ center.appendUnichar('[');
+ center.append(f_cx);
+ center.appendUnichar(',');
+ center.append(f_cy);
+ center.appendUnichar(']');
+ parser._addAttribute("center", center);
+ parser._addAttribute("radius", f_r);
+ INHERITED::translate(parser, defState);
+ parser._endElement();
+}
diff --git a/svg/SkSVGRadialGradient.h b/svg/SkSVGRadialGradient.h
new file mode 100644
index 00000000..8eba3f5e
--- /dev/null
+++ b/svg/SkSVGRadialGradient.h
@@ -0,0 +1,30 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGRadialGradient_DEFINED
+#define SkSVGRadialGradient_DEFINED
+
+#include "SkSVGGradient.h"
+
+class SkSVGRadialGradient : public SkSVGGradient {
+ DECLARE_SVG_INFO(RadialGradient);
+protected:
+ SkString f_cx;
+ SkString f_cy;
+ SkString f_fx;
+ SkString f_fy;
+ SkString f_gradientTransform;
+ SkString f_gradientUnits;
+ SkString f_r;
+ SkString fMatrixID;
+private:
+ typedef SkSVGGradient INHERITED;
+};
+
+#endif // SkSVGRadialGradient_DEFINED
diff --git a/svg/SkSVGRect.cpp b/svg/SkSVGRect.cpp
new file mode 100644
index 00000000..32a7f99c
--- /dev/null
+++ b/svg/SkSVGRect.cpp
@@ -0,0 +1,35 @@
+
+/*
+ * 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 "SkSVGRect.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGRect::gAttributes[] = {
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(x),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Rect)
+
+SkSVGRect::SkSVGRect() {
+ f_x.set("0");
+ f_y.set("0");
+}
+
+void SkSVGRect::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("rect");
+ INHERITED::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE_ALIAS(left, x);
+ SVG_ADD_ATTRIBUTE_ALIAS(top, y);
+ SVG_ADD_ATTRIBUTE(width);
+ SVG_ADD_ATTRIBUTE(height);
+ parser._endElement();
+}
diff --git a/svg/SkSVGRect.h b/svg/SkSVGRect.h
new file mode 100644
index 00000000..4ae820c5
--- /dev/null
+++ b/svg/SkSVGRect.h
@@ -0,0 +1,26 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGRect_DEFINED
+#define SkSVGRect_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGRect : public SkSVGElement {
+ DECLARE_SVG_INFO(Rect);
+ SkSVGRect();
+private:
+ SkString f_height;
+ SkString f_width;
+ SkString f_x;
+ SkString f_y;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGRect_DEFINED
diff --git a/svg/SkSVGSVG.cpp b/svg/SkSVGSVG.cpp
new file mode 100644
index 00000000..fcce62de
--- /dev/null
+++ b/svg/SkSVGSVG.cpp
@@ -0,0 +1,76 @@
+
+/*
+ * 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 "SkSVGSVG.h"
+#include "SkParse.h"
+#include "SkRect.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGSVG::gAttributes[] = {
+ SVG_LITERAL_ATTRIBUTE(enable-background, f_enable_background),
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(overflow),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(version),
+ SVG_ATTRIBUTE(viewBox),
+ SVG_ATTRIBUTE(x),
+ SVG_LITERAL_ATTRIBUTE(xml:space, f_xml_space),
+ SVG_ATTRIBUTE(xmlns),
+ SVG_LITERAL_ATTRIBUTE(xmlns:xlink, f_xml_xlink),
+ SVG_ATTRIBUTE(y),
+};
+
+DEFINE_SVG_INFO(SVG)
+
+
+bool SkSVGSVG::isFlushable() {
+ return false;
+}
+
+void SkSVGSVG::translate(SkSVGParser& parser, bool defState) {
+ SkScalar height, width;
+ SkScalar viewBox[4];
+ const char* hSuffix = SkParse::FindScalar(f_height.c_str(), &height);
+ if (strcmp(hSuffix, "pt") == 0)
+ height = SkScalarMulDiv(height, SK_Scalar1 * 72, SK_Scalar1 * 96);
+ const char* wSuffix = SkParse::FindScalar(f_width.c_str(), &width);
+ if (strcmp(wSuffix, "pt") == 0)
+ width = SkScalarMulDiv(width, SK_Scalar1 * 72, SK_Scalar1 * 96);
+ SkParse::FindScalars(f_viewBox.c_str(), viewBox, 4);
+ SkRect box;
+ box.fLeft = SkScalarDiv(viewBox[0], width);
+ box.fTop = SkScalarDiv(viewBox[1], height);
+ box.fRight = SkScalarDiv(viewBox[2], width);
+ box.fBottom = SkScalarDiv(viewBox[3], height);
+ if (box.fLeft == 0 && box.fTop == 0 &&
+ box.fRight == SK_Scalar1 && box.fBottom == SK_Scalar1)
+ return;
+ parser._startElement("matrix");
+ if (box.fLeft != 0) {
+ SkString x;
+ x.appendScalar(box.fLeft);
+ parser._addAttributeLen("translateX", x.c_str(), x.size());
+ }
+ if (box.fTop != 0) {
+ SkString y;
+ y.appendScalar(box.fTop);
+ parser._addAttributeLen("translateY", y.c_str(), y.size());
+ }
+ if (box.fRight != SK_Scalar1) {
+ SkString x;
+ x.appendScalar(box.fRight);
+ parser._addAttributeLen("scaleX", x.c_str(), x.size());
+ }
+ if (box.fBottom != SK_Scalar1) {
+ SkString y;
+ y.appendScalar(box.fBottom);
+ parser._addAttributeLen("scaleY", y.c_str(), y.size());
+ }
+ parser._endElement();
+}
diff --git a/svg/SkSVGSVG.h b/svg/SkSVGSVG.h
new file mode 100644
index 00000000..155f9a9b
--- /dev/null
+++ b/svg/SkSVGSVG.h
@@ -0,0 +1,34 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGSVG_DEFINED
+#define SkSVGSVG_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGSVG : public SkSVGElement {
+ DECLARE_SVG_INFO(SVG);
+ virtual bool isFlushable();
+private:
+ SkString f_enable_background;
+ SkString f_height;
+ SkString f_overflow;
+ SkString f_width;
+ SkString f_version;
+ SkString f_viewBox;
+ SkString f_x;
+ SkString f_xml_space;
+ SkString f_xmlns;
+ SkString f_xml_xlink;
+ SkString f_y;
+
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGSVG_DEFINED
diff --git a/svg/SkSVGStop.cpp b/svg/SkSVGStop.cpp
new file mode 100644
index 00000000..0630f612
--- /dev/null
+++ b/svg/SkSVGStop.cpp
@@ -0,0 +1,24 @@
+
+/*
+ * 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 "SkSVGStop.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGStop::gAttributes[] = {
+ SVG_ATTRIBUTE(offset)
+};
+
+DEFINE_SVG_INFO(Stop)
+
+void SkSVGStop::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("color");
+ INHERITED::translate(parser, defState);
+ parser._addAttribute("color", parser.getPaintLast(SkSVGPaint::kStopColor));
+ parser._endElement();
+}
diff --git a/svg/SkSVGStop.h b/svg/SkSVGStop.h
new file mode 100644
index 00000000..e55936be
--- /dev/null
+++ b/svg/SkSVGStop.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGStop_DEFINED
+#define SkSVGStop_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGStop : public SkSVGElement {
+ DECLARE_SVG_INFO(Stop);
+private:
+ SkString f_offset;
+ friend class SkSVGGradient;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGStop_DEFINED
diff --git a/svg/SkSVGSymbol.cpp b/svg/SkSVGSymbol.cpp
new file mode 100644
index 00000000..ce341e64
--- /dev/null
+++ b/svg/SkSVGSymbol.cpp
@@ -0,0 +1,22 @@
+
+/*
+ * 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 "SkSVGSymbol.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGSymbol::gAttributes[] = {
+ SVG_ATTRIBUTE(viewBox)
+};
+
+DEFINE_SVG_INFO(Symbol)
+
+void SkSVGSymbol::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+ // !!! children need to be written into document
+}
diff --git a/svg/SkSVGSymbol.h b/svg/SkSVGSymbol.h
new file mode 100644
index 00000000..80fd61a1
--- /dev/null
+++ b/svg/SkSVGSymbol.h
@@ -0,0 +1,22 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGSymbol_DEFINED
+#define SkSVGSymbol_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGSymbol : public SkSVGElement {
+ DECLARE_SVG_INFO(Symbol);
+private:
+ SkString f_viewBox;
+ typedef SkSVGElement INHERITED;
+};
+
+#endif // SkSVGSymbol_DEFINED
diff --git a/svg/SkSVGText.cpp b/svg/SkSVGText.cpp
new file mode 100644
index 00000000..dfaba086
--- /dev/null
+++ b/svg/SkSVGText.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * 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 "SkSVGText.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGText::gAttributes[] = {
+ SVG_ATTRIBUTE(x),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Text)
+
+void SkSVGText::translate(SkSVGParser& parser, bool defState) {
+ parser._startElement("text");
+ INHERITED::translate(parser, defState);
+ SVG_ADD_ATTRIBUTE(x);
+ SVG_ADD_ATTRIBUTE(y);
+ SVG_ADD_ATTRIBUTE(text);
+ parser._endElement();
+}
+
+
+const SkSVGAttribute SkSVGTspan::gAttributes[] = {
+ SVG_ATTRIBUTE(x),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Tspan)
+
+void SkSVGTspan::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+}
diff --git a/svg/SkSVGText.h b/svg/SkSVGText.h
new file mode 100644
index 00000000..5ac2fbdd
--- /dev/null
+++ b/svg/SkSVGText.h
@@ -0,0 +1,32 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGText_DEFINED
+#define SkSVGText_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGText : public SkSVGElement {
+ DECLARE_SVG_INFO(Text);
+protected:
+ SkString f_x;
+ SkString f_y;
+ SkString f_text; // not an attribute
+private:
+ typedef SkSVGElement INHERITED;
+ friend class SkSVGParser;
+};
+
+class SkSVGTspan : public SkSVGText {
+ DECLARE_SVG_INFO(Tspan);
+private:
+ typedef SkSVGText INHERITED;
+};
+
+#endif // SkSVGText_DEFINED
diff --git a/svg/SkSVGUse.cpp b/svg/SkSVGUse.cpp
new file mode 100644
index 00000000..0496d98b
--- /dev/null
+++ b/svg/SkSVGUse.cpp
@@ -0,0 +1,30 @@
+
+/*
+ * 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 "SkSVGUse.h"
+#include "SkSVGParser.h"
+
+const SkSVGAttribute SkSVGUse::gAttributes[] = {
+ SVG_ATTRIBUTE(height),
+ SVG_ATTRIBUTE(width),
+ SVG_ATTRIBUTE(x),
+ SVG_LITERAL_ATTRIBUTE(xlink:href, f_xlink_href),
+ SVG_ATTRIBUTE(y)
+};
+
+DEFINE_SVG_INFO(Use)
+
+void SkSVGUse::translate(SkSVGParser& parser, bool defState) {
+ INHERITED::translate(parser, defState);
+ parser._startElement("add");
+ const char* start = strchr(f_xlink_href.c_str(), '#') + 1;
+ SkASSERT(start);
+ parser._addAttributeLen("use", start, strlen(start) - 1);
+ parser._endElement(); // clip
+}
diff --git a/svg/SkSVGUse.h b/svg/SkSVGUse.h
new file mode 100644
index 00000000..b907189f
--- /dev/null
+++ b/svg/SkSVGUse.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkSVGUse_DEFINED
+#define SkSVGUse_DEFINED
+
+#include "SkSVGElements.h"
+
+class SkSVGUse : public SkSVGElement {
+ DECLARE_SVG_INFO(Use);
+protected:
+ SkString f_height;
+ SkString f_width;
+ SkString f_x;
+ SkString f_xlink_href;
+ SkString f_y;
+private:
+ typedef SkSVGElement INHERITED;
+ friend class SkSVGClipPath;
+};
+
+#endif // SkSVGUse_DEFINED
diff --git a/text/SkTextLayout.cpp b/text/SkTextLayout.cpp
new file mode 100644
index 00000000..4e531cf2
--- /dev/null
+++ b/text/SkTextLayout.cpp
@@ -0,0 +1,80 @@
+
+/*
+ * 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 "SkTextLayout.h"
+
+SK_DEFINE_INST_COUNT(SkTextStyle)
+
+SkTextStyle::SkTextStyle() {
+ fPaint.setAntiAlias(true);
+}
+
+SkTextStyle::SkTextStyle(const SkTextStyle& src) : fPaint(src.fPaint) {}
+
+SkTextStyle::SkTextStyle(const SkPaint& paint) : fPaint(paint) {}
+
+SkTextStyle::~SkTextStyle() {}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTextLayout::SkTextLayout() {
+ fBounds.setEmpty();
+ fDefaultStyle = new SkTextStyle;
+}
+
+SkTextLayout::~SkTextLayout() {
+ fDefaultStyle->unref();
+ fLines.deleteAll();
+}
+
+void SkTextLayout::setText(const char text[], size_t length) {
+ fText.setCount(length);
+ memcpy(fText.begin(), text, length);
+}
+
+void SkTextLayout::setBounds(const SkRect& bounds) {
+ fBounds = bounds;
+ // if width changed, inval cache
+}
+
+SkTextStyle* SkTextLayout::setDefaultStyle(SkTextStyle* style) {
+ SkRefCnt_SafeAssign(fDefaultStyle, style);
+ return style;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct SkTextLayout::GlyphRun {
+ GlyphRun();
+ ~GlyphRun();
+
+ SkPoint* fLocs;
+ uint16_t* fGlyphIDs;
+ int fCount;
+};
+
+SkTextLayout::GlyphRun::GlyphRun() : fLocs(NULL), fGlyphIDs(NULL), fCount(0) {}
+
+SkTextLayout::GlyphRun::~GlyphRun() {
+ delete[] fLocs;
+ delete[] fGlyphIDs;
+}
+
+struct SkTextLayout::Line {
+ Line() {}
+ ~Line();
+
+ SkScalar fBaselineY;
+ SkTDArray<GlyphRun*> fRuns;
+};
+
+SkTextLayout::Line::~Line() {
+ fRuns.deleteAll();
+}
+
+void SkTextLayout::draw(SkCanvas* canvas) {
+}
diff --git a/utils/SkBase64.cpp b/utils/SkBase64.cpp
new file mode 100644
index 00000000..11b647fe
--- /dev/null
+++ b/utils/SkBase64.cpp
@@ -0,0 +1,185 @@
+
+/*
+ * 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 "SkBase64.h"
+
+#define DecodePad -2
+#define EncodePad 64
+
+static const char default_encode[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/=";
+
+static const signed char decodeData[] = {
+ 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, DecodePad, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+};
+
+SkBase64::SkBase64() : fLength((size_t) -1), fData(NULL) {
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300 // disable 'two', etc. may be used without having been initialized
+#pragma warning ( push )
+#pragma warning ( disable : 4701 )
+#endif
+
+SkBase64::Error SkBase64::decode(const void* srcPtr, size_t size, bool writeDestination) {
+ unsigned char* dst = (unsigned char*) fData;
+ const unsigned char* dstStart = (const unsigned char*) fData;
+ const unsigned char* src = (const unsigned char*) srcPtr;
+ bool padTwo = false;
+ bool padThree = false;
+ const unsigned char* end = src + size;
+ while (src < end) {
+ unsigned char bytes[4];
+ int byte = 0;
+ do {
+ unsigned char srcByte = *src++;
+ if (srcByte == 0)
+ goto goHome;
+ if (srcByte <= ' ')
+ continue; // treat as white space
+ if (srcByte < '+' || srcByte > 'z')
+ return kBadCharError;
+ signed char decoded = decodeData[srcByte - '+'];
+ bytes[byte] = decoded;
+ if (decoded < 0) {
+ if (decoded == DecodePad)
+ goto handlePad;
+ return kBadCharError;
+ } else
+ byte++;
+ if (*src)
+ continue;
+ if (byte == 0)
+ goto goHome;
+ if (byte == 4)
+ break;
+handlePad:
+ if (byte < 2)
+ return kPadError;
+ padThree = true;
+ if (byte == 2)
+ padTwo = true;
+ break;
+ } while (byte < 4);
+ int two = 0;
+ int three = 0;
+ if (writeDestination) {
+ int one = (uint8_t) (bytes[0] << 2);
+ two = bytes[1];
+ one |= two >> 4;
+ two = (uint8_t) (two << 4);
+ three = bytes[2];
+ two |= three >> 2;
+ three = (uint8_t) (three << 6);
+ three |= bytes[3];
+ SkASSERT(one < 256 && two < 256 && three < 256);
+ *dst = (unsigned char) one;
+ }
+ dst++;
+ if (padTwo)
+ break;
+ if (writeDestination)
+ *dst = (unsigned char) two;
+ dst++;
+ if (padThree)
+ break;
+ if (writeDestination)
+ *dst = (unsigned char) three;
+ dst++;
+ }
+goHome:
+ fLength = dst - dstStart;
+ return kNoError;
+}
+
+#if defined _WIN32 && _MSC_VER >= 1300
+#pragma warning ( pop )
+#endif
+
+size_t SkBase64::Encode(const void* srcPtr, size_t length, void* dstPtr, const char* encodeMap) {
+ const char* encode;
+ if (NULL == encodeMap) {
+ encode = default_encode;
+ } else {
+ encode = encodeMap;
+ }
+ const unsigned char* src = (const unsigned char*) srcPtr;
+ unsigned char* dst = (unsigned char*) dstPtr;
+ if (dst) {
+ size_t remainder = length % 3;
+ const unsigned char* end = &src[length - remainder];
+ while (src < end) {
+ unsigned a = *src++;
+ unsigned b = *src++;
+ unsigned c = *src++;
+ int d = c & 0x3F;
+ c = (c >> 6 | b << 2) & 0x3F;
+ b = (b >> 4 | a << 4) & 0x3F;
+ a = a >> 2;
+ *dst++ = encode[a];
+ *dst++ = encode[b];
+ *dst++ = encode[c];
+ *dst++ = encode[d];
+ }
+ if (remainder > 0) {
+ int k1 = 0;
+ int k2 = EncodePad;
+ int a = (uint8_t) *src++;
+ if (remainder == 2)
+ {
+ int b = *src++;
+ k1 = b >> 4;
+ k2 = (b << 2) & 0x3F;
+ }
+ *dst++ = encode[a >> 2];
+ *dst++ = encode[(k1 | a << 4) & 0x3F];
+ *dst++ = encode[k2];
+ *dst++ = encode[EncodePad];
+ }
+ }
+ return (length + 2) / 3 * 4;
+}
+
+SkBase64::Error SkBase64::decode(const char* src, size_t len) {
+ Error err = decode(src, len, false);
+ SkASSERT(err == kNoError);
+ if (err != kNoError)
+ return err;
+ fData = new char[fLength]; // should use sk_malloc/sk_free
+ decode(src, len, true);
+ return kNoError;
+}
+
+#ifdef SK_SUPPORT_UNITTEST
+void SkBase64::UnitTest() {
+ signed char all[256];
+ for (int index = 0; index < 256; index++)
+ all[index] = (signed char) (index + 1);
+ for (int offset = 0; offset < 6; offset++) {
+ size_t length = 256 - offset;
+ size_t encodeLength = Encode(all + offset, length, NULL);
+ char* src = (char*)sk_malloc_throw(encodeLength + 1);
+ Encode(all + offset, length, src);
+ src[encodeLength] = '\0';
+ SkBase64 tryMe;
+ tryMe.decode(src, encodeLength);
+ SkASSERT(length == tryMe.fLength);
+ SkASSERT(strcmp((const char*) (all + offset), tryMe.fData) == 0);
+ sk_free(src);
+ delete[] tryMe.fData;
+ }
+}
+#endif
diff --git a/utils/SkBase64.h b/utils/SkBase64.h
new file mode 100644
index 00000000..5bf90066
--- /dev/null
+++ b/utils/SkBase64.h
@@ -0,0 +1,44 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBase64_DEFINED
+#define SkBase64_DEFINED
+
+#include "SkTypes.h"
+
+struct SkBase64 {
+public:
+ enum Error {
+ kNoError,
+ kPadError,
+ kBadCharError
+ };
+
+ SkBase64();
+ Error decode(const char* src, size_t length);
+ char* getData() { return fData; }
+ /**
+ Base64 encodes src into dst. encode is a pointer to at least 65 chars.
+ encode[64] will be used as the pad character. Encodings other than the
+ default encoding cannot be decoded.
+ */
+ static size_t Encode(const void* src, size_t length, void* dest, const char* encode = NULL);
+
+#ifdef SK_SUPPORT_UNITTEST
+ static void UnitTest();
+#endif
+private:
+ Error decode(const void* srcPtr, size_t length, bool writeDestination);
+
+ size_t fLength;
+ char* fData;
+ friend class SkImageBaseBitmap;
+};
+
+#endif // SkBase64_DEFINED
diff --git a/utils/SkBitSet.cpp b/utils/SkBitSet.cpp
new file mode 100755
index 00000000..36ff6cbb
--- /dev/null
+++ b/utils/SkBitSet.cpp
@@ -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.
+ */
+
+
+#include "SkBitSet.h"
+
+SkBitSet::SkBitSet(int numberOfBits)
+ : fBitData(NULL), fDwordCount(0), fBitCount(numberOfBits) {
+ SkASSERT(numberOfBits > 0);
+ // Round up size to 32-bit boundary.
+ fDwordCount = (numberOfBits + 31) / 32;
+ fBitData.set(malloc(fDwordCount * sizeof(uint32_t)));
+ clearAll();
+}
+
+SkBitSet::SkBitSet(const SkBitSet& source)
+ : fBitData(NULL), fDwordCount(0), fBitCount(0) {
+ *this = source;
+}
+
+SkBitSet& SkBitSet::operator=(const SkBitSet& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+ fBitCount = rhs.fBitCount;
+ fBitData.free();
+ fDwordCount = rhs.fDwordCount;
+ fBitData.set(malloc(fDwordCount * sizeof(uint32_t)));
+ memcpy(fBitData.get(), rhs.fBitData.get(), fDwordCount * sizeof(uint32_t));
+ return *this;
+}
+
+bool SkBitSet::operator==(const SkBitSet& rhs) {
+ if (fBitCount == rhs.fBitCount) {
+ if (fBitData.get() != NULL) {
+ return (memcmp(fBitData.get(), rhs.fBitData.get(),
+ fDwordCount * sizeof(uint32_t)) == 0);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkBitSet::operator!=(const SkBitSet& rhs) {
+ return !(*this == rhs);
+}
+
+void SkBitSet::clearAll() {
+ if (fBitData.get() != NULL) {
+ sk_bzero(fBitData.get(), fDwordCount * sizeof(uint32_t));
+ }
+}
+
+void SkBitSet::setBit(int index, bool value) {
+ uint32_t mask = 1 << (index % 32);
+ if (value) {
+ *(internalGet(index)) |= mask;
+ } else {
+ *(internalGet(index)) &= ~mask;
+ }
+}
+
+bool SkBitSet::isBitSet(int index) const {
+ uint32_t mask = 1 << (index % 32);
+ return 0 != (*internalGet(index) & mask);
+}
+
+bool SkBitSet::orBits(const SkBitSet& source) {
+ if (fBitCount != source.fBitCount) {
+ return false;
+ }
+ uint32_t* targetBitmap = internalGet(0);
+ uint32_t* sourceBitmap = source.internalGet(0);
+ for (size_t i = 0; i < fDwordCount; ++i) {
+ targetBitmap[i] |= sourceBitmap[i];
+ }
+ return true;
+}
diff --git a/utils/SkBitSet.h b/utils/SkBitSet.h
new file mode 100755
index 00000000..e113fd70
--- /dev/null
+++ b/utils/SkBitSet.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 SkBitSet_DEFINED
+#define SkBitSet_DEFINED
+
+#include "SkTypes.h"
+#include "SkTDArray.h"
+
+class SkBitSet {
+public:
+ /** NumberOfBits must be greater than zero.
+ */
+ explicit SkBitSet(int numberOfBits);
+ explicit SkBitSet(const SkBitSet& source);
+
+ SkBitSet& operator=(const SkBitSet& rhs);
+ bool operator==(const SkBitSet& rhs);
+ bool operator!=(const SkBitSet& rhs);
+
+ /** Clear all data.
+ */
+ void clearAll();
+
+ /** Set the value of the index-th bit.
+ */
+ void setBit(int index, bool value);
+
+ /** Test if bit index is set.
+ */
+ bool isBitSet(int index) const;
+
+ /** Or bits from source. false is returned if this doesn't have the same
+ * bit count as source.
+ */
+ bool orBits(const SkBitSet& source);
+
+ /** Export indices of set bits to T array.
+ */
+ template<typename T>
+ void exportTo(SkTDArray<T>* array) const {
+ SkASSERT(array);
+ uint32_t* data = reinterpret_cast<uint32_t*>(fBitData.get());
+ for (unsigned int i = 0; i < fDwordCount; ++i) {
+ uint32_t value = data[i];
+ if (value) { // There are set bits
+ unsigned int index = i * 32;
+ for (unsigned int j = 0; j < 32; ++j) {
+ if (0x1 & (value >> j)) {
+ array->push(index + j);
+ }
+ }
+ }
+ }
+ }
+
+private:
+ SkAutoFree fBitData;
+ // Dword (32-bit) count of the bitset.
+ size_t fDwordCount;
+ size_t fBitCount;
+
+ uint32_t* internalGet(int index) const {
+ SkASSERT((size_t)index < fBitCount);
+ size_t internalIndex = index / 32;
+ SkASSERT(internalIndex < fDwordCount);
+ return reinterpret_cast<uint32_t*>(fBitData.get()) + internalIndex;
+ }
+};
+
+
+#endif
diff --git a/utils/SkBitmapHasher.cpp b/utils/SkBitmapHasher.cpp
new file mode 100644
index 00000000..9f0affde
--- /dev/null
+++ b/utils/SkBitmapHasher.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 "SkBitmap.h"
+#include "SkBitmapHasher.h"
+#include "SkEndian.h"
+#include "SkImageEncoder.h"
+
+#include "SkMD5.h"
+
+/**
+ * Write an int32 value to a stream in little-endian order.
+ */
+static void write_int32_to_buffer(uint32_t val, SkWStream* out) {
+ val = SkEndian_SwapLE32(val);
+ for (size_t byte = 0; byte < 4; ++byte) {
+ out->write8((uint8_t)(val & 0xff));
+ val = val >> 8;
+ }
+}
+
+/**
+ * Return the first 8 bytes of a bytearray, encoded as a little-endian uint64.
+ */
+static inline uint64_t first_8_bytes_as_uint64(const uint8_t *bytearray) {
+ return SkEndian_SwapLE64(*(reinterpret_cast<const uint64_t *>(bytearray)));
+}
+
+/*static*/ bool SkBitmapHasher::ComputeDigestInternal(const SkBitmap& bitmap, uint64_t *result) {
+ SkMD5 out;
+
+ // start with the x/y dimensions
+ write_int32_to_buffer(SkToU32(bitmap.width()), &out);
+ write_int32_to_buffer(SkToU32(bitmap.height()), &out);
+
+ // add all the pixel data
+ SkAutoTDelete<SkImageEncoder> enc(CreateARGBImageEncoder());
+ if (!enc->encodeStream(&out, bitmap, SkImageEncoder::kDefaultQuality)) {
+ return false;
+ }
+
+ SkMD5::Digest digest;
+ out.finish(digest);
+ *result = first_8_bytes_as_uint64(digest.data);
+ return true;
+}
+
+/*static*/ bool SkBitmapHasher::ComputeDigest(const SkBitmap& bitmap, uint64_t *result) {
+ if (ComputeDigestInternal(bitmap, result)) {
+ return true;
+ }
+
+ // Hmm, that didn't work. Maybe if we create a new
+ // kARGB_8888_Config version of the bitmap it will work better?
+ SkBitmap copyBitmap;
+ if (!bitmap.copyTo(&copyBitmap, SkBitmap::kARGB_8888_Config)) {
+ return false;
+ }
+ return ComputeDigestInternal(copyBitmap, result);
+}
diff --git a/utils/SkBitmapHasher.h b/utils/SkBitmapHasher.h
new file mode 100644
index 00000000..d52a6d73
--- /dev/null
+++ b/utils/SkBitmapHasher.h
@@ -0,0 +1,35 @@
+
+/*
+ * 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 SkBitmapHasher_DEFINED
+#define SkBitmapHasher_DEFINED
+
+#include "SkBitmap.h"
+
+/**
+ * Static class that generates a uint64 hash digest from an SkBitmap.
+ */
+class SkBitmapHasher {
+public:
+ /**
+ * Fills in "result" with a hash of the pixels in this bitmap.
+ *
+ * If this is unable to compute the hash for some reason,
+ * it returns false.
+ *
+ * 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 bool ComputeDigest(const SkBitmap& bitmap, uint64_t *result);
+
+private:
+ static bool ComputeDigestInternal(const SkBitmap& bitmap, uint64_t *result);
+};
+
+#endif
diff --git a/utils/SkBoundaryPatch.cpp b/utils/SkBoundaryPatch.cpp
new file mode 100644
index 00000000..fd1545d2
--- /dev/null
+++ b/utils/SkBoundaryPatch.cpp
@@ -0,0 +1,81 @@
+
+/*
+ * 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 "SkBoundaryPatch.h"
+
+SK_DEFINE_INST_COUNT(SkBoundary)
+
+SkBoundaryPatch::SkBoundaryPatch() : fBoundary(NULL) {}
+
+SkBoundaryPatch::~SkBoundaryPatch() {
+ SkSafeUnref(fBoundary);
+}
+
+SkBoundary* SkBoundaryPatch::setBoundary(SkBoundary* b) {
+ SkRefCnt_SafeAssign(fBoundary, b);
+ return b;
+}
+
+static SkPoint SkMakePoint(SkScalar x, SkScalar y) {
+ SkPoint pt;
+ pt.set(x, y);
+ return pt;
+}
+
+static SkPoint SkPointInterp(const SkPoint& a, const SkPoint& b, SkScalar t) {
+ return SkMakePoint(SkScalarInterp(a.fX, b.fX, t),
+ SkScalarInterp(a.fY, b.fY, t));
+}
+
+SkPoint SkBoundaryPatch::eval(SkScalar unitU, SkScalar unitV) {
+ SkBoundary* b = fBoundary;
+ SkPoint u = SkPointInterp(b->eval(SkBoundary::kLeft, SK_Scalar1 - unitV),
+ b->eval(SkBoundary::kRight, unitV),
+ unitU);
+ SkPoint v = SkPointInterp(b->eval(SkBoundary::kTop, unitU),
+ b->eval(SkBoundary::kBottom, SK_Scalar1 - unitU),
+ unitV);
+ return SkMakePoint(SkScalarAve(u.fX, v.fX),
+ SkScalarAve(u.fY, v.fY));
+}
+
+bool SkBoundaryPatch::evalPatch(SkPoint verts[], int rows, int cols) {
+ if (rows < 2 || cols < 2) {
+ return false;
+ }
+
+ const SkScalar invR = SkScalarInvert(SkIntToScalar(rows - 1));
+ const SkScalar invC = SkScalarInvert(SkIntToScalar(cols - 1));
+
+ for (int y = 0; y < cols; y++) {
+ SkScalar yy = y * invC;
+ for (int x = 0; x < rows; x++) {
+ *verts++ = this->eval(x * invR, yy);
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+#include "SkGeometry.h"
+
+SkPoint SkLineBoundary::eval(Edge e, SkScalar t) {
+ SkASSERT((unsigned)e < 4);
+ return SkPointInterp(fPts[e], fPts[(e + 1) & 3], t);
+}
+
+SkPoint SkCubicBoundary::eval(Edge e, SkScalar t) {
+ SkASSERT((unsigned)e < 4);
+
+ // ensure our 4th cubic wraps to the start of the first
+ fPts[12] = fPts[0];
+
+ SkPoint loc;
+ SkEvalCubicAt(&fPts[e * 3], t, &loc, NULL, NULL);
+ return loc;
+}
diff --git a/utils/SkCamera.cpp b/utils/SkCamera.cpp
new file mode 100644
index 00000000..a7c0b148
--- /dev/null
+++ b/utils/SkCamera.cpp
@@ -0,0 +1,425 @@
+
+/*
+ * 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 "SkCamera.h"
+
+static SkScalar SkScalarDotDiv(int count, const SkScalar a[], int step_a,
+ const SkScalar b[], int step_b,
+ SkScalar denom) {
+#ifdef SK_SCALAR_IS_FLOAT
+ float prod = 0;
+ for (int i = 0; i < count; i++) {
+ prod += a[0] * b[0];
+ a += step_a;
+ b += step_b;
+ }
+ return prod / denom;
+#else
+ Sk64 prod, tmp;
+
+ prod.set(0);
+ for (int i = 0; i < count; i++) {
+ tmp.setMul(a[0], b[0]);
+ prod.add(tmp);
+ a += step_a;
+ b += step_b;
+ }
+ prod.div(denom, Sk64::kRound_DivOption);
+ return prod.get32();
+#endif
+}
+
+static SkScalar SkScalarDot(int count, const SkScalar a[], int step_a,
+ const SkScalar b[], int step_b) {
+#ifdef SK_SCALAR_IS_FLOAT
+ float prod = 0;
+ for (int i = 0; i < count; i++) {
+ prod += a[0] * b[0];
+ a += step_a;
+ b += step_b;
+ }
+ return prod;
+#else
+ Sk64 prod, tmp;
+
+ prod.set(0);
+ for (int i = 0; i < count; i++) {
+ tmp.setMul(a[0], b[0]);
+ prod.add(tmp);
+ a += step_a;
+ b += step_b;
+ }
+ return prod.getFixed();
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkUnitScalar SkPoint3D::normalize(SkUnit3D* unit) const {
+#ifdef SK_SCALAR_IS_FLOAT
+ float mag = sk_float_sqrt(fX*fX + fY*fY + fZ*fZ);
+ if (mag) {
+ float scale = 1.0f / mag;
+ unit->fX = fX * scale;
+ unit->fY = fY * scale;
+ unit->fZ = fZ * scale;
+ } else {
+ unit->fX = unit->fY = unit->fZ = 0;
+ }
+#else
+ Sk64 tmp1, tmp2;
+
+ tmp1.setMul(fX, fX);
+ tmp2.setMul(fY, fY);
+ tmp1.add(tmp2);
+ tmp2.setMul(fZ, fZ);
+ tmp1.add(tmp2);
+
+ SkFixed mag = tmp1.getSqrt();
+ if (mag) {
+ // what if mag < SK_Fixed1 ??? we will underflow the fixdiv
+ SkFixed scale = SkFixedDiv(SK_Fract1, mag);
+ unit->fX = SkFixedMul(fX, scale);
+ unit->fY = SkFixedMul(fY, scale);
+ unit->fZ = SkFixedMul(fZ, scale);
+ } else {
+ unit->fX = unit->fY = unit->fZ = 0;
+ }
+#endif
+ return mag;
+}
+
+SkUnitScalar SkUnit3D::Dot(const SkUnit3D& a, const SkUnit3D& b) {
+ return SkUnitScalarMul(a.fX, b.fX) +
+ SkUnitScalarMul(a.fY, b.fY) +
+ SkUnitScalarMul(a.fZ, b.fZ);
+}
+
+void SkUnit3D::Cross(const SkUnit3D& a, const SkUnit3D& b, SkUnit3D* cross) {
+ SkASSERT(cross);
+
+ // use x,y,z, in case &a == cross or &b == cross
+
+ SkScalar x = SkUnitScalarMul(a.fY, b.fZ) - SkUnitScalarMul(a.fZ, b.fY);
+ SkScalar y = SkUnitScalarMul(a.fZ, b.fX) - SkUnitScalarMul(a.fX, b.fY);
+ SkScalar z = SkUnitScalarMul(a.fX, b.fY) - SkUnitScalarMul(a.fY, b.fX);
+
+ cross->set(x, y, z);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPatch3D::SkPatch3D() {
+ this->reset();
+}
+
+void SkPatch3D::reset() {
+ fOrigin.set(0, 0, 0);
+ fU.set(SK_Scalar1, 0, 0);
+ fV.set(0, -SK_Scalar1, 0);
+}
+
+void SkPatch3D::transform(const SkMatrix3D& m, SkPatch3D* dst) const {
+ if (dst == NULL) {
+ dst = (SkPatch3D*)this;
+ }
+ m.mapVector(fU, &dst->fU);
+ m.mapVector(fV, &dst->fV);
+ m.mapPoint(fOrigin, &dst->fOrigin);
+}
+
+SkScalar SkPatch3D::dotWith(SkScalar dx, SkScalar dy, SkScalar dz) const {
+ SkScalar cx = SkScalarMul(fU.fY, fV.fZ) - SkScalarMul(fU.fZ, fV.fY);
+ SkScalar cy = SkScalarMul(fU.fZ, fV.fX) - SkScalarMul(fU.fX, fV.fY);
+ SkScalar cz = SkScalarMul(fU.fX, fV.fY) - SkScalarMul(fU.fY, fV.fX);
+
+ return SkScalarMul(cx, dx) + SkScalarMul(cy, dy) + SkScalarMul(cz, dz);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix3D::reset() {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[0][0] = fMat[1][1] = fMat[2][2] = SK_Scalar1;
+}
+
+void SkMatrix3D::setTranslate(SkScalar x, SkScalar y, SkScalar z) {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[0][0] = x;
+ fMat[1][1] = y;
+ fMat[2][2] = z;
+}
+
+void SkMatrix3D::setRotateX(SkScalar degX) {
+ SkScalar s, c;
+
+ s = SkScalarSinCos(SkDegreesToRadians(degX), &c);
+ this->setRow(0, SK_Scalar1, 0, 0);
+ this->setRow(1, 0, c, -s);
+ this->setRow(2, 0, s, c);
+}
+
+void SkMatrix3D::setRotateY(SkScalar degY) {
+ SkScalar s, c;
+
+ s = SkScalarSinCos(SkDegreesToRadians(degY), &c);
+ this->setRow(0, c, 0, -s);
+ this->setRow(1, 0, SK_Scalar1, 0);
+ this->setRow(2, s, 0, c);
+}
+
+void SkMatrix3D::setRotateZ(SkScalar degZ) {
+ SkScalar s, c;
+
+ s = SkScalarSinCos(SkDegreesToRadians(degZ), &c);
+ this->setRow(0, c, -s, 0);
+ this->setRow(1, s, c, 0);
+ this->setRow(2, 0, 0, SK_Scalar1);
+}
+
+void SkMatrix3D::preTranslate(SkScalar x, SkScalar y, SkScalar z) {
+ SkScalar col[3] = { x, y, z};
+
+ for (int i = 0; i < 3; i++) {
+ fMat[i][3] += SkScalarDot(3, &fMat[i][0], 1, col, 1);
+ }
+}
+
+void SkMatrix3D::preRotateX(SkScalar degX) {
+ SkMatrix3D m;
+ m.setRotateX(degX);
+ this->setConcat(*this, m);
+}
+
+void SkMatrix3D::preRotateY(SkScalar degY) {
+ SkMatrix3D m;
+ m.setRotateY(degY);
+ this->setConcat(*this, m);
+}
+
+void SkMatrix3D::preRotateZ(SkScalar degZ) {
+ SkMatrix3D m;
+ m.setRotateZ(degZ);
+ this->setConcat(*this, m);
+}
+
+void SkMatrix3D::setConcat(const SkMatrix3D& a, const SkMatrix3D& b) {
+ SkMatrix3D tmp;
+ SkMatrix3D* c = this;
+
+ if (this == &a || this == &b) {
+ c = &tmp;
+ }
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ c->fMat[i][j] = SkScalarDot(3, &a.fMat[i][0], 1, &b.fMat[0][j], 4);
+ }
+ c->fMat[i][3] = SkScalarDot(3, &a.fMat[i][0], 1,
+ &b.fMat[0][3], 4) + a.fMat[i][3];
+ }
+
+ if (c == &tmp) {
+ *this = tmp;
+ }
+}
+
+void SkMatrix3D::mapPoint(const SkPoint3D& src, SkPoint3D* dst) const {
+ SkScalar x = SkScalarDot(3, &fMat[0][0], 1, &src.fX, 1) + fMat[0][3];
+ SkScalar y = SkScalarDot(3, &fMat[1][0], 1, &src.fX, 1) + fMat[1][3];
+ SkScalar z = SkScalarDot(3, &fMat[2][0], 1, &src.fX, 1) + fMat[2][3];
+ dst->set(x, y, z);
+}
+
+void SkMatrix3D::mapVector(const SkVector3D& src, SkVector3D* dst) const {
+ SkScalar x = SkScalarDot(3, &fMat[0][0], 1, &src.fX, 1);
+ SkScalar y = SkScalarDot(3, &fMat[1][0], 1, &src.fX, 1);
+ SkScalar z = SkScalarDot(3, &fMat[2][0], 1, &src.fX, 1);
+ dst->set(x, y, z);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkCamera3D::SkCamera3D() {
+ this->reset();
+}
+
+void SkCamera3D::reset() {
+ fLocation.set(0, 0, -SkIntToScalar(576)); // 8 inches backward
+ fAxis.set(0, 0, SK_Scalar1); // forward
+ fZenith.set(0, -SK_Scalar1, 0); // up
+
+ fObserver.set(0, 0, fLocation.fZ);
+
+ fNeedToUpdate = true;
+}
+
+void SkCamera3D::update() {
+ fNeedToUpdate = true;
+}
+
+void SkCamera3D::doUpdate() const {
+ SkUnit3D axis, zenith, cross;
+
+ fAxis.normalize(&axis);
+
+ {
+ SkScalar dot = SkUnit3D::Dot(*SkTCast<const SkUnit3D*>(&fZenith), axis);
+
+ zenith.fX = fZenith.fX - SkUnitScalarMul(dot, axis.fX);
+ zenith.fY = fZenith.fY - SkUnitScalarMul(dot, axis.fY);
+ zenith.fZ = fZenith.fZ - SkUnitScalarMul(dot, axis.fZ);
+
+ SkTCast<SkPoint3D*>(&zenith)->normalize(&zenith);
+ }
+
+ SkUnit3D::Cross(axis, zenith, &cross);
+
+ {
+ SkMatrix* orien = &fOrientation;
+ SkScalar x = fObserver.fX;
+ SkScalar y = fObserver.fY;
+ SkScalar z = fObserver.fZ;
+
+ orien->set(SkMatrix::kMScaleX, SkUnitScalarMul(x, axis.fX) - SkUnitScalarMul(z, cross.fX));
+ orien->set(SkMatrix::kMSkewX, SkUnitScalarMul(x, axis.fY) - SkUnitScalarMul(z, cross.fY));
+ orien->set(SkMatrix::kMTransX, SkUnitScalarMul(x, axis.fZ) - SkUnitScalarMul(z, cross.fZ));
+ orien->set(SkMatrix::kMSkewY, SkUnitScalarMul(y, axis.fX) - SkUnitScalarMul(z, zenith.fX));
+ orien->set(SkMatrix::kMScaleY, SkUnitScalarMul(y, axis.fY) - SkUnitScalarMul(z, zenith.fY));
+ orien->set(SkMatrix::kMTransY, SkUnitScalarMul(y, axis.fZ) - SkUnitScalarMul(z, zenith.fZ));
+ orien->set(SkMatrix::kMPersp0, axis.fX);
+ orien->set(SkMatrix::kMPersp1, axis.fY);
+ orien->set(SkMatrix::kMPersp2, axis.fZ);
+ }
+}
+
+void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const {
+ if (fNeedToUpdate) {
+ this->doUpdate();
+ fNeedToUpdate = false;
+ }
+
+ const SkScalar* mapPtr = (const SkScalar*)(const void*)&fOrientation;
+ const SkScalar* patchPtr;
+ SkPoint3D diff;
+ SkScalar dot;
+
+ diff.fX = quilt.fOrigin.fX - fLocation.fX;
+ diff.fY = quilt.fOrigin.fY - fLocation.fY;
+ diff.fZ = quilt.fOrigin.fZ - fLocation.fZ;
+
+ dot = SkUnit3D::Dot(*SkTCast<const SkUnit3D*>(&diff),
+ *SkTCast<const SkUnit3D*>(SkTCast<const SkScalar*>(&fOrientation) + 6));
+
+ patchPtr = (const SkScalar*)&quilt;
+ matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
+ matrix->set(SkMatrix::kMSkewY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
+ matrix->set(SkMatrix::kMPersp0, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
+
+ patchPtr += 3;
+ matrix->set(SkMatrix::kMSkewX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
+ matrix->set(SkMatrix::kMScaleY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
+ matrix->set(SkMatrix::kMPersp1, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
+
+ patchPtr = (const SkScalar*)(const void*)&diff;
+ matrix->set(SkMatrix::kMTransX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
+ matrix->set(SkMatrix::kMTransY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
+ matrix->set(SkMatrix::kMPersp2, SK_UnitScalar1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+Sk3DView::Sk3DView() {
+ fInitialRec.fMatrix.reset();
+ fRec = &fInitialRec;
+}
+
+Sk3DView::~Sk3DView() {
+ Rec* rec = fRec;
+ while (rec != &fInitialRec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+}
+
+void Sk3DView::save() {
+ Rec* rec = SkNEW(Rec);
+ rec->fNext = fRec;
+ rec->fMatrix = fRec->fMatrix;
+ fRec = rec;
+}
+
+void Sk3DView::restore() {
+ SkASSERT(fRec != &fInitialRec);
+ Rec* next = fRec->fNext;
+ SkDELETE(fRec);
+ fRec = next;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+void Sk3DView::setCameraLocation(SkScalar x, SkScalar y, SkScalar z) {
+ // the camera location is passed in inches, set in pt
+ SkScalar lz = z * SkFloatToScalar(72.0f);
+ fCamera.fLocation.set(x * SkFloatToScalar(72.0f), y * SkFloatToScalar(72.0f), lz);
+ fCamera.fObserver.set(0, 0, lz);
+ fCamera.update();
+
+}
+
+SkScalar Sk3DView::getCameraLocationX() {
+ return fCamera.fLocation.fX / SkFloatToScalar(72.0f);
+}
+
+SkScalar Sk3DView::getCameraLocationY() {
+ return fCamera.fLocation.fY / SkFloatToScalar(72.0f);
+}
+
+SkScalar Sk3DView::getCameraLocationZ() {
+ return fCamera.fLocation.fZ / SkFloatToScalar(72.0f);
+}
+#endif
+
+void Sk3DView::translate(SkScalar x, SkScalar y, SkScalar z) {
+ fRec->fMatrix.preTranslate(x, y, z);
+}
+
+void Sk3DView::rotateX(SkScalar deg) {
+ fRec->fMatrix.preRotateX(deg);
+}
+
+void Sk3DView::rotateY(SkScalar deg) {
+ fRec->fMatrix.preRotateY(deg);
+}
+
+void Sk3DView::rotateZ(SkScalar deg) {
+ fRec->fMatrix.preRotateZ(deg);
+}
+
+SkScalar Sk3DView::dotWithNormal(SkScalar x, SkScalar y, SkScalar z) const {
+ SkPatch3D patch;
+ patch.transform(fRec->fMatrix);
+ return patch.dotWith(x, y, z);
+}
+
+void Sk3DView::getMatrix(SkMatrix* matrix) const {
+ if (matrix != NULL) {
+ SkPatch3D patch;
+ patch.transform(fRec->fMatrix);
+ fCamera.patchToMatrix(patch, matrix);
+ }
+}
+
+#include "SkCanvas.h"
+
+void Sk3DView::applyToCanvas(SkCanvas* canvas) const {
+ SkMatrix matrix;
+
+ this->getMatrix(&matrix);
+ canvas->concat(matrix);
+}
diff --git a/utils/SkCanvasStack.cpp b/utils/SkCanvasStack.cpp
new file mode 100644
index 00000000..db5a8b27
--- /dev/null
+++ b/utils/SkCanvasStack.cpp
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkCanvasStack.h"
+
+SkCanvasStack::SkCanvasStack(int width, int height)
+ : INHERITED(width, height) {}
+
+SkCanvasStack::~SkCanvasStack() {
+ this->removeAll();
+}
+
+void SkCanvasStack::pushCanvas(SkCanvas* canvas, const SkIPoint& origin) {
+ if (canvas) {
+ // compute the bounds of this canvas
+ const SkIRect canvasBounds = SkIRect::MakeSize(canvas->getDeviceSize());
+
+ // push the canvas onto the stack
+ this->INHERITED::addCanvas(canvas);
+
+ // push the canvas data onto the stack
+ CanvasData* data = &fCanvasData.push_back();
+ data->origin = origin;
+ data->requiredClip.setRect(canvasBounds);
+
+ // subtract this region from the canvas objects already on the stack.
+ // This ensures they do not draw into the space occupied by the layers
+ // above them.
+ for (int i = fList.count() - 1; i > 0; --i) {
+ SkIRect localBounds = canvasBounds;
+ localBounds.offset(origin - fCanvasData[i-1].origin);
+
+ fCanvasData[i-1].requiredClip.op(localBounds, SkRegion::kDifference_Op);
+ fList[i-i]->clipRegion(fCanvasData[i-1].requiredClip);
+ }
+ }
+ SkASSERT(fList.count() == fCanvasData.count());
+}
+
+void SkCanvasStack::removeAll() {
+ fCanvasData.reset();
+ this->INHERITED::removeAll();
+}
+
+/**
+ * Traverse all canvases (e.g. layers) the stack and ensure that they are clipped
+ * to their bounds and that the area covered by any canvas higher in the stack is
+ * also clipped out.
+ */
+void SkCanvasStack::clipToZOrderedBounds() {
+ SkASSERT(fList.count() == fCanvasData.count());
+ for (int i = 0; i < fList.count(); ++i) {
+ fList[i]->clipRegion(fCanvasData[i].requiredClip, SkRegion::kIntersect_Op);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * We need to handle setMatrix specially as it overwrites the matrix in each
+ * canvas unlike all other matrix operations (i.e. translate, scale, etc) which
+ * just pre-concatenate with the existing matrix.
+ */
+void SkCanvasStack::setMatrix(const SkMatrix& matrix) {
+ SkASSERT(fList.count() == fCanvasData.count());
+ for (int i = 0; i < fList.count(); ++i) {
+
+ SkMatrix tempMatrix = matrix;
+ tempMatrix.postTranslate(SkIntToScalar(-fCanvasData[i].origin.x()),
+ SkIntToScalar(-fCanvasData[i].origin.y()));
+ fList[i]->setMatrix(tempMatrix);
+ }
+ this->SkCanvas::setMatrix(matrix);
+}
+
+bool SkCanvasStack::clipRect(const SkRect& r, SkRegion::Op op, bool aa) {
+ bool result = this->INHERITED::clipRect(r, op, aa);
+ this->clipToZOrderedBounds();
+ return result;
+}
+
+bool SkCanvasStack::clipRRect(const SkRRect& rr, SkRegion::Op op, bool aa) {
+ bool result = this->INHERITED::clipRRect(rr, op, aa);
+ this->clipToZOrderedBounds();
+ return result;
+}
+
+bool SkCanvasStack::clipPath(const SkPath& p, SkRegion::Op op, bool aa) {
+ bool result = this->INHERITED::clipPath(p, op, aa);
+ this->clipToZOrderedBounds();
+ return result;
+}
+
+bool SkCanvasStack::clipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+ SkASSERT(fList.count() == fCanvasData.count());
+ for (int i = 0; i < fList.count(); ++i) {
+ SkRegion tempRegion;
+ deviceRgn.translate(-fCanvasData[i].origin.x(),
+ -fCanvasData[i].origin.y(), &tempRegion);
+ tempRegion.op(fCanvasData[i].requiredClip, SkRegion::kIntersect_Op);
+ fList[i]->clipRegion(tempRegion, op);
+ }
+ return this->SkCanvas::clipRegion(deviceRgn, op);
+}
diff --git a/utils/SkCanvasStack.h b/utils/SkCanvasStack.h
new file mode 100644
index 00000000..2137d308
--- /dev/null
+++ b/utils/SkCanvasStack.h
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCanvasStack_DEFINED
+#define SkCanvasStack_DEFINED
+
+#include "SkNWayCanvas.h"
+#include "SkTArray.h"
+
+class SkCanvasStack : public SkNWayCanvas {
+public:
+ SkCanvasStack(int width, int height);
+ virtual ~SkCanvasStack();
+
+ void pushCanvas(SkCanvas* canvas, const SkIPoint& origin);
+ virtual void removeAll() SK_OVERRIDE;
+
+ /*
+ * The following add/remove canvas methods are overrides from SkNWayCanvas
+ * that do not make sense in the context of our CanvasStack, but since we
+ * can share most of the other implementation of NWay we override those
+ * methods to be no-ops.
+ */
+ virtual void addCanvas(SkCanvas*) SK_OVERRIDE { SkASSERT(!"Invalid Op"); }
+ virtual void removeCanvas(SkCanvas*) SK_OVERRIDE { SkASSERT(!"Invalid Op"); }
+
+ 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;
+
+private:
+ void clipToZOrderedBounds();
+
+ struct CanvasData {
+ SkIPoint origin;
+ SkRegion requiredClip;
+ };
+
+ SkTArray<CanvasData> fCanvasData;
+
+ typedef SkNWayCanvas INHERITED;
+};
+
+#endif
diff --git a/utils/SkCanvasStateUtils.cpp b/utils/SkCanvasStateUtils.cpp
new file mode 100644
index 00000000..3fd125bf
--- /dev/null
+++ b/utils/SkCanvasStateUtils.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvasStateUtils.h"
+
+#include "SkDevice.h"
+#include "SkCanvas.h"
+#include "SkCanvasStack.h"
+#include "SkWriter32.h"
+
+typedef SkDevice SkBitmapDevice;
+
+#define CANVAS_STATE_VERSION 1
+/*
+ * WARNING: The structs below are part of a stable ABI and as such we explicitly
+ * use unambigious primitives (e.g. int32_t instead of an enum).
+ *
+ * ANY CHANGES TO THE STRUCTS BELOW THAT IMPACT THE ABI SHOULD RESULT IN AN
+ * UPDATE OF THE CANVAS_STATE_VERSION. SUCH CHANGES SHOULD ONLY BE MADE IF
+ * ABSOLUTELY NECESSARY!
+ */
+enum RasterConfigs {
+ kUnknown_RasterConfig = 0,
+ kRGB_565_RasterConfig = 1,
+ kARGB_8888_RasterConfig = 2
+};
+typedef int32_t RasterConfig;
+
+enum CanvasBackends {
+ kUnknown_CanvasBackend = 0,
+ kRaster_CanvasBackend = 1,
+ kGPU_CanvasBackend = 2,
+ kPDF_CanvasBackend = 3
+};
+typedef int32_t CanvasBackend;
+
+struct ClipRect {
+ int32_t left, top, right, bottom;
+};
+
+struct SkMCState {
+ float matrix[9];
+ // NOTE: this only works for non-antialiased clips
+ int32_t clipRectCount;
+ ClipRect* clipRects;
+};
+
+// NOTE: If you add more members, bump CanvasState::version.
+struct SkCanvasLayerState {
+ CanvasBackend type;
+ int32_t x, y;
+ int32_t width;
+ int32_t height;
+
+ SkMCState mcState;
+
+ union {
+ struct {
+ RasterConfig config; // pixel format: a value from RasterConfigs.
+ uint32_t rowBytes; // Number of bytes from start of one line to next.
+ void* pixels; // The pixels, all (height * rowBytes) of them.
+ } raster;
+ struct {
+ int32_t textureID;
+ } gpu;
+ };
+};
+
+class SkCanvasState {
+public:
+ SkCanvasState(SkCanvas* canvas) {
+ SkASSERT(canvas);
+ version = CANVAS_STATE_VERSION;
+ width = canvas->getDeviceSize().width();
+ height = canvas->getDeviceSize().height();
+ layerCount = 0;
+ layers = NULL;
+ originalCanvas = SkRef(canvas);
+
+ mcState.clipRectCount = 0;
+ mcState.clipRects = NULL;
+ }
+
+ ~SkCanvasState() {
+ // loop through the layers and free the data allocated to the clipRects
+ for (int i = 0; i < layerCount; ++i) {
+ sk_free(layers[i].mcState.clipRects);
+ }
+
+ sk_free(mcState.clipRects);
+ sk_free(layers);
+
+ // it is now safe to free the canvas since there should be no remaining
+ // references to the content that is referenced by this canvas (e.g. pixels)
+ originalCanvas->unref();
+ }
+
+ /**
+ * The version this struct was built with. This field must always appear
+ * first in the struct so that when the versions don't match (and the
+ * remaining contents and size are potentially different) we can still
+ * compare the version numbers.
+ */
+ int32_t version;
+
+ int32_t width;
+ int32_t height;
+
+ SkMCState mcState;
+
+ int32_t layerCount;
+ SkCanvasLayerState* layers;
+
+private:
+ SkCanvas* originalCanvas;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class ClipValidator : public SkCanvas::ClipVisitor {
+public:
+ ClipValidator() : fFailed(false) {}
+ bool failed() { return fFailed; }
+
+ // ClipVisitor
+ virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) SK_OVERRIDE {
+ fFailed |= antialias;
+ }
+
+ virtual void clipPath(const SkPath&, SkRegion::Op, bool antialias) SK_OVERRIDE {
+ fFailed |= antialias;
+ }
+
+private:
+ bool fFailed;
+};
+
+static void setup_MC_state(SkMCState* state, const SkMatrix& matrix, const SkRegion& clip) {
+ // initialize the struct
+ state->clipRectCount = 0;
+
+ // capture the matrix
+ for (int i = 0; i < 9; i++) {
+ state->matrix[i] = matrix.get(i);
+ }
+
+ /*
+ * capture the clip
+ *
+ * storage is allocated on the stack for the first 4 rects. This value was
+ * chosen somewhat arbitrarily, but does allow us to represent simple clips
+ * and some more common complex clips (e.g. a clipRect with a sub-rect
+ * clipped out of its interior) without needing to malloc any additional memory.
+ */
+ const int clipBufferSize = 4 * sizeof(ClipRect);
+ char clipBuffer[clipBufferSize];
+ SkWriter32 clipWriter(sizeof(ClipRect), clipBuffer, clipBufferSize);
+
+ if (!clip.isEmpty()) {
+ // only returns the b/w clip so aa clips fail
+ SkRegion::Iterator clip_iterator(clip);
+ for (; !clip_iterator.done(); clip_iterator.next()) {
+ // this assumes the SkIRect is stored in l,t,r,b ordering which
+ // matches the ordering of our ClipRect struct
+ clipWriter.writeIRect(clip_iterator.rect());
+ state->clipRectCount++;
+ }
+ }
+
+ // allocate memory for the clip then and copy them to the struct
+ state->clipRects = (ClipRect*) sk_malloc_throw(clipWriter.size());
+ clipWriter.flatten(state->clipRects);
+}
+
+
+
+SkCanvasState* SkCanvasStateUtils::CaptureCanvasState(SkCanvas* canvas) {
+ SkASSERT(canvas);
+
+ // Check the clip can be decomposed into rectangles (i.e. no soft clips).
+ ClipValidator validator;
+ canvas->replayClips(&validator);
+ if (validator.failed()) {
+ SkDEBUGF(("CaptureCanvasState does not support canvases with antialiased clips.\n"));
+ return NULL;
+ }
+
+ SkAutoTDelete<SkCanvasState> canvasState(SkNEW_ARGS(SkCanvasState, (canvas)));
+
+ // decompose the total matrix and clip
+ setup_MC_state(&canvasState->mcState, canvas->getTotalMatrix(), canvas->getTotalClip());
+
+ /*
+ * decompose the layers
+ *
+ * storage is allocated on the stack for the first 3 layers. It is common in
+ * some view systems (e.g. Android) that a few non-clipped layers are present
+ * and we will not need to malloc any additional memory in those cases.
+ */
+ const int layerBufferSize = 3 * sizeof(SkCanvasLayerState);
+ char layerBuffer[layerBufferSize];
+ SkWriter32 layerWriter(sizeof(SkCanvasLayerState), layerBuffer, layerBufferSize);
+ int layerCount = 0;
+ for (SkCanvas::LayerIter layer(canvas, true/*skipEmptyClips*/); !layer.done(); layer.next()) {
+
+ // we currently only work for bitmap backed devices
+ const SkBitmap& bitmap = layer.device()->accessBitmap(true/*changePixels*/);
+ if (bitmap.empty() || bitmap.isNull() || !bitmap.lockPixelsAreWritable()) {
+ return NULL;
+ }
+
+ SkCanvasLayerState* layerState =
+ (SkCanvasLayerState*) layerWriter.reserve(sizeof(SkCanvasLayerState));
+ layerState->type = kRaster_CanvasBackend;
+ layerState->x = layer.x();
+ layerState->y = layer.y();
+ layerState->width = bitmap.width();
+ layerState->height = bitmap.height();
+
+ switch (bitmap.config()) {
+ case SkBitmap::kARGB_8888_Config:
+ layerState->raster.config = kARGB_8888_RasterConfig;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ layerState->raster.config = kRGB_565_RasterConfig;
+ break;
+ default:
+ return NULL;
+ }
+ layerState->raster.rowBytes = bitmap.rowBytes();
+ layerState->raster.pixels = bitmap.getPixels();
+
+ setup_MC_state(&layerState->mcState, layer.matrix(), layer.clip());
+ layerCount++;
+ }
+
+ // allocate memory for the layers and then and copy them to the struct
+ SkASSERT(layerWriter.size() == layerCount * sizeof(SkCanvasLayerState));
+ canvasState->layerCount = layerCount;
+ canvasState->layers = (SkCanvasLayerState*) sk_malloc_throw(layerWriter.size());
+ layerWriter.flatten(canvasState->layers);
+
+ // for now, just ignore any client supplied DrawFilter.
+ if (canvas->getDrawFilter()) {
+ SkDEBUGF(("CaptureCanvasState will ignore the canvases draw filter.\n"));
+ }
+
+ return canvasState.detach();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void setup_canvas_from_MC_state(const SkMCState& state, SkCanvas* canvas) {
+ // reconstruct the matrix
+ SkMatrix matrix;
+ for (int i = 0; i < 9; i++) {
+ matrix.set(i, state.matrix[i]);
+ }
+
+ // reconstruct the clip
+ SkRegion clip;
+ for (int i = 0; i < state.clipRectCount; ++i) {
+ clip.op(SkIRect::MakeLTRB(state.clipRects[i].left,
+ state.clipRects[i].top,
+ state.clipRects[i].right,
+ state.clipRects[i].bottom),
+ SkRegion::kUnion_Op);
+ }
+
+ canvas->setMatrix(matrix);
+ canvas->setClipRegion(clip);
+}
+
+static SkCanvas* create_canvas_from_canvas_layer(const SkCanvasLayerState& layerState) {
+ SkASSERT(kRaster_CanvasBackend == layerState.type);
+
+ SkBitmap bitmap;
+ SkBitmap::Config config =
+ layerState.raster.config == kARGB_8888_RasterConfig ? SkBitmap::kARGB_8888_Config :
+ layerState.raster.config == kRGB_565_RasterConfig ? SkBitmap::kRGB_565_Config :
+ SkBitmap::kNo_Config;
+
+ if (config == SkBitmap::kNo_Config) {
+ return NULL;
+ }
+
+ bitmap.setConfig(config, layerState.width, layerState.height,
+ layerState.raster.rowBytes);
+ bitmap.setPixels(layerState.raster.pixels);
+
+ SkASSERT(!bitmap.empty());
+ SkASSERT(!bitmap.isNull());
+
+ // create a device & canvas
+ SkAutoTUnref<SkBitmapDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
+ SkAutoTUnref<SkCanvas> canvas(SkNEW_ARGS(SkCanvas, (device.get())));
+
+ // setup the matrix and clip
+ setup_canvas_from_MC_state(layerState.mcState, canvas.get());
+
+ return canvas.detach();
+}
+
+SkCanvas* SkCanvasStateUtils::CreateFromCanvasState(const SkCanvasState* state) {
+ SkASSERT(state);
+
+ // check that the versions match
+ if (CANVAS_STATE_VERSION != state->version) {
+ SkDebugf("CreateFromCanvasState version does not match the one use to create the input");
+ return NULL;
+ }
+
+ if (state->layerCount < 1) {
+ return NULL;
+ }
+
+ SkAutoTUnref<SkCanvasStack> canvas(SkNEW_ARGS(SkCanvasStack, (state->width, state->height)));
+
+ // setup the matrix and clip on the n-way canvas
+ setup_canvas_from_MC_state(state->mcState, canvas);
+
+ // Iterate over the layers and add them to the n-way canvas
+ for (int i = state->layerCount - 1; i >= 0; --i) {
+ SkAutoTUnref<SkCanvas> canvasLayer(create_canvas_from_canvas_layer(state->layers[i]));
+ if (!canvasLayer.get()) {
+ return NULL;
+ }
+ canvas->pushCanvas(canvasLayer.get(), SkIPoint::Make(state->layers[i].x,
+ state->layers[i].y));
+ }
+
+ return canvas.detach();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SkCanvasStateUtils::ReleaseCanvasState(SkCanvasState* state) {
+ SkDELETE(state);
+}
diff --git a/utils/SkCondVar.cpp b/utils/SkCondVar.cpp
new file mode 100644
index 00000000..5d001c0e
--- /dev/null
+++ b/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/utils/SkCountdown.cpp b/utils/SkCountdown.cpp
new file mode 100644
index 00000000..98b35450
--- /dev/null
+++ b/utils/SkCountdown.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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/utils/SkCubicInterval.cpp b/utils/SkCubicInterval.cpp
new file mode 100644
index 00000000..566023a2
--- /dev/null
+++ b/utils/SkCubicInterval.cpp
@@ -0,0 +1,67 @@
+
+/*
+ * 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 "SkCubicInterval.h"
+
+static SkScalar eval_cubic(SkScalar c1, SkScalar c2, SkScalar c3,
+ SkScalar t) {
+ return SkScalarMul(SkScalarMul(SkScalarMul(c3, t) + c2, t) + c1, t);
+}
+
+static SkScalar find_cubic_t(SkScalar c1, SkScalar c2, SkScalar c3,
+ SkScalar targetX) {
+ SkScalar minT = 0;
+ SkScalar maxT = SK_Scalar1;
+ SkScalar t;
+
+ for (;;) {
+ t = SkScalarAve(minT, maxT);
+ SkScalar x = eval_cubic(c1, c2, c3, t);
+ if (SkScalarNearlyZero(x - targetX)) {
+ break;
+ }
+ // subdivide the range and try again
+ if (x < targetX) {
+ minT = t;
+ } else {
+ maxT = t;
+ }
+ }
+ return t;
+}
+
+/*
+ a(1-t)^3 + 3bt(1-t)^2 + 3ct^2(1-t) + dt^3
+ a: [0, 0]
+ d: [1, 1]
+
+ 3bt - 6bt^2 + 3bt^3 + 3ct^2 - 3ct^3 + t^3
+ C1 = t^1: 3b
+ C2 = t^2: 3c - 6b
+ C3 = t^3: 3b - 3c + 1
+
+ ((C3*t + C2)*t + C1)*t
+ */
+SkScalar SkEvalCubicInterval(SkScalar x1, SkScalar y1,
+ SkScalar x2, SkScalar y2,
+ SkScalar unitX) {
+ x1 = SkScalarPin(x1, 0, SK_Scalar1);
+ x2 = SkScalarPin(x2, 0, SK_Scalar1);
+ unitX = SkScalarPin(unitX, 0, SK_Scalar1);
+
+ // First compute our coefficients in X
+ x1 *= 3;
+ x2 *= 3;
+
+ // now search for t given unitX
+ SkScalar t = find_cubic_t(x1, x2 - 2*x1, x1 - x2 + SK_Scalar1, unitX);
+
+ // now evaluate the cubic in Y
+ y1 *= 3;
+ y2 *= 3;
+ return eval_cubic(y1, y2 - 2*y1, y1 - y2 + SK_Scalar1, t);
+}
diff --git a/utils/SkCullPoints.cpp b/utils/SkCullPoints.cpp
new file mode 100644
index 00000000..efb94f48
--- /dev/null
+++ b/utils/SkCullPoints.cpp
@@ -0,0 +1,219 @@
+
+/*
+ * 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 "SkCullPoints.h"
+#include "Sk64.h"
+
+static bool cross_product_is_neg(const SkIPoint& v, int dx, int dy) {
+#if 0
+ return v.fX * dy - v.fY * dx < 0;
+#else
+ Sk64 tmp0, tmp1;
+
+ tmp0.setMul(v.fX, dy);
+ tmp1.setMul(dx, v.fY);
+ tmp0.sub(tmp1);
+ return tmp0.isNeg() != 0;
+#endif
+}
+
+bool SkCullPoints::sect_test(int x0, int y0, int x1, int y1) const {
+ const SkIRect& r = fR;
+
+ if ((x0 < r.fLeft && x1 < r.fLeft) ||
+ (x0 > r.fRight && x1 > r.fRight) ||
+ (y0 < r.fTop && y1 < r.fTop) ||
+ (y0 > r.fBottom && y1 > r.fBottom)) {
+ return false;
+ }
+
+ // since the crossprod test is a little expensive, check for easy-in cases first
+ if (r.contains(x0, y0) || r.contains(x1, y1)) {
+ return true;
+ }
+
+ // At this point we're not sure, so we do a crossprod test
+ SkIPoint vec;
+ const SkIPoint* rAsQuad = fAsQuad;
+
+ vec.set(x1 - x0, y1 - y0);
+ bool isNeg = cross_product_is_neg(vec, x0 - rAsQuad[0].fX, y0 - rAsQuad[0].fY);
+ for (int i = 1; i < 4; i++) {
+ if (cross_product_is_neg(vec, x0 - rAsQuad[i].fX, y0 - rAsQuad[i].fY) != isNeg) {
+ return true;
+ }
+ }
+ return false; // we didn't intersect
+}
+
+static void toQuad(const SkIRect& r, SkIPoint quad[4]) {
+ SkASSERT(quad);
+
+ quad[0].set(r.fLeft, r.fTop);
+ quad[1].set(r.fRight, r.fTop);
+ quad[2].set(r.fRight, r.fBottom);
+ quad[3].set(r.fLeft, r.fBottom);
+}
+
+SkCullPoints::SkCullPoints() {
+ SkIRect r;
+ r.setEmpty();
+ this->reset(r);
+}
+
+SkCullPoints::SkCullPoints(const SkIRect& r) {
+ this->reset(r);
+}
+
+void SkCullPoints::reset(const SkIRect& r) {
+ fR = r;
+ toQuad(fR, fAsQuad);
+ fPrevPt.set(0, 0);
+ fPrevResult = kNo_Result;
+}
+
+void SkCullPoints::moveTo(int x, int y) {
+ fPrevPt.set(x, y);
+ fPrevResult = kNo_Result; // so we trigger a movetolineto later
+}
+
+SkCullPoints::LineToResult SkCullPoints::lineTo(int x, int y, SkIPoint line[]) {
+ SkASSERT(line != NULL);
+
+ LineToResult result = kNo_Result;
+ int x0 = fPrevPt.fX;
+ int y0 = fPrevPt.fY;
+
+ // need to upgrade sect_test to chop the result
+ // and to correctly return kLineTo_Result when the result is connected
+ // to the previous call-out
+ if (this->sect_test(x0, y0, x, y)) {
+ line[0].set(x0, y0);
+ line[1].set(x, y);
+
+ if (fPrevResult != kNo_Result && fPrevPt.equals(x0, y0)) {
+ result = kLineTo_Result;
+ } else {
+ result = kMoveToLineTo_Result;
+ }
+ }
+
+ fPrevPt.set(x, y);
+ fPrevResult = result;
+
+ return result;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "SkPath.h"
+
+SkCullPointsPath::SkCullPointsPath()
+ : fCP(), fPath(NULL) {
+}
+
+SkCullPointsPath::SkCullPointsPath(const SkIRect& r, SkPath* dst)
+ : fCP(r), fPath(dst) {
+}
+
+void SkCullPointsPath::reset(const SkIRect& r, SkPath* dst) {
+ fCP.reset(r);
+ fPath = dst;
+}
+
+void SkCullPointsPath::moveTo(int x, int y) {
+ fCP.moveTo(x, y);
+}
+
+void SkCullPointsPath::lineTo(int x, int y) {
+ SkIPoint pts[2];
+
+ switch (fCP.lineTo(x, y, pts)) {
+ case SkCullPoints::kMoveToLineTo_Result:
+ fPath->moveTo(SkIntToScalar(pts[0].fX), SkIntToScalar(pts[0].fY));
+ // fall through to the lineto case
+ case SkCullPoints::kLineTo_Result:
+ fPath->lineTo(SkIntToScalar(pts[1].fX), SkIntToScalar(pts[1].fY));
+ break;
+ default:
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkMatrix.h"
+#include "SkRegion.h"
+
+bool SkHitTestPath(const SkPath& path, SkRect& target, bool hires) {
+ if (target.isEmpty()) {
+ return false;
+ }
+
+ bool isInverse = path.isInverseFillType();
+ if (path.isEmpty()) {
+ return isInverse;
+ }
+
+ SkRect bounds = path.getBounds();
+
+ bool sects = SkRect::Intersects(target, bounds);
+ if (isInverse) {
+ if (!sects) {
+ return true;
+ }
+ } else {
+ if (!sects) {
+ return false;
+ }
+ if (target.contains(bounds)) {
+ return true;
+ }
+ }
+
+ SkPath devPath;
+ const SkPath* pathPtr;
+ SkRect devTarget;
+
+ if (hires) {
+ const SkScalar coordLimit = SkIntToScalar(16384);
+ const SkRect limit = { 0, 0, coordLimit, coordLimit };
+
+ SkMatrix matrix;
+ matrix.setRectToRect(bounds, limit, SkMatrix::kFill_ScaleToFit);
+
+ path.transform(matrix, &devPath);
+ matrix.mapRect(&devTarget, target);
+
+ pathPtr = &devPath;
+ } else {
+ devTarget = target;
+ pathPtr = &path;
+ }
+
+ SkIRect iTarget;
+ devTarget.round(&iTarget);
+ if (iTarget.isEmpty()) {
+ iTarget.fLeft = SkScalarFloorToInt(devTarget.fLeft);
+ iTarget.fTop = SkScalarFloorToInt(devTarget.fTop);
+ iTarget.fRight = iTarget.fLeft + 1;
+ iTarget.fBottom = iTarget.fTop + 1;
+ }
+
+ SkRegion clip(iTarget);
+ SkRegion rgn;
+ return rgn.setPath(*pathPtr, clip) ^ isInverse;
+}
+
+bool SkHitTestPath(const SkPath& path, SkScalar x, SkScalar y, bool hires) {
+ const SkScalar half = SK_ScalarHalf;
+ const SkScalar one = SK_Scalar1;
+ SkRect r = SkRect::MakeXYWH(x - half, y - half, one, one);
+ return SkHitTestPath(path, r, hires);
+}
diff --git a/utils/SkDebugTrace.h b/utils/SkDebugTrace.h
new file mode 100644
index 00000000..5ec3e1c4
--- /dev/null
+++ b/utils/SkDebugTrace.h
@@ -0,0 +1,24 @@
+
+/*
+ * 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 SkUserTrace_DEFINED
+#define SkUserTrace_DEFINED
+
+/* Sample implementation of SkUserTrace that routes all of the
+ trace macros to debug output stream.
+ To use this, redefine SK_USER_TRACE_INCLUDE_FILE in
+ include/config/SkUserConfig.h to point to this file
+*/
+#define SK_TRACE_EVENT0(event) \
+ SkDebugf("Trace: %s\n", event)
+#define SK_TRACE_EVENT1(event, name1, value1) \
+ SkDebugf("Trace: %s (%s=%s)\n", event, name1, value1)
+#define SK_TRACE_EVENT2(event, name1, value1, name2, value2) \
+ SkDebugf("Trace: %s (%s=%s, %s=%s)\n", event, name1, value1, name2, value2)
+
+#endif
diff --git a/utils/SkDeferredCanvas.cpp b/utils/SkDeferredCanvas.cpp
new file mode 100644
index 00000000..05daf63c
--- /dev/null
+++ b/utils/SkDeferredCanvas.cpp
@@ -0,0 +1,1011 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDeferredCanvas.h"
+
+#include "SkChunkAlloc.h"
+#include "SkColorFilter.h"
+#include "SkDevice.h"
+#include "SkDrawFilter.h"
+#include "SkGPipe.h"
+#include "SkPaint.h"
+#include "SkPaintPriv.h"
+#include "SkRRect.h"
+#include "SkShader.h"
+#include "SkSurface.h"
+
+enum {
+ // Deferred canvas will auto-flush when recording reaches this limit
+ kDefaultMaxRecordingStorageBytes = 64*1024*1024,
+ kDeferredCanvasBitmapSizeThreshold = ~0U, // Disables this feature
+};
+
+enum PlaybackMode {
+ kNormal_PlaybackMode,
+ kSilent_PlaybackMode,
+};
+
+namespace {
+bool shouldDrawImmediately(const SkBitmap* bitmap, const SkPaint* paint,
+ size_t bitmapSizeThreshold) {
+ if (bitmap && ((bitmap->getTexture() && !bitmap->isImmutable()) ||
+ (bitmap->getSize() > bitmapSizeThreshold))) {
+ return true;
+ }
+ if (paint) {
+ SkShader* shader = paint->getShader();
+ // Here we detect the case where the shader is an SkBitmapProcShader
+ // with a gpu texture attached. Checking this without RTTI
+ // requires making the assumption that only gradient shaders
+ // and SkBitmapProcShader implement asABitmap(). The following
+ // code may need to be revised if that assumption is ever broken.
+ if (shader && !shader->asAGradient(NULL)) {
+ SkBitmap bm;
+ if (shader->asABitmap(&bm, NULL, NULL) &&
+ NULL != bm.getTexture()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+}
+
+//-----------------------------------------------------------------------------
+// DeferredPipeController
+//-----------------------------------------------------------------------------
+
+class DeferredPipeController : public SkGPipeController {
+public:
+ DeferredPipeController();
+ void setPlaybackCanvas(SkCanvas*);
+ virtual ~DeferredPipeController();
+ virtual void* requestBlock(size_t minRequest, size_t* actual) SK_OVERRIDE;
+ virtual void notifyWritten(size_t bytes) SK_OVERRIDE;
+ void playback(bool silent);
+ bool hasPendingCommands() const { return fAllocator.blockCount() != 0; }
+ size_t storageAllocatedForRecording() const { return fAllocator.totalCapacity(); }
+private:
+ enum {
+ kMinBlockSize = 4096
+ };
+ struct PipeBlock {
+ PipeBlock(void* block, size_t size) { fBlock = block, fSize = size; }
+ void* fBlock;
+ size_t fSize;
+ };
+ void* fBlock;
+ size_t fBytesWritten;
+ SkChunkAlloc fAllocator;
+ SkTDArray<PipeBlock> fBlockList;
+ SkGPipeReader fReader;
+};
+
+DeferredPipeController::DeferredPipeController() :
+ fAllocator(kMinBlockSize) {
+ fBlock = NULL;
+ fBytesWritten = 0;
+}
+
+DeferredPipeController::~DeferredPipeController() {
+ fAllocator.reset();
+}
+
+void DeferredPipeController::setPlaybackCanvas(SkCanvas* canvas) {
+ fReader.setCanvas(canvas);
+}
+
+void* DeferredPipeController::requestBlock(size_t minRequest, size_t *actual) {
+ if (fBlock) {
+ // Save the previous block for later
+ PipeBlock previousBloc(fBlock, fBytesWritten);
+ fBlockList.push(previousBloc);
+ }
+ int32_t blockSize = SkMax32(minRequest, kMinBlockSize);
+ fBlock = fAllocator.allocThrow(blockSize);
+ fBytesWritten = 0;
+ *actual = blockSize;
+ return fBlock;
+}
+
+void DeferredPipeController::notifyWritten(size_t bytes) {
+ fBytesWritten += bytes;
+}
+
+void DeferredPipeController::playback(bool silent) {
+ uint32_t flags = silent ? SkGPipeReader::kSilent_PlaybackFlag : 0;
+ for (int currentBlock = 0; currentBlock < fBlockList.count(); currentBlock++ ) {
+ fReader.playback(fBlockList[currentBlock].fBlock, fBlockList[currentBlock].fSize,
+ flags);
+ }
+ fBlockList.reset();
+
+ if (fBlock) {
+ fReader.playback(fBlock, fBytesWritten, flags);
+ fBlock = NULL;
+ }
+
+ // Release all allocated blocks
+ fAllocator.reset();
+}
+
+//-----------------------------------------------------------------------------
+// DeferredDevice
+//-----------------------------------------------------------------------------
+class DeferredDevice : public SkDevice {
+public:
+ explicit DeferredDevice(SkDevice* immediateDevice);
+ explicit DeferredDevice(SkSurface* surface);
+ ~DeferredDevice();
+
+ void setNotificationClient(SkDeferredCanvas::NotificationClient* notificationClient);
+ SkCanvas* recordingCanvas();
+ SkCanvas* immediateCanvas() const {return fImmediateCanvas;}
+ SkDevice* immediateDevice() const {return fImmediateCanvas->getTopDevice();}
+ SkImage* newImageSnapshot();
+ void setSurface(SkSurface* surface);
+ bool isFreshFrame();
+ 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);
+ void recordedDrawCommand();
+
+ virtual uint32_t getDeviceCapabilities() SK_OVERRIDE;
+ virtual int width() const SK_OVERRIDE;
+ virtual int height() const SK_OVERRIDE;
+ virtual GrRenderTarget* accessRenderTarget() SK_OVERRIDE;
+
+ virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config config,
+ int width, int height,
+ bool isOpaque,
+ Usage usage) SK_OVERRIDE;
+
+ virtual void writePixels(const SkBitmap& bitmap, int x, int y,
+ SkCanvas::Config8888 config8888) SK_OVERRIDE;
+
+protected:
+ virtual const SkBitmap& onAccessBitmap(SkBitmap*) SK_OVERRIDE;
+ virtual bool onReadPixels(const SkBitmap& bitmap,
+ int x, int y,
+ SkCanvas::Config8888 config8888) SK_OVERRIDE;
+
+ // The following methods are no-ops on a deferred device
+ virtual bool filterTextFlags(const SkPaint& paint, TextFlags*)
+ SK_OVERRIDE
+ {return false;}
+
+ // None of the following drawing methods should ever get called on the
+ // deferred device
+ virtual void clear(SkColor color) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawPaint(const SkDraw&, const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode,
+ size_t count, const SkPoint[],
+ const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawRect(const SkDraw&, const SkRect& r,
+ const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawPath(const SkDraw&, const SkPath& path,
+ const SkPaint& paint,
+ const SkMatrix* prePathMatrix = NULL,
+ bool pathIsMutable = false) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap,
+ int x, int y, const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawText(const SkDraw&, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawPosText(const SkDraw&, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPos, const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+ virtual void drawTextOnPath(const SkDraw&, const void* text,
+ size_t len, const SkPath& path,
+ const SkMatrix* matrix,
+ const SkPaint& paint) SK_OVERRIDE
+ {SkASSERT(0);}
+#ifdef SK_BUILD_FOR_ANDROID
+ virtual void drawPosTextOnPath(const SkDraw& draw, const void* text,
+ size_t len, const SkPoint pos[],
+ const SkPaint& paint,
+ const SkPath& path,
+ const SkMatrix* matrix) SK_OVERRIDE
+ {SkASSERT(0);}
+#endif
+ 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
+ {SkASSERT(0);}
+ virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y,
+ const SkPaint&) SK_OVERRIDE
+ {SkASSERT(0);}
+private:
+ virtual void flush() SK_OVERRIDE;
+
+ void beginRecording();
+ void init();
+ void aboutToDraw();
+ void prepareForImmediatePixelWrite();
+
+ DeferredPipeController fPipeController;
+ SkGPipeWriter fPipeWriter;
+ SkCanvas* fImmediateCanvas;
+ SkCanvas* fRecordingCanvas;
+ SkSurface* fSurface;
+ SkDeferredCanvas::NotificationClient* fNotificationClient;
+ bool fFreshFrame;
+ bool fCanDiscardCanvasContents;
+ size_t fMaxRecordingStorageBytes;
+ size_t fPreviousStorageAllocated;
+ size_t fBitmapSizeThreshold;
+};
+
+DeferredDevice::DeferredDevice(SkDevice* immediateDevice)
+ : SkDevice(SkBitmap::kNo_Config,
+ immediateDevice->width(), immediateDevice->height(),
+ immediateDevice->isOpaque(),
+ immediateDevice->getDeviceProperties()) {
+ fSurface = NULL;
+ fImmediateCanvas = SkNEW_ARGS(SkCanvas, (immediateDevice));
+ fPipeController.setPlaybackCanvas(fImmediateCanvas);
+ this->init();
+}
+
+DeferredDevice::DeferredDevice(SkSurface* surface)
+ : SkDevice(SkBitmap::kNo_Config,
+ surface->getCanvas()->getDevice()->width(),
+ surface->getCanvas()->getDevice()->height(),
+ surface->getCanvas()->getDevice()->isOpaque(),
+ surface->getCanvas()->getDevice()->getDeviceProperties()) {
+ fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
+ fNotificationClient = NULL;
+ fImmediateCanvas = NULL;
+ fSurface = NULL;
+ this->setSurface(surface);
+ this->init();
+}
+
+void DeferredDevice::setSurface(SkSurface* surface) {
+ SkRefCnt_SafeAssign(fImmediateCanvas, surface->getCanvas());
+ SkRefCnt_SafeAssign(fSurface, surface);
+ fPipeController.setPlaybackCanvas(fImmediateCanvas);
+}
+
+void DeferredDevice::init() {
+ fRecordingCanvas = NULL;
+ fFreshFrame = true;
+ fCanDiscardCanvasContents = false;
+ fPreviousStorageAllocated = 0;
+ fBitmapSizeThreshold = kDeferredCanvasBitmapSizeThreshold;
+ fMaxRecordingStorageBytes = kDefaultMaxRecordingStorageBytes;
+ fNotificationClient = NULL;
+ this->beginRecording();
+}
+
+DeferredDevice::~DeferredDevice() {
+ this->flushPendingCommands(kSilent_PlaybackMode);
+ SkSafeUnref(fImmediateCanvas);
+ SkSafeUnref(fSurface);
+}
+
+void DeferredDevice::setMaxRecordingStorage(size_t maxStorage) {
+ fMaxRecordingStorageBytes = maxStorage;
+ this->recordingCanvas(); // Accessing the recording canvas applies the new limit.
+}
+
+void DeferredDevice::beginRecording() {
+ SkASSERT(NULL == fRecordingCanvas);
+ fRecordingCanvas = fPipeWriter.startRecording(&fPipeController, 0,
+ immediateDevice()->width(), immediateDevice()->height());
+}
+
+void DeferredDevice::setNotificationClient(
+ SkDeferredCanvas::NotificationClient* notificationClient) {
+ fNotificationClient = notificationClient;
+}
+
+void DeferredDevice::skipPendingCommands() {
+ if (!fRecordingCanvas->isDrawingToLayer()) {
+ fCanDiscardCanvasContents = true;
+ if (fPipeController.hasPendingCommands()) {
+ fFreshFrame = true;
+ flushPendingCommands(kSilent_PlaybackMode);
+ if (fNotificationClient) {
+ fNotificationClient->skippedPendingDrawCommands();
+ }
+ }
+ }
+}
+
+bool DeferredDevice::isFreshFrame() {
+ bool ret = fFreshFrame;
+ fFreshFrame = false;
+ return ret;
+}
+
+bool DeferredDevice::hasPendingCommands() {
+ return fPipeController.hasPendingCommands();
+}
+
+void DeferredDevice::aboutToDraw()
+{
+ if (NULL != fNotificationClient) {
+ fNotificationClient->prepareForDraw();
+ }
+ if (fCanDiscardCanvasContents) {
+ if (NULL != fSurface) {
+ fSurface->notifyContentWillChange(SkSurface::kDiscard_ContentChangeMode);
+ }
+ fCanDiscardCanvasContents = false;
+ }
+}
+
+void DeferredDevice::flushPendingCommands(PlaybackMode playbackMode) {
+ if (!fPipeController.hasPendingCommands()) {
+ return;
+ }
+ if (playbackMode == kNormal_PlaybackMode) {
+ aboutToDraw();
+ }
+ fPipeWriter.flushRecording(true);
+ fPipeController.playback(kSilent_PlaybackMode == playbackMode);
+ if (playbackMode == kNormal_PlaybackMode && fNotificationClient) {
+ fNotificationClient->flushedDrawCommands();
+ }
+ fPreviousStorageAllocated = storageAllocatedForRecording();
+}
+
+void DeferredDevice::flush() {
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ fImmediateCanvas->flush();
+}
+
+size_t DeferredDevice::freeMemoryIfPossible(size_t bytesToFree) {
+ size_t val = fPipeWriter.freeMemoryIfPossible(bytesToFree);
+ fPreviousStorageAllocated = storageAllocatedForRecording();
+ 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());
+}
+
+void DeferredDevice::recordedDrawCommand() {
+ size_t storageAllocated = this->storageAllocatedForRecording();
+
+ if (storageAllocated > fMaxRecordingStorageBytes) {
+ // First, attempt to reduce cache without flushing
+ size_t tryFree = storageAllocated - fMaxRecordingStorageBytes;
+ if (this->freeMemoryIfPossible(tryFree) < tryFree) {
+ // Flush is necessary to free more space.
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ // Free as much as possible to avoid oscillating around fMaxRecordingStorageBytes
+ // which could cause a high flushing frequency.
+ this->freeMemoryIfPossible(~0U);
+ }
+ storageAllocated = this->storageAllocatedForRecording();
+ }
+
+ if (fNotificationClient &&
+ storageAllocated != fPreviousStorageAllocated) {
+ fPreviousStorageAllocated = storageAllocated;
+ fNotificationClient->storageAllocatedForRecordingChanged(storageAllocated);
+ }
+}
+
+SkCanvas* DeferredDevice::recordingCanvas() {
+ return fRecordingCanvas;
+}
+
+SkImage* DeferredDevice::newImageSnapshot() {
+ this->flush();
+ return fSurface ? fSurface->newImageSnapshot() : NULL;
+}
+
+uint32_t DeferredDevice::getDeviceCapabilities() {
+ return immediateDevice()->getDeviceCapabilities();
+}
+
+int DeferredDevice::width() const {
+ return immediateDevice()->width();
+}
+
+int DeferredDevice::height() const {
+ return immediateDevice()->height();
+}
+
+GrRenderTarget* DeferredDevice::accessRenderTarget() {
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ return immediateDevice()->accessRenderTarget();
+}
+
+void DeferredDevice::prepareForImmediatePixelWrite() {
+ // The purpose of the following code is to make sure commands are flushed, that
+ // aboutToDraw() is called and that notifyContentWillChange is called, without
+ // calling anything redundantly.
+ if (fPipeController.hasPendingCommands()) {
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ } else {
+ bool mustNotifyDirectly = !fCanDiscardCanvasContents;
+ this->aboutToDraw();
+ if (mustNotifyDirectly) {
+ fSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode);
+ }
+ }
+
+ fImmediateCanvas->flush();
+}
+
+void DeferredDevice::writePixels(const SkBitmap& bitmap,
+ int x, int y, SkCanvas::Config8888 config8888) {
+
+ if (x <= 0 && y <= 0 && (x + bitmap.width()) >= width() &&
+ (y + bitmap.height()) >= height()) {
+ this->skipPendingCommands();
+ }
+
+ if (SkBitmap::kARGB_8888_Config == bitmap.config() &&
+ SkCanvas::kNative_Premul_Config8888 != config8888 &&
+ kPMColorAlias != config8888) {
+ //Special case config: no deferral
+ prepareForImmediatePixelWrite();
+ immediateDevice()->writePixels(bitmap, x, y, config8888);
+ return;
+ }
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ if (shouldDrawImmediately(&bitmap, NULL, getBitmapSizeThreshold())) {
+ prepareForImmediatePixelWrite();
+ fImmediateCanvas->drawSprite(bitmap, x, y, &paint);
+ } else {
+ this->recordingCanvas()->drawSprite(bitmap, x, y, &paint);
+ this->recordedDrawCommand();
+
+ }
+}
+
+const SkBitmap& DeferredDevice::onAccessBitmap(SkBitmap*) {
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ return immediateDevice()->accessBitmap(false);
+}
+
+SkDevice* DeferredDevice::onCreateCompatibleDevice(
+ SkBitmap::Config config, int width, int height, bool isOpaque,
+ Usage usage) {
+
+ // Save layer usage not supported, and not required by SkDeferredCanvas.
+ SkASSERT(usage != kSaveLayer_Usage);
+ // Create a compatible non-deferred device.
+ // We do not create a deferred device because we know the new device
+ // will not be used with a deferred canvas (there is no API for that).
+ // And connecting a DeferredDevice to non-deferred canvas can result
+ // in unpredictable behavior.
+ return immediateDevice()->createCompatibleDevice(config, width, height, isOpaque);
+}
+
+bool DeferredDevice::onReadPixels(
+ const SkBitmap& bitmap, int x, int y, SkCanvas::Config8888 config8888) {
+ this->flushPendingCommands(kNormal_PlaybackMode);
+ return fImmediateCanvas->readPixels(const_cast<SkBitmap*>(&bitmap),
+ 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::Create(SkSurface* surface) {
+ SkAutoTUnref<DeferredDevice> deferredDevice(SkNEW_ARGS(DeferredDevice, (surface)));
+ return SkNEW_ARGS(SkDeferredCanvas, (deferredDevice));
+}
+
+SkDeferredCanvas* SkDeferredCanvas::Create(SkDevice* device) {
+ SkAutoTUnref<DeferredDevice> deferredDevice(SkNEW_ARGS(DeferredDevice, (device)));
+ return SkNEW_ARGS(SkDeferredCanvas, (deferredDevice));
+}
+
+SkDeferredCanvas::SkDeferredCanvas(DeferredDevice* device) : SkCanvas (device) {
+ this->init();
+}
+
+void SkDeferredCanvas::init() {
+ fDeferredDrawing = true; // On by default
+}
+
+void SkDeferredCanvas::setMaxRecordingStorage(size_t maxStorage) {
+ this->validate();
+ this->getDeferredDevice()->setMaxRecordingStorage(maxStorage);
+}
+
+size_t SkDeferredCanvas::storageAllocatedForRecording() const {
+ return this->getDeferredDevice()->storageAllocatedForRecording();
+}
+
+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();
+ }
+}
+
+void SkDeferredCanvas::validate() const {
+ SkASSERT(this->getDevice());
+}
+
+SkCanvas* SkDeferredCanvas::drawingCanvas() const {
+ this->validate();
+ return fDeferredDrawing ? this->getDeferredDevice()->recordingCanvas() :
+ this->getDeferredDevice()->immediateCanvas();
+}
+
+SkCanvas* SkDeferredCanvas::immediateCanvas() const {
+ this->validate();
+ return this->getDeferredDevice()->immediateCanvas();
+}
+
+DeferredDevice* SkDeferredCanvas::getDeferredDevice() const {
+ return static_cast<DeferredDevice*>(this->getDevice());
+}
+
+void SkDeferredCanvas::setDeferredDrawing(bool val) {
+ this->validate(); // Must set device before calling this method
+ if (val != fDeferredDrawing) {
+ if (fDeferredDrawing) {
+ // Going live.
+ this->getDeferredDevice()->flushPendingCommands(kNormal_PlaybackMode);
+ }
+ fDeferredDrawing = val;
+ }
+}
+
+bool SkDeferredCanvas::isDeferredDrawing() const {
+ return fDeferredDrawing;
+}
+
+bool SkDeferredCanvas::isFreshFrame() const {
+ return this->getDeferredDevice()->isFreshFrame();
+}
+
+bool SkDeferredCanvas::hasPendingCommands() const {
+ return this->getDeferredDevice()->hasPendingCommands();
+}
+
+void SkDeferredCanvas::silentFlush() {
+ if (fDeferredDrawing) {
+ this->getDeferredDevice()->flushPendingCommands(kSilent_PlaybackMode);
+ }
+}
+
+SkDeferredCanvas::~SkDeferredCanvas() {
+}
+
+SkSurface* SkDeferredCanvas::setSurface(SkSurface* surface) {
+ DeferredDevice* deferredDevice = this->getDeferredDevice();
+ SkASSERT(NULL != deferredDevice);
+ // By swapping the surface into the existing device, we preserve
+ // all pending commands, which can help to seamlessly recover from
+ // a lost accelerated graphics context.
+ deferredDevice->setSurface(surface);
+ return surface;
+}
+
+SkDeferredCanvas::NotificationClient* SkDeferredCanvas::setNotificationClient(
+ NotificationClient* notificationClient) {
+
+ DeferredDevice* deferredDevice = this->getDeferredDevice();
+ SkASSERT(deferredDevice);
+ if (deferredDevice) {
+ deferredDevice->setNotificationClient(notificationClient);
+ }
+ return notificationClient;
+}
+
+SkImage* SkDeferredCanvas::newImageSnapshot() {
+ DeferredDevice* deferredDevice = this->getDeferredDevice();
+ SkASSERT(deferredDevice);
+ return deferredDevice ? deferredDevice->newImageSnapshot() : NULL;
+}
+
+bool SkDeferredCanvas::isFullFrame(const SkRect* rect,
+ const SkPaint* paint) const {
+ SkCanvas* canvas = this->drawingCanvas();
+ SkISize canvasSize = this->getDeviceSize();
+ if (rect) {
+ if (!canvas->getTotalMatrix().rectStaysRect()) {
+ return false; // conservative
+ }
+
+ SkRect transformedRect;
+ canvas->getTotalMatrix().mapRect(&transformedRect, *rect);
+
+ if (paint) {
+ SkPaint::Style paintStyle = paint->getStyle();
+ if (!(paintStyle == SkPaint::kFill_Style ||
+ paintStyle == SkPaint::kStrokeAndFill_Style)) {
+ return false;
+ }
+ if (paint->getMaskFilter() || paint->getLooper()
+ || paint->getPathEffect() || paint->getImageFilter()) {
+ return false; // conservative
+ }
+ }
+
+ // The following test holds with AA enabled, and is conservative
+ // by a 0.5 pixel margin with AA disabled
+ if (transformedRect.fLeft > SkIntToScalar(0) ||
+ transformedRect.fTop > SkIntToScalar(0) ||
+ transformedRect.fRight < SkIntToScalar(canvasSize.fWidth) ||
+ transformedRect.fBottom < SkIntToScalar(canvasSize.fHeight)) {
+ return false;
+ }
+ }
+
+ return this->getClipStack()->quickContains(SkRect::MakeXYWH(0, 0,
+ SkIntToScalar(canvasSize.fWidth), SkIntToScalar(canvasSize.fHeight)));
+}
+
+int SkDeferredCanvas::save(SaveFlags flags) {
+ this->drawingCanvas()->save(flags);
+ int val = this->INHERITED::save(flags);
+ this->recordedDrawCommand();
+
+ return val;
+}
+
+int SkDeferredCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ this->drawingCanvas()->saveLayer(bounds, paint, flags);
+ int count = this->INHERITED::save(flags);
+ this->clipRectBounds(bounds, flags, NULL);
+ this->recordedDrawCommand();
+
+ return count;
+}
+
+void SkDeferredCanvas::restore() {
+ this->drawingCanvas()->restore();
+ this->INHERITED::restore();
+ this->recordedDrawCommand();
+}
+
+bool SkDeferredCanvas::isDrawingToLayer() const {
+ return this->drawingCanvas()->isDrawingToLayer();
+}
+
+bool SkDeferredCanvas::translate(SkScalar dx, SkScalar dy) {
+ this->drawingCanvas()->translate(dx, dy);
+ bool val = this->INHERITED::translate(dx, dy);
+ this->recordedDrawCommand();
+ return val;
+}
+
+bool SkDeferredCanvas::scale(SkScalar sx, SkScalar sy) {
+ this->drawingCanvas()->scale(sx, sy);
+ bool val = this->INHERITED::scale(sx, sy);
+ this->recordedDrawCommand();
+ return val;
+}
+
+bool SkDeferredCanvas::rotate(SkScalar degrees) {
+ this->drawingCanvas()->rotate(degrees);
+ bool val = this->INHERITED::rotate(degrees);
+ this->recordedDrawCommand();
+ return val;
+}
+
+bool SkDeferredCanvas::skew(SkScalar sx, SkScalar sy) {
+ this->drawingCanvas()->skew(sx, sy);
+ bool val = this->INHERITED::skew(sx, sy);
+ this->recordedDrawCommand();
+ return val;
+}
+
+bool SkDeferredCanvas::concat(const SkMatrix& matrix) {
+ this->drawingCanvas()->concat(matrix);
+ bool val = this->INHERITED::concat(matrix);
+ this->recordedDrawCommand();
+ return val;
+}
+
+void SkDeferredCanvas::setMatrix(const SkMatrix& matrix) {
+ this->drawingCanvas()->setMatrix(matrix);
+ this->INHERITED::setMatrix(matrix);
+ this->recordedDrawCommand();
+}
+
+bool SkDeferredCanvas::clipRect(const SkRect& rect,
+ SkRegion::Op op,
+ bool doAntiAlias) {
+ this->drawingCanvas()->clipRect(rect, op, doAntiAlias);
+ bool val = this->INHERITED::clipRect(rect, op, doAntiAlias);
+ this->recordedDrawCommand();
+ 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) {
+ this->drawingCanvas()->clipPath(path, op, doAntiAlias);
+ bool val = this->INHERITED::clipPath(path, op, doAntiAlias);
+ this->recordedDrawCommand();
+ return val;
+}
+
+bool SkDeferredCanvas::clipRegion(const SkRegion& deviceRgn,
+ SkRegion::Op op) {
+ this->drawingCanvas()->clipRegion(deviceRgn, op);
+ bool val = this->INHERITED::clipRegion(deviceRgn, op);
+ this->recordedDrawCommand();
+ return val;
+}
+
+void SkDeferredCanvas::clear(SkColor color) {
+ // purge pending commands
+ if (fDeferredDrawing) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+
+ this->drawingCanvas()->clear(color);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawPaint(const SkPaint& paint) {
+ if (fDeferredDrawing && this->isFullFrame(NULL, &paint) &&
+ isPaintOpaque(&paint)) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawPaint(paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawPoints(mode, count, pts, paint);
+ 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)) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawRect(rect, 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);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
+ SkScalar top, const SkPaint* paint) {
+ SkRect bitmapRect = SkRect::MakeXYWH(left, top,
+ SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
+ if (fDeferredDrawing &&
+ this->isFullFrame(&bitmapRect, paint) &&
+ isPaintOpaque(paint, &bitmap)) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+
+ AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
+ this->drawingCanvas()->drawBitmap(bitmap, left, top, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawBitmapRectToRect(const SkBitmap& bitmap,
+ const SkRect* src,
+ const SkRect& dst,
+ const SkPaint* paint) {
+ if (fDeferredDrawing &&
+ this->isFullFrame(&dst, paint) &&
+ isPaintOpaque(paint, &bitmap)) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+
+ AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
+ this->drawingCanvas()->drawBitmapRectToRect(bitmap, src, dst, paint);
+ this->recordedDrawCommand();
+}
+
+
+void SkDeferredCanvas::drawBitmapMatrix(const SkBitmap& bitmap,
+ const SkMatrix& m,
+ const SkPaint* paint) {
+ // TODO: reset recording canvas if paint+bitmap is opaque and clip rect
+ // covers canvas entirely and transformed bitmap covers canvas entirely
+ AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
+ this->drawingCanvas()->drawBitmapMatrix(bitmap, m, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawBitmapNine(const SkBitmap& bitmap,
+ const SkIRect& center, const SkRect& dst,
+ const SkPaint* paint) {
+ // TODO: reset recording canvas if paint+bitmap is opaque and clip rect
+ // covers canvas entirely and dst covers canvas entirely
+ AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
+ this->drawingCanvas()->drawBitmapNine(bitmap, center, dst, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint) {
+ SkRect bitmapRect = SkRect::MakeXYWH(
+ SkIntToScalar(left),
+ SkIntToScalar(top),
+ SkIntToScalar(bitmap.width()),
+ SkIntToScalar(bitmap.height()));
+ if (fDeferredDrawing &&
+ this->isFullFrame(&bitmapRect, paint) &&
+ isPaintOpaque(paint, &bitmap)) {
+ this->getDeferredDevice()->skipPendingCommands();
+ }
+
+ AutoImmediateDrawIfNeeded autoDraw(*this, &bitmap, paint);
+ this->drawingCanvas()->drawSprite(bitmap, left, top, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawText(const void* text, size_t byteLength,
+ SkScalar x, SkScalar y, const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawText(text, byteLength, x, y, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawPosText(text, byteLength, pos, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawPosTextH(text, byteLength, xpos, constY, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path,
+ const SkMatrix* matrix,
+ const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawTextOnPath(text, byteLength, path, matrix, paint);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawPicture(SkPicture& picture) {
+ this->drawingCanvas()->drawPicture(picture);
+ this->recordedDrawCommand();
+}
+
+void SkDeferredCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[],
+ const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+ this->drawingCanvas()->drawVertices(vmode, vertexCount, vertices, texs, colors, xmode,
+ indices, indexCount, paint);
+ this->recordedDrawCommand();
+}
+
+SkBounder* SkDeferredCanvas::setBounder(SkBounder* bounder) {
+ this->drawingCanvas()->setBounder(bounder);
+ this->INHERITED::setBounder(bounder);
+ this->recordedDrawCommand();
+ return bounder;
+}
+
+SkDrawFilter* SkDeferredCanvas::setDrawFilter(SkDrawFilter* filter) {
+ this->drawingCanvas()->setDrawFilter(filter);
+ this->INHERITED::setDrawFilter(filter);
+ this->recordedDrawCommand();
+ return filter;
+}
+
+SkCanvas* SkDeferredCanvas::canvasForDrawIter() {
+ return this->drawingCanvas();
+}
diff --git a/utils/SkDumpCanvas.cpp b/utils/SkDumpCanvas.cpp
new file mode 100644
index 00000000..0d5ca2b3
--- /dev/null
+++ b/utils/SkDumpCanvas.cpp
@@ -0,0 +1,525 @@
+
+/*
+ * 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 "SkDumpCanvas.h"
+
+#ifdef SK_DEVELOPER
+#include "SkPicture.h"
+#include "SkPixelRef.h"
+#include "SkRRect.h"
+#include "SkString.h"
+#include <stdarg.h>
+
+// needed just to know that these are all subclassed from SkFlattenable
+#include "SkShader.h"
+#include "SkPathEffect.h"
+#include "SkXfermode.h"
+#include "SkColorFilter.h"
+#include "SkPathEffect.h"
+#include "SkMaskFilter.h"
+
+SK_DEFINE_INST_COUNT(SkDumpCanvas::Dumper)
+
+static void toString(const SkRect& r, SkString* str) {
+ str->appendf("[%g,%g %g:%g]",
+ SkScalarToFloat(r.fLeft), SkScalarToFloat(r.fTop),
+ SkScalarToFloat(r.width()), SkScalarToFloat(r.height()));
+}
+
+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];
+ for (;;) {
+ switch (iter.next(pts, false)) {
+ case SkPath::kMove_Verb:
+ str->appendf(" M%g,%g", pts[0].fX, pts[0].fY);
+ break;
+ case SkPath::kLine_Verb:
+ str->appendf(" L%g,%g", pts[0].fX, pts[0].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ str->appendf(" Q%g,%g,%g,%g", pts[1].fX, pts[1].fY,
+ pts[2].fX, pts[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ str->appendf(" C%g,%g,%g,%g,%g,%g", pts[1].fX, pts[1].fY,
+ pts[2].fX, pts[2].fY, pts[3].fX, pts[3].fY);
+ break;
+ case SkPath::kClose_Verb:
+ str->append("X");
+ break;
+ case SkPath::kDone_Verb:
+ return;
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ }
+ }
+}
+
+static void toString(const SkPath& path, SkString* str) {
+ if (path.isEmpty()) {
+ str->append("path:empty");
+ } else {
+ toString(path.getBounds(), str);
+#if 1
+ SkString s;
+ dumpVerbs(path, &s);
+ str->append(s.c_str());
+#endif
+ str->append("]");
+ str->prepend("path:[");
+ }
+}
+
+static const char* toString(SkRegion::Op op) {
+ static const char* gOpNames[] = {
+ "DIFF", "SECT", "UNION", "XOR", "RDIFF", "REPLACE"
+ };
+ return gOpNames[op];
+}
+
+static void toString(const SkRegion& rgn, SkString* str) {
+ str->append("Region:[");
+ toString(rgn.getBounds(), str);
+ str->append("]");
+ if (rgn.isComplex()) {
+ str->append(".complex");
+ }
+}
+
+static const char* toString(SkCanvas::VertexMode vm) {
+ static const char* gVMNames[] = {
+ "TRIANGLES", "STRIP", "FAN"
+ };
+ return gVMNames[vm];
+}
+
+static const char* toString(SkCanvas::PointMode pm) {
+ static const char* gPMNames[] = {
+ "POINTS", "LINES", "POLYGON"
+ };
+ return gPMNames[pm];
+}
+
+static void toString(const void* text, size_t byteLen, SkPaint::TextEncoding enc,
+ SkString* str) {
+ // FIXME: this code appears to be untested - and probably unused - and probably wrong
+ switch (enc) {
+ case SkPaint::kUTF8_TextEncoding:
+ str->appendf("\"%.*s\"%s", SkMax32(byteLen, 32), (const char*) text,
+ byteLen > 32 ? "..." : "");
+ break;
+ case SkPaint::kUTF16_TextEncoding:
+ str->appendf("\"%.*ls\"%s", SkMax32(byteLen, 32), (const wchar_t*) text,
+ byteLen > 64 ? "..." : "");
+ break;
+ case SkPaint::kUTF32_TextEncoding:
+ str->appendf("\"%.*ls\"%s", SkMax32(byteLen, 32), (const wchar_t*) text,
+ byteLen > 128 ? "..." : "");
+ break;
+ case SkPaint::kGlyphID_TextEncoding:
+ str->append("<glyphs>");
+ break;
+
+ default:
+ SkASSERT(false);
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkBitmap make_wideopen_bm() {
+ static const int WIDE_OPEN = 16384;
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kNo_Config, WIDE_OPEN, WIDE_OPEN);
+ return bm;
+}
+
+SkDumpCanvas::SkDumpCanvas(Dumper* dumper) : INHERITED(make_wideopen_bm()) {
+ fNestLevel = 0;
+ SkSafeRef(dumper);
+ fDumper = dumper;
+}
+
+SkDumpCanvas::~SkDumpCanvas() {
+ SkSafeUnref(fDumper);
+}
+
+void SkDumpCanvas::dump(Verb verb, const SkPaint* paint,
+ const char format[], ...) {
+ static const size_t BUFFER_SIZE = 1024;
+
+ char buffer[BUFFER_SIZE];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buffer, BUFFER_SIZE, format, args);
+ va_end(args);
+
+ if (fDumper) {
+ fDumper->dump(this, verb, buffer, paint);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkDumpCanvas::save(SaveFlags flags) {
+ this->dump(kSave_Verb, NULL, "save(0x%X)", flags);
+ return this->INHERITED::save(flags);
+}
+
+int SkDumpCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ SkString str;
+ str.printf("saveLayer(0x%X)", flags);
+ if (bounds) {
+ str.append(" bounds");
+ toString(*bounds, &str);
+ }
+ if (paint) {
+ if (paint->getAlpha() != 0xFF) {
+ str.appendf(" alpha:0x%02X", paint->getAlpha());
+ }
+ if (paint->getXfermode()) {
+ str.appendf(" xfermode:%p", paint->getXfermode());
+ }
+ }
+ this->dump(kSave_Verb, paint, str.c_str());
+ return this->INHERITED::saveLayer(bounds, paint, flags);
+}
+
+void SkDumpCanvas::restore() {
+ this->INHERITED::restore();
+ this->dump(kRestore_Verb, NULL, "restore");
+}
+
+bool SkDumpCanvas::translate(SkScalar dx, SkScalar dy) {
+ this->dump(kMatrix_Verb, NULL, "translate(%g %g)",
+ SkScalarToFloat(dx), SkScalarToFloat(dy));
+ return this->INHERITED::translate(dx, dy);
+}
+
+bool SkDumpCanvas::scale(SkScalar sx, SkScalar sy) {
+ this->dump(kMatrix_Verb, NULL, "scale(%g %g)",
+ SkScalarToFloat(sx), SkScalarToFloat(sy));
+ return this->INHERITED::scale(sx, sy);
+}
+
+bool SkDumpCanvas::rotate(SkScalar degrees) {
+ this->dump(kMatrix_Verb, NULL, "rotate(%g)", SkScalarToFloat(degrees));
+ return this->INHERITED::rotate(degrees);
+}
+
+bool SkDumpCanvas::skew(SkScalar sx, SkScalar sy) {
+ this->dump(kMatrix_Verb, NULL, "skew(%g %g)",
+ SkScalarToFloat(sx), SkScalarToFloat(sy));
+ return this->INHERITED::skew(sx, sy);
+}
+
+bool SkDumpCanvas::concat(const SkMatrix& matrix) {
+ SkString str;
+ matrix.toString(&str);
+ this->dump(kMatrix_Verb, NULL, "concat(%s)", str.c_str());
+ return this->INHERITED::concat(matrix);
+}
+
+void SkDumpCanvas::setMatrix(const SkMatrix& matrix) {
+ SkString str;
+ matrix.toString(&str);
+ this->dump(kMatrix_Verb, NULL, "setMatrix(%s)", str.c_str());
+ this->INHERITED::setMatrix(matrix);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char* bool_to_aastring(bool doAA) {
+ return doAA ? "AA" : "BW";
+}
+
+bool SkDumpCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ SkString str;
+ toString(rect, &str);
+ this->dump(kClip_Verb, NULL, "clipRect(%s %s %s)", str.c_str(), toString(op),
+ bool_to_aastring(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);
+ this->dump(kClip_Verb, NULL, "clipPath(%s %s %s)", str.c_str(), toString(op),
+ bool_to_aastring(doAA));
+ return this->INHERITED::clipPath(path, op, doAA);
+}
+
+bool SkDumpCanvas::clipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+ SkString str;
+ toString(deviceRgn, &str);
+ this->dump(kClip_Verb, NULL, "clipRegion(%s %s)", str.c_str(),
+ toString(op));
+ return this->INHERITED::clipRegion(deviceRgn, op);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkDumpCanvas::drawPaint(const SkPaint& paint) {
+ this->dump(kDrawPaint_Verb, &paint, "drawPaint()");
+}
+
+void SkDumpCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ this->dump(kDrawPoints_Verb, &paint, "drawPoints(%s, %d)", toString(mode),
+ 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);
+ this->dump(kDrawPath_Verb, &paint, "drawPath(%s)", str.c_str());
+}
+
+void SkDumpCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ SkString str;
+ bitmap.toString(&str);
+ this->dump(kDrawBitmap_Verb, paint, "drawBitmap(%s %g %g)", str.c_str(),
+ SkScalarToFloat(x), SkScalarToFloat(y));
+}
+
+void SkDumpCanvas::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ SkString bs, rs;
+ bitmap.toString(&bs);
+ toString(dst, &rs);
+ // show the src-rect only if its not everything
+ if (src && (src->fLeft > 0 || src->fTop > 0 ||
+ src->fRight < SkIntToScalar(bitmap.width()) ||
+ src->fBottom < SkIntToScalar(bitmap.height()))) {
+ SkString ss;
+ toString(*src, &ss);
+ rs.prependf("%s ", ss.c_str());
+ }
+
+ this->dump(kDrawBitmap_Verb, paint, "drawBitmapRectToRect(%s %s)",
+ bs.c_str(), rs.c_str());
+}
+
+void SkDumpCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m,
+ const SkPaint* paint) {
+ SkString bs, ms;
+ bitmap.toString(&bs);
+ m.toString(&ms);
+ this->dump(kDrawBitmap_Verb, paint, "drawBitmapMatrix(%s %s)",
+ bs.c_str(), ms.c_str());
+}
+
+void SkDumpCanvas::drawSprite(const SkBitmap& bitmap, int x, int y,
+ const SkPaint* paint) {
+ SkString str;
+ bitmap.toString(&str);
+ this->dump(kDrawBitmap_Verb, paint, "drawSprite(%s %d %d)", str.c_str(),
+ x, y);
+}
+
+void SkDumpCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ SkString str;
+ toString(text, byteLength, paint.getTextEncoding(), &str);
+ this->dump(kDrawText_Verb, &paint, "drawText(%s [%d] %g %g)", str.c_str(),
+ byteLength, SkScalarToFloat(x), SkScalarToFloat(y));
+}
+
+void SkDumpCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ SkString str;
+ toString(text, byteLength, paint.getTextEncoding(), &str);
+ this->dump(kDrawText_Verb, &paint, "drawPosText(%s [%d] %g %g ...)",
+ str.c_str(), byteLength, SkScalarToFloat(pos[0].fX),
+ SkScalarToFloat(pos[0].fY));
+}
+
+void SkDumpCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ SkString str;
+ toString(text, byteLength, paint.getTextEncoding(), &str);
+ this->dump(kDrawText_Verb, &paint, "drawPosTextH(%s [%d] %g %g ...)",
+ str.c_str(), byteLength, SkScalarToFloat(xpos[0]),
+ SkScalarToFloat(constY));
+}
+
+void SkDumpCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ SkString str;
+ toString(text, byteLength, paint.getTextEncoding(), &str);
+ this->dump(kDrawText_Verb, &paint, "drawTextOnPath(%s [%d])",
+ str.c_str(), byteLength);
+}
+
+void SkDumpCanvas::drawPicture(SkPicture& picture) {
+ this->dump(kDrawPicture_Verb, NULL, "drawPicture(%p) %d:%d", &picture,
+ picture.width(), picture.height());
+ fNestLevel += 1;
+ this->INHERITED::drawPicture(picture);
+ fNestLevel -= 1;
+ this->dump(kDrawPicture_Verb, NULL, "endPicture(%p) %d:%d", &picture,
+ picture.width(), picture.height());
+}
+
+void SkDumpCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ this->dump(kDrawVertices_Verb, &paint, "drawVertices(%s [%d] %g %g ...)",
+ toString(vmode), vertexCount, SkScalarToFloat(vertices[0].fX),
+ SkScalarToFloat(vertices[0].fY));
+}
+
+void SkDumpCanvas::drawData(const void* data, size_t length) {
+// this->dump(kDrawData_Verb, NULL, "drawData(%d)", length);
+ this->dump(kDrawData_Verb, NULL, "drawData(%d) %.*s", length,
+ SkMin32(length, 64), data);
+}
+
+void SkDumpCanvas::beginCommentGroup(const char* description) {
+ this->dump(kBeginCommentGroup_Verb, NULL, "beginCommentGroup(%s)", description);
+}
+
+void SkDumpCanvas::addComment(const char* kywd, const char* value) {
+ this->dump(kAddComment_Verb, NULL, "addComment(%s, %s)", kywd, value);
+}
+
+void SkDumpCanvas::endCommentGroup() {
+ this->dump(kEndCommentGroup_Verb, NULL, "endCommentGroup()");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+SkFormatDumper::SkFormatDumper(void (*proc)(const char*, void*), void* refcon) {
+ fProc = proc;
+ fRefcon = refcon;
+}
+
+static void appendPtr(SkString* str, const void* ptr, const char name[]) {
+ if (ptr) {
+ str->appendf(" %s:%p", name, ptr);
+ }
+}
+
+static void appendFlattenable(SkString* str, const SkFlattenable* ptr,
+ const char name[]) {
+ if (ptr) {
+ str->appendf(" %s:%p", name, ptr);
+ }
+}
+
+void SkFormatDumper::dump(SkDumpCanvas* canvas, SkDumpCanvas::Verb verb,
+ const char str[], const SkPaint* p) {
+ SkString msg, tab;
+ const int level = canvas->getNestLevel() + canvas->getSaveCount() - 1;
+ SkASSERT(level >= 0);
+ for (int i = 0; i < level; i++) {
+#if 0
+ tab.append("\t");
+#else
+ tab.append(" "); // tabs are often too wide to be useful
+#endif
+ }
+ msg.printf("%s%s", tab.c_str(), str);
+
+ if (p) {
+ msg.appendf(" color:0x%08X flags:%X", p->getColor(), p->getFlags());
+ appendFlattenable(&msg, p->getShader(), "shader");
+ appendFlattenable(&msg, p->getXfermode(), "xfermode");
+ appendFlattenable(&msg, p->getPathEffect(), "pathEffect");
+ appendFlattenable(&msg, p->getMaskFilter(), "maskFilter");
+ appendFlattenable(&msg, p->getPathEffect(), "pathEffect");
+ appendFlattenable(&msg, p->getColorFilter(), "filter");
+
+ if (SkDumpCanvas::kDrawText_Verb == verb) {
+ msg.appendf(" textSize:%g", SkScalarToFloat(p->getTextSize()));
+ appendPtr(&msg, p->getTypeface(), "typeface");
+ }
+
+ if (p->getStyle() != SkPaint::kFill_Style) {
+ msg.appendf(" strokeWidth:%g", SkScalarToFloat(p->getStrokeWidth()));
+ }
+ }
+
+ fProc(msg.c_str(), fRefcon);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void dumpToDebugf(const char text[], void*) {
+ SkDebugf("%s\n", text);
+}
+
+SkDebugfDumper::SkDebugfDumper() : INHERITED(dumpToDebugf, NULL) {}
+
+#endif
diff --git a/utils/SkFloatUtils.h b/utils/SkFloatUtils.h
new file mode 100644
index 00000000..101aac74
--- /dev/null
+++ b/utils/SkFloatUtils.h
@@ -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.
+ */
+
+#ifndef SkFloatUtils_DEFINED
+#define SkFloatUtils_DEFINED
+
+#include "SkTypes.h"
+#include <limits.h>
+#include <float.h>
+
+template <size_t size>
+class SkTypeWithSize {
+public:
+ // Prevents using SkTypeWithSize<N> with non-specialized N.
+ typedef void UInt;
+};
+
+template <>
+class SkTypeWithSize<32> {
+public:
+ typedef uint32_t UInt;
+};
+
+template <>
+class SkTypeWithSize<64> {
+public:
+ typedef uint64_t UInt;
+};
+
+template <typename RawType>
+struct SkNumericLimits {
+ static const int digits = 0;
+};
+
+template <>
+struct SkNumericLimits<double> {
+ static const int digits = DBL_MANT_DIG;
+};
+
+template <>
+struct SkNumericLimits<float> {
+ static const int digits = FLT_MANT_DIG;
+};
+
+//See
+//http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison/3423299#3423299
+//http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h
+//http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
+
+template <typename RawType, unsigned int ULPs>
+class SkFloatingPoint {
+public:
+ /** Bits is a unsigned integer the same size as the floating point number. */
+ typedef typename SkTypeWithSize<sizeof(RawType) * CHAR_BIT>::UInt Bits;
+
+ /** # of bits in a number. */
+ static const size_t kBitCount = CHAR_BIT * sizeof(RawType);
+
+ /** # of fraction bits in a number. */
+ static const size_t kFractionBitCount = SkNumericLimits<RawType>::digits - 1;
+
+ /** # of exponent bits in a number. */
+ static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
+
+ /** The mask for the sign bit. */
+ static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
+
+ /** The mask for the fraction bits. */
+ static const Bits kFractionBitMask =
+ ~static_cast<Bits>(0) >> (kExponentBitCount + 1);
+
+ /** The mask for the exponent bits. */
+ static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
+
+ /** How many ULP's (Units in the Last Place) to tolerate when comparing. */
+ static const size_t kMaxUlps = ULPs;
+
+ /**
+ * Constructs a FloatingPoint from a raw floating-point number.
+ *
+ * On an Intel CPU, passing a non-normalized NAN (Not a Number)
+ * around may change its bits, although the new value is guaranteed
+ * to be also a NAN. Therefore, don't expect this constructor to
+ * preserve the bits in x when x is a NAN.
+ */
+ explicit SkFloatingPoint(const RawType& x) { fU.value = x; }
+
+ /** Returns the exponent bits of this number. */
+ Bits exponent_bits() const { return kExponentBitMask & fU.bits; }
+
+ /** Returns the fraction bits of this number. */
+ Bits fraction_bits() const { return kFractionBitMask & fU.bits; }
+
+ /** Returns true iff this is NAN (not a number). */
+ bool is_nan() const {
+ // It's a NAN if both of the folloowing are true:
+ // * the exponent bits are all ones
+ // * the fraction bits are not all zero.
+ return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
+ }
+
+ /**
+ * Returns true iff this number is at most kMaxUlps ULP's away from ths.
+ * In particular, this function:
+ * - returns false if either number is (or both are) NAN.
+ * - treats really large numbers as almost equal to infinity.
+ * - thinks +0.0 and -0.0 are 0 DLP's apart.
+ */
+ bool AlmostEquals(const SkFloatingPoint& rhs) const {
+ // Any comparison operation involving a NAN must return false.
+ if (is_nan() || rhs.is_nan()) return false;
+
+ const Bits dist = DistanceBetweenSignAndMagnitudeNumbers(fU.bits,
+ rhs.fU.bits);
+ //SkDEBUGF(("(%f, %f, %d) ", u_.value_, rhs.u_.value_, dist));
+ return dist <= kMaxUlps;
+ }
+
+private:
+ /** The data type used to store the actual floating-point number. */
+ union FloatingPointUnion {
+ /** The raw floating-point number. */
+ RawType value;
+ /** The bits that represent the number. */
+ Bits bits;
+ };
+
+ /**
+ * Converts an integer from the sign-and-magnitude representation to
+ * the biased representation. More precisely, let N be 2 to the
+ * power of (kBitCount - 1), an integer x is represented by the
+ * unsigned number x + N.
+ *
+ * For instance,
+ *
+ * -N + 1 (the most negative number representable using
+ * sign-and-magnitude) is represented by 1;
+ * 0 is represented by N; and
+ * N - 1 (the biggest number representable using
+ * sign-and-magnitude) is represented by 2N - 1.
+ *
+ * Read http://en.wikipedia.org/wiki/Signed_number_representations
+ * for more details on signed number representations.
+ */
+ static Bits SignAndMagnitudeToBiased(const Bits &sam) {
+ if (kSignBitMask & sam) {
+ // sam represents a negative number.
+ return ~sam + 1;
+ } else {
+ // sam represents a positive number.
+ return kSignBitMask | sam;
+ }
+ }
+
+ /**
+ * Given two numbers in the sign-and-magnitude representation,
+ * returns the distance between them as an unsigned number.
+ */
+ static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
+ const Bits &sam2) {
+ const Bits biased1 = SignAndMagnitudeToBiased(sam1);
+ const Bits biased2 = SignAndMagnitudeToBiased(sam2);
+ return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
+ }
+
+ FloatingPointUnion fU;
+};
+
+#endif
diff --git a/utils/SkInterpolator.cpp b/utils/SkInterpolator.cpp
new file mode 100644
index 00000000..2853b070
--- /dev/null
+++ b/utils/SkInterpolator.cpp
@@ -0,0 +1,331 @@
+
+/*
+ * 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.
+ */
+
+
+#include "SkInterpolator.h"
+#include "SkMath.h"
+#include "SkTSearch.h"
+
+SkInterpolatorBase::SkInterpolatorBase() {
+ fStorage = NULL;
+ fTimes = NULL;
+ SkDEBUGCODE(fTimesArray = NULL;)
+}
+
+SkInterpolatorBase::~SkInterpolatorBase() {
+ if (fStorage) {
+ sk_free(fStorage);
+ }
+}
+
+void SkInterpolatorBase::reset(int elemCount, int frameCount) {
+ fFlags = 0;
+ fElemCount = SkToU8(elemCount);
+ fFrameCount = SkToS16(frameCount);
+ fRepeat = SK_Scalar1;
+ if (fStorage) {
+ sk_free(fStorage);
+ fStorage = NULL;
+ fTimes = NULL;
+ SkDEBUGCODE(fTimesArray = NULL);
+ }
+}
+
+/* Each value[] run is formated as:
+ <time (in msec)>
+ <blend>
+ <data[fElemCount]>
+
+ Totaling fElemCount+2 entries per keyframe
+*/
+
+bool SkInterpolatorBase::getDuration(SkMSec* startTime, SkMSec* endTime) const {
+ if (fFrameCount == 0) {
+ return false;
+ }
+
+ if (startTime) {
+ *startTime = fTimes[0].fTime;
+ }
+ if (endTime) {
+ *endTime = fTimes[fFrameCount - 1].fTime;
+ }
+ return true;
+}
+
+SkScalar SkInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime,
+ SkMSec nextTime, const SkScalar blend[4]) {
+ SkASSERT(time > prevTime && time < nextTime);
+
+ SkScalar t = SkScalarDiv((SkScalar)(time - prevTime),
+ (SkScalar)(nextTime - prevTime));
+ return blend ?
+ SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
+}
+
+SkInterpolatorBase::Result SkInterpolatorBase::timeToT(SkMSec time, SkScalar* T,
+ int* indexPtr, SkBool* exactPtr) const {
+ SkASSERT(fFrameCount > 0);
+ Result result = kNormal_Result;
+ if (fRepeat != SK_Scalar1) {
+ SkMSec startTime = 0, endTime = 0; // initialize to avoid warning
+ this->getDuration(&startTime, &endTime);
+ SkMSec totalTime = endTime - startTime;
+ SkMSec offsetTime = time - startTime;
+ endTime = SkScalarMulFloor(fRepeat, totalTime);
+ if (offsetTime >= endTime) {
+ SkScalar fraction = SkScalarFraction(fRepeat);
+ offsetTime = fraction == 0 && fRepeat > 0 ? totalTime :
+ (SkMSec) SkScalarMulFloor(fraction, totalTime);
+ result = kFreezeEnd_Result;
+ } else {
+ int mirror = fFlags & kMirror;
+ offsetTime = offsetTime % (totalTime << mirror);
+ if (offsetTime > totalTime) { // can only be true if fMirror is true
+ offsetTime = (totalTime << 1) - offsetTime;
+ }
+ }
+ time = offsetTime + startTime;
+ }
+
+ int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time,
+ sizeof(SkTimeCode));
+
+ bool exact = true;
+
+ if (index < 0) {
+ index = ~index;
+ if (index == 0) {
+ result = kFreezeStart_Result;
+ } else if (index == fFrameCount) {
+ if (fFlags & kReset) {
+ index = 0;
+ } else {
+ index -= 1;
+ }
+ result = kFreezeEnd_Result;
+ } else {
+ exact = false;
+ }
+ }
+ SkASSERT(index < fFrameCount);
+ const SkTimeCode* nextTime = &fTimes[index];
+ SkMSec nextT = nextTime[0].fTime;
+ if (exact) {
+ *T = 0;
+ } else {
+ SkMSec prevT = nextTime[-1].fTime;
+ *T = ComputeRelativeT(time, prevT, nextT, nextTime[-1].fBlend);
+ }
+ *indexPtr = index;
+ *exactPtr = exact;
+ return result;
+}
+
+
+SkInterpolator::SkInterpolator() {
+ INHERITED::reset(0, 0);
+ fValues = NULL;
+ SkDEBUGCODE(fScalarsArray = NULL;)
+}
+
+SkInterpolator::SkInterpolator(int elemCount, int frameCount) {
+ SkASSERT(elemCount > 0);
+ this->reset(elemCount, frameCount);
+}
+
+void SkInterpolator::reset(int elemCount, int frameCount) {
+ INHERITED::reset(elemCount, frameCount);
+ fStorage = sk_malloc_throw((sizeof(SkScalar) * elemCount +
+ sizeof(SkTimeCode)) * frameCount);
+ fTimes = (SkTimeCode*) fStorage;
+ fValues = (SkScalar*) ((char*) fStorage + sizeof(SkTimeCode) * frameCount);
+#ifdef SK_DEBUG
+ fTimesArray = (SkTimeCode(*)[10]) fTimes;
+ fScalarsArray = (SkScalar(*)[10]) fValues;
+#endif
+}
+
+#define SK_Fixed1Third (SK_Fixed1/3)
+#define SK_Fixed2Third (SK_Fixed1*2/3)
+
+static const SkScalar gIdentityBlend[4] = {
+#ifdef SK_SCALAR_IS_FLOAT
+ 0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f
+#else
+ SK_Fixed1Third, SK_Fixed1Third, SK_Fixed2Third, SK_Fixed2Third
+#endif
+};
+
+bool SkInterpolator::setKeyFrame(int index, SkMSec time,
+ const SkScalar values[], const SkScalar blend[4]) {
+ SkASSERT(values != NULL);
+
+ if (blend == NULL) {
+ blend = gIdentityBlend;
+ }
+
+ bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time,
+ sizeof(SkTimeCode));
+ SkASSERT(success);
+ if (success) {
+ SkTimeCode* timeCode = &fTimes[index];
+ timeCode->fTime = time;
+ memcpy(timeCode->fBlend, blend, sizeof(timeCode->fBlend));
+ SkScalar* dst = &fValues[fElemCount * index];
+ memcpy(dst, values, fElemCount * sizeof(SkScalar));
+ }
+ return success;
+}
+
+SkInterpolator::Result SkInterpolator::timeToValues(SkMSec time,
+ SkScalar values[]) const {
+ SkScalar T;
+ int index;
+ SkBool exact;
+ Result result = timeToT(time, &T, &index, &exact);
+ if (values) {
+ const SkScalar* nextSrc = &fValues[index * fElemCount];
+
+ if (exact) {
+ memcpy(values, nextSrc, fElemCount * sizeof(SkScalar));
+ } else {
+ SkASSERT(index > 0);
+
+ const SkScalar* prevSrc = nextSrc - fElemCount;
+
+ for (int i = fElemCount - 1; i >= 0; --i) {
+ values[i] = SkScalarInterp(prevSrc[i], nextSrc[i], T);
+ }
+ }
+ }
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef int Dot14;
+#define Dot14_ONE (1 << 14)
+#define Dot14_HALF (1 << 13)
+
+#define Dot14ToFloat(x) ((x) / 16384.f)
+
+static inline Dot14 Dot14Mul(Dot14 a, Dot14 b) {
+ return (a * b + Dot14_HALF) >> 14;
+}
+
+static inline Dot14 eval_cubic(Dot14 t, Dot14 A, Dot14 B, Dot14 C) {
+ return Dot14Mul(Dot14Mul(Dot14Mul(C, t) + B, t) + A, t);
+}
+
+static inline Dot14 pin_and_convert(SkScalar x) {
+ if (x <= 0) {
+ return 0;
+ }
+ if (x >= SK_Scalar1) {
+ return Dot14_ONE;
+ }
+ return SkScalarToFixed(x) >> 2;
+}
+
+SkScalar SkUnitCubicInterp(SkScalar value, SkScalar bx, SkScalar by,
+ SkScalar cx, SkScalar cy) {
+ // pin to the unit-square, and convert to 2.14
+ Dot14 x = pin_and_convert(value);
+
+ if (x == 0) return 0;
+ if (x == Dot14_ONE) return SK_Scalar1;
+
+ Dot14 b = pin_and_convert(bx);
+ Dot14 c = pin_and_convert(cx);
+
+ // Now compute our coefficients from the control points
+ // t -> 3b
+ // t^2 -> 3c - 6b
+ // t^3 -> 3b - 3c + 1
+ Dot14 A = 3*b;
+ Dot14 B = 3*(c - 2*b);
+ Dot14 C = 3*(b - c) + Dot14_ONE;
+
+ // Now search for a t value given x
+ Dot14 t = Dot14_HALF;
+ Dot14 dt = Dot14_HALF;
+ for (int i = 0; i < 13; i++) {
+ dt >>= 1;
+ Dot14 guess = eval_cubic(t, A, B, C);
+ if (x < guess) {
+ t -= dt;
+ } else {
+ t += dt;
+ }
+ }
+
+ // Now we have t, so compute the coeff for Y and evaluate
+ b = pin_and_convert(by);
+ c = pin_and_convert(cy);
+ A = 3*b;
+ B = 3*(c - 2*b);
+ C = 3*(b - c) + Dot14_ONE;
+ return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+#ifdef SK_SUPPORT_UNITTEST
+ static SkScalar* iset(SkScalar array[3], int a, int b, int c) {
+ array[0] = SkIntToScalar(a);
+ array[1] = SkIntToScalar(b);
+ array[2] = SkIntToScalar(c);
+ return array;
+ }
+#endif
+
+void SkInterpolator::UnitTest() {
+#ifdef SK_SUPPORT_UNITTEST
+ SkInterpolator inter(3, 2);
+ SkScalar v1[3], v2[3], v[3], vv[3];
+ Result result;
+
+ inter.setKeyFrame(0, 100, iset(v1, 10, 20, 30), 0);
+ inter.setKeyFrame(1, 200, iset(v2, 110, 220, 330));
+
+ result = inter.timeToValues(0, v);
+ SkASSERT(result == kFreezeStart_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(99, v);
+ SkASSERT(result == kFreezeStart_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(100, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, v1, sizeof(v)) == 0);
+
+ result = inter.timeToValues(200, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, v2, sizeof(v)) == 0);
+
+ result = inter.timeToValues(201, v);
+ SkASSERT(result == kFreezeEnd_Result);
+ SkASSERT(memcmp(v, v2, sizeof(v)) == 0);
+
+ result = inter.timeToValues(150, v);
+ SkASSERT(result == kNormal_Result);
+ SkASSERT(memcmp(v, iset(vv, 60, 120, 180), sizeof(v)) == 0);
+
+ result = inter.timeToValues(125, v);
+ SkASSERT(result == kNormal_Result);
+ result = inter.timeToValues(175, v);
+ SkASSERT(result == kNormal_Result);
+#endif
+}
+
+#endif
diff --git a/utils/SkJSON.cpp b/utils/SkJSON.cpp
new file mode 100644
index 00000000..9b122082
--- /dev/null
+++ b/utils/SkJSON.cpp
@@ -0,0 +1,634 @@
+/*
+ * 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 "SkJSON.h"
+#include "SkString.h"
+
+#ifdef SK_DEBUG
+// #define TRACE_SKJSON_LEAKS
+#endif
+
+#ifdef TRACE_SKJSON_LEAKS
+ static int gStringCount;
+ static int gSlotCount;
+ static int gObjectCount;
+ static int gArrayCount;
+ #define LEAK_CODE(code) code
+#else
+ #define LEAK_CODE(code)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static char* alloc_string(size_t len) {
+ LEAK_CODE(SkDebugf(" string[%d]\n", gStringCount++);)
+ char* str = (char*)sk_malloc_throw(len + 1);
+ str[len] = 0;
+ return str;
+}
+
+static char* dup_string(const char src[]) {
+ if (NULL == src) {
+ return NULL;
+ }
+ size_t len = strlen(src);
+ char* dst = alloc_string(len);
+ memcpy(dst, src, len);
+ return dst;
+}
+
+static void free_string(char* str) {
+ if (str) {
+ sk_free(str);
+ LEAK_CODE(SkASSERT(gStringCount > 0); SkDebugf("~string[%d]\n", --gStringCount);)
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct SkJSON::Object::Slot {
+ Slot(const char name[], Type type) {
+ LEAK_CODE(SkDebugf(" slot[%d]\n", gSlotCount++);)
+ SkASSERT(name);
+
+ fNext = NULL;
+
+ size_t len = strlen(name);
+ // extra 1 for str[0] which stores the type
+ char* str = alloc_string(1 + len);
+ str[0] = (char)type;
+ // str[1] skips the type, len+1 includes the terminating 0 byte.
+ memcpy(&str[1], name, len + 1);
+ fName = str;
+
+ // fValue is uninitialized
+ }
+ ~Slot();
+
+ Type type() const { return (Type)fName[0]; }
+ const char* name() const { return &fName[1]; }
+
+ Slot* fNext;
+ char* fName; // fName[0] is the type, &fName[1] is the "name"
+ union {
+ Object* fObject;
+ Array* fArray;
+ char* fString;
+ int32_t fInt;
+ float fFloat;
+ bool fBool;
+ } fValue;
+};
+
+SkJSON::Object::Slot::~Slot() {
+ free_string(fName);
+ switch (this->type()) {
+ case kObject:
+ delete fValue.fObject;
+ break;
+ case kArray:
+ delete fValue.fArray;
+ break;
+ case kString:
+ free_string(fValue.fString);
+ break;
+ default:
+ break;
+ }
+ LEAK_CODE(SkASSERT(gSlotCount > 0); SkDebugf("~slot[%d]\n", --gSlotCount);)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkJSON::Object::Iter::Iter(const Object& obj) : fSlot(obj.fHead) {}
+
+bool SkJSON::Object::Iter::done() const {
+ return NULL == fSlot;
+}
+
+void SkJSON::Object::Iter::next() {
+ SkASSERT(fSlot);
+ fSlot = fSlot->fNext;
+}
+
+SkJSON::Type SkJSON::Object::Iter::type() const {
+ SkASSERT(fSlot);
+ return fSlot->type();
+}
+
+const char* SkJSON::Object::Iter::name() const {
+ SkASSERT(fSlot);
+ return fSlot->name();
+}
+
+SkJSON::Object* SkJSON::Object::Iter::objectValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kObject == fSlot->type());
+ return fSlot->fValue.fObject;
+}
+
+SkJSON::Array* SkJSON::Object::Iter::arrayValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kArray == fSlot->type());
+ return fSlot->fValue.fArray;
+}
+
+const char* SkJSON::Object::Iter::stringValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kString == fSlot->type());
+ return fSlot->fValue.fString;
+}
+
+int32_t SkJSON::Object::Iter::intValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kInt == fSlot->type());
+ return fSlot->fValue.fInt;
+}
+
+float SkJSON::Object::Iter::floatValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kFloat == fSlot->type());
+ return fSlot->fValue.fFloat;
+}
+
+bool SkJSON::Object::Iter::boolValue() const {
+ SkASSERT(fSlot);
+ SkASSERT(kBool == fSlot->type());
+ return fSlot->fValue.fBool;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkJSON::Object::Object() : fHead(NULL), fTail(NULL) {
+ LEAK_CODE(SkDebugf(" object[%d]\n", gObjectCount++);)
+}
+
+SkJSON::Object::Object(const Object& other) : fHead(NULL), fTail(NULL) {
+ LEAK_CODE(SkDebugf(" object[%d]\n", gObjectCount++);)
+
+ Iter iter(other);
+ while (!iter.done()) {
+ switch (iter.type()) {
+ case kObject:
+ this->addObject(iter.name(), new Object(*iter.objectValue()));
+ break;
+ case kArray:
+ this->addArray(iter.name(), new Array(*iter.arrayValue()));
+ break;
+ case kString:
+ this->addString(iter.name(), dup_string(iter.stringValue()));
+ break;
+ case kInt:
+ this->addInt(iter.name(), iter.intValue());
+ break;
+ case kFloat:
+ this->addFloat(iter.name(), iter.floatValue());
+ break;
+ case kBool:
+ this->addBool(iter.name(), iter.boolValue());
+ break;
+ }
+ iter.next();
+ }
+}
+
+SkJSON::Object::~Object() {
+ Slot* slot = fHead;
+ while (slot) {
+ Slot* next = slot->fNext;
+ delete slot;
+ slot = next;
+ }
+ LEAK_CODE(SkASSERT(gObjectCount > 0); SkDebugf("~object[%d]\n", --gObjectCount);)
+}
+
+int SkJSON::Object::count() const {
+ int n = 0;
+ for (const Slot* slot = fHead; slot; slot = slot->fNext) {
+ n += 1;
+ }
+ return n;
+}
+
+SkJSON::Object::Slot* SkJSON::Object::addSlot(Slot* slot) {
+ SkASSERT(NULL == slot->fNext);
+ if (NULL == fHead) {
+ SkASSERT(NULL == fTail);
+ fHead = fTail = slot;
+ } else {
+ SkASSERT(fTail);
+ SkASSERT(NULL == fTail->fNext);
+ fTail->fNext = slot;
+ fTail = slot;
+ }
+ return slot;
+}
+
+void SkJSON::Object::addObject(const char name[], SkJSON::Object* value) {
+ this->addSlot(new Slot(name, kObject))->fValue.fObject = value;
+}
+
+void SkJSON::Object::addArray(const char name[], SkJSON::Array* value) {
+ this->addSlot(new Slot(name, kArray))->fValue.fArray = value;
+}
+
+void SkJSON::Object::addString(const char name[], const char value[]) {
+ this->addSlot(new Slot(name, kString))->fValue.fString = dup_string(value);
+}
+
+void SkJSON::Object::addInt(const char name[], int32_t value) {
+ this->addSlot(new Slot(name, kInt))->fValue.fInt = value;
+}
+
+void SkJSON::Object::addFloat(const char name[], float value) {
+ this->addSlot(new Slot(name, kFloat))->fValue.fFloat = value;
+}
+
+void SkJSON::Object::addBool(const char name[], bool value) {
+ this->addSlot(new Slot(name, kBool))->fValue.fBool = value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const SkJSON::Object::Slot* SkJSON::Object::findSlot(const char name[],
+ Type t) const {
+ for (const Slot* slot = fHead; slot; slot = slot->fNext) {
+ if (t == slot->type() && !strcmp(slot->name(), name)) {
+ return slot;
+ }
+ }
+ return NULL;
+}
+
+bool SkJSON::Object::find(const char name[], Type t) const {
+ return this->findSlot(name, t) != NULL;
+}
+
+bool SkJSON::Object::findObject(const char name[], SkJSON::Object** value) const {
+ const Slot* slot = this->findSlot(name, kObject);
+ if (slot) {
+ if (value) {
+ *value = slot->fValue.fObject;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::findArray(const char name[], SkJSON::Array** value) const {
+ const Slot* slot = this->findSlot(name, kArray);
+ if (slot) {
+ if (value) {
+ *value = slot->fValue.fArray;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::findString(const char name[], SkString* value) const {
+ const Slot* slot = this->findSlot(name, kString);
+ if (slot) {
+ if (value) {
+ value->set(slot->fValue.fString);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::findInt(const char name[], int32_t* value) const {
+ const Slot* slot = this->findSlot(name, kInt);
+ if (slot) {
+ if (value) {
+ *value = slot->fValue.fInt;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::findFloat(const char name[], float* value) const {
+ const Slot* slot = this->findSlot(name, kFloat);
+ if (slot) {
+ if (value) {
+ *value = slot->fValue.fFloat;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::findBool(const char name[], bool* value) const {
+ const Slot* slot = this->findSlot(name, kBool);
+ if (slot) {
+ if (value) {
+ *value = slot->fValue.fBool;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkJSON::Object::remove(const char name[], Type t) {
+ SkDEBUGCODE(int count = this->count();)
+ Slot* prev = NULL;
+ Slot* slot = fHead;
+ while (slot) {
+ Slot* next = slot->fNext;
+ if (t == slot->type() && !strcmp(slot->name(), name)) {
+ if (prev) {
+ SkASSERT(fHead != slot);
+ prev->fNext = next;
+ } else {
+ SkASSERT(fHead == slot);
+ fHead = next;
+ }
+ if (fTail == slot) {
+ fTail = prev;
+ }
+ delete slot;
+ SkASSERT(count - 1 == this->count());
+ return true;
+ }
+ prev = slot;
+ slot = next;
+ }
+ SkASSERT(count == this->count());
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void tabForLevel(int level) {
+ for (int i = 0; i < level; ++i) {
+ SkDebugf(" ");
+ }
+}
+
+void SkJSON::Object::toDebugf() const {
+ SkDebugf("{\n");
+ this->dumpLevel(0);
+ SkDebugf("}\n");
+}
+
+void SkJSON::Object::dumpLevel(int level) const {
+ for (Slot* slot = fHead; slot; slot = slot->fNext) {
+ Type t = slot->type();
+ tabForLevel(level + 1);
+ SkDebugf("\"%s\" : ", slot->name());
+ switch (slot->type()) {
+ case kObject:
+ if (slot->fValue.fObject) {
+ SkDebugf("{\n");
+ slot->fValue.fObject->dumpLevel(level + 1);
+ tabForLevel(level + 1);
+ SkDebugf("}");
+ } else {
+ SkDebugf("null");
+ }
+ break;
+ case kArray:
+ if (slot->fValue.fArray) {
+ SkDebugf("[");
+ slot->fValue.fArray->dumpLevel(level + 1);
+ SkDebugf("]");
+ } else {
+ SkDebugf("null");
+ }
+ break;
+ case kString:
+ SkDebugf("\"%s\"", slot->fValue.fString);
+ break;
+ case kInt:
+ SkDebugf("%d", slot->fValue.fInt);
+ break;
+ case kFloat:
+ SkDebugf("%g", slot->fValue.fFloat);
+ break;
+ case kBool:
+ SkDebugf("%s", slot->fValue.fBool ? "true" : "false");
+ break;
+ default:
+ SkASSERT(!"how did I get here");
+ break;
+ }
+ if (slot->fNext) {
+ SkDebugf(",");
+ }
+ SkDebugf("\n");
+ }
+}
+
+void SkJSON::Array::dumpLevel(int level) const {
+ if (0 == fCount) {
+ return;
+ }
+ int last = fCount - 1;
+
+ switch (this->type()) {
+ case kObject: {
+ SkDebugf("\n");
+ for (int i = 0; i <= last; ++i) {
+ Object* obj = fArray.fObjects[i];
+ tabForLevel(level + 1);
+ if (obj) {
+ SkDebugf("{\n");
+ obj->dumpLevel(level + 1);
+ tabForLevel(level + 1);
+ SkDebugf(i < last ? "}," : "}");
+ } else {
+ SkDebugf(i < last ? "null," : "null");
+ }
+ SkDebugf("\n");
+ }
+ } break;
+ case kArray: {
+ SkDebugf("\n");
+ for (int i = 0; i <= last; ++i) {
+ Array* array = fArray.fArrays[i];
+ tabForLevel(level + 1);
+ if (array) {
+ SkDebugf("[");
+ array->dumpLevel(level + 1);
+ tabForLevel(level + 1);
+ SkDebugf(i < last ? "]," : "]");
+ } else {
+ SkDebugf(i < last ? "null," : "null");
+ }
+ SkDebugf("\n");
+ }
+ } break;
+ case kString: {
+ for (int i = 0; i < last; ++i) {
+ const char* str = fArray.fStrings[i];
+ SkDebugf(str ? " \"%s\"," : " null,", str);
+ }
+ const char* str = fArray.fStrings[last];
+ SkDebugf(str ? " \"%s\" " : " null ", str);
+ } break;
+ case kInt: {
+ for (int i = 0; i < last; ++i) {
+ SkDebugf(" %d,", fArray.fInts[i]);
+ }
+ SkDebugf(" %d ", fArray.fInts[last]);
+ } break;
+ case kFloat: {
+ for (int i = 0; i < last; ++i) {
+ SkDebugf(" %g,", fArray.fFloats[i]);
+ }
+ SkDebugf(" %g ", fArray.fFloats[last]);
+ } break;
+ case kBool: {
+ for (int i = 0; i < last; ++i) {
+ SkDebugf(" %s,", fArray.fBools[i] ? "true" : "false");
+ }
+ SkDebugf(" %s ", fArray.fInts[last] ? "true" : "false");
+ } break;
+ default:
+ SkASSERT(!"unsupported array type");
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const uint8_t gBytesPerType[] = {
+ sizeof(SkJSON::Object*),
+ sizeof(SkJSON::Array*),
+ sizeof(char*),
+ sizeof(int32_t),
+ sizeof(float),
+ sizeof(bool)
+};
+
+typedef void* (*DupProc)(const void*);
+
+static void* dup_object(const void* src) {
+ return SkNEW_ARGS(SkJSON::Object, (*(SkJSON::Object*)src));
+}
+
+static void* dup_array(const void* src) {
+ return SkNEW_ARGS(SkJSON::Array, (*(SkJSON::Array*)src));
+}
+
+static const DupProc gDupProcs[] = {
+ dup_object, // Object
+ dup_array, // Array
+ (DupProc)dup_string, // String
+ NULL, // int
+ NULL, // float
+ NULL, // bool
+};
+
+void SkJSON::Array::init(Type type, int count, const void* src) {
+ LEAK_CODE(SkDebugf(" array[%d]\n", gArrayCount++);)
+
+ SkASSERT((unsigned)type < SK_ARRAY_COUNT(gBytesPerType));
+
+ if (count < 0) {
+ count = 0;
+ }
+ size_t size = count * gBytesPerType[type];
+
+ fCount = count;
+ fType = type;
+ fArray.fVoids = sk_malloc_throw(size);
+ if (src) {
+ DupProc proc = gDupProcs[fType];
+ if (!proc) {
+ memcpy(fArray.fVoids, src, size);
+ } else {
+ void** srcPtr = (void**)src;
+ void** dstPtr = (void**)fArray.fVoids;
+ for (int i = 0; i < fCount; ++i) {
+ dstPtr[i] = proc(srcPtr[i]);
+ }
+ }
+ } else {
+ sk_bzero(fArray.fVoids, size);
+ }
+}
+
+SkJSON::Array::Array(Type type, int count) {
+ this->init(type, count, NULL);
+}
+
+SkJSON::Array::Array(const int32_t values[], int count) {
+ this->init(kInt, count, values);
+}
+
+SkJSON::Array::Array(const float values[], int count) {
+ this->init(kFloat, count, values);
+}
+
+SkJSON::Array::Array(const bool values[], int count) {
+ this->init(kBool, count, values);
+}
+
+SkJSON::Array::Array(const Array& other) {
+ this->init(other.type(), other.count(), other.fArray.fVoids);
+}
+
+typedef void (*FreeProc)(void*);
+
+static void free_object(void* obj) {
+ delete (SkJSON::Object*)obj;
+}
+
+static void free_array(void* array) {
+ delete (SkJSON::Array*)array;
+}
+
+static const FreeProc gFreeProcs[] = {
+ free_object, // Object
+ free_array, // Array
+ (FreeProc)free_string, // String
+ NULL, // int
+ NULL, // float
+ NULL, // bool
+};
+
+SkJSON::Array::~Array() {
+ FreeProc proc = gFreeProcs[fType];
+ if (proc) {
+ void** ptr = (void**)fArray.fVoids;
+ for (int i = 0; i < fCount; ++i) {
+ proc(ptr[i]);
+ }
+ }
+ sk_free(fArray.fVoids);
+
+ LEAK_CODE(SkASSERT(gArrayCount > 0); SkDebugf("~array[%d]\n", --gArrayCount);)
+}
+
+void SkJSON::Array::setObject(int index, Object* object) {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ Object*& prev = fArray.fObjects[index];
+ if (prev != object) {
+ delete prev;
+ prev = object;
+ }
+}
+
+void SkJSON::Array::setArray(int index, Array* array) {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ Array*& prev = fArray.fArrays[index];
+ if (prev != array) {
+ delete prev;
+ prev = array;
+ }
+}
+
+void SkJSON::Array::setString(int index, const char str[]) {
+ SkASSERT((unsigned)index < (unsigned)fCount);
+ char*& prev = fArray.fStrings[index];
+ if (prev != str) {
+ free_string(prev);
+ prev = dup_string(str);
+ }
+}
diff --git a/utils/SkLayer.cpp b/utils/SkLayer.cpp
new file mode 100644
index 00000000..126dd921
--- /dev/null
+++ b/utils/SkLayer.cpp
@@ -0,0 +1,232 @@
+
+/*
+ * 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 "SkLayer.h"
+#include "SkCanvas.h"
+
+//#define DEBUG_DRAW_LAYER_BOUNDS
+//#define DEBUG_TRACK_NEW_DELETE
+
+#ifdef DEBUG_TRACK_NEW_DELETE
+ static int gLayerAllocCount;
+#endif
+
+SK_DEFINE_INST_COUNT(SkLayer)
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLayer::SkLayer() {
+ fParent = NULL;
+ m_opacity = SK_Scalar1;
+ m_size.set(0, 0);
+ m_position.set(0, 0);
+ m_anchorPoint.set(SK_ScalarHalf, SK_ScalarHalf);
+
+ fMatrix.reset();
+ fChildrenMatrix.reset();
+ fFlags = 0;
+
+#ifdef DEBUG_TRACK_NEW_DELETE
+ gLayerAllocCount += 1;
+ SkDebugf("SkLayer new: %d\n", gLayerAllocCount);
+#endif
+}
+
+SkLayer::SkLayer(const SkLayer& src) : INHERITED() {
+ fParent = NULL;
+ m_opacity = src.m_opacity;
+ m_size = src.m_size;
+ m_position = src.m_position;
+ m_anchorPoint = src.m_anchorPoint;
+
+ fMatrix = src.fMatrix;
+ fChildrenMatrix = src.fChildrenMatrix;
+ fFlags = src.fFlags;
+
+#ifdef DEBUG_TRACK_NEW_DELETE
+ gLayerAllocCount += 1;
+ SkDebugf("SkLayer copy: %d\n", gLayerAllocCount);
+#endif
+}
+
+SkLayer::~SkLayer() {
+ this->removeChildren();
+
+#ifdef DEBUG_TRACK_NEW_DELETE
+ gLayerAllocCount -= 1;
+ SkDebugf("SkLayer delete: %d\n", gLayerAllocCount);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkLayer::isInheritFromRootTransform() const {
+ return (fFlags & kInheritFromRootTransform_Flag) != 0;
+}
+
+void SkLayer::setInheritFromRootTransform(bool doInherit) {
+ if (doInherit) {
+ fFlags |= kInheritFromRootTransform_Flag;
+ } else {
+ fFlags &= ~kInheritFromRootTransform_Flag;
+ }
+}
+
+void SkLayer::setMatrix(const SkMatrix& matrix) {
+ fMatrix = matrix;
+}
+
+void SkLayer::setChildrenMatrix(const SkMatrix& matrix) {
+ fChildrenMatrix = matrix;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SkLayer::countChildren() const {
+ return m_children.count();
+}
+
+SkLayer* SkLayer::getChild(int index) const {
+ if ((unsigned)index < (unsigned)m_children.count()) {
+ SkASSERT(m_children[index]->fParent == this);
+ return m_children[index];
+ }
+ return NULL;
+}
+
+SkLayer* SkLayer::addChild(SkLayer* child) {
+ SkASSERT(this != child);
+ child->ref();
+ child->detachFromParent();
+ SkASSERT(child->fParent == NULL);
+ child->fParent = this;
+
+ *m_children.append() = child;
+ return child;
+}
+
+void SkLayer::detachFromParent() {
+ if (fParent) {
+ int index = fParent->m_children.find(this);
+ SkASSERT(index >= 0);
+ fParent->m_children.remove(index);
+ fParent = NULL;
+ this->unref(); // this call might delete us
+ }
+}
+
+void SkLayer::removeChildren() {
+ int count = m_children.count();
+ for (int i = 0; i < count; i++) {
+ SkLayer* child = m_children[i];
+ SkASSERT(child->fParent == this);
+ child->fParent = NULL; // in case it has more than one owner
+ child->unref();
+ }
+ m_children.reset();
+}
+
+SkLayer* SkLayer::getRootLayer() const {
+ const SkLayer* root = this;
+ while (root->fParent != NULL) {
+ root = root->fParent;
+ }
+ return const_cast<SkLayer*>(root);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkLayer::getLocalTransform(SkMatrix* matrix) const {
+ matrix->setTranslate(m_position.fX, m_position.fY);
+
+ SkScalar tx = SkScalarMul(m_anchorPoint.fX, m_size.width());
+ SkScalar ty = SkScalarMul(m_anchorPoint.fY, m_size.height());
+ matrix->preTranslate(tx, ty);
+ matrix->preConcat(this->getMatrix());
+ matrix->preTranslate(-tx, -ty);
+}
+
+void SkLayer::localToGlobal(SkMatrix* matrix) const {
+ this->getLocalTransform(matrix);
+
+ if (this->isInheritFromRootTransform()) {
+ matrix->postConcat(this->getRootLayer()->getMatrix());
+ return;
+ }
+
+ const SkLayer* layer = this;
+ while (layer->fParent != NULL) {
+ layer = layer->fParent;
+
+ SkMatrix tmp;
+ layer->getLocalTransform(&tmp);
+ tmp.preConcat(layer->getChildrenMatrix());
+ matrix->postConcat(tmp);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkLayer::onDraw(SkCanvas*, SkScalar opacity) {
+// SkDebugf("----- no onDraw for %p\n", this);
+}
+
+#include "SkString.h"
+
+void SkLayer::draw(SkCanvas* canvas, SkScalar opacity) {
+#if 0
+ SkString str1, str2;
+ // this->getMatrix().toDumpString(&str1);
+ // this->getChildrenMatrix().toDumpString(&str2);
+ SkDebugf("--- drawlayer %p opacity %g size [%g %g] pos [%g %g] matrix %s children %s\n",
+ this, opacity * this->getOpacity(), m_size.width(), m_size.height(),
+ m_position.fX, m_position.fY, str1.c_str(), str2.c_str());
+#endif
+
+ opacity = SkScalarMul(opacity, this->getOpacity());
+ if (opacity <= 0) {
+// SkDebugf("---- abort drawing %p opacity %g\n", this, opacity);
+ return;
+ }
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ // apply our local transform
+ {
+ SkMatrix tmp;
+ this->getLocalTransform(&tmp);
+ if (this->isInheritFromRootTransform()) {
+ // should we also apply the root's childrenMatrix?
+ canvas->setMatrix(getRootLayer()->getMatrix());
+ }
+ canvas->concat(tmp);
+ }
+
+ this->onDraw(canvas, opacity);
+
+#ifdef DEBUG_DRAW_LAYER_BOUNDS
+ {
+ SkRect r = SkRect::MakeSize(this->getSize());
+ SkPaint p;
+ p.setAntiAlias(true);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(SkIntToScalar(2));
+ p.setColor(0xFFFF44DD);
+ canvas->drawRect(r, p);
+ canvas->drawLine(r.fLeft, r.fTop, r.fRight, r.fBottom, p);
+ canvas->drawLine(r.fLeft, r.fBottom, r.fRight, r.fTop, p);
+ }
+#endif
+
+ int count = this->countChildren();
+ if (count > 0) {
+ canvas->concat(this->getChildrenMatrix());
+ for (int i = 0; i < count; i++) {
+ this->getChild(i)->draw(canvas, opacity);
+ }
+ }
+}
diff --git a/utils/SkLua.cpp b/utils/SkLua.cpp
new file mode 100644
index 00000000..427093b2
--- /dev/null
+++ b/utils/SkLua.cpp
@@ -0,0 +1,1129 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLua.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkDocument.h"
+#include "SkImage.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPixelRef.h"
+#include "SkRRect.h"
+#include "SkString.h"
+#include "SkTypeface.h"
+
+extern "C" {
+ #include "lua.h"
+ #include "lualib.h"
+ #include "lauxlib.h"
+}
+
+// return the metatable name for a given class
+template <typename T> const char* get_mtname();
+#define DEF_MTNAME(T) \
+ template <> const char* get_mtname<T>() { \
+ return #T "_LuaMetaTableName"; \
+ }
+
+DEF_MTNAME(SkCanvas)
+DEF_MTNAME(SkDocument)
+DEF_MTNAME(SkImage)
+DEF_MTNAME(SkMatrix)
+DEF_MTNAME(SkRRect)
+DEF_MTNAME(SkPath)
+DEF_MTNAME(SkPaint)
+DEF_MTNAME(SkShader)
+DEF_MTNAME(SkTypeface)
+
+template <typename T> T* push_new(lua_State* L) {
+ T* addr = (T*)lua_newuserdata(L, sizeof(T));
+ new (addr) T;
+ luaL_getmetatable(L, get_mtname<T>());
+ lua_setmetatable(L, -2);
+ return addr;
+}
+
+template <typename T> void push_obj(lua_State* L, const T& obj) {
+ new (lua_newuserdata(L, sizeof(T))) T(obj);
+ luaL_getmetatable(L, get_mtname<T>());
+ lua_setmetatable(L, -2);
+}
+
+template <typename T> void push_ref(lua_State* L, T* ref) {
+ *(T**)lua_newuserdata(L, sizeof(T*)) = SkRef(ref);
+ luaL_getmetatable(L, get_mtname<T>());
+ lua_setmetatable(L, -2);
+}
+
+template <typename T> T* get_ref(lua_State* L, int index) {
+ return *(T**)luaL_checkudata(L, index, get_mtname<T>());
+}
+
+template <typename T> T* get_obj(lua_State* L, int index) {
+ return (T*)luaL_checkudata(L, index, get_mtname<T>());
+}
+
+static bool lua2bool(lua_State* L, int index) {
+ return !!lua_toboolean(L, index);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLua::SkLua(const char termCode[]) : fTermCode(termCode), fWeOwnL(true) {
+ fL = luaL_newstate();
+ luaL_openlibs(fL);
+ SkLua::Load(fL);
+}
+
+SkLua::SkLua(lua_State* L) : fL(L), fWeOwnL(false) {}
+
+SkLua::~SkLua() {
+ if (fWeOwnL) {
+ if (fTermCode.size() > 0) {
+ lua_getglobal(fL, fTermCode.c_str());
+ if (lua_pcall(fL, 0, 0, 0) != LUA_OK) {
+ SkDebugf("lua err: %s\n", lua_tostring(fL, -1));
+ }
+ }
+ lua_close(fL);
+ }
+}
+
+bool SkLua::runCode(const char code[]) {
+ int err = luaL_loadstring(fL, code) || lua_pcall(fL, 0, 0, 0);
+ if (err) {
+ SkDebugf("--- lua failed: %s\n", lua_tostring(fL, -1));
+ return false;
+ }
+ return true;
+}
+
+bool SkLua::runCode(const void* code, size_t size) {
+ SkString str((const char*)code, size);
+ return this->runCode(str.c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define CHECK_SETFIELD(key) do if (key) lua_setfield(fL, -2, key); while (0)
+
+static void setfield_bool_if(lua_State* L, const char key[], bool pred) {
+ if (pred) {
+ lua_pushboolean(L, true);
+ lua_setfield(L, -2, key);
+ }
+}
+
+static void setfield_string(lua_State* L, const char key[], const char value[]) {
+ lua_pushstring(L, value);
+ lua_setfield(L, -2, key);
+}
+
+static void setfield_number(lua_State* L, const char key[], double value) {
+ lua_pushnumber(L, value);
+ lua_setfield(L, -2, key);
+}
+
+static void setfield_boolean(lua_State* L, const char key[], bool value) {
+ lua_pushboolean(L, value);
+ lua_setfield(L, -2, key);
+}
+
+static void setfield_scalar(lua_State* L, const char key[], SkScalar value) {
+ setfield_number(L, key, SkScalarToLua(value));
+}
+
+static void setfield_function(lua_State* L,
+ const char key[], lua_CFunction value) {
+ lua_pushcfunction(L, value);
+ lua_setfield(L, -2, key);
+}
+
+static void setarray_number(lua_State* L, int index, double value) {
+ lua_pushnumber(L, value);
+ lua_rawseti(L, -2, index);
+}
+
+void SkLua::pushBool(bool value, const char key[]) {
+ lua_pushboolean(fL, value);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushString(const char str[], const char key[]) {
+ lua_pushstring(fL, str);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushString(const char str[], size_t length, const char key[]) {
+ // TODO: how to do this w/o making a copy?
+ SkString s(str, length);
+ lua_pushstring(fL, s.c_str());
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushString(const SkString& str, const char key[]) {
+ lua_pushstring(fL, str.c_str());
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushColor(SkColor color, const char key[]) {
+ lua_newtable(fL);
+ setfield_number(fL, "a", SkColorGetA(color) / 255.0);
+ setfield_number(fL, "r", SkColorGetR(color) / 255.0);
+ setfield_number(fL, "g", SkColorGetG(color) / 255.0);
+ setfield_number(fL, "b", SkColorGetB(color) / 255.0);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushU32(uint32_t value, const char key[]) {
+ lua_pushnumber(fL, (double)value);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushScalar(SkScalar value, const char key[]) {
+ lua_pushnumber(fL, SkScalarToLua(value));
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushArrayU16(const uint16_t array[], int count, const char key[]) {
+ lua_newtable(fL);
+ for (int i = 0; i < count; ++i) {
+ // make it base-1 to match lua convention
+ setarray_number(fL, i + 1, (double)array[i]);
+ }
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushRect(const SkRect& r, const char key[]) {
+ lua_newtable(fL);
+ setfield_scalar(fL, "left", r.fLeft);
+ setfield_scalar(fL, "top", r.fTop);
+ setfield_scalar(fL, "right", r.fRight);
+ setfield_scalar(fL, "bottom", r.fBottom);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushRRect(const SkRRect& rr, const char key[]) {
+ push_obj(fL, rr);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushMatrix(const SkMatrix& matrix, const char key[]) {
+ push_obj(fL, matrix);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushPaint(const SkPaint& paint, const char key[]) {
+ push_obj(fL, paint);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushPath(const SkPath& path, const char key[]) {
+ push_obj(fL, path);
+ CHECK_SETFIELD(key);
+}
+
+void SkLua::pushCanvas(SkCanvas* canvas, const char key[]) {
+ push_ref(fL, canvas);
+ CHECK_SETFIELD(key);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static SkScalar lua2scalar(lua_State* L, int index) {
+ SkASSERT(lua_isnumber(L, index));
+ return SkLuaToScalar(lua_tonumber(L, index));
+}
+
+static SkScalar lua2scalar_def(lua_State* L, int index, SkScalar defaultValue) {
+ if (lua_isnumber(L, index)) {
+ return SkLuaToScalar(lua_tonumber(L, index));
+ } else {
+ return defaultValue;
+ }
+}
+
+static SkScalar getfield_scalar(lua_State* L, int index, const char key[]) {
+ SkASSERT(lua_istable(L, index));
+ lua_pushstring(L, key);
+ lua_gettable(L, index);
+
+ SkScalar value = lua2scalar(L, -1);
+ lua_pop(L, 1);
+ return value;
+}
+
+static SkScalar getfield_scalar_default(lua_State* L, int index, const char key[], SkScalar def) {
+ SkASSERT(lua_istable(L, index));
+ lua_pushstring(L, key);
+ lua_gettable(L, index);
+
+ SkScalar value;
+ if (lua_isnil(L, -1)) {
+ value = def;
+ } else {
+ value = lua2scalar(L, -1);
+ }
+ lua_pop(L, 1);
+ return value;
+}
+
+static U8CPU unit2byte(SkScalar x) {
+ if (x <= 0) {
+ return 0;
+ } else if (x >= 1) {
+ return 255;
+ } else {
+ return SkScalarRoundToInt(x * 255);
+ }
+}
+
+static SkColor lua2color(lua_State* L, int index) {
+ return SkColorSetARGB(unit2byte(getfield_scalar(L, index, "a")),
+ unit2byte(getfield_scalar(L, index, "r")),
+ unit2byte(getfield_scalar(L, index, "g")),
+ unit2byte(getfield_scalar(L, index, "b")));
+}
+
+static SkRect* lua2rect(lua_State* L, int index, SkRect* rect) {
+ rect->set(getfield_scalar_default(L, index, "left", 0),
+ getfield_scalar_default(L, index, "top", 0),
+ getfield_scalar(L, index, "right"),
+ getfield_scalar(L, index, "bottom"));
+ return rect;
+}
+
+static int lcanvas_drawColor(lua_State* L) {
+ get_ref<SkCanvas>(L, 1)->drawColor(lua2color(L, 2));
+ return 0;
+}
+
+static int lcanvas_drawRect(lua_State* L) {
+ SkRect rect;
+ get_ref<SkCanvas>(L, 1)->drawRect(*lua2rect(L, 2, &rect),
+ *get_obj<SkPaint>(L, 3));
+ return 0;
+}
+
+static int lcanvas_drawOval(lua_State* L) {
+ SkRect rect;
+ get_ref<SkCanvas>(L, 1)->drawOval(*lua2rect(L, 2, &rect),
+ *get_obj<SkPaint>(L, 3));
+ return 0;
+}
+
+static int lcanvas_drawCircle(lua_State* L) {
+ get_ref<SkCanvas>(L, 1)->drawCircle(lua2scalar(L, 2),
+ lua2scalar(L, 3),
+ lua2scalar(L, 4),
+ *get_obj<SkPaint>(L, 5));
+ return 0;
+}
+
+static int lcanvas_drawImage(lua_State* L) {
+ SkCanvas* canvas = get_ref<SkCanvas>(L, 1);
+ SkImage* image = get_ref<SkImage>(L, 2);
+ if (NULL == image) {
+ return 0;
+ }
+ SkScalar x = lua2scalar(L, 3);
+ SkScalar y = lua2scalar(L, 4);
+
+ SkPaint paint;
+ const SkPaint* paintPtr = NULL;
+ if (lua_isnumber(L, 5)) {
+ paint.setAlpha(SkScalarRoundToInt(lua2scalar(L, 5) * 255));
+ paintPtr = &paint;
+ }
+ image->draw(canvas, x, y, paintPtr);
+ return 0;
+}
+
+static int lcanvas_drawPath(lua_State* L) {
+ get_ref<SkCanvas>(L, 1)->drawPath(*get_obj<SkPath>(L, 2),
+ *get_obj<SkPaint>(L, 3));
+ return 0;
+}
+
+static int lcanvas_drawText(lua_State* L) {
+ if (lua_gettop(L) < 5) {
+ return 0;
+ }
+
+ if (lua_isstring(L, 2) && lua_isnumber(L, 3) && lua_isnumber(L, 4)) {
+ size_t len;
+ const char* text = lua_tolstring(L, 2, &len);
+ get_ref<SkCanvas>(L, 1)->drawText(text, len,
+ lua2scalar(L, 3), lua2scalar(L, 4),
+ *get_obj<SkPaint>(L, 5));
+ }
+ return 0;
+}
+
+static int lcanvas_getSaveCount(lua_State* L) {
+ lua_pushnumber(L, get_ref<SkCanvas>(L, 1)->getSaveCount());
+ return 1;
+}
+
+static int lcanvas_getTotalMatrix(lua_State* L) {
+ SkLua(L).pushMatrix(get_ref<SkCanvas>(L, 1)->getTotalMatrix());
+ return 1;
+}
+
+static int lcanvas_save(lua_State* L) {
+ lua_pushinteger(L, get_ref<SkCanvas>(L, 1)->save());
+ return 1;
+}
+
+static int lcanvas_restore(lua_State* L) {
+ get_ref<SkCanvas>(L, 1)->restore();
+ return 0;
+}
+
+static int lcanvas_scale(lua_State* L) {
+ SkScalar sx = lua2scalar_def(L, 2, 1);
+ SkScalar sy = lua2scalar_def(L, 3, sx);
+ get_ref<SkCanvas>(L, 1)->scale(sx, sy);
+ return 0;
+}
+
+static int lcanvas_translate(lua_State* L) {
+ SkScalar tx = lua2scalar_def(L, 2, 0);
+ SkScalar ty = lua2scalar_def(L, 3, 0);
+ get_ref<SkCanvas>(L, 1)->translate(tx, ty);
+ return 0;
+}
+
+static int lcanvas_rotate(lua_State* L) {
+ SkScalar degrees = lua2scalar_def(L, 2, 0);
+ get_ref<SkCanvas>(L, 1)->rotate(degrees);
+ return 0;
+}
+
+static int lcanvas_gc(lua_State* L) {
+ get_ref<SkCanvas>(L, 1)->unref();
+ return 0;
+}
+
+static const struct luaL_Reg gSkCanvas_Methods[] = {
+ { "drawColor", lcanvas_drawColor },
+ { "drawRect", lcanvas_drawRect },
+ { "drawOval", lcanvas_drawOval },
+ { "drawCircle", lcanvas_drawCircle },
+ { "drawImage", lcanvas_drawImage },
+ { "drawPath", lcanvas_drawPath },
+ { "drawText", lcanvas_drawText },
+ { "getSaveCount", lcanvas_getSaveCount },
+ { "getTotalMatrix", lcanvas_getTotalMatrix },
+ { "save", lcanvas_save },
+ { "restore", lcanvas_restore },
+ { "scale", lcanvas_scale },
+ { "translate", lcanvas_translate },
+ { "rotate", lcanvas_rotate },
+ { "__gc", lcanvas_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int ldocument_beginPage(lua_State* L) {
+ const SkRect* contentPtr = NULL;
+ push_ref(L, get_ref<SkDocument>(L, 1)->beginPage(lua2scalar(L, 2),
+ lua2scalar(L, 3),
+ contentPtr));
+ return 1;
+}
+
+static int ldocument_endPage(lua_State* L) {
+ get_ref<SkDocument>(L, 1)->endPage();
+ return 0;
+}
+
+static int ldocument_close(lua_State* L) {
+ get_ref<SkDocument>(L, 1)->close();
+ return 0;
+}
+
+static int ldocument_gc(lua_State* L) {
+ get_ref<SkDocument>(L, 1)->unref();
+ return 0;
+}
+
+static const struct luaL_Reg gSkDocument_Methods[] = {
+ { "beginPage", ldocument_beginPage },
+ { "endPage", ldocument_endPage },
+ { "close", ldocument_close },
+ { "__gc", ldocument_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int lpaint_isAntiAlias(lua_State* L) {
+ lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isAntiAlias());
+ return 1;
+}
+
+static int lpaint_setAntiAlias(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->setAntiAlias(lua2bool(L, 2));
+ return 0;
+}
+
+static int lpaint_getColor(lua_State* L) {
+ SkLua(L).pushColor(get_obj<SkPaint>(L, 1)->getColor());
+ return 1;
+}
+
+static int lpaint_setColor(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->setColor(lua2color(L, 2));
+ return 0;
+}
+
+static int lpaint_getTextSize(lua_State* L) {
+ SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getTextSize());
+ return 1;
+}
+
+static int lpaint_setTextSize(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->setTextSize(lua2scalar(L, 2));
+ return 0;
+}
+
+static int lpaint_getTypeface(lua_State* L) {
+ push_ref(L, get_obj<SkPaint>(L, 1)->getTypeface());
+ return 1;
+}
+
+static int lpaint_setTypeface(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->setTypeface(get_ref<SkTypeface>(L, 2));
+ return 0;
+}
+
+static int lpaint_getFontID(lua_State* L) {
+ SkTypeface* face = get_obj<SkPaint>(L, 1)->getTypeface();
+ SkLua(L).pushU32(SkTypeface::UniqueID(face));
+ return 1;
+}
+
+static const struct {
+ const char* fLabel;
+ SkPaint::Align fAlign;
+} gAlignRec[] = {
+ { "left", SkPaint::kLeft_Align },
+ { "center", SkPaint::kCenter_Align },
+ { "right", SkPaint::kRight_Align },
+};
+
+static int lpaint_getTextAlign(lua_State* L) {
+ SkPaint::Align align = get_obj<SkPaint>(L, 1)->getTextAlign();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gAlignRec); ++i) {
+ if (gAlignRec[i].fAlign == align) {
+ lua_pushstring(L, gAlignRec[i].fLabel);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int lpaint_setTextAlign(lua_State* L) {
+ if (lua_isstring(L, 2)) {
+ size_t len;
+ const char* label = lua_tolstring(L, 2, &len);
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gAlignRec); ++i) {
+ if (!strcmp(gAlignRec[i].fLabel, label)) {
+ get_obj<SkPaint>(L, 1)->setTextAlign(gAlignRec[i].fAlign);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int lpaint_getStroke(lua_State* L) {
+ lua_pushboolean(L, SkPaint::kStroke_Style == get_obj<SkPaint>(L, 1)->getStyle());
+ return 1;
+}
+
+static int lpaint_setStroke(lua_State* L) {
+ SkPaint::Style style;
+
+ if (lua_toboolean(L, 2)) {
+ style = SkPaint::kStroke_Style;
+ } else {
+ style = SkPaint::kFill_Style;
+ }
+ get_obj<SkPaint>(L, 1)->setStyle(style);
+ return 0;
+}
+
+static int lpaint_getStrokeWidth(lua_State* L) {
+ SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getStrokeWidth());
+ return 1;
+}
+
+static int lpaint_setStrokeWidth(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->setStrokeWidth(lua2scalar(L, 2));
+ return 0;
+}
+
+static int lpaint_measureText(lua_State* L) {
+ if (lua_isstring(L, 2)) {
+ size_t len;
+ const char* text = lua_tolstring(L, 2, &len);
+ SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->measureText(text, len));
+ return 1;
+ }
+ return 0;
+}
+
+struct FontMetrics {
+ SkScalar fTop; //!< The greatest distance above the baseline for any glyph (will be <= 0)
+ SkScalar fAscent; //!< The recommended distance above the baseline (will be <= 0)
+ SkScalar fDescent; //!< The recommended distance below the baseline (will be >= 0)
+ SkScalar fBottom; //!< The greatest distance below the baseline for any glyph (will be >= 0)
+ SkScalar fLeading; //!< The recommended distance to add between lines of text (will be >= 0)
+ SkScalar fAvgCharWidth; //!< the average charactor width (>= 0)
+ SkScalar fXMin; //!< The minimum bounding box x value for all glyphs
+ SkScalar fXMax; //!< The maximum bounding box x value for all glyphs
+ SkScalar fXHeight; //!< the height of an 'x' in px, or 0 if no 'x' in face
+};
+
+static int lpaint_getFontMetrics(lua_State* L) {
+ SkPaint::FontMetrics fm;
+ SkScalar height = get_obj<SkPaint>(L, 1)->getFontMetrics(&fm);
+
+ lua_newtable(L);
+ setfield_scalar(L, "top", fm.fTop);
+ setfield_scalar(L, "ascent", fm.fAscent);
+ setfield_scalar(L, "descent", fm.fDescent);
+ setfield_scalar(L, "bottom", fm.fBottom);
+ setfield_scalar(L, "leading", fm.fLeading);
+ SkLua(L).pushScalar(height);
+ return 2;
+}
+
+static int lpaint_getEffects(lua_State* L) {
+ const SkPaint* paint = get_obj<SkPaint>(L, 1);
+
+ lua_newtable(L);
+ setfield_bool_if(L, "looper", !!paint->getLooper());
+ setfield_bool_if(L, "pathEffect", !!paint->getPathEffect());
+ setfield_bool_if(L, "rasterizer", !!paint->getRasterizer());
+ setfield_bool_if(L, "maskFilter", !!paint->getMaskFilter());
+ setfield_bool_if(L, "shader", !!paint->getShader());
+ setfield_bool_if(L, "colorFilter", !!paint->getColorFilter());
+ setfield_bool_if(L, "imageFilter", !!paint->getImageFilter());
+ setfield_bool_if(L, "xfermode", !!paint->getXfermode());
+ return 1;
+}
+
+static int lpaint_getShader(lua_State* L) {
+ const SkPaint* paint = get_obj<SkPaint>(L, 1);
+ SkShader* shader = paint->getShader();
+ if (shader) {
+ push_ref(L, shader);
+ return 1;
+ }
+ return 0;
+}
+
+static int lpaint_gc(lua_State* L) {
+ get_obj<SkPaint>(L, 1)->~SkPaint();
+ return 0;
+}
+
+static const struct luaL_Reg gSkPaint_Methods[] = {
+ { "isAntiAlias", lpaint_isAntiAlias },
+ { "setAntiAlias", lpaint_setAntiAlias },
+ { "getColor", lpaint_getColor },
+ { "setColor", lpaint_setColor },
+ { "getTextSize", lpaint_getTextSize },
+ { "setTextSize", lpaint_setTextSize },
+ { "getTypeface", lpaint_getTypeface },
+ { "setTypeface", lpaint_setTypeface },
+ { "getFontID", lpaint_getFontID },
+ { "getTextAlign", lpaint_getTextAlign },
+ { "setTextAlign", lpaint_setTextAlign },
+ { "getStroke", lpaint_getStroke },
+ { "setStroke", lpaint_setStroke },
+ { "getStrokeWidth", lpaint_getStrokeWidth },
+ { "setStrokeWidth", lpaint_setStrokeWidth },
+ { "measureText", lpaint_measureText },
+ { "getFontMetrics", lpaint_getFontMetrics },
+ { "getEffects", lpaint_getEffects },
+ { "getShader", lpaint_getShader },
+ { "__gc", lpaint_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char* mode2string(SkShader::TileMode mode) {
+ static const char* gNames[] = { "clamp", "repeat", "mirror" };
+ SkASSERT((unsigned)mode < SK_ARRAY_COUNT(gNames));
+ return gNames[mode];
+}
+
+static const char* gradtype2string(SkShader::GradientType t) {
+ static const char* gNames[] = {
+ "none", "color", "linear", "radial", "radial2", "sweep", "conical"
+ };
+ SkASSERT((unsigned)t < SK_ARRAY_COUNT(gNames));
+ return gNames[t];
+}
+
+static int lshader_isOpaque(lua_State* L) {
+ SkShader* shader = get_ref<SkShader>(L, 1);
+ return shader && shader->isOpaque();
+}
+
+static int lshader_asABitmap(lua_State* L) {
+ SkShader* shader = get_ref<SkShader>(L, 1);
+ if (shader) {
+ SkBitmap bm;
+ SkMatrix matrix;
+ SkShader::TileMode modes[2];
+ switch (shader->asABitmap(&bm, &matrix, modes)) {
+ case SkShader::kDefault_BitmapType:
+ lua_newtable(L);
+ setfield_number(L, "genID", bm.pixelRef() ? bm.pixelRef()->getGenerationID() : 0);
+ setfield_number(L, "width", bm.width());
+ setfield_number(L, "height", bm.height());
+ setfield_string(L, "tileX", mode2string(modes[0]));
+ setfield_string(L, "tileY", mode2string(modes[1]));
+ return 1;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int lshader_asAGradient(lua_State* L) {
+ SkShader* shader = get_ref<SkShader>(L, 1);
+ if (shader) {
+ SkShader::GradientInfo info;
+ sk_bzero(&info, sizeof(info));
+
+ SkColor colors[3]; // hacked in for extracting info on 3 color case.
+ SkScalar pos[3];
+
+ info.fColorCount = 3;
+ info.fColors = &colors[0];
+ info.fColorOffsets = &pos[0];
+
+ SkShader::GradientType t = shader->asAGradient(&info);
+
+ if (SkShader::kNone_GradientType != t) {
+ lua_newtable(L);
+ setfield_string(L, "type", gradtype2string(t));
+ setfield_number(L, "colorCount", info.fColorCount);
+ setfield_string(L, "tile", mode2string(info.fTileMode));
+
+ if (info.fColorCount == 3){
+ setfield_number(L, "midPos", pos[1]);
+ }
+
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int lshader_gc(lua_State* L) {
+ get_ref<SkShader>(L, 1)->unref();
+ return 0;
+}
+
+static const struct luaL_Reg gSkShader_Methods[] = {
+ { "isOpaque", lshader_isOpaque },
+ { "asABitmap", lshader_asABitmap },
+ { "asAGradient", lshader_asAGradient },
+ { "__gc", lshader_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int lmatrix_getType(lua_State* L) {
+ SkMatrix::TypeMask mask = get_obj<SkMatrix>(L, 1)->getType();
+
+ lua_newtable(L);
+ setfield_boolean(L, "translate", SkToBool(mask & SkMatrix::kTranslate_Mask));
+ setfield_boolean(L, "scale", SkToBool(mask & SkMatrix::kScale_Mask));
+ setfield_boolean(L, "affine", SkToBool(mask & SkMatrix::kAffine_Mask));
+ setfield_boolean(L, "perspective", SkToBool(mask & SkMatrix::kPerspective_Mask));
+ return 1;
+}
+
+static int lmatrix_getScaleX(lua_State* L) {
+ lua_pushnumber(L, get_obj<SkMatrix>(L,1)->getScaleX());
+ return 1;
+}
+
+static int lmatrix_getScaleY(lua_State* L) {
+ lua_pushnumber(L, get_obj<SkMatrix>(L,1)->getScaleY());
+ return 1;
+}
+
+static int lmatrix_getTranslateX(lua_State* L) {
+ lua_pushnumber(L, get_obj<SkMatrix>(L,1)->getTranslateX());
+ return 1;
+}
+
+static int lmatrix_getTranslateY(lua_State* L) {
+ lua_pushnumber(L, get_obj<SkMatrix>(L,1)->getTranslateY());
+ return 1;
+}
+
+static const struct luaL_Reg gSkMatrix_Methods[] = {
+ { "getType", lmatrix_getType },
+ { "getScaleX", lmatrix_getScaleX },
+ { "getScaleY", lmatrix_getScaleY },
+ { "getTranslateX", lmatrix_getTranslateX },
+ { "getTranslateY", lmatrix_getTranslateY },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int lpath_getBounds(lua_State* L) {
+ SkLua(L).pushRect(get_obj<SkPath>(L, 1)->getBounds());
+ return 1;
+}
+
+static int lpath_isEmpty(lua_State* L) {
+ lua_pushboolean(L, get_obj<SkPath>(L, 1)->isEmpty());
+ return 1;
+}
+
+static int lpath_isRect(lua_State* L) {
+ SkRect r;
+ bool pred = get_obj<SkPath>(L, 1)->isRect(&r);
+ int ret_count = 1;
+ lua_pushboolean(L, pred);
+ if (pred) {
+ SkLua(L).pushRect(r);
+ ret_count += 1;
+ }
+ return ret_count;
+}
+
+static const char* dir2string(SkPath::Direction dir) {
+ static const char* gStr[] = {
+ "unknown", "cw", "ccw"
+ };
+ SkASSERT((unsigned)dir < SK_ARRAY_COUNT(gStr));
+ return gStr[dir];
+}
+
+static int lpath_isNestedRects(lua_State* L) {
+ SkRect rects[2];
+ SkPath::Direction dirs[2];
+ bool pred = get_obj<SkPath>(L, 1)->isNestedRects(rects, dirs);
+ int ret_count = 1;
+ lua_pushboolean(L, pred);
+ if (pred) {
+ SkLua lua(L);
+ lua.pushRect(rects[0]);
+ lua.pushRect(rects[1]);
+ lua_pushstring(L, dir2string(dirs[0]));
+ lua_pushstring(L, dir2string(dirs[0]));
+ ret_count += 4;
+ }
+ return ret_count;
+}
+
+static int lpath_reset(lua_State* L) {
+ get_obj<SkPath>(L, 1)->reset();
+ return 0;
+}
+
+static int lpath_moveTo(lua_State* L) {
+ get_obj<SkPath>(L, 1)->moveTo(lua2scalar(L, 2), lua2scalar(L, 3));
+ return 0;
+}
+
+static int lpath_lineTo(lua_State* L) {
+ get_obj<SkPath>(L, 1)->lineTo(lua2scalar(L, 2), lua2scalar(L, 3));
+ return 0;
+}
+
+static int lpath_quadTo(lua_State* L) {
+ get_obj<SkPath>(L, 1)->quadTo(lua2scalar(L, 2), lua2scalar(L, 3),
+ lua2scalar(L, 4), lua2scalar(L, 5));
+ return 0;
+}
+
+static int lpath_cubicTo(lua_State* L) {
+ get_obj<SkPath>(L, 1)->cubicTo(lua2scalar(L, 2), lua2scalar(L, 3),
+ lua2scalar(L, 4), lua2scalar(L, 5),
+ lua2scalar(L, 6), lua2scalar(L, 7));
+ return 0;
+}
+
+static int lpath_close(lua_State* L) {
+ get_obj<SkPath>(L, 1)->close();
+ return 0;
+}
+
+static int lpath_gc(lua_State* L) {
+ get_obj<SkPath>(L, 1)->~SkPath();
+ return 0;
+}
+
+static const struct luaL_Reg gSkPath_Methods[] = {
+ { "getBounds", lpath_getBounds },
+ { "isEmpty", lpath_isEmpty },
+ { "isRect", lpath_isRect },
+ { "isNestedRects", lpath_isNestedRects },
+ { "reset", lpath_reset },
+ { "moveTo", lpath_moveTo },
+ { "lineTo", lpath_lineTo },
+ { "quadTo", lpath_quadTo },
+ { "cubicTo", lpath_cubicTo },
+ { "close", lpath_close },
+ { "__gc", lpath_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const char* rrect_type(const SkRRect& rr) {
+ switch (rr.getType()) {
+ case SkRRect::kUnknown_Type: return "unknown";
+ case SkRRect::kEmpty_Type: return "empty";
+ case SkRRect::kRect_Type: return "rect";
+ case SkRRect::kOval_Type: return "oval";
+ case SkRRect::kSimple_Type: return "simple";
+ case SkRRect::kComplex_Type: return "complex";
+ }
+ SkASSERT(!"never get here");
+ return "";
+}
+
+static int lrrect_rect(lua_State* L) {
+ SkLua(L).pushRect(get_obj<SkRRect>(L, 1)->rect());
+ return 1;
+}
+
+static int lrrect_type(lua_State* L) {
+ lua_pushstring(L, rrect_type(*get_obj<SkRRect>(L, 1)));
+ return 1;
+}
+
+static int lrrect_radii(lua_State* L) {
+ int corner = lua_tointeger(L, 2);
+ SkVector v;
+ if (corner < 0 || corner > 3) {
+ SkDebugf("bad corner index %d", corner);
+ v.set(0, 0);
+ } else {
+ v = get_obj<SkRRect>(L, 1)->radii((SkRRect::Corner)corner);
+ }
+ lua_pushnumber(L, v.fX);
+ lua_pushnumber(L, v.fY);
+ return 2;
+}
+
+static int lrrect_gc(lua_State* L) {
+ get_obj<SkRRect>(L, 1)->~SkRRect();
+ return 0;
+}
+
+static const struct luaL_Reg gSkRRect_Methods[] = {
+ { "rect", lrrect_rect },
+ { "type", lrrect_type },
+ { "radii", lrrect_radii },
+ { "__gc", lrrect_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int limage_width(lua_State* L) {
+ lua_pushinteger(L, get_ref<SkImage>(L, 1)->width());
+ return 1;
+}
+
+static int limage_height(lua_State* L) {
+ lua_pushinteger(L, get_ref<SkImage>(L, 1)->height());
+ return 1;
+}
+
+static int limage_gc(lua_State* L) {
+ get_ref<SkImage>(L, 1)->unref();
+ return 0;
+}
+
+static const struct luaL_Reg gSkImage_Methods[] = {
+ { "width", limage_width },
+ { "height", limage_height },
+ { "__gc", limage_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int ltypeface_gc(lua_State* L) {
+ get_ref<SkTypeface>(L, 1)->unref();
+ return 0;
+}
+
+static const struct luaL_Reg gSkTypeface_Methods[] = {
+ { "__gc", ltypeface_gc },
+ { NULL, NULL }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class AutoCallLua {
+public:
+ AutoCallLua(lua_State* L, const char func[], const char verb[]) : fL(L) {
+ lua_getglobal(L, func);
+ if (!lua_isfunction(L, -1)) {
+ int t = lua_type(L, -1);
+ SkDebugf("--- expected function %d\n", t);
+ }
+
+ lua_newtable(L);
+ setfield_string(L, "verb", verb);
+ }
+
+ ~AutoCallLua() {
+ if (lua_pcall(fL, 1, 0, 0) != LUA_OK) {
+ SkDebugf("lua err: %s\n", lua_tostring(fL, -1));
+ }
+ lua_settop(fL, -1);
+ }
+
+private:
+ lua_State* fL;
+};
+
+#define AUTO_LUA(verb) AutoCallLua acl(fL, fFunc.c_str(), verb)
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int lsk_newDocumentPDF(lua_State* L) {
+ const char* file = NULL;
+ if (lua_gettop(L) > 0 && lua_isstring(L, 1)) {
+ file = lua_tolstring(L, 1, NULL);
+ }
+
+ SkDocument* doc = SkDocument::CreatePDF(file);
+ if (NULL == doc) {
+ // do I need to push a nil on the stack and return 1?
+ return 0;
+ } else {
+ push_ref(L, doc);
+ doc->unref();
+ return 1;
+ }
+}
+
+static int lsk_newPaint(lua_State* L) {
+ push_new<SkPaint>(L);
+ return 1;
+}
+
+static int lsk_newPath(lua_State* L) {
+ push_new<SkPath>(L);
+ return 1;
+}
+
+static int lsk_newRRect(lua_State* L) {
+ SkRRect* rr = push_new<SkRRect>(L);
+ rr->setEmpty();
+ return 1;
+}
+
+static int lsk_newTypeface(lua_State* L) {
+ const char* name = NULL;
+ int style = SkTypeface::kNormal;
+
+ int count = lua_gettop(L);
+ if (count > 0 && lua_isstring(L, 1)) {
+ name = lua_tolstring(L, 1, NULL);
+ if (count > 1 && lua_isnumber(L, 2)) {
+ style = lua_tointegerx(L, 2, NULL) & SkTypeface::kBoldItalic;
+ }
+ }
+
+ SkTypeface* face = SkTypeface::CreateFromName(name,
+ (SkTypeface::Style)style);
+// SkDebugf("---- name <%s> style=%d, face=%p ref=%d\n", name, style, face, face->getRefCnt());
+ if (NULL == face) {
+ face = SkTypeface::RefDefault();
+ }
+ push_ref(L, face);
+ face->unref();
+ return 1;
+}
+
+static int lsk_loadImage(lua_State* L) {
+ if (lua_gettop(L) > 0 && lua_isstring(L, 1)) {
+ const char* name = lua_tolstring(L, 1, NULL);
+ SkAutoDataUnref data(SkData::NewFromFileName(name));
+ if (data.get()) {
+ SkImage* image = SkImage::NewEncodedData(data.get());
+ if (image) {
+ push_ref(L, image);
+ image->unref();
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void register_Sk(lua_State* L) {
+ lua_newtable(L);
+ lua_pushvalue(L, -1);
+ lua_setglobal(L, "Sk");
+ // the Sk table is still on top
+
+ setfield_function(L, "newDocumentPDF", lsk_newDocumentPDF);
+ setfield_function(L, "loadImage", lsk_loadImage);
+ setfield_function(L, "newPaint", lsk_newPaint);
+ setfield_function(L, "newPath", lsk_newPath);
+ setfield_function(L, "newRRect", lsk_newRRect);
+ setfield_function(L, "newTypeface", lsk_newTypeface);
+ lua_pop(L, 1); // pop off the Sk table
+}
+
+#define REG_CLASS(L, C) \
+ do { \
+ luaL_newmetatable(L, get_mtname<C>()); \
+ lua_pushvalue(L, -1); \
+ lua_setfield(L, -2, "__index"); \
+ luaL_setfuncs(L, g##C##_Methods, 0); \
+ lua_pop(L, 1); /* pop off the meta-table */ \
+ } while (0)
+
+void SkLua::Load(lua_State* L) {
+ register_Sk(L);
+ REG_CLASS(L, SkCanvas);
+ REG_CLASS(L, SkDocument);
+ REG_CLASS(L, SkImage);
+ REG_CLASS(L, SkPath);
+ REG_CLASS(L, SkPaint);
+ REG_CLASS(L, SkRRect);
+ REG_CLASS(L, SkShader);
+ REG_CLASS(L, SkTypeface);
+ REG_CLASS(L, SkMatrix);
+}
+
+extern "C" int luaopen_skia(lua_State* L);
+extern "C" int luaopen_skia(lua_State* L) {
+ SkLua::Load(L);
+ return 0;
+}
diff --git a/utils/SkLuaCanvas.cpp b/utils/SkLuaCanvas.cpp
new file mode 100644
index 00000000..1299a0e2
--- /dev/null
+++ b/utils/SkLuaCanvas.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLuaCanvas.h"
+#include "SkLua.h"
+
+extern "C" {
+ #include "lua.h"
+ #include "lauxlib.h"
+}
+
+class AutoCallLua : public SkLua {
+public:
+ AutoCallLua(lua_State* L, const char func[], const char verb[]) : INHERITED(L) {
+ lua_getglobal(L, func);
+ if (!lua_isfunction(L, -1)) {
+ int t = lua_type(L, -1);
+ SkDebugf("--- expected function %d\n", t);
+ }
+
+ lua_newtable(L);
+ this->pushString(verb, "verb");
+ }
+
+ ~AutoCallLua() {
+ lua_State* L = this->get();
+ if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
+ SkDebugf("lua err: %s\n", lua_tostring(L, -1));
+ }
+ lua_settop(L, -1);
+ }
+
+ void pushEncodedText(SkPaint::TextEncoding, const void*, size_t);
+
+private:
+ typedef SkLua INHERITED;
+};
+
+#define AUTO_LUA(verb) AutoCallLua lua(fL, fFunc.c_str(), verb)
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AutoCallLua::pushEncodedText(SkPaint::TextEncoding enc, const void* text,
+ size_t length) {
+ switch (enc) {
+ case SkPaint::kUTF8_TextEncoding:
+ this->pushString((const char*)text, length, "text");
+ break;
+ case SkPaint::kUTF16_TextEncoding: {
+ SkString str;
+ str.setUTF16((const uint16_t*)text, length);
+ this->pushString(str, "text");
+ } break;
+ case SkPaint::kGlyphID_TextEncoding:
+ this->pushArrayU16((const uint16_t*)text, length >> 1, "glyphs");
+ break;
+ case SkPaint::kUTF32_TextEncoding:
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkLuaCanvas::pushThis() {
+ SkLua(fL).pushCanvas(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkBitmap make_bm(int width, int height) {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kNo_Config, width, height);
+ return bm;
+}
+
+SkLuaCanvas::SkLuaCanvas(int width, int height, lua_State* L, const char func[])
+ : INHERITED(make_bm(width, height))
+ , fL(L)
+ , fFunc(func) {
+}
+
+SkLuaCanvas::~SkLuaCanvas() {}
+
+int SkLuaCanvas::save(SaveFlags flags) {
+ AUTO_LUA("save");
+ return this->INHERITED::save(flags);
+}
+
+int SkLuaCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ AUTO_LUA("saveLayer");
+ if (bounds) {
+ lua.pushRect(*bounds, "bounds");
+ }
+ if (paint) {
+ lua.pushPaint(*paint, "paint");
+ }
+ return this->INHERITED::save(flags);
+}
+
+void SkLuaCanvas::restore() {
+ AUTO_LUA("restore");
+ this->INHERITED::restore();
+}
+
+bool SkLuaCanvas::translate(SkScalar dx, SkScalar dy) {
+ AUTO_LUA("translate");
+ lua.pushScalar(dx, "dx");
+ lua.pushScalar(dy, "dy");
+ return this->INHERITED::translate(dx, dy);
+}
+
+bool SkLuaCanvas::scale(SkScalar sx, SkScalar sy) {
+ AUTO_LUA("scale");
+ lua.pushScalar(sx, "sx");
+ lua.pushScalar(sy, "sy");
+ return this->INHERITED::scale(sx, sy);
+}
+
+bool SkLuaCanvas::rotate(SkScalar degrees) {
+ AUTO_LUA("rotate");
+ lua.pushScalar(degrees, "degrees");
+ return this->INHERITED::rotate(degrees);
+}
+
+bool SkLuaCanvas::skew(SkScalar kx, SkScalar ky) {
+ AUTO_LUA("skew");
+ lua.pushScalar(kx, "kx");
+ lua.pushScalar(ky, "ky");
+ return this->INHERITED::skew(kx, ky);
+}
+
+bool SkLuaCanvas::concat(const SkMatrix& matrix) {
+ AUTO_LUA("concat");
+ return this->INHERITED::concat(matrix);
+}
+
+void SkLuaCanvas::setMatrix(const SkMatrix& matrix) {
+ this->INHERITED::setMatrix(matrix);
+}
+
+bool SkLuaCanvas::clipRect(const SkRect& r, SkRegion::Op op, bool doAA) {
+ AUTO_LUA("clipRect");
+ lua.pushRect(r, "rect");
+ lua.pushBool(doAA, "aa");
+ return this->INHERITED::clipRect(r, op, doAA);
+}
+
+bool SkLuaCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ AUTO_LUA("clipRRect");
+ lua.pushRRect(rrect, "rrect");
+ lua.pushBool(doAA, "aa");
+ return this->INHERITED::clipRRect(rrect, op, doAA);
+}
+
+bool SkLuaCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
+ AUTO_LUA("clipPath");
+ lua.pushPath(path, "path");
+ lua.pushBool(doAA, "aa");
+ return this->INHERITED::clipPath(path, op, doAA);
+}
+
+bool SkLuaCanvas::clipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+ AUTO_LUA("clipRegion");
+ return this->INHERITED::clipRegion(deviceRgn, op);
+}
+
+void SkLuaCanvas::drawPaint(const SkPaint& paint) {
+ AUTO_LUA("drawPaint");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ AUTO_LUA("drawPoints");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawOval(const SkRect& rect, const SkPaint& paint) {
+ AUTO_LUA("drawOval");
+ lua.pushRect(rect, "rect");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
+ AUTO_LUA("drawRect");
+ lua.pushRect(rect, "rect");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ AUTO_LUA("drawRRect");
+ lua.pushRRect(rrect, "rrect");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ AUTO_LUA("drawPath");
+ lua.pushPath(path, "path");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ AUTO_LUA("drawBitmap");
+ if (paint) {
+ lua.pushPaint(*paint, "paint");
+ }
+}
+
+void SkLuaCanvas::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ AUTO_LUA("drawBitmapRectToRect");
+ if (paint) {
+ lua.pushPaint(*paint, "paint");
+ }
+}
+
+void SkLuaCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m,
+ const SkPaint* paint) {
+ AUTO_LUA("drawBitmapMatrix");
+ if (paint) {
+ lua.pushPaint(*paint, "paint");
+ }
+}
+
+void SkLuaCanvas::drawSprite(const SkBitmap& bitmap, int x, int y,
+ const SkPaint* paint) {
+ AUTO_LUA("drawSprite");
+ if (paint) {
+ lua.pushPaint(*paint, "paint");
+ }
+}
+
+void SkLuaCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ AUTO_LUA("drawText");
+ lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ AUTO_LUA("drawPosText");
+ lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ AUTO_LUA("drawPosTextH");
+ lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ AUTO_LUA("drawTextOnPath");
+ lua.pushPath(path, "path");
+ lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawPicture(SkPicture& picture) {
+ AUTO_LUA("drawPicture");
+ // call through so we can see the nested picture ops
+ this->INHERITED::drawPicture(picture);
+}
+
+void SkLuaCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ AUTO_LUA("drawVertices");
+ lua.pushPaint(paint, "paint");
+}
+
+void SkLuaCanvas::drawData(const void* data, size_t length) {
+ AUTO_LUA("drawData");
+}
diff --git a/utils/SkMD5.cpp b/utils/SkMD5.cpp
new file mode 100644
index 00000000..725ae55f
--- /dev/null
+++ b/utils/SkMD5.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The following code is based on the description in RFC 1321.
+ * http://www.ietf.org/rfc/rfc1321.txt
+ */
+
+#include "SkTypes.h"
+#include "SkMD5.h"
+#include <string.h>
+
+/** MD5 basic transformation. Transforms state based on block. */
+static void transform(uint32_t state[4], const uint8_t block[64]);
+
+/** Encodes input into output (4 little endian 32 bit values). */
+static void encode(uint8_t output[16], const uint32_t input[4]);
+
+/** Encodes input into output (little endian 64 bit value). */
+static void encode(uint8_t output[8], const uint64_t input);
+
+/** Decodes input (4 little endian 32 bit values) into storage, if required. */
+static const uint32_t* decode(uint32_t storage[16], const uint8_t input[64]);
+
+SkMD5::SkMD5() : byteCount(0) {
+ // These are magic numbers from the specification.
+ this->state[0] = 0x67452301;
+ this->state[1] = 0xefcdab89;
+ this->state[2] = 0x98badcfe;
+ this->state[3] = 0x10325476;
+}
+
+void SkMD5::update(const uint8_t* input, size_t inputLength) {
+ unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F);
+ unsigned int bufferAvailable = 64 - bufferIndex;
+
+ unsigned int inputIndex;
+ if (inputLength >= bufferAvailable) {
+ if (bufferIndex) {
+ memcpy(&this->buffer[bufferIndex], input, bufferAvailable);
+ transform(this->state, this->buffer);
+ inputIndex = bufferAvailable;
+ } else {
+ inputIndex = 0;
+ }
+
+ for (; inputIndex + 63 < inputLength; inputIndex += 64) {
+ transform(this->state, &input[inputIndex]);
+ }
+
+ bufferIndex = 0;
+ } else {
+ inputIndex = 0;
+ }
+
+ memcpy(&this->buffer[bufferIndex], &input[inputIndex], inputLength - inputIndex);
+
+ this->byteCount += inputLength;
+}
+
+void SkMD5::finish(Digest& digest) {
+ // Get the number of bits before padding.
+ uint8_t bits[8];
+ encode(bits, this->byteCount << 3);
+
+ // Pad out to 56 mod 64.
+ unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F);
+ unsigned int paddingLength = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
+ static uint8_t PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ this->update(PADDING, paddingLength);
+
+ // Append length (length before padding, will cause final update).
+ this->update(bits, 8);
+
+ // Write out digest.
+ encode(digest.data, this->state);
+
+#if defined(SK_MD5_CLEAR_DATA)
+ // Clear state.
+ memset(this, 0, sizeof(*this));
+#endif
+}
+
+struct F { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) {
+ //return (x & y) | ((~x) & z);
+ return ((y ^ z) & x) ^ z; //equivelent but faster
+}};
+
+struct G { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) {
+ return (x & z) | (y & (~z));
+ //return ((x ^ y) & z) ^ y; //equivelent but slower
+}};
+
+struct H { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) {
+ return x ^ y ^ z;
+}};
+
+struct I { uint32_t operator()(uint32_t x, uint32_t y, uint32_t z) {
+ return y ^ (x | (~z));
+}};
+
+/** Rotates x left n bits. */
+static inline uint32_t rotate_left(uint32_t x, uint8_t n) {
+ return (x << n) | (x >> (32 - n));
+}
+
+template <typename T>
+static inline void operation(T operation, uint32_t& a, uint32_t b, uint32_t c, uint32_t d,
+ uint32_t x, uint8_t s, uint32_t t) {
+ a = b + rotate_left(a + operation(b, c, d) + x + t, s);
+}
+
+static void transform(uint32_t state[4], const uint8_t block[64]) {
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
+
+ uint32_t storage[16];
+ const uint32_t* X = decode(storage, block);
+
+ // Round 1
+ operation(F(), a, b, c, d, X[ 0], 7, 0xd76aa478); // 1
+ operation(F(), d, a, b, c, X[ 1], 12, 0xe8c7b756); // 2
+ operation(F(), c, d, a, b, X[ 2], 17, 0x242070db); // 3
+ operation(F(), b, c, d, a, X[ 3], 22, 0xc1bdceee); // 4
+ operation(F(), a, b, c, d, X[ 4], 7, 0xf57c0faf); // 5
+ operation(F(), d, a, b, c, X[ 5], 12, 0x4787c62a); // 6
+ operation(F(), c, d, a, b, X[ 6], 17, 0xa8304613); // 7
+ operation(F(), b, c, d, a, X[ 7], 22, 0xfd469501); // 8
+ operation(F(), a, b, c, d, X[ 8], 7, 0x698098d8); // 9
+ operation(F(), d, a, b, c, X[ 9], 12, 0x8b44f7af); // 10
+ operation(F(), c, d, a, b, X[10], 17, 0xffff5bb1); // 11
+ operation(F(), b, c, d, a, X[11], 22, 0x895cd7be); // 12
+ operation(F(), a, b, c, d, X[12], 7, 0x6b901122); // 13
+ operation(F(), d, a, b, c, X[13], 12, 0xfd987193); // 14
+ operation(F(), c, d, a, b, X[14], 17, 0xa679438e); // 15
+ operation(F(), b, c, d, a, X[15], 22, 0x49b40821); // 16
+
+ // Round 2
+ operation(G(), a, b, c, d, X[ 1], 5, 0xf61e2562); // 17
+ operation(G(), d, a, b, c, X[ 6], 9, 0xc040b340); // 18
+ operation(G(), c, d, a, b, X[11], 14, 0x265e5a51); // 19
+ operation(G(), b, c, d, a, X[ 0], 20, 0xe9b6c7aa); // 20
+ operation(G(), a, b, c, d, X[ 5], 5, 0xd62f105d); // 21
+ operation(G(), d, a, b, c, X[10], 9, 0x2441453); // 22
+ operation(G(), c, d, a, b, X[15], 14, 0xd8a1e681); // 23
+ operation(G(), b, c, d, a, X[ 4], 20, 0xe7d3fbc8); // 24
+ operation(G(), a, b, c, d, X[ 9], 5, 0x21e1cde6); // 25
+ operation(G(), d, a, b, c, X[14], 9, 0xc33707d6); // 26
+ operation(G(), c, d, a, b, X[ 3], 14, 0xf4d50d87); // 27
+ operation(G(), b, c, d, a, X[ 8], 20, 0x455a14ed); // 28
+ operation(G(), a, b, c, d, X[13], 5, 0xa9e3e905); // 29
+ operation(G(), d, a, b, c, X[ 2], 9, 0xfcefa3f8); // 30
+ operation(G(), c, d, a, b, X[ 7], 14, 0x676f02d9); // 31
+ operation(G(), b, c, d, a, X[12], 20, 0x8d2a4c8a); // 32
+
+ // Round 3
+ operation(H(), a, b, c, d, X[ 5], 4, 0xfffa3942); // 33
+ operation(H(), d, a, b, c, X[ 8], 11, 0x8771f681); // 34
+ operation(H(), c, d, a, b, X[11], 16, 0x6d9d6122); // 35
+ operation(H(), b, c, d, a, X[14], 23, 0xfde5380c); // 36
+ operation(H(), a, b, c, d, X[ 1], 4, 0xa4beea44); // 37
+ operation(H(), d, a, b, c, X[ 4], 11, 0x4bdecfa9); // 38
+ operation(H(), c, d, a, b, X[ 7], 16, 0xf6bb4b60); // 39
+ operation(H(), b, c, d, a, X[10], 23, 0xbebfbc70); // 40
+ operation(H(), a, b, c, d, X[13], 4, 0x289b7ec6); // 41
+ operation(H(), d, a, b, c, X[ 0], 11, 0xeaa127fa); // 42
+ operation(H(), c, d, a, b, X[ 3], 16, 0xd4ef3085); // 43
+ operation(H(), b, c, d, a, X[ 6], 23, 0x4881d05); // 44
+ operation(H(), a, b, c, d, X[ 9], 4, 0xd9d4d039); // 45
+ operation(H(), d, a, b, c, X[12], 11, 0xe6db99e5); // 46
+ operation(H(), c, d, a, b, X[15], 16, 0x1fa27cf8); // 47
+ operation(H(), b, c, d, a, X[ 2], 23, 0xc4ac5665); // 48
+
+ // Round 4
+ operation(I(), a, b, c, d, X[ 0], 6, 0xf4292244); // 49
+ operation(I(), d, a, b, c, X[ 7], 10, 0x432aff97); // 50
+ operation(I(), c, d, a, b, X[14], 15, 0xab9423a7); // 51
+ operation(I(), b, c, d, a, X[ 5], 21, 0xfc93a039); // 52
+ operation(I(), a, b, c, d, X[12], 6, 0x655b59c3); // 53
+ operation(I(), d, a, b, c, X[ 3], 10, 0x8f0ccc92); // 54
+ operation(I(), c, d, a, b, X[10], 15, 0xffeff47d); // 55
+ operation(I(), b, c, d, a, X[ 1], 21, 0x85845dd1); // 56
+ operation(I(), a, b, c, d, X[ 8], 6, 0x6fa87e4f); // 57
+ operation(I(), d, a, b, c, X[15], 10, 0xfe2ce6e0); // 58
+ operation(I(), c, d, a, b, X[ 6], 15, 0xa3014314); // 59
+ operation(I(), b, c, d, a, X[13], 21, 0x4e0811a1); // 60
+ operation(I(), a, b, c, d, X[ 4], 6, 0xf7537e82); // 61
+ operation(I(), d, a, b, c, X[11], 10, 0xbd3af235); // 62
+ operation(I(), c, d, a, b, X[ 2], 15, 0x2ad7d2bb); // 63
+ operation(I(), b, c, d, a, X[ 9], 21, 0xeb86d391); // 64
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+#if defined(SK_MD5_CLEAR_DATA)
+ // Clear sensitive information.
+ if (X == &storage) {
+ memset(storage, 0, sizeof(storage));
+ }
+#endif
+}
+
+static void encode(uint8_t output[16], const uint32_t input[4]) {
+ for (size_t i = 0, j = 0; i < 4; i++, j += 4) {
+ output[j ] = (uint8_t) (input[i] & 0xff);
+ output[j+1] = (uint8_t)((input[i] >> 8) & 0xff);
+ output[j+2] = (uint8_t)((input[i] >> 16) & 0xff);
+ output[j+3] = (uint8_t)((input[i] >> 24) & 0xff);
+ }
+}
+
+static void encode(uint8_t output[8], const uint64_t input) {
+ output[0] = (uint8_t) (input & 0xff);
+ output[1] = (uint8_t)((input >> 8) & 0xff);
+ output[2] = (uint8_t)((input >> 16) & 0xff);
+ output[3] = (uint8_t)((input >> 24) & 0xff);
+ output[4] = (uint8_t)((input >> 32) & 0xff);
+ output[5] = (uint8_t)((input >> 40) & 0xff);
+ output[6] = (uint8_t)((input >> 48) & 0xff);
+ output[7] = (uint8_t)((input >> 56) & 0xff);
+}
+
+static inline bool is_aligned(const void *pointer, size_t byte_count) {
+ return reinterpret_cast<uintptr_t>(pointer) % byte_count == 0;
+}
+
+static const uint32_t* decode(uint32_t storage[16], const uint8_t input[64]) {
+#if defined(SK_CPU_LENDIAN) && defined(SK_CPU_FAST_UNALIGNED_ACCESS)
+ return reinterpret_cast<const uint32_t*>(input);
+#else
+#if defined(SK_CPU_LENDIAN)
+ if (is_aligned(input, 4)) {
+ return reinterpret_cast<const uint32_t*>(input);
+ }
+#endif
+ for (size_t i = 0, j = 0; j < 64; i++, j += 4) {
+ storage[i] = ((uint32_t)input[j ]) |
+ (((uint32_t)input[j+1]) << 8) |
+ (((uint32_t)input[j+2]) << 16) |
+ (((uint32_t)input[j+3]) << 24);
+ }
+ return storage;
+#endif
+}
diff --git a/utils/SkMD5.h b/utils/SkMD5.h
new file mode 100644
index 00000000..6b4fc536
--- /dev/null
+++ b/utils/SkMD5.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 SkMD5_DEFINED
+#define SkMD5_DEFINED
+
+#include "SkTypes.h"
+#include "SkEndian.h"
+#include "SkStream.h"
+
+//The following macros can be defined to affect the MD5 code generated.
+//SK_MD5_CLEAR_DATA causes all intermediate state to be overwritten with 0's.
+//SK_CPU_LENDIAN allows 32 bit <=> 8 bit conversions without copies (if alligned).
+//SK_CPU_FAST_UNALIGNED_ACCESS allows 32 bit <=> 8 bit conversions without copies if SK_CPU_LENDIAN.
+
+class SkMD5 : public SkWStream {
+public:
+ SkMD5();
+
+ /** Processes input, adding it to the digest.
+ * Note that this treats the buffer as a series of uint8_t values.
+ */
+ virtual bool write(const void* buffer, size_t size) SK_OVERRIDE {
+ update(reinterpret_cast<const uint8_t*>(buffer), size);
+ return true;
+ }
+
+ /** Processes input, adding it to the digest. Calling this after finish is undefined. */
+ void update(const uint8_t* input, size_t length);
+
+ struct Digest {
+ uint8_t data[16];
+ };
+
+ /** Computes and returns the digest. */
+ void finish(Digest& digest);
+
+private:
+ // number of bytes, modulo 2^64
+ uint64_t byteCount;
+
+ // state (ABCD)
+ uint32_t state[4];
+
+ // input buffer
+ uint8_t buffer[64];
+};
+
+#endif
diff --git a/utils/SkMatrix44.cpp b/utils/SkMatrix44.cpp
new file mode 100644
index 00000000..92c8715f
--- /dev/null
+++ b/utils/SkMatrix44.cpp
@@ -0,0 +1,854 @@
+/*
+ * 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 "SkMatrix44.h"
+
+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
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix44::asColMajorf(float dst[]) const {
+ const SkMScalar* src = &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
+}
+
+void SkMatrix44::asColMajord(double dst[]) const {
+ const SkMScalar* src = &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] = SkMScalarToDouble(src[i]);
+ }
+#endif
+}
+
+void SkMatrix44::asRowMajorf(float dst[]) const {
+ const SkMScalar* src = &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;
+ }
+}
+
+void SkMatrix44::asRowMajord(double dst[]) const {
+ const SkMScalar* src = &fMat[0][0];
+ for (int i = 0; i < 4; ++i) {
+ dst[0] = SkMScalarToDouble(src[0]);
+ dst[4] = SkMScalarToDouble(src[1]);
+ dst[8] = SkMScalarToDouble(src[2]);
+ dst[12] = SkMScalarToDouble(src[3]);
+ src += 4;
+ dst += 1;
+ }
+}
+
+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
+
+ 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) {
+ 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 dx, SkMScalar dy, SkMScalar dz) {
+ this->setIdentity();
+
+ 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) {
+ 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) {
+ 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) {
+ this->setIdentity();
+
+ if (1 == sx && 1 == sy && 1 == sz) {
+ return;
+ }
+
+ fMat[0][0] = sx;
+ fMat[1][1] = sy;
+ fMat[2][2] = sz;
+ this->setTypeMask(kScale_Mask);
+}
+
+void SkMatrix44::preScale(SkMScalar sx, SkMScalar sy, SkMScalar sz) {
+ 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 = (double)x * x + (double)y * y + (double)z * z;
+ if (1 != len2) {
+ if (0 == len2) {
+ this->setIdentity();
+ return;
+ }
+ double scale = 1 / sqrt(len2);
+ x = SkDoubleToMScalar(x * scale);
+ y = SkDoubleToMScalar(y * scale);
+ z = SkDoubleToMScalar(z * scale);
+ }
+ this->setRotateAboutUnit(x, y, z, radians);
+}
+
+void SkMatrix44::setRotateAboutUnit(SkMScalar x, SkMScalar y, SkMScalar z,
+ SkMScalar radians) {
+ double c = cos(radians);
+ double s = sin(radians);
+ double C = 1 - c;
+ double xs = x * s;
+ double ys = y * s;
+ double zs = z * s;
+ double xC = x * C;
+ double yC = y * C;
+ double zC = z * C;
+ double xyC = x * yC;
+ double yzC = y * zC;
+ double zxC = z * xC;
+
+ // if you're looking at wikipedia, remember that we're column major.
+ this->set3x3(SkDoubleToMScalar(x * xC + c), // scale x
+ SkDoubleToMScalar(xyC + zs), // skew x
+ SkDoubleToMScalar(zxC - ys), // trans x
+
+ SkDoubleToMScalar(xyC - zs), // skew y
+ SkDoubleToMScalar(y * yC + c), // scale y
+ SkDoubleToMScalar(yzC + xs), // trans y
+
+ SkDoubleToMScalar(zxC + ys), // persp x
+ SkDoubleToMScalar(yzC - xs), // persp y
+ SkDoubleToMScalar(z * zC + c)); // persp 2
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool bits_isonly(int value, int mask) {
+ return 0 == (value & ~mask);
+}
+
+void SkMatrix44::setConcat(const SkMatrix44& a, const SkMatrix44& b) {
+ 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];
+
+ // Both matrices are at most scale+translate
+ if (bits_isonly(a_mask | b_mask, kScale_Mask | kTranslate_Mask)) {
+ result[0] = a.fMat[0][0] * b.fMat[0][0];
+ result[1] = result[2] = result[3] = result[4] = 0;
+ result[5] = a.fMat[1][1] * b.fMat[1][1];
+ result[6] = result[7] = result[8] = result[9] = 0;
+ result[10] = a.fMat[2][2] * b.fMat[2][2];
+ result[11] = 0;
+ 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++) {
+ 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);
+ }
+ }
+ }
+
+ if (useStorage) {
+ memcpy(fMat, storage, sizeof(storage));
+ }
+ this->dirtyTypeMask();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline SkMScalar det2x2(double m00, double m01, double m10, double m11) {
+ return SkDoubleToMScalar(m00 * m11 - m10 * m01);
+}
+
+static inline double det3x3(double m00, double m01, double m02,
+ double m10, double m11, double m12,
+ double m20, double m21, double m22) {
+ return m00 * det2x2(m11, m12, m21, m22) -
+ m10 * det2x2(m01, m02, m21, m22) +
+ m20 * det2x2(m01, m02, m11, m12);
+}
+
+/** We always perform the calculation in doubles, to avoid prematurely losing
+ precision along the way. This relies on the compiler automatically
+ promoting our SkMScalar values to double (if needed).
+ */
+double SkMatrix44::determinant() const {
+ 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;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// just picked a small value. not sure how to pick the "right" one
+#define TOO_SMALL_FOR_DETERMINANT (1.e-8)
+
+static inline double dabs(double x) {
+ if (x < 0) {
+ x = -x;
+ }
+ return x;
+}
+
+bool SkMatrix44::invert(SkMatrix44* inverse) const {
+ 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;
+
+ 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);
+ }
+
+ if (storage == result) {
+ memcpy(dst, storage, sizeof(storage));
+ }
+}
+
+#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;
+
+ 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] = 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;
+ }
+}
+
+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);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMatrix44::dump() const {
+ static const char* format =
+ "[%g %g %g %g][%g %g %g %g][%g %g %g %g][%g %g %g %g]\n";
+#if 0
+ SkDebugf(format,
+ fMat[0][0], fMat[1][0], fMat[2][0], fMat[3][0],
+ fMat[0][1], fMat[1][1], fMat[2][1], fMat[3][1],
+ fMat[0][2], fMat[1][2], fMat[2][2], fMat[3][2],
+ fMat[0][3], fMat[1][3], fMat[2][3], fMat[3][3]);
+#else
+ SkDebugf(format,
+ fMat[0][0], fMat[0][1], fMat[0][2], fMat[0][3],
+ fMat[1][0], fMat[1][1], fMat[1][2], fMat[1][3],
+ fMat[2][0], fMat[2][1], fMat[2][2], fMat[2][3],
+ fMat[3][0], fMat[3][1], fMat[3][2], fMat[3][3]);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// 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]);
+ dst[1][0] = SkScalarToMScalar(src[SkMatrix::kMSkewX]);
+ dst[3][0] = SkScalarToMScalar(src[SkMatrix::kMTransX]);
+ dst[0][1] = SkScalarToMScalar(src[SkMatrix::kMSkewY]);
+ dst[1][1] = SkScalarToMScalar(src[SkMatrix::kMScaleY]);
+ dst[3][1] = SkScalarToMScalar(src[SkMatrix::kMTransY]);
+ dst[2][2] = dst[3][3] = 1;
+}
+
+SkMatrix44::SkMatrix44(const SkMatrix& src) {
+ initFromMatrix(fMat, 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
+
+ dst[SkMatrix::kMScaleX] = SkMScalarToScalar(fMat[0][0]);
+ dst[SkMatrix::kMSkewX] = SkMScalarToScalar(fMat[1][0]);
+ dst[SkMatrix::kMTransX] = SkMScalarToScalar(fMat[3][0]);
+
+ dst[SkMatrix::kMSkewY] = SkMScalarToScalar(fMat[0][1]);
+ dst[SkMatrix::kMScaleY] = SkMScalarToScalar(fMat[1][1]);
+ dst[SkMatrix::kMTransY] = SkMScalarToScalar(fMat[3][1]);
+
+ return dst;
+}
diff --git a/utils/SkMeshUtils.cpp b/utils/SkMeshUtils.cpp
new file mode 100644
index 00000000..3857dc95
--- /dev/null
+++ b/utils/SkMeshUtils.cpp
@@ -0,0 +1,102 @@
+
+/*
+ * 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 "SkMeshUtils.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+SkMeshIndices::SkMeshIndices() {
+ sk_bzero(this, sizeof(*this));
+}
+
+SkMeshIndices::~SkMeshIndices() {
+ sk_free(fStorage);
+}
+
+bool SkMeshIndices::init(SkPoint tex[], uint16_t indices[],
+ int texW, int texH, int rows, int cols) {
+ if (rows < 2 || cols < 2) {
+ sk_free(fStorage);
+ fStorage = NULL;
+ fTex = NULL;
+ fIndices = NULL;
+ fTexCount = fIndexCount = 0;
+ return false;
+ }
+
+ sk_free(fStorage);
+ fStorage = NULL;
+
+ fTexCount = rows * cols;
+ rows -= 1;
+ cols -= 1;
+ fIndexCount = rows * cols * 6;
+
+ if (tex) {
+ fTex = tex;
+ fIndices = indices;
+ } else {
+ fStorage = sk_malloc_throw(fTexCount * sizeof(SkPoint) +
+ fIndexCount * sizeof(uint16_t));
+ fTex = (SkPoint*)fStorage;
+ fIndices = (uint16_t*)(fTex + fTexCount);
+ }
+
+ // compute the indices
+ {
+ uint16_t* idx = fIndices;
+ int index = 0;
+ for (int y = 0; y < cols; y++) {
+ for (int x = 0; x < rows; x++) {
+ *idx++ = index;
+ *idx++ = index + rows + 1;
+ *idx++ = index + 1;
+
+ *idx++ = index + 1;
+ *idx++ = index + rows + 1;
+ *idx++ = index + rows + 2;
+
+ index += 1;
+ }
+ index += 1;
+ }
+ }
+
+ // compute texture coordinates
+ {
+ SkPoint* tex = fTex;
+ const SkScalar dx = SkIntToScalar(texW) / rows;
+ const SkScalar dy = SkIntToScalar(texH) / cols;
+ for (int y = 0; y <= cols; y++) {
+ for (int x = 0; x <= rows; x++) {
+ tex->set(x*dx, y*dy);
+ tex += 1;
+ }
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkShader.h"
+
+void SkMeshUtils::Draw(SkCanvas* canvas, const SkBitmap& bitmap,
+ int rows, int cols, const SkPoint verts[],
+ const SkColor colors[], const SkPaint& paint) {
+ SkMeshIndices idx;
+
+ if (idx.init(bitmap.width(), bitmap.height(), rows, cols)) {
+ SkPaint p(paint);
+ p.setShader(SkShader::CreateBitmapShader(bitmap,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode))->unref();
+ canvas->drawVertices(SkCanvas::kTriangles_VertexMode,
+ rows * cols, verts, idx.tex(), colors, NULL,
+ idx.indices(), idx.indexCount(), p);
+ }
+}
diff --git a/utils/SkNWayCanvas.cpp b/utils/SkNWayCanvas.cpp
new file mode 100644
index 00000000..5eebd01f
--- /dev/null
+++ b/utils/SkNWayCanvas.cpp
@@ -0,0 +1,334 @@
+
+/*
+ * 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 "SkNWayCanvas.h"
+
+static SkBitmap make_noconfig_bm(int width, int height) {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kNo_Config, width, height);
+ return bm;
+}
+
+SkNWayCanvas::SkNWayCanvas(int width, int height)
+ : INHERITED(make_noconfig_bm(width, height)) {}
+
+SkNWayCanvas::~SkNWayCanvas() {
+ this->removeAll();
+}
+
+void SkNWayCanvas::addCanvas(SkCanvas* canvas) {
+ if (canvas) {
+ canvas->ref();
+ *fList.append() = canvas;
+ }
+}
+
+void SkNWayCanvas::removeCanvas(SkCanvas* canvas) {
+ int index = fList.find(canvas);
+ if (index >= 0) {
+ canvas->unref();
+ fList.removeShuffle(index);
+ }
+}
+
+void SkNWayCanvas::removeAll() {
+ fList.unrefAll();
+ fList.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////
+// These are forwarded to the N canvases we're referencing
+
+class SkNWayCanvas::Iter {
+public:
+ Iter(const SkTDArray<SkCanvas*>& list) : fList(list) {
+ fIndex = 0;
+ }
+ bool next() {
+ if (fIndex < fList.count()) {
+ fCanvas = fList[fIndex++];
+ return true;
+ }
+ return false;
+ }
+ SkCanvas* operator->() { return fCanvas; }
+
+private:
+ const SkTDArray<SkCanvas*>& fList;
+ int fIndex;
+ SkCanvas* fCanvas;
+};
+
+int SkNWayCanvas::save(SaveFlags flags) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->save(flags);
+ }
+ return this->INHERITED::save(flags);
+}
+
+int SkNWayCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->saveLayer(bounds, paint, flags);
+ }
+ return this->INHERITED::saveLayer(bounds, paint, flags);
+}
+
+void SkNWayCanvas::restore() {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->restore();
+ }
+ this->INHERITED::restore();
+}
+
+bool SkNWayCanvas::translate(SkScalar dx, SkScalar dy) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->translate(dx, dy);
+ }
+ return this->INHERITED::translate(dx, dy);
+}
+
+bool SkNWayCanvas::scale(SkScalar sx, SkScalar sy) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->scale(sx, sy);
+ }
+ return this->INHERITED::scale(sx, sy);
+}
+
+bool SkNWayCanvas::rotate(SkScalar degrees) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->rotate(degrees);
+ }
+ return this->INHERITED::rotate(degrees);
+}
+
+bool SkNWayCanvas::skew(SkScalar sx, SkScalar sy) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->skew(sx, sy);
+ }
+ return this->INHERITED::skew(sx, sy);
+}
+
+bool SkNWayCanvas::concat(const SkMatrix& matrix) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->concat(matrix);
+ }
+ return this->INHERITED::concat(matrix);
+}
+
+void SkNWayCanvas::setMatrix(const SkMatrix& matrix) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->setMatrix(matrix);
+ }
+ this->INHERITED::setMatrix(matrix);
+}
+
+bool SkNWayCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->clipRect(rect, op, 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()) {
+ iter->clipPath(path, op, doAA);
+ }
+ return this->INHERITED::clipPath(path, op, doAA);
+}
+
+bool SkNWayCanvas::clipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->clipRegion(deviceRgn, op);
+ }
+ return this->INHERITED::clipRegion(deviceRgn, op);
+}
+
+void SkNWayCanvas::drawPaint(const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawPaint(paint);
+ }
+}
+
+void SkNWayCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
+ const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawPoints(mode, count, pts, paint);
+ }
+}
+
+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()) {
+ iter->drawRect(rect, 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()) {
+ iter->drawPath(path, paint);
+ }
+}
+
+void SkNWayCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawBitmap(bitmap, x, y, paint);
+ }
+}
+
+void SkNWayCanvas::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawBitmapRectToRect(bitmap, src, dst, paint);
+ }
+}
+
+void SkNWayCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m,
+ const SkPaint* paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawBitmapMatrix(bitmap, m, paint);
+ }
+}
+
+void SkNWayCanvas::drawSprite(const SkBitmap& bitmap, int x, int y,
+ const SkPaint* paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawSprite(bitmap, x, y, paint);
+ }
+}
+
+void SkNWayCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawText(text, byteLength, x, y, paint);
+ }
+}
+
+void SkNWayCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawPosText(text, byteLength, pos, paint);
+ }
+}
+
+void SkNWayCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawPosTextH(text, byteLength, xpos, constY, paint);
+ }
+}
+
+void SkNWayCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawTextOnPath(text, byteLength, path, matrix, paint);
+ }
+}
+
+void SkNWayCanvas::drawPicture(SkPicture& picture) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawPicture(picture);
+ }
+}
+
+void SkNWayCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->drawVertices(vmode, vertexCount, vertices, texs, colors, xmode,
+ indices, indexCount, paint);
+ }
+}
+
+SkBounder* SkNWayCanvas::setBounder(SkBounder* bounder) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->setBounder(bounder);
+ }
+ return this->INHERITED::setBounder(bounder);
+}
+
+SkDrawFilter* SkNWayCanvas::setDrawFilter(SkDrawFilter* filter) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->setDrawFilter(filter);
+ }
+ return this->INHERITED::setDrawFilter(filter);
+}
+
+void SkNWayCanvas::beginCommentGroup(const char* description) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->beginCommentGroup(description);
+ }
+}
+
+void SkNWayCanvas::addComment(const char* kywd, const char* value) {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->addComment(kywd, value);
+ }
+}
+
+void SkNWayCanvas::endCommentGroup() {
+ Iter iter(fList);
+ while (iter.next()) {
+ iter->endCommentGroup();
+ }
+}
diff --git a/utils/SkNinePatch.cpp b/utils/SkNinePatch.cpp
new file mode 100644
index 00000000..9dc8bd26
--- /dev/null
+++ b/utils/SkNinePatch.cpp
@@ -0,0 +1,335 @@
+
+/*
+ * 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 "SkNinePatch.h"
+#include "SkCanvas.h"
+#include "SkShader.h"
+
+static const uint16_t g3x3Indices[] = {
+ 0, 5, 1, 0, 4, 5,
+ 1, 6, 2, 1, 5, 6,
+ 2, 7, 3, 2, 6, 7,
+
+ 4, 9, 5, 4, 8, 9,
+ 5, 10, 6, 5, 9, 10,
+ 6, 11, 7, 6, 10, 11,
+
+ 8, 13, 9, 8, 12, 13,
+ 9, 14, 10, 9, 13, 14,
+ 10, 15, 11, 10, 14, 15
+};
+
+static int fillIndices(uint16_t indices[], int xCount, int yCount) {
+ uint16_t* startIndices = indices;
+
+ int n = 0;
+ for (int y = 0; y < yCount; y++) {
+ for (int x = 0; x < xCount; x++) {
+ *indices++ = n;
+ *indices++ = n + xCount + 2;
+ *indices++ = n + 1;
+
+ *indices++ = n;
+ *indices++ = n + xCount + 1;
+ *indices++ = n + xCount + 2;
+
+ n += 1;
+ }
+ n += 1;
+ }
+ return indices - startIndices;
+}
+
+// Computes the delta between vertices along a single axis
+static SkScalar computeVertexDelta(bool isStretchyVertex,
+ SkScalar currentVertex,
+ SkScalar prevVertex,
+ SkScalar stretchFactor) {
+ // the standard delta between vertices if no stretching is required
+ SkScalar delta = currentVertex - prevVertex;
+
+ // if the stretch factor is negative or zero we need to shrink the 9-patch
+ // to fit within the target bounds. This means that we will eliminate all
+ // stretchy areas and scale the fixed areas to fit within the target bounds.
+ if (stretchFactor <= 0) {
+ if (isStretchyVertex)
+ delta = 0; // collapse stretchable areas
+ else
+ delta = SkScalarMul(delta, -stretchFactor); // scale fixed areas
+ // if the stretch factor is positive then we use the standard delta for
+ // fixed and scale the stretchable areas to fill the target bounds.
+ } else if (isStretchyVertex) {
+ delta = SkScalarMul(delta, stretchFactor);
+ }
+
+ return delta;
+}
+
+static void fillRow(SkPoint verts[], SkPoint texs[],
+ const SkScalar vy, const SkScalar ty,
+ const SkRect& bounds, const int32_t xDivs[], int numXDivs,
+ const SkScalar stretchX, int width) {
+ SkScalar vx = bounds.fLeft;
+ verts->set(vx, vy); verts++;
+ texs->set(0, ty); texs++;
+
+ SkScalar prev = 0;
+ for (int x = 0; x < numXDivs; x++) {
+
+ const SkScalar tx = SkIntToScalar(xDivs[x]);
+ vx += computeVertexDelta(x & 1, tx, prev, stretchX);
+ prev = tx;
+
+ verts->set(vx, vy); verts++;
+ texs->set(tx, ty); texs++;
+ }
+ verts->set(bounds.fRight, vy); verts++;
+ texs->set(SkIntToScalar(width), ty); texs++;
+}
+
+struct Mesh {
+ const SkPoint* fVerts;
+ const SkPoint* fTexs;
+ const SkColor* fColors;
+ const uint16_t* fIndices;
+};
+
+void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds,
+ const SkBitmap& bitmap,
+ const int32_t xDivs[], int numXDivs,
+ const int32_t yDivs[], int numYDivs,
+ const SkPaint* paint) {
+ if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) {
+ return;
+ }
+
+ // should try a quick-reject test before calling lockPixels
+ SkAutoLockPixels alp(bitmap);
+ // after the lock, it is valid to check
+ if (!bitmap.readyToDraw()) {
+ return;
+ }
+
+ // check for degenerate divs (just an optimization, not required)
+ {
+ int i;
+ int zeros = 0;
+ for (i = 0; i < numYDivs && yDivs[i] == 0; i++) {
+ zeros += 1;
+ }
+ numYDivs -= zeros;
+ yDivs += zeros;
+ for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) {
+ numYDivs -= 1;
+ }
+ }
+
+ Mesh mesh;
+
+ const int numXStretch = (numXDivs + 1) >> 1;
+ const int numYStretch = (numYDivs + 1) >> 1;
+
+ if (numXStretch < 1 && numYStretch < 1) {
+ canvas->drawBitmapRect(bitmap, NULL, bounds, paint);
+ return;
+ }
+
+ if (false) {
+ int i;
+ for (i = 0; i < numXDivs; i++) {
+ SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]);
+ }
+ for (i = 0; i < numYDivs; i++) {
+ SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]);
+ }
+ }
+
+ SkScalar stretchX = 0, stretchY = 0;
+
+ if (numXStretch > 0) {
+ int stretchSize = 0;
+ for (int i = 1; i < numXDivs; i += 2) {
+ stretchSize += xDivs[i] - xDivs[i-1];
+ }
+ const SkScalar fixed = SkIntToScalar(bitmap.width() - stretchSize);
+ if (bounds.width() >= fixed)
+ stretchX = (bounds.width() - fixed) / stretchSize;
+ else // reuse stretchX, but keep it negative as a signal
+ stretchX = SkScalarDiv(-bounds.width(), fixed);
+ }
+
+ if (numYStretch > 0) {
+ int stretchSize = 0;
+ for (int i = 1; i < numYDivs; i += 2) {
+ stretchSize += yDivs[i] - yDivs[i-1];
+ }
+ const SkScalar fixed = SkIntToScalar(bitmap.height() - stretchSize);
+ if (bounds.height() >= fixed)
+ stretchY = (bounds.height() - fixed) / stretchSize;
+ else // reuse stretchX, but keep it negative as a signal
+ stretchY = SkScalarDiv(-bounds.height(), fixed);
+ }
+
+#if 0
+ SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n",
+ bitmap.width(), bitmap.height(),
+ SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
+ numXDivs + 1, numYDivs + 1,
+ SkScalarToFloat(stretchX), SkScalarToFloat(stretchY));
+#endif
+
+ const int vCount = (numXDivs + 2) * (numYDivs + 2);
+ // number of celss * 2 (tris per cell) * 3 (verts per tri)
+ const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3;
+ // allocate 2 times, one for verts, one for texs, plus indices
+ SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 +
+ indexCount * sizeof(uint16_t));
+ SkPoint* verts = (SkPoint*)storage.get();
+ SkPoint* texs = verts + vCount;
+ uint16_t* indices = (uint16_t*)(texs + vCount);
+
+ mesh.fVerts = verts;
+ mesh.fTexs = texs;
+ mesh.fColors = NULL;
+ mesh.fIndices = NULL;
+
+ // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too
+ if (numXDivs == 2 && numYDivs <= 2) {
+ mesh.fIndices = g3x3Indices;
+ } else {
+ SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1);
+ SkASSERT(n == indexCount);
+ mesh.fIndices = indices;
+ }
+
+ SkScalar vy = bounds.fTop;
+ fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs,
+ stretchX, bitmap.width());
+ verts += numXDivs + 2;
+ texs += numXDivs + 2;
+ for (int y = 0; y < numYDivs; y++) {
+ const SkScalar ty = SkIntToScalar(yDivs[y]);
+ if (stretchY >= 0) {
+ if (y & 1) {
+ vy += stretchY;
+ } else {
+ vy += ty;
+ }
+ } else { // shrink fixed sections, and collaps stretchy sections
+ if (y & 1) {
+ ;// do nothing
+ } else {
+ vy += SkScalarMul(ty, -stretchY);
+ }
+ }
+ fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs,
+ stretchX, bitmap.width());
+ verts += numXDivs + 2;
+ texs += numXDivs + 2;
+ }
+ fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()),
+ bounds, xDivs, numXDivs, stretchX, bitmap.width());
+
+ SkShader* shader = SkShader::CreateBitmapShader(bitmap,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode);
+ SkPaint p;
+ if (paint) {
+ p = *paint;
+ }
+ p.setShader(shader)->unref();
+ canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount,
+ mesh.fVerts, mesh.fTexs, mesh.fColors, NULL,
+ mesh.fIndices, indexCount, p);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst,
+ const SkBitmap& bitmap, const SkIRect& margins,
+ const SkPaint* paint) {
+ const int32_t srcX[4] = {
+ 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width()
+ };
+ const int32_t srcY[4] = {
+ 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height()
+ };
+ SkScalar dstX[4] = {
+ dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft),
+ dst.fRight - SkIntToScalar(margins.fRight), dst.fRight
+ };
+ SkScalar dstY[4] = {
+ dst.fTop, dst.fTop + SkIntToScalar(margins.fTop),
+ dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom
+ };
+
+ if (dstX[1] > dstX[2]) {
+ dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) /
+ (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight));
+ dstX[2] = dstX[1];
+ }
+
+ if (dstY[1] > dstY[2]) {
+ dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) /
+ (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom));
+ dstY[2] = dstY[1];
+ }
+
+ SkIRect s;
+ SkRect d;
+ for (int y = 0; y < 3; y++) {
+ s.fTop = srcY[y];
+ s.fBottom = srcY[y+1];
+ d.fTop = dstY[y];
+ d.fBottom = dstY[y+1];
+ for (int x = 0; x < 3; x++) {
+ s.fLeft = srcX[x];
+ s.fRight = srcX[x+1];
+ d.fLeft = dstX[x];
+ d.fRight = dstX[x+1];
+ canvas->drawBitmapRect(bitmap, &s, d, paint);
+ }
+ }
+}
+
+void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds,
+ const SkBitmap& bitmap, const SkIRect& margins,
+ const SkPaint* paint) {
+ /** Our vertices code has numerical precision problems if the transformed
+ coordinates land directly on a 1/2 pixel boundary. To work around that
+ for now, we only take the vertices case if we are in opengl. Also,
+ when not in GL, the vertices impl is slower (more math) than calling
+ the viaRects code.
+ */
+ if (false /* is our canvas backed by a gpu?*/) {
+ int32_t xDivs[2];
+ int32_t yDivs[2];
+
+ xDivs[0] = margins.fLeft;
+ xDivs[1] = bitmap.width() - margins.fRight;
+ yDivs[0] = margins.fTop;
+ yDivs[1] = bitmap.height() - margins.fBottom;
+
+ if (xDivs[0] > xDivs[1]) {
+ xDivs[0] = bitmap.width() * margins.fLeft /
+ (margins.fLeft + margins.fRight);
+ xDivs[1] = xDivs[0];
+ }
+ if (yDivs[0] > yDivs[1]) {
+ yDivs[0] = bitmap.height() * margins.fTop /
+ (margins.fTop + margins.fBottom);
+ yDivs[1] = yDivs[0];
+ }
+
+ SkNinePatch::DrawMesh(canvas, bounds, bitmap,
+ xDivs, 2, yDivs, 2, paint);
+ } else {
+ drawNineViaRects(canvas, bounds, bitmap, margins, paint);
+ }
+}
diff --git a/utils/SkNullCanvas.cpp b/utils/SkNullCanvas.cpp
new file mode 100644
index 00000000..866a3614
--- /dev/null
+++ b/utils/SkNullCanvas.cpp
@@ -0,0 +1,18 @@
+/*
+ * 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 "SkNullCanvas.h"
+
+#include "SkCanvas.h"
+#include "SkNWayCanvas.h"
+
+
+SkCanvas* SkCreateNullCanvas() {
+ // An N-Way canvas forwards calls to N canvas's. When N == 0 it's
+ // effectively a null canvas.
+ return SkNEW_ARGS(SkNWayCanvas, (0,0));
+}
diff --git a/utils/SkOSFile.cpp b/utils/SkOSFile.cpp
new file mode 100644
index 00000000..0a403750
--- /dev/null
+++ b/utils/SkOSFile.cpp
@@ -0,0 +1,249 @@
+/*
+ * 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 "SkOSFile.h"
+
+SkString SkOSPath::SkPathJoin(const char *rootPath, const char *relativePath) {
+ SkString result(rootPath);
+ if (!result.endsWith(SkPATH_SEPARATOR)) {
+ result.appendUnichar(SkPATH_SEPARATOR);
+ }
+ result.append(relativePath);
+ return result;
+}
+
+SkString SkOSPath::SkBasename(const char* fullPath) {
+ if (!fullPath) {
+ return SkString();
+ }
+ const char* filename = strrchr(fullPath, SkPATH_SEPARATOR);
+ if (NULL == filename) {
+ filename = fullPath;
+ } else {
+ ++filename;
+ }
+ return SkString(filename);
+}
+
+#ifdef SK_BUILD_FOR_WIN
+
+static uint16_t* concat_to_16(const char src[], const char suffix[])
+{
+ size_t i, len = strlen(src);
+ size_t len2 = 3 + (suffix ? strlen(suffix) : 0);
+ uint16_t* dst = (uint16_t*)sk_malloc_throw((len + len2) * sizeof(uint16_t));
+
+ for (i = 0; i < len; i++)
+ dst[i] = src[i];
+
+ if (i > 0 && dst[i-1] != '/')
+ dst[i++] = '/';
+ dst[i++] = '*';
+
+ if (suffix)
+ {
+ while (*suffix)
+ dst[i++] = *suffix++;
+ }
+ dst[i] = 0;
+ SkASSERT(i + 1 <= len + len2);
+
+ return dst;
+}
+
+SkUTF16_Str::SkUTF16_Str(const char src[])
+{
+ size_t len = strlen(src);
+
+ fStr = (uint16_t*)sk_malloc_throw((len + 1) * sizeof(uint16_t));
+ size_t i;
+ for (i = 0; i < len; i++)
+ fStr[i] = src[i];
+ fStr[i] = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+SkOSFile::Iter::Iter() : fHandle(0), fPath16(NULL)
+{
+}
+
+SkOSFile::Iter::Iter(const char path[], const char suffix[]) : fHandle(0), fPath16(NULL)
+{
+ this->reset(path, suffix);
+}
+
+SkOSFile::Iter::~Iter()
+{
+ sk_free(fPath16);
+ if (fHandle)
+ ::FindClose(fHandle);
+}
+
+void SkOSFile::Iter::reset(const char path[], const char suffix[])
+{
+ if (fHandle)
+ {
+ ::FindClose(fHandle);
+ fHandle = 0;
+ }
+ if (NULL == path)
+ path = "";
+
+ sk_free(fPath16);
+ fPath16 = concat_to_16(path, suffix);
+}
+
+static bool is_magic_dir(const uint16_t dir[])
+{
+ // return true for "." and ".."
+ return dir[0] == '.' && (dir[1] == 0 || dir[1] == '.' && dir[2] == 0);
+}
+
+static bool get_the_file(HANDLE handle, SkString* name, WIN32_FIND_DATAW* dataPtr, bool getDir)
+{
+ WIN32_FIND_DATAW data;
+
+ if (NULL == dataPtr)
+ {
+ if (::FindNextFileW(handle, &data))
+ dataPtr = &data;
+ else
+ return false;
+ }
+
+ for (;;)
+ {
+ if (getDir)
+ {
+ if ((dataPtr->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !is_magic_dir((uint16_t*)dataPtr->cFileName))
+ break;
+ }
+ else
+ {
+ if (!(dataPtr->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ break;
+ }
+ if (!::FindNextFileW(handle, dataPtr))
+ return false;
+ }
+ // if we get here, we've found a file/dir
+ if (name)
+ name->setUTF16((uint16_t*)dataPtr->cFileName);
+ return true;
+}
+
+bool SkOSFile::Iter::next(SkString* name, bool getDir)
+{
+ WIN32_FIND_DATAW data;
+ WIN32_FIND_DATAW* dataPtr = NULL;
+
+ if (fHandle == 0) // our first time
+ {
+ if (fPath16 == NULL || *fPath16 == 0) // check for no path
+ return false;
+
+ fHandle = ::FindFirstFileW((LPCWSTR)fPath16, &data);
+ if (fHandle != 0 && fHandle != (HANDLE)~0)
+ dataPtr = &data;
+ }
+ return fHandle != (HANDLE)~0 && get_the_file(fHandle, name, dataPtr, getDir);
+}
+
+#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
+
+#if 0
+OSStatus FSPathMakeRef (
+ const UInt8 * path,
+ FSRef * ref,
+ Boolean * isDirectory
+);
+#endif
+
+SkOSFile::Iter::Iter() : fDIR(0)
+{
+}
+
+SkOSFile::Iter::Iter(const char path[], const char suffix[]) : fDIR(0)
+{
+ this->reset(path, suffix);
+}
+
+SkOSFile::Iter::~Iter()
+{
+ if (fDIR)
+ ::closedir(fDIR);
+}
+
+void SkOSFile::Iter::reset(const char path[], const char suffix[])
+{
+ if (fDIR)
+ {
+ ::closedir(fDIR);
+ fDIR = 0;
+ }
+
+ fPath.set(path);
+ if (path)
+ {
+ fDIR = ::opendir(path);
+ fSuffix.set(suffix);
+ }
+ else
+ fSuffix.reset();
+}
+
+// returns true if suffix is empty, or if str ends with suffix
+static bool issuffixfor(const SkString& suffix, const char str[])
+{
+ size_t suffixLen = suffix.size();
+ size_t strLen = strlen(str);
+
+ return strLen >= suffixLen &&
+ memcmp(suffix.c_str(), str + strLen - suffixLen, suffixLen) == 0;
+}
+
+#include <sys/stat.h>
+
+bool SkOSFile::Iter::next(SkString* name, bool getDir)
+{
+ if (fDIR)
+ {
+ dirent* entry;
+
+ while ((entry = ::readdir(fDIR)) != NULL)
+ {
+ struct stat s;
+ SkString str(fPath);
+
+ if (!str.endsWith("/") && !str.endsWith("\\"))
+ str.append("/");
+ str.append(entry->d_name);
+
+ if (0 == stat(str.c_str(), &s))
+ {
+ if (getDir)
+ {
+ if (s.st_mode & S_IFDIR)
+ break;
+ }
+ else
+ {
+ if (!(s.st_mode & S_IFDIR) && issuffixfor(fSuffix, entry->d_name))
+ break;
+ }
+ }
+ }
+ if (entry) // we broke out with a file
+ {
+ if (name)
+ name->set(entry->d_name);
+ return true;
+ }
+ }
+ return false;
+}
+#endif // if one of:SK_BUILD_FOR_MAC, SK_BUILD_FOR_UNIX, SK_BUILD_FOR_ANDROID,SK_BUILD_FOR_IOS
diff --git a/utils/SkParse.cpp b/utils/SkParse.cpp
new file mode 100644
index 00000000..9609ddc5
--- /dev/null
+++ b/utils/SkParse.cpp
@@ -0,0 +1,338 @@
+
+/*
+ * 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 "SkParse.h"
+
+static inline bool is_between(int c, int min, int max)
+{
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_ws(int c)
+{
+ return is_between(c, 1, 32);
+}
+
+static inline bool is_digit(int c)
+{
+ return is_between(c, '0', '9');
+}
+
+static inline bool is_sep(int c)
+{
+ return is_ws(c) || c == ',' || c == ';';
+}
+
+static int to_hex(int c)
+{
+ if (is_digit(c))
+ return c - '0';
+
+ c |= 0x20; // make us lower-case
+ if (is_between(c, 'a', 'f'))
+ return c + 10 - 'a';
+ else
+ return -1;
+}
+
+static inline bool is_hex(int c)
+{
+ return to_hex(c) >= 0;
+}
+
+static const char* skip_ws(const char str[])
+{
+ SkASSERT(str);
+ while (is_ws(*str))
+ str++;
+ return str;
+}
+
+static const char* skip_sep(const char str[])
+{
+ SkASSERT(str);
+ while (is_sep(*str))
+ str++;
+ return str;
+}
+
+int SkParse::Count(const char str[])
+{
+ char c;
+ int count = 0;
+ goto skipLeading;
+ do {
+ count++;
+ do {
+ if ((c = *str++) == '\0')
+ goto goHome;
+ } while (is_sep(c) == false);
+skipLeading:
+ do {
+ if ((c = *str++) == '\0')
+ goto goHome;
+ } while (is_sep(c));
+ } while (true);
+goHome:
+ return count;
+}
+
+int SkParse::Count(const char str[], char separator)
+{
+ char c;
+ int count = 0;
+ goto skipLeading;
+ do {
+ count++;
+ do {
+ if ((c = *str++) == '\0')
+ goto goHome;
+ } while (c != separator);
+skipLeading:
+ do {
+ if ((c = *str++) == '\0')
+ goto goHome;
+ } while (c == separator);
+ } while (true);
+goHome:
+ return count;
+}
+
+const char* SkParse::FindHex(const char str[], uint32_t* value)
+{
+ SkASSERT(str);
+ str = skip_ws(str);
+
+ if (!is_hex(*str))
+ return NULL;
+
+ uint32_t n = 0;
+ int max_digits = 8;
+ int digit;
+
+ while ((digit = to_hex(*str)) >= 0)
+ {
+ if (--max_digits < 0)
+ return NULL;
+ n = (n << 4) | digit;
+ str += 1;
+ }
+
+ if (*str == 0 || is_ws(*str))
+ {
+ if (value)
+ *value = n;
+ return str;
+ }
+ return NULL;
+}
+
+const char* SkParse::FindS32(const char str[], int32_t* value)
+{
+ SkASSERT(str);
+ str = skip_ws(str);
+
+ int sign = 0;
+ if (*str == '-')
+ {
+ sign = -1;
+ str += 1;
+ }
+
+ if (!is_digit(*str))
+ return NULL;
+
+ int n = 0;
+ while (is_digit(*str))
+ {
+ n = 10*n + *str - '0';
+ str += 1;
+ }
+ if (value)
+ *value = (n ^ sign) - sign;
+ return str;
+}
+
+const char* SkParse::FindMSec(const char str[], SkMSec* value)
+{
+ SkASSERT(str);
+ str = skip_ws(str);
+
+ int sign = 0;
+ if (*str == '-')
+ {
+ sign = -1;
+ str += 1;
+ }
+
+ if (!is_digit(*str))
+ return NULL;
+
+ int n = 0;
+ while (is_digit(*str))
+ {
+ n = 10*n + *str - '0';
+ str += 1;
+ }
+ int remaining10s = 3;
+ if (*str == '.') {
+ str++;
+ while (is_digit(*str))
+ {
+ n = 10*n + *str - '0';
+ str += 1;
+ if (--remaining10s == 0)
+ break;
+ }
+ }
+ while (--remaining10s >= 0)
+ n *= 10;
+ if (value)
+ *value = (n ^ sign) - sign;
+ return str;
+}
+
+const char* SkParse::FindScalar(const char str[], SkScalar* value) {
+ SkASSERT(str);
+ str = skip_ws(str);
+#ifdef SK_SCALAR_IS_FLOAT
+ char* stop;
+ float v = (float)strtod(str, &stop);
+ if (str == stop) {
+ return NULL;
+ }
+ if (value) {
+ *value = v;
+ }
+ return stop;
+#else
+ int sign = 0;
+ if (*str == '-')
+ {
+ sign = -1;
+ str += 1;
+ }
+
+ if (!is_digit(*str) && *str != '.')
+ return NULL;
+
+ int n = 0;
+ while (is_digit(*str))
+ {
+ n = 10*n + *str - '0';
+ if (n > 0x7FFF)
+ return NULL;
+ str += 1;
+ }
+ n <<= 16;
+
+ if (*str == '.')
+ {
+ static const int gFractions[] = { (1 << 24) / 10, (1 << 24) / 100, (1 << 24) / 1000,
+ (1 << 24) / 10000, (1 << 24) / 100000 };
+ str += 1;
+ int d = 0;
+ const int* fraction = gFractions;
+ const int* end = &fraction[SK_ARRAY_COUNT(gFractions)];
+ while (is_digit(*str) && fraction < end)
+ d += (*str++ - '0') * *fraction++;
+ d += 0x80; // round
+ n += d >> 8;
+ }
+ while (is_digit(*str))
+ str += 1;
+ if (value)
+ {
+ n = (n ^ sign) - sign; // apply the sign
+ *value = SkFixedToScalar(n);
+ }
+#endif
+ return str;
+}
+
+const char* SkParse::FindScalars(const char str[], SkScalar value[], int count)
+{
+ SkASSERT(count >= 0);
+
+ if (count > 0)
+ {
+ for (;;)
+ {
+ str = SkParse::FindScalar(str, value);
+ if (--count == 0 || str == NULL)
+ break;
+
+ // keep going
+ str = skip_sep(str);
+ if (value)
+ value += 1;
+ }
+ }
+ return str;
+}
+
+static bool lookup_str(const char str[], const char** table, int count)
+{
+ while (--count >= 0)
+ if (!strcmp(str, table[count]))
+ return true;
+ return false;
+}
+
+bool SkParse::FindBool(const char str[], bool* value)
+{
+ static const char* gYes[] = { "yes", "1", "true" };
+ static const char* gNo[] = { "no", "0", "false" };
+
+ if (lookup_str(str, gYes, SK_ARRAY_COUNT(gYes)))
+ {
+ if (value) *value = true;
+ return true;
+ }
+ else if (lookup_str(str, gNo, SK_ARRAY_COUNT(gNo)))
+ {
+ if (value) *value = false;
+ return true;
+ }
+ return false;
+}
+
+int SkParse::FindList(const char target[], const char list[])
+{
+ size_t len = strlen(target);
+ int index = 0;
+
+ for (;;)
+ {
+ const char* end = strchr(list, ',');
+ size_t entryLen;
+
+ if (end == NULL) // last entry
+ entryLen = strlen(list);
+ else
+ entryLen = end - list;
+
+ if (entryLen == len && memcmp(target, list, len) == 0)
+ return index;
+ if (end == NULL)
+ break;
+
+ list = end + 1; // skip the ','
+ index += 1;
+ }
+ return -1;
+}
+
+#ifdef SK_SUPPORT_UNITTEST
+void SkParse::UnitTest()
+{
+ // !!! additional parse tests go here
+ SkParse::TestColor();
+}
+#endif
diff --git a/utils/SkParseColor.cpp b/utils/SkParseColor.cpp
new file mode 100644
index 00000000..becad2ca
--- /dev/null
+++ b/utils/SkParseColor.cpp
@@ -0,0 +1,539 @@
+
+/*
+ * 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 "SkParse.h"
+
+#ifdef SK_DEBUG
+#include "SkString.h"
+
+ // compress names 6 chars per long (packed 5 bits/char )
+ // note: little advantage to splitting chars across longs, since 3 longs at 2 unused bits each
+ // allow for one additional split char (vs. the 18 unsplit chars in the three longs)
+ // use extra two bits to represent:
+ // 00 : final 6 (or fewer) chars (if 'a' is 0x01, zero could have special meaning)
+ // 01 : not final 6 chars
+ // 10 : color
+ // 11 : unused, except as debugging sentinal? (could be -1 for easier test)
+ // !!! the bit to end the word (last) is at the low bit for binary search
+ // lookup first character in offset for quick start
+ // offset is 27-entry table of bytes(?) that trims linear search to at most 21 entries ('d')
+ // shift match into long; set bit 30 if it all doesn't fit
+ // while longs don't match, march forward
+ // if they do match, and bit 30 is set, advance match, clearing bit 30 if
+ // final chars, and advance to next test
+ // if they do match, and bit 30 is clear, get next long (color) and return it
+ // stop at lookup of first char + 1
+static const struct SkNameRGB {
+ const char* name;
+ int rgb;
+} colorNames[] = {
+ { "aliceblue", 0xF0F8FF },
+ { "antiquewhite", 0xFAEBD7 },
+ { "aqua", 0x00FFFF },
+ { "aquamarine", 0x7FFFD4 },
+ { "azure", 0xF0FFFF },
+ { "beige", 0xF5F5DC },
+ { "bisque", 0xFFE4C4 },
+ { "black", 0x000000 },
+ { "blanchedalmond", 0xFFEBCD },
+ { "blue", 0x0000FF },
+ { "blueviolet", 0x8A2BE2 },
+ { "brown", 0xA52A2A },
+ { "burlywood", 0xDEB887 },
+ { "cadetblue", 0x5F9EA0 },
+ { "chartreuse", 0x7FFF00 },
+ { "chocolate", 0xD2691E },
+ { "coral", 0xFF7F50 },
+ { "cornflowerblue", 0x6495ED },
+ { "cornsilk", 0xFFF8DC },
+ { "crimson", 0xDC143C },
+ { "cyan", 0x00FFFF },
+ { "darkblue", 0x00008B },
+ { "darkcyan", 0x008B8B },
+ { "darkgoldenrod", 0xB8860B },
+ { "darkgray", 0xA9A9A9 },
+ { "darkgreen", 0x006400 },
+ { "darkkhaki", 0xBDB76B },
+ { "darkmagenta", 0x8B008B },
+ { "darkolivegreen", 0x556B2F },
+ { "darkorange", 0xFF8C00 },
+ { "darkorchid", 0x9932CC },
+ { "darkred", 0x8B0000 },
+ { "darksalmon", 0xE9967A },
+ { "darkseagreen", 0x8FBC8F },
+ { "darkslateblue", 0x483D8B },
+ { "darkslategray", 0x2F4F4F },
+ { "darkturquoise", 0x00CED1 },
+ { "darkviolet", 0x9400D3 },
+ { "deeppink", 0xFF1493 },
+ { "deepskyblue", 0x00BFFF },
+ { "dimgray", 0x696969 },
+ { "dodgerblue", 0x1E90FF },
+ { "firebrick", 0xB22222 },
+ { "floralwhite", 0xFFFAF0 },
+ { "forestgreen", 0x228B22 },
+ { "fuchsia", 0xFF00FF },
+ { "gainsboro", 0xDCDCDC },
+ { "ghostwhite", 0xF8F8FF },
+ { "gold", 0xFFD700 },
+ { "goldenrod", 0xDAA520 },
+ { "gray", 0x808080 },
+ { "green", 0x008000 },
+ { "greenyellow", 0xADFF2F },
+ { "honeydew", 0xF0FFF0 },
+ { "hotpink", 0xFF69B4 },
+ { "indianred", 0xCD5C5C },
+ { "indigo", 0x4B0082 },
+ { "ivory", 0xFFFFF0 },
+ { "khaki", 0xF0E68C },
+ { "lavender", 0xE6E6FA },
+ { "lavenderblush", 0xFFF0F5 },
+ { "lawngreen", 0x7CFC00 },
+ { "lemonchiffon", 0xFFFACD },
+ { "lightblue", 0xADD8E6 },
+ { "lightcoral", 0xF08080 },
+ { "lightcyan", 0xE0FFFF },
+ { "lightgoldenrodyellow", 0xFAFAD2 },
+ { "lightgreen", 0x90EE90 },
+ { "lightgrey", 0xD3D3D3 },
+ { "lightpink", 0xFFB6C1 },
+ { "lightsalmon", 0xFFA07A },
+ { "lightseagreen", 0x20B2AA },
+ { "lightskyblue", 0x87CEFA },
+ { "lightslategray", 0x778899 },
+ { "lightsteelblue", 0xB0C4DE },
+ { "lightyellow", 0xFFFFE0 },
+ { "lime", 0x00FF00 },
+ { "limegreen", 0x32CD32 },
+ { "linen", 0xFAF0E6 },
+ { "magenta", 0xFF00FF },
+ { "maroon", 0x800000 },
+ { "mediumaquamarine", 0x66CDAA },
+ { "mediumblue", 0x0000CD },
+ { "mediumorchid", 0xBA55D3 },
+ { "mediumpurple", 0x9370DB },
+ { "mediumseagreen", 0x3CB371 },
+ { "mediumslateblue", 0x7B68EE },
+ { "mediumspringgreen", 0x00FA9A },
+ { "mediumturquoise", 0x48D1CC },
+ { "mediumvioletred", 0xC71585 },
+ { "midnightblue", 0x191970 },
+ { "mintcream", 0xF5FFFA },
+ { "mistyrose", 0xFFE4E1 },
+ { "moccasin", 0xFFE4B5 },
+ { "navajowhite", 0xFFDEAD },
+ { "navy", 0x000080 },
+ { "oldlace", 0xFDF5E6 },
+ { "olive", 0x808000 },
+ { "olivedrab", 0x6B8E23 },
+ { "orange", 0xFFA500 },
+ { "orangered", 0xFF4500 },
+ { "orchid", 0xDA70D6 },
+ { "palegoldenrod", 0xEEE8AA },
+ { "palegreen", 0x98FB98 },
+ { "paleturquoise", 0xAFEEEE },
+ { "palevioletred", 0xDB7093 },
+ { "papayawhip", 0xFFEFD5 },
+ { "peachpuff", 0xFFDAB9 },
+ { "peru", 0xCD853F },
+ { "pink", 0xFFC0CB },
+ { "plum", 0xDDA0DD },
+ { "powderblue", 0xB0E0E6 },
+ { "purple", 0x800080 },
+ { "red", 0xFF0000 },
+ { "rosybrown", 0xBC8F8F },
+ { "royalblue", 0x4169E1 },
+ { "saddlebrown", 0x8B4513 },
+ { "salmon", 0xFA8072 },
+ { "sandybrown", 0xF4A460 },
+ { "seagreen", 0x2E8B57 },
+ { "seashell", 0xFFF5EE },
+ { "sienna", 0xA0522D },
+ { "silver", 0xC0C0C0 },
+ { "skyblue", 0x87CEEB },
+ { "slateblue", 0x6A5ACD },
+ { "slategray", 0x708090 },
+ { "snow", 0xFFFAFA },
+ { "springgreen", 0x00FF7F },
+ { "steelblue", 0x4682B4 },
+ { "tan", 0xD2B48C },
+ { "teal", 0x008080 },
+ { "thistle", 0xD8BFD8 },
+ { "tomato", 0xFF6347 },
+ { "turquoise", 0x40E0D0 },
+ { "violet", 0xEE82EE },
+ { "wheat", 0xF5DEB3 },
+ { "white", 0xFFFFFF },
+ { "whitesmoke", 0xF5F5F5 },
+ { "yellow", 0xFFFF00 },
+ { "yellowgreen", 0x9ACD32 }
+};
+
+int colorNamesSize = sizeof(colorNames) / sizeof(colorNames[0]);
+
+#ifdef SK_SUPPORT_UNITTEST
+static void CreateTable() {
+ SkString comment;
+ size_t originalSize = 0;
+ int replacement = 0;
+ for (int index = 0; index < colorNamesSize; index++) {
+ SkNameRGB nameRGB = colorNames[index];
+ const char* name = nameRGB.name;
+ size_t len = strlen(name);
+ originalSize += len + 9;
+ bool first = true;
+ bool last = false;
+ do {
+ int compressed = 0;
+ const char* start = name;
+ for (int chIndex = 0; chIndex < 6; chIndex++) {
+ compressed <<= 5;
+ compressed |= *name ? *name++ - 'a' + 1 : 0 ;
+ }
+ replacement += sizeof(int);
+ compressed <<= 1;
+ compressed |= 1;
+ if (first) {
+ compressed |= 0x80000000;
+ first = false;
+ }
+ if (len <= 6) { // last
+ compressed &= ~1;
+ last = true;
+ }
+ len -= 6;
+ SkDebugf("0x%08x, ", compressed);
+ comment.append(start, name - start);
+ } while (last == false);
+ replacement += sizeof(int);
+ SkDebugf("0x%08x, ", nameRGB.rgb);
+ SkDebugf("// %s\n", comment.c_str());
+ comment.reset();
+ }
+ SkDebugf("// original = %d : replacement = %d\n", originalSize, replacement);
+ SkASSERT(0); // always stop after creating table
+}
+#endif
+
+#endif
+
+static const unsigned int gColorNames[] = {
+0x85891945, 0x32a50000, 0x00f0f8ff, // aliceblue
+0x85d44c6b, 0x16e84d0a, 0x00faebd7, // antiquewhite
+0x86350800, 0x0000ffff, // aqua
+0x86350b43, 0x492e2800, 0x007fffd4, // aquamarine
+0x87559140, 0x00f0ffff, // azure
+0x88a93940, 0x00f5f5dc, // beige
+0x89338d4a, 0x00ffe4c4, // bisque
+0x89811ac0, 0x00000000, // black
+0x898170d1, 0x1481635f, 0x38800000, 0x00ffebcd, // blanchedalmond
+0x89952800, 0x000000ff, // blue
+0x89952d93, 0x3d85a000, 0x008a2be2, // blueviolet
+0x8a4fbb80, 0x00a52a2a, // brown
+0x8ab2666f, 0x3de40000, 0x00deb887, // burlywood
+0x8c242d05, 0x32a50000, 0x005f9ea0, // cadetblue
+0x8d019525, 0x16b32800, 0x007fff00, // chartreuse
+0x8d0f1bd9, 0x06850000, 0x00d2691e, // chocolate
+0x8df20b00, 0x00ff7f50, // coral
+0x8df27199, 0x3ee59099, 0x54a00000, 0x006495ed, // cornflowerblue
+0x8df274d3, 0x31600000, 0x00fff8dc, // cornsilk
+0x8e496cdf, 0x38000000, 0x00dc143c, // crimson
+0x8f217000, 0x0000ffff, // cyan
+0x90325899, 0x54a00000, 0x0000008b, // darkblue
+0x903258f3, 0x05c00000, 0x00008b8b, // darkcyan
+0x903259df, 0x3085749f, 0x10000000, 0x00b8860b, // darkgoldenrod
+0x903259e5, 0x07200000, 0x00a9a9a9, // darkgray
+0x903259e5, 0x14ae0000, 0x00006400, // darkgreen
+0x90325ad1, 0x05690000, 0x00bdb76b, // darkkhaki
+0x90325b43, 0x1caea040, 0x008b008b, // darkmagenta
+0x90325bd9, 0x26c53c8b, 0x15c00000, 0x00556b2f, // darkolivegreen
+0x90325be5, 0x05c72800, 0x00ff8c00, // darkorange
+0x90325be5, 0x0d092000, 0x009932cc, // darkorchid
+0x90325c8b, 0x10000000, 0x008b0000, // darkred
+0x90325cc3, 0x31af7000, 0x00e9967a, // darksalmon
+0x90325ccb, 0x04f2295c, 0x008fbc8f, // darkseagreen
+0x90325cd9, 0x0685132b, 0x14000000, 0x00483d8b, // darkslateblue
+0x90325cd9, 0x06853c83, 0x64000000, 0x002f4f4f, // darkslategray
+0x90325d2b, 0x4a357a67, 0x14000000, 0x0000ced1, // darkturquoise
+0x90325d93, 0x3d85a000, 0x009400d3, // darkviolet
+0x90a58413, 0x39600000, 0x00ff1493, // deeppink
+0x90a584d7, 0x644ca940, 0x0000bfff, // deepskyblue
+0x912d3c83, 0x64000000, 0x00696969, // dimgray
+0x91e43965, 0x09952800, 0x001e90ff, // dodgerblue
+0x993228a5, 0x246b0000, 0x00b22222, // firebrick
+0x998f9059, 0x5d09a140, 0x00fffaf0, // floralwhite
+0x99f22ce9, 0x1e452b80, 0x00228b22, // forestgreen
+0x9aa344d3, 0x04000000, 0x00ff00ff, // fuchsia
+0x9c2974c5, 0x3e4f0000, 0x00dcdcdc, // gainsboro
+0x9d0f9d2f, 0x21342800, 0x00f8f8ff, // ghostwhite
+0x9dec2000, 0x00ffd700, // gold
+0x9dec215d, 0x49e40000, 0x00daa520, // goldenrod
+0x9e41c800, 0x00808080, // gray
+0x9e452b80, 0x00008000, // green
+0x9e452bb3, 0x158c7dc0, 0x00adff2f, // greenyellow
+0xa1ee2e49, 0x16e00000, 0x00f0fff0, // honeydew
+0xa1f4825d, 0x2c000000, 0x00ff69b4, // hotpink
+0xa5c4485d, 0x48a40000, 0x00cd5c5c, // indianred
+0xa5c449de, 0x004b0082, // indigo
+0xa6cf9640, 0x00fffff0, // ivory
+0xad015a40, 0x00f0e68c, // khaki
+0xb0362b89, 0x16400000, 0x00e6e6fa, // lavender
+0xb0362b89, 0x16426567, 0x20000000, 0x00fff0f5, // lavenderblush
+0xb03771e5, 0x14ae0000, 0x007cfc00, // lawngreen
+0xb0ad7b87, 0x212633dc, 0x00fffacd, // lemonchiffon
+0xb1274505, 0x32a50000, 0x00add8e6, // lightblue
+0xb1274507, 0x3e416000, 0x00f08080, // lightcoral
+0xb1274507, 0x642e0000, 0x00e0ffff, // lightcyan
+0xb127450f, 0x3d842ba5, 0x3c992b19, 0x3ee00000, 0x00fafad2, // lightgoldenrodyellow
+0xb127450f, 0x48a57000, 0x0090ee90, // lightgreen
+0xb127450f, 0x48b90000, 0x00d3d3d3, // lightgrey
+0xb1274521, 0x25cb0000, 0x00ffb6c1, // lightpink
+0xb1274527, 0x058d7b80, 0x00ffa07a, // lightsalmon
+0xb1274527, 0x1427914b, 0x38000000, 0x0020b2aa, // lightseagreen
+0xb1274527, 0x2f22654a, 0x0087cefa, // lightskyblue
+0xb1274527, 0x303429e5, 0x07200000, 0x00778899, // lightslategray
+0xb1274527, 0x50a56099, 0x54a00000, 0x00b0c4de, // lightsteelblue
+0xb1274533, 0x158c7dc0, 0x00ffffe0, // lightyellow
+0xb12d2800, 0x0000ff00, // lime
+0xb12d29e5, 0x14ae0000, 0x0032cd32, // limegreen
+0xb12e2b80, 0x00faf0e6, // linen
+0xb4272ba9, 0x04000000, 0x00ff00ff, // magenta
+0xb4327bdc, 0x00800000, // maroon
+0xb4a44d5b, 0x06350b43, 0x492e2800, 0x0066cdaa, // mediumaquamarine
+0xb4a44d5b, 0x09952800, 0x000000cd, // mediumblue
+0xb4a44d5b, 0x3e434248, 0x00ba55d3, // mediumorchid
+0xb4a44d5b, 0x42b2830a, 0x009370db, // mediumpurple
+0xb4a44d5b, 0x4ca13c8b, 0x15c00000, 0x003cb371, // mediumseagreen
+0xb4a44d5b, 0x4d81a145, 0x32a50000, 0x007b68ee, // mediumslateblue
+0xb4a44d5b, 0x4e124b8f, 0x1e452b80, 0x0000fa9a, // mediumspringgreen
+0xb4a44d5b, 0x52b28d5f, 0x26650000, 0x0048d1cc, // mediumturquoise
+0xb4a44d5b, 0x592f6169, 0x48a40000, 0x00c71585, // mediumvioletred
+0xb524724f, 0x2282654a, 0x00191970, // midnightblue
+0xb52ea0e5, 0x142d0000, 0x00f5fffa, // mintcream
+0xb533a665, 0x3e650000, 0x00ffe4e1, // mistyrose
+0xb5e31867, 0x25c00000, 0x00ffe4b5, // moccasin
+0xb8360a9f, 0x5d09a140, 0x00ffdead, // navajowhite
+0xb836c800, 0x00000080, // navy
+0xbd846047, 0x14000000, 0x00fdf5e6, // oldlace
+0xbd89b140, 0x00808000, // olive
+0xbd89b149, 0x48220000, 0x006b8e23, // olivedrab
+0xbe4171ca, 0x00ffa500, // orange
+0xbe4171cb, 0x48a40000, 0x00ff4500, // orangered
+0xbe434248, 0x00da70d6, // orchid
+0xc02c29df, 0x3085749f, 0x10000000, 0x00eee8aa, // palegoldenrod
+0xc02c29e5, 0x14ae0000, 0x0098fb98, // palegreen
+0xc02c2d2b, 0x4a357a67, 0x14000000, 0x00afeeee, // paleturquoise
+0xc02c2d93, 0x3d85a48b, 0x10000000, 0x00db7093, // palevioletred
+0xc0300e43, 0x5d098000, 0x00ffefd5, // papayawhip
+0xc0a11a21, 0x54c60000, 0x00ffdab9, // peachpuff
+0xc0b2a800, 0x00cd853f, // peru
+0xc12e5800, 0x00ffc0cb, // pink
+0xc1956800, 0x00dda0dd, // plum
+0xc1f72165, 0x09952800, 0x00b0e0e6, // powderblue
+0xc2b2830a, 0x00800080, // purple
+0xc8a40000, 0x00ff0000, // red
+0xc9f3c8a5, 0x3eee0000, 0x00bc8f8f, // rosybrown
+0xc9f90b05, 0x32a50000, 0x004169e1, // royalblue
+0xcc24230b, 0x0a4fbb80, 0x008b4513, // saddlebrown
+0xcc2c6bdc, 0x00fa8072, // salmon
+0xcc2e2645, 0x49f77000, 0x00f4a460, // sandybrown
+0xcca13c8b, 0x15c00000, 0x002e8b57, // seagreen
+0xcca19a0b, 0x31800000, 0x00fff5ee, // seashell
+0xcd257382, 0x00a0522d, // sienna
+0xcd2cb164, 0x00c0c0c0, // silver
+0xcd79132b, 0x14000000, 0x0087ceeb, // skyblue
+0xcd81a145, 0x32a50000, 0x006a5acd, // slateblue
+0xcd81a14f, 0x48390000, 0x00708090, // slategray
+0xcdcfb800, 0x00fffafa, // snow
+0xce124b8f, 0x1e452b80, 0x0000ff7f, // springgreen
+0xce852b05, 0x32a50000, 0x004682b4, // steelblue
+0xd02e0000, 0x00d2b48c, // tan
+0xd0a16000, 0x00008080, // teal
+0xd1099d19, 0x14000000, 0x00d8bfd8, // thistle
+0xd1ed0d1e, 0x00ff6347, // tomato
+0xd2b28d5f, 0x26650000, 0x0040e0d0, // turquoise
+0xd92f6168, 0x00ee82ee, // violet
+0xdd050d00, 0x00f5deb3, // wheat
+0xdd09a140, 0x00ffffff, // white
+0xdd09a167, 0x35eb2800, 0x00f5f5f5, // whitesmoke
+0xe4ac63ee, 0x00ffff00, // yellow
+0xe4ac63ef, 0x1e452b80, 0x009acd32 // yellowgreen
+}; // original = 2505 : replacement = 1616
+
+
+const char* SkParse::FindNamedColor(const char* name, size_t len, SkColor* color) {
+ const char* namePtr = name;
+ unsigned int sixMatches[4];
+ unsigned int* sixMatchPtr = sixMatches;
+ bool first = true;
+ bool last = false;
+ char ch;
+ do {
+ unsigned int sixMatch = 0;
+ for (int chIndex = 0; chIndex < 6; chIndex++) {
+ sixMatch <<= 5;
+ ch = *namePtr | 0x20;
+ if (ch < 'a' || ch > 'z')
+ ch = 0;
+ else {
+ ch = ch - 'a' + 1;
+ namePtr++;
+ }
+ sixMatch |= ch ; // turn 'A' (0x41) into 'a' (0x61);
+ }
+ sixMatch <<= 1;
+ sixMatch |= 1;
+ if (first) {
+ sixMatch |= 0x80000000;
+ first = false;
+ }
+ ch = *namePtr | 0x20;
+ last = ch < 'a' || ch > 'z';
+ if (last)
+ sixMatch &= ~1;
+ len -= 6;
+ *sixMatchPtr++ = sixMatch;
+ } while (last == false && len > 0);
+ const int colorNameSize = sizeof(gColorNames) / sizeof(unsigned int);
+ int lo = 0;
+ int hi = colorNameSize - 3; // back off to beginning of yellowgreen
+ while (lo <= hi) {
+ int mid = (hi + lo) >> 1;
+ while ((int) gColorNames[mid] >= 0)
+ --mid;
+ sixMatchPtr = sixMatches;
+ while (gColorNames[mid] == *sixMatchPtr) {
+ ++mid;
+ if ((*sixMatchPtr & 1) == 0) { // last
+ *color = gColorNames[mid] | 0xFF000000;
+ return namePtr;
+ }
+ ++sixMatchPtr;
+ }
+ int sixMask = *sixMatchPtr & ~0x80000000;
+ int midMask = gColorNames[mid] & ~0x80000000;
+ if (sixMask > midMask) {
+ lo = mid + 2; // skip color
+ while ((int) gColorNames[lo] >= 0)
+ ++lo;
+ } else if (hi == mid)
+ return NULL;
+ else
+ hi = mid;
+ }
+ return NULL;
+}
+
+// !!! move to char utilities
+//static int count_separators(const char* str, const char* sep) {
+// char c;
+// int separators = 0;
+// while ((c = *str++) != '\0') {
+// if (strchr(sep, c) == NULL)
+// continue;
+// do {
+// if ((c = *str++) == '\0')
+// goto goHome;
+// } while (strchr(sep, c) != NULL);
+// separators++;
+// }
+//goHome:
+// return separators;
+//}
+
+static inline unsigned nib2byte(unsigned n)
+{
+ SkASSERT((n & ~0xF) == 0);
+ return (n << 4) | n;
+}
+
+const char* SkParse::FindColor(const char* value, SkColor* colorPtr) {
+ unsigned int oldAlpha = SkColorGetA(*colorPtr);
+ if (value[0] == '#') {
+ uint32_t hex;
+ const char* end = SkParse::FindHex(value + 1, &hex);
+// SkASSERT(end);
+ if (end == NULL)
+ return end;
+ size_t len = end - value - 1;
+ if (len == 3 || len == 4) {
+ unsigned a = len == 4 ? nib2byte(hex >> 12) : oldAlpha;
+ unsigned r = nib2byte((hex >> 8) & 0xF);
+ unsigned g = nib2byte((hex >> 4) & 0xF);
+ unsigned b = nib2byte(hex & 0xF);
+ *colorPtr = SkColorSetARGB(a, r, g, b);
+ return end;
+ } else if (len == 6 || len == 8) {
+ if (len == 6)
+ hex |= oldAlpha << 24;
+ *colorPtr = hex;
+ return end;
+ } else {
+// SkASSERT(0);
+ return NULL;
+ }
+// } else if (strchr(value, ',')) {
+// SkScalar array[4];
+// int count = count_separators(value, ",") + 1; // !!! count commas, add 1
+// SkASSERT(count == 3 || count == 4);
+// array[0] = SK_Scalar1 * 255;
+// const char* end = SkParse::FindScalars(value, &array[4 - count], count);
+// if (end == NULL)
+// return NULL;
+ // !!! range check for errors?
+// *colorPtr = SkColorSetARGB(SkScalarRound(array[0]), SkScalarRound(array[1]),
+// SkScalarRound(array[2]), SkScalarRound(array[3]));
+// return end;
+ } else
+ return FindNamedColor(value, strlen(value), colorPtr);
+}
+
+#ifdef SK_SUPPORT_UNITTEST
+void SkParse::TestColor() {
+ if (false)
+ CreateTable(); // regenerates data table in the output window
+ SkColor result;
+ int index;
+ for (index = 0; index < colorNamesSize; index++) {
+ result = SK_ColorBLACK;
+ SkNameRGB nameRGB = colorNames[index];
+ SkASSERT(FindColor(nameRGB.name, &result) != NULL);
+ SkASSERT(result == (SkColor) (nameRGB.rgb | 0xFF000000));
+ }
+ for (index = 0; index < colorNamesSize; index++) {
+ result = SK_ColorBLACK;
+ SkNameRGB nameRGB = colorNames[index];
+ char bad[24];
+ size_t len = strlen(nameRGB.name);
+ memcpy(bad, nameRGB.name, len);
+ bad[len - 1] -= 1;
+ SkASSERT(FindColor(bad, &result) == false);
+ bad[len - 1] += 2;
+ SkASSERT(FindColor(bad, &result) == false);
+ }
+ result = SK_ColorBLACK;
+ SkASSERT(FindColor("lightGrey", &result));
+ SkASSERT(result == 0xffd3d3d3);
+// SkASSERT(FindColor("12,34,56,78", &result));
+// SkASSERT(result == ((12 << 24) | (34 << 16) | (56 << 8) | (78 << 0)));
+ result = SK_ColorBLACK;
+ SkASSERT(FindColor("#ABCdef", &result));
+ SkASSERT(result == 0XFFABCdef);
+ SkASSERT(FindColor("#12ABCdef", &result));
+ SkASSERT(result == 0X12ABCdef);
+ result = SK_ColorBLACK;
+ SkASSERT(FindColor("#123", &result));
+ SkASSERT(result == 0Xff112233);
+ SkASSERT(FindColor("#abcd", &result));
+ SkASSERT(result == 0Xaabbccdd);
+ result = SK_ColorBLACK;
+// SkASSERT(FindColor("71,162,253", &result));
+// SkASSERT(result == ((0xFF << 24) | (71 << 16) | (162 << 8) | (253 << 0)));
+}
+#endif
diff --git a/utils/SkParsePath.cpp b/utils/SkParsePath.cpp
new file mode 100644
index 00000000..4c9923f4
--- /dev/null
+++ b/utils/SkParsePath.cpp
@@ -0,0 +1,248 @@
+
+/*
+ * 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 "SkParse.h"
+#include "SkParsePath.h"
+
+static inline bool is_between(int c, int min, int max) {
+ return (unsigned)(c - min) <= (unsigned)(max - min);
+}
+
+static inline bool is_ws(int c) {
+ return is_between(c, 1, 32);
+}
+
+static inline bool is_digit(int c) {
+ return is_between(c, '0', '9');
+}
+
+static inline bool is_sep(int c) {
+ return is_ws(c) || c == ',';
+}
+
+static inline bool is_lower(int c) {
+ return is_between(c, 'a', 'z');
+}
+
+static inline int to_upper(int c) {
+ return c - 'a' + 'A';
+}
+
+static const char* skip_ws(const char str[]) {
+ SkASSERT(str);
+ while (is_ws(*str))
+ str++;
+ return str;
+}
+
+static const char* skip_sep(const char str[]) {
+ SkASSERT(str);
+ while (is_sep(*str))
+ str++;
+ return str;
+}
+
+static const char* find_points(const char str[], SkPoint value[], int count,
+ bool isRelative, SkPoint* relative) {
+ str = SkParse::FindScalars(str, &value[0].fX, count * 2);
+ if (isRelative) {
+ for (int index = 0; index < count; index++) {
+ value[index].fX += relative->fX;
+ value[index].fY += relative->fY;
+ }
+ }
+ return str;
+}
+
+static const char* find_scalar(const char str[], SkScalar* value,
+ bool isRelative, SkScalar relative) {
+ str = SkParse::FindScalar(str, value);
+ if (isRelative) {
+ *value += relative;
+ }
+ return str;
+}
+
+bool SkParsePath::FromSVGString(const char data[], SkPath* result) {
+ SkPath path;
+ SkPoint f = {0, 0};
+ SkPoint c = {0, 0};
+ SkPoint lastc = {0, 0};
+ SkPoint points[3];
+ char op = '\0';
+ char previousOp = '\0';
+ bool relative = false;
+ for (;;) {
+ data = skip_ws(data);
+ if (data[0] == '\0') {
+ break;
+ }
+ char ch = data[0];
+ if (is_digit(ch) || ch == '-' || ch == '+') {
+ if (op == '\0') {
+ return false;
+ }
+ } else {
+ op = ch;
+ relative = false;
+ if (is_lower(op)) {
+ op = (char) to_upper(op);
+ relative = true;
+ }
+ data++;
+ data = skip_sep(data);
+ }
+ switch (op) {
+ case 'M':
+ data = find_points(data, points, 1, relative, &c);
+ path.moveTo(points[0]);
+ op = 'L';
+ c = points[0];
+ break;
+ case 'L':
+ data = find_points(data, points, 1, relative, &c);
+ path.lineTo(points[0]);
+ c = points[0];
+ break;
+ case 'H': {
+ SkScalar x;
+ data = find_scalar(data, &x, relative, c.fX);
+ path.lineTo(x, c.fY);
+ c.fX = x;
+ } break;
+ case 'V': {
+ SkScalar y;
+ data = find_scalar(data, &y, relative, c.fY);
+ path.lineTo(c.fX, y);
+ c.fY = y;
+ } break;
+ case 'C':
+ data = find_points(data, points, 3, relative, &c);
+ goto cubicCommon;
+ case 'S':
+ data = find_points(data, &points[1], 2, relative, &c);
+ points[0] = c;
+ if (previousOp == 'C' || previousOp == 'S') {
+ points[0].fX -= lastc.fX - c.fX;
+ points[0].fY -= lastc.fY - c.fY;
+ }
+ cubicCommon:
+ path.cubicTo(points[0], points[1], points[2]);
+ lastc = points[1];
+ c = points[2];
+ break;
+ case 'Q': // Quadratic Bezier Curve
+ data = find_points(data, points, 2, relative, &c);
+ goto quadraticCommon;
+ case 'T':
+ data = find_points(data, &points[1], 1, relative, &c);
+ points[0] = points[1];
+ if (previousOp == 'Q' || previousOp == 'T') {
+ points[0].fX = c.fX * 2 - lastc.fX;
+ points[0].fY = c.fY * 2 - lastc.fY;
+ }
+ quadraticCommon:
+ path.quadTo(points[0], points[1]);
+ lastc = points[0];
+ c = points[1];
+ break;
+ case 'Z':
+ path.close();
+#if 0 // !!! still a bug?
+ if (fPath.isEmpty() && (f.fX != 0 || f.fY != 0)) {
+ c.fX -= SkScalar.Epsilon; // !!! enough?
+ fPath.moveTo(c);
+ fPath.lineTo(f);
+ fPath.close();
+ }
+#endif
+ c = f;
+ op = '\0';
+ break;
+ case '~': {
+ SkPoint args[2];
+ data = find_points(data, args, 2, false, NULL);
+ path.moveTo(args[0].fX, args[0].fY);
+ path.lineTo(args[1].fX, args[1].fY);
+ } break;
+ default:
+ return false;
+ }
+ if (previousOp == 0) {
+ f = c;
+ }
+ previousOp = op;
+ }
+ // we're good, go ahead and swap in the result
+ result->swap(path);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkString.h"
+#include "SkStream.h"
+
+static void write_scalar(SkWStream* stream, SkScalar value) {
+#ifdef SK_SCALAR_IS_FLOAT
+ char buffer[64];
+#ifdef SK_BUILD_FOR_WIN32
+ int len = _snprintf(buffer, sizeof(buffer), "%g", value);
+#else
+ int len = snprintf(buffer, sizeof(buffer), "%g", value);
+#endif
+ char* stop = buffer + len;
+#else
+ char buffer[SkStrAppendScalar_MaxSize];
+ char* stop = SkStrAppendScalar(buffer, value);
+#endif
+ stream->write(buffer, stop - buffer);
+}
+
+static void append_scalars(SkWStream* stream, char verb, const SkScalar data[],
+ int count) {
+ stream->write(&verb, 1);
+ write_scalar(stream, data[0]);
+ for (int i = 1; i < count; i++) {
+ stream->write(" ", 1);
+ write_scalar(stream, data[i]);
+ }
+}
+
+void SkParsePath::ToSVGString(const SkPath& path, SkString* str) {
+ SkDynamicMemoryWStream stream;
+
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+
+ for (;;) {
+ switch (iter.next(pts, false)) {
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kMove_Verb:
+ append_scalars(&stream, 'M', &pts[0].fX, 2);
+ break;
+ case SkPath::kLine_Verb:
+ append_scalars(&stream, 'L', &pts[1].fX, 2);
+ break;
+ case SkPath::kQuad_Verb:
+ append_scalars(&stream, 'Q', &pts[1].fX, 4);
+ break;
+ case SkPath::kCubic_Verb:
+ append_scalars(&stream, 'C', &pts[1].fX, 6);
+ break;
+ case SkPath::kClose_Verb:
+ stream.write("Z", 1);
+ break;
+ case SkPath::kDone_Verb:
+ str->resize(stream.getOffset());
+ stream.copyTo(str->writable_str());
+ return;
+ }
+ }
+}
diff --git a/utils/SkPathUtils.cpp b/utils/SkPathUtils.cpp
new file mode 100644
index 00000000..2e17ccb1
--- /dev/null
+++ b/utils/SkPathUtils.cpp
@@ -0,0 +1,152 @@
+/*
+ * CAUTION: EXPERIMENTAL CODE
+ *
+ * This code is not to be used and will not be supported
+ * if it fails on you. DO NOT USE!
+ *
+ */
+
+#include "SkPathUtils.h"
+
+#include "SkPath.h"
+#include "SkPathOps.h" // this can't be found, how do I link it?
+#include "SkRegion.h"
+
+typedef void (*line2path)(SkPath*, const char*, int, int);
+#define SQRT_2 1.41421356237f
+#define ON 0xFF000000 // black pixel
+#define OFF 0x00000000 // transparent pixel
+
+// assumes stride is in bytes
+/*
+static void FillRandomBits( int chars, char* bits ){
+ SkTime time;
+ SkMWCRandom rand = SkMWCRandom( time.GetMSecs() );
+
+ for (int i = 0; i < chars; ++i){
+ bits[i] = rand.nextU();
+ }
+}OA
+*/
+
+static int GetBit( const char* buffer, int x ) {
+ int byte = x >> 3;
+ int bit = x & 7;
+
+ return buffer[byte] & (128 >> bit);
+}
+
+/*
+static void Line2path_pixel(SkPath* path, const char* line,
+ int lineIdx, int width) {
+ for (int i = 0; i < width; ++i) {
+ // simply makes every ON pixel into a rect path
+ if (GetBit(line,i)) {
+ path->addRect(SkRect::MakeXYWH(i, lineIdx, 1, 1),
+ SkPath::kCW_Direction);
+ }
+ }
+}
+
+static void Line2path_pixelCircle(SkPath* path, const char* line,
+ int lineIdx, int width) {
+ for (int i = 0; i < width; ++i) {
+ // simply makes every ON pixel into a circle path
+ if (GetBit(line,i)) {
+ path->addCircle(i + SK_ScalarHalf,
+ lineIdx + SK_ScalarHalf,
+ SkFloatToScalar(SQRT_2 / 2.0f));
+ }
+ }
+}
+*/
+
+static void Line2path_span(SkPath* path, const char* line,
+ int lineIdx, int width) {
+ bool inRun = 0;
+ int start = 1;
+
+ for (int i = 0; i < width; ++i) {
+ int curPixel = GetBit(line,i);
+
+ if ( (curPixel!=0) != inRun ) { // if transition
+ if (curPixel) { // if transition on
+ inRun = 1;
+ start = i; // mark beginning of span
+ }else { // if transition off add the span as a path
+ inRun = 0;
+ path->addRect(SkRect::MakeXYWH(SkIntToScalar(start), SkIntToScalar(lineIdx),
+ SkIntToScalar(i-start), SK_Scalar1),
+ SkPath::kCW_Direction);
+ }
+ }
+ }
+
+ if (inRun==1) { // close any open spans
+ int end = 0;
+ if ( GetBit(line,width-1) ) ++end;
+ path->addRect(SkRect::MakeXYWH(SkIntToScalar(start), SkIntToScalar(lineIdx),
+ SkIntToScalar(width - 1 + end - start), SK_Scalar1),
+ SkPath::kCW_Direction);
+ } else if ( GetBit(line, width - 1) ) { // if last pixel on add
+ path->addRect(SkRect::MakeXYWH(width - SK_Scalar1, SkIntToScalar(lineIdx),
+ SK_Scalar1, SK_Scalar1),
+ SkPath::kCW_Direction);
+ }
+}
+
+void SkPathUtils::BitsToPath_Path(SkPath* path,
+ const char* bitmap,
+ int w, int h, int stride) {
+ // loop for every line in bitmap
+ for (int i = 0; i < h; ++i) {
+ // fn ptr handles each line separately
+ //l2p_fn(path, &bitmap[i*stride], i, w);
+ Line2path_span(path, &bitmap[i*stride], i, w);
+ }
+ Simplify(*path, path); // simplify resulting path.
+}
+
+void SkPathUtils::BitsToPath_Region(SkPath* path,
+ const char* bitmap,
+ int w, int h, int stride) {
+ SkRegion region;
+
+ // loop for each line
+ for (int y = 0; y < h; ++y){
+ bool inRun = 0;
+ int start = 1;
+ const char* line = &bitmap[y * stride];
+
+ // loop for each pixel
+ for (int i = 0; i < w; ++i) {
+ int curPixel = GetBit(line,i);
+
+ if ( (curPixel!=0) != inRun ) { // if transition
+ if (curPixel) { // if transition on
+ inRun = 1;
+ start = i; // mark beginning of span
+ }else { // if transition off add the span as a path
+ inRun = 0;
+ //add here
+ region.op(SkIRect::MakeXYWH(start, y, i-start, 1),
+ SkRegion::kUnion_Op );
+ }
+ }
+ }
+ if (inRun==1) { // close any open spans
+ int end = 0;
+ if ( GetBit(line,w-1) ) ++end;
+ // add the thing here
+ region.op(SkIRect::MakeXYWH(start, y, w-1-start+end, 1),
+ SkRegion::kUnion_Op );
+
+ } else if ( GetBit(line,w-1) ) { // if last pixel on add rect
+ // add the thing here
+ region.op(SkIRect::MakeXYWH(w-1, y, 1, 1),
+ SkRegion::kUnion_Op );
+ }
+ }
+ // convert region to path
+ region.getBoundaryPath(path);
+}
diff --git a/utils/SkPictureUtils.cpp b/utils/SkPictureUtils.cpp
new file mode 100644
index 00000000..36b62e51
--- /dev/null
+++ b/utils/SkPictureUtils.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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"
+#include "SkRRect.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;
+ // Check whether the shader is a gradient in order to short-circuit
+ // call to asABitmap to prevent generation of bitmaps from
+ // gradient shaders, which implement asABitmap.
+ if (SkShader::kNone_GradientType == shader->asAGradient(NULL) &&
+ 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&,
+ const SkPaint& paint) SK_OVERRIDE {
+ this->addBitmapFromPaint(paint);
+ }
+ virtual void drawOval(const SkDraw&, const SkRect&,
+ 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 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->updateClipConservativelyUsingBounds(path.getBounds(), op, path.isInverseFillType());
+ }
+ virtual bool clipRRect(const SkRRect& rrect, SkRegion::Op op,
+ bool doAA) SK_OVERRIDE {
+ return this->updateClipConservativelyUsingBounds(rrect.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/utils/SkProxyCanvas.cpp b/utils/SkProxyCanvas.cpp
new file mode 100644
index 00000000..053a7577
--- /dev/null
+++ b/utils/SkProxyCanvas.cpp
@@ -0,0 +1,179 @@
+
+/*
+ * 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 "SkProxyCanvas.h"
+
+SkProxyCanvas::SkProxyCanvas(SkCanvas* proxy) : fProxy(proxy) {
+ SkSafeRef(fProxy);
+}
+
+SkProxyCanvas::~SkProxyCanvas() {
+ SkSafeUnref(fProxy);
+}
+
+void SkProxyCanvas::setProxy(SkCanvas* proxy) {
+ SkRefCnt_SafeAssign(fProxy, proxy);
+}
+
+///////////////////////////////// Overrides ///////////
+
+int SkProxyCanvas::save(SaveFlags flags) {
+ return fProxy->save(flags);
+}
+
+int SkProxyCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ return fProxy->saveLayer(bounds, paint, flags);
+}
+
+void SkProxyCanvas::restore() {
+ fProxy->restore();
+}
+
+bool SkProxyCanvas::translate(SkScalar dx, SkScalar dy) {
+ return fProxy->translate(dx, dy);
+}
+
+bool SkProxyCanvas::scale(SkScalar sx, SkScalar sy) {
+ return fProxy->scale(sx, sy);
+}
+
+bool SkProxyCanvas::rotate(SkScalar degrees) {
+ return fProxy->rotate(degrees);
+}
+
+bool SkProxyCanvas::skew(SkScalar sx, SkScalar sy) {
+ return fProxy->skew(sx, sy);
+}
+
+bool SkProxyCanvas::concat(const SkMatrix& matrix) {
+ return fProxy->concat(matrix);
+}
+
+void SkProxyCanvas::setMatrix(const SkMatrix& matrix) {
+ fProxy->setMatrix(matrix);
+}
+
+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);
+}
+
+bool SkProxyCanvas::clipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+ return fProxy->clipRegion(deviceRgn, op);
+}
+
+void SkProxyCanvas::drawPaint(const SkPaint& paint) {
+ fProxy->drawPaint(paint);
+}
+
+void SkProxyCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ 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);
+}
+
+void SkProxyCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
+ const SkPaint* paint) {
+ fProxy->drawBitmap(bitmap, x, y, paint);
+}
+
+void SkProxyCanvas::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ fProxy->drawBitmapRectToRect(bitmap, src, dst, paint);
+}
+
+void SkProxyCanvas::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m,
+ const SkPaint* paint) {
+ fProxy->drawBitmapMatrix(bitmap, m, paint);
+}
+
+void SkProxyCanvas::drawSprite(const SkBitmap& bitmap, int x, int y,
+ const SkPaint* paint) {
+ fProxy->drawSprite(bitmap, x, y, paint);
+}
+
+void SkProxyCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ fProxy->drawText(text, byteLength, x, y, paint);
+}
+
+void SkProxyCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ fProxy->drawPosText(text, byteLength, pos, paint);
+}
+
+void SkProxyCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ fProxy->drawPosTextH(text, byteLength, xpos, constY, paint);
+}
+
+void SkProxyCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ fProxy->drawTextOnPath(text, byteLength, path, matrix, paint);
+}
+
+void SkProxyCanvas::drawPicture(SkPicture& picture) {
+ fProxy->drawPicture(picture);
+}
+
+void SkProxyCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ fProxy->drawVertices(vmode, vertexCount, vertices, texs, colors,
+ xmode, indices, indexCount, paint);
+}
+
+void SkProxyCanvas::drawData(const void* data, size_t length) {
+ fProxy->drawData(data, length);
+}
+
+void SkProxyCanvas::beginCommentGroup(const char* description) {
+ fProxy->beginCommentGroup(description);
+}
+
+void SkProxyCanvas::addComment(const char* kywd, const char* value) {
+ fProxy->addComment(kywd, value);
+}
+
+void SkProxyCanvas::endCommentGroup() {
+ fProxy->endCommentGroup();
+}
+
+SkBounder* SkProxyCanvas::setBounder(SkBounder* bounder) {
+ return fProxy->setBounder(bounder);
+}
+
+SkDrawFilter* SkProxyCanvas::setDrawFilter(SkDrawFilter* filter) {
+ return fProxy->setDrawFilter(filter);
+}
diff --git a/utils/SkRTConf.cpp b/utils/SkRTConf.cpp
new file mode 100644
index 00000000..e0977fe4
--- /dev/null
+++ b/utils/SkRTConf.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRTConf.h"
+#include "SkOSFile.h"
+
+SkRTConfRegistry::SkRTConfRegistry(): fConfs(100) {
+
+ SkFILE *fp = sk_fopen(configFileLocation(), kRead_SkFILE_Flag);
+
+ if (!fp) {
+ return;
+ }
+
+ char line[1024];
+
+ while (!sk_feof(fp)) {
+
+ if (!sk_fgets(line, sizeof(line), fp)) {
+ break;
+ }
+
+ char *commentptr = strchr(line, '#');
+ if (commentptr == line) {
+ continue;
+ }
+ if (NULL != commentptr) {
+ *commentptr = '\0';
+ }
+
+ char sep[] = " \t\r\n";
+
+ char *keyptr = strtok(line, sep);
+ if (!keyptr) {
+ continue;
+ }
+
+ char *valptr = strtok(NULL, sep);
+ if (!valptr) {
+ continue;
+ }
+
+ SkString* key = new SkString(keyptr);
+ SkString* val = new SkString(valptr);
+
+ fConfigFileKeys.append(1, &key);
+ fConfigFileValues.append(1, &val);
+ }
+ sk_fclose(fp);
+}
+
+const char *SkRTConfRegistry::configFileLocation() const {
+ return "skia.conf"; // for now -- should probably do something fancier like home directories or whatever.
+}
+
+// dump all known runtime config options to the file with their default values.
+// to trigger this, make a config file of zero size.
+void SkRTConfRegistry::possiblyDumpFile() const {
+ const char *path = configFileLocation();
+ SkFILE *fp = sk_fopen(path, kRead_SkFILE_Flag);
+ if (!fp) {
+ return;
+ }
+ size_t configFileSize = sk_fgetsize(fp);
+ if (configFileSize == 0) {
+ printAll(path);
+ }
+ sk_fclose(fp);
+}
+
+// Run through every provided configuration option and print a warning if the user hasn't
+// declared a correponding configuration object somewhere.
+void SkRTConfRegistry::validate() const {
+ for (int i = 0 ; i < fConfigFileKeys.count() ; i++) {
+ if (!fConfs.find(fConfigFileKeys[i]->c_str())) {
+ SkDebugf("WARNING: You have config value %s in your configuration file, but I've never heard of that.\n", fConfigFileKeys[i]->c_str());
+ }
+ }
+}
+
+void SkRTConfRegistry::printAll(const char *fname) const {
+ SkWStream *o;
+
+ if (NULL != fname) {
+ o = new SkFILEWStream(fname);
+ } else {
+ o = new SkDebugWStream();
+ }
+
+ ConfMap::Iter iter(fConfs);
+ SkTDArray<SkRTConfBase *> *confArray;
+
+ while (iter.next(&confArray)) {
+ if (confArray->getAt(0)->isDefault()) {
+ o->writeText("# ");
+ }
+ confArray->getAt(0)->print(o);
+ o->newline();
+ }
+
+ delete o;
+}
+
+void SkRTConfRegistry::printNonDefault(const char *fname) const {
+ SkWStream *o;
+
+ if (NULL != fname) {
+ o = new SkFILEWStream(fname);
+ } else {
+ o = new SkDebugWStream();
+ }
+ ConfMap::Iter iter(fConfs);
+ SkTDArray<SkRTConfBase *> *confArray;
+
+ while (iter.next(&confArray)) {
+ if (!confArray->getAt(0)->isDefault()) {
+ confArray->getAt(0)->print(o);
+ o->newline();
+ }
+ }
+
+ delete o;
+}
+
+// register a configuration variable after its value has been set by the parser.
+// we maintain a vector of these things instead of just a single one because the
+// user might set the value after initialization time and we need to have
+// all the pointers lying around, not just one.
+void SkRTConfRegistry::registerConf(SkRTConfBase *conf) {
+ SkTDArray<SkRTConfBase *> *confArray;
+ if (fConfs.find(conf->getName(), &confArray)) {
+ if (!conf->equals(confArray->getAt(0))) {
+ SkDebugf("WARNING: Skia config \"%s\" was registered more than once in incompatible ways.\n", conf->getName());
+ } else {
+ confArray->append(1, &conf);
+ }
+ } else {
+ confArray = new SkTDArray<SkRTConfBase *>;
+ confArray->append(1, &conf);
+ fConfs.set(conf->getName(),confArray);
+ }
+}
+
+template <typename T> T doParse(const char *, bool *success ) {
+ SkDebugf("WARNING: Invoked non-specialized doParse function...\n");
+ if (success) {
+ *success = false;
+ }
+ return (T) 0;
+}
+
+template<> bool doParse<bool>(const char *s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ if (!strcmp(s,"1") || !strcmp(s,"true")) {
+ return true;
+ }
+ if (!strcmp(s,"0") || !strcmp(s,"false")) {
+ return false;
+ }
+ if (success) {
+ *success = false;
+ }
+ return false;
+}
+
+template<> const char * doParse<const char *>(const char * s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ return s;
+}
+
+template<> int doParse<int>(const char * s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ return atoi(s);
+}
+
+template<> unsigned int doParse<unsigned int>(const char * s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ return (unsigned int) atoi(s);
+}
+
+template<> float doParse<float>(const char * s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ return (float) atof(s);
+}
+
+template<> double doParse<double>(const char * s, bool *success) {
+ if (success) {
+ *success = true;
+ }
+ return atof(s);
+}
+
+static inline void str_replace(char *s, char search, char replace) {
+ for (char *ptr = s ; *ptr ; ptr++) {
+ if (*ptr == search) {
+ *ptr = replace;
+ }
+ }
+}
+
+template<typename T> bool SkRTConfRegistry::parse(const char *name, T* value) {
+ SkString *str = NULL;
+
+ for (int i = fConfigFileKeys.count() - 1 ; i >= 0; i--) {
+ if (fConfigFileKeys[i]->equals(name)) {
+ str = fConfigFileValues[i];
+ break;
+ }
+ }
+
+ SkString environment_variable("skia.");
+ environment_variable.append(name);
+
+ const char *environment_value = getenv(environment_variable.c_str());
+ if (environment_value) {
+ str->set(environment_value);
+ } else {
+ // apparently my shell doesn't let me have environment variables that
+ // have periods in them, so also let the user substitute underscores.
+ SkString underscore_environment_variable("skia_");
+ char *underscore_name = SkStrDup(name);
+ str_replace(underscore_name,'.','_');
+ underscore_environment_variable.append(underscore_name);
+ sk_free(underscore_name);
+ environment_value = getenv(underscore_environment_variable.c_str());
+ if (environment_value) {
+ str->set(environment_value);
+ }
+ }
+
+ if (!str) {
+ return false;
+ }
+
+ bool success;
+ T new_value = doParse<T>(str->c_str(),&success);
+ if (success) {
+ *value = new_value;
+ } else {
+ SkDebugf("WARNING: Couldn't parse value \'%s\' for variable \'%s\'\n", str->c_str(), name);
+ }
+ return success;
+}
+
+// need to explicitly instantiate the parsing function for every config type we might have...
+
+template bool SkRTConfRegistry::parse(const char *name, bool *value);
+template bool SkRTConfRegistry::parse(const char *name, int *value);
+template bool SkRTConfRegistry::parse(const char *name, unsigned int *value);
+template bool SkRTConfRegistry::parse(const char *name, float *value);
+template bool SkRTConfRegistry::parse(const char *name, double *value);
+template bool SkRTConfRegistry::parse(const char *name, const char **value);
+
+template <typename T> void SkRTConfRegistry::set(const char *name, T value) {
+
+ SkTDArray<SkRTConfBase *> *confArray;
+ if (!fConfs.find(name, &confArray)) {
+ SkDebugf("WARNING: Attempting to set configuration value \"%s\", but I've never heard of that.\n", name);
+ return;
+ }
+
+ for (SkRTConfBase **confBase = confArray->begin(); confBase != confArray->end(); confBase++) {
+ // static_cast here is okay because there's only one kind of child class.
+ SkRTConf<T> *concrete = static_cast<SkRTConf<T> *>(*confBase);
+
+ if (concrete) {
+ concrete->set(value);
+ }
+ }
+}
+
+template void SkRTConfRegistry::set(const char *name, bool value);
+template void SkRTConfRegistry::set(const char *name, int value);
+template void SkRTConfRegistry::set(const char *name, unsigned int value);
+template void SkRTConfRegistry::set(const char *name, float value);
+template void SkRTConfRegistry::set(const char *name, double value);
+template void SkRTConfRegistry::set(const char *name, char * value);
+
+SkRTConfRegistry &skRTConfRegistry() {
+ static SkRTConfRegistry r;
+ return r;
+}
diff --git a/utils/SkSHA1.cpp b/utils/SkSHA1.cpp
new file mode 100644
index 00000000..1abac064
--- /dev/null
+++ b/utils/SkSHA1.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The following code is based on the description in RFC 3174.
+ * http://www.ietf.org/rfc/rfc3174.txt
+ */
+
+#include "SkTypes.h"
+#include "SkSHA1.h"
+#include <string.h>
+
+/** SHA1 basic transformation. Transforms state based on block. */
+static void transform(uint32_t state[5], const uint8_t block[64]);
+
+/** Encodes input into output (5 big endian 32 bit values). */
+static void encode(uint8_t output[20], const uint32_t input[5]);
+
+/** Encodes input into output (big endian 64 bit value). */
+static void encode(uint8_t output[8], const uint64_t input);
+
+SkSHA1::SkSHA1() : byteCount(0) {
+ // These are magic numbers from the specification. The first four are the same as MD5.
+ this->state[0] = 0x67452301;
+ this->state[1] = 0xefcdab89;
+ this->state[2] = 0x98badcfe;
+ this->state[3] = 0x10325476;
+ this->state[4] = 0xc3d2e1f0;
+}
+
+void SkSHA1::update(const uint8_t* input, size_t inputLength) {
+ unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F);
+ unsigned int bufferAvailable = 64 - bufferIndex;
+
+ unsigned int inputIndex;
+ if (inputLength >= bufferAvailable) {
+ if (bufferIndex) {
+ memcpy(&this->buffer[bufferIndex], input, bufferAvailable);
+ transform(this->state, this->buffer);
+ inputIndex = bufferAvailable;
+ } else {
+ inputIndex = 0;
+ }
+
+ for (; inputIndex + 63 < inputLength; inputIndex += 64) {
+ transform(this->state, &input[inputIndex]);
+ }
+
+ bufferIndex = 0;
+ } else {
+ inputIndex = 0;
+ }
+
+ memcpy(&this->buffer[bufferIndex], &input[inputIndex], inputLength - inputIndex);
+
+ this->byteCount += inputLength;
+}
+
+void SkSHA1::finish(Digest& digest) {
+ // Get the number of bits before padding.
+ uint8_t bits[8];
+ encode(bits, this->byteCount << 3);
+
+ // Pad out to 56 mod 64.
+ unsigned int bufferIndex = (unsigned int)(this->byteCount & 0x3F);
+ unsigned int paddingLength = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
+ static uint8_t PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ this->update(PADDING, paddingLength);
+
+ // Append length (length before padding, will cause final update).
+ this->update(bits, 8);
+
+ // Write out digest.
+ encode(digest.data, this->state);
+
+#if defined(SK_SHA1_CLEAR_DATA)
+ // Clear state.
+ memset(this, 0, sizeof(*this));
+#endif
+}
+
+struct F1 { uint32_t operator()(uint32_t B, uint32_t C, uint32_t D) {
+ return (B & C) | ((~B) & D);
+ //return D ^ (B & (C ^ D));
+ //return (B & C) ^ ((~B) & D);
+ //return (B & C) + ((~B) & D);
+ //return _mm_or_ps(_mm_andnot_ps(B, D), _mm_and_ps(B, C)); //SSE2
+ //return vec_sel(D, C, B); //PPC
+}};
+
+struct F2 { uint32_t operator()(uint32_t B, uint32_t C, uint32_t D) {
+ return B ^ C ^ D;
+}};
+
+struct F3 { uint32_t operator()(uint32_t B, uint32_t C, uint32_t D) {
+ return (B & C) | (B & D) | (C & D);
+ //return (B & C) | (D & (B | C));
+ //return (B & C) | (D & (B ^ C));
+ //return (B & C) + (D & (B ^ C));
+ //return (B & C) ^ (B & D) ^ (C & D);
+}};
+
+/** Rotates x left n bits. */
+static inline uint32_t rotate_left(uint32_t x, uint8_t n) {
+ return (x << n) | (x >> (32 - n));
+}
+
+template <typename T>
+static inline void operation(T operation,
+ uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E,
+ uint32_t w, uint32_t k) {
+ E += rotate_left(A, 5) + operation(B, C, D) + w + k;
+ B = rotate_left(B, 30);
+}
+
+static void transform(uint32_t state[5], const uint8_t block[64]) {
+ uint32_t A = state[0], B = state[1], C = state[2], D = state[3], E = state[4];
+
+ // Round constants defined in SHA-1.
+ static const uint32_t K[] = {
+ 0x5A827999, //sqrt(2) * 2^30
+ 0x6ED9EBA1, //sqrt(3) * 2^30
+ 0x8F1BBCDC, //sqrt(5) * 2^30
+ 0xCA62C1D6, //sqrt(10) * 2^30
+ };
+
+ uint32_t W[80];
+
+ // Initialize the array W.
+ size_t i = 0;
+ for (size_t j = 0; i < 16; ++i, j += 4) {
+ W[i] = (((uint32_t)block[j ]) << 24) |
+ (((uint32_t)block[j+1]) << 16) |
+ (((uint32_t)block[j+2]) << 8) |
+ (((uint32_t)block[j+3]) );
+ }
+ for (; i < 80; ++i) {
+ W[i] = rotate_left(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
+ //The following is equivelent and speeds up SSE implementations, but slows non-SSE.
+ //W[i] = rotate_left(W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32], 2);
+ }
+
+ // Round 1
+ operation(F1(), A, B, C, D, E, W[ 0], K[0]);
+ operation(F1(), E, A, B, C, D, W[ 1], K[0]);
+ operation(F1(), D, E, A, B, C, W[ 2], K[0]);
+ operation(F1(), C, D, E, A, B, W[ 3], K[0]);
+ operation(F1(), B, C, D, E, A, W[ 4], K[0]);
+ operation(F1(), A, B, C, D, E, W[ 5], K[0]);
+ operation(F1(), E, A, B, C, D, W[ 6], K[0]);
+ operation(F1(), D, E, A, B, C, W[ 7], K[0]);
+ operation(F1(), C, D, E, A, B, W[ 8], K[0]);
+ operation(F1(), B, C, D, E, A, W[ 9], K[0]);
+ operation(F1(), A, B, C, D, E, W[10], K[0]);
+ operation(F1(), E, A, B, C, D, W[11], K[0]);
+ operation(F1(), D, E, A, B, C, W[12], K[0]);
+ operation(F1(), C, D, E, A, B, W[13], K[0]);
+ operation(F1(), B, C, D, E, A, W[14], K[0]);
+ operation(F1(), A, B, C, D, E, W[15], K[0]);
+ operation(F1(), E, A, B, C, D, W[16], K[0]);
+ operation(F1(), D, E, A, B, C, W[17], K[0]);
+ operation(F1(), C, D, E, A, B, W[18], K[0]);
+ operation(F1(), B, C, D, E, A, W[19], K[0]);
+
+ // Round 2
+ operation(F2(), A, B, C, D, E, W[20], K[1]);
+ operation(F2(), E, A, B, C, D, W[21], K[1]);
+ operation(F2(), D, E, A, B, C, W[22], K[1]);
+ operation(F2(), C, D, E, A, B, W[23], K[1]);
+ operation(F2(), B, C, D, E, A, W[24], K[1]);
+ operation(F2(), A, B, C, D, E, W[25], K[1]);
+ operation(F2(), E, A, B, C, D, W[26], K[1]);
+ operation(F2(), D, E, A, B, C, W[27], K[1]);
+ operation(F2(), C, D, E, A, B, W[28], K[1]);
+ operation(F2(), B, C, D, E, A, W[29], K[1]);
+ operation(F2(), A, B, C, D, E, W[30], K[1]);
+ operation(F2(), E, A, B, C, D, W[31], K[1]);
+ operation(F2(), D, E, A, B, C, W[32], K[1]);
+ operation(F2(), C, D, E, A, B, W[33], K[1]);
+ operation(F2(), B, C, D, E, A, W[34], K[1]);
+ operation(F2(), A, B, C, D, E, W[35], K[1]);
+ operation(F2(), E, A, B, C, D, W[36], K[1]);
+ operation(F2(), D, E, A, B, C, W[37], K[1]);
+ operation(F2(), C, D, E, A, B, W[38], K[1]);
+ operation(F2(), B, C, D, E, A, W[39], K[1]);
+
+ // Round 3
+ operation(F3(), A, B, C, D, E, W[40], K[2]);
+ operation(F3(), E, A, B, C, D, W[41], K[2]);
+ operation(F3(), D, E, A, B, C, W[42], K[2]);
+ operation(F3(), C, D, E, A, B, W[43], K[2]);
+ operation(F3(), B, C, D, E, A, W[44], K[2]);
+ operation(F3(), A, B, C, D, E, W[45], K[2]);
+ operation(F3(), E, A, B, C, D, W[46], K[2]);
+ operation(F3(), D, E, A, B, C, W[47], K[2]);
+ operation(F3(), C, D, E, A, B, W[48], K[2]);
+ operation(F3(), B, C, D, E, A, W[49], K[2]);
+ operation(F3(), A, B, C, D, E, W[50], K[2]);
+ operation(F3(), E, A, B, C, D, W[51], K[2]);
+ operation(F3(), D, E, A, B, C, W[52], K[2]);
+ operation(F3(), C, D, E, A, B, W[53], K[2]);
+ operation(F3(), B, C, D, E, A, W[54], K[2]);
+ operation(F3(), A, B, C, D, E, W[55], K[2]);
+ operation(F3(), E, A, B, C, D, W[56], K[2]);
+ operation(F3(), D, E, A, B, C, W[57], K[2]);
+ operation(F3(), C, D, E, A, B, W[58], K[2]);
+ operation(F3(), B, C, D, E, A, W[59], K[2]);
+
+ // Round 4
+ operation(F2(), A, B, C, D, E, W[60], K[3]);
+ operation(F2(), E, A, B, C, D, W[61], K[3]);
+ operation(F2(), D, E, A, B, C, W[62], K[3]);
+ operation(F2(), C, D, E, A, B, W[63], K[3]);
+ operation(F2(), B, C, D, E, A, W[64], K[3]);
+ operation(F2(), A, B, C, D, E, W[65], K[3]);
+ operation(F2(), E, A, B, C, D, W[66], K[3]);
+ operation(F2(), D, E, A, B, C, W[67], K[3]);
+ operation(F2(), C, D, E, A, B, W[68], K[3]);
+ operation(F2(), B, C, D, E, A, W[69], K[3]);
+ operation(F2(), A, B, C, D, E, W[70], K[3]);
+ operation(F2(), E, A, B, C, D, W[71], K[3]);
+ operation(F2(), D, E, A, B, C, W[72], K[3]);
+ operation(F2(), C, D, E, A, B, W[73], K[3]);
+ operation(F2(), B, C, D, E, A, W[74], K[3]);
+ operation(F2(), A, B, C, D, E, W[75], K[3]);
+ operation(F2(), E, A, B, C, D, W[76], K[3]);
+ operation(F2(), D, E, A, B, C, W[77], K[3]);
+ operation(F2(), C, D, E, A, B, W[78], K[3]);
+ operation(F2(), B, C, D, E, A, W[79], K[3]);
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+
+#if defined(SK_SHA1_CLEAR_DATA)
+ // Clear sensitive information.
+ memset(W, 0, sizeof(W));
+#endif
+}
+
+static void encode(uint8_t output[20], const uint32_t input[5]) {
+ for (size_t i = 0, j = 0; i < 5; i++, j += 4) {
+ output[j ] = (uint8_t)((input[i] >> 24) & 0xff);
+ output[j+1] = (uint8_t)((input[i] >> 16) & 0xff);
+ output[j+2] = (uint8_t)((input[i] >> 8) & 0xff);
+ output[j+3] = (uint8_t)((input[i] ) & 0xff);
+ }
+}
+
+static void encode(uint8_t output[8], const uint64_t input) {
+ output[0] = (uint8_t)((input >> 56) & 0xff);
+ output[1] = (uint8_t)((input >> 48) & 0xff);
+ output[2] = (uint8_t)((input >> 40) & 0xff);
+ output[3] = (uint8_t)((input >> 32) & 0xff);
+ output[4] = (uint8_t)((input >> 24) & 0xff);
+ output[5] = (uint8_t)((input >> 16) & 0xff);
+ output[6] = (uint8_t)((input >> 8) & 0xff);
+ output[7] = (uint8_t)((input ) & 0xff);
+}
diff --git a/utils/SkSHA1.h b/utils/SkSHA1.h
new file mode 100644
index 00000000..cf2cb8c6
--- /dev/null
+++ b/utils/SkSHA1.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSHA1_DEFINED
+#define SkSHA1_DEFINED
+
+#include "SkTypes.h"
+#include "SkEndian.h"
+#include "SkStream.h"
+
+//The following macros can be defined to affect the SHA1 code generated.
+//SK_SHA1_CLEAR_DATA causes all intermediate state to be overwritten with 0's.
+//SK_CPU_BENDIAN allows 32 bit <=> 8 bit conversions without copies (if alligned).
+//SK_CPU_FAST_UNALIGNED_ACCESS allows 32 bit <=> 8 bit conversions without copies if SK_CPU_BENDIAN.
+
+class SkSHA1 : public SkWStream {
+public:
+ SkSHA1();
+
+ /** Processes input, adding it to the digest.
+ * Note that this treats the buffer as a series of uint8_t values.
+ */
+ virtual bool write(const void* buffer, size_t size) SK_OVERRIDE {
+ update(reinterpret_cast<const uint8_t*>(buffer), size);
+ return true;
+ }
+
+ /** Processes input, adding it to the digest. Calling this after finish is undefined. */
+ void update(const uint8_t* input, size_t length);
+
+ struct Digest {
+ uint8_t data[20];
+ };
+
+ /** Computes and returns the digest. */
+ void finish(Digest& digest);
+
+private:
+ // number of bytes, modulo 2^64
+ uint64_t byteCount;
+
+ // state (ABCDE)
+ uint32_t state[5];
+
+ // input buffer
+ uint8_t buffer[64];
+};
+
+#endif
diff --git a/utils/SkTFitsIn.h b/utils/SkTFitsIn.h
new file mode 100644
index 00000000..1d04981c
--- /dev/null
+++ b/utils/SkTFitsIn.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTFitsIn_DEFINED
+#define SkTFitsIn_DEFINED
+
+#include "SkTypes.h"
+#include "SkTLogic.h"
+#include <limits>
+
+namespace sktfitsin {
+namespace Private {
+
+/** SkTHasMoreDigits::type = (digits(A) >= digits(B)) ? SkTrue : SkFalse. */
+template<typename A, typename B> struct SkTHasMoreDigits {
+ typedef SkTBool<std::numeric_limits<A>::digits >= std::numeric_limits<B>::digits> type;
+};
+
+/** A high or low side predicate which is used when it is statically known
+ * that source values are in the range of the Destination.
+ */
+template <typename S> struct SkTOutOfRange_False {
+ typedef SkFalse can_be_true;
+ typedef S source_type;
+ static bool apply(S s) {
+ return false;
+ }
+};
+
+/** A low side predicate which tests if the source value < Min(D).
+ * Assumes that Min(S) <= Min(D).
+ */
+template <typename D, typename S> struct SkTOutOfRange_LT_MinD {
+ typedef SkTrue can_be_true;
+ typedef S source_type;
+ static bool apply(S s) {
+ typedef typename SkTHasMoreDigits<S, D>::type precondition;
+ SK_COMPILE_ASSERT(precondition::value, SkTOutOfRange_LT_MinD__minS_gt_minD);
+
+ return s < static_cast<S>((std::numeric_limits<D>::min)());
+ }
+};
+
+/** A low side predicate which tests if the source value is less than 0. */
+template <typename D, typename S> struct SkTOutOfRange_LT_Zero {
+ typedef SkTrue can_be_true;
+ typedef S source_type;
+ static bool apply(S s) {
+ return s < static_cast<S>(0);
+ }
+};
+
+/** A high side predicate which tests if the source value > Max(D).
+ * Assumes that Max(S) >= Max(D).
+ */
+template <typename D, typename S> struct SkTOutOfRange_GT_MaxD {
+ typedef SkTrue can_be_true;
+ typedef S source_type;
+ static bool apply(S s) {
+ typedef typename SkTHasMoreDigits<S, D>::type precondition;
+ SK_COMPILE_ASSERT(precondition::value, SkTOutOfRange_GT_MaxD__maxS_lt_maxD);
+
+ return s > static_cast<S>((std::numeric_limits<D>::max)());
+ }
+};
+
+/** Composes two SkTOutOfRange predicates.
+ * First checks OutOfRange_Low then, if in range, OutOfRange_High.
+ */
+template<class OutOfRange_Low, class OutOfRange_High> struct SkTOutOfRange_Either {
+ typedef SkTrue can_be_true;
+ typedef typename OutOfRange_Low::source_type source_type;
+ static bool apply(source_type s) {
+ bool outOfRange = OutOfRange_Low::apply(s);
+ if (!outOfRange) {
+ outOfRange = OutOfRange_High::apply(s);
+ }
+ return outOfRange;
+ }
+};
+
+/** SkTCombineOutOfRange::type is an SkTOutOfRange_XXX type which is the
+ * optimal combination of OutOfRange_Low and OutOfRange_High.
+ */
+template<class OutOfRange_Low, class OutOfRange_High> struct SkTCombineOutOfRange {
+ typedef SkTOutOfRange_Either<OutOfRange_Low, OutOfRange_High> Both;
+ typedef SkTOutOfRange_False<typename OutOfRange_Low::source_type> Neither;
+
+ typedef typename OutOfRange_Low::can_be_true apply_low;
+ typedef typename OutOfRange_High::can_be_true apply_high;
+
+ typedef typename SkTMux<apply_low, apply_high,
+ Both, OutOfRange_Low, OutOfRange_High, Neither>::type type;
+};
+
+template<typename D, typename S, class OutOfRange_Low, class OutOfRange_High>
+struct SkTRangeChecker {
+ /** This is the method which is called at runtime to do the range check. */
+ static bool OutOfRange(S s) {
+ typedef typename SkTCombineOutOfRange<OutOfRange_Low, OutOfRange_High>::type Combined;
+ return Combined::apply(s);
+ }
+};
+
+/** SkTFitsIn_Unsigned2Unsiged::type is an SkTRangeChecker with an OutOfRange(S s) method
+ * the implementation of which is tailored for the source and destination types.
+ * Assumes that S and D are unsigned integer types.
+ */
+template<typename D, typename S> struct SkTFitsIn_Unsigned2Unsiged {
+ typedef SkTOutOfRange_False<S> OutOfRange_Low;
+ typedef SkTOutOfRange_GT_MaxD<D, S> OutOfRange_High;
+
+ typedef SkTRangeChecker<D, S, OutOfRange_Low, OutOfRange_High> HighSideOnlyCheck;
+ typedef SkTRangeChecker<D, S, SkTOutOfRange_False<S>, SkTOutOfRange_False<S> > NoCheck;
+
+ // If std::numeric_limits<D>::digits >= std::numeric_limits<S>::digits, nothing to check.
+ // This also protects the precondition of SkTOutOfRange_GT_MaxD.
+ typedef typename SkTHasMoreDigits<D, S>::type sourceFitsInDesitination;
+ typedef typename SkTIf<sourceFitsInDesitination, NoCheck, HighSideOnlyCheck>::type type;
+};
+
+/** SkTFitsIn_Signed2Signed::type is an SkTRangeChecker with an OutOfRange(S s) method
+ * the implementation of which is tailored for the source and destination types.
+ * Assumes that S and D are signed integer types.
+ */
+template<typename D, typename S> struct SkTFitsIn_Signed2Signed {
+ typedef SkTOutOfRange_LT_MinD<D, S> OutOfRange_Low;
+ typedef SkTOutOfRange_GT_MaxD<D, S> OutOfRange_High;
+
+ typedef SkTRangeChecker<D, S, OutOfRange_Low, OutOfRange_High> FullCheck;
+ typedef SkTRangeChecker<D, S, SkTOutOfRange_False<S>, SkTOutOfRange_False<S> > NoCheck;
+
+ // If std::numeric_limits<D>::digits >= std::numeric_limits<S>::digits, nothing to check.
+ // This also protects the precondition of SkTOutOfRange_LT_MinD and SkTOutOfRange_GT_MaxD.
+ typedef typename SkTHasMoreDigits<D, S>::type sourceFitsInDesitination;
+ typedef typename SkTIf<sourceFitsInDesitination, NoCheck, FullCheck>::type type;
+};
+
+/** SkTFitsIn_Signed2Unsigned::type is an SkTRangeChecker with an OutOfRange(S s) method
+ * the implementation of which is tailored for the source and destination types.
+ * Assumes that S is a signed integer type and D is an unsigned integer type.
+ */
+template<typename D, typename S> struct SkTFitsIn_Signed2Unsigned {
+ typedef SkTOutOfRange_LT_Zero<D, S> OutOfRange_Low;
+ typedef SkTOutOfRange_GT_MaxD<D, S> OutOfRange_High;
+
+ typedef SkTRangeChecker<D, S, OutOfRange_Low, OutOfRange_High> FullCheck;
+ typedef SkTRangeChecker<D, S, OutOfRange_Low, SkTOutOfRange_False<S> > LowSideOnlyCheck;
+
+ // If std::numeric_limits<D>::max() >= std::numeric_limits<S>::max(),
+ // no need to check the high side. (Until C++11, assume more digits means greater max.)
+ // This also protects the precondition of SkTOutOfRange_GT_MaxD.
+ typedef typename SkTHasMoreDigits<D, S>::type sourceCannotExceedDesitination;
+ typedef typename SkTIf<sourceCannotExceedDesitination, LowSideOnlyCheck, FullCheck>::type type;
+};
+
+/** SkTFitsIn_Unsigned2Signed::type is an SkTRangeChecker with an OutOfRange(S s) method
+ * the implementation of which is tailored for the source and destination types.
+ * Assumes that S is an usigned integer type and D is a signed integer type.
+ */
+template<typename D, typename S> struct SkTFitsIn_Unsigned2Signed {
+ typedef SkTOutOfRange_False<S> OutOfRange_Low;
+ typedef SkTOutOfRange_GT_MaxD<D, S> OutOfRange_High;
+
+ typedef SkTRangeChecker<D, S, OutOfRange_Low, OutOfRange_High> HighSideOnlyCheck;
+ typedef SkTRangeChecker<D, S, SkTOutOfRange_False<S>, SkTOutOfRange_False<S> > NoCheck;
+
+ // If std::numeric_limits<D>::max() >= std::numeric_limits<S>::max(), nothing to check.
+ // (Until C++11, assume more digits means greater max.)
+ // This also protects the precondition of SkTOutOfRange_GT_MaxD.
+ typedef typename SkTHasMoreDigits<D, S>::type sourceCannotExceedDesitination;
+ typedef typename SkTIf<sourceCannotExceedDesitination, NoCheck, HighSideOnlyCheck>::type type;
+};
+
+/** SkTFitsIn::type is an SkTRangeChecker with an OutOfRange(S s) method
+ * the implementation of which is tailored for the source and destination types.
+ * Assumes that S and D are integer types.
+ */
+template<typename D, typename S> struct SkTFitsIn {
+ // One of the following will be the 'selector' type.
+ typedef SkTFitsIn_Signed2Signed<D, S> S2S;
+ typedef SkTFitsIn_Signed2Unsigned<D, S> S2U;
+ typedef SkTFitsIn_Unsigned2Signed<D, S> U2S;
+ typedef SkTFitsIn_Unsigned2Unsiged<D, S> U2U;
+
+ typedef SkTBool<std::numeric_limits<S>::is_signed> S_is_signed;
+ typedef SkTBool<std::numeric_limits<D>::is_signed> D_is_signed;
+
+ typedef typename SkTMux<S_is_signed, D_is_signed, S2S, S2U, U2S, U2U>::type selector;
+ // This type is an SkTRangeChecker.
+ typedef typename selector::type type;
+};
+
+} // namespace Private
+} // namespace sktfitsin
+
+/** Returns true if the integer source value 's' will fit in the integer destination type 'D'. */
+template <typename D, typename S> inline bool SkTFitsIn(S s) {
+ SK_COMPILE_ASSERT(std::numeric_limits<S>::is_integer, SkTFitsIn_source_must_be_integer);
+ SK_COMPILE_ASSERT(std::numeric_limits<D>::is_integer, SkTFitsIn_destination_must_be_integer);
+
+ return !sktfitsin::Private::SkTFitsIn<D, S>::type::OutOfRange(s);
+}
+
+#endif
diff --git a/utils/SkTLogic.h b/utils/SkTLogic.h
new file mode 100644
index 00000000..bf9ee1ab
--- /dev/null
+++ b/utils/SkTLogic.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ *
+ * This header provides some of the helpers (std::integral_constant) and
+ * type transformations (std::conditional) which will become available with
+ * C++11 in the type_traits header.
+ *
+ * Because we lack constexpr, we cannot mimic
+ * std::integral_constant::'constexpr operator T()'.
+ * As a result we introduce SkTBool and SkTIf similar to Boost in order to
+ * minimize the visual noise of many uses of '::value'.
+ */
+
+#ifndef SkTLogic_DEFINED
+#define SkTLogic_DEFINED
+
+/** Represents a templated integer constant.
+ * Pre-C++11 version of std::integral_constant.
+ */
+template <typename T, T v> struct SkTIntegralConstant {
+ static const T value = v;
+ typedef T value_type;
+ typedef SkTIntegralConstant<T, v> type;
+};
+
+/** Convenience specialization of SkTIntegralConstant. */
+template <bool b> struct SkTBool : SkTIntegralConstant<bool, b> { };
+
+/** Pre-C++11 version of std::true_type. */
+typedef SkTBool<true> SkTrue;
+
+/** Pre-C++11 version of std::false_type. */
+typedef SkTBool<false> SkFalse;
+
+/** SkTIf_c::type = (condition) ? T : F;
+ * Pre-C++11 version of std::conditional.
+ */
+template <bool condition, typename T, typename F> struct SkTIf_c {
+ typedef F type;
+};
+template <typename T, typename F> struct SkTIf_c<true, T, F> {
+ typedef T type;
+};
+
+/** SkTIf::type = (Condition::value) ? T : F; */
+template <typename Condition, typename T, typename F> struct SkTIf {
+ typedef typename SkTIf_c<static_cast<bool>(Condition::value), T, F>::type type;
+};
+
+/** SkTMux::type = (a && b) ? Both : (a) ? A : (b) ? B : Neither; */
+template <typename a, typename b, typename Both, typename A, typename B, typename Neither>
+struct SkTMux {
+ typedef typename SkTIf<a, typename SkTIf<b, Both, A>::type,
+ typename SkTIf<b, B, Neither>::type>::type type;
+};
+
+#endif
diff --git a/utils/SkThreadPool.cpp b/utils/SkThreadPool.cpp
new file mode 100644
index 00000000..5fe10255
--- /dev/null
+++ b/utils/SkThreadPool.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 "SkRunnable.h"
+#include "SkThreadPool.h"
+#include "SkThreadUtils.h"
+#include "SkTypes.h"
+
+#if defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
+#include <unistd.h>
+#endif
+
+// Returns the number of cores on this machine.
+static int num_cores() {
+#if defined(SK_BUILD_FOR_WIN32)
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ return sysinfo.dwNumberOfProcessors;
+#elif defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_ANDROID)
+ return sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ return 1;
+#endif
+}
+
+SkThreadPool::SkThreadPool(int count)
+: fDone(false) {
+ if (count < 0) count = num_cores();
+ // 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/utils/SkThreadUtils.h b/utils/SkThreadUtils.h
new file mode 100644
index 00000000..89639815
--- /dev/null
+++ b/utils/SkThreadUtils.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 SkThreadUtils_DEFINED
+#define SkThreadUtils_DEFINED
+
+#include "SkTypes.h"
+
+class SkThread : SkNoncopyable {
+public:
+ typedef void (*entryPointProc)(void*);
+
+ SkThread(entryPointProc entryPoint, void* data = NULL);
+
+ /**
+ * Non-virtual, do not subclass.
+ */
+ ~SkThread();
+
+ /**
+ * Starts the thread. Returns false if the thread could not be started.
+ */
+ bool start();
+
+ /**
+ * Waits for the thread to finish.
+ * If the thread has not started, returns immediately.
+ */
+ void join();
+
+ /**
+ * SkThreads with an affinity for the same processor will attempt to run cache
+ * locally with each other. SkThreads with an affinity for different processors
+ * will attempt to run on different cores. Returns false if the request failed.
+ */
+ bool setProcessorAffinity(unsigned int processor);
+
+private:
+ void* fData;
+};
+
+#endif
diff --git a/utils/SkThreadUtils_pthread.cpp b/utils/SkThreadUtils_pthread.cpp
new file mode 100644
index 00000000..7dec907d
--- /dev/null
+++ b/utils/SkThreadUtils_pthread.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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"
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <pthread.h>
+#include <signal.h>
+
+PThreadEvent::PThreadEvent() : fConditionFlag(false) {
+ pthread_cond_init(&fCondition, NULL);
+ pthread_mutex_init(&fConditionMutex, NULL);
+}
+PThreadEvent::~PThreadEvent() {
+ pthread_mutex_destroy(&fConditionMutex);
+ pthread_cond_destroy(&fCondition);
+}
+void PThreadEvent::trigger() {
+ pthread_mutex_lock(&fConditionMutex);
+ fConditionFlag = true;
+ pthread_cond_signal(&fCondition);
+ pthread_mutex_unlock(&fConditionMutex);
+}
+void PThreadEvent::wait() {
+ pthread_mutex_lock(&fConditionMutex);
+ while (!fConditionFlag) {
+ pthread_cond_wait(&fCondition, &fConditionMutex);
+ }
+ pthread_mutex_unlock(&fConditionMutex);
+}
+bool PThreadEvent::isTriggered() {
+ bool currentFlag;
+ pthread_mutex_lock(&fConditionMutex);
+ currentFlag = fConditionFlag;
+ pthread_mutex_unlock(&fConditionMutex);
+ return currentFlag;
+}
+
+SkThread_PThreadData::SkThread_PThreadData(SkThread::entryPointProc entryPoint, void* data)
+ : fPThread()
+ , fValidPThread(false)
+ , fParam(data)
+ , fEntryPoint(entryPoint)
+{
+ pthread_attr_init(&fAttr);
+ pthread_attr_setdetachstate(&fAttr, PTHREAD_CREATE_JOINABLE);
+}
+
+SkThread_PThreadData::~SkThread_PThreadData() {
+ pthread_attr_destroy(&fAttr);
+}
+
+static void* thread_start(void* arg) {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(arg);
+ // Wait for start signal
+ pthreadData->fStarted.wait();
+
+ // Call entry point only if thread was not canceled before starting.
+ if (!pthreadData->fCanceled.isTriggered()) {
+ pthreadData->fEntryPoint(pthreadData->fParam);
+ }
+ return NULL;
+}
+
+SkThread::SkThread(entryPointProc entryPoint, void* data) {
+ SkThread_PThreadData* pthreadData = new SkThread_PThreadData(entryPoint, data);
+ fData = pthreadData;
+
+ int ret = pthread_create(&(pthreadData->fPThread),
+ &(pthreadData->fAttr),
+ thread_start,
+ pthreadData);
+
+ pthreadData->fValidPThread = (0 == ret);
+}
+
+SkThread::~SkThread() {
+ if (fData != NULL) {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+ // If created thread but start was never called, kill the thread.
+ if (pthreadData->fValidPThread && !pthreadData->fStarted.isTriggered()) {
+ pthreadData->fCanceled.trigger();
+ if (this->start()) {
+ this->join();
+ }
+ }
+ delete pthreadData;
+ }
+}
+
+bool SkThread::start() {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+ if (!pthreadData->fValidPThread) {
+ return false;
+ }
+
+ if (pthreadData->fStarted.isTriggered()) {
+ return false;
+ }
+ pthreadData->fStarted.trigger();
+ return true;
+}
+
+void SkThread::join() {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+ if (!pthreadData->fValidPThread || !pthreadData->fStarted.isTriggered()) {
+ return;
+ }
+
+ pthread_join(pthreadData->fPThread, NULL);
+}
diff --git a/utils/SkThreadUtils_pthread.h b/utils/SkThreadUtils_pthread.h
new file mode 100644
index 00000000..3e102027
--- /dev/null
+++ b/utils/SkThreadUtils_pthread.h
@@ -0,0 +1,43 @@
+/*
+ * 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 SkThreadUtils_PThreadData_DEFINED
+#define SkThreadUtils_PThreadData_DEFINED
+
+#include "SkThreadUtils.h"
+#include <pthread.h>
+
+class PThreadEvent : SkNoncopyable {
+public:
+ PThreadEvent();
+ ~PThreadEvent();
+ void trigger();
+ void wait();
+ bool isTriggered();
+
+private:
+ pthread_cond_t fCondition;
+ pthread_mutex_t fConditionMutex;
+ bool fConditionFlag;
+};
+
+class SkThread_PThreadData : SkNoncopyable {
+public:
+ SkThread_PThreadData(SkThread::entryPointProc entryPoint, void* data);
+ ~SkThread_PThreadData();
+ pthread_t fPThread;
+ bool fValidPThread;
+ PThreadEvent fStarted;
+ PThreadEvent fCanceled;
+
+ pthread_attr_t fAttr;
+
+ void* fParam;
+ SkThread::entryPointProc fEntryPoint;
+};
+
+#endif
diff --git a/utils/SkThreadUtils_pthread_linux.cpp b/utils/SkThreadUtils_pthread_linux.cpp
new file mode 100644
index 00000000..4a03cb82
--- /dev/null
+++ b/utils/SkThreadUtils_pthread_linux.cpp
@@ -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 _GNU_SOURCE
+#define _GNU_SOURCE //for pthread_setaffinity_np
+#endif
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <pthread.h>
+
+static int nth_set_cpu(unsigned int n, cpu_set_t* cpuSet) {
+ n %= CPU_COUNT(cpuSet);
+ for (unsigned int setCpusSeen = 0, currentCpu = 0; true; ++currentCpu) {
+ if (CPU_ISSET(currentCpu, cpuSet)) {
+ ++setCpusSeen;
+ if (setCpusSeen > n) {
+ return currentCpu;
+ }
+ }
+ }
+}
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+ if (!pthreadData->fValidPThread) {
+ return false;
+ }
+
+ cpu_set_t parentCpuset;
+ if (0 != pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &parentCpuset)) {
+ return false;
+ }
+
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(nth_set_cpu(processor, &parentCpuset), &cpuset);
+ return 0 == pthread_setaffinity_np(pthreadData->fPThread,
+ sizeof(cpu_set_t),
+ &cpuset);
+}
diff --git a/utils/SkThreadUtils_pthread_mach.cpp b/utils/SkThreadUtils_pthread_mach.cpp
new file mode 100644
index 00000000..0f6e2639
--- /dev/null
+++ b/utils/SkThreadUtils_pthread_mach.cpp
@@ -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.
+ */
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <mach/mach.h>
+#include <mach/thread_policy.h>
+#include <pthread.h>
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+ SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+ if (!pthreadData->fValidPThread) {
+ return false;
+ }
+
+ mach_port_t tid = pthread_mach_thread_np(pthreadData->fPThread);
+
+ thread_affinity_policy_data_t policy;
+ policy.affinity_tag = processor;
+
+ return 0 == thread_policy_set(tid,
+ THREAD_AFFINITY_POLICY,
+ (thread_policy_t) &policy,
+ THREAD_AFFINITY_POLICY_COUNT);
+}
diff --git a/utils/SkThreadUtils_pthread_other.cpp b/utils/SkThreadUtils_pthread_other.cpp
new file mode 100644
index 00000000..a3973f1d
--- /dev/null
+++ b/utils/SkThreadUtils_pthread_other.cpp
@@ -0,0 +1,12 @@
+/*
+ * 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 "SkThreadUtils.h"
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+ return false;
+}
diff --git a/utils/SkThreadUtils_win.cpp b/utils/SkThreadUtils_win.cpp
new file mode 100644
index 00000000..a064d3b6
--- /dev/null
+++ b/utils/SkThreadUtils_win.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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"
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_win.h"
+
+SkThread_WinData::SkThread_WinData(SkThread::entryPointProc entryPoint, void* data)
+ : fHandle(NULL)
+ , fParam(data)
+ , fThreadId(0)
+ , fEntryPoint(entryPoint)
+ , fStarted(false)
+{
+ fCancelEvent = CreateEvent(
+ NULL, // default security attributes
+ false, //auto reset
+ false, //not signaled
+ NULL); //no name
+}
+
+SkThread_WinData::~SkThread_WinData() {
+ CloseHandle(fCancelEvent);
+}
+
+static DWORD WINAPI thread_start(LPVOID data) {
+ SkThread_WinData* winData = static_cast<SkThread_WinData*>(data);
+
+ //See if this thread was canceled before starting.
+ if (WaitForSingleObject(winData->fCancelEvent, 0) == WAIT_OBJECT_0) {
+ return 0;
+ }
+
+ winData->fEntryPoint(winData->fParam);
+ return 0;
+}
+
+SkThread::SkThread(entryPointProc entryPoint, void* data) {
+ SkThread_WinData* winData = new SkThread_WinData(entryPoint, data);
+ fData = winData;
+
+ if (NULL == winData->fCancelEvent) {
+ return;
+ }
+
+ winData->fHandle = CreateThread(
+ NULL, // default security attributes
+ 0, // use default stack size
+ thread_start, // thread function name (proxy)
+ winData, // argument to thread function (proxy args)
+ CREATE_SUSPENDED, // create suspended so affinity can be set
+ &winData->fThreadId); // returns the thread identifier
+}
+
+SkThread::~SkThread() {
+ if (fData != NULL) {
+ SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+ // If created thread but start was never called, kill the thread.
+ if (winData->fHandle != NULL && !winData->fStarted) {
+ if (SetEvent(winData->fCancelEvent) != 0) {
+ if (this->start()) {
+ this->join();
+ }
+ } else {
+ //kill with prejudice
+ TerminateThread(winData->fHandle, -1);
+ }
+ }
+ delete winData;
+ }
+}
+
+bool SkThread::start() {
+ SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+ if (NULL == winData->fHandle) {
+ return false;
+ }
+
+ if (winData->fStarted) {
+ return false;
+ }
+ winData->fStarted = -1 != ResumeThread(winData->fHandle);
+ return winData->fStarted;
+}
+
+void SkThread::join() {
+ SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+ if (NULL == winData->fHandle || !winData->fStarted) {
+ return;
+ }
+
+ WaitForSingleObject(winData->fHandle, INFINITE);
+}
+
+static unsigned int num_bits_set(DWORD_PTR mask) {
+ unsigned int count;
+ for (count = 0; mask; ++count) {
+ mask &= mask - 1;
+ }
+ return count;
+}
+
+static unsigned int nth_set_bit(unsigned int n, DWORD_PTR mask) {
+ n %= num_bits_set(mask);
+ for (unsigned int setBitsSeen = 0, currentBit = 0; true; ++currentBit) {
+ if (mask & (static_cast<DWORD_PTR>(1) << currentBit)) {
+ ++setBitsSeen;
+ if (setBitsSeen > n) {
+ return currentBit;
+ }
+ }
+ }
+}
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+ SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+ if (NULL == winData->fHandle) {
+ return false;
+ }
+
+ DWORD_PTR processAffinityMask;
+ DWORD_PTR systemAffinityMask;
+ if (0 == GetProcessAffinityMask(GetCurrentProcess(),
+ &processAffinityMask,
+ &systemAffinityMask)) {
+ return false;
+ }
+
+ DWORD_PTR threadAffinityMask = 1 << nth_set_bit(processor, processAffinityMask);
+ return 0 != SetThreadAffinityMask(winData->fHandle, threadAffinityMask);
+}
diff --git a/utils/SkThreadUtils_win.h b/utils/SkThreadUtils_win.h
new file mode 100644
index 00000000..51b32fad
--- /dev/null
+++ b/utils/SkThreadUtils_win.h
@@ -0,0 +1,28 @@
+/*
+ * 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 SkThreadUtils_WinData_DEFINED
+#define SkThreadUtils_WinData_DEFINED
+
+#include "SkTypes.h"
+
+#include "SkThreadUtils.h"
+
+class SkThread_WinData : SkNoncopyable {
+public:
+ SkThread_WinData(SkThread::entryPointProc entryPoint, void* data);
+ ~SkThread_WinData();
+ HANDLE fHandle;
+ HANDLE fCancelEvent;
+
+ LPVOID fParam;
+ DWORD fThreadId;
+ SkThread::entryPointProc fEntryPoint;
+ bool fStarted;
+};
+
+#endif
diff --git a/utils/SkUnitMappers.cpp b/utils/SkUnitMappers.cpp
new file mode 100644
index 00000000..ceff9ca0
--- /dev/null
+++ b/utils/SkUnitMappers.cpp
@@ -0,0 +1,61 @@
+
+/*
+ * 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 "SkUnitMappers.h"
+#include "SkFlattenableBuffers.h"
+
+SK_DEFINE_INST_COUNT(SkUnitMapper)
+
+SkDiscreteMapper::SkDiscreteMapper(int segments) {
+ if (segments < 2) {
+ fSegments = 0;
+ fScale = 0;
+ } else {
+ if (segments > 0xFFFF) {
+ segments = 0xFFFF;
+ }
+ fSegments = segments;
+ fScale = SK_Fract1 / (segments - 1);
+ }
+}
+
+uint16_t SkDiscreteMapper::mapUnit16(uint16_t input) {
+ SkFixed x = input * fSegments >> 16;
+ x = x * fScale >> 14;
+ x += x << 15 >> 31; // map 0x10000 to 0xFFFF
+ return SkToU16(x);
+}
+
+SkDiscreteMapper::SkDiscreteMapper(SkFlattenableReadBuffer& rb)
+ : SkUnitMapper(rb) {
+ fSegments = rb.readInt();
+ fScale = rb.read32();
+}
+
+void SkDiscreteMapper::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+
+ wb.writeInt(fSegments);
+ wb.write32(fScale);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint16_t SkCosineMapper::mapUnit16(uint16_t input)
+{
+ /* we want to call cosine(input * pi/2) treating input as [0...1)
+ however, the straight multitply would overflow 32bits since input is
+ 16bits and pi/2 is 17bits, so we shift down our pi const before we mul
+ */
+ SkFixed rads = (unsigned)(input * (SK_FixedPI >> 2)) >> 15;
+ SkFixed x = SkFixedCos(rads);
+ x += x << 15 >> 31; // map 0x10000 to 0xFFFF
+ return SkToU16(x);
+}
+
+SkCosineMapper::SkCosineMapper(SkFlattenableReadBuffer& rb)
+ : SkUnitMapper(rb) {}
diff --git a/utils/android/ashmem.cpp b/utils/android/ashmem.cpp
new file mode 100644
index 00000000..461c0623
--- /dev/null
+++ b/utils/android/ashmem.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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);
+}
+
+int ashmem_purge_all_caches(int fd)
+{
+ return ioctl(fd, ASHMEM_PURGE_ALL_CACHES, NULL);
+}
diff --git a/utils/android/ashmem.h b/utils/android/ashmem.h
new file mode 100644
index 00000000..278192b4
--- /dev/null
+++ b/utils/android/ashmem.h
@@ -0,0 +1,44 @@
+/*
+ * 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);
+int ashmem_purge_all_caches(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/utils/debugger/SkDebugCanvas.cpp b/utils/debugger/SkDebugCanvas.cpp
new file mode 100644
index 00000000..0e07084e
--- /dev/null
+++ b/utils/debugger/SkDebugCanvas.cpp
@@ -0,0 +1,421 @@
+
+/*
+ * 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 "SkColorPriv.h"
+#include "SkDebugCanvas.h"
+#include "SkDrawCommand.h"
+#include "SkDrawFilter.h"
+#include "SkDevice.h"
+#include "SkXfermode.h"
+
+static SkBitmap make_noconfig_bm(int width, int height) {
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kNo_Config, width, height);
+ return bm;
+}
+
+SkDebugCanvas::SkDebugCanvas(int width, int height)
+ : INHERITED(make_noconfig_bm(width, height))
+ , fOverdrawViz(false)
+ , fOverdrawFilter(NULL)
+ , fOutstandingSaveCount(0) {
+ // TODO(chudy): Free up memory from all draw commands in destructor.
+ fWidth = width;
+ fHeight = height;
+ // do we need fBm anywhere?
+ fBm.setConfig(SkBitmap::kNo_Config, fWidth, fHeight);
+ fFilter = false;
+ fIndex = 0;
+ fUserMatrix.reset();
+}
+
+SkDebugCanvas::~SkDebugCanvas() {
+ fCommandVector.deleteAll();
+ SkSafeUnref(fOverdrawFilter);
+}
+
+void SkDebugCanvas::addDrawCommand(SkDrawCommand* command) {
+ fCommandVector.push(command);
+}
+
+void SkDebugCanvas::draw(SkCanvas* canvas) {
+ if(!fCommandVector.isEmpty()) {
+ for (int i = 0; i < fCommandVector.count(); i++) {
+ if (fCommandVector[i]->isVisible()) {
+ fCommandVector[i]->execute(canvas);
+ }
+ }
+ }
+ fIndex = fCommandVector.count() - 1;
+}
+
+void SkDebugCanvas::applyUserTransform(SkCanvas* canvas) {
+ canvas->concat(fUserMatrix);
+}
+
+int SkDebugCanvas::getCommandAtPoint(int x, int y, int index) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ bitmap.allocPixels();
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(SkIntToScalar(-x), SkIntToScalar(-y));
+ applyUserTransform(&canvas);
+
+ int layer = 0;
+ SkColor prev = bitmap.getColor(0,0);
+ for (int i = 0; i < index; i++) {
+ if (fCommandVector[i]->isVisible()) {
+ fCommandVector[i]->execute(&canvas);
+ }
+ if (prev != bitmap.getColor(0,0)) {
+ layer = i;
+ }
+ prev = bitmap.getColor(0,0);
+ }
+ return layer;
+}
+
+static SkPMColor OverdrawXferModeProc(SkPMColor src, SkPMColor dst) {
+ // This table encodes the color progression of the overdraw visualization
+ static const SkPMColor gTable[] = {
+ SkPackARGB32(0x00, 0x00, 0x00, 0x00),
+ SkPackARGB32(0xFF, 128, 158, 255),
+ SkPackARGB32(0xFF, 170, 185, 212),
+ SkPackARGB32(0xFF, 213, 195, 170),
+ SkPackARGB32(0xFF, 255, 192, 127),
+ SkPackARGB32(0xFF, 255, 185, 85),
+ SkPackARGB32(0xFF, 255, 165, 42),
+ SkPackARGB32(0xFF, 255, 135, 0),
+ SkPackARGB32(0xFF, 255, 95, 0),
+ SkPackARGB32(0xFF, 255, 50, 0),
+ SkPackARGB32(0xFF, 255, 0, 0)
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gTable)-1; ++i) {
+ if (gTable[i] == dst) {
+ return gTable[i+1];
+ }
+ }
+
+ return gTable[SK_ARRAY_COUNT(gTable)-1];
+}
+
+// The OverdrawFilter modifies every paint to use an SkProcXfermode which
+// in turn invokes OverdrawXferModeProc
+class OverdrawFilter : public SkDrawFilter {
+public:
+ OverdrawFilter() {
+ fXferMode = new SkProcXfermode(OverdrawXferModeProc);
+ }
+
+ virtual ~OverdrawFilter() {
+ delete fXferMode;
+ }
+
+ virtual bool filter(SkPaint* p, Type) SK_OVERRIDE {
+ p->setXfermode(fXferMode);
+ return true;
+ }
+
+protected:
+ SkXfermode* fXferMode;
+
+private:
+ typedef SkDrawFilter INHERITED;
+};
+
+void SkDebugCanvas::drawTo(SkCanvas* canvas, int index) {
+ SkASSERT(!fCommandVector.isEmpty());
+ SkASSERT(index < fCommandVector.count());
+ int i;
+
+ // This only works assuming the canvas and device are the same ones that
+ // were previously drawn into because they need to preserve all saves
+ // and restores.
+ if (fIndex < index) {
+ i = fIndex + 1;
+ } else {
+ for (int j = 0; j < fOutstandingSaveCount; j++) {
+ canvas->restore();
+ }
+ i = 0;
+ canvas->clear(SK_ColorTRANSPARENT);
+ canvas->resetMatrix();
+ SkRect rect = SkRect::MakeWH(SkIntToScalar(fWidth),
+ SkIntToScalar(fHeight));
+ canvas->clipRect(rect, SkRegion::kReplace_Op );
+ applyUserTransform(canvas);
+ fOutstandingSaveCount = 0;
+ }
+
+ // The setting of the draw filter has to go here (rather than in
+ // SkRasterWidget) due to the canvas restores this class performs.
+ // Since the draw filter is stored in the layer stack if we
+ // call setDrawFilter on anything but the root layer odd things happen.
+ if (fOverdrawViz) {
+ if (NULL == fOverdrawFilter) {
+ fOverdrawFilter = new OverdrawFilter;
+ }
+
+ if (fOverdrawFilter != canvas->getDrawFilter()) {
+ canvas->setDrawFilter(fOverdrawFilter);
+ }
+ } else {
+ canvas->setDrawFilter(NULL);
+ }
+
+ for (; i <= index; i++) {
+ if (i == index && fFilter) {
+ SkPaint p;
+ p.setColor(0xAAFFFFFF);
+ canvas->save();
+ canvas->resetMatrix();
+ SkRect mask;
+ mask.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(fWidth), SkIntToScalar(fHeight));
+ canvas->clipRect(mask, SkRegion::kReplace_Op, false);
+ canvas->drawRectCoords(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(fWidth), SkIntToScalar(fHeight), p);
+ canvas->restore();
+ }
+
+ if (fCommandVector[i]->isVisible()) {
+ fCommandVector[i]->execute(canvas);
+ fCommandVector[i]->trackSaveState(&fOutstandingSaveCount);
+ }
+ }
+ fMatrix = canvas->getTotalMatrix();
+ fClip = canvas->getTotalClip().getBounds();
+ fIndex = index;
+}
+
+void SkDebugCanvas::deleteDrawCommandAt(int index) {
+ SkASSERT(index < fCommandVector.count());
+ delete fCommandVector[index];
+ fCommandVector.remove(index);
+}
+
+SkDrawCommand* SkDebugCanvas::getDrawCommandAt(int index) {
+ SkASSERT(index < fCommandVector.count());
+ return fCommandVector[index];
+}
+
+void SkDebugCanvas::setDrawCommandAt(int index, SkDrawCommand* command) {
+ SkASSERT(index < fCommandVector.count());
+ delete fCommandVector[index];
+ fCommandVector[index] = command;
+}
+
+SkTDArray<SkString*>* SkDebugCanvas::getCommandInfo(int index) {
+ SkASSERT(index < fCommandVector.count());
+ return fCommandVector[index]->Info();
+}
+
+bool SkDebugCanvas::getDrawCommandVisibilityAt(int index) {
+ SkASSERT(index < fCommandVector.count());
+ return fCommandVector[index]->isVisible();
+}
+
+const SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() const {
+ return fCommandVector;
+}
+
+SkTDArray <SkDrawCommand*>& SkDebugCanvas::getDrawCommands() {
+ return fCommandVector;
+}
+
+// TODO(chudy): Free command string memory.
+SkTArray<SkString>* SkDebugCanvas::getDrawCommandsAsStrings() const {
+ SkTArray<SkString>* commandString = new SkTArray<SkString>(fCommandVector.count());
+ if (!fCommandVector.isEmpty()) {
+ for (int i = 0; i < fCommandVector.count(); i ++) {
+ commandString->push_back() = fCommandVector[i]->toString();
+ }
+ }
+ return commandString;
+}
+
+void SkDebugCanvas::toggleFilter(bool toggle) {
+ fFilter = toggle;
+}
+
+void SkDebugCanvas::clear(SkColor color) {
+ addDrawCommand(new SkClearCommand(color));
+}
+
+bool SkDebugCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
+ addDrawCommand(new SkClipPathCommand(path, op, doAA));
+ return true;
+}
+
+bool SkDebugCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ addDrawCommand(new SkClipRectCommand(rect, op, doAA));
+ return true;
+}
+
+bool SkDebugCanvas::clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ addDrawCommand(new SkClipRRectCommand(rrect, op, doAA));
+ return true;
+}
+
+bool SkDebugCanvas::clipRegion(const SkRegion& region, SkRegion::Op op) {
+ addDrawCommand(new SkClipRegionCommand(region, op));
+ return true;
+}
+
+bool SkDebugCanvas::concat(const SkMatrix& matrix) {
+ addDrawCommand(new SkConcatCommand(matrix));
+ return true;
+}
+
+void SkDebugCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar left,
+ SkScalar top, const SkPaint* paint = NULL) {
+ addDrawCommand(new SkDrawBitmapCommand(bitmap, left, top, paint));
+}
+
+void SkDebugCanvas::drawBitmapRectToRect(const SkBitmap& bitmap,
+ const SkRect* src, const SkRect& dst, const SkPaint* paint) {
+ addDrawCommand(new SkDrawBitmapRectCommand(bitmap, src, dst, paint));
+}
+
+void SkDebugCanvas::drawBitmapMatrix(const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint* paint) {
+ addDrawCommand(new SkDrawBitmapMatrixCommand(bitmap, matrix, paint));
+}
+
+void SkDebugCanvas::drawBitmapNine(const SkBitmap& bitmap,
+ const SkIRect& center, const SkRect& dst, const SkPaint* paint) {
+ addDrawCommand(new SkDrawBitmapNineCommand(bitmap, center, dst, paint));
+}
+
+void SkDebugCanvas::drawData(const void* data, size_t length) {
+ addDrawCommand(new SkDrawDataCommand(data, length));
+}
+
+void SkDebugCanvas::beginCommentGroup(const char* description) {
+ addDrawCommand(new SkBeginCommentGroupCommand(description));
+}
+
+void SkDebugCanvas::addComment(const char* kywd, const char* value) {
+ addDrawCommand(new SkCommentCommand(kywd, value));
+}
+
+void SkDebugCanvas::endCommentGroup() {
+ addDrawCommand(new SkEndCommentGroupCommand());
+}
+
+void SkDebugCanvas::drawOval(const SkRect& oval, const SkPaint& paint) {
+ addDrawCommand(new SkDrawOvalCommand(oval, paint));
+}
+
+void SkDebugCanvas::drawPaint(const SkPaint& paint) {
+ addDrawCommand(new SkDrawPaintCommand(paint));
+}
+
+void SkDebugCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ addDrawCommand(new SkDrawPathCommand(path, paint));
+}
+
+void SkDebugCanvas::drawPicture(SkPicture& picture) {
+ addDrawCommand(new SkDrawPictureCommand(picture));
+}
+
+void SkDebugCanvas::drawPoints(PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
+}
+
+void SkDebugCanvas::drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ addDrawCommand(new SkDrawPosTextCommand(text, byteLength, pos, paint));
+}
+
+void SkDebugCanvas::drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY, const SkPaint& paint) {
+ addDrawCommand(
+ new SkDrawPosTextHCommand(text, byteLength, xpos, constY, paint));
+}
+
+void SkDebugCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
+ // NOTE(chudy): Messing up when renamed to DrawRect... Why?
+ addDrawCommand(new SkDrawRectCommand(rect, paint));
+}
+
+void SkDebugCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+ addDrawCommand(new SkDrawRRectCommand(rrect, paint));
+}
+
+void SkDebugCanvas::drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint = NULL) {
+ addDrawCommand(new SkDrawSpriteCommand(bitmap, left, top, paint));
+}
+
+void SkDebugCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ addDrawCommand(new SkDrawTextCommand(text, byteLength, x, y, paint));
+}
+
+void SkDebugCanvas::drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix, const SkPaint& paint) {
+ addDrawCommand(
+ new SkDrawTextOnPathCommand(text, byteLength, path, matrix, paint));
+}
+
+void SkDebugCanvas::drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[], const SkColor colors[],
+ SkXfermode*, const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ addDrawCommand(new SkDrawVerticesCommand(vmode, vertexCount, vertices,
+ texs, colors, NULL, indices, indexCount, paint));
+}
+
+void SkDebugCanvas::restore() {
+ addDrawCommand(new SkRestoreCommand());
+}
+
+bool SkDebugCanvas::rotate(SkScalar degrees) {
+ addDrawCommand(new SkRotateCommand(degrees));
+ return true;
+}
+
+int SkDebugCanvas::save(SaveFlags flags) {
+ addDrawCommand(new SkSaveCommand(flags));
+ return true;
+}
+
+int SkDebugCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint,
+ SaveFlags flags) {
+ addDrawCommand(new SkSaveLayerCommand(bounds, paint, flags));
+ return true;
+}
+
+bool SkDebugCanvas::scale(SkScalar sx, SkScalar sy) {
+ addDrawCommand(new SkScaleCommand(sx, sy));
+ return true;
+}
+
+void SkDebugCanvas::setMatrix(const SkMatrix& matrix) {
+ addDrawCommand(new SkSetMatrixCommand(matrix));
+}
+
+bool SkDebugCanvas::skew(SkScalar sx, SkScalar sy) {
+ addDrawCommand(new SkSkewCommand(sx, sy));
+ return true;
+}
+
+bool SkDebugCanvas::translate(SkScalar dx, SkScalar dy) {
+ addDrawCommand(new SkTranslateCommand(dx, dy));
+ return true;
+}
+
+void SkDebugCanvas::toggleCommand(int index, bool toggle) {
+ SkASSERT(index < fCommandVector.count());
+ fCommandVector[index]->setVisible(toggle);
+}
diff --git a/utils/debugger/SkDebugCanvas.h b/utils/debugger/SkDebugCanvas.h
new file mode 100644
index 00000000..5fb99aa8
--- /dev/null
+++ b/utils/debugger/SkDebugCanvas.h
@@ -0,0 +1,273 @@
+
+/*
+ * 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 SKDEBUGCANVAS_H_
+#define SKDEBUGCANVAS_H_
+
+#include "SkCanvas.h"
+#include "SkDrawCommand.h"
+#include "SkPicture.h"
+#include "SkTArray.h"
+#include "SkString.h"
+
+class SK_API SkDebugCanvas : public SkCanvas {
+public:
+ SkDebugCanvas(int width, int height);
+ virtual ~SkDebugCanvas();
+
+ void toggleFilter(bool toggle);
+
+ /**
+ * Enable or disable overdraw visualization
+ */
+ void setOverdrawViz(bool overdrawViz) { fOverdrawViz = overdrawViz; }
+
+ /**
+ Executes all draw calls to the canvas.
+ @param canvas The canvas being drawn to
+ */
+ void draw(SkCanvas* canvas);
+
+ /**
+ Executes the draw calls in the specified range.
+ @param canvas The canvas being drawn to
+ @param i The beginning of the range
+ @param j The end of the range
+ TODO(chudy): Implement
+ */
+ void drawRange(SkCanvas* canvas, int i, int j);
+
+ /**
+ Executes the draw calls up to the specified index.
+ @param canvas The canvas being drawn to
+ @param index The index of the final command being executed
+ */
+ void drawTo(SkCanvas* canvas, int index);
+
+ /**
+ Returns the most recently calculated transformation matrix
+ */
+ const SkMatrix& getCurrentMatrix() {
+ return fMatrix;
+ }
+
+ /**
+ Returns the most recently calculated clip
+ */
+ const SkIRect& getCurrentClip() {
+ return fClip;
+ }
+
+ /**
+ Returns the index of the last draw command to write to the pixel at (x,y)
+ */
+ int getCommandAtPoint(int x, int y, int index);
+
+ /**
+ Removes the command at the specified index
+ @param index The index of the command to delete
+ */
+ void deleteDrawCommandAt(int index);
+
+ /**
+ Returns the draw command at the given index.
+ @param index The index of the command
+ */
+ SkDrawCommand* getDrawCommandAt(int index);
+
+ /**
+ Sets the draw command for a given index.
+ @param index The index to overwrite
+ @param command The new command
+ */
+ void setDrawCommandAt(int index, SkDrawCommand* command);
+
+ /**
+ Returns information about the command at the given index.
+ @param index The index of the command
+ */
+ SkTDArray<SkString*>* getCommandInfo(int index);
+
+ /**
+ Returns the visibility of the command at the given index.
+ @param index The index of the command
+ */
+ bool getDrawCommandVisibilityAt(int index);
+
+ /**
+ Returns the vector of draw commands
+ DEPRECATED: please use getDrawCommandAt and getSize instead
+ */
+ const SkTDArray<SkDrawCommand*>& getDrawCommands() const;
+
+ /**
+ Returns the vector of draw commands. Do not use this entry
+ point - it is going away!
+ */
+ SkTDArray<SkDrawCommand*>& getDrawCommands();
+
+ /**
+ * Returns the string vector of draw commands
+ */
+ SkTArray<SkString>* getDrawCommandsAsStrings() const;
+
+ /**
+ Returns length of draw command vector.
+ */
+ int getSize() const {
+ return fCommandVector.count();
+ }
+
+ /**
+ Toggles the visibility / execution of the draw command at index i with
+ the value of toggle.
+ */
+ void toggleCommand(int index, bool toggle);
+
+ void setBounds(int width, int height) {
+ fWidth = width;
+ fHeight = height;
+ }
+
+ void setUserMatrix(SkMatrix matrix) {
+ fUserMatrix = matrix;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// Inherited from SkCanvas
+////////////////////////////////////////////////////////////////////////////////
+
+ virtual void clear(SkColor) SK_OVERRIDE;
+
+ virtual bool clipPath(const SkPath&, SkRegion::Op, bool) SK_OVERRIDE;
+
+ virtual bool clipRect(const SkRect&, SkRegion::Op, bool) SK_OVERRIDE;
+
+ virtual bool clipRRect(const SkRRect& rrect,
+ SkRegion::Op op = SkRegion::kIntersect_Op,
+ bool doAntiAlias = false) SK_OVERRIDE;
+
+ virtual bool clipRegion(const SkRegion& region, SkRegion::Op op) SK_OVERRIDE;
+
+ virtual bool concat(const SkMatrix& matrix) SK_OVERRIDE;
+
+ virtual void drawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
+ const SkPaint*) SK_OVERRIDE;
+
+ virtual void drawBitmapRectToRect(const SkBitmap&, const SkRect* src,
+ const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+
+ virtual void drawBitmapMatrix(const SkBitmap&, const SkMatrix&,
+ const SkPaint*) SK_OVERRIDE;
+
+ virtual void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint*) SK_OVERRIDE;
+
+ virtual void drawData(const void*, size_t) SK_OVERRIDE;
+
+ virtual void beginCommentGroup(const char* description) SK_OVERRIDE;
+
+ virtual void addComment(const char* kywd, const char* value) SK_OVERRIDE;
+
+ virtual void endCommentGroup() SK_OVERRIDE;
+
+ virtual void drawOval(const SkRect& oval, const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawPaint(const SkPaint& paint) SK_OVERRIDE;
+
+ virtual void drawPath(const SkPath& path, const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawPicture(SkPicture& picture) SK_OVERRIDE;
+
+ virtual void drawPoints(PointMode, size_t count, const SkPoint pts[],
+ const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawPosText(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawRect(const SkRect& rect, const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawRRect(const SkRRect& rrect, const SkPaint& paint) SK_OVERRIDE;
+
+ virtual void drawSprite(const SkBitmap&, int left, int top,
+ const SkPaint*) SK_OVERRIDE;
+
+ virtual void drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawTextOnPath(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint&) SK_OVERRIDE;
+
+ virtual void drawVertices(VertexMode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode*,
+ const uint16_t indices[], int indexCount,
+ const SkPaint&) SK_OVERRIDE;
+
+ virtual void restore() SK_OVERRIDE;
+
+ virtual bool rotate(SkScalar degrees) SK_OVERRIDE;
+
+ virtual int save(SaveFlags) SK_OVERRIDE;
+
+ virtual int saveLayer(const SkRect* bounds, const SkPaint*, SaveFlags) SK_OVERRIDE;
+
+ virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE;
+
+ virtual void setMatrix(const SkMatrix& matrix) SK_OVERRIDE;
+
+ virtual bool skew(SkScalar sx, SkScalar sy) SK_OVERRIDE;
+
+ virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE;
+
+ static const int kVizImageHeight = 256;
+ static const int kVizImageWidth = 256;
+
+private:
+ SkTDArray<SkDrawCommand*> fCommandVector;
+ int fHeight;
+ int fWidth;
+ SkBitmap fBm;
+ bool fFilter;
+ int fIndex;
+ SkMatrix fUserMatrix;
+ SkMatrix fMatrix;
+ SkIRect fClip;
+ bool fOverdrawViz;
+ SkDrawFilter* fOverdrawFilter;
+
+ /**
+ Number of unmatched save() calls at any point during a draw.
+ If there are any saveLayer() calls outstanding, we need to resolve
+ all of them, which in practice means resolving all save() calls,
+ to avoid corruption of our canvas.
+ */
+ int fOutstandingSaveCount;
+
+ /**
+ Adds the command to the classes vector of commands.
+ @param command The draw command for execution
+ */
+ void addDrawCommand(SkDrawCommand* command);
+
+ /**
+ Applies any panning and zooming the user has specified before
+ drawing anything else into the canvas.
+ */
+ void applyUserTransform(SkCanvas* canvas);
+
+ typedef SkCanvas INHERITED;
+};
+
+#endif
diff --git a/utils/debugger/SkDrawCommand.cpp b/utils/debugger/SkDrawCommand.cpp
new file mode 100644
index 00000000..ad66c8dc
--- /dev/null
+++ b/utils/debugger/SkDrawCommand.cpp
@@ -0,0 +1,878 @@
+
+/*
+ * 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 "SkDrawCommand.h"
+#include "SkObjectParser.h"
+
+// TODO(chudy): Refactor into non subclass model.
+
+SkDrawCommand::SkDrawCommand(DrawType type)
+ : fDrawType(type)
+ , fVisible(true) {
+}
+
+SkDrawCommand::SkDrawCommand() {
+ fVisible = true;
+}
+
+SkDrawCommand::~SkDrawCommand() {
+ fInfo.deleteAll();
+}
+
+const char* SkDrawCommand::GetCommandString(DrawType type) {
+ switch (type) {
+ case UNUSED: SkDEBUGFAIL("DrawType UNUSED\n"); break;
+ case DRAW_CLEAR: return "Clear";
+ case CLIP_PATH: return "Clip Path";
+ case CLIP_REGION: return "Clip Region";
+ case CLIP_RECT: return "Clip Rect";
+ case CLIP_RRECT: return "Clip RRect";
+ case CONCAT: return "Concat";
+ case DRAW_BITMAP: return "Draw Bitmap";
+ case DRAW_BITMAP_MATRIX: return "Draw Bitmap Matrix";
+ case DRAW_BITMAP_NINE: return "Draw Bitmap Nine";
+ case DRAW_BITMAP_RECT_TO_RECT: return "Draw Bitmap Rect";
+ case DRAW_DATA: return "Draw Data";
+ case DRAW_OVAL: return "Draw Oval";
+ case DRAW_PAINT: return "Draw Paint";
+ case DRAW_PATH: return "Draw Path";
+ case DRAW_PICTURE: return "Draw Picture";
+ case DRAW_POINTS: return "Draw Points";
+ case DRAW_POS_TEXT: return "Draw Pos Text";
+ case DRAW_POS_TEXT_H: return "Draw Pos Text H";
+ case DRAW_RECT: return "Draw Rect";
+ case DRAW_RRECT: return "Draw RRect";
+ case DRAW_SPRITE: return "Draw Sprite";
+ case DRAW_TEXT: return "Draw Text";
+ case DRAW_TEXT_ON_PATH: return "Draw Text On Path";
+ case DRAW_VERTICES: return "Draw Vertices";
+ case RESTORE: return "Restore";
+ case ROTATE: return "Rotate";
+ case SAVE: return "Save";
+ case SAVE_LAYER: return "Save Layer";
+ case SCALE: return "Scale";
+ case SET_MATRIX: return "Set Matrix";
+ case SKEW: return "Skew";
+ case TRANSLATE: return "Translate";
+ case NOOP: return "NoOp";
+ case BEGIN_COMMENT_GROUP: return "BeginCommentGroup";
+ case COMMENT: return "Comment";
+ case END_COMMENT_GROUP: return "EndCommentGroup";
+ default:
+ SkDebugf("DrawType error 0x%08x\n", type);
+ SkASSERT(0);
+ break;
+ }
+ SkDEBUGFAIL("DrawType UNUSED\n");
+ return NULL;
+}
+
+SkString SkDrawCommand::toString() {
+ return SkString(GetCommandString(fDrawType));
+}
+
+SkClearCommand::SkClearCommand(SkColor color) {
+ fColor = color;
+ fDrawType = DRAW_CLEAR;
+ fInfo.push(SkObjectParser::CustomTextToString("No Parameters"));
+}
+
+void SkClearCommand::execute(SkCanvas* canvas) {
+ canvas->clear(fColor);
+}
+
+namespace {
+
+void xlate_and_scale_to_bounds(SkCanvas* canvas, const SkRect& bounds) {
+ const SkISize& size = canvas->getDeviceSize();
+
+ static const SkScalar kInsetFrac = 0.9f; // Leave a border around object
+
+ canvas->translate(size.fWidth/2.0f, size.fHeight/2.0f);
+ if (bounds.width() > bounds.height()) {
+ canvas->scale(SkDoubleToScalar((kInsetFrac*size.fWidth)/bounds.width()),
+ SkDoubleToScalar((kInsetFrac*size.fHeight)/bounds.width()));
+ } else {
+ canvas->scale(SkDoubleToScalar((kInsetFrac*size.fWidth)/bounds.height()),
+ SkDoubleToScalar((kInsetFrac*size.fHeight)/bounds.height()));
+ }
+ canvas->translate(-bounds.centerX(), -bounds.centerY());
+}
+
+
+void render_path(SkCanvas* canvas, const SkPath& path) {
+ canvas->clear(0xFFFFFFFF);
+ canvas->save();
+
+ const SkRect& bounds = path.getBounds();
+
+ xlate_and_scale_to_bounds(canvas, bounds);
+
+ SkPaint p;
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+
+ canvas->drawPath(path, p);
+ canvas->restore();
+}
+
+void render_bitmap(SkCanvas* canvas, const SkBitmap& input, const SkRect* srcRect = NULL) {
+ const SkISize& size = canvas->getDeviceSize();
+
+ SkScalar xScale = SkIntToScalar(size.fWidth-2) / input.width();
+ SkScalar yScale = SkIntToScalar(size.fHeight-2) / input.height();
+
+ if (input.width() > input.height()) {
+ yScale *= input.height() / (float) input.width();
+ } else {
+ xScale *= input.width() / (float) input.height();
+ }
+
+ SkRect dst = SkRect::MakeXYWH(SK_Scalar1, SK_Scalar1,
+ xScale * input.width(),
+ yScale * input.height());
+
+ canvas->clear(0xFFFFFFFF);
+ canvas->drawBitmapRect(input, NULL, dst);
+
+ if (NULL != srcRect) {
+ SkRect r = SkRect::MakeLTRB(srcRect->fLeft * xScale + SK_Scalar1,
+ srcRect->fTop * yScale + SK_Scalar1,
+ srcRect->fRight * xScale + SK_Scalar1,
+ srcRect->fBottom * yScale + SK_Scalar1);
+ SkPaint p;
+ p.setColor(SK_ColorRED);
+ p.setStyle(SkPaint::kStroke_Style);
+
+ canvas->drawRect(r, p);
+ }
+}
+
+void render_rrect(SkCanvas* canvas, const SkRRect& rrect) {
+ canvas->clear(0xFFFFFFFF);
+ canvas->save();
+
+ const SkRect& bounds = rrect.getBounds();
+
+ xlate_and_scale_to_bounds(canvas, bounds);
+
+ SkPaint p;
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+
+ canvas->drawRRect(rrect, p);
+ canvas->restore();
+}
+
+};
+
+
+SkClipPathCommand::SkClipPathCommand(const SkPath& path, SkRegion::Op op, bool doAA) {
+ fPath = path;
+ fOp = op;
+ fDoAA = doAA;
+ fDrawType = CLIP_PATH;
+
+ fInfo.push(SkObjectParser::PathToString(path));
+ fInfo.push(SkObjectParser::RegionOpToString(op));
+ fInfo.push(SkObjectParser::BoolToString(doAA));
+}
+
+void SkClipPathCommand::execute(SkCanvas* canvas) {
+ canvas->clipPath(fPath, fOp, fDoAA);
+}
+
+bool SkClipPathCommand::render(SkCanvas* canvas) const {
+ render_path(canvas, fPath);
+ return true;
+}
+
+SkClipRegionCommand::SkClipRegionCommand(const SkRegion& region, SkRegion::Op op) {
+ fRegion = region;
+ fOp = op;
+ fDrawType = CLIP_REGION;
+
+ fInfo.push(SkObjectParser::RegionToString(region));
+ fInfo.push(SkObjectParser::RegionOpToString(op));
+}
+
+void SkClipRegionCommand::execute(SkCanvas* canvas) {
+ canvas->clipRegion(fRegion, fOp);
+}
+
+SkClipRectCommand::SkClipRectCommand(const SkRect& rect, SkRegion::Op op, bool doAA) {
+ fRect = rect;
+ fOp = op;
+ fDoAA = doAA;
+ fDrawType = CLIP_RECT;
+
+ fInfo.push(SkObjectParser::RectToString(rect));
+ fInfo.push(SkObjectParser::RegionOpToString(op));
+ fInfo.push(SkObjectParser::BoolToString(doAA));
+}
+
+void SkClipRectCommand::execute(SkCanvas* canvas) {
+ canvas->clipRect(fRect, fOp, fDoAA);
+}
+
+SkClipRRectCommand::SkClipRRectCommand(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+ fRRect = rrect;
+ fOp = op;
+ fDoAA = doAA;
+ fDrawType = CLIP_RRECT;
+
+ fInfo.push(SkObjectParser::RRectToString(rrect));
+ fInfo.push(SkObjectParser::RegionOpToString(op));
+ fInfo.push(SkObjectParser::BoolToString(doAA));
+}
+
+void SkClipRRectCommand::execute(SkCanvas* canvas) {
+ canvas->clipRRect(fRRect, fOp, fDoAA);
+}
+
+bool SkClipRRectCommand::render(SkCanvas* canvas) const {
+ render_rrect(canvas, fRRect);
+ return true;
+}
+
+SkConcatCommand::SkConcatCommand(const SkMatrix& matrix) {
+ fMatrix = matrix;
+ fDrawType = CONCAT;
+
+ fInfo.push(SkObjectParser::MatrixToString(matrix));
+}
+
+void SkConcatCommand::execute(SkCanvas* canvas) {
+ canvas->concat(fMatrix);
+}
+
+SkDrawBitmapCommand::SkDrawBitmapCommand(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+ const SkPaint* paint) {
+ fBitmap = bitmap;
+ fLeft = left;
+ fTop = top;
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fDrawType = DRAW_BITMAP;
+
+ fInfo.push(SkObjectParser::BitmapToString(bitmap));
+ fInfo.push(SkObjectParser::ScalarToString(left, "SkScalar left: "));
+ fInfo.push(SkObjectParser::ScalarToString(top, "SkScalar top: "));
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+}
+
+void SkDrawBitmapCommand::execute(SkCanvas* canvas) {
+ canvas->drawBitmap(fBitmap, fLeft, fTop, fPaintPtr);
+}
+
+bool SkDrawBitmapCommand::render(SkCanvas* canvas) const {
+ render_bitmap(canvas, fBitmap);
+ return true;
+}
+
+SkDrawBitmapMatrixCommand::SkDrawBitmapMatrixCommand(const SkBitmap& bitmap,
+ const SkMatrix& matrix,
+ const SkPaint* paint) {
+ fBitmap = bitmap;
+ fMatrix = matrix;
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fDrawType = DRAW_BITMAP_MATRIX;
+
+ fInfo.push(SkObjectParser::BitmapToString(bitmap));
+ fInfo.push(SkObjectParser::MatrixToString(matrix));
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+}
+
+void SkDrawBitmapMatrixCommand::execute(SkCanvas* canvas) {
+ canvas->drawBitmapMatrix(fBitmap, fMatrix, fPaintPtr);
+}
+
+bool SkDrawBitmapMatrixCommand::render(SkCanvas* canvas) const {
+ render_bitmap(canvas, fBitmap);
+ return true;
+}
+
+SkDrawBitmapNineCommand::SkDrawBitmapNineCommand(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint) {
+ fBitmap = bitmap;
+ fCenter = center;
+ fDst = dst;
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fDrawType = DRAW_BITMAP_NINE;
+
+ fInfo.push(SkObjectParser::BitmapToString(bitmap));
+ fInfo.push(SkObjectParser::IRectToString(center));
+ fInfo.push(SkObjectParser::RectToString(dst, "Dst: "));
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+}
+
+void SkDrawBitmapNineCommand::execute(SkCanvas* canvas) {
+ canvas->drawBitmapNine(fBitmap, fCenter, fDst, fPaintPtr);
+}
+
+bool SkDrawBitmapNineCommand::render(SkCanvas* canvas) const {
+ render_bitmap(canvas, fBitmap);
+ return true;
+}
+
+SkDrawBitmapRectCommand::SkDrawBitmapRectCommand(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint) {
+ fBitmap = bitmap;
+ if (NULL != src) {
+ fSrc = *src;
+ } else {
+ fSrc.setEmpty();
+ }
+ fDst = dst;
+
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fDrawType = DRAW_BITMAP_RECT_TO_RECT;
+
+ fInfo.push(SkObjectParser::BitmapToString(bitmap));
+ if (NULL != src) {
+ fInfo.push(SkObjectParser::RectToString(*src, "Src: "));
+ }
+ fInfo.push(SkObjectParser::RectToString(dst, "Dst: "));
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+}
+
+void SkDrawBitmapRectCommand::execute(SkCanvas* canvas) {
+ canvas->drawBitmapRectToRect(fBitmap, this->srcRect(), fDst, fPaintPtr);
+}
+
+bool SkDrawBitmapRectCommand::render(SkCanvas* canvas) const {
+ render_bitmap(canvas, fBitmap, this->srcRect());
+ return true;
+}
+
+SkDrawDataCommand::SkDrawDataCommand(const void* data, size_t length) {
+ fData = new char[length];
+ memcpy(fData, data, length);
+ fLength = length;
+ fDrawType = DRAW_DATA;
+
+ // TODO: add display of actual data?
+ SkString* str = new SkString;
+ str->appendf("length: %d", (int) length);
+ fInfo.push(str);
+}
+
+void SkDrawDataCommand::execute(SkCanvas* canvas) {
+ canvas->drawData(fData, fLength);
+}
+
+SkBeginCommentGroupCommand::SkBeginCommentGroupCommand(const char* description)
+ : INHERITED(BEGIN_COMMENT_GROUP)
+ , fDescription(description) {
+ SkString* temp = new SkString;
+ temp->appendf("Description: %s", description);
+ fInfo.push(temp);
+}
+
+SkCommentCommand::SkCommentCommand(const char* kywd, const char* value)
+ : INHERITED(COMMENT)
+ , fKywd(kywd)
+ , fValue(value) {
+ SkString* temp = new SkString;
+ temp->appendf("%s: %s", kywd, value);
+ fInfo.push(temp);
+}
+
+SkEndCommentGroupCommand::SkEndCommentGroupCommand() : INHERITED(END_COMMENT_GROUP) {
+}
+
+SkDrawOvalCommand::SkDrawOvalCommand(const SkRect& oval, const SkPaint& paint) {
+ fOval = oval;
+ fPaint = paint;
+ fDrawType = DRAW_OVAL;
+
+ fInfo.push(SkObjectParser::RectToString(oval));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawOvalCommand::execute(SkCanvas* canvas) {
+ canvas->drawOval(fOval, fPaint);
+}
+
+bool SkDrawOvalCommand::render(SkCanvas* canvas) const {
+ canvas->clear(0xFFFFFFFF);
+ canvas->save();
+
+ xlate_and_scale_to_bounds(canvas, fOval);
+
+ SkPaint p;
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+
+ canvas->drawOval(fOval, p);
+ canvas->restore();
+
+ return true;
+}
+
+SkDrawPaintCommand::SkDrawPaintCommand(const SkPaint& paint) {
+ fPaint = paint;
+ fDrawType = DRAW_PAINT;
+
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawPaintCommand::execute(SkCanvas* canvas) {
+ canvas->drawPaint(fPaint);
+}
+
+bool SkDrawPaintCommand::render(SkCanvas* canvas) const {
+ canvas->clear(0xFFFFFFFF);
+ canvas->drawPaint(fPaint);
+ return true;
+}
+
+SkDrawPathCommand::SkDrawPathCommand(const SkPath& path, const SkPaint& paint) {
+ fPath = path;
+ fPaint = paint;
+ fDrawType = DRAW_PATH;
+
+ fInfo.push(SkObjectParser::PathToString(path));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawPathCommand::execute(SkCanvas* canvas) {
+ canvas->drawPath(fPath, fPaint);
+}
+
+bool SkDrawPathCommand::render(SkCanvas* canvas) const {
+ render_path(canvas, fPath);
+ return true;
+}
+
+SkDrawPictureCommand::SkDrawPictureCommand(SkPicture& picture) :
+ fPicture(picture) {
+ fDrawType = DRAW_PICTURE;
+ fInfo.push(SkObjectParser::CustomTextToString("To be implemented."));
+}
+
+void SkDrawPictureCommand::execute(SkCanvas* canvas) {
+ canvas->drawPicture(fPicture);
+}
+
+SkDrawPointsCommand::SkDrawPointsCommand(SkCanvas::PointMode mode, size_t count,
+ const SkPoint pts[], const SkPaint& paint) {
+ fMode = mode;
+ fCount = count;
+ fPts = new SkPoint[count];
+ memcpy(fPts, pts, count * sizeof(SkPoint));
+ fPaint = paint;
+ fDrawType = DRAW_POINTS;
+
+ fInfo.push(SkObjectParser::PointsToString(pts, count));
+ fInfo.push(SkObjectParser::ScalarToString(SkIntToScalar((unsigned int)count),
+ "Points: "));
+ fInfo.push(SkObjectParser::PointModeToString(mode));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawPointsCommand::execute(SkCanvas* canvas) {
+ canvas->drawPoints(fMode, fCount, fPts, fPaint);
+}
+
+bool SkDrawPointsCommand::render(SkCanvas* canvas) const {
+ canvas->clear(0xFFFFFFFF);
+ canvas->save();
+
+ SkRect bounds;
+
+ bounds.setEmpty();
+ for (unsigned int i = 0; i < fCount; ++i) {
+ bounds.growToInclude(fPts[i].fX, fPts[i].fY);
+ }
+
+ xlate_and_scale_to_bounds(canvas, bounds);
+
+ SkPaint p;
+ p.setColor(SK_ColorBLACK);
+ p.setStyle(SkPaint::kStroke_Style);
+
+ canvas->drawPoints(fMode, fCount, fPts, p);
+ canvas->restore();
+
+ return true;
+}
+
+SkDrawPosTextCommand::SkDrawPosTextCommand(const void* text, size_t byteLength,
+ const SkPoint pos[], const SkPaint& paint) {
+ size_t numPts = paint.countText(text, byteLength);
+
+ fText = new char[byteLength];
+ memcpy(fText, text, byteLength);
+ fByteLength = byteLength;
+
+ fPos = new SkPoint[numPts];
+ memcpy(fPos, pos, numPts * sizeof(SkPoint));
+
+ fPaint = paint;
+ fDrawType = DRAW_POS_TEXT;
+
+ fInfo.push(SkObjectParser::TextToString(text, byteLength, paint.getTextEncoding()));
+ // TODO(chudy): Test that this works.
+ fInfo.push(SkObjectParser::PointsToString(pos, 1));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawPosTextCommand::execute(SkCanvas* canvas) {
+ canvas->drawPosText(fText, fByteLength, fPos, fPaint);
+}
+
+
+SkDrawPosTextHCommand::SkDrawPosTextHCommand(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ size_t numPts = paint.countText(text, byteLength);
+
+ fText = new char[byteLength];
+ memcpy(fText, text, byteLength);
+ fByteLength = byteLength;
+
+ fXpos = new SkScalar[numPts];
+ memcpy(fXpos, xpos, numPts * sizeof(SkScalar));
+
+ fConstY = constY;
+ fPaint = paint;
+ fDrawType = DRAW_POS_TEXT_H;
+
+ fInfo.push(SkObjectParser::TextToString(text, byteLength, paint.getTextEncoding()));
+ fInfo.push(SkObjectParser::ScalarToString(xpos[0], "XPOS: "));
+ fInfo.push(SkObjectParser::ScalarToString(constY, "SkScalar constY: "));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawPosTextHCommand::execute(SkCanvas* canvas) {
+ canvas->drawPosTextH(fText, fByteLength, fXpos, fConstY, fPaint);
+}
+
+SkDrawRectCommand::SkDrawRectCommand(const SkRect& rect, const SkPaint& paint) {
+ fRect = rect;
+ fPaint = paint;
+ fDrawType = DRAW_RECT;
+
+ fInfo.push(SkObjectParser::RectToString(rect));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawRectCommand::execute(SkCanvas* canvas) {
+ canvas->drawRect(fRect, fPaint);
+}
+
+SkDrawRRectCommand::SkDrawRRectCommand(const SkRRect& rrect, const SkPaint& paint) {
+ fRRect = rrect;
+ fPaint = paint;
+ fDrawType = DRAW_RRECT;
+
+ fInfo.push(SkObjectParser::RRectToString(rrect));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawRRectCommand::execute(SkCanvas* canvas) {
+ canvas->drawRRect(fRRect, fPaint);
+}
+
+bool SkDrawRRectCommand::render(SkCanvas* canvas) const {
+ render_rrect(canvas, fRRect);
+ return true;
+}
+
+SkDrawSpriteCommand::SkDrawSpriteCommand(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint) {
+ fBitmap = bitmap;
+ fLeft = left;
+ fTop = top;
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fDrawType = DRAW_SPRITE;
+
+ fInfo.push(SkObjectParser::BitmapToString(bitmap));
+ fInfo.push(SkObjectParser::IntToString(left, "Left: "));
+ fInfo.push(SkObjectParser::IntToString(top, "Top: "));
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+}
+
+void SkDrawSpriteCommand::execute(SkCanvas* canvas) {
+ canvas->drawSprite(fBitmap, fLeft, fTop, fPaintPtr);
+}
+
+bool SkDrawSpriteCommand::render(SkCanvas* canvas) const {
+ render_bitmap(canvas, fBitmap);
+ return true;
+}
+
+SkDrawTextCommand::SkDrawTextCommand(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+ const SkPaint& paint) {
+ fText = new char[byteLength];
+ memcpy(fText, text, byteLength);
+ fByteLength = byteLength;
+ fX = x;
+ fY = y;
+ fPaint = paint;
+ fDrawType = DRAW_TEXT;
+
+ fInfo.push(SkObjectParser::TextToString(text, byteLength, paint.getTextEncoding()));
+ fInfo.push(SkObjectParser::ScalarToString(x, "SkScalar x: "));
+ fInfo.push(SkObjectParser::ScalarToString(y, "SkScalar y: "));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawTextCommand::execute(SkCanvas* canvas) {
+ canvas->drawText(fText, fByteLength, fX, fY, fPaint);
+}
+
+SkDrawTextOnPathCommand::SkDrawTextOnPathCommand(const void* text, size_t byteLength,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint) {
+ fText = new char[byteLength];
+ memcpy(fText, text, byteLength);
+ fByteLength = byteLength;
+ fPath = path;
+ if (NULL != matrix) {
+ fMatrix = *matrix;
+ } else {
+ fMatrix.setIdentity();
+ }
+ fPaint = paint;
+ fDrawType = DRAW_TEXT_ON_PATH;
+
+ fInfo.push(SkObjectParser::TextToString(text, byteLength, paint.getTextEncoding()));
+ fInfo.push(SkObjectParser::PathToString(path));
+ if (NULL != matrix) {
+ fInfo.push(SkObjectParser::MatrixToString(*matrix));
+ }
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawTextOnPathCommand::execute(SkCanvas* canvas) {
+ canvas->drawTextOnPath(fText, fByteLength, fPath,
+ fMatrix.isIdentity() ? NULL : &fMatrix,
+ fPaint);
+}
+
+SkDrawVerticesCommand::SkDrawVerticesCommand(SkCanvas::VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xfermode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ fVmode = vmode;
+
+ fVertexCount = vertexCount;
+
+ fVertices = new SkPoint[vertexCount];
+ memcpy(fVertices, vertices, vertexCount * sizeof(SkPoint));
+
+ if (NULL != texs) {
+ fTexs = new SkPoint[vertexCount];
+ memcpy(fTexs, texs, vertexCount * sizeof(SkPoint));
+ } else {
+ fTexs = NULL;
+ }
+
+ if (NULL != colors) {
+ fColors = new SkColor[vertexCount];
+ memcpy(fColors, colors, vertexCount * sizeof(SkColor));
+ } else {
+ fColors = NULL;
+ }
+
+ fXfermode = xfermode;
+ if (NULL != fXfermode) {
+ fXfermode->ref();
+ }
+
+ if (indexCount > 0) {
+ fIndices = new uint16_t[indexCount];
+ memcpy(fIndices, indices, indexCount * sizeof(uint16_t));
+ } else {
+ fIndices = NULL;
+ }
+
+ fIndexCount = indexCount;
+ fPaint = paint;
+ fDrawType = DRAW_VERTICES;
+
+ // TODO(chudy)
+ fInfo.push(SkObjectParser::CustomTextToString("To be implemented."));
+ fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+SkDrawVerticesCommand::~SkDrawVerticesCommand() {
+ delete [] fVertices;
+ delete [] fTexs;
+ delete [] fColors;
+ SkSafeUnref(fXfermode);
+ delete [] fIndices;
+}
+
+void SkDrawVerticesCommand::execute(SkCanvas* canvas) {
+ canvas->drawVertices(fVmode, fVertexCount, fVertices,
+ fTexs, fColors, fXfermode, fIndices,
+ fIndexCount, fPaint);
+}
+
+SkRestoreCommand::SkRestoreCommand() {
+ fDrawType = RESTORE;
+ fInfo.push(SkObjectParser::CustomTextToString("No Parameters"));
+}
+
+void SkRestoreCommand::execute(SkCanvas* canvas) {
+ canvas->restore();
+}
+
+void SkRestoreCommand::trackSaveState(int* state) {
+ (*state)--;
+}
+
+SkRotateCommand::SkRotateCommand(SkScalar degrees) {
+ fDegrees = degrees;
+ fDrawType = ROTATE;
+
+ fInfo.push(SkObjectParser::ScalarToString(degrees, "SkScalar degrees: "));
+}
+
+void SkRotateCommand::execute(SkCanvas* canvas) {
+ canvas->rotate(fDegrees);
+}
+
+SkSaveCommand::SkSaveCommand(SkCanvas::SaveFlags flags) {
+ fFlags = flags;
+ fDrawType = SAVE;
+ fInfo.push(SkObjectParser::SaveFlagsToString(flags));
+}
+
+void SkSaveCommand::execute(SkCanvas* canvas) {
+ canvas->save(fFlags);
+}
+
+void SkSaveCommand::trackSaveState(int* state) {
+ (*state)++;
+}
+
+SkSaveLayerCommand::SkSaveLayerCommand(const SkRect* bounds, const SkPaint* paint,
+ SkCanvas::SaveFlags flags) {
+ if (NULL != bounds) {
+ fBounds = *bounds;
+ } else {
+ fBounds.setEmpty();
+ }
+
+ if (NULL != paint) {
+ fPaint = *paint;
+ fPaintPtr = &fPaint;
+ } else {
+ fPaintPtr = NULL;
+ }
+ fFlags = flags;
+ fDrawType = SAVE_LAYER;
+
+ if (NULL != bounds) {
+ fInfo.push(SkObjectParser::RectToString(*bounds, "Bounds: "));
+ }
+ if (NULL != paint) {
+ fInfo.push(SkObjectParser::PaintToString(*paint));
+ }
+ fInfo.push(SkObjectParser::SaveFlagsToString(flags));
+}
+
+void SkSaveLayerCommand::execute(SkCanvas* canvas) {
+ canvas->saveLayer(fBounds.isEmpty() ? NULL : &fBounds,
+ fPaintPtr,
+ fFlags);
+}
+
+void SkSaveLayerCommand::trackSaveState(int* state) {
+ (*state)++;
+}
+
+SkScaleCommand::SkScaleCommand(SkScalar sx, SkScalar sy) {
+ fSx = sx;
+ fSy = sy;
+ fDrawType = SCALE;
+
+ fInfo.push(SkObjectParser::ScalarToString(sx, "SkScalar sx: "));
+ fInfo.push(SkObjectParser::ScalarToString(sy, "SkScalar sy: "));
+}
+
+void SkScaleCommand::execute(SkCanvas* canvas) {
+ canvas->scale(fSx, fSy);
+}
+
+SkSetMatrixCommand::SkSetMatrixCommand(const SkMatrix& matrix) {
+ fMatrix = matrix;
+ fDrawType = SET_MATRIX;
+
+ fInfo.push(SkObjectParser::MatrixToString(matrix));
+}
+
+void SkSetMatrixCommand::execute(SkCanvas* canvas) {
+ canvas->setMatrix(fMatrix);
+}
+
+SkSkewCommand::SkSkewCommand(SkScalar sx, SkScalar sy) {
+ fSx = sx;
+ fSy = sy;
+ fDrawType = SKEW;
+
+ fInfo.push(SkObjectParser::ScalarToString(sx, "SkScalar sx: "));
+ fInfo.push(SkObjectParser::ScalarToString(sy, "SkScalar sy: "));
+}
+
+void SkSkewCommand::execute(SkCanvas* canvas) {
+ canvas->skew(fSx, fSy);
+}
+
+SkTranslateCommand::SkTranslateCommand(SkScalar dx, SkScalar dy) {
+ fDx = dx;
+ fDy = dy;
+ fDrawType = TRANSLATE;
+
+ fInfo.push(SkObjectParser::ScalarToString(dx, "SkScalar dx: "));
+ fInfo.push(SkObjectParser::ScalarToString(dy, "SkScalar dy: "));
+}
+
+void SkTranslateCommand::execute(SkCanvas* canvas) {
+ canvas->translate(fDx, fDy);
+}
diff --git a/utils/debugger/SkDrawCommand.h b/utils/debugger/SkDrawCommand.h
new file mode 100644
index 00000000..e62fe4f6
--- /dev/null
+++ b/utils/debugger/SkDrawCommand.h
@@ -0,0 +1,553 @@
+
+/*
+ * 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 SKDRAWCOMMAND_H_
+#define SKDRAWCOMMAND_H_
+
+#include "SkPictureFlat.h"
+#include "SkCanvas.h"
+#include "SkString.h"
+
+class SK_API SkDrawCommand {
+public:
+ /* TODO(chudy): Remove subclasses. */
+ SkDrawCommand(DrawType drawType);
+ SkDrawCommand();
+
+ virtual ~SkDrawCommand();
+
+ virtual SkString toString();
+
+ virtual const char* toCString() {
+ return GetCommandString(fDrawType);
+ }
+
+ bool isVisible() const {
+ return fVisible;
+ }
+
+ void setVisible(bool toggle) {
+ fVisible = toggle;
+ }
+
+ SkTDArray<SkString*>* Info() {return &fInfo; };
+ virtual void execute(SkCanvas* canvas)=0;
+ /** Does nothing by default, but used by save() and restore()-type
+ subclassse to track unresolved save() calls. */
+ virtual void trackSaveState(int* state) { };
+ DrawType getType() { return fDrawType; };
+
+ virtual bool render(SkCanvas* canvas) const { return false; }
+
+ static const char* GetCommandString(DrawType type);
+
+protected:
+ DrawType fDrawType;
+ SkTDArray<SkString*> fInfo;
+
+private:
+ bool fVisible;
+};
+
+class SkRestoreCommand : public SkDrawCommand {
+public:
+ SkRestoreCommand();
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual void trackSaveState(int* state) SK_OVERRIDE;
+
+private:
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkClearCommand : public SkDrawCommand {
+public:
+ SkClearCommand(SkColor color);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkColor fColor;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkClipPathCommand : public SkDrawCommand {
+public:
+ SkClipPathCommand(const SkPath& path, SkRegion::Op op, bool doAA);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkPath fPath;
+ SkRegion::Op fOp;
+ bool fDoAA;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkClipRegionCommand : public SkDrawCommand {
+public:
+ SkClipRegionCommand(const SkRegion& region, SkRegion::Op op);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkRegion fRegion;
+ SkRegion::Op fOp;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkClipRectCommand : public SkDrawCommand {
+public:
+ SkClipRectCommand(const SkRect& rect, SkRegion::Op op, bool doAA);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+
+ const SkRect& rect() const { return fRect; }
+ SkRegion::Op op() const { return fOp; }
+ bool doAA() const { return fDoAA; }
+
+private:
+ SkRect fRect;
+ SkRegion::Op fOp;
+ bool fDoAA;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkClipRRectCommand : public SkDrawCommand {
+public:
+ SkClipRRectCommand(const SkRRect& rrect, SkRegion::Op op, bool doAA);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+
+ const SkRRect& rrect() const { return fRRect; }
+ SkRegion::Op op() const { return fOp; }
+ bool doAA() const { return fDoAA; }
+
+private:
+ SkRRect fRRect;
+ SkRegion::Op fOp;
+ bool fDoAA;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkConcatCommand : public SkDrawCommand {
+public:
+ SkConcatCommand(const SkMatrix& matrix);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkMatrix fMatrix;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawBitmapCommand : public SkDrawCommand {
+public:
+ SkDrawBitmapCommand(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+ const SkPaint* paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkBitmap fBitmap;
+ SkScalar fLeft;
+ SkScalar fTop;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawBitmapMatrixCommand : public SkDrawCommand {
+public:
+ SkDrawBitmapMatrixCommand(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkBitmap fBitmap;
+ SkMatrix fMatrix;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawBitmapNineCommand : public SkDrawCommand {
+public:
+ SkDrawBitmapNineCommand(const SkBitmap& bitmap, const SkIRect& center,
+ const SkRect& dst, const SkPaint* paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkBitmap fBitmap;
+ SkIRect fCenter;
+ SkRect fDst;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawBitmapRectCommand : public SkDrawCommand {
+public:
+ SkDrawBitmapRectCommand(const SkBitmap& bitmap, const SkRect* src,
+ const SkRect& dst, const SkPaint* paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+
+ const SkBitmap& bitmap() const { return fBitmap; }
+
+ // The non-const 'paint' method allows modification of this object's
+ // SkPaint. For this reason the ctor and setPaint method make a local copy.
+ // The 'fPaintPtr' member acts a signal that the local SkPaint is valid
+ // (since only an SkPaint* is passed into the ctor).
+ const SkPaint* paint() const { return fPaintPtr; }
+ SkPaint* paint() { return fPaintPtr; }
+
+ void setPaint(const SkPaint& paint) { fPaint = paint; fPaintPtr = &fPaint; }
+
+ const SkRect* srcRect() const { return fSrc.isEmpty() ? NULL : &fSrc; }
+ const SkRect& dstRect() const { return fDst; }
+
+ void setSrcRect(const SkRect& src) { fSrc = src; }
+ void setDstRect(const SkRect& dst) { fDst = dst; }
+
+private:
+ SkBitmap fBitmap;
+ SkRect fSrc;
+ SkRect fDst;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawDataCommand : public SkDrawCommand {
+public:
+ SkDrawDataCommand(const void* data, size_t length);
+ virtual ~SkDrawDataCommand() { delete [] fData; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ char* fData;
+ size_t fLength;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkBeginCommentGroupCommand : public SkDrawCommand {
+public:
+ SkBeginCommentGroupCommand(const char* description);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE {
+ canvas->beginCommentGroup(fDescription.c_str());
+ };
+private:
+ SkString fDescription;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkCommentCommand : public SkDrawCommand {
+public:
+ SkCommentCommand(const char* kywd, const char* value);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE {
+ canvas->addComment(fKywd.c_str(), fValue.c_str());
+ };
+private:
+ SkString fKywd;
+ SkString fValue;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkEndCommentGroupCommand : public SkDrawCommand {
+public:
+ SkEndCommentGroupCommand();
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE {
+ canvas->endCommentGroup();
+ };
+private:
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawOvalCommand : public SkDrawCommand {
+public:
+ SkDrawOvalCommand(const SkRect& oval, const SkPaint& paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkRect fOval;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPaintCommand : public SkDrawCommand {
+public:
+ SkDrawPaintCommand(const SkPaint& paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPathCommand : public SkDrawCommand {
+public:
+ SkDrawPathCommand(const SkPath& path, const SkPaint& paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+
+private:
+ SkPath fPath;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPictureCommand : public SkDrawCommand {
+public:
+ SkDrawPictureCommand(SkPicture& picture);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkPicture fPicture;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPointsCommand : public SkDrawCommand {
+public:
+ SkDrawPointsCommand(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
+ const SkPaint& paint);
+ virtual ~SkDrawPointsCommand() { delete [] fPts; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkCanvas::PointMode fMode;
+ size_t fCount;
+ SkPoint* fPts;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawTextCommand : public SkDrawCommand {
+public:
+ SkDrawTextCommand(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+ const SkPaint& paint);
+ virtual ~SkDrawTextCommand() { delete [] fText; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ char* fText;
+ size_t fByteLength;
+ SkScalar fX;
+ SkScalar fY;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPosTextCommand : public SkDrawCommand {
+public:
+ SkDrawPosTextCommand(const void* text, size_t byteLength, const SkPoint pos[],
+ const SkPaint& paint);
+ virtual ~SkDrawPosTextCommand() { delete [] fPos; delete [] fText; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ char* fText;
+ size_t fByteLength;
+ SkPoint* fPos;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawTextOnPathCommand : public SkDrawCommand {
+public:
+ SkDrawTextOnPathCommand(const void* text, size_t byteLength, const SkPath& path,
+ const SkMatrix* matrix, const SkPaint& paint);
+ virtual ~SkDrawTextOnPathCommand() { delete [] fText; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ char* fText;
+ size_t fByteLength;
+ SkPath fPath;
+ SkMatrix fMatrix;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawPosTextHCommand : public SkDrawCommand {
+public:
+ SkDrawPosTextHCommand(const void* text, size_t byteLength, const SkScalar xpos[],
+ SkScalar constY, const SkPaint& paint);
+ virtual ~SkDrawPosTextHCommand() { delete [] fXpos; delete [] fText; }
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkScalar* fXpos;
+ char* fText;
+ size_t fByteLength;
+ SkScalar fConstY;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawRectCommand : public SkDrawCommand {
+public:
+ SkDrawRectCommand(const SkRect& rect, const SkPaint& paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+
+ const SkRect& rect() const { return fRect; }
+ const SkPaint& paint() const { return fPaint; }
+private:
+ SkRect fRect;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawRRectCommand : public SkDrawCommand {
+public:
+ SkDrawRRectCommand(const SkRRect& rrect, const SkPaint& paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkRRect fRRect;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawSpriteCommand : public SkDrawCommand {
+public:
+ SkDrawSpriteCommand(const SkBitmap& bitmap, int left, int top, const SkPaint* paint);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual bool render(SkCanvas* canvas) const SK_OVERRIDE;
+private:
+ SkBitmap fBitmap;
+ int fLeft;
+ int fTop;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkDrawVerticesCommand : public SkDrawCommand {
+public:
+ SkDrawVerticesCommand(SkCanvas::VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xfermode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint);
+ virtual ~SkDrawVerticesCommand();
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkCanvas::VertexMode fVmode;
+ int fVertexCount;
+ SkPoint* fVertices;
+ SkPoint* fTexs;
+ SkColor* fColors;
+ SkXfermode* fXfermode;
+ uint16_t* fIndices;
+ int fIndexCount;
+ SkPaint fPaint;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkRotateCommand : public SkDrawCommand {
+public:
+ SkRotateCommand(SkScalar degrees);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkScalar fDegrees;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkSaveCommand : public SkDrawCommand {
+public:
+ SkSaveCommand(SkCanvas::SaveFlags flags);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual void trackSaveState(int* state) SK_OVERRIDE;
+private:
+ SkCanvas::SaveFlags fFlags;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkSaveLayerCommand : public SkDrawCommand {
+public:
+ SkSaveLayerCommand(const SkRect* bounds, const SkPaint* paint,
+ SkCanvas::SaveFlags flags);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+ virtual void trackSaveState(int* state) SK_OVERRIDE;
+
+ const SkPaint* paint() const { return fPaintPtr; }
+
+private:
+ SkRect fBounds;
+ SkPaint fPaint;
+ SkPaint* fPaintPtr;
+ SkCanvas::SaveFlags fFlags;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkScaleCommand : public SkDrawCommand {
+public:
+ SkScaleCommand(SkScalar sx, SkScalar sy);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+
+ SkScalar x() const { return fSx; }
+ SkScalar y() const { return fSy; }
+
+private:
+ SkScalar fSx;
+ SkScalar fSy;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkSetMatrixCommand : public SkDrawCommand {
+public:
+ SkSetMatrixCommand(const SkMatrix& matrix);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkMatrix fMatrix;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkSkewCommand : public SkDrawCommand {
+public:
+ SkSkewCommand(SkScalar sx, SkScalar sy);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+private:
+ SkScalar fSx;
+ SkScalar fSy;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+class SkTranslateCommand : public SkDrawCommand {
+public:
+ SkTranslateCommand(SkScalar dx, SkScalar dy);
+ virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+
+ SkScalar x() const { return fDx; }
+ SkScalar y() const { return fDy; }
+
+private:
+ SkScalar fDx;
+ SkScalar fDy;
+
+ typedef SkDrawCommand INHERITED;
+};
+
+#endif
diff --git a/utils/debugger/SkObjectParser.cpp b/utils/debugger/SkObjectParser.cpp
new file mode 100644
index 00000000..504cd085
--- /dev/null
+++ b/utils/debugger/SkObjectParser.cpp
@@ -0,0 +1,371 @@
+
+/*
+ * 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 "SkObjectParser.h"
+#include "SkData.h"
+#include "SkFontDescriptor.h"
+#include "SkRRect.h"
+#include "SkShader.h"
+#include "SkStream.h"
+#include "SkStringUtils.h"
+#include "SkTypeface.h"
+#include "SkUtils.h"
+
+/* TODO(chudy): Replace all std::strings with char */
+
+SkString* SkObjectParser::BitmapToString(const SkBitmap& bitmap) {
+ SkString* mBitmap = new SkString("SkBitmap: ");
+ mBitmap->append("W: ");
+ mBitmap->appendS32(bitmap.width());
+ mBitmap->append(" H: ");
+ mBitmap->appendS32(bitmap.height());
+
+ const char* gConfigStrings[] = {
+ "None", "A1", "A8", "Index8", "RGB565", "ARGB4444", "ARGB8888"
+ };
+ SkASSERT(SkBitmap::kConfigCount == 7);
+
+ mBitmap->append(" Config: ");
+ mBitmap->append(gConfigStrings[bitmap.getConfig()]);
+
+ if (bitmap.isOpaque()) {
+ mBitmap->append(" opaque");
+ } else {
+ mBitmap->append(" not-opaque");
+ }
+
+ if (bitmap.isImmutable()) {
+ mBitmap->append(" immutable");
+ } else {
+ mBitmap->append(" not-immutable");
+ }
+
+ if (bitmap.isVolatile()) {
+ mBitmap->append(" volatile");
+ } else {
+ mBitmap->append(" not-volatile");
+ }
+
+ mBitmap->append(" genID: ");
+ mBitmap->appendS32(bitmap.getGenerationID());
+
+ return mBitmap;
+}
+
+SkString* SkObjectParser::BoolToString(bool doAA) {
+ SkString* mBool = new SkString("Bool doAA: ");
+ if (doAA) {
+ mBool->append("True");
+ } else {
+ mBool->append("False");
+ }
+ return mBool;
+}
+
+SkString* SkObjectParser::CustomTextToString(const char* text) {
+ SkString* mText = new SkString(text);
+ return mText;
+}
+
+SkString* SkObjectParser::IntToString(int x, const char* text) {
+ SkString* mInt = new SkString(text);
+ mInt->append(" ");
+ mInt->appendScalar(SkIntToScalar(x));
+ return mInt;
+}
+
+SkString* SkObjectParser::IRectToString(const SkIRect& rect) {
+ SkString* mRect = new SkString("SkIRect: ");
+ mRect->append("L: ");
+ mRect->appendS32(rect.left());
+ mRect->append(", T: ");
+ mRect->appendS32(rect.top());
+ mRect->append(", R: ");
+ mRect->appendS32(rect.right());
+ mRect->append(", B: ");
+ mRect->appendS32(rect.bottom());
+ return mRect;
+}
+
+SkString* SkObjectParser::MatrixToString(const SkMatrix& matrix) {
+ SkString* str = new SkString("SkMatrix: ");
+#ifdef SK_DEVELOPER
+ matrix.toString(str);
+#endif
+ return str;
+}
+
+SkString* SkObjectParser::PaintToString(const SkPaint& paint) {
+ SkString* str = new SkString;
+#ifdef SK_DEVELOPER
+ paint.toString(str);
+#endif
+ return str;
+}
+
+SkString* SkObjectParser::PathToString(const SkPath& path) {
+ SkString* mPath = new SkString("Path (");
+
+ static const char* gFillStrings[] = {
+ "Winding", "EvenOdd", "InverseWinding", "InverseEvenOdd"
+ };
+
+ mPath->append(gFillStrings[path.getFillType()]);
+ mPath->append(", ");
+
+ static const char* gConvexityStrings[] = {
+ "Unknown", "Convex", "Concave"
+ };
+ SkASSERT(SkPath::kConcave_Convexity == 2);
+
+ mPath->append(gConvexityStrings[path.getConvexity()]);
+ mPath->append(", ");
+
+ if (path.isRect(NULL)) {
+ mPath->append("isRect, ");
+ } else {
+ mPath->append("isNotRect, ");
+ }
+
+ mPath->appendS32(path.countVerbs());
+ mPath->append("V, ");
+ mPath->appendS32(path.countPoints());
+ mPath->append("P): ");
+
+ static const char* gVerbStrings[] = {
+ "Move", "Line", "Quad", "Conic", "Cubic", "Close", "Done"
+ };
+ static const int gPtsPerVerb[] = { 1, 1, 2, 2, 3, 0, 0 };
+ static const int gPtOffsetPerVerb[] = { 0, 1, 1, 1, 1, 0, 0 };
+ SkASSERT(SkPath::kDone_Verb == 6);
+
+ SkPath::Iter iter(const_cast<SkPath&>(path), false);
+ SkPath::Verb verb;
+ SkPoint points[4];
+
+ for(verb = iter.next(points, false);
+ verb != SkPath::kDone_Verb;
+ verb = iter.next(points, false)) {
+
+ mPath->append(gVerbStrings[verb]);
+ mPath->append(" ");
+
+ for (int i = 0; i < gPtsPerVerb[verb]; ++i) {
+ mPath->append("(");
+ mPath->appendScalar(points[gPtOffsetPerVerb[verb]+i].fX);
+ mPath->append(", ");
+ mPath->appendScalar(points[gPtOffsetPerVerb[verb]+i].fY);
+ mPath->append(")");
+ }
+
+ if (SkPath::kConic_Verb == verb) {
+ mPath->append("(");
+ mPath->appendScalar(iter.conicWeight());
+ mPath->append(")");
+ }
+
+ mPath->append(" ");
+ }
+
+ SkString* boundStr = SkObjectParser::RectToString(path.getBounds(), " Bound: ");
+
+ if (NULL != boundStr) {
+ mPath->append(*boundStr);
+ SkDELETE(boundStr);
+ }
+
+ return mPath;
+}
+
+SkString* SkObjectParser::PointsToString(const SkPoint pts[], size_t count) {
+ SkString* mPoints = new SkString("SkPoints pts[]: ");
+ for (unsigned int i = 0; i < count; i++) {
+ mPoints->append("(");
+ mPoints->appendScalar(pts[i].fX);
+ mPoints->append(",");
+ mPoints->appendScalar(pts[i].fY);
+ mPoints->append(")");
+ }
+ return mPoints;
+}
+
+SkString* SkObjectParser::PointModeToString(SkCanvas::PointMode mode) {
+ SkString* mMode = new SkString("SkCanvas::PointMode: ");
+ if (mode == SkCanvas::kPoints_PointMode) {
+ mMode->append("kPoints_PointMode");
+ } else if (mode == SkCanvas::kLines_PointMode) {
+ mMode->append("kLines_Mode");
+ } else if (mode == SkCanvas::kPolygon_PointMode) {
+ mMode->append("kPolygon_PointMode");
+ }
+ return mMode;
+}
+
+SkString* SkObjectParser::RectToString(const SkRect& rect, const char* title) {
+
+ SkString* mRect = new SkString;
+
+ if (NULL == title) {
+ mRect->append("SkRect: ");
+ } else {
+ mRect->append(title);
+ }
+ mRect->append("(");
+ mRect->appendScalar(rect.left());
+ mRect->append(", ");
+ mRect->appendScalar(rect.top());
+ mRect->append(", ");
+ mRect->appendScalar(rect.right());
+ mRect->append(", ");
+ mRect->appendScalar(rect.bottom());
+ mRect->append(")");
+ return mRect;
+}
+
+SkString* SkObjectParser::RRectToString(const SkRRect& rrect, const char* title) {
+
+ SkString* mRRect = new SkString;
+
+ if (NULL == title) {
+ mRRect->append("SkRRect (");
+ if (rrect.isEmpty()) {
+ mRRect->append("empty");
+ } else if (rrect.isRect()) {
+ mRRect->append("rect");
+ } else if (rrect.isOval()) {
+ mRRect->append("oval");
+ } else if (rrect.isSimple()) {
+ mRRect->append("simple");
+ } else {
+ SkASSERT(rrect.isComplex());
+ mRRect->append("complex");
+ }
+ mRRect->append("): ");
+ } else {
+ mRRect->append(title);
+ }
+ mRRect->append("(");
+ mRRect->appendScalar(rrect.rect().left());
+ mRRect->append(", ");
+ mRRect->appendScalar(rrect.rect().top());
+ mRRect->append(", ");
+ mRRect->appendScalar(rrect.rect().right());
+ mRRect->append(", ");
+ mRRect->appendScalar(rrect.rect().bottom());
+ mRRect->append(") radii: (");
+ for (int i = 0; i < 4; ++i) {
+ const SkVector& radii = rrect.radii((SkRRect::Corner) i);
+ mRRect->appendScalar(radii.fX);
+ mRRect->append(", ");
+ mRRect->appendScalar(radii.fY);
+ if (i < 3) {
+ mRRect->append(", ");
+ }
+ }
+ mRRect->append(")");
+ return mRRect;
+}
+
+SkString* SkObjectParser::RegionOpToString(SkRegion::Op op) {
+ SkString* mOp = new SkString("SkRegion::Op: ");
+ if (op == SkRegion::kDifference_Op) {
+ mOp->append("kDifference_Op");
+ } else if (op == SkRegion::kIntersect_Op) {
+ mOp->append("kIntersect_Op");
+ } else if (op == SkRegion::kUnion_Op) {
+ mOp->append("kUnion_Op");
+ } else if (op == SkRegion::kXOR_Op) {
+ mOp->append("kXOR_Op");
+ } else if (op == SkRegion::kReverseDifference_Op) {
+ mOp->append("kReverseDifference_Op");
+ } else if (op == SkRegion::kReplace_Op) {
+ mOp->append("kReplace_Op");
+ } else {
+ mOp->append("Unknown Type");
+ }
+ return mOp;
+}
+
+SkString* SkObjectParser::RegionToString(const SkRegion& region) {
+ SkString* mRegion = new SkString("SkRegion: Data unavailable.");
+ return mRegion;
+}
+
+SkString* SkObjectParser::SaveFlagsToString(SkCanvas::SaveFlags flags) {
+ SkString* mFlags = new SkString("SkCanvas::SaveFlags: ");
+ if (flags & SkCanvas::kMatrix_SaveFlag) {
+ mFlags->append("kMatrix_SaveFlag ");
+ }
+ if (flags & SkCanvas::kClip_SaveFlag) {
+ mFlags->append("kClip_SaveFlag ");
+ }
+ if (flags & SkCanvas::kHasAlphaLayer_SaveFlag) {
+ mFlags->append("kHasAlphaLayer_SaveFlag ");
+ }
+ if (flags & SkCanvas::kFullColorLayer_SaveFlag) {
+ mFlags->append("kFullColorLayer_SaveFlag ");
+ }
+ if (flags & SkCanvas::kClipToLayer_SaveFlag) {
+ mFlags->append("kClipToLayer_SaveFlag ");
+ }
+ return mFlags;
+}
+
+SkString* SkObjectParser::ScalarToString(SkScalar x, const char* text) {
+ SkString* mScalar = new SkString(text);
+ mScalar->append(" ");
+ mScalar->appendScalar(x);
+ return mScalar;
+}
+
+SkString* SkObjectParser::TextToString(const void* text, size_t byteLength,
+ SkPaint::TextEncoding encoding) {
+
+ SkString* decodedText = new SkString();
+ switch (encoding) {
+ case SkPaint::kUTF8_TextEncoding: {
+ decodedText->append("UTF-8: ");
+ decodedText->append((const char*)text, byteLength);
+ break;
+ }
+ case SkPaint::kUTF16_TextEncoding: {
+ decodedText->append("UTF-16: ");
+ size_t sizeNeeded = SkUTF16_ToUTF8((uint16_t*)text, byteLength / 2, NULL);
+ char* utf8 = new char[sizeNeeded];
+ SkUTF16_ToUTF8((uint16_t*)text, byteLength / 2, utf8);
+ decodedText->append(utf8, sizeNeeded);
+ delete utf8;
+ break;
+ }
+ case SkPaint::kUTF32_TextEncoding: {
+ decodedText->append("UTF-32: ");
+ const SkUnichar* begin = (const SkUnichar*)text;
+ const SkUnichar* end = (const SkUnichar*)((const char*)text + byteLength);
+ for (const SkUnichar* unichar = begin; unichar < end; ++unichar) {
+ decodedText->appendUnichar(*unichar);
+ }
+ break;
+ }
+ case SkPaint::kGlyphID_TextEncoding: {
+ decodedText->append("GlyphID: ");
+ const uint16_t* begin = (const uint16_t*)text;
+ const uint16_t* end = (const uint16_t*)((const char*)text + byteLength);
+ for (const uint16_t* glyph = begin; glyph < end; ++glyph) {
+ decodedText->append("0x");
+ decodedText->appendHex(*glyph);
+ decodedText->append(" ");
+ }
+ break;
+ }
+ default:
+ decodedText->append("Unknown text encoding.");
+ break;
+ }
+
+ return decodedText;
+}
diff --git a/utils/debugger/SkObjectParser.h b/utils/debugger/SkObjectParser.h
new file mode 100644
index 00000000..911b7789
--- /dev/null
+++ b/utils/debugger/SkObjectParser.h
@@ -0,0 +1,128 @@
+/*
+ * 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 SKOBJECTPARSER_H_
+#define SKOBJECTPARSER_H_
+
+#include "SkCanvas.h"
+#include "SkString.h"
+
+/** \class SkObjectParser
+
+ The ObjectParser is used to return string information about parameters
+ in each draw command.
+ */
+class SkObjectParser {
+public:
+
+ /**
+ Returns a string about a bitmaps bounds and config.
+ @param bitmap SkBitmap
+ */
+ static SkString* BitmapToString(const SkBitmap& bitmap);
+
+ /**
+ Returns a string representation of a boolean.
+ @param doAA boolean
+ */
+ static SkString* BoolToString(bool doAA);
+
+ /**
+ Returns a string representation of the text pointer passed in.
+ */
+ static SkString* CustomTextToString(const char* text);
+
+ /**
+ Returns a string representation of an integer with the text parameter
+ at the front of the string.
+ @param x integer
+ @param text
+ */
+ static SkString* IntToString(int x, const char* text);
+ /**
+ Returns a string representation of the SkIRects coordinates.
+ @param rect SkIRect
+ */
+ static SkString* IRectToString(const SkIRect& rect);
+
+ /**
+ Returns a string representation of an SkMatrix's contents
+ @param matrix SkMatrix
+ */
+ static SkString* MatrixToString(const SkMatrix& matrix);
+
+ /**
+ Returns a string representation of an SkPaint's color
+ @param paint SkPaint
+ */
+ static SkString* PaintToString(const SkPaint& paint);
+
+ /**
+ Returns a string representation of a SkPath's points.
+ @param path SkPath
+ */
+ static SkString* PathToString(const SkPath& path);
+
+ /**
+ Returns a string representation of the points in the point array.
+ @param pts[] Array of SkPoints
+ @param count
+ */
+ static SkString* PointsToString(const SkPoint pts[], size_t count);
+
+ /**
+ Returns a string representation of the SkCanvas PointMode enum.
+ */
+ static SkString* PointModeToString(SkCanvas::PointMode mode);
+
+ /**
+ Returns a string representation of the SkRects coordinates.
+ @param rect SkRect
+ */
+ static SkString* RectToString(const SkRect& rect, const char* title = NULL);
+
+ /**
+ Returns a string representation of an SkRRect.
+ @param rrect SkRRect
+ */
+ static SkString* RRectToString(const SkRRect& rrect, const char* title = NULL);
+
+ /**
+ Returns a string representation of the SkRegion enum.
+ @param op SkRegion::op enum
+ */
+ static SkString* RegionOpToString(SkRegion::Op op);
+
+ /**
+ Returns a string representation of the SkRegion.
+ @param region SkRegion
+ */
+ static SkString* RegionToString(const SkRegion& region);
+
+ /**
+ Returns a string representation of the SkCanvas::SaveFlags enum.
+ @param flags SkCanvas::SaveFlags enum
+ */
+ static SkString* SaveFlagsToString(SkCanvas::SaveFlags flags);
+
+ /**
+ Returns a string representation of an SkScalar with the text parameter
+ at the front of the string.
+ @param x SkScalar
+ @param text
+ */
+ static SkString* ScalarToString(SkScalar x, const char* text);
+
+ /**
+ Returns a string representation of the char pointer passed in.
+ @param text const void* that will be cast to a char*
+ */
+ static SkString* TextToString(const void* text, size_t byteLength,
+ SkPaint::TextEncoding encoding);
+};
+
+#endif
diff --git a/utils/ios/SkFontHost_iOS.mm b/utils/ios/SkFontHost_iOS.mm
new file mode 100755
index 00000000..60f111c8
--- /dev/null
+++ b/utils/ios/SkFontHost_iOS.mm
@@ -0,0 +1,262 @@
+#import <UIKit/UIKit.h>
+
+#include "SkStream_NSData.h"
+#include "SkTypeface.h"
+#include "SkFontHost.h"
+#include "SkThread.h"
+#include "SkTemplates.h"
+
+enum FontDesign {
+ kUnknown_Design,
+ kSans_FontDesign,
+ kSerif_FontDesign,
+
+ kIllegal_FontDesign, // never use with a real font
+};
+
+// returns kIllegal_FontDesign if not found
+static FontDesign find_design_from_name(const char name[]) {
+ static const struct {
+ const char* fName;
+ FontDesign fDesign;
+ } gRec[] = {
+ { "sans-serif", kSans_FontDesign },
+ { "serif", kSerif_FontDesign },
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
+ if (!strcasecmp(name, gRec[i].fName)) {
+ return gRec[i].fDesign;
+ }
+ }
+ return kIllegal_FontDesign;
+}
+
+struct FontRes {
+ const char* fName;
+ SkTypeface::Style fStyle;
+ FontDesign fDesign;
+};
+
+static const FontRes gFontRes[] = {
+ { "DroidSans", SkTypeface::kNormal, kSans_FontDesign },
+ { "DroidSans", SkTypeface::kBold, kSans_FontDesign },
+ { "DroidSerif-Regular", SkTypeface::kNormal, kSerif_FontDesign },
+ { "DroidSerif-Bold", SkTypeface::kBold, kSerif_FontDesign },
+// { "PescaderoPro", SkTypeface::kNormal, kSerif_FontDesign },
+// { "PescaderoPro-Bold", SkTypeface::kBold, kSerif_FontDesign },
+};
+#define FONTRES_COUNT SK_ARRAY_COUNT(gFontRes)
+
+#define DEFAULT_INDEX_REGULAR 1
+#define DEFAULT_INDEX_BOLD 2
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkTypeface_Stream : public SkTypeface {
+public:
+ SkTypeface_Stream(SkStream* stream, Style style);
+ virtual ~SkTypeface_Stream();
+
+ SkStream* refStream() {
+ fStream->ref();
+ return fStream;
+ }
+
+private:
+ SkStream* fStream;
+};
+
+static int32_t gUniqueFontID;
+
+SkTypeface_Stream::SkTypeface_Stream(SkStream* stream, Style style)
+: SkTypeface(style, sk_atomic_inc(&gUniqueFontID) + 1) {
+ fStream = stream;
+ fStream->ref();
+}
+
+SkTypeface_Stream::~SkTypeface_Stream() {
+ fStream->unref();
+}
+
+static SkTypeface_Stream* create_from_fontres(const FontRes& res) {
+ SkStream* stream = SkStream_NSData::CreateFromResource(res.fName, "ttf");
+ SkAutoUnref aur(stream);
+
+ return SkNEW_ARGS(SkTypeface_Stream, (stream, res.fStyle));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int compute_style_distance(SkTypeface::Style a, SkTypeface::Style b) {
+ int dist = 0;
+ int diff = a ^ b;
+ if (diff & SkTypeface::kBold) {
+ dist += 2;
+ }
+ if (diff & SkTypeface::kItalic) {
+ dist += 1;
+ }
+ return dist;
+}
+
+static SkTypeface_Stream* gFonts[FONTRES_COUNT];
+
+static void assure_init_fonts() {
+ static bool gOnce;
+ if (!gOnce) {
+ for (size_t i = 0; i < FONTRES_COUNT; i++) {
+ gFonts[i] = create_from_fontres(gFontRes[i]);
+ gOnce = true;
+ }
+ }
+}
+
+static SkTypeface_Stream* get_default_font(SkTypeface::Style style) {
+ assure_init_fonts();
+
+ if (style & SkTypeface::kBold) {
+ return gFonts[DEFAULT_INDEX_BOLD];
+ } else {
+ return gFonts[DEFAULT_INDEX_REGULAR];
+ }
+}
+
+static SkTypeface_Stream* find_by_id(SkFontID fontID) {
+ assure_init_fonts();
+
+ for (size_t i = 0; i < FONTRES_COUNT; i++) {
+ if (gFonts[i]->uniqueID() == fontID) {
+ return gFonts[i];
+ }
+ }
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> T* ref_and_return(T* obj) {
+ obj->ref();
+ return obj;
+}
+
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
+ const char familyName[],
+ const void* data, size_t bytelength,
+ SkTypeface::Style style) {
+ assure_init_fonts();
+
+ if (familyName) {
+ FontDesign design = find_design_from_name(familyName);
+ if (kIllegal_FontDesign != design) {
+ familyName = "$#@*&%*#$@ never match any name";
+ }
+
+ int bestDistance = 999;
+ int bestIndex = -1;
+ for (size_t i = 0; i < FONTRES_COUNT; i++) {
+ if (design == gFontRes[i].fDesign || !strcmp(gFontRes[i].fName, familyName)) {
+ int dist = compute_style_distance(style, gFontRes[i].fStyle);
+ if (dist < bestDistance) {
+ bestDistance = dist;
+ bestIndex = i;
+ }
+ }
+ }
+ if (bestIndex >= 0) {
+ return ref_and_return(gFonts[bestIndex]);
+ }
+ }
+
+ return ref_and_return(get_default_font(style));
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
+ SkASSERT(!"SkFontHost::CreateTypeface unimplemented");
+ return NULL;
+}
+
+SkTypeface* SkFontHost::CreateTypefaceFromFile(char const*) {
+// SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented");
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkStream* SkFontHost::OpenStream(uint32_t uniqueID) {
+ SkTypeface_Stream* tf = find_by_id(uniqueID);
+ SkASSERT(tf);
+ return tf->refStream();
+}
+
+size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length,
+ int32_t* index) {
+ SkDebugf("SkFontHost::GetFileName unimplemented\n");
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) {
+ SkASSERT(!"SkFontHost::Serialize unimplemented");
+}
+
+SkTypeface* SkFontHost::Deserialize(SkStream* stream) {
+ int style = stream->readU8();
+ int len = stream->readPackedUInt();
+ const char* name = NULL;
+ if (len > 0) {
+ SkString str;
+ str.resize(len);
+ stream->read(str.writable_str(), len);
+
+ if (str.startsWith("DroidSans")) {
+ name = "sans-serif";
+ } else if (str.startsWith("DroidSerif")) {
+ name = "serif";
+ }
+ SkDebugf("---- deserialize typeface <%s> %d %s\n", str.c_str(), style, name);
+ }
+// name = NULL; style = 0;
+ return SkFontHost::CreateTypeface(NULL, name, NULL, NULL,
+ (SkTypeface::Style)style);
+}
+
+SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) {
+ return 0;
+}
+
+#define FONT_CACHE_MEMORY_BUDGET 1 * 1024 * 1024
+
+size_t SkFontHost::ShouldPurgeFontCache(size_t sizeAllocatedSoFar) {
+ if (sizeAllocatedSoFar > FONT_CACHE_MEMORY_BUDGET)
+ return sizeAllocatedSoFar - FONT_CACHE_MEMORY_BUDGET;
+ else
+ return 0; // nothing to do
+}
+
+///////////////////////////////////////////////////////////////////////////////
+int SkFontHost::ComputeGammaFlag(const SkPaint& paint) {
+ return 0;
+}
+
+void SkFontHost::GetGammaTables(const uint8_t* tables[2]) {
+ tables[0] = NULL; // black gamma (e.g. exp=1.4)
+ tables[1] = NULL; // white gamma (e.g. exp= 1/1.4)
+}
+
+// static
+SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
+ uint32_t fontID,
+ SkAdvancedTypefaceMetrics::PerGlyphInfo perGlyphInfo) {
+ SkASSERT(!"SkFontHost::GetAdvancedTypefaceMetrics unimplemented");
+ return NULL;
+}
+
+void SkFontHost::FilterRec(SkScalerContext::Rec* rec, SkTypeface*) {
+}
+
+SkScalerContext* SkFontHost::CreateScalerContext(const SkDescriptor* desc) {
+ SkASSERT(!"SkFontHost::CreateScalarContext unimplemented");
+ return NULL;
+} \ No newline at end of file
diff --git a/utils/ios/SkImageDecoder_iOS.mm b/utils/ios/SkImageDecoder_iOS.mm
new file mode 100755
index 00000000..77d49a59
--- /dev/null
+++ b/utils/ios/SkImageDecoder_iOS.mm
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import <CoreGraphics/CoreGraphics.h>
+#include <CoreGraphics/CGColorSpace.h>
+#import <UIKit/UIKit.h>
+
+#include "SkImageDecoder.h"
+#include "SkImageEncoder.h"
+#include "SkMovie.h"
+#include "SkStream_NSData.h"
+
+class SkImageDecoder_iOS : public SkImageDecoder {
+protected:
+ virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
+};
+
+#define BITMAP_INFO (kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast)
+
+bool SkImageDecoder_iOS::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+
+ NSData* data = NSData_dataWithStream(stream);
+
+ UIImage* uimage = [UIImage imageWithData:data];
+
+ const int width = uimage.size.width;
+ const int height = uimage.size.height;
+ bm->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
+
+ if (!this->allocPixelRef(bm, NULL)) {
+ return false;
+ }
+
+ bm->lockPixels();
+ bm->eraseColor(0);
+
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef cg = CGBitmapContextCreate(bm->getPixels(), width, height,
+ 8, bm->rowBytes(), cs, BITMAP_INFO);
+ CGContextDrawImage(cg, CGRectMake(0, 0, width, height), uimage.CGImage);
+ CGContextRelease(cg);
+ CGColorSpaceRelease(cs);
+
+ bm->unlockPixels();
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) {
+ return new SkImageDecoder_iOS;
+}
+
+SkMovie* SkMovie::DecodeStream(SkStream* stream) {
+ return NULL;
+}
+
+SkImageEncoder* SkImageEncoder::Create(Type t) {
+ return NULL;
+}
+
diff --git a/utils/ios/SkOSFile_iOS.mm b/utils/ios/SkOSFile_iOS.mm
new file mode 100755
index 00000000..a6857613
--- /dev/null
+++ b/utils/ios/SkOSFile_iOS.mm
@@ -0,0 +1,98 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#include "SkOSFile.h"
+#include "SkString.h"
+
+struct SkFILE {
+ NSData* fData;
+ size_t fOffset;
+ size_t fLength;
+};
+
+SkFILE* sk_fopen(const char cpath[], SkFILE_Flags flags) {
+ if (flags & kWrite_SkFILE_Flag) {
+ return NULL;
+ }
+
+ SkString cname, csuffix;
+
+ const char* start = strrchr(cpath, '/');
+ if (NULL == start) {
+ start = cpath;
+ } else {
+ start += 1;
+ }
+ const char* stop = strrchr(cpath, '.');
+ if (NULL == stop) {
+ return NULL;
+ } else {
+ stop += 1;
+ }
+
+ cname.set(start, stop - start - 1);
+ csuffix.set(stop);
+
+ NSBundle* bundle = [NSBundle mainBundle];
+ NSString* name = [NSString stringWithUTF8String:cname.c_str()];
+ NSString* suffix = [NSString stringWithUTF8String:csuffix.c_str()];
+ NSString* path = [bundle pathForResource:name ofType:suffix];
+ NSData* data = [NSData dataWithContentsOfMappedFile:path];
+
+ if (data) {
+ [data retain];
+ SkFILE* rec = new SkFILE;
+ rec->fData = data;
+ rec->fOffset = 0;
+ rec->fLength = [data length];
+ return reinterpret_cast<SkFILE*>(rec);
+ }
+ return NULL;
+}
+
+size_t sk_fgetsize(SkFILE* rec) {
+ SkASSERT(rec);
+ return rec->fLength;
+}
+
+bool sk_frewind(SkFILE* rec) {
+ SkASSERT(rec);
+ rec->fOffset = 0;
+ return true;
+}
+
+size_t sk_fread(void* buffer, size_t byteCount, SkFILE* rec) {
+ if (NULL == buffer) {
+ return rec->fLength;
+ } else {
+ size_t remaining = rec->fLength - rec->fOffset;
+ if (byteCount > remaining) {
+ byteCount = remaining;
+ }
+ memcpy(buffer, (char*)[rec->fData bytes] + rec->fOffset, byteCount);
+ rec->fOffset += byteCount;
+ SkASSERT(rec->fOffset <= rec->fLength);
+ return byteCount;
+ }
+}
+
+size_t sk_fwrite(const void* buffer, size_t byteCount, SkFILE* f) {
+ SkASSERT(!"Not supported yet");
+ return 0;
+}
+
+void sk_fflush(SkFILE* f) {
+ SkASSERT(!"Not supported yet");
+}
+
+void sk_fclose(SkFILE* rec) {
+ SkASSERT(rec);
+ [rec->fData release];
+ delete rec;
+}
+
diff --git a/utils/ios/SkStream_NSData.mm b/utils/ios/SkStream_NSData.mm
new file mode 100755
index 00000000..ef20f63e
--- /dev/null
+++ b/utils/ios/SkStream_NSData.mm
@@ -0,0 +1,44 @@
+/*
+ * 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 "SkStream_NSData.h"
+
+NSData* NSData_dataWithStream(SkStream* stream) {
+ size_t length = stream->getLength();
+ void* src = malloc(length);
+ size_t bytes = stream->read(src, length);
+ SkASSERT(bytes == length);
+ return [NSData dataWithBytesNoCopy:src length:length freeWhenDone:YES];
+}
+
+NSData* NSData_dataFromResource(const char cname[], const char csuffix[]) {
+ NSBundle* bundle = [NSBundle mainBundle];
+ NSString* name = [NSString stringWithUTF8String:cname];
+ NSString* suffix = [NSString stringWithUTF8String:csuffix];
+ NSString* path = [bundle pathForResource:name ofType:suffix];
+ return [NSData dataWithContentsOfMappedFile:path];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkStream_NSData::SkStream_NSData(NSData* data) {
+ fNSData = data;
+ [fNSData retain];
+
+ this->setMemory([fNSData bytes], [fNSData length], false);
+}
+
+SkStream_NSData::~SkStream_NSData() {
+ [fNSData release];
+}
+
+SkStream_NSData* SkStream_NSData::CreateFromResource(const char name[],
+ const char suffix[]) {
+ NSData* data = NSData_dataFromResource(name, suffix);
+ return SkNEW_ARGS(SkStream_NSData, (data));
+}
+
diff --git a/utils/mac/SkCreateCGImageRef.cpp b/utils/mac/SkCreateCGImageRef.cpp
new file mode 100644
index 00000000..e931901b
--- /dev/null
+++ b/utils/mac/SkCreateCGImageRef.cpp
@@ -0,0 +1,232 @@
+
+/*
+ * 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;
+}
+
+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 SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
+ *info = kCGBitmapByteOrder32Big;
+ if (bm.isOpaque()) {
+ *info |= kCGImageAlphaNoneSkipLast;
+ } else {
+ *info |= kCGImageAlphaPremultipliedLast;
+ }
+#elif SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
+ // 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;
+}
+
+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/utils/mac/SkStream_mac.cpp b/utils/mac/SkStream_mac.cpp
new file mode 100644
index 00000000..64d2dbb2
--- /dev/null
+++ b/utils/mac/SkStream_mac.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 "SkCGUtils.h"
+#include "SkStream.h"
+
+// This is used by CGDataProviderCreateWithData
+
+static void unref_proc(void* info, const void* addr, size_t size) {
+ SkASSERT(info);
+ ((SkRefCnt*)info)->unref();
+}
+
+// These are used by CGDataProviderSequentialCallbacks
+
+static size_t get_bytes_proc(void* info, void* buffer, size_t bytes) {
+ SkASSERT(info);
+ return ((SkStream*)info)->read(buffer, bytes);
+}
+
+static off_t skip_forward_proc(void* info, off_t bytes) {
+ return ((SkStream*)info)->skip((size_t) bytes);
+}
+
+static void rewind_proc(void* info) {
+ SkASSERT(info);
+ ((SkStream*)info)->rewind();
+}
+
+static void release_info_proc(void* info) {
+ SkASSERT(info);
+ ((SkStream*)info)->unref();
+}
+
+CGDataProviderRef SkCreateDataProviderFromStream(SkStream* stream) {
+ stream->ref(); // unref will be called when the provider is deleted
+
+ const void* addr = stream->getMemoryBase();
+ if (addr) {
+ // special-case when the stream is just a block of ram
+ return CGDataProviderCreateWithData(stream, addr, stream->getLength(),
+ unref_proc);
+ }
+
+ CGDataProviderSequentialCallbacks rec;
+ sk_bzero(&rec, sizeof(rec));
+ rec.version = 0;
+ rec.getBytes = get_bytes_proc;
+ rec.skipForward = skip_forward_proc;
+ rec.rewind = rewind_proc;
+ rec.releaseInfo = release_info_proc;
+ return CGDataProviderCreateSequential(stream, &rec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkData.h"
+
+CGDataProviderRef SkCreateDataProviderFromData(SkData* data) {
+ data->ref();
+ return CGDataProviderCreateWithData(data, data->data(), data->size(),
+ unref_proc);
+}
diff --git a/utils/win/SkAutoCoInitialize.cpp b/utils/win/SkAutoCoInitialize.cpp
new file mode 100644
index 00000000..dd6e9368
--- /dev/null
+++ b/utils/win/SkAutoCoInitialize.cpp
@@ -0,0 +1,29 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <ole2.h>
+#include "SkAutoCoInitialize.h"
+
+SkAutoCoInitialize::SkAutoCoInitialize() :
+ fHR(
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
+ )
+{ }
+
+SkAutoCoInitialize::~SkAutoCoInitialize() {
+ if (SUCCEEDED(this->fHR)) {
+ CoUninitialize();
+ }
+}
+
+bool SkAutoCoInitialize::succeeded() {
+ return SUCCEEDED(this->fHR) || RPC_E_CHANGED_MODE == this->fHR;
+}
diff --git a/utils/win/SkDWriteFontFileStream.cpp b/utils/win/SkDWriteFontFileStream.cpp
new file mode 100644
index 00000000..c7dc3b21
--- /dev/null
+++ b/utils/win/SkDWriteFontFileStream.cpp
@@ -0,0 +1,232 @@
+/*
+ * 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"
+#include "SkDWriteFontFileStream.h"
+#include "SkHRESULT.h"
+#include "SkTemplates.h"
+#include "SkTFitsIn.h"
+#include "SkTScopedComPtr.h"
+
+#include <dwrite.h>
+
+///////////////////////////////////////////////////////////////////////////////
+// SkIDWriteFontFileStream
+
+SkDWriteFontFileStream::SkDWriteFontFileStream(IDWriteFontFileStream* fontFileStream)
+ : fFontFileStream(SkRefComPtr(fontFileStream))
+ , fPos(0)
+ , fLockedMemory(NULL)
+ , fFragmentLock(NULL) {
+}
+
+SkDWriteFontFileStream::~SkDWriteFontFileStream() {
+ if (fFragmentLock) {
+ fFontFileStream->ReleaseFileFragment(fFragmentLock);
+ }
+}
+
+size_t SkDWriteFontFileStream::read(void* buffer, size_t size) {
+ HRESULT hr = S_OK;
+
+ if (NULL == buffer) {
+ size_t fileSize = this->getLength();
+
+ if (fPos + size > fileSize) {
+ size_t skipped = fileSize - fPos;
+ fPos = fileSize;
+ return skipped;
+ } else {
+ fPos += size;
+ return size;
+ }
+ }
+
+ const void* start;
+ void* fragmentLock;
+ hr = fFontFileStream->ReadFileFragment(&start, fPos, size, &fragmentLock);
+ if (SUCCEEDED(hr)) {
+ memcpy(buffer, start, size);
+ fFontFileStream->ReleaseFileFragment(fragmentLock);
+ fPos += size;
+ return size;
+ }
+
+ //The read may have failed because we asked for too much data.
+ size_t fileSize = this->getLength();
+ if (fPos + size <= fileSize) {
+ //This means we were within bounds, but failed for some other reason.
+ return 0;
+ }
+
+ size_t read = fileSize - fPos;
+ hr = fFontFileStream->ReadFileFragment(&start, fPos, read, &fragmentLock);
+ if (SUCCEEDED(hr)) {
+ memcpy(buffer, start, read);
+ fFontFileStream->ReleaseFileFragment(fragmentLock);
+ fPos = fileSize;
+ return read;
+ }
+
+ return 0;
+}
+
+bool SkDWriteFontFileStream::isAtEnd() const {
+ return fPos == this->getLength();
+}
+
+bool SkDWriteFontFileStream::rewind() {
+ fPos = 0;
+ return true;
+}
+
+SkDWriteFontFileStream* SkDWriteFontFileStream::duplicate() const {
+ return SkNEW_ARGS(SkDWriteFontFileStream, (fFontFileStream.get()));
+}
+
+size_t SkDWriteFontFileStream::getPosition() const {
+ return fPos;
+}
+
+bool SkDWriteFontFileStream::seek(size_t position) {
+ size_t length = this->getLength();
+ fPos = (position > length) ? length : position;
+ return true;
+}
+
+bool SkDWriteFontFileStream::move(long offset) {
+ return seek(fPos + offset);
+}
+
+SkDWriteFontFileStream* SkDWriteFontFileStream::fork() const {
+ SkAutoTUnref<SkDWriteFontFileStream> that(this->duplicate());
+ that->seek(fPos);
+ return that.detach();
+}
+
+size_t SkDWriteFontFileStream::getLength() const {
+ HRESULT hr = S_OK;
+ UINT64 realFileSize = 0;
+ hr = fFontFileStream->GetFileSize(&realFileSize);
+ if (!SkTFitsIn<size_t>(realFileSize)) {
+ return 0;
+ }
+ return static_cast<size_t>(realFileSize);
+}
+
+const void* SkDWriteFontFileStream::getMemoryBase() {
+ if (fLockedMemory) {
+ return fLockedMemory;
+ }
+
+ UINT64 fileSize;
+ HRNM(fFontFileStream->GetFileSize(&fileSize), "Could not get file size");
+ HRNM(fFontFileStream->ReadFileFragment(&fLockedMemory, 0, fileSize, &fFragmentLock),
+ "Could not lock file fragment.");
+ return fLockedMemory;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SkIDWriteFontFileStreamWrapper
+
+HRESULT SkDWriteFontFileStreamWrapper::Create(SkStream* stream, SkDWriteFontFileStreamWrapper** streamFontFileStream) {
+ *streamFontFileStream = new SkDWriteFontFileStreamWrapper(stream);
+ if (NULL == streamFontFileStream) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+SkDWriteFontFileStreamWrapper::SkDWriteFontFileStreamWrapper(SkStream* stream)
+ : fRefCount(1), fStream(SkRef(stream)) {
+}
+
+HRESULT STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::QueryInterface(REFIID iid, void** ppvObject) {
+ if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) {
+ *ppvObject = this;
+ AddRef();
+ return S_OK;
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::AddRef() {
+ return InterlockedIncrement(&fRefCount);
+}
+
+ULONG STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::Release() {
+ ULONG newCount = InterlockedDecrement(&fRefCount);
+ if (0 == newCount) {
+ delete this;
+ }
+ return newCount;
+}
+
+HRESULT STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::ReadFileFragment(
+ void const** fragmentStart,
+ UINT64 fileOffset,
+ UINT64 fragmentSize,
+ void** fragmentContext)
+{
+ // The loader is responsible for doing a bounds check.
+ UINT64 fileSize;
+ this->GetFileSize(&fileSize);
+ if (fileOffset > fileSize || fragmentSize > fileSize - fileOffset) {
+ *fragmentStart = NULL;
+ *fragmentContext = NULL;
+ return E_FAIL;
+ }
+
+ if (!SkTFitsIn<size_t>(fileOffset + fragmentSize)) {
+ return E_FAIL;
+ }
+
+ const void* data = fStream->getMemoryBase();
+ if (NULL != data) {
+ *fragmentStart = static_cast<BYTE const*>(data) + static_cast<size_t>(fileOffset);
+ *fragmentContext = NULL;
+
+ } else {
+ //May be called from multiple threads.
+ SkAutoMutexAcquire ama(fStreamMutex);
+
+ *fragmentStart = NULL;
+ *fragmentContext = NULL;
+
+ if (!fStream->rewind()) {
+ return E_FAIL;
+ }
+ if (fStream->skip(static_cast<size_t>(fileOffset)) != fileOffset) {
+ return E_FAIL;
+ }
+ SkAutoTMalloc<uint8_t> streamData(static_cast<size_t>(fragmentSize));
+ if (fStream->read(streamData.get(), static_cast<size_t>(fragmentSize)) != fragmentSize) {
+ return E_FAIL;
+ }
+
+ *fragmentStart = streamData.get();
+ *fragmentContext = streamData.detach();
+ }
+ return S_OK;
+}
+
+void STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::ReleaseFileFragment(void* fragmentContext) {
+ sk_free(fragmentContext);
+}
+
+HRESULT STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::GetFileSize(UINT64* fileSize) {
+ *fileSize = fStream->getLength();
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SkDWriteFontFileStreamWrapper::GetLastWriteTime(UINT64* lastWriteTime) {
+ // The concept of last write time does not apply to this loader.
+ *lastWriteTime = 0;
+ return E_NOTIMPL;
+}
diff --git a/utils/win/SkDWriteFontFileStream.h b/utils/win/SkDWriteFontFileStream.h
new file mode 100644
index 00000000..5a56290c
--- /dev/null
+++ b/utils/win/SkDWriteFontFileStream.h
@@ -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.
+ */
+
+#ifndef SkDWriteFontFileStream_DEFINED
+#define SkDWriteFontFileStream_DEFINED
+
+#include "SkTypes.h"
+
+#include "SkStream.h"
+#include "SkTScopedComPtr.h"
+
+#include <dwrite.h>
+
+/**
+ * An SkStream backed by an IDWriteFontFileStream.
+ * This allows Skia code to read an IDWriteFontFileStream.
+ */
+class SkDWriteFontFileStream : public SkStreamMemory {
+public:
+ explicit SkDWriteFontFileStream(IDWriteFontFileStream* fontFileStream);
+ virtual ~SkDWriteFontFileStream();
+
+ virtual size_t read(void* buffer, size_t size) SK_OVERRIDE;
+ virtual bool isAtEnd() const SK_OVERRIDE;
+ virtual bool rewind() SK_OVERRIDE;
+ virtual SkDWriteFontFileStream* duplicate() const SK_OVERRIDE;
+ virtual size_t getPosition() const SK_OVERRIDE;
+ virtual bool seek(size_t position) SK_OVERRIDE;
+ virtual bool move(long offset) SK_OVERRIDE;
+ virtual SkDWriteFontFileStream* fork() const SK_OVERRIDE;
+ virtual size_t getLength() const SK_OVERRIDE;
+ virtual const void* getMemoryBase() SK_OVERRIDE;
+
+private:
+ SkTScopedComPtr<IDWriteFontFileStream> fFontFileStream;
+ size_t fPos;
+ const void* fLockedMemory;
+ void* fFragmentLock;
+};
+
+/**
+ * An IDWriteFontFileStream backed by an SkStream.
+ * This allows DirectWrite to read an SkStream.
+ */
+class SkDWriteFontFileStreamWrapper : public IDWriteFontFileStream {
+public:
+ // IUnknown methods
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ // IDWriteFontFileStream methods
+ virtual HRESULT STDMETHODCALLTYPE ReadFileFragment(
+ void const** fragmentStart,
+ UINT64 fileOffset,
+ UINT64 fragmentSize,
+ void** fragmentContext);
+
+ virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext);
+ virtual HRESULT STDMETHODCALLTYPE GetFileSize(UINT64* fileSize);
+ virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(UINT64* lastWriteTime);
+
+ static HRESULT Create(SkStream* stream, SkDWriteFontFileStreamWrapper** streamFontFileStream);
+
+private:
+ explicit SkDWriteFontFileStreamWrapper(SkStream* stream);
+
+ ULONG fRefCount;
+ SkAutoTUnref<SkStream> fStream;
+ SkMutex fStreamMutex;
+};
+#endif
diff --git a/utils/win/SkDWriteGeometrySink.cpp b/utils/win/SkDWriteGeometrySink.cpp
new file mode 100644
index 00000000..5455e669
--- /dev/null
+++ b/utils/win/SkDWriteGeometrySink.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 "SkTypes.h"
+
+#include "SkDWriteGeometrySink.h"
+#include "SkFloatUtils.h"
+#include "SkPath.h"
+
+#include <dwrite.h>
+#include <d2d1.h>
+
+SkDWriteGeometrySink::SkDWriteGeometrySink(SkPath* path) : fRefCount(1), fPath(path) { }
+
+SkDWriteGeometrySink::~SkDWriteGeometrySink() { }
+
+HRESULT STDMETHODCALLTYPE SkDWriteGeometrySink::QueryInterface(REFIID iid, void **object) {
+ if (NULL == object) {
+ return E_INVALIDARG;
+ }
+ if (iid == __uuidof(IUnknown) || iid == __uuidof(IDWriteGeometrySink)) {
+ *object = static_cast<IDWriteGeometrySink*>(this);
+ this->AddRef();
+ return S_OK;
+ } else {
+ *object = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG STDMETHODCALLTYPE SkDWriteGeometrySink::AddRef(void) {
+ return static_cast<ULONG>(InterlockedIncrement(&fRefCount));
+}
+
+ULONG STDMETHODCALLTYPE SkDWriteGeometrySink::Release(void) {
+ ULONG res = static_cast<ULONG>(InterlockedDecrement(&fRefCount));
+ if (0 == res) {
+ delete this;
+ }
+ return res;
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::SetFillMode(D2D1_FILL_MODE fillMode) {
+ switch (fillMode) {
+ case D2D1_FILL_MODE_ALTERNATE:
+ fPath->setFillType(SkPath::kEvenOdd_FillType);
+ break;
+ case D2D1_FILL_MODE_WINDING:
+ fPath->setFillType(SkPath::kWinding_FillType);
+ break;
+ default:
+ SkASSERT(!"Unknown D2D1_FILL_MODE.");
+ break;
+ }
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::SetSegmentFlags(D2D1_PATH_SEGMENT vertexFlags) {
+ if (vertexFlags == D2D1_PATH_SEGMENT_NONE || vertexFlags == D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN) {
+ SkASSERT(!"Invalid D2D1_PATH_SEGMENT value.");
+ }
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::BeginFigure(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin) {
+ fPath->moveTo(SkFloatToScalar(startPoint.x), SkFloatToScalar(startPoint.y));
+ if (figureBegin == D2D1_FIGURE_BEGIN_HOLLOW) {
+ SkASSERT(!"Invalid D2D1_FIGURE_BEGIN value.");
+ }
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::AddLines(const D2D1_POINT_2F *points, UINT pointsCount) {
+ for (const D2D1_POINT_2F *end = &points[pointsCount]; points < end; ++points) {
+ fPath->lineTo(SkFloatToScalar(points->x), SkFloatToScalar(points->y));
+ }
+}
+
+static bool approximately_equal(float a, float b) {
+ const SkFloatingPoint<float, 10> lhs(a), rhs(b);
+ return lhs.AlmostEquals(rhs);
+}
+
+typedef struct {
+ float x;
+ float y;
+} Cubic[4], Quadratic[3];
+
+static bool check_quadratic(const Cubic& cubic, Quadratic& reduction) {
+ float dx10 = cubic[1].x - cubic[0].x;
+ float dx23 = cubic[2].x - cubic[3].x;
+ float midX = cubic[0].x + dx10 * 3 / 2;
+ //NOTE: !approximately_equal(midX - cubic[3].x, dx23 * 3 / 2)
+ //does not work as subnormals get in between the left side and 0.
+ if (!approximately_equal(midX, (dx23 * 3 / 2) + cubic[3].x)) {
+ return false;
+ }
+ float dy10 = cubic[1].y - cubic[0].y;
+ float dy23 = cubic[2].y - cubic[3].y;
+ float midY = cubic[0].y + dy10 * 3 / 2;
+ if (!approximately_equal(midY, (dy23 * 3 / 2) + cubic[3].y)) {
+ return false;
+ }
+ reduction[0] = cubic[0];
+ reduction[1].x = midX;
+ reduction[1].y = midY;
+ reduction[2] = cubic[3];
+ return true;
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount) {
+ SkPoint lastPt;
+ fPath->getLastPt(&lastPt);
+ D2D1_POINT_2F prevPt = { SkScalarToFloat(lastPt.fX), SkScalarToFloat(lastPt.fY) };
+
+ for (const D2D1_BEZIER_SEGMENT *end = &beziers[beziersCount]; beziers < end; ++beziers) {
+ Cubic cubic = { { prevPt.x, prevPt.y },
+ { beziers->point1.x, beziers->point1.y },
+ { beziers->point2.x, beziers->point2.y },
+ { beziers->point3.x, beziers->point3.y }, };
+ Quadratic quadratic;
+ if (check_quadratic(cubic, quadratic)) {
+ fPath->quadTo(SkFloatToScalar(quadratic[1].x), SkFloatToScalar(quadratic[1].y),
+ SkFloatToScalar(quadratic[2].x), SkFloatToScalar(quadratic[2].y));
+ } else {
+ fPath->cubicTo(SkFloatToScalar(beziers->point1.x), SkFloatToScalar(beziers->point1.y),
+ SkFloatToScalar(beziers->point2.x), SkFloatToScalar(beziers->point2.y),
+ SkFloatToScalar(beziers->point3.x), SkFloatToScalar(beziers->point3.y));
+ }
+ prevPt = beziers->point3;
+ }
+}
+
+void STDMETHODCALLTYPE SkDWriteGeometrySink::EndFigure(D2D1_FIGURE_END figureEnd) {
+ fPath->close();
+}
+
+HRESULT SkDWriteGeometrySink::Close() {
+ return S_OK;
+}
+
+HRESULT SkDWriteGeometrySink::Create(SkPath* path, IDWriteGeometrySink** geometryToPath) {
+ *geometryToPath = new SkDWriteGeometrySink(path);
+ return S_OK;
+}
diff --git a/utils/win/SkDWriteGeometrySink.h b/utils/win/SkDWriteGeometrySink.h
new file mode 100644
index 00000000..99a32627
--- /dev/null
+++ b/utils/win/SkDWriteGeometrySink.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 SkDWriteToPath_DEFINED
+#define SkDWriteToPath_DEFINED
+
+#include "SkTypes.h"
+
+class SkPath;
+
+#include <dwrite.h>
+#include <d2d1.h>
+
+class SkDWriteGeometrySink : public IDWriteGeometrySink {
+private:
+ LONG fRefCount;
+ SkPath* fPath;
+
+ SkDWriteGeometrySink(const SkDWriteGeometrySink&);
+ SkDWriteGeometrySink& operator=(const SkDWriteGeometrySink&);
+
+protected:
+ explicit SkDWriteGeometrySink(SkPath* path);
+ virtual ~SkDWriteGeometrySink();
+
+public:
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object) SK_OVERRIDE;
+ virtual ULONG STDMETHODCALLTYPE AddRef(void) SK_OVERRIDE;
+ virtual ULONG STDMETHODCALLTYPE Release(void) SK_OVERRIDE;
+
+ virtual void STDMETHODCALLTYPE SetFillMode(D2D1_FILL_MODE fillMode) SK_OVERRIDE;
+ virtual void STDMETHODCALLTYPE SetSegmentFlags(D2D1_PATH_SEGMENT vertexFlags) SK_OVERRIDE;
+ virtual void STDMETHODCALLTYPE BeginFigure(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin) SK_OVERRIDE;
+ virtual void STDMETHODCALLTYPE AddLines(const D2D1_POINT_2F *points, UINT pointsCount) SK_OVERRIDE;
+ virtual void STDMETHODCALLTYPE AddBeziers(const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount) SK_OVERRIDE;
+ virtual void STDMETHODCALLTYPE EndFigure(D2D1_FIGURE_END figureEnd) SK_OVERRIDE;
+ virtual HRESULT STDMETHODCALLTYPE Close() SK_OVERRIDE;
+
+ static HRESULT Create(SkPath* path, IDWriteGeometrySink** geometryToPath);
+};
+
+#endif
diff --git a/utils/win/SkHRESULT.cpp b/utils/win/SkHRESULT.cpp
new file mode 100644
index 00000000..32d9d4c3
--- /dev/null
+++ b/utils/win/SkHRESULT.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "SkTypes.h"
+
+#include "SKHRESULT.h"
+
+void SkTraceHR(const char* file, unsigned long line,
+ HRESULT hr, const char* msg) {
+ SkDEBUGCODE(if (NULL != msg) SkDEBUGF(("%s\n", msg)));
+ SkDEBUGF(("%s(%lu) : error 0x%x: ", file, line, hr));
+
+ LPSTR errorText = NULL;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ hr,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR) &errorText,
+ 0,
+ NULL
+ );
+
+ if (NULL == errorText) {
+ SkDEBUGF(("<unknown>\n"));
+ } else {
+ SkDEBUGF(("%s", errorText));
+ LocalFree(errorText);
+ errorText = NULL;
+ }
+}
diff --git a/utils/win/SkIStream.cpp b/utils/win/SkIStream.cpp
new file mode 100644
index 00000000..7880fa07
--- /dev/null
+++ b/utils/win/SkIStream.cpp
@@ -0,0 +1,270 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <ole2.h>
+#include "SkIStream.h"
+#include "SkStream.h"
+
+/**
+ * SkBaseIStream
+ */
+SkBaseIStream::SkBaseIStream() : _refcount(1) { }
+SkBaseIStream::~SkBaseIStream() { }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::QueryInterface(REFIID iid
+ , void ** ppvObject)
+{
+ if (NULL == ppvObject) {
+ return E_INVALIDARG;
+ }
+ if (iid == __uuidof(IUnknown)
+ || iid == __uuidof(IStream)
+ || iid == __uuidof(ISequentialStream))
+ {
+ *ppvObject = static_cast<IStream*>(this);
+ AddRef();
+ return S_OK;
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG STDMETHODCALLTYPE SkBaseIStream::AddRef(void) {
+ return (ULONG)InterlockedIncrement(&_refcount);
+}
+
+ULONG STDMETHODCALLTYPE SkBaseIStream::Release(void) {
+ ULONG res = (ULONG) InterlockedDecrement(&_refcount);
+ if (0 == res) {
+ delete this;
+ }
+ return res;
+}
+
+// ISequentialStream Interface
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Read(void* pv
+ , ULONG cb
+ , ULONG* pcbRead)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Write(void const* pv
+ , ULONG cb
+ , ULONG* pcbWritten)
+{ return E_NOTIMPL; }
+
+// IStream Interface
+HRESULT STDMETHODCALLTYPE SkBaseIStream::SetSize(ULARGE_INTEGER)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::CopyTo(IStream*
+ , ULARGE_INTEGER
+ , ULARGE_INTEGER*
+ , ULARGE_INTEGER*)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Commit(DWORD)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Revert(void)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::LockRegion(ULARGE_INTEGER
+ , ULARGE_INTEGER
+ , DWORD)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::UnlockRegion(ULARGE_INTEGER
+ , ULARGE_INTEGER
+ , DWORD)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Clone(IStream **)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Seek(LARGE_INTEGER liDistanceToMove
+ , DWORD dwOrigin
+ , ULARGE_INTEGER* lpNewFilePointer)
+{ return E_NOTIMPL; }
+
+HRESULT STDMETHODCALLTYPE SkBaseIStream::Stat(STATSTG* pStatstg
+ , DWORD grfStatFlag)
+{ return E_NOTIMPL; }
+
+
+/**
+ * SkIStream
+ */
+SkIStream::SkIStream(SkStream* stream, bool unrefOnRelease)
+ : SkBaseIStream()
+ , fSkStream(stream)
+ , fUnrefOnRelease(unrefOnRelease)
+ , fLocation()
+{
+ this->fSkStream->rewind();
+}
+
+SkIStream::~SkIStream() {
+ if (NULL != this->fSkStream && fUnrefOnRelease) {
+ this->fSkStream->unref();
+ }
+}
+
+HRESULT SkIStream::CreateFromSkStream(SkStream* stream
+ , bool unrefOnRelease
+ , IStream ** ppStream)
+{
+ *ppStream = new SkIStream(stream, unrefOnRelease);
+ return S_OK;
+}
+
+// ISequentialStream Interface
+HRESULT STDMETHODCALLTYPE SkIStream::Read(void* pv, ULONG cb, ULONG* pcbRead) {
+ *pcbRead = static_cast<ULONG>(this->fSkStream->read(pv, cb));
+ this->fLocation.QuadPart += *pcbRead;
+ return (*pcbRead == cb) ? S_OK : S_FALSE;
+}
+
+HRESULT STDMETHODCALLTYPE SkIStream::Write(void const* pv
+ , ULONG cb
+ , ULONG* pcbWritten)
+{
+ return STG_E_CANTSAVE;
+}
+
+// IStream Interface
+HRESULT STDMETHODCALLTYPE SkIStream::Seek(LARGE_INTEGER liDistanceToMove
+ , DWORD dwOrigin
+ , ULARGE_INTEGER* lpNewFilePointer)
+{
+ HRESULT hr = S_OK;
+
+ switch(dwOrigin) {
+ case STREAM_SEEK_SET: {
+ if (!this->fSkStream->rewind()) {
+ hr = E_FAIL;
+ } else {
+ size_t skipped = this->fSkStream->skip(
+ static_cast<size_t>(liDistanceToMove.QuadPart)
+ );
+ this->fLocation.QuadPart = skipped;
+ if (skipped != liDistanceToMove.QuadPart) {
+ hr = E_FAIL;
+ }
+ }
+ break;
+ }
+ case STREAM_SEEK_CUR: {
+ size_t skipped = this->fSkStream->skip(
+ static_cast<size_t>(liDistanceToMove.QuadPart)
+ );
+ this->fLocation.QuadPart += skipped;
+ if (skipped != liDistanceToMove.QuadPart) {
+ hr = E_FAIL;
+ }
+ break;
+ }
+ case STREAM_SEEK_END: {
+ if (!this->fSkStream->rewind()) {
+ hr = E_FAIL;
+ } else {
+ LONGLONG skip = this->fSkStream->getLength()
+ + liDistanceToMove.QuadPart;
+ size_t skipped = this->fSkStream->skip(static_cast<size_t>(skip));
+ this->fLocation.QuadPart = skipped;
+ if (skipped != skip) {
+ hr = E_FAIL;
+ }
+ }
+ break;
+ }
+ default:
+ hr = STG_E_INVALIDFUNCTION;
+ break;
+ }
+
+ if (NULL != lpNewFilePointer) {
+ lpNewFilePointer->QuadPart = this->fLocation.QuadPart;
+ }
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE SkIStream::Stat(STATSTG* pStatstg
+ , DWORD grfStatFlag)
+{
+ if (0 == (grfStatFlag & STATFLAG_NONAME)) {
+ return STG_E_INVALIDFLAG;
+ }
+ pStatstg->pwcsName = NULL;
+ pStatstg->cbSize.QuadPart = this->fSkStream->getLength();
+ pStatstg->clsid = CLSID_NULL;
+ pStatstg->type = STGTY_STREAM;
+ pStatstg->grfMode = STGM_READ;
+ return S_OK;
+}
+
+
+/**
+ * SkIWStream
+ */
+SkWIStream::SkWIStream(SkWStream* stream)
+ : SkBaseIStream()
+ , fSkWStream(stream)
+{ }
+
+SkWIStream::~SkWIStream() {
+ if (NULL != this->fSkWStream) {
+ this->fSkWStream->flush();
+ }
+}
+
+HRESULT SkWIStream::CreateFromSkWStream(SkWStream* stream
+ , IStream ** ppStream)
+{
+ *ppStream = new SkWIStream(stream);
+ return S_OK;
+}
+
+// ISequentialStream Interface
+HRESULT STDMETHODCALLTYPE SkWIStream::Write(void const* pv
+ , ULONG cb
+ , ULONG* pcbWritten)
+{
+ HRESULT hr = S_OK;
+ bool wrote = this->fSkWStream->write(pv, cb);
+ if (wrote) {
+ *pcbWritten = cb;
+ } else {
+ *pcbWritten = 0;
+ hr = S_FALSE;
+ }
+ return hr;
+}
+
+// IStream Interface
+HRESULT STDMETHODCALLTYPE SkWIStream::Commit(DWORD) {
+ this->fSkWStream->flush();
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SkWIStream::Stat(STATSTG* pStatstg
+ , DWORD grfStatFlag)
+{
+ if (0 == (grfStatFlag & STATFLAG_NONAME)) {
+ return STG_E_INVALIDFLAG;
+ }
+ pStatstg->pwcsName = NULL;
+ pStatstg->cbSize.QuadPart = 0;
+ pStatstg->clsid = CLSID_NULL;
+ pStatstg->type = STGTY_STREAM;
+ pStatstg->grfMode = STGM_WRITE;
+ return S_OK;
+}
diff --git a/utils/win/SkWGL_win.cpp b/utils/win/SkWGL_win.cpp
new file mode 100644
index 00000000..4b3b3e63
--- /dev/null
+++ b/utils/win/SkWGL_win.cpp
@@ -0,0 +1,368 @@
+
+/*
+ * 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 "SkWGL.h"
+
+#include "SkTDArray.h"
+#include "SkTSearch.h"
+#include "SkTSort.h"
+
+bool SkWGLExtensions::hasExtension(HDC dc, const char* ext) const {
+ if (NULL == this->fGetExtensionsString) {
+ return false;
+ }
+ if (!strcmp("WGL_ARB_extensions_string", ext)) {
+ return true;
+ }
+ const char* extensionString = this->getExtensionsString(dc);
+ int extLength = strlen(ext);
+
+ while (true) {
+ int n = strcspn(extensionString, " ");
+ if (n == extLength && 0 == strncmp(ext, extensionString, n)) {
+ return true;
+ }
+ if (0 == extensionString[n]) {
+ return false;
+ }
+ extensionString += n+1;
+ }
+
+ return false;
+}
+
+const char* SkWGLExtensions::getExtensionsString(HDC hdc) const {
+ return fGetExtensionsString(hdc);
+}
+
+BOOL SkWGLExtensions::choosePixelFormat(HDC hdc,
+ const int* piAttribIList,
+ const FLOAT* pfAttribFList,
+ UINT nMaxFormats,
+ int* piFormats,
+ UINT* nNumFormats) const {
+ return fChoosePixelFormat(hdc, piAttribIList, pfAttribFList,
+ nMaxFormats, piFormats, nNumFormats);
+}
+
+BOOL SkWGLExtensions::getPixelFormatAttribiv(HDC hdc,
+ int iPixelFormat,
+ int iLayerPlane,
+ UINT nAttributes,
+ const int *piAttributes,
+ int *piValues) const {
+ return fGetPixelFormatAttribiv(hdc, iPixelFormat, iLayerPlane,
+ nAttributes, piAttributes, piValues);
+}
+
+BOOL SkWGLExtensions::getPixelFormatAttribfv(HDC hdc,
+ int iPixelFormat,
+ int iLayerPlane,
+ UINT nAttributes,
+ const int *piAttributes,
+ float *pfValues) const {
+ return fGetPixelFormatAttribfv(hdc, iPixelFormat, iLayerPlane,
+ nAttributes, piAttributes, pfValues);
+}
+HGLRC SkWGLExtensions::createContextAttribs(HDC hDC,
+ HGLRC hShareContext,
+ const int *attribList) const {
+ return fCreateContextAttribs(hDC, hShareContext, attribList);
+}
+
+namespace {
+
+struct PixelFormat {
+ int fFormat;
+ int fCoverageSamples;
+ int fColorSamples;
+ int fChoosePixelFormatRank;
+};
+
+bool pf_less(const PixelFormat& a, const PixelFormat& b) {
+ if (a.fCoverageSamples < b.fCoverageSamples) {
+ return true;
+ } else if (b.fCoverageSamples < a.fCoverageSamples) {
+ return false;
+ } else if (a.fColorSamples < b.fColorSamples) {
+ return true;
+ } else if (b.fColorSamples < a.fColorSamples) {
+ return false;
+ } else if (a.fChoosePixelFormatRank < b.fChoosePixelFormatRank) {
+ return true;
+ }
+ return false;
+}
+}
+
+int SkWGLExtensions::selectFormat(const int formats[],
+ int formatCount,
+ HDC dc,
+ int desiredSampleCount) {
+ PixelFormat desiredFormat = {
+ 0,
+ desiredSampleCount,
+ 0,
+ 0,
+ };
+ SkTDArray<PixelFormat> rankedFormats;
+ rankedFormats.setCount(formatCount);
+ bool supportsCoverage = this->hasExtension(dc,
+ "WGL_NV_multisample_coverage");
+ for (int i = 0; i < formatCount; ++i) {
+ static const int queryAttrs[] = {
+ SK_WGL_COVERAGE_SAMPLES,
+ // Keep COLOR_SAMPLES at the end so it can be skipped
+ SK_WGL_COLOR_SAMPLES,
+ };
+ int answers[2];
+ int queryAttrCnt = supportsCoverage ?
+ SK_ARRAY_COUNT(queryAttrs) :
+ SK_ARRAY_COUNT(queryAttrs) - 1;
+ this->getPixelFormatAttribiv(dc,
+ formats[i],
+ 0,
+ queryAttrCnt,
+ queryAttrs,
+ answers);
+ rankedFormats[i].fFormat = formats[i];
+ rankedFormats[i].fCoverageSamples = answers[0];
+ rankedFormats[i].fColorSamples = answers[supportsCoverage ? 1 : 0];
+ rankedFormats[i].fChoosePixelFormatRank = i;
+ }
+ SkTQSort(rankedFormats.begin(),
+ rankedFormats.begin() + rankedFormats.count() - 1,
+ SkTLessFunctionToFunctorAdaptor<PixelFormat, pf_less>());
+ int idx = SkTSearch<PixelFormat, pf_less>(rankedFormats.begin(),
+ rankedFormats.count(),
+ desiredFormat,
+ sizeof(PixelFormat));
+ if (idx < 0) {
+ idx = ~idx;
+ }
+ return rankedFormats[idx].fFormat;
+}
+
+
+namespace {
+
+#if defined(UNICODE)
+ #define STR_LIT(X) L## #X
+#else
+ #define STR_LIT(X) #X
+#endif
+
+#define DUMMY_CLASS STR_LIT("DummyClass")
+
+HWND create_dummy_window() {
+ HMODULE module = GetModuleHandle(NULL);
+ HWND dummy;
+ RECT windowRect;
+ windowRect.left = 0;
+ windowRect.right = 8;
+ windowRect.top = 0;
+ windowRect.bottom = 8;
+
+ WNDCLASS wc;
+
+ wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+ wc.lpfnWndProc = (WNDPROC) DefWindowProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = NULL;
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = DUMMY_CLASS;
+
+ if(!RegisterClass(&wc)) {
+ return 0;
+ }
+
+ DWORD style, exStyle;
+ exStyle = WS_EX_CLIENTEDGE;
+ style = WS_SYSMENU;
+
+ AdjustWindowRectEx(&windowRect, style, false, exStyle);
+ if(!(dummy = CreateWindowEx(exStyle,
+ DUMMY_CLASS,
+ STR_LIT("DummyWindow"),
+ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | style,
+ 0, 0,
+ windowRect.right-windowRect.left,
+ windowRect.bottom-windowRect.top,
+ NULL, NULL,
+ module,
+ NULL))) {
+ UnregisterClass(DUMMY_CLASS, module);
+ return NULL;
+ }
+ ShowWindow(dummy, SW_HIDE);
+
+ return dummy;
+}
+
+void destroy_dummy_window(HWND dummy) {
+ DestroyWindow(dummy);
+ HMODULE module = GetModuleHandle(NULL);
+ UnregisterClass(DUMMY_CLASS, module);
+}
+}
+
+#define GET_PROC(NAME, SUFFIX) f##NAME = \
+ (##NAME##Proc) wglGetProcAddress("wgl" #NAME #SUFFIX)
+
+SkWGLExtensions::SkWGLExtensions()
+ : fGetExtensionsString(NULL)
+ , fChoosePixelFormat(NULL)
+ , fGetPixelFormatAttribfv(NULL)
+ , fGetPixelFormatAttribiv(NULL)
+ , fCreateContextAttribs(NULL) {
+ HDC prevDC = wglGetCurrentDC();
+ HGLRC prevGLRC = wglGetCurrentContext();
+
+ PIXELFORMATDESCRIPTOR dummyPFD;
+
+ ZeroMemory(&dummyPFD, sizeof(dummyPFD));
+ dummyPFD.nSize = sizeof(dummyPFD);
+ dummyPFD.nVersion = 1;
+ dummyPFD.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
+ dummyPFD.iPixelType = PFD_TYPE_RGBA;
+ dummyPFD.cColorBits = 32;
+ dummyPFD.cDepthBits = 0;
+ dummyPFD.cStencilBits = 8;
+ dummyPFD.iLayerType = PFD_MAIN_PLANE;
+ HWND dummyWND = create_dummy_window();
+ if (dummyWND) {
+ HDC dummyDC = GetDC(dummyWND);
+ int dummyFormat = ChoosePixelFormat(dummyDC, &dummyPFD);
+ SetPixelFormat(dummyDC, dummyFormat, &dummyPFD);
+ HGLRC dummyGLRC = wglCreateContext(dummyDC);
+ SkASSERT(dummyGLRC);
+ wglMakeCurrent(dummyDC, dummyGLRC);
+
+ GET_PROC(GetExtensionsString, ARB);
+ GET_PROC(ChoosePixelFormat, ARB);
+ GET_PROC(GetPixelFormatAttribiv, ARB);
+ GET_PROC(GetPixelFormatAttribfv, ARB);
+ GET_PROC(CreateContextAttribs, ARB);
+
+ wglMakeCurrent(dummyDC, NULL);
+ wglDeleteContext(dummyGLRC);
+ destroy_dummy_window(dummyWND);
+ }
+
+ wglMakeCurrent(prevDC, prevGLRC);
+}
+
+HGLRC SkCreateWGLContext(HDC dc, int msaaSampleCount, bool preferCoreProfile) {
+ SkWGLExtensions extensions;
+ if (!extensions.hasExtension(dc, "WGL_ARB_pixel_format")) {
+ return NULL;
+ }
+
+ HDC prevDC = wglGetCurrentDC();
+ HGLRC prevGLRC = wglGetCurrentContext();
+ PIXELFORMATDESCRIPTOR pfd;
+
+ int format = 0;
+
+ static const int iAttrs[] = {
+ SK_WGL_DRAW_TO_WINDOW, TRUE,
+ SK_WGL_DOUBLE_BUFFER, TRUE,
+ SK_WGL_ACCELERATION, SK_WGL_FULL_ACCELERATION,
+ SK_WGL_SUPPORT_OPENGL, TRUE,
+ SK_WGL_COLOR_BITS, 24,
+ SK_WGL_ALPHA_BITS, 8,
+ SK_WGL_STENCIL_BITS, 8,
+ 0, 0
+ };
+
+ float fAttrs[] = {0, 0};
+
+ if (msaaSampleCount > 0 &&
+ extensions.hasExtension(dc, "WGL_ARB_multisample")) {
+ static const int kIAttrsCount = SK_ARRAY_COUNT(iAttrs);
+ int msaaIAttrs[kIAttrsCount + 6];
+ memcpy(msaaIAttrs, iAttrs, sizeof(int) * kIAttrsCount);
+ SkASSERT(0 == msaaIAttrs[kIAttrsCount - 2] &&
+ 0 == msaaIAttrs[kIAttrsCount - 1]);
+ msaaIAttrs[kIAttrsCount - 2] = SK_WGL_SAMPLE_BUFFERS;
+ msaaIAttrs[kIAttrsCount - 1] = TRUE;
+ msaaIAttrs[kIAttrsCount + 0] = SK_WGL_SAMPLES;
+ msaaIAttrs[kIAttrsCount + 1] = msaaSampleCount;
+ if (extensions.hasExtension(dc, "WGL_NV_multisample_coverage")) {
+ msaaIAttrs[kIAttrsCount + 2] = SK_WGL_COLOR_SAMPLES;
+ // We want the fewest number of color samples possible.
+ // Passing 0 gives only the formats where all samples are color
+ // samples.
+ msaaIAttrs[kIAttrsCount + 3] = 1;
+ msaaIAttrs[kIAttrsCount + 4] = 0;
+ msaaIAttrs[kIAttrsCount + 5] = 0;
+ } else {
+ msaaIAttrs[kIAttrsCount + 2] = 0;
+ msaaIAttrs[kIAttrsCount + 3] = 0;
+ }
+ unsigned int num;
+ int formats[64];
+ extensions.choosePixelFormat(dc, msaaIAttrs, fAttrs, 64, formats, &num);
+ num = SkTMin(num, 64U);
+ int formatToTry = extensions.selectFormat(formats,
+ num,
+ dc,
+ msaaSampleCount);
+ DescribePixelFormat(dc, formatToTry, sizeof(pfd), &pfd);
+ if (SetPixelFormat(dc, formatToTry, &pfd)) {
+ format = formatToTry;
+ }
+ }
+
+ if (0 == format) {
+ // Either MSAA wasn't requested or creation failed
+ unsigned int num;
+ extensions.choosePixelFormat(dc, iAttrs, fAttrs, 1, &format, &num);
+ DescribePixelFormat(dc, format, sizeof(pfd), &pfd);
+ SkDEBUGCODE(BOOL set =) SetPixelFormat(dc, format, &pfd);
+ SkASSERT(TRUE == set);
+ }
+
+ HGLRC glrc = NULL;
+ if (preferCoreProfile && extensions.hasExtension(dc, "WGL_ARB_create_context")) {
+ static const int kCoreGLVersions[] = {
+ 4, 3,
+ 4, 2,
+ 4, 1,
+ 4, 0,
+ 3, 3,
+ 3, 2,
+ };
+ int coreProfileAttribs[] = {
+ SK_WGL_CONTEXT_MAJOR_VERSION, -1,
+ SK_WGL_CONTEXT_MINOR_VERSION, -1,
+ SK_WGL_CONTEXT_PROFILE_MASK, SK_WGL_CONTEXT_CORE_PROFILE_BIT,
+ 0,
+ };
+ for (int v = 0; v < SK_ARRAY_COUNT(kCoreGLVersions) / 2; ++v) {
+ coreProfileAttribs[1] = kCoreGLVersions[2 * v];
+ coreProfileAttribs[3] = kCoreGLVersions[2 * v + 1];
+ glrc = extensions.createContextAttribs(dc, NULL, coreProfileAttribs);
+ if (NULL != glrc) {
+ break;
+ }
+ }
+ }
+
+ if (NULL == glrc) {
+ glrc = wglCreateContext(dc);
+ }
+ SkASSERT(glrc);
+
+ wglMakeCurrent(prevDC, prevGLRC);
+ return glrc;
+}
diff --git a/views/SkBGViewArtist.cpp b/views/SkBGViewArtist.cpp
new file mode 100644
index 00000000..c10fe3eb
--- /dev/null
+++ b/views/SkBGViewArtist.cpp
@@ -0,0 +1,30 @@
+
+/*
+ * 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 "SkBGViewArtist.h"
+#include "SkCanvas.h"
+#include "SkParsePaint.h"
+
+SkBGViewArtist::SkBGViewArtist(SkColor c)
+{
+ fPaint.setColor(c);
+}
+
+SkBGViewArtist::~SkBGViewArtist()
+{
+}
+
+void SkBGViewArtist::onDraw(SkView*, SkCanvas* canvas)
+{
+ // only works for views that are clipped their bounds.
+ canvas->drawPaint(fPaint);
+}
+
+void SkBGViewArtist::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ SkPaint_Inflate(&fPaint, dom, node);
+}
diff --git a/views/SkEvent.cpp b/views/SkEvent.cpp
new file mode 100644
index 00000000..52a0c4dd
--- /dev/null
+++ b/views/SkEvent.cpp
@@ -0,0 +1,508 @@
+
+/*
+ * 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 "SkEvent.h"
+
+void SkEvent::initialize(const char* type, size_t typeLen,
+ SkEventSinkID targetID) {
+ fType = NULL;
+ setType(type, typeLen);
+ f32 = 0;
+ fTargetID = targetID;
+ fTargetProc = NULL;
+#ifdef SK_DEBUG
+ fTime = 0;
+ fNextEvent = NULL;
+#endif
+}
+
+SkEvent::SkEvent()
+{
+ initialize("", 0, 0);
+}
+
+SkEvent::SkEvent(const SkEvent& src)
+{
+ *this = src;
+ if (((size_t) fType & 1) == 0)
+ setType(src.fType);
+}
+
+SkEvent::SkEvent(const SkString& type, SkEventSinkID targetID)
+{
+ initialize(type.c_str(), type.size(), targetID);
+}
+
+SkEvent::SkEvent(const char type[], SkEventSinkID targetID)
+{
+ SkASSERT(type);
+ initialize(type, strlen(type), targetID);
+}
+
+SkEvent::~SkEvent()
+{
+ if (((size_t) fType & 1) == 0)
+ sk_free((void*) fType);
+}
+
+static size_t makeCharArray(char* buffer, size_t compact)
+{
+ size_t bits = (size_t) compact >> 1;
+ memcpy(buffer, &bits, sizeof(compact));
+ buffer[sizeof(compact)] = 0;
+ return strlen(buffer);
+}
+
+void SkEvent::getType(SkString* str) const
+{
+ if (str)
+ {
+ if ((size_t) fType & 1) // not a pointer
+ {
+ char chars[sizeof(size_t) + 1];
+ size_t len = makeCharArray(chars, (size_t) fType);
+ str->set(chars, len);
+ }
+ else
+ str->set(fType);
+ }
+}
+
+bool SkEvent::isType(const SkString& str) const
+{
+ return this->isType(str.c_str(), str.size());
+}
+
+bool SkEvent::isType(const char type[], size_t typeLen) const
+{
+ if (typeLen == 0)
+ typeLen = strlen(type);
+ if ((size_t) fType & 1) { // not a pointer
+ char chars[sizeof(size_t) + 1];
+ size_t len = makeCharArray(chars, (size_t) fType);
+ return len == typeLen && strncmp(chars, type, typeLen) == 0;
+ }
+ return strncmp(fType, type, typeLen) == 0 && fType[typeLen] == 0;
+}
+
+void SkEvent::setType(const char type[], size_t typeLen)
+{
+ if (typeLen == 0)
+ typeLen = strlen(type);
+ if (typeLen <= sizeof(fType)) {
+ size_t slot = 0;
+ memcpy(&slot, type, typeLen);
+ if (slot << 1 >> 1 != slot)
+ goto useCharStar;
+ slot <<= 1;
+ slot |= 1;
+ fType = (char*) slot;
+ } else {
+useCharStar:
+ fType = (char*) sk_malloc_throw(typeLen + 1);
+ SkASSERT(((size_t) fType & 1) == 0);
+ memcpy(fType, type, typeLen);
+ fType[typeLen] = 0;
+ }
+}
+
+void SkEvent::setType(const SkString& type)
+{
+ setType(type.c_str());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#include "SkParse.h"
+
+void SkEvent::inflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ const char* name = dom.findAttr(node, "type");
+ if (name)
+ this->setType(name);
+
+ const char* value;
+ if ((value = dom.findAttr(node, "fast32")) != NULL)
+ {
+ int32_t n;
+ if (SkParse::FindS32(value, &n))
+ this->setFast32(n);
+ }
+
+ for (node = dom.getFirstChild(node); node; node = dom.getNextSibling(node))
+ {
+ if (strcmp(dom.getName(node), "data"))
+ {
+ SkDEBUGCODE(SkDebugf("SkEvent::inflate unrecognized subelement <%s>\n", dom.getName(node));)
+ continue;
+ }
+
+ name = dom.findAttr(node, "name");
+ if (name == NULL)
+ {
+ SkDEBUGCODE(SkDebugf("SkEvent::inflate missing required \"name\" attribute in <data> subelement\n");)
+ continue;
+ }
+
+ if ((value = dom.findAttr(node, "s32")) != NULL)
+ {
+ int32_t n;
+ if (SkParse::FindS32(value, &n))
+ this->setS32(name, n);
+ }
+ else if ((value = dom.findAttr(node, "scalar")) != NULL)
+ {
+ SkScalar x;
+ if (SkParse::FindScalar(value, &x))
+ this->setScalar(name, x);
+ }
+ else if ((value = dom.findAttr(node, "string")) != NULL)
+ this->setString(name, value);
+#ifdef SK_DEBUG
+ else
+ {
+ SkDebugf("SkEvent::inflate <data name=\"%s\"> subelement missing required type attribute [S32 | scalar | string]\n", name);
+ }
+#endif
+ }
+}
+
+#ifdef SK_DEBUG
+
+ #ifndef SkScalarToFloat
+ #define SkScalarToFloat(x) ((x) / 65536.f)
+ #endif
+
+ void SkEvent::dump(const char title[])
+ {
+ if (title)
+ SkDebugf("%s ", title);
+
+ SkString etype;
+ this->getType(&etype);
+ SkDebugf("event<%s> fast32=%d", etype.c_str(), this->getFast32());
+
+ const SkMetaData& md = this->getMetaData();
+ SkMetaData::Iter iter(md);
+ SkMetaData::Type mtype;
+ int count;
+ const char* name;
+
+ while ((name = iter.next(&mtype, &count)) != NULL)
+ {
+ SkASSERT(count > 0);
+
+ SkDebugf(" <%s>=", name);
+ switch (mtype) {
+ case SkMetaData::kS32_Type: // vector version???
+ {
+ int32_t value;
+ md.findS32(name, &value);
+ SkDebugf("%d ", value);
+ }
+ break;
+ case SkMetaData::kScalar_Type:
+ {
+ const SkScalar* values = md.findScalars(name, &count, NULL);
+ SkDebugf("%f", SkScalarToFloat(values[0]));
+ for (int i = 1; i < count; i++)
+ SkDebugf(", %f", SkScalarToFloat(values[i]));
+ SkDebugf(" ");
+ }
+ break;
+ case SkMetaData::kString_Type:
+ {
+ const char* value = md.findString(name);
+ SkASSERT(value);
+ SkDebugf("<%s> ", value);
+ }
+ break;
+ case SkMetaData::kPtr_Type: // vector version???
+ {
+ void* value;
+ md.findPtr(name, &value);
+ SkDebugf("%p ", value);
+ }
+ break;
+ case SkMetaData::kBool_Type: // vector version???
+ {
+ bool value;
+ md.findBool(name, &value);
+ SkDebugf("%s ", value ? "true" : "false");
+ }
+ break;
+ default:
+ SkDEBUGFAIL("unknown metadata type returned from iterator");
+ break;
+ }
+ }
+ SkDebugf("\n");
+ }
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+// #define SK_TRACE_EVENTSx
+#endif
+
+#ifdef SK_TRACE_EVENTS
+ static void event_log(const char s[])
+ {
+ SkDEBUGF(("%s\n", s));
+ }
+
+ #define EVENT_LOG(s) event_log(s)
+ #define EVENT_LOGN(s, n) do { SkString str(s); str.append(" "); str.appendS32(n); event_log(str.c_str()); } while (0)
+#else
+ #define EVENT_LOG(s)
+ #define EVENT_LOGN(s, n)
+#endif
+
+#include "SkThread.h"
+#include "SkTime.h"
+
+class SkEvent_Globals {
+public:
+ SkEvent_Globals() {
+ fEventQHead = NULL;
+ fEventQTail = NULL;
+ fDelayQHead = NULL;
+ SkDEBUGCODE(fEventCounter = 0;)
+ }
+
+ SkMutex fEventMutex;
+ SkEvent* fEventQHead, *fEventQTail;
+ SkEvent* fDelayQHead;
+ SkDEBUGCODE(int fEventCounter;)
+};
+
+static SkEvent_Globals& getGlobals() {
+ // leak this, so we don't incure any shutdown perf hit
+ static SkEvent_Globals* gGlobals = new SkEvent_Globals;
+ return *gGlobals;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::postDelay(SkMSec delay) {
+ if (!fTargetID && !fTargetProc) {
+ delete this;
+ return;
+ }
+
+ if (delay) {
+ this->postTime(SkTime::GetMSecs() + delay);
+ return;
+ }
+
+ SkEvent_Globals& globals = getGlobals();
+
+ globals.fEventMutex.acquire();
+ bool wasEmpty = SkEvent::Enqueue(this);
+ globals.fEventMutex.release();
+
+ // call outside of us holding the mutex
+ if (wasEmpty) {
+ SkEvent::SignalNonEmptyQueue();
+ }
+}
+
+void SkEvent::postTime(SkMSec time) {
+ if (!fTargetID && !fTargetProc) {
+ delete this;
+ return;
+ }
+
+ SkEvent_Globals& globals = getGlobals();
+
+ globals.fEventMutex.acquire();
+ SkMSec queueDelay = SkEvent::EnqueueTime(this, time);
+ globals.fEventMutex.release();
+
+ // call outside of us holding the mutex
+ if ((int32_t)queueDelay != ~0) {
+ SkEvent::SignalQueueTimer(queueDelay);
+ }
+}
+
+bool SkEvent::Enqueue(SkEvent* evt) {
+ SkEvent_Globals& globals = getGlobals();
+ // gEventMutex acquired by caller
+
+ SkASSERT(evt);
+
+ bool wasEmpty = globals.fEventQHead == NULL;
+
+ if (globals.fEventQTail)
+ globals.fEventQTail->fNextEvent = evt;
+ globals.fEventQTail = evt;
+ if (globals.fEventQHead == NULL)
+ globals.fEventQHead = evt;
+ evt->fNextEvent = NULL;
+
+ SkDEBUGCODE(++globals.fEventCounter);
+
+ return wasEmpty;
+}
+
+SkEvent* SkEvent::Dequeue() {
+ SkEvent_Globals& globals = getGlobals();
+ globals.fEventMutex.acquire();
+
+ SkEvent* evt = globals.fEventQHead;
+ if (evt) {
+ SkDEBUGCODE(--globals.fEventCounter);
+
+ globals.fEventQHead = evt->fNextEvent;
+ if (globals.fEventQHead == NULL) {
+ globals.fEventQTail = NULL;
+ }
+ }
+ globals.fEventMutex.release();
+
+ return evt;
+}
+
+bool SkEvent::QHasEvents() {
+ SkEvent_Globals& globals = getGlobals();
+
+ // this is not thread accurate, need a semaphore for that
+ return globals.fEventQHead != NULL;
+}
+
+#ifdef SK_TRACE_EVENTS
+ static int gDelayDepth;
+#endif
+
+SkMSec SkEvent::EnqueueTime(SkEvent* evt, SkMSec time) {
+ SkEvent_Globals& globals = getGlobals();
+ // gEventMutex acquired by caller
+
+ SkEvent* curr = globals.fDelayQHead;
+ SkEvent* prev = NULL;
+
+ while (curr) {
+ if (SkMSec_LT(time, curr->fTime)) {
+ break;
+ }
+ prev = curr;
+ curr = curr->fNextEvent;
+ }
+
+ evt->fTime = time;
+ evt->fNextEvent = curr;
+ if (prev == NULL) {
+ globals.fDelayQHead = evt;
+ } else {
+ prev->fNextEvent = evt;
+ }
+
+ SkMSec delay = globals.fDelayQHead->fTime - SkTime::GetMSecs();
+ if ((int32_t)delay <= 0) {
+ delay = 1;
+ }
+ return delay;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkEventSink.h"
+
+bool SkEvent::ProcessEvent() {
+ SkEvent* evt = SkEvent::Dequeue();
+ SkAutoTDelete<SkEvent> autoDelete(evt);
+ bool again = false;
+
+ EVENT_LOGN("ProcessEvent", (int32_t)evt);
+
+ if (evt) {
+ (void)SkEventSink::DoEvent(*evt);
+ again = SkEvent::QHasEvents();
+ }
+ return again;
+}
+
+void SkEvent::ServiceQueueTimer()
+{
+ SkEvent_Globals& globals = getGlobals();
+
+ globals.fEventMutex.acquire();
+
+ bool wasEmpty = false;
+ SkMSec now = SkTime::GetMSecs();
+ SkEvent* evt = globals.fDelayQHead;
+
+ while (evt)
+ {
+ if (SkMSec_LT(now, evt->fTime))
+ break;
+
+#ifdef SK_TRACE_EVENTS
+ --gDelayDepth;
+ SkDebugf("dequeue-delay %s (%d)", evt->getType(), gDelayDepth);
+ const char* idStr = evt->findString("id");
+ if (idStr)
+ SkDebugf(" (%s)", idStr);
+ SkDebugf("\n");
+#endif
+
+ SkEvent* next = evt->fNextEvent;
+ if (SkEvent::Enqueue(evt))
+ wasEmpty = true;
+ evt = next;
+ }
+ globals.fDelayQHead = evt;
+
+ SkMSec time = evt ? evt->fTime - now : 0;
+
+ globals.fEventMutex.release();
+
+ if (wasEmpty)
+ SkEvent::SignalNonEmptyQueue();
+
+ SkEvent::SignalQueueTimer(time);
+}
+
+int SkEvent::CountEventsOnQueue() {
+ SkEvent_Globals& globals = getGlobals();
+ globals.fEventMutex.acquire();
+
+ int count = 0;
+ const SkEvent* evt = globals.fEventQHead;
+ while (evt) {
+ count += 1;
+ evt = evt->fNextEvent;
+ }
+ globals.fEventMutex.release();
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::Init() {}
+
+void SkEvent::Term() {
+ SkEvent_Globals& globals = getGlobals();
+
+ SkEvent* evt = globals.fEventQHead;
+ while (evt) {
+ SkEvent* next = evt->fNextEvent;
+ delete evt;
+ evt = next;
+ }
+
+ evt = globals.fDelayQHead;
+ while (evt) {
+ SkEvent* next = evt->fNextEvent;
+ delete evt;
+ evt = next;
+ }
+}
diff --git a/views/SkEventSink.cpp b/views/SkEventSink.cpp
new file mode 100644
index 00000000..b6a3a6ee
--- /dev/null
+++ b/views/SkEventSink.cpp
@@ -0,0 +1,305 @@
+
+/*
+ * 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 "SkEventSink.h"
+#include "SkTagList.h"
+#include "SkThread.h"
+
+#include "SkThread.h"
+#include "SkTime.h"
+
+SK_DEFINE_INST_COUNT(SkEventSink)
+
+class SkEventSink_Globals {
+public:
+ SkEventSink_Globals() {
+ fNextSinkID = 0;
+ fSinkHead = NULL;
+ }
+
+ SkMutex fSinkMutex;
+ SkEventSinkID fNextSinkID;
+ SkEventSink* fSinkHead;
+};
+
+static SkEventSink_Globals& getGlobals() {
+ // leak this, so we don't incur any shutdown perf hit
+ static SkEventSink_Globals* gGlobals = new SkEventSink_Globals;
+ return *gGlobals;
+}
+
+SkEventSink::SkEventSink() : fTagHead(NULL) {
+ SkEventSink_Globals& globals = getGlobals();
+
+ globals.fSinkMutex.acquire();
+
+ fID = ++globals.fNextSinkID;
+ fNextSink = globals.fSinkHead;
+ globals.fSinkHead = this;
+
+ globals.fSinkMutex.release();
+}
+
+SkEventSink::~SkEventSink() {
+ SkEventSink_Globals& globals = getGlobals();
+
+ if (fTagHead)
+ SkTagList::DeleteAll(fTagHead);
+
+ globals.fSinkMutex.acquire();
+
+ SkEventSink* sink = globals.fSinkHead;
+ SkEventSink* prev = NULL;
+
+ for (;;) {
+ SkEventSink* next = sink->fNextSink;
+ if (sink == this) {
+ if (prev) {
+ prev->fNextSink = next;
+ } else {
+ globals.fSinkHead = next;
+ }
+ break;
+ }
+ prev = sink;
+ sink = next;
+ }
+ globals.fSinkMutex.release();
+}
+
+bool SkEventSink::doEvent(const SkEvent& evt) {
+ return this->onEvent(evt);
+}
+
+bool SkEventSink::doQuery(SkEvent* evt) {
+ SkASSERT(evt);
+ return this->onQuery(evt);
+}
+
+bool SkEventSink::onEvent(const SkEvent&) {
+ return false;
+}
+
+bool SkEventSink::onQuery(SkEvent*) {
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTagList* SkEventSink::findTagList(U8CPU tag) const {
+ return fTagHead ? SkTagList::Find(fTagHead, tag) : NULL;
+}
+
+void SkEventSink::addTagList(SkTagList* rec) {
+ SkASSERT(rec);
+ SkASSERT(fTagHead == NULL || SkTagList::Find(fTagHead, rec->fTag) == NULL);
+
+ rec->fNext = fTagHead;
+ fTagHead = rec;
+}
+
+void SkEventSink::removeTagList(U8CPU tag) {
+ if (fTagHead) {
+ SkTagList::DeleteTag(&fTagHead, tag);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct SkListenersTagList : SkTagList {
+ SkListenersTagList(U16CPU count) : SkTagList(kListeners_SkTagList)
+ {
+ fExtra16 = SkToU16(count);
+ fIDs = (SkEventSinkID*)sk_malloc_throw(count * sizeof(SkEventSinkID));
+ }
+ virtual ~SkListenersTagList()
+ {
+ sk_free(fIDs);
+ }
+
+ int countListners() const { return fExtra16; }
+
+ int find(SkEventSinkID id) const
+ {
+ const SkEventSinkID* idptr = fIDs;
+ for (int i = fExtra16 - 1; i >= 0; --i)
+ if (idptr[i] == id)
+ return i;
+ return -1;
+ }
+
+ SkEventSinkID* fIDs;
+};
+
+void SkEventSink::addListenerID(SkEventSinkID id)
+{
+ if (id == 0)
+ return;
+
+ SkListenersTagList* prev = (SkListenersTagList*)this->findTagList(kListeners_SkTagList);
+ int count = 0;
+
+ if (prev)
+ {
+ if (prev->find(id) >= 0)
+ return;
+ count = prev->countListners();
+ }
+
+ SkListenersTagList* next = SkNEW_ARGS(SkListenersTagList, (count + 1));
+
+ if (prev)
+ {
+ memcpy(next->fIDs, prev->fIDs, count * sizeof(SkEventSinkID));
+ this->removeTagList(kListeners_SkTagList);
+ }
+ next->fIDs[count] = id;
+ this->addTagList(next);
+}
+
+void SkEventSink::copyListeners(const SkEventSink& sink)
+{
+ SkListenersTagList* sinkList = (SkListenersTagList*)sink.findTagList(kListeners_SkTagList);
+ if (sinkList == NULL)
+ return;
+ SkASSERT(sinkList->countListners() > 0);
+ const SkEventSinkID* iter = sinkList->fIDs;
+ const SkEventSinkID* stop = iter + sinkList->countListners();
+ while (iter < stop)
+ addListenerID(*iter++);
+}
+
+void SkEventSink::removeListenerID(SkEventSinkID id)
+{
+ if (id == 0)
+ return;
+
+ SkListenersTagList* list = (SkListenersTagList*)this->findTagList(kListeners_SkTagList);
+
+ if (list == NULL)
+ return;
+
+ int index = list->find(id);
+ if (index >= 0)
+ {
+ int count = list->countListners();
+ SkASSERT(count > 0);
+ if (count == 1)
+ this->removeTagList(kListeners_SkTagList);
+ else
+ {
+ // overwrite without resize/reallocating our struct (for speed)
+ list->fIDs[index] = list->fIDs[count - 1];
+ list->fExtra16 = SkToU16(count - 1);
+ }
+ }
+}
+
+bool SkEventSink::hasListeners() const
+{
+ return this->findTagList(kListeners_SkTagList) != NULL;
+}
+
+void SkEventSink::postToListeners(const SkEvent& evt, SkMSec delay) {
+ SkListenersTagList* list = (SkListenersTagList*)this->findTagList(kListeners_SkTagList);
+ if (list) {
+ SkASSERT(list->countListners() > 0);
+ const SkEventSinkID* iter = list->fIDs;
+ const SkEventSinkID* stop = iter + list->countListners();
+ while (iter < stop) {
+ SkEvent* copy = SkNEW_ARGS(SkEvent, (evt));
+ copy->setTargetID(*iter++)->postDelay(delay);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkEventSink::EventResult SkEventSink::DoEvent(const SkEvent& evt) {
+ SkEvent::Proc proc = evt.getTargetProc();
+ if (proc) {
+ return proc(evt) ? kHandled_EventResult : kNotHandled_EventResult;
+ }
+
+ SkEventSink* sink = SkEventSink::FindSink(evt.getTargetID());
+ if (sink) {
+ return sink->doEvent(evt) ? kHandled_EventResult : kNotHandled_EventResult;
+ }
+
+ return kSinkNotFound_EventResult;
+}
+
+SkEventSink* SkEventSink::FindSink(SkEventSinkID sinkID)
+{
+ if (sinkID == 0)
+ return 0;
+
+ SkEventSink_Globals& globals = getGlobals();
+ SkAutoMutexAcquire ac(globals.fSinkMutex);
+ SkEventSink* sink = globals.fSinkHead;
+
+ while (sink)
+ {
+ if (sink->getSinkID() == sinkID)
+ return sink;
+ sink = sink->fNextSink;
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////
+
+#if 0 // experimental, not tested
+
+#include "SkThread.h"
+#include "SkTDict.h"
+
+#define kMinStringBufferSize 128
+SK_DECLARE_STATIC_MUTEX(gNamedSinkMutex);
+static SkTDict<SkEventSinkID> gNamedSinkIDs(kMinStringBufferSize);
+
+/** Register a name/id pair with the system. If the name already exists,
+ replace its ID with the new id. This pair will persist until UnregisterNamedSink()
+ is called.
+*/
+void SkEventSink::RegisterNamedSinkID(const char name[], SkEventSinkID id)
+{
+ if (id && name && *name)
+ {
+ SkAutoMutexAcquire ac(gNamedSinkMutex);
+ gNamedSinkIDs.set(name, id);
+ }
+}
+
+/** Return the id that matches the specified name (from a previous call to
+ RegisterNamedSinkID(). If no match is found, return 0
+*/
+SkEventSinkID SkEventSink::FindNamedSinkID(const char name[])
+{
+ SkEventSinkID id = 0;
+
+ if (name && *name)
+ {
+ SkAutoMutexAcquire ac(gNamedSinkMutex);
+ (void)gNamedSinkIDs.find(name, &id);
+ }
+ return id;
+}
+
+/** Remove all name/id pairs from the system. This is call internally
+ on shutdown, to ensure no memory leaks. It should not be called
+ before shutdown.
+*/
+void SkEventSink::RemoveAllNamedSinkIDs()
+{
+ SkAutoMutexAcquire ac(gNamedSinkMutex);
+ (void)gNamedSinkIDs.reset();
+}
+#endif
diff --git a/views/SkOSMenu.cpp b/views/SkOSMenu.cpp
new file mode 100644
index 00000000..3de0a9eb
--- /dev/null
+++ b/views/SkOSMenu.cpp
@@ -0,0 +1,263 @@
+/*
+ * 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 <stdarg.h>
+#include "SkOSMenu.h"
+#include "SkThread.h"
+
+static int gOSMenuCmd = 7000;
+
+SkOSMenu::SkOSMenu(const char title[]) {
+ fTitle.set(title);
+}
+
+SkOSMenu::~SkOSMenu() {
+ this->reset();
+}
+
+void SkOSMenu::reset() {
+ fItems.deleteAll();
+ fTitle.reset();
+}
+
+const SkOSMenu::Item* SkOSMenu::getItemByID(int itemID) const {
+ for (int i = 0; i < fItems.count(); ++i) {
+ if (itemID == fItems[i]->getID())
+ return fItems[i];
+ }
+ return NULL;
+}
+
+void SkOSMenu::getItems(const SkOSMenu::Item* items[]) const {
+ if (NULL != items) {
+ for (int i = 0; i < fItems.count(); ++i) {
+ items[i] = fItems[i];
+ }
+ }
+}
+
+void SkOSMenu::assignKeyEquivalentToItem(int itemID, SkUnichar key) {
+ for (int i = 0; i < fItems.count(); ++i) {
+ if (itemID == fItems[i]->getID())
+ fItems[i]->setKeyEquivalent(key);
+ }
+}
+
+bool SkOSMenu::handleKeyEquivalent(SkUnichar key) {
+ int value = 0, size = 0;
+ bool state;
+ SkOSMenu::TriState tristate;
+ for (int i = 0; i < fItems.count(); ++i) {
+ Item* item = fItems[i];
+ if (item->getKeyEquivalent()== key) {
+ SkString list;
+ switch (item->getType()) {
+ case kList_Type:
+ SkOSMenu::FindListItemCount(*item->getEvent(), &size);
+ SkOSMenu::FindListIndex(*item->getEvent(), item->getSlotName(), &value);
+ value = (value + 1) % size;
+ item->setInt(value);
+ break;
+ case kSwitch_Type:
+ SkOSMenu::FindSwitchState(*item->getEvent(), item->getSlotName(), &state);
+ item->setBool(!state);
+ break;
+ case kTriState_Type:
+ SkOSMenu::FindTriState(*item->getEvent(), item->getSlotName(), &tristate);
+ if (kOnState == tristate)
+ tristate = kMixedState;
+ else
+ tristate = (SkOSMenu::TriState)((int)tristate + 1);
+ item->setTriState(tristate);
+ break;
+ case kAction_Type:
+ case kCustom_Type:
+ case kSlider_Type:
+ case kTextField_Type:
+ default:
+ break;
+ }
+ item->postEvent();
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+SkOSMenu::Item::Item(const char label[], SkOSMenu::Type type,
+ const char slotName[], SkEvent* evt) {
+ fLabel.set(label);
+ fSlotName.set(slotName);
+ fType = type;
+ fEvent = evt;
+ fKey = 0;
+ fID = sk_atomic_inc(&gOSMenuCmd);
+}
+
+void SkOSMenu::Item::setBool(bool value) const {
+ SkASSERT(SkOSMenu::kSwitch_Type == fType);
+ fEvent->setBool(fSlotName.c_str(), value);
+}
+
+void SkOSMenu::Item::setScalar(SkScalar value) const {
+ SkASSERT(SkOSMenu::kSlider_Type == fType);
+ fEvent->setScalar(fSlotName.c_str(), value);
+}
+
+void SkOSMenu::Item::setInt(int value) const {
+ SkASSERT(SkOSMenu::kList_Type == fType);
+ fEvent->setS32(fSlotName.c_str(), value);
+}
+
+void SkOSMenu::Item::setTriState(TriState value) const {
+ SkASSERT(SkOSMenu::kTriState_Type == fType);
+ fEvent->setS32(fSlotName.c_str(), value);
+}
+
+void SkOSMenu::Item::setString(const char value[]) const {
+ SkASSERT(SkOSMenu::kTextField_Type == fType);
+ fEvent->setString(fSlotName.c_str(), value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const char* gMenuEventType = "SkOSMenuEventType";
+static const char* gSlider_Min_Scalar = "SkOSMenuSlider_Min";
+static const char* gSlider_Max_Scalar = "SkOSMenuSlider_Max";
+static const char* gDelimiter = "|";
+static const char* gList_Items_Str = "SkOSMenuList_Items";
+static const char* gList_ItemCount_S32 = "SkOSMenuList_ItemCount";
+
+int SkOSMenu::appendItem(const char label[], Type type, const char slotName[],
+ SkEvent* evt) {
+ SkOSMenu::Item* item = new Item(label, type, slotName, evt);
+ fItems.append(1, &item);
+ return item->getID();
+}
+
+int SkOSMenu::appendAction(const char label[], SkEventSinkID target) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ //Store label in event so it can be used to identify the action later
+ evt->setString(label, label);
+ return appendItem(label, SkOSMenu::kAction_Type, "", evt);
+}
+
+int SkOSMenu::appendList(const char label[], const char slotName[],
+ SkEventSinkID target, int index, const char option[], ...) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ va_list args;
+ if (option) {
+ SkString str(option);
+ va_start(args, option);
+ int count = 1;
+ for (const char* arg = va_arg(args, const char*); arg != NULL; arg = va_arg(args, const char*)) {
+ str += gDelimiter;
+ str += arg;
+ ++count;
+ }
+ va_end(args);
+ evt->setString(gList_Items_Str, str);
+ evt->setS32(gList_ItemCount_S32, count);
+ evt->setS32(slotName, index);
+ }
+ return appendItem(label, SkOSMenu::kList_Type, slotName, evt);
+}
+
+int SkOSMenu::appendSlider(const char label[], const char slotName[],
+ SkEventSinkID target, SkScalar min, SkScalar max,
+ SkScalar defaultValue) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ evt->setScalar(gSlider_Min_Scalar, min);
+ evt->setScalar(gSlider_Max_Scalar, max);
+ evt->setScalar(slotName, defaultValue);
+ return appendItem(label, SkOSMenu::kSlider_Type, slotName, evt);
+}
+
+int SkOSMenu::appendSwitch(const char label[], const char slotName[],
+ SkEventSinkID target, bool defaultState) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ evt->setBool(slotName, defaultState);
+ return appendItem(label, SkOSMenu::kSwitch_Type, slotName, evt);
+}
+
+int SkOSMenu::appendTriState(const char label[], const char slotName[],
+ SkEventSinkID target, SkOSMenu::TriState defaultState) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ evt->setS32(slotName, defaultState);
+ return appendItem(label, SkOSMenu::kTriState_Type, slotName, evt);
+}
+
+int SkOSMenu::appendTextField(const char label[], const char slotName[],
+ SkEventSinkID target, const char placeholder[]) {
+ SkEvent* evt = new SkEvent(gMenuEventType, target);
+ evt->setString(slotName, placeholder);
+ return appendItem(label, SkOSMenu::kTextField_Type, slotName, evt);
+}
+
+bool SkOSMenu::FindListItemCount(const SkEvent& evt, int* count) {
+ return evt.isType(gMenuEventType) && evt.findS32(gList_ItemCount_S32, count);
+}
+
+bool SkOSMenu::FindListItems(const SkEvent& evt, SkString items[]) {
+ if (evt.isType(gMenuEventType) && NULL != items) {
+ const char* text = evt.findString(gList_Items_Str);
+ if (text != NULL) {
+ SkString temp(text);
+ char* token = strtok((char*)temp.c_str(), gDelimiter);
+ int index = 0;
+ while (token != NULL) {
+ items[index].set(token, strlen(token));
+ token = strtok (NULL, gDelimiter);
+ ++index;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkOSMenu::FindSliderMin(const SkEvent& evt, SkScalar* min) {
+ return evt.isType(gMenuEventType) && evt.findScalar(gSlider_Min_Scalar, min);
+}
+
+bool SkOSMenu::FindSliderMax(const SkEvent& evt, SkScalar* max) {
+ return evt.isType(gMenuEventType) && evt.findScalar(gSlider_Max_Scalar, max);
+}
+
+bool SkOSMenu::FindAction(const SkEvent& evt, const char label[]) {
+ return evt.isType(gMenuEventType) && evt.findString(label);
+}
+
+bool SkOSMenu::FindListIndex(const SkEvent& evt, const char slotName[], int* value) {
+ return evt.isType(gMenuEventType) && evt.findS32(slotName, value);
+}
+
+bool SkOSMenu::FindSliderValue(const SkEvent& evt, const char slotName[], SkScalar* value) {
+ return evt.isType(gMenuEventType) && evt.findScalar(slotName, value);
+}
+
+bool SkOSMenu::FindSwitchState(const SkEvent& evt, const char slotName[], bool* value) {
+ return evt.isType(gMenuEventType) && evt.findBool(slotName, value);
+}
+
+bool SkOSMenu::FindTriState(const SkEvent& evt, const char slotName[], SkOSMenu::TriState* value) {
+ return evt.isType(gMenuEventType) && evt.findS32(slotName, (int*)value);
+}
+
+bool SkOSMenu::FindText(const SkEvent& evt, const char slotName[], SkString* value) {
+ if (evt.isType(gMenuEventType)) {
+ const char* text = evt.findString(slotName);
+ if (!text || !*text)
+ return false;
+ else {
+ value->set(text);
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/views/SkParsePaint.cpp b/views/SkParsePaint.cpp
new file mode 100644
index 00000000..344da0b5
--- /dev/null
+++ b/views/SkParsePaint.cpp
@@ -0,0 +1,109 @@
+
+/*
+ * 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 "SkParsePaint.h"
+#include "SkTSearch.h"
+#include "SkParse.h"
+#include "SkImageDecoder.h"
+#include "SkGradientShader.h"
+
+static SkShader* inflate_shader(const SkDOM& dom, const SkDOM::Node* node)
+{
+ if ((node = dom.getFirstChild(node, "shader")) == NULL)
+ return NULL;
+
+ const char* str;
+
+ if (dom.hasAttr(node, "type", "linear-gradient"))
+ {
+ SkColor colors[2];
+ SkPoint pts[2];
+
+ colors[0] = colors[1] = SK_ColorBLACK; // need to initialized the alpha to opaque, since FindColor doesn't set it
+ if ((str = dom.findAttr(node, "c0")) != NULL &&
+ SkParse::FindColor(str, &colors[0]) &&
+ (str = dom.findAttr(node, "c1")) != NULL &&
+ SkParse::FindColor(str, &colors[1]) &&
+ dom.findScalars(node, "p0", &pts[0].fX, 2) &&
+ dom.findScalars(node, "p1", &pts[1].fX, 2))
+ {
+ SkShader::TileMode mode = SkShader::kClamp_TileMode;
+ int index;
+
+ if ((index = dom.findList(node, "tile-mode", "clamp,repeat,mirror")) >= 0)
+ mode = (SkShader::TileMode)index;
+ return SkGradientShader::CreateLinear(pts, colors, NULL, 2, mode);
+ }
+ }
+ else if (dom.hasAttr(node, "type", "bitmap"))
+ {
+ if ((str = dom.findAttr(node, "src")) == NULL)
+ return NULL;
+
+ SkBitmap bm;
+
+ if (SkImageDecoder::DecodeFile(str, &bm))
+ {
+ SkShader::TileMode mode = SkShader::kRepeat_TileMode;
+ int index;
+
+ if ((index = dom.findList(node, "tile-mode", "clamp,repeat,mirror")) >= 0)
+ mode = (SkShader::TileMode)index;
+
+ return SkShader::CreateBitmapShader(bm, mode, mode);
+ }
+ }
+ return NULL;
+}
+
+void SkPaint_Inflate(SkPaint* paint, const SkDOM& dom, const SkDOM::Node* node)
+{
+ SkASSERT(paint);
+ SkASSERT(&dom);
+ SkASSERT(node);
+
+ SkScalar x;
+
+ if (dom.findScalar(node, "stroke-width", &x))
+ paint->setStrokeWidth(x);
+ if (dom.findScalar(node, "text-size", &x))
+ paint->setTextSize(x);
+
+ bool b;
+
+ SkASSERT("legacy: use is-stroke" && !dom.findBool(node, "is-frame", &b));
+
+ if (dom.findBool(node, "is-stroke", &b))
+ paint->setStyle(b ? SkPaint::kStroke_Style : SkPaint::kFill_Style);
+ if (dom.findBool(node, "is-antialias", &b))
+ paint->setAntiAlias(b);
+ if (dom.findBool(node, "is-lineartext", &b))
+ paint->setLinearText(b);
+
+ const char* str = dom.findAttr(node, "color");
+ if (str)
+ {
+ SkColor c = paint->getColor();
+ if (SkParse::FindColor(str, &c))
+ paint->setColor(c);
+ }
+
+ // do this AFTER parsing for the color
+ if (dom.findScalar(node, "opacity", &x))
+ {
+ x = SkMaxScalar(0, SkMinScalar(x, SK_Scalar1));
+ paint->setAlpha(SkScalarRound(x * 255));
+ }
+
+ int index = dom.findList(node, "text-anchor", "left,center,right");
+ if (index >= 0)
+ paint->setTextAlign((SkPaint::Align)index);
+
+ SkShader* shader = inflate_shader(dom, node);
+ if (shader)
+ paint->setShader(shader)->unref();
+}
diff --git a/views/SkProgressView.cpp b/views/SkProgressView.cpp
new file mode 100644
index 00000000..03e28eb2
--- /dev/null
+++ b/views/SkProgressView.cpp
@@ -0,0 +1,132 @@
+
+/*
+ * 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 "SkWidget.h"
+#include "SkCanvas.h"
+#include "SkMath.h"
+#include "SkShader.h"
+#include "SkInterpolator.h"
+#include "SkTime.h"
+
+SkProgressView::SkProgressView(uint32_t flags) : SkView(flags), fOnShader(NULL), fOffShader(NULL)
+{
+ fValue = 0;
+ fMax = 0;
+ fInterp = NULL;
+ fDoInterp = false;
+}
+
+SkProgressView::~SkProgressView()
+{
+ delete fInterp;
+ SkSafeUnref(fOnShader);
+ SkSafeUnref(fOffShader);
+}
+
+void SkProgressView::setMax(U16CPU max)
+{
+ if (fMax != max)
+ {
+ fMax = SkToU16(max);
+ if (fValue > 0)
+ this->inval(NULL);
+ }
+}
+
+void SkProgressView::setValue(U16CPU value)
+{
+ if (fValue != value)
+ {
+ if (fDoInterp)
+ {
+ if (fInterp)
+ delete fInterp;
+ fInterp = new SkInterpolator(1, 2);
+ SkScalar x = (SkScalar)(fValue << 8);
+ fInterp->setKeyFrame(0, SkTime::GetMSecs(), &x, 0);
+ x = (SkScalar)(value << 8);
+ fInterp->setKeyFrame(1, SkTime::GetMSecs() + 333, &x);
+ }
+ fValue = SkToU16(value);
+ this->inval(NULL);
+ }
+}
+
+void SkProgressView::onDraw(SkCanvas* canvas)
+{
+ if (fMax == 0)
+ return;
+
+ SkFixed percent;
+
+ if (fInterp)
+ {
+ SkScalar x;
+ if (fInterp->timeToValues(SkTime::GetMSecs(), &x) == SkInterpolator::kFreezeEnd_Result)
+ {
+ delete fInterp;
+ fInterp = NULL;
+ }
+ percent = (SkFixed)x; // now its 16.8
+ percent = SkMax32(0, SkMin32(percent, fMax << 8)); // now its pinned
+ percent = SkFixedDiv(percent, fMax << 8); // now its 0.16
+ this->inval(NULL);
+ }
+ else
+ {
+ U16CPU value = SkMax32(0, SkMin32(fValue, fMax));
+ percent = SkFixedDiv(value, fMax);
+ }
+
+
+ SkRect r;
+ SkPaint p;
+
+ r.set(0, 0, this->width(), this->height());
+ p.setAntiAlias(true);
+
+ r.fRight = r.fLeft + SkScalarMul(r.width(), SkFixedToScalar(percent));
+ p.setStyle(SkPaint::kFill_Style);
+
+ p.setColor(SK_ColorDKGRAY);
+ p.setShader(fOnShader);
+ canvas->drawRect(r, p);
+
+ p.setColor(SK_ColorWHITE);
+ p.setShader(fOffShader);
+ r.fLeft = r.fRight;
+ r.fRight = this->width() - SK_Scalar1;
+ if (r.width() > 0)
+ canvas->drawRect(r, p);
+}
+
+#include "SkImageDecoder.h"
+
+static SkShader* inflate_shader(const char file[])
+{
+ SkBitmap bm;
+
+ return SkImageDecoder::DecodeFile(file, &bm) ?
+ SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode) :
+ NULL;
+}
+
+void SkProgressView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ const char* s;
+
+ SkASSERT(fOnShader == NULL);
+ SkASSERT(fOffShader == NULL);
+
+ if ((s = dom.findAttr(node, "src-on")) != NULL)
+ fOnShader = inflate_shader(s);
+ if ((s = dom.findAttr(node, "src-off")) != NULL)
+ fOffShader = inflate_shader(s);
+ (void)dom.findBool(node, "do-interp", &fDoInterp);
+}
diff --git a/views/SkStackViewLayout.cpp b/views/SkStackViewLayout.cpp
new file mode 100644
index 00000000..9a3a3528
--- /dev/null
+++ b/views/SkStackViewLayout.cpp
@@ -0,0 +1,273 @@
+
+/*
+ * 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 "SkStackViewLayout.h"
+
+SkStackViewLayout::SkStackViewLayout()
+{
+ fMargin.set(0, 0, 0, 0);
+ fSpacer = 0;
+ fOrient = kHorizontal_Orient;
+ fPack = kStart_Pack;
+ fAlign = kStart_Align;
+ fRound = false;
+}
+
+void SkStackViewLayout::setOrient(Orient ori)
+{
+ SkASSERT((unsigned)ori < kOrientCount);
+ fOrient = SkToU8(ori);
+}
+
+void SkStackViewLayout::getMargin(SkRect* margin) const
+{
+ if (margin)
+ *margin = fMargin;
+}
+
+void SkStackViewLayout::setMargin(const SkRect& margin)
+{
+ fMargin = margin;
+}
+
+void SkStackViewLayout::setSpacer(SkScalar spacer)
+{
+ fSpacer = spacer;
+}
+
+void SkStackViewLayout::setPack(Pack pack)
+{
+ SkASSERT((unsigned)pack < kPackCount);
+ fPack = SkToU8(pack);
+}
+
+void SkStackViewLayout::setAlign(Align align)
+{
+ SkASSERT((unsigned)align < kAlignCount);
+ fAlign = SkToU8(align);
+}
+
+void SkStackViewLayout::setRound(bool r)
+{
+ fRound = SkToU8(r);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+typedef SkScalar (*AlignProc)(SkScalar childLimit, SkScalar parentLimit);
+typedef SkScalar (SkView::*GetSizeProc)() const;
+typedef void (SkView::*SetLocProc)(SkScalar coord);
+typedef void (SkView::*SetSizeProc)(SkScalar coord);
+
+static SkScalar left_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }
+static SkScalar center_align_proc(SkScalar childLimit, SkScalar parentLimit) { return SkScalarHalf(parentLimit - childLimit); }
+static SkScalar right_align_proc(SkScalar childLimit, SkScalar parentLimit) { return parentLimit - childLimit; }
+static SkScalar fill_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }
+
+/* Measure the main-dimension for all the children. If a child is marked flex in that direction
+ ignore its current value but increment the counter for flexChildren
+*/
+static SkScalar compute_children_limit(SkView* parent, GetSizeProc sizeProc, int* count,
+ uint32_t flexMask, int* flexCount)
+{
+ SkView::B2FIter iter(parent);
+ SkView* child;
+ SkScalar limit = 0;
+ int n = 0, flex = 0;
+
+ while ((child = iter.next()) != NULL)
+ {
+ n += 1;
+ if (child->getFlags() & flexMask)
+ flex += 1;
+ else
+ limit += (child->*sizeProc)();
+ }
+ if (count)
+ *count = n;
+ if (flexCount)
+ *flexCount = flex;
+ return limit;
+}
+
+void SkStackViewLayout::onLayoutChildren(SkView* parent)
+{
+ static AlignProc gAlignProcs[] = {
+ left_align_proc,
+ center_align_proc,
+ right_align_proc,
+ fill_align_proc
+ };
+
+ SkScalar startM, endM, crossStartM, crossLimit;
+ GetSizeProc mainGetSizeP, crossGetSizeP;
+ SetLocProc mainLocP, crossLocP;
+ SetSizeProc mainSetSizeP, crossSetSizeP;
+ SkView::Flag_Mask flexMask;
+
+ if (fOrient == kHorizontal_Orient)
+ {
+ startM = fMargin.fLeft;
+ endM = fMargin.fRight;
+ crossStartM = fMargin.fTop;
+ crossLimit = -fMargin.fTop - fMargin.fBottom;
+
+ mainGetSizeP = &SkView::width;
+ crossGetSizeP = &SkView::height;
+ mainLocP = &SkView::setLocX;
+ crossLocP = &SkView::setLocY;
+
+ mainSetSizeP = &SkView::setWidth;
+ crossSetSizeP = &SkView::setHeight;
+
+ flexMask = SkView::kFlexH_Mask;
+ }
+ else
+ {
+ startM = fMargin.fTop;
+ endM = fMargin.fBottom;
+ crossStartM = fMargin.fLeft;
+ crossLimit = -fMargin.fLeft - fMargin.fRight;
+
+ mainGetSizeP = &SkView::height;
+ crossGetSizeP = &SkView::width;
+ mainLocP = &SkView::setLocY;
+ crossLocP = &SkView::setLocX;
+
+ mainSetSizeP = &SkView::setHeight;
+ crossSetSizeP = &SkView::setWidth;
+
+ flexMask = SkView::kFlexV_Mask;
+ }
+ crossLimit += (parent->*crossGetSizeP)();
+ if (fAlign != kStretch_Align)
+ crossSetSizeP = NULL;
+
+ int childCount, flexCount;
+ SkScalar childLimit = compute_children_limit(parent, mainGetSizeP, &childCount, flexMask, &flexCount);
+
+ if (childCount == 0)
+ return;
+
+ childLimit += (childCount - 1) * fSpacer;
+
+ SkScalar parentLimit = (parent->*mainGetSizeP)() - startM - endM;
+ SkScalar pos = startM + gAlignProcs[fPack](childLimit, parentLimit);
+ SkScalar flexAmount = 0;
+ SkView::B2FIter iter(parent);
+ SkView* child;
+
+ if (flexCount > 0 && parentLimit > childLimit)
+ flexAmount = (parentLimit - childLimit) / flexCount;
+
+ while ((child = iter.next()) != NULL)
+ {
+ if (fRound)
+ pos = SkIntToScalar(SkScalarRound(pos));
+ (child->*mainLocP)(pos);
+ SkScalar crossLoc = crossStartM + gAlignProcs[fAlign]((child->*crossGetSizeP)(), crossLimit);
+ if (fRound)
+ crossLoc = SkIntToScalar(SkScalarRound(crossLoc));
+ (child->*crossLocP)(crossLoc);
+
+ if (crossSetSizeP)
+ (child->*crossSetSizeP)(crossLimit);
+ if (child->getFlags() & flexMask)
+ (child->*mainSetSizeP)(flexAmount);
+ pos += (child->*mainGetSizeP)() + fSpacer;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+ static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
+ {
+ const char* value = dom.findAttr(node, attr);
+ if (value)
+ SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
+ }
+#else
+ #define assert_no_attr(dom, node, attr)
+#endif
+
+void SkStackViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ int index;
+ SkScalar value[4];
+
+ if ((index = dom.findList(node, "orient", "horizontal,vertical")) >= 0)
+ this->setOrient((Orient)index);
+ else {
+ assert_no_attr(dom, node, "orient");
+ }
+
+ if (dom.findScalars(node, "margin", value, 4))
+ {
+ SkRect margin;
+ margin.set(value[0], value[1], value[2], value[3]);
+ this->setMargin(margin);
+ }
+ else {
+ assert_no_attr(dom, node, "margin");
+ }
+
+ if (dom.findScalar(node, "spacer", value))
+ this->setSpacer(value[0]);
+ else {
+ assert_no_attr(dom, node, "spacer");
+ }
+
+ if ((index = dom.findList(node, "pack", "start,center,end")) >= 0)
+ this->setPack((Pack)index);
+ else {
+ assert_no_attr(dom, node, "pack");
+ }
+
+ if ((index = dom.findList(node, "align", "start,center,end,stretch")) >= 0)
+ this->setAlign((Align)index);
+ else {
+ assert_no_attr(dom, node, "align");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+
+SkFillViewLayout::SkFillViewLayout()
+{
+ fMargin.setEmpty();
+}
+
+void SkFillViewLayout::getMargin(SkRect* r) const
+{
+ if (r)
+ *r = fMargin;
+}
+
+void SkFillViewLayout::setMargin(const SkRect& margin)
+{
+ fMargin = margin;
+}
+
+void SkFillViewLayout::onLayoutChildren(SkView* parent)
+{
+ SkView::B2FIter iter(parent);
+ SkView* child;
+
+ while ((child = iter.next()) != NULL)
+ {
+ child->setLoc(fMargin.fLeft, fMargin.fTop);
+ child->setSize( parent->width() - fMargin.fRight - fMargin.fLeft,
+ parent->height() - fMargin.fBottom - fMargin.fTop);
+ }
+}
+
+void SkFillViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+ (void)dom.findScalars(node, "margin", (SkScalar*)&fMargin, 4);
+}
diff --git a/views/SkTagList.cpp b/views/SkTagList.cpp
new file mode 100644
index 00000000..8ede870f
--- /dev/null
+++ b/views/SkTagList.cpp
@@ -0,0 +1,62 @@
+
+/*
+ * 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 "SkTagList.h"
+
+SkTagList::~SkTagList()
+{
+}
+
+SkTagList* SkTagList::Find(SkTagList* rec, U8CPU tag)
+{
+ SkASSERT(tag < kSkTagListCount);
+
+ while (rec != NULL)
+ {
+ if (rec->fTag == tag)
+ break;
+ rec = rec->fNext;
+ }
+ return rec;
+}
+
+void SkTagList::DeleteTag(SkTagList** head, U8CPU tag)
+{
+ SkASSERT(tag < kSkTagListCount);
+
+ SkTagList* rec = *head;
+ SkTagList* prev = NULL;
+
+ while (rec != NULL)
+ {
+ SkTagList* next = rec->fNext;
+
+ if (rec->fTag == tag)
+ {
+ if (prev)
+ prev->fNext = next;
+ else
+ *head = next;
+ delete rec;
+ break;
+ }
+ prev = rec;
+ rec = next;
+ }
+}
+
+void SkTagList::DeleteAll(SkTagList* rec)
+{
+ while (rec)
+ {
+ SkTagList* next = rec->fNext;
+ delete rec;
+ rec = next;
+ }
+}
diff --git a/views/SkTagList.h b/views/SkTagList.h
new file mode 100644
index 00000000..47294e32
--- /dev/null
+++ b/views/SkTagList.h
@@ -0,0 +1,43 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkTagList_DEFINED
+#define SkTagList_DEFINED
+
+#include "SkTypes.h"
+
+enum SkTagListEnum {
+ kListeners_SkTagList,
+ kViewLayout_SkTagList,
+ kViewArtist_SkTagList,
+
+ kSkTagListCount
+};
+
+struct SkTagList {
+ SkTagList* fNext;
+ uint16_t fExtra16;
+ uint8_t fExtra8;
+ uint8_t fTag;
+
+ SkTagList(U8CPU tag) : fTag(SkToU8(tag))
+ {
+ SkASSERT(tag < kSkTagListCount);
+ fNext = NULL;
+ fExtra16 = 0;
+ fExtra8 = 0;
+ }
+ virtual ~SkTagList();
+
+ static SkTagList* Find(SkTagList* head, U8CPU tag);
+ static void DeleteTag(SkTagList** headptr, U8CPU tag);
+ static void DeleteAll(SkTagList* head);
+};
+
+#endif
diff --git a/views/SkTextBox.cpp b/views/SkTextBox.cpp
new file mode 100644
index 00000000..b0a42cd7
--- /dev/null
+++ b/views/SkTextBox.cpp
@@ -0,0 +1,259 @@
+/*
+ * 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 "SkTextBox.h"
+#include "SkUtils.h"
+
+static inline int is_ws(int c)
+{
+ return !((c - 1) >> 5);
+}
+
+static size_t linebreak(const char text[], const char stop[],
+ const SkPaint& paint, SkScalar margin,
+ size_t* trailing = NULL)
+{
+ size_t lengthBreak = paint.breakText(text, stop - text, margin);
+
+ //Check for white space or line breakers before the lengthBreak
+ const char* start = text;
+ const char* word_start = text;
+ int prevWS = true;
+ if (trailing) {
+ *trailing = 0;
+ }
+
+ while (text < stop) {
+ const char* prevText = text;
+ SkUnichar uni = SkUTF8_NextUnichar(&text);
+ int currWS = is_ws(uni);
+
+ if (!currWS && prevWS) {
+ word_start = prevText;
+ }
+ prevWS = currWS;
+
+ if (text > start + lengthBreak) {
+ if (currWS) {
+ // eat the rest of the whitespace
+ while (text < stop && is_ws(SkUTF8_ToUnichar(text))) {
+ text += SkUTF8_CountUTF8Bytes(text);
+ }
+ if (trailing) {
+ *trailing = text - prevText;
+ }
+ } else {
+ // backup until a whitespace (or 1 char)
+ if (word_start == start) {
+ if (prevText > start) {
+ text = prevText;
+ }
+ } else {
+ text = word_start;
+ }
+ }
+ break;
+ }
+
+ if ('\n' == uni) {
+ size_t ret = text - start;
+ size_t lineBreakSize = 1;
+ if (text < stop) {
+ uni = SkUTF8_NextUnichar(&text);
+ if ('\r' == uni) {
+ ret = text - start;
+ ++lineBreakSize;
+ }
+ }
+ if (trailing) {
+ *trailing = lineBreakSize;
+ }
+ return ret;
+ }
+
+ if ('\r' == uni) {
+ size_t ret = text - start;
+ size_t lineBreakSize = 1;
+ if (text < stop) {
+ uni = SkUTF8_NextUnichar(&text);
+ if ('\n' == uni) {
+ ret = text - start;
+ ++lineBreakSize;
+ }
+ }
+ if (trailing) {
+ *trailing = lineBreakSize;
+ }
+ return ret;
+ }
+ }
+
+ return text - start;
+}
+
+int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
+{
+ const char* stop = text + len;
+ int count = 0;
+
+ if (width > 0)
+ {
+ do {
+ count += 1;
+ text += linebreak(text, stop, paint, width);
+ } while (text < stop);
+ }
+ return count;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+SkTextBox::SkTextBox()
+{
+ fBox.setEmpty();
+ fSpacingMul = SK_Scalar1;
+ fSpacingAdd = 0;
+ fMode = kLineBreak_Mode;
+ fSpacingAlign = kStart_SpacingAlign;
+}
+
+void SkTextBox::setMode(Mode mode)
+{
+ SkASSERT((unsigned)mode < kModeCount);
+ fMode = SkToU8(mode);
+}
+
+void SkTextBox::setSpacingAlign(SpacingAlign align)
+{
+ SkASSERT((unsigned)align < kSpacingAlignCount);
+ fSpacingAlign = SkToU8(align);
+}
+
+void SkTextBox::getBox(SkRect* box) const
+{
+ if (box)
+ *box = fBox;
+}
+
+void SkTextBox::setBox(const SkRect& box)
+{
+ fBox = box;
+}
+
+void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
+{
+ fBox.set(left, top, right, bottom);
+}
+
+void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
+{
+ if (mul)
+ *mul = fSpacingMul;
+ if (add)
+ *add = fSpacingAdd;
+}
+
+void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
+{
+ fSpacingMul = mul;
+ fSpacingAdd = add;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint)
+{
+ SkASSERT(canvas && &paint && (text || len == 0));
+
+ SkScalar marginWidth = fBox.width();
+
+ if (marginWidth <= 0 || len == 0)
+ return;
+
+ const char* textStop = text + len;
+
+ SkScalar x, y, scaledSpacing, height, fontHeight;
+ SkPaint::FontMetrics metrics;
+
+ switch (paint.getTextAlign()) {
+ case SkPaint::kLeft_Align:
+ x = 0;
+ break;
+ case SkPaint::kCenter_Align:
+ x = SkScalarHalf(marginWidth);
+ break;
+ default:
+ x = marginWidth;
+ break;
+ }
+ x += fBox.fLeft;
+
+ fontHeight = paint.getFontMetrics(&metrics);
+ scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd;
+ height = fBox.height();
+
+ // compute Y position for first line
+ {
+ SkScalar textHeight = fontHeight;
+
+ if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign)
+ {
+ int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
+ SkASSERT(count > 0);
+ textHeight += scaledSpacing * (count - 1);
+ }
+
+ switch (fSpacingAlign) {
+ case kStart_SpacingAlign:
+ y = 0;
+ break;
+ case kCenter_SpacingAlign:
+ y = SkScalarHalf(height - textHeight);
+ break;
+ default:
+ SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
+ y = height - textHeight;
+ break;
+ }
+ y += fBox.fTop - metrics.fAscent;
+ }
+
+ for (;;)
+ {
+ size_t trailing;
+ len = linebreak(text, textStop, paint, marginWidth, &trailing);
+ if (y + metrics.fDescent + metrics.fLeading > 0)
+ canvas->drawText(text, len - trailing, x, y, paint);
+ text += len;
+ if (text >= textStop)
+ break;
+ y += scaledSpacing;
+ if (y + metrics.fAscent >= fBox.fBottom)
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
+ fText = text;
+ fLen = len;
+ fPaint = &paint;
+}
+
+void SkTextBox::draw(SkCanvas* canvas) {
+ this->draw(canvas, fText, fLen, *fPaint);
+}
+
+int SkTextBox::countLines() const {
+ return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
+}
+
+SkScalar SkTextBox::getTextHeight() const {
+ SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd;
+ return this->countLines() * spacing;
+}
diff --git a/views/SkTouchGesture.cpp b/views/SkTouchGesture.cpp
new file mode 100644
index 00000000..3becccd8
--- /dev/null
+++ b/views/SkTouchGesture.cpp
@@ -0,0 +1,327 @@
+
+/*
+ * 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 "SkTouchGesture.h"
+#include "SkMatrix.h"
+#include "SkTime.h"
+
+#define DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER true
+
+static const SkScalar MAX_FLING_SPEED = SkIntToScalar(1500);
+
+static SkScalar pin_max_fling(SkScalar speed) {
+ if (speed > MAX_FLING_SPEED) {
+ speed = MAX_FLING_SPEED;
+ }
+ return speed;
+}
+
+static double getseconds() {
+ return SkTime::GetMSecs() * 0.001;
+}
+
+// returns +1 or -1, depending on the sign of x
+// returns +1 if z is zero
+static SkScalar SkScalarSignNonZero(SkScalar x) {
+ SkScalar sign = SK_Scalar1;
+ if (x < 0) {
+ sign = -sign;
+ }
+ return sign;
+}
+
+static void unit_axis_align(SkVector* unit) {
+ const SkScalar TOLERANCE = SkDoubleToScalar(0.15);
+ if (SkScalarAbs(unit->fX) < TOLERANCE) {
+ unit->fX = 0;
+ unit->fY = SkScalarSignNonZero(unit->fY);
+ } else if (SkScalarAbs(unit->fY) < TOLERANCE) {
+ unit->fX = SkScalarSignNonZero(unit->fX);
+ unit->fY = 0;
+ }
+}
+
+void SkFlingState::reset(float sx, float sy) {
+ fActive = true;
+ fDirection.set(SkFloatToScalar(sx), SkFloatToScalar(sy));
+ fSpeed0 = SkPoint::Normalize(&fDirection);
+ fSpeed0 = pin_max_fling(fSpeed0);
+ fTime0 = getseconds();
+
+ unit_axis_align(&fDirection);
+// printf("---- speed %g dir %g %g\n", fSpeed0, fDirection.fX, fDirection.fY);
+}
+
+bool SkFlingState::evaluateMatrix(SkMatrix* matrix) {
+ if (!fActive) {
+ return false;
+ }
+
+ const float t = (float)(getseconds() - fTime0);
+ const float MIN_SPEED = 2;
+ const float K0 = 5;
+ const float K1 = 0.02f;
+ const float speed = fSpeed0 * (sk_float_exp(- K0 * t) - K1);
+ if (speed <= MIN_SPEED) {
+ fActive = false;
+ return false;
+ }
+ float dist = (fSpeed0 - speed) / K0;
+
+// printf("---- time %g speed %g dist %g\n", t, speed, dist);
+ float tx = fDirection.fX * dist;
+ float ty = fDirection.fY * dist;
+ if (DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER) {
+ tx = (float)sk_float_round2int(tx);
+ ty = (float)sk_float_round2int(ty);
+ }
+ matrix->setTranslate(SkFloatToScalar(tx), SkFloatToScalar(ty));
+// printf("---- evaluate (%g %g)\n", tx, ty);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const SkMSec MAX_DBL_TAP_INTERVAL = 300;
+static const float MAX_DBL_TAP_DISTANCE = 100;
+static const float MAX_JITTER_RADIUS = 2;
+
+// if true, then ignore the touch-move, 'cause its probably just jitter
+static bool close_enough_for_jitter(float x0, float y0, float x1, float y1) {
+ return sk_float_abs(x0 - x1) <= MAX_JITTER_RADIUS &&
+ sk_float_abs(y0 - y1) <= MAX_JITTER_RADIUS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkTouchGesture::SkTouchGesture() {
+ this->reset();
+}
+
+SkTouchGesture::~SkTouchGesture() {
+}
+
+void SkTouchGesture::reset() {
+ fTouches.reset();
+ fState = kEmpty_State;
+ fLocalM.reset();
+ fGlobalM.reset();
+
+ fLastUpT = SkTime::GetMSecs() - 2*MAX_DBL_TAP_INTERVAL;
+ fLastUpP.set(0, 0);
+}
+
+void SkTouchGesture::flushLocalM() {
+ fGlobalM.postConcat(fLocalM);
+ fLocalM.reset();
+}
+
+const SkMatrix& SkTouchGesture::localM() {
+ if (fFlinger.isActive()) {
+ if (!fFlinger.evaluateMatrix(&fLocalM)) {
+ this->flushLocalM();
+ }
+ }
+ return fLocalM;
+}
+
+void SkTouchGesture::appendNewRec(void* owner, float x, float y) {
+ Rec* rec = fTouches.append();
+ rec->fOwner = owner;
+ rec->fStartX = rec->fPrevX = rec->fLastX = x;
+ rec->fStartY = rec->fPrevY = rec->fLastY = y;
+ rec->fLastT = rec->fPrevT = SkTime::GetMSecs();
+}
+
+void SkTouchGesture::touchBegin(void* owner, float x, float y) {
+// GrPrintf("--- %d touchBegin %p %g %g\n", fTouches.count(), owner, x, y);
+
+ int index = this->findRec(owner);
+ if (index >= 0) {
+ this->flushLocalM();
+ fTouches.removeShuffle(index);
+ SkDebugf("---- already exists, removing\n");
+ }
+
+ if (fTouches.count() == 2) {
+ return;
+ }
+
+ this->flushLocalM();
+ fFlinger.stop();
+
+ this->appendNewRec(owner, x, y);
+
+ switch (fTouches.count()) {
+ case 1:
+ fState = kTranslate_State;
+ break;
+ case 2:
+ fState = kZoom_State;
+ break;
+ default:
+ break;
+ }
+}
+
+int SkTouchGesture::findRec(void* owner) const {
+ for (int i = 0; i < fTouches.count(); i++) {
+ if (owner == fTouches[i].fOwner) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static SkScalar center(float pos0, float pos1) {
+ return SkFloatToScalar((pos0 + pos1) * 0.5f);
+}
+
+static const float MAX_ZOOM_SCALE = 4;
+static const float MIN_ZOOM_SCALE = 0.25f;
+
+float SkTouchGesture::limitTotalZoom(float scale) const {
+ // this query works 'cause we know that we're square-scale w/ no skew/rotation
+ const float curr = SkScalarToFloat(fGlobalM[0]);
+
+ if (scale > 1 && curr * scale > MAX_ZOOM_SCALE) {
+ scale = MAX_ZOOM_SCALE / curr;
+ } else if (scale < 1 && curr * scale < MIN_ZOOM_SCALE) {
+ scale = MIN_ZOOM_SCALE / curr;
+ }
+ return scale;
+}
+
+void SkTouchGesture::touchMoved(void* owner, float x, float y) {
+// GrPrintf("--- %d touchMoved %p %g %g\n", fTouches.count(), owner, x, y);
+
+ SkASSERT(kEmpty_State != fState);
+
+ int index = this->findRec(owner);
+ if (index < 0) {
+ // not found, so I guess we should add it...
+ SkDebugf("---- add missing begin\n");
+ this->appendNewRec(owner, x, y);
+ index = fTouches.count() - 1;
+ }
+
+ Rec& rec = fTouches[index];
+
+ // not sure how valuable this is
+ if (fTouches.count() == 2) {
+ if (close_enough_for_jitter(rec.fLastX, rec.fLastY, x, y)) {
+// GrPrintf("--- drop touchMove, withing jitter tolerance %g %g\n", rec.fLastX - x, rec.fLastY - y);
+ return;
+ }
+ }
+
+ rec.fPrevX = rec.fLastX; rec.fLastX = x;
+ rec.fPrevY = rec.fLastY; rec.fLastY = y;
+ rec.fPrevT = rec.fLastT; rec.fLastT = SkTime::GetMSecs();
+
+ switch (fTouches.count()) {
+ case 1: {
+ float dx = rec.fLastX - rec.fStartX;
+ float dy = rec.fLastY - rec.fStartY;
+ dx = (float)sk_float_round2int(dx);
+ dy = (float)sk_float_round2int(dy);
+ fLocalM.setTranslate(dx, dy);
+ } break;
+ case 2: {
+ SkASSERT(kZoom_State == fState);
+ const Rec& rec0 = fTouches[0];
+ const Rec& rec1 = fTouches[1];
+
+ float scale = this->computePinch(rec0, rec1);
+ scale = this->limitTotalZoom(scale);
+
+ fLocalM.setTranslate(-center(rec0.fStartX, rec1.fStartX),
+ -center(rec0.fStartY, rec1.fStartY));
+ fLocalM.postScale(scale, scale);
+ fLocalM.postTranslate(center(rec0.fLastX, rec1.fLastX),
+ center(rec0.fLastY, rec1.fLastY));
+ } break;
+ default:
+ break;
+ }
+}
+
+void SkTouchGesture::touchEnd(void* owner) {
+// GrPrintf("--- %d touchEnd %p\n", fTouches.count(), owner);
+
+ int index = this->findRec(owner);
+ if (index < 0) {
+ SkDebugf("--- not found\n");
+ return;
+ }
+
+ const Rec& rec = fTouches[index];
+ if (this->handleDblTap(rec.fLastX, rec.fLastY)) {
+ return;
+ }
+
+ // count() reflects the number before we removed the owner
+ switch (fTouches.count()) {
+ case 1: {
+ this->flushLocalM();
+ float dx = rec.fLastX - rec.fPrevX;
+ float dy = rec.fLastY - rec.fPrevY;
+ float dur = (rec.fLastT - rec.fPrevT) * 0.001f;
+ if (dur > 0) {
+ fFlinger.reset(dx / dur, dy / dur);
+ }
+ fState = kEmpty_State;
+ } break;
+ case 2:
+ this->flushLocalM();
+ SkASSERT(kZoom_State == fState);
+ fState = kEmpty_State;
+ break;
+ default:
+ SkASSERT(kZoom_State == fState);
+ break;
+ }
+
+ fTouches.removeShuffle(index);
+}
+
+float SkTouchGesture::computePinch(const Rec& rec0, const Rec& rec1) {
+ double dx = rec0.fStartX - rec1.fStartX;
+ double dy = rec0.fStartY - rec1.fStartY;
+ double dist0 = sqrt(dx*dx + dy*dy);
+
+ dx = rec0.fLastX - rec1.fLastX;
+ dy = rec0.fLastY - rec1.fLastY;
+ double dist1 = sqrt(dx*dx + dy*dy);
+
+ double scale = dist1 / dist0;
+ return (float)scale;
+}
+
+bool SkTouchGesture::handleDblTap(float x, float y) {
+ bool found = false;
+ SkMSec now = SkTime::GetMSecs();
+ if (now - fLastUpT <= MAX_DBL_TAP_INTERVAL) {
+ if (SkPoint::Length(fLastUpP.fX - x,
+ fLastUpP.fY - y) <= MAX_DBL_TAP_DISTANCE) {
+ fFlinger.stop();
+ fLocalM.reset();
+ fGlobalM.reset();
+ fTouches.reset();
+ fState = kEmpty_State;
+ found = true;
+ }
+ }
+
+ fLastUpT = now;
+ fLastUpP.set(x, y);
+ return found;
+}
diff --git a/views/SkView.cpp b/views/SkView.cpp
new file mode 100644
index 00000000..e3e05f91
--- /dev/null
+++ b/views/SkView.cpp
@@ -0,0 +1,846 @@
+
+/*
+ * 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 "SkView.h"
+#include "SkCanvas.h"
+
+SK_DEFINE_INST_COUNT(SkView::Artist)
+SK_DEFINE_INST_COUNT(SkView::Layout)
+
+////////////////////////////////////////////////////////////////////////
+
+SkView::SkView(uint32_t flags) : fFlags(SkToU8(flags))
+{
+ fWidth = fHeight = 0;
+ fLoc.set(0, 0);
+ fParent = fFirstChild = fNextSibling = fPrevSibling = NULL;
+ fMatrix.setIdentity();
+ fContainsFocus = 0;
+}
+
+SkView::~SkView()
+{
+ this->detachAllChildren();
+}
+
+void SkView::setFlags(uint32_t flags)
+{
+ SkASSERT((flags & ~kAllFlagMasks) == 0);
+
+ uint32_t diff = fFlags ^ flags;
+
+ if (diff & kVisible_Mask)
+ this->inval(NULL);
+
+ fFlags = SkToU8(flags);
+
+ if (diff & kVisible_Mask)
+ {
+ this->inval(NULL);
+ }
+}
+
+void SkView::setVisibleP(bool pred)
+{
+ this->setFlags(SkSetClearShift(fFlags, pred, kVisible_Shift));
+}
+
+void SkView::setEnabledP(bool pred)
+{
+ this->setFlags(SkSetClearShift(fFlags, pred, kEnabled_Shift));
+}
+
+void SkView::setFocusableP(bool pred)
+{
+ this->setFlags(SkSetClearShift(fFlags, pred, kFocusable_Shift));
+}
+
+void SkView::setClipToBounds(bool pred) {
+ this->setFlags(SkSetClearShift(fFlags, !pred, kNoClip_Shift));
+}
+
+void SkView::setSize(SkScalar width, SkScalar height)
+{
+ width = SkMaxScalar(0, width);
+ height = SkMaxScalar(0, height);
+
+ if (fWidth != width || fHeight != height)
+ {
+ this->inval(NULL);
+ fWidth = width;
+ fHeight = height;
+ this->inval(NULL);
+ this->onSizeChange();
+ this->invokeLayout();
+ }
+}
+
+void SkView::setLoc(SkScalar x, SkScalar y)
+{
+ if (fLoc.fX != x || fLoc.fY != y)
+ {
+ this->inval(NULL);
+ fLoc.set(x, y);
+ this->inval(NULL);
+ }
+}
+
+void SkView::offset(SkScalar dx, SkScalar dy)
+{
+ if (dx || dy)
+ this->setLoc(fLoc.fX + dx, fLoc.fY + dy);
+}
+
+void SkView::setLocalMatrix(const SkMatrix& matrix)
+{
+ this->inval(NULL);
+ fMatrix = matrix;
+ this->inval(NULL);
+}
+
+void SkView::draw(SkCanvas* canvas)
+{
+ if (fWidth && fHeight && this->isVisible())
+ {
+ SkRect r;
+ r.set(fLoc.fX, fLoc.fY, fLoc.fX + fWidth, fLoc.fY + fHeight);
+ if (this->isClipToBounds() &&
+ canvas->quickReject(r)) {
+ return;
+ }
+
+ SkAutoCanvasRestore as(canvas, true);
+
+ if (this->isClipToBounds()) {
+ canvas->clipRect(r);
+ }
+
+ canvas->translate(fLoc.fX, fLoc.fY);
+ canvas->concat(fMatrix);
+
+ if (fParent) {
+ fParent->beforeChild(this, canvas);
+ }
+
+ int sc = canvas->save();
+ this->onDraw(canvas);
+ canvas->restoreToCount(sc);
+
+ if (fParent) {
+ fParent->afterChild(this, canvas);
+ }
+
+ B2FIter iter(this);
+ SkView* child;
+
+ SkCanvas* childCanvas = this->beforeChildren(canvas);
+
+ while ((child = iter.next()) != NULL)
+ child->draw(childCanvas);
+
+ this->afterChildren(canvas);
+ }
+}
+
+void SkView::inval(SkRect* rect) {
+ SkView* view = this;
+ SkRect storage;
+
+ for (;;) {
+ if (!view->isVisible()) {
+ return;
+ }
+ if (view->isClipToBounds()) {
+ SkRect bounds;
+ view->getLocalBounds(&bounds);
+ if (rect && !bounds.intersect(*rect)) {
+ return;
+ }
+ storage = bounds;
+ rect = &storage;
+ }
+ if (view->handleInval(rect)) {
+ return;
+ }
+
+ SkView* parent = view->fParent;
+ if (parent == NULL) {
+ return;
+ }
+
+ if (rect) {
+ rect->offset(view->fLoc.fX, view->fLoc.fY);
+ }
+ view = parent;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+bool SkView::setFocusView(SkView* fv)
+{
+ SkView* view = this;
+
+ do {
+ if (view->onSetFocusView(fv))
+ return true;
+ } while ((view = view->fParent) != NULL);
+ return false;
+}
+
+SkView* SkView::getFocusView() const
+{
+ SkView* focus = NULL;
+ const SkView* view = this;
+ do {
+ if (view->onGetFocusView(&focus))
+ break;
+ } while ((view = view->fParent) != NULL);
+ return focus;
+}
+
+bool SkView::hasFocus() const
+{
+ return this == this->getFocusView();
+}
+
+bool SkView::acceptFocus()
+{
+ return this->isFocusable() && this->setFocusView(this);
+}
+
+/*
+ Try to give focus to this view, or its children
+*/
+SkView* SkView::acceptFocus(FocusDirection dir)
+{
+ if (dir == kNext_FocusDirection)
+ {
+ if (this->acceptFocus())
+ return this;
+
+ B2FIter iter(this);
+ SkView* child, *focus;
+ while ((child = iter.next()) != NULL)
+ if ((focus = child->acceptFocus(dir)) != NULL)
+ return focus;
+ }
+ else // prev
+ {
+ F2BIter iter(this);
+ SkView* child, *focus;
+ while ((child = iter.next()) != NULL)
+ if ((focus = child->acceptFocus(dir)) != NULL)
+ return focus;
+
+ if (this->acceptFocus())
+ return this;
+ }
+
+ return NULL;
+}
+
+SkView* SkView::moveFocus(FocusDirection dir)
+{
+ SkView* focus = this->getFocusView();
+
+ if (focus == NULL)
+ { // start with the root
+ focus = this;
+ while (focus->fParent)
+ focus = focus->fParent;
+ }
+
+ SkView* child, *parent;
+
+ if (dir == kNext_FocusDirection)
+ {
+ parent = focus;
+ child = focus->fFirstChild;
+ if (child)
+ goto FIRST_CHILD;
+ else
+ goto NEXT_SIB;
+
+ do {
+ while (child != parent->fFirstChild)
+ {
+ FIRST_CHILD:
+ if ((focus = child->acceptFocus(dir)) != NULL)
+ return focus;
+ child = child->fNextSibling;
+ }
+ NEXT_SIB:
+ child = parent->fNextSibling;
+ parent = parent->fParent;
+ } while (parent != NULL);
+ }
+ else // prevfocus
+ {
+ parent = focus->fParent;
+ if (parent == NULL) // we're the root
+ return focus->acceptFocus(dir);
+ else
+ {
+ child = focus;
+ while (parent)
+ {
+ while (child != parent->fFirstChild)
+ {
+ child = child->fPrevSibling;
+ if ((focus = child->acceptFocus(dir)) != NULL)
+ return focus;
+ }
+ if (parent->acceptFocus())
+ return parent;
+
+ child = parent;
+ parent = parent->fParent;
+ }
+ }
+ }
+ return NULL;
+}
+
+void SkView::onFocusChange(bool gainFocusP)
+{
+ this->inval(NULL);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+SkView::Click::Click(SkView* target)
+{
+ SkASSERT(target);
+ fTargetID = target->getSinkID();
+ fType = NULL;
+ fWeOwnTheType = false;
+ fOwner = NULL;
+}
+
+SkView::Click::~Click()
+{
+ this->resetType();
+}
+
+void SkView::Click::resetType()
+{
+ if (fWeOwnTheType)
+ {
+ sk_free(fType);
+ fWeOwnTheType = false;
+ }
+ fType = NULL;
+}
+
+bool SkView::Click::isType(const char type[]) const
+{
+ const char* t = fType;
+
+ if (type == t)
+ return true;
+
+ if (type == NULL)
+ type = "";
+ if (t == NULL)
+ t = "";
+ return !strcmp(t, type);
+}
+
+void SkView::Click::setType(const char type[])
+{
+ this->resetType();
+ fType = (char*)type;
+}
+
+void SkView::Click::copyType(const char type[])
+{
+ if (fType != type)
+ {
+ this->resetType();
+ if (type)
+ {
+ size_t len = strlen(type) + 1;
+ fType = (char*)sk_malloc_throw(len);
+ memcpy(fType, type, len);
+ fWeOwnTheType = true;
+ }
+ }
+}
+
+SkView::Click* SkView::findClickHandler(SkScalar x, SkScalar y, unsigned modi) {
+ if (x < 0 || y < 0 || x >= fWidth || y >= fHeight) {
+ return NULL;
+ }
+
+ if (this->onSendClickToChildren(x, y, modi)) {
+ F2BIter iter(this);
+ SkView* child;
+
+ while ((child = iter.next()) != NULL)
+ {
+ SkPoint p;
+ if (!child->globalToLocal(x, y, &p)) {
+ continue;
+ }
+
+ Click* click = child->findClickHandler(p.fX, p.fY, modi);
+
+ if (click) {
+ return click;
+ }
+ }
+ }
+
+ return this->onFindClickHandler(x, y, modi);
+}
+
+void SkView::DoClickDown(Click* click, int x, int y, unsigned modi)
+{
+ SkASSERT(click);
+
+ SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
+ if (NULL == target) {
+ return;
+ }
+
+ click->fIOrig.set(x, y);
+ click->fICurr = click->fIPrev = click->fIOrig;
+
+ click->fOrig.iset(x, y);
+ if (!target->globalToLocal(&click->fOrig)) {
+ // no history to let us recover from this failure
+ return;
+ }
+ click->fPrev = click->fCurr = click->fOrig;
+
+ click->fState = Click::kDown_State;
+ click->fModifierKeys = modi;
+ target->onClick(click);
+}
+
+void SkView::DoClickMoved(Click* click, int x, int y, unsigned modi)
+{
+ SkASSERT(click);
+
+ SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
+ if (NULL == target) {
+ return;
+ }
+
+ click->fIPrev = click->fICurr;
+ click->fICurr.set(x, y);
+
+ click->fPrev = click->fCurr;
+ click->fCurr.iset(x, y);
+ if (!target->globalToLocal(&click->fCurr)) {
+ // on failure pretend the mouse didn't move
+ click->fCurr = click->fPrev;
+ }
+
+ click->fState = Click::kMoved_State;
+ click->fModifierKeys = modi;
+ target->onClick(click);
+}
+
+void SkView::DoClickUp(Click* click, int x, int y, unsigned modi)
+{
+ SkASSERT(click);
+
+ SkView* target = (SkView*)SkEventSink::FindSink(click->fTargetID);
+ if (NULL == target) {
+ return;
+ }
+
+ click->fIPrev = click->fICurr;
+ click->fICurr.set(x, y);
+
+ click->fPrev = click->fCurr;
+ click->fCurr.iset(x, y);
+ if (!target->globalToLocal(&click->fCurr)) {
+ // on failure pretend the mouse didn't move
+ click->fCurr = click->fPrev;
+ }
+
+ click->fState = Click::kUp_State;
+ click->fModifierKeys = modi;
+ target->onClick(click);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void SkView::invokeLayout() {
+ SkView::Layout* layout = this->getLayout();
+
+ if (layout) {
+ layout->layoutChildren(this);
+ }
+}
+
+void SkView::onDraw(SkCanvas* canvas) {
+ Artist* artist = this->getArtist();
+
+ if (artist) {
+ artist->draw(this, canvas);
+ }
+}
+
+void SkView::onSizeChange() {}
+
+bool SkView::onSendClickToChildren(SkScalar x, SkScalar y, unsigned modi) {
+ return true;
+}
+
+SkView::Click* SkView::onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) {
+ return NULL;
+}
+
+bool SkView::onClick(Click*) {
+ return false;
+}
+
+bool SkView::handleInval(const SkRect*) {
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void SkView::getLocalBounds(SkRect* bounds) const {
+ if (bounds) {
+ bounds->set(0, 0, fWidth, fHeight);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+
+void SkView::detachFromParent_NoLayout() {
+ this->validate();
+ if (fParent == NULL) {
+ return;
+ }
+
+ if (fContainsFocus) {
+ (void)this->setFocusView(NULL);
+ }
+
+ this->inval(NULL);
+
+ SkView* next = NULL;
+
+ if (fNextSibling != this) { // do we have any siblings
+ fNextSibling->fPrevSibling = fPrevSibling;
+ fPrevSibling->fNextSibling = fNextSibling;
+ next = fNextSibling;
+ }
+
+ if (fParent->fFirstChild == this) {
+ fParent->fFirstChild = next;
+ }
+
+ fParent = fNextSibling = fPrevSibling = NULL;
+
+ this->validate();
+ this->unref();
+}
+
+void SkView::detachFromParent() {
+ this->validate();
+ SkView* parent = fParent;
+
+ if (parent) {
+ this->detachFromParent_NoLayout();
+ parent->invokeLayout();
+ }
+}
+
+SkView* SkView::attachChildToBack(SkView* child) {
+ this->validate();
+ SkASSERT(child != this);
+
+ if (child == NULL || fFirstChild == child)
+ goto DONE;
+
+ child->ref();
+ child->detachFromParent_NoLayout();
+
+ if (fFirstChild == NULL) {
+ child->fNextSibling = child;
+ child->fPrevSibling = child;
+ } else {
+ child->fNextSibling = fFirstChild;
+ child->fPrevSibling = fFirstChild->fPrevSibling;
+ fFirstChild->fPrevSibling->fNextSibling = child;
+ fFirstChild->fPrevSibling = child;
+ }
+
+ fFirstChild = child;
+ child->fParent = this;
+ child->inval(NULL);
+
+ this->validate();
+ this->invokeLayout();
+DONE:
+ return child;
+}
+
+SkView* SkView::attachChildToFront(SkView* child) {
+ this->validate();
+ SkASSERT(child != this);
+
+ if (child == NULL || (fFirstChild && fFirstChild->fPrevSibling == child))
+ goto DONE;
+
+ child->ref();
+ child->detachFromParent_NoLayout();
+
+ if (fFirstChild == NULL) {
+ fFirstChild = child;
+ child->fNextSibling = child;
+ child->fPrevSibling = child;
+ } else {
+ child->fNextSibling = fFirstChild;
+ child->fPrevSibling = fFirstChild->fPrevSibling;
+ fFirstChild->fPrevSibling->fNextSibling = child;
+ fFirstChild->fPrevSibling = child;
+ }
+
+ child->fParent = this;
+ child->inval(NULL);
+
+ this->validate();
+ this->invokeLayout();
+DONE:
+ return child;
+}
+
+void SkView::detachAllChildren() {
+ this->validate();
+ while (fFirstChild)
+ fFirstChild->detachFromParent_NoLayout();
+}
+
+void SkView::localToGlobal(SkMatrix* matrix) const {
+ if (matrix) {
+ matrix->reset();
+ const SkView* view = this;
+ while (view)
+ {
+ matrix->preConcat(view->getLocalMatrix());
+ matrix->preTranslate(-view->fLoc.fX, -view->fLoc.fY);
+ view = view->fParent;
+ }
+ }
+}
+bool SkView::globalToLocal(SkScalar x, SkScalar y, SkPoint* local) const
+{
+ SkASSERT(this);
+
+ if (NULL != local) {
+ SkMatrix m;
+ this->localToGlobal(&m);
+ if (!m.invert(&m)) {
+ return false;
+ }
+ SkPoint p;
+ m.mapXY(x, y, &p);
+ local->set(p.fX, p.fY);
+ }
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////
+
+/* Even if the subclass overrides onInflate, they should always be
+ sure to call the inherited method, so that we get called.
+*/
+void SkView::onInflate(const SkDOM& dom, const SkDOM::Node* node) {
+ SkScalar x, y;
+
+ x = this->locX();
+ y = this->locY();
+ (void)dom.findScalar(node, "x", &x);
+ (void)dom.findScalar(node, "y", &y);
+ this->setLoc(x, y);
+
+ x = this->width();
+ y = this->height();
+ (void)dom.findScalar(node, "width", &x);
+ (void)dom.findScalar(node, "height", &y);
+ this->setSize(x, y);
+
+ // inflate the flags
+
+ static const char* gFlagNames[] = {
+ "visible", "enabled", "focusable", "flexH", "flexV"
+ };
+ SkASSERT(SK_ARRAY_COUNT(gFlagNames) == kFlagShiftCount);
+
+ bool b;
+ uint32_t flags = this->getFlags();
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gFlagNames); i++)
+ if (dom.findBool(node, gFlagNames[i], &b))
+ flags = SkSetClearShift(flags, b, i);
+ this->setFlags(flags);
+}
+
+void SkView::inflate(const SkDOM& dom, const SkDOM::Node* node) {
+ this->onInflate(dom, node);
+}
+
+void SkView::onPostInflate(const SkTDict<SkView*>&) {
+ // override in subclass as needed
+}
+
+void SkView::postInflate(const SkTDict<SkView*>& dict) {
+ this->onPostInflate(dict);
+
+ B2FIter iter(this);
+ SkView* child;
+ while ((child = iter.next()) != NULL)
+ child->postInflate(dict);
+}
+
+//////////////////////////////////////////////////////////////////
+
+SkView* SkView::sendEventToParents(const SkEvent& evt) {
+ SkView* parent = fParent;
+
+ while (parent) {
+ if (parent->doEvent(evt)) {
+ return parent;
+ }
+ parent = parent->fParent;
+ }
+ return NULL;
+}
+
+SkView* SkView::sendQueryToParents(SkEvent* evt) {
+ SkView* parent = fParent;
+
+ while (parent) {
+ if (parent->doQuery(evt)) {
+ return parent;
+ }
+ parent = parent->fParent;
+ }
+ return NULL;
+}
+
+//////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////
+
+SkView::F2BIter::F2BIter(const SkView* parent) {
+ fFirstChild = parent ? parent->fFirstChild : NULL;
+ fChild = fFirstChild ? fFirstChild->fPrevSibling : NULL;
+}
+
+SkView* SkView::F2BIter::next() {
+ SkView* curr = fChild;
+
+ if (fChild) {
+ if (fChild == fFirstChild) {
+ fChild = NULL;
+ } else {
+ fChild = fChild->fPrevSibling;
+ }
+ }
+ return curr;
+}
+
+SkView::B2FIter::B2FIter(const SkView* parent) {
+ fFirstChild = parent ? parent->fFirstChild : NULL;
+ fChild = fFirstChild;
+}
+
+SkView* SkView::B2FIter::next() {
+ SkView* curr = fChild;
+
+ if (fChild) {
+ SkView* next = fChild->fNextSibling;
+ if (next == fFirstChild)
+ next = NULL;
+ fChild = next;
+ }
+ return curr;
+}
+
+//////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkView::validate() const {
+// SkASSERT(this->getRefCnt() > 0 && this->getRefCnt() < 100);
+ if (fParent) {
+ SkASSERT(fNextSibling);
+ SkASSERT(fPrevSibling);
+ } else {
+ bool nextNull = NULL == fNextSibling;
+ bool prevNull = NULL == fNextSibling;
+ SkASSERT(nextNull == prevNull);
+ }
+}
+
+static inline void show_if_nonzero(const char name[], SkScalar value)
+{
+ if (value)
+ SkDebugf("%s=\"%g\"", name, value/65536.);
+}
+
+static void tab(int level)
+{
+ for (int i = 0; i < level; i++)
+ SkDebugf(" ");
+}
+
+static void dumpview(const SkView* view, int level, bool recurse)
+{
+ tab(level);
+
+ SkDebugf("<view");
+ show_if_nonzero(" x", view->locX());
+ show_if_nonzero(" y", view->locY());
+ show_if_nonzero(" width", view->width());
+ show_if_nonzero(" height", view->height());
+
+ if (recurse)
+ {
+ SkView::B2FIter iter(view);
+ SkView* child;
+ bool noChildren = true;
+
+ while ((child = iter.next()) != NULL)
+ {
+ if (noChildren)
+ SkDebugf(">\n");
+ noChildren = false;
+ dumpview(child, level + 1, true);
+ }
+
+ if (!noChildren)
+ {
+ tab(level);
+ SkDebugf("</view>\n");
+ }
+ else
+ goto ONELINER;
+ }
+ else
+ {
+ ONELINER:
+ SkDebugf(" />\n");
+ }
+}
+
+void SkView::dump(bool recurse) const
+{
+ dumpview(this, 0, recurse);
+}
+
+#endif
diff --git a/views/SkViewInflate.cpp b/views/SkViewInflate.cpp
new file mode 100644
index 00000000..59cc4813
--- /dev/null
+++ b/views/SkViewInflate.cpp
@@ -0,0 +1,145 @@
+
+/*
+ * 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 "SkViewInflate.h"
+#include "SkView.h"
+#include <stdio.h>
+
+SkViewInflate::SkViewInflate() : fIDs(kMinIDStrAlloc), fStrings(kMinIDStrAlloc)
+{
+}
+
+SkViewInflate::~SkViewInflate()
+{
+}
+
+void SkViewInflate::rInflate(const SkDOM& dom, const SkDOM::Node* node, SkView* parent)
+{
+ const char* str = dom.findAttr(node, "id");
+ if (str)
+ fIDs.set(str, parent);
+
+ const SkDOM::Node* child = dom.getFirstChild(node);
+ while (child)
+ {
+ SkView* view = this->createView(dom, child);
+ if (view)
+ {
+ this->rInflate(dom, child, view);
+ parent->attachChildToFront(view)->unref();
+ }
+ else
+ {
+ const char* name = dom.getName(child);
+ const char* target;
+
+ if (!strcmp(name, "listenTo") && (target = dom.findAttr(child, "target")) != NULL)
+ this->addIDStr(&fListenTo, parent, target);
+
+ if (!strcmp(name, "broadcastTo") && (target = dom.findAttr(child, "target")) != NULL)
+ this->addIDStr(&fBroadcastTo, parent, target);
+ }
+ child = dom.getNextSibling(child);
+ }
+
+ parent->setVisibleP(true);
+ this->inflateView(parent, dom, node);
+}
+
+void SkViewInflate::inflateView(SkView* view, const SkDOM& dom, const SkDOM::Node* node)
+{
+ // called after all of view's children have been instantiated.
+ // this may be overridden by a subclass, to load in layout or other helpers
+ // they should call through to us (INHERITED) before or after their patch
+ view->inflate(dom, node);
+}
+
+SkView* SkViewInflate::inflate(const SkDOM& dom, const SkDOM::Node* node, SkView* root)
+{
+ fIDs.reset();
+
+ if (root == NULL)
+ {
+ root = this->createView(dom, node);
+ if (root == NULL)
+ {
+ printf("createView returned NULL on <%s>\n", dom.getName(node));
+ return NULL;
+ }
+ }
+ this->rInflate(dom, node, root);
+
+ // resolve listeners and broadcasters
+ {
+ SkView* target;
+ const IDStr* iter = fListenTo.begin();
+ const IDStr* stop = fListenTo.end();
+ for (; iter < stop; iter++)
+ {
+ if (fIDs.find(iter->fStr, &target))
+ target->addListenerID(iter->fView->getSinkID());
+ }
+
+ iter = fBroadcastTo.begin();
+ stop = fBroadcastTo.end();
+ for (; iter < stop; iter++)
+ {
+ if (fIDs.find(iter->fStr, &target))
+ iter->fView->addListenerID(target->getSinkID());
+ }
+ }
+
+ // now that the tree is built, give everyone a shot at the ID dict
+ root->postInflate(fIDs);
+ return root;
+}
+
+SkView* SkViewInflate::inflate(const char xml[], size_t len, SkView* root)
+{
+ SkDOM dom;
+ const SkDOM::Node* node = dom.build(xml, len);
+
+ return node ? this->inflate(dom, node, root) : NULL;
+}
+
+SkView* SkViewInflate::findViewByID(const char id[]) const
+{
+ SkASSERT(id);
+ SkView* view;
+ return fIDs.find(id, &view) ? view : NULL;
+}
+
+SkView* SkViewInflate::createView(const SkDOM& dom, const SkDOM::Node* node)
+{
+ if (!strcmp(dom.getName(node), "view"))
+ return new SkView;
+ return NULL;
+}
+
+void SkViewInflate::addIDStr(SkTDArray<IDStr>* list, SkView* view, const char* str)
+{
+ size_t len = strlen(str) + 1;
+ IDStr* pair = list->append();
+ pair->fView = view;
+ pair->fStr = (char*)fStrings.alloc(len, SkChunkAlloc::kThrow_AllocFailType);
+ memcpy(pair->fStr, str, len);
+}
+
+#ifdef SK_DEBUG
+void SkViewInflate::dump() const
+{
+ const IDStr* iter = fListenTo.begin();
+ const IDStr* stop = fListenTo.end();
+ for (; iter < stop; iter++)
+ SkDebugf("inflate: listenTo(\"%s\")\n", iter->fStr);
+
+ iter = fBroadcastTo.begin();
+ stop = fBroadcastTo.end();
+ for (; iter < stop; iter++)
+ SkDebugf("inflate: broadcastFrom(\"%s\")\n", iter->fStr);
+}
+#endif
diff --git a/views/SkViewPriv.cpp b/views/SkViewPriv.cpp
new file mode 100644
index 00000000..048ff08b
--- /dev/null
+++ b/views/SkViewPriv.cpp
@@ -0,0 +1,103 @@
+
+/*
+ * 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 "SkViewPriv.h"
+
+//////////////////////////////////////////////////////////////////////
+
+void SkView::Artist::draw(SkView* view, SkCanvas* canvas)
+{
+ SkASSERT(view && canvas);
+ this->onDraw(view, canvas);
+}
+
+void SkView::Artist::inflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ SkASSERT(&dom && node);
+ this->onInflate(dom, node);
+}
+
+void SkView::Artist::onInflate(const SkDOM&, const SkDOM::Node*)
+{
+ // subclass should override this as needed
+}
+
+SkView::Artist* SkView::getArtist() const
+{
+ Artist_SkTagList* rec = (Artist_SkTagList*)this->findTagList(kViewArtist_SkTagList);
+ SkASSERT(rec == NULL || rec->fArtist != NULL);
+
+ return rec ? rec->fArtist : NULL;
+}
+
+SkView::Artist* SkView::setArtist(Artist* obj)
+{
+ if (obj == NULL)
+ {
+ this->removeTagList(kViewArtist_SkTagList);
+ }
+ else // add/replace
+ {
+ Artist_SkTagList* rec = (Artist_SkTagList*)this->findTagList(kViewArtist_SkTagList);
+
+ if (rec)
+ SkRefCnt_SafeAssign(rec->fArtist, obj);
+ else
+ this->addTagList(new Artist_SkTagList(obj));
+ }
+ return obj;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SkView::Layout::layoutChildren(SkView* parent)
+{
+ SkASSERT(parent);
+ if (parent->width() > 0 && parent->height() > 0)
+ this->onLayoutChildren(parent);
+}
+
+void SkView::Layout::inflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ SkASSERT(&dom && node);
+ this->onInflate(dom, node);
+}
+
+void SkView::Layout::onInflate(const SkDOM&, const SkDOM::Node*)
+{
+ // subclass should override this as needed
+}
+
+SkView::Layout* SkView::getLayout() const
+{
+ Layout_SkTagList* rec = (Layout_SkTagList*)this->findTagList(kViewLayout_SkTagList);
+ SkASSERT(rec == NULL || rec->fLayout != NULL);
+
+ return rec ? rec->fLayout : NULL;
+}
+
+SkView::Layout* SkView::setLayout(Layout* obj, bool invokeLayoutNow)
+{
+ if (obj == NULL)
+ {
+ this->removeTagList(kViewLayout_SkTagList);
+ }
+ else // add/replace
+ {
+ Layout_SkTagList* rec = (Layout_SkTagList*)this->findTagList(kViewLayout_SkTagList);
+
+ if (rec)
+ SkRefCnt_SafeAssign(rec->fLayout, obj);
+ else
+ this->addTagList(new Layout_SkTagList(obj));
+ }
+
+ if (invokeLayoutNow)
+ this->invokeLayout();
+
+ return obj;
+}
diff --git a/views/SkViewPriv.h b/views/SkViewPriv.h
new file mode 100644
index 00000000..487e3794
--- /dev/null
+++ b/views/SkViewPriv.h
@@ -0,0 +1,44 @@
+
+/*
+ * 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 SkViewPriv_DEFINED
+#define SkViewPriv_DEFINED
+
+#include "SkView.h"
+#include "SkTagList.h"
+
+struct Layout_SkTagList : SkTagList {
+ SkView::Layout* fLayout;
+
+ Layout_SkTagList(SkView::Layout* layout)
+ : SkTagList(kViewLayout_SkTagList), fLayout(layout)
+ {
+ SkASSERT(layout);
+ layout->ref();
+ }
+ virtual ~Layout_SkTagList()
+ {
+ fLayout->unref();
+ }
+};
+
+struct Artist_SkTagList : SkTagList {
+ SkView::Artist* fArtist;
+
+ Artist_SkTagList(SkView::Artist* artist)
+ : SkTagList(kViewArtist_SkTagList), fArtist(artist)
+ {
+ SkASSERT(artist);
+ artist->ref();
+ }
+ virtual ~Artist_SkTagList()
+ {
+ fArtist->unref();
+ }
+};
+
+#endif
diff --git a/views/SkWidgets.cpp b/views/SkWidgets.cpp
new file mode 100644
index 00000000..e4547ec0
--- /dev/null
+++ b/views/SkWidgets.cpp
@@ -0,0 +1,560 @@
+
+/*
+ * 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 "SkWidget.h"
+#include "SkCanvas.h"
+#include "SkKey.h"
+#include "SkParsePaint.h"
+#include "SkSystemEventTypes.h"
+#include "SkTextBox.h"
+
+#if 0
+
+#ifdef SK_DEBUG
+ static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
+ {
+ const char* value = dom.findAttr(node, attr);
+ if (value)
+ SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
+ }
+#else
+ #define assert_no_attr(dom, node, attr)
+#endif
+
+#include "SkAnimator.h"
+#include "SkTime.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum SkinType {
+ kPushButton_SkinType,
+ kStaticText_SkinType,
+
+ kSkinTypeCount
+};
+
+struct SkinSuite {
+ SkinSuite();
+ ~SkinSuite()
+ {
+ for (int i = 0; i < kSkinTypeCount; i++)
+ delete fAnimators[i];
+ }
+
+ SkAnimator* get(SkinType);
+
+private:
+ SkAnimator* fAnimators[kSkinTypeCount];
+};
+
+SkinSuite::SkinSuite()
+{
+ static const char kSkinPath[] = "skins/";
+
+ static const char* gSkinNames[] = {
+ "pushbutton_skin.xml",
+ "statictext_skin.xml"
+ };
+
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gSkinNames); i++)
+ {
+ size_t len = strlen(gSkinNames[i]);
+ SkString path(sizeof(kSkinPath) - 1 + len);
+
+ memcpy(path.writable_str(), kSkinPath, sizeof(kSkinPath) - 1);
+ memcpy(path.writable_str() + sizeof(kSkinPath) - 1, gSkinNames[i], len);
+
+ fAnimators[i] = new SkAnimator;
+ if (!fAnimators[i]->decodeURI(path.c_str()))
+ {
+ delete fAnimators[i];
+ fAnimators[i] = NULL;
+ }
+ }
+}
+
+SkAnimator* SkinSuite::get(SkinType st)
+{
+ SkASSERT((unsigned)st < kSkinTypeCount);
+ return fAnimators[st];
+}
+
+static SkinSuite* gSkinSuite;
+
+static SkAnimator* get_skin_animator(SkinType st)
+{
+#if 0
+ if (gSkinSuite == NULL)
+ gSkinSuite = new SkinSuite;
+ return gSkinSuite->get(st);
+#else
+ return NULL;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkWidget::Init()
+{
+}
+
+void SkWidget::Term()
+{
+ delete gSkinSuite;
+}
+
+void SkWidget::onEnabledChange()
+{
+ this->inval(NULL);
+}
+
+void SkWidget::postWidgetEvent()
+{
+ if (!fEvent.isType("") && this->hasListeners())
+ {
+ this->prepareWidgetEvent(&fEvent);
+ this->postToListeners(fEvent);
+ }
+}
+
+void SkWidget::prepareWidgetEvent(SkEvent*)
+{
+ // override in subclass to add any additional fields before posting
+}
+
+void SkWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ if ((node = dom.getFirstChild(node, "event")) != NULL)
+ fEvent.inflate(dom, node);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+size_t SkHasLabelWidget::getLabel(SkString* str) const
+{
+ if (str)
+ *str = fLabel;
+ return fLabel.size();
+}
+
+size_t SkHasLabelWidget::getLabel(char buffer[]) const
+{
+ if (buffer)
+ memcpy(buffer, fLabel.c_str(), fLabel.size());
+ return fLabel.size();
+}
+
+void SkHasLabelWidget::setLabel(const SkString& str)
+{
+ this->setLabel(str.c_str(), str.size());
+}
+
+void SkHasLabelWidget::setLabel(const char label[])
+{
+ this->setLabel(label, strlen(label));
+}
+
+void SkHasLabelWidget::setLabel(const char label[], size_t len)
+{
+ if (!fLabel.equals(label, len))
+ {
+ fLabel.set(label, len);
+ this->onLabelChange();
+ }
+}
+
+void SkHasLabelWidget::onLabelChange()
+{
+ // override in subclass
+}
+
+void SkHasLabelWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ const char* text = dom.findAttr(node, "label");
+ if (text)
+ this->setLabel(text);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+void SkButtonWidget::setButtonState(State state)
+{
+ if (fState != state)
+ {
+ fState = state;
+ this->onButtonStateChange();
+ }
+}
+
+void SkButtonWidget::onButtonStateChange()
+{
+ this->inval(NULL);
+}
+
+void SkButtonWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ int index;
+ if ((index = dom.findList(node, "buttonState", "off,on,unknown")) >= 0)
+ this->setButtonState((State)index);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+bool SkPushButtonWidget::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Key) && evt.getFast32() == kOK_SkKey)
+ {
+ this->postWidgetEvent();
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+static const char* computeAnimatorState(int enabled, int focused, SkButtonWidget::State state)
+{
+ if (!enabled)
+ return "disabled";
+ if (state == SkButtonWidget::kOn_State)
+ {
+ SkASSERT(focused);
+ return "enabled-pressed";
+ }
+ if (focused)
+ return "enabled-focused";
+ return "enabled";
+}
+
+#include "SkBlurMaskFilter.h"
+#include "SkEmbossMaskFilter.h"
+
+static void create_emboss(SkPaint* paint, SkScalar radius, bool focus, bool pressed)
+{
+ SkEmbossMaskFilter::Light light;
+
+ light.fDirection[0] = SK_Scalar1/2;
+ light.fDirection[1] = SK_Scalar1/2;
+ light.fDirection[2] = SK_Scalar1/3;
+ light.fAmbient = 0x48;
+ light.fSpecular = 0x80;
+
+ if (pressed)
+ {
+ light.fDirection[0] = -light.fDirection[0];
+ light.fDirection[1] = -light.fDirection[1];
+ }
+ if (focus)
+ light.fDirection[2] += SK_Scalar1/4;
+
+ paint->setMaskFilter(new SkEmbossMaskFilter(light, radius))->unref();
+}
+
+void SkPushButtonWidget::onDraw(SkCanvas* canvas)
+{
+ this->INHERITED::onDraw(canvas);
+
+ SkString label;
+ this->getLabel(&label);
+
+ SkAnimator* anim = get_skin_animator(kPushButton_SkinType);
+
+ if (anim)
+ {
+ SkEvent evt("user");
+
+ evt.setString("id", "prime");
+ evt.setScalar("prime-width", this->width());
+ evt.setScalar("prime-height", this->height());
+ evt.setString("prime-text", label);
+ evt.setString("prime-state", computeAnimatorState(this->isEnabled(), this->hasFocus(), this->getButtonState()));
+
+ (void)anim->doUserEvent(evt);
+ SkPaint paint;
+ anim->draw(canvas, &paint, SkTime::GetMSecs());
+ }
+ else
+ {
+ SkRect r;
+ SkPaint p;
+
+ r.set(0, 0, this->width(), this->height());
+ p.setAntiAliasOn(true);
+ p.setColor(SK_ColorBLUE);
+ create_emboss(&p, SkIntToScalar(12)/5, this->hasFocus(), this->getButtonState() == kOn_State);
+ canvas->drawRoundRect(r, SkScalarHalf(this->height()), SkScalarHalf(this->height()), p);
+ p.setMaskFilter(NULL);
+
+ p.setTextAlign(SkPaint::kCenter_Align);
+
+ SkTextBox box;
+ box.setMode(SkTextBox::kOneLine_Mode);
+ box.setSpacingAlign(SkTextBox::kCenter_SpacingAlign);
+ box.setBox(0, 0, this->width(), this->height());
+
+// if (this->getButtonState() == kOn_State)
+// p.setColor(SK_ColorRED);
+// else
+ p.setColor(SK_ColorWHITE);
+
+ box.draw(canvas, label.c_str(), label.size(), p);
+ }
+}
+
+SkView::Click* SkPushButtonWidget::onFindClickHandler(SkScalar x, SkScalar y, unsigned modi)
+{
+ this->acceptFocus();
+ return new Click(this);
+}
+
+bool SkPushButtonWidget::onClick(Click* click)
+{
+ SkRect r;
+ State state = kOff_State;
+
+ this->getLocalBounds(&r);
+ if (r.contains(click->fCurr))
+ {
+ if (click->fState == Click::kUp_State)
+ this->postWidgetEvent();
+ else
+ state = kOn_State;
+ }
+ this->setButtonState(state);
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+SkStaticTextView::SkStaticTextView(U32 flags) : SkView(flags)
+{
+ fMargin.set(0, 0);
+ fMode = kFixedSize_Mode;
+ fSpacingAlign = SkTextBox::kStart_SpacingAlign;
+}
+
+SkStaticTextView::~SkStaticTextView()
+{
+}
+
+void SkStaticTextView::computeSize()
+{
+ if (fMode == kAutoWidth_Mode)
+ {
+ SkScalar width = fPaint.measureText(fText.c_str(), fText.size(), NULL, NULL);
+ this->setWidth(width + fMargin.fX * 2);
+ }
+ else if (fMode == kAutoHeight_Mode)
+ {
+ SkScalar width = this->width() - fMargin.fX * 2;
+ int lines = width > 0 ? SkTextLineBreaker::CountLines(fText.c_str(), fText.size(), fPaint, width) : 0;
+
+ SkScalar before, after;
+ (void)fPaint.measureText(0, NULL, &before, &after);
+
+ this->setHeight(lines * (after - before) + fMargin.fY * 2);
+ }
+}
+
+void SkStaticTextView::setMode(Mode mode)
+{
+ SkASSERT((unsigned)mode < kModeCount);
+
+ if (fMode != mode)
+ {
+ fMode = SkToU8(mode);
+ this->computeSize();
+ }
+}
+
+void SkStaticTextView::setSpacingAlign(SkTextBox::SpacingAlign align)
+{
+ fSpacingAlign = SkToU8(align);
+ this->inval(NULL);
+}
+
+void SkStaticTextView::getMargin(SkPoint* margin) const
+{
+ if (margin)
+ *margin = fMargin;
+}
+
+void SkStaticTextView::setMargin(SkScalar dx, SkScalar dy)
+{
+ if (fMargin.fX != dx || fMargin.fY != dy)
+ {
+ fMargin.set(dx, dy);
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+size_t SkStaticTextView::getText(SkString* text) const
+{
+ if (text)
+ *text = fText;
+ return fText.size();
+}
+
+size_t SkStaticTextView::getText(char text[]) const
+{
+ if (text)
+ memcpy(text, fText.c_str(), fText.size());
+ return fText.size();
+}
+
+void SkStaticTextView::setText(const SkString& text)
+{
+ this->setText(text.c_str(), text.size());
+}
+
+void SkStaticTextView::setText(const char text[])
+{
+ this->setText(text, strlen(text));
+}
+
+void SkStaticTextView::setText(const char text[], size_t len)
+{
+ if (!fText.equals(text, len))
+ {
+ fText.set(text, len);
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+void SkStaticTextView::getPaint(SkPaint* paint) const
+{
+ if (paint)
+ *paint = fPaint;
+}
+
+void SkStaticTextView::setPaint(const SkPaint& paint)
+{
+ if (fPaint != paint)
+ {
+ fPaint = paint;
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+void SkStaticTextView::onDraw(SkCanvas* canvas)
+{
+ this->INHERITED::onDraw(canvas);
+
+ if (fText.isEmpty())
+ return;
+
+ SkTextBox box;
+
+ box.setMode(fMode == kAutoWidth_Mode ? SkTextBox::kOneLine_Mode : SkTextBox::kLineBreak_Mode);
+ box.setSpacingAlign(this->getSpacingAlign());
+ box.setBox(fMargin.fX, fMargin.fY, this->width() - fMargin.fX, this->height() - fMargin.fY);
+ box.draw(canvas, fText.c_str(), fText.size(), fPaint);
+}
+
+void SkStaticTextView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ int index;
+ if ((index = dom.findList(node, "mode", "fixed,auto-width,auto-height")) >= 0)
+ this->setMode((Mode)index);
+ else
+ assert_no_attr(dom, node, "mode");
+
+ if ((index = dom.findList(node, "spacing-align", "start,center,end")) >= 0)
+ this->setSpacingAlign((SkTextBox::SpacingAlign)index);
+ else
+ assert_no_attr(dom, node, "mode");
+
+ SkScalar s[2];
+ if (dom.findScalars(node, "margin", s, 2))
+ this->setMargin(s[0], s[1]);
+ else
+ assert_no_attr(dom, node, "margin");
+
+ const char* text = dom.findAttr(node, "text");
+ if (text)
+ this->setText(text);
+
+ if ((node = dom.getFirstChild(node, "paint")) != NULL)
+ SkPaint_Inflate(&fPaint, dom, node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "SkImageDecoder.h"
+
+SkBitmapView::SkBitmapView(U32 flags) : SkView(flags)
+{
+}
+
+SkBitmapView::~SkBitmapView()
+{
+}
+
+bool SkBitmapView::getBitmap(SkBitmap* bitmap) const
+{
+ if (bitmap)
+ *bitmap = fBitmap;
+ return fBitmap.getConfig() != SkBitmap::kNo_Config;
+}
+
+void SkBitmapView::setBitmap(const SkBitmap* bitmap, bool viewOwnsPixels)
+{
+ if (bitmap)
+ {
+ fBitmap = *bitmap;
+ fBitmap.setOwnsPixels(viewOwnsPixels);
+ }
+}
+
+bool SkBitmapView::loadBitmapFromFile(const char path[])
+{
+ SkBitmap bitmap;
+
+ if (SkImageDecoder::DecodeFile(path, &bitmap))
+ {
+ this->setBitmap(&bitmap, true);
+ bitmap.setOwnsPixels(false);
+ return true;
+ }
+ return false;
+}
+
+void SkBitmapView::onDraw(SkCanvas* canvas)
+{
+ if (fBitmap.getConfig() != SkBitmap::kNo_Config &&
+ fBitmap.width() && fBitmap.height())
+ {
+ SkAutoCanvasRestore restore(canvas, true);
+ SkPaint p;
+
+ p.setFilterType(SkPaint::kBilinear_FilterType);
+ canvas->scale( this->width() / fBitmap.width(),
+ this->height() / fBitmap.height(),
+ 0, 0);
+ canvas->drawBitmap(fBitmap, 0, 0, p);
+ }
+}
+
+void SkBitmapView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ const char* src = dom.findAttr(node, "src");
+ if (src)
+ (void)this->loadBitmapFromFile(src);
+}
+
+#endif
diff --git a/views/SkWindow.cpp b/views/SkWindow.cpp
new file mode 100644
index 00000000..f25eb123
--- /dev/null
+++ b/views/SkWindow.cpp
@@ -0,0 +1,415 @@
+
+/*
+ * 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 "SkWindow.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkOSMenu.h"
+#include "SkSystemEventTypes.h"
+#include "SkTime.h"
+
+#define SK_EventDelayInval "\xd" "n" "\xa" "l"
+
+#define TEST_BOUNDERx
+
+#include "SkBounder.h"
+class test_bounder : public SkBounder {
+public:
+ test_bounder(const SkBitmap& bm) : fCanvas(bm) {}
+protected:
+ virtual bool onIRect(const SkIRect& r)
+ {
+ SkRect rr;
+
+ rr.set(SkIntToScalar(r.fLeft), SkIntToScalar(r.fTop),
+ SkIntToScalar(r.fRight), SkIntToScalar(r.fBottom));
+
+ SkPaint p;
+
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setColor(SK_ColorYELLOW);
+
+#if 0
+ rr.inset(SK_ScalarHalf, SK_ScalarHalf);
+#else
+ rr.inset(-SK_ScalarHalf, -SK_ScalarHalf);
+#endif
+
+ fCanvas.drawRect(rr, p);
+ return true;
+ }
+private:
+ SkCanvas fCanvas;
+};
+
+SkWindow::SkWindow() : fFocusView(NULL)
+{
+ fClicks.reset();
+ fWaitingOnInval = false;
+
+#ifdef SK_BUILD_FOR_WINCE
+ fConfig = SkBitmap::kRGB_565_Config;
+#else
+ fConfig = SkBitmap::kARGB_8888_Config;
+#endif
+
+ fMatrix.reset();
+}
+
+SkWindow::~SkWindow()
+{
+ fClicks.deleteAll();
+ fMenus.deleteAll();
+}
+
+SkCanvas* SkWindow::createCanvas() {
+ return new SkCanvas(this->getBitmap());
+}
+
+void SkWindow::setMatrix(const SkMatrix& matrix) {
+ if (fMatrix != matrix) {
+ fMatrix = matrix;
+ this->inval(NULL);
+ }
+}
+
+void SkWindow::preConcat(const SkMatrix& matrix) {
+ SkMatrix m;
+ m.setConcat(fMatrix, matrix);
+ this->setMatrix(m);
+}
+
+void SkWindow::postConcat(const SkMatrix& matrix) {
+ SkMatrix m;
+ m.setConcat(matrix, fMatrix);
+ this->setMatrix(m);
+}
+
+void SkWindow::setConfig(SkBitmap::Config config)
+{
+ this->resize(fBitmap.width(), fBitmap.height(), config);
+}
+
+void SkWindow::resize(int width, int height, SkBitmap::Config config)
+{
+ if (config == SkBitmap::kNo_Config)
+ config = fConfig;
+
+ if (width != fBitmap.width() || height != fBitmap.height() || config != fConfig)
+ {
+ fConfig = config;
+ fBitmap.setConfig(config, width, height);
+ fBitmap.allocPixels();
+ fBitmap.setIsOpaque(true);
+
+ this->setSize(SkIntToScalar(width), SkIntToScalar(height));
+ this->inval(NULL);
+ }
+}
+
+void SkWindow::eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
+{
+ fBitmap.eraseARGB(a, r, g, b);
+}
+
+void SkWindow::eraseRGB(U8CPU r, U8CPU g, U8CPU b)
+{
+ fBitmap.eraseRGB(r, g, b);
+}
+
+bool SkWindow::handleInval(const SkRect* localR)
+{
+ SkIRect ir;
+
+ if (localR) {
+ SkRect devR;
+ SkMatrix inverse;
+ if (!fMatrix.invert(&inverse)) {
+ return false;
+ }
+ fMatrix.mapRect(&devR, *localR);
+ devR.round(&ir);
+ } else {
+ ir.set(0, 0,
+ SkScalarRound(this->width()),
+ SkScalarRound(this->height()));
+ }
+ fDirtyRgn.op(ir, SkRegion::kUnion_Op);
+
+ this->onHandleInval(ir);
+ return true;
+}
+
+void SkWindow::forceInvalAll() {
+ fDirtyRgn.setRect(0, 0,
+ SkScalarCeil(this->width()),
+ SkScalarCeil(this->height()));
+}
+
+#if defined(SK_BUILD_FOR_WINCE) && defined(USE_GX_SCREEN)
+ #include <windows.h>
+ #include <gx.h>
+ extern GXDisplayProperties gDisplayProps;
+#endif
+
+#ifdef SK_SIMULATE_FAILED_MALLOC
+extern bool gEnableControlledThrow;
+#endif
+
+bool SkWindow::update(SkIRect* updateArea)
+{
+ if (!fDirtyRgn.isEmpty())
+ {
+ SkBitmap bm = this->getBitmap();
+
+#if defined(SK_BUILD_FOR_WINCE) && defined(USE_GX_SCREEN)
+ char* buffer = (char*)GXBeginDraw();
+ SkASSERT(buffer);
+
+ RECT rect;
+ GetWindowRect((HWND)((SkOSWindow*)this)->getHWND(), &rect);
+ buffer += rect.top * gDisplayProps.cbyPitch + rect.left * gDisplayProps.cbxPitch;
+
+ bm.setPixels(buffer);
+#endif
+
+ SkAutoTUnref<SkCanvas> canvas(this->createCanvas());
+
+ canvas->clipRegion(fDirtyRgn);
+ if (updateArea)
+ *updateArea = fDirtyRgn.getBounds();
+
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->concat(fMatrix);
+
+ // empty this now, so we can correctly record any inval calls that
+ // might be made during the draw call.
+ fDirtyRgn.setEmpty();
+
+#ifdef TEST_BOUNDER
+ test_bounder b(bm);
+ canvas->setBounder(&b);
+#endif
+#ifdef SK_SIMULATE_FAILED_MALLOC
+ gEnableControlledThrow = true;
+#endif
+#ifdef SK_BUILD_FOR_WIN32
+ //try {
+ this->draw(canvas);
+ //}
+ //catch (...) {
+ //}
+#else
+ this->draw(canvas);
+#endif
+#ifdef SK_SIMULATE_FAILED_MALLOC
+ gEnableControlledThrow = false;
+#endif
+#ifdef TEST_BOUNDER
+ canvas->setBounder(NULL);
+#endif
+
+#if defined(SK_BUILD_FOR_WINCE) && defined(USE_GX_SCREEN)
+ GXEndDraw();
+#endif
+
+ return true;
+ }
+ return false;
+}
+
+bool SkWindow::handleChar(SkUnichar uni)
+{
+ if (this->onHandleChar(uni))
+ return true;
+
+ SkView* focus = this->getFocusView();
+ if (focus == NULL)
+ focus = this;
+
+ SkEvent evt(SK_EventType_Unichar);
+ evt.setFast32(uni);
+ return focus->doEvent(evt);
+}
+
+bool SkWindow::handleKey(SkKey key)
+{
+ if (key == kNONE_SkKey)
+ return false;
+
+ if (this->onHandleKey(key))
+ return true;
+
+ // send an event to the focus-view
+ {
+ SkView* focus = this->getFocusView();
+ if (focus == NULL)
+ focus = this;
+
+ SkEvent evt(SK_EventType_Key);
+ evt.setFast32(key);
+ if (focus->doEvent(evt))
+ return true;
+ }
+
+ if (key == kUp_SkKey || key == kDown_SkKey)
+ {
+ if (this->moveFocus(key == kUp_SkKey ? kPrev_FocusDirection : kNext_FocusDirection) == NULL)
+ this->onSetFocusView(NULL);
+ return true;
+ }
+ return false;
+}
+
+bool SkWindow::handleKeyUp(SkKey key)
+{
+ if (key == kNONE_SkKey)
+ return false;
+
+ if (this->onHandleKeyUp(key))
+ return true;
+
+ //send an event to the focus-view
+ {
+ SkView* focus = this->getFocusView();
+ if (focus == NULL)
+ focus = this;
+
+ //should this one be the same?
+ SkEvent evt(SK_EventType_KeyUp);
+ evt.setFast32(key);
+ if (focus->doEvent(evt))
+ return true;
+ }
+ return false;
+}
+
+void SkWindow::addMenu(SkOSMenu* menu) {
+ *fMenus.append() = menu;
+ this->onAddMenu(menu);
+}
+
+void SkWindow::setTitle(const char title[]) {
+ if (NULL == title) {
+ title = "";
+ }
+ fTitle.set(title);
+ this->onSetTitle(title);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+bool SkWindow::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventDelayInval))
+ {
+ SkRegion::Iterator iter(fDirtyRgn);
+
+ for (; !iter.done(); iter.next())
+ this->onHandleInval(iter.rect());
+ fWaitingOnInval = false;
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+bool SkWindow::onGetFocusView(SkView** focus) const
+{
+ if (focus)
+ *focus = fFocusView;
+ return true;
+}
+
+bool SkWindow::onSetFocusView(SkView* focus)
+{
+ if (fFocusView != focus)
+ {
+ if (fFocusView)
+ fFocusView->onFocusChange(false);
+ fFocusView = focus;
+ if (focus)
+ focus->onFocusChange(true);
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void SkWindow::onHandleInval(const SkIRect&)
+{
+}
+
+bool SkWindow::onHandleChar(SkUnichar)
+{
+ return false;
+}
+
+bool SkWindow::onHandleKey(SkKey)
+{
+ return false;
+}
+
+bool SkWindow::onHandleKeyUp(SkKey)
+{
+ return false;
+}
+
+bool SkWindow::handleClick(int x, int y, Click::State state, void *owner,
+ unsigned modifierKeys) {
+ return this->onDispatchClick(x, y, state, owner, modifierKeys);
+}
+
+bool SkWindow::onDispatchClick(int x, int y, Click::State state,
+ void* owner, unsigned modifierKeys) {
+ bool handled = false;
+
+ // First, attempt to find an existing click with this owner.
+ int index = -1;
+ for (int i = 0; i < fClicks.count(); i++) {
+ if (owner == fClicks[i]->fOwner) {
+ index = i;
+ break;
+ }
+ }
+
+ switch (state) {
+ case Click::kDown_State: {
+ if (index != -1) {
+ delete fClicks[index];
+ fClicks.remove(index);
+ }
+ Click* click = this->findClickHandler(SkIntToScalar(x),
+ SkIntToScalar(y), modifierKeys);
+
+ if (click) {
+ click->fOwner = owner;
+ *fClicks.append() = click;
+ SkView::DoClickDown(click, x, y, modifierKeys);
+ handled = true;
+ }
+ break;
+ }
+ case Click::kMoved_State:
+ if (index != -1) {
+ SkView::DoClickMoved(fClicks[index], x, y, modifierKeys);
+ handled = true;
+ }
+ break;
+ case Click::kUp_State:
+ if (index != -1) {
+ SkView::DoClickUp(fClicks[index], x, y, modifierKeys);
+ delete fClicks[index];
+ fClicks.remove(index);
+ handled = true;
+ }
+ break;
+ default:
+ // Do nothing
+ break;
+ }
+ return handled;
+}
diff --git a/views/animated/SkBorderView.cpp b/views/animated/SkBorderView.cpp
new file mode 100644
index 00000000..3eff6056
--- /dev/null
+++ b/views/animated/SkBorderView.cpp
@@ -0,0 +1,96 @@
+
+/*
+ * 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 "SkBorderView.h"
+#include "SkAnimator.h"
+#include "SkWidgetViews.h"
+#include "SkSystemEventTypes.h"
+#include "SkTime.h"
+#include "SkStackViewLayout.h"
+
+SkBorderView::SkBorderView() : fLeft(SkIntToScalar(0)),
+ fRight(SkIntToScalar(0)),
+ fTop(SkIntToScalar(0)),
+ fBottom(SkIntToScalar(0))
+{
+ fAnim.setHostEventSink(this);
+ init_skin_anim(kBorder_SkinEnum, &fAnim);
+}
+
+SkBorderView::~SkBorderView()
+{
+
+}
+
+void SkBorderView::setSkin(const char skin[])
+{
+ init_skin_anim(skin, &fAnim);
+}
+
+/* virtual */ void SkBorderView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+}
+
+/*virtual*/ void SkBorderView::onSizeChange()
+{
+ this->INHERITED::onSizeChange();
+ SkEvent evt("user");
+ evt.setString("id", "setDim");
+ evt.setScalar("dimX", this->width());
+ evt.setScalar("dimY", this->height());
+ fAnim.doUserEvent(evt);
+}
+
+/*virtual*/ void SkBorderView::onDraw(SkCanvas* canvas)
+{
+ SkPaint paint;
+ SkAnimator::DifferenceType diff = fAnim.draw(canvas, &paint, SkTime::GetMSecs());
+
+ if (diff == SkAnimator::kDifferent)
+ this->inval(NULL);
+ else if (diff == SkAnimator::kPartiallyDifferent)
+ {
+ SkRect bounds;
+ fAnim.getInvalBounds(&bounds);
+ this->inval(&bounds);
+ }
+}
+
+/*virtual*/ bool SkBorderView::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Inval))
+ {
+ this->inval(NULL);
+ return true;
+ }
+ if (evt.isType("recommendDim"))
+ {
+ evt.findScalar("leftMargin", &fLeft);
+ evt.findScalar("rightMargin", &fRight);
+ evt.findScalar("topMargin", &fTop);
+ evt.findScalar("bottomMargin", &fBottom);
+
+ //setup_views.cpp uses SkView::Layout instead of SkStackViewLayout
+ //but that gives me an error
+ SkStackViewLayout* layout;
+ fMargin.set(fLeft, fTop, fRight, fBottom);
+ if (this->getLayout())
+ {
+ layout = (SkStackViewLayout*)this->getLayout();
+ layout->setMargin(fMargin);
+ }
+ else
+ {
+ layout = new SkStackViewLayout;
+ layout->setMargin(fMargin);
+ this->setLayout(layout)->unref();
+ }
+ this->invokeLayout();
+ }
+ return this->INHERITED::onEvent(evt);
+}
diff --git a/views/animated/SkImageView.cpp b/views/animated/SkImageView.cpp
new file mode 100644
index 00000000..2956729f
--- /dev/null
+++ b/views/animated/SkImageView.cpp
@@ -0,0 +1,302 @@
+
+/*
+ * 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 "SkImageView.h"
+#include "SkAnimator.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkImageDecoder.h"
+#include "SkMatrix.h"
+#include "SkSystemEventTypes.h"
+#include "SkTime.h"
+
+SkImageView::SkImageView()
+{
+ fMatrix = NULL;
+ fScaleType = kMatrix_ScaleType;
+
+ fData.fAnim = NULL; // handles initializing the other union values
+ fDataIsAnim = true;
+
+ fUriIsValid = false; // an empty string is not valid
+}
+
+SkImageView::~SkImageView()
+{
+ if (fMatrix)
+ sk_free(fMatrix);
+
+ this->freeData();
+}
+
+void SkImageView::getUri(SkString* uri) const
+{
+ if (uri)
+ *uri = fUri;
+}
+
+void SkImageView::setUri(const char uri[])
+{
+ if (!fUri.equals(uri))
+ {
+ fUri.set(uri);
+ this->onUriChange();
+ }
+}
+
+void SkImageView::setUri(const SkString& uri)
+{
+ if (fUri != uri)
+ {
+ fUri = uri;
+ this->onUriChange();
+ }
+}
+
+void SkImageView::setScaleType(ScaleType st)
+{
+ SkASSERT((unsigned)st <= kFitEnd_ScaleType);
+
+ if ((ScaleType)fScaleType != st)
+ {
+ fScaleType = SkToU8(st);
+ if (fUriIsValid)
+ this->inval(NULL);
+ }
+}
+
+bool SkImageView::getImageMatrix(SkMatrix* matrix) const
+{
+ if (fMatrix)
+ {
+ SkASSERT(!fMatrix->isIdentity());
+ if (matrix)
+ *matrix = *fMatrix;
+ return true;
+ }
+ else
+ {
+ if (matrix)
+ matrix->reset();
+ return false;
+ }
+}
+
+void SkImageView::setImageMatrix(const SkMatrix* matrix)
+{
+ bool changed = false;
+
+ if (matrix && !matrix->isIdentity())
+ {
+ if (fMatrix == NULL)
+ fMatrix = (SkMatrix*)sk_malloc_throw(sizeof(SkMatrix));
+ *fMatrix = *matrix;
+ changed = true;
+ }
+ else // set us to identity
+ {
+ if (fMatrix)
+ {
+ SkASSERT(!fMatrix->isIdentity());
+ sk_free(fMatrix);
+ fMatrix = NULL;
+ changed = true;
+ }
+ }
+
+ // only redraw if we changed our matrix and we're not in scaleToFit mode
+ if (changed && this->getScaleType() == kMatrix_ScaleType && fUriIsValid)
+ this->inval(NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+bool SkImageView::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Inval))
+ {
+ if (fUriIsValid)
+ this->inval(NULL);
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+static inline SkMatrix::ScaleToFit scaleTypeToScaleToFit(SkImageView::ScaleType st)
+{
+ SkASSERT(st != SkImageView::kMatrix_ScaleType);
+ SkASSERT((unsigned)st <= SkImageView::kFitEnd_ScaleType);
+
+ SkASSERT(SkImageView::kFitXY_ScaleType - 1 == SkMatrix::kFill_ScaleToFit);
+ SkASSERT(SkImageView::kFitStart_ScaleType - 1 == SkMatrix::kStart_ScaleToFit);
+ SkASSERT(SkImageView::kFitCenter_ScaleType - 1 == SkMatrix::kCenter_ScaleToFit);
+ SkASSERT(SkImageView::kFitEnd_ScaleType - 1 == SkMatrix::kEnd_ScaleToFit);
+
+ return (SkMatrix::ScaleToFit)(st - 1);
+}
+
+void SkImageView::onDraw(SkCanvas* canvas)
+{
+ SkRect src;
+ if (!this->getDataBounds(&src))
+ {
+ SkDEBUGCODE(canvas->drawColor(SK_ColorRED);)
+ return; // nothing to draw
+ }
+
+ SkAutoCanvasRestore restore(canvas, true);
+ SkMatrix matrix;
+
+ if (this->getScaleType() == kMatrix_ScaleType)
+ (void)this->getImageMatrix(&matrix);
+ else
+ {
+ SkRect dst;
+ dst.set(0, 0, this->width(), this->height());
+ matrix.setRectToRect(src, dst, scaleTypeToScaleToFit(this->getScaleType()));
+ }
+ canvas->concat(matrix);
+
+ SkPaint paint;
+
+ paint.setAntiAlias(true);
+
+ if (fDataIsAnim)
+ {
+ SkMSec now = SkTime::GetMSecs();
+
+ SkAnimator::DifferenceType diff = fData.fAnim->draw(canvas, &paint, now);
+
+SkDEBUGF(("SkImageView : now = %X[%12.3f], diff = %d\n", now, now/1000., diff));
+
+ if (diff == SkAnimator::kDifferent)
+ this->inval(NULL);
+ else if (diff == SkAnimator::kPartiallyDifferent)
+ {
+ SkRect bounds;
+ fData.fAnim->getInvalBounds(&bounds);
+ matrix.mapRect(&bounds); // get the bounds into view coordinates
+ this->inval(&bounds);
+ }
+ }
+ else
+ canvas->drawBitmap(*fData.fBitmap, 0, 0, &paint);
+}
+
+void SkImageView::onInflate(const SkDOM& dom, const SkDOMNode* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ const char* src = dom.findAttr(node, "src");
+ if (src)
+ this->setUri(src);
+
+ int index = dom.findList(node, "scaleType", "matrix,fitXY,fitStart,fitCenter,fitEnd");
+ if (index >= 0)
+ this->setScaleType((ScaleType)index);
+
+ // need inflate syntax/reader for matrix
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+void SkImageView::onUriChange()
+{
+ if (this->freeData())
+ this->inval(NULL);
+ fUriIsValid = true; // give ensureUriIsLoaded() a shot at the new uri
+}
+
+bool SkImageView::freeData()
+{
+ if (fData.fAnim) // test is valid for all union values
+ {
+ if (fDataIsAnim)
+ delete fData.fAnim;
+ else
+ delete fData.fBitmap;
+
+ fData.fAnim = NULL; // valid for all union values
+ return true;
+ }
+ return false;
+}
+
+bool SkImageView::getDataBounds(SkRect* bounds)
+{
+ SkASSERT(bounds);
+
+ if (this->ensureUriIsLoaded())
+ {
+ SkScalar width, height;
+
+ if (fDataIsAnim)
+ {
+ if (SkScalarIsNaN(width = fData.fAnim->getScalar("dimensions", "x")) ||
+ SkScalarIsNaN(height = fData.fAnim->getScalar("dimensions", "y")))
+ {
+ // cons up fake bounds
+ width = this->width();
+ height = this->height();
+ }
+ }
+ else
+ {
+ width = SkIntToScalar(fData.fBitmap->width());
+ height = SkIntToScalar(fData.fBitmap->height());
+ }
+ bounds->set(0, 0, width, height);
+ return true;
+ }
+ return false;
+}
+
+bool SkImageView::ensureUriIsLoaded()
+{
+ if (fData.fAnim) // test is valid for all union values
+ {
+ SkASSERT(fUriIsValid);
+ return true;
+ }
+ if (!fUriIsValid)
+ return false;
+
+ // try to load the url
+ if (fUri.endsWith(".xml")) // assume it is screenplay
+ {
+ SkAnimator* anim = new SkAnimator;
+
+ if (!anim->decodeURI(fUri.c_str()))
+ {
+ delete anim;
+ fUriIsValid = false;
+ return false;
+ }
+ anim->setHostEventSink(this);
+
+ fData.fAnim = anim;
+ fDataIsAnim = true;
+ }
+ else // assume it is an image format
+ {
+ #if 0
+ SkBitmap* bitmap = new SkBitmap;
+
+ if (!SkImageDecoder::DecodeURL(fUri.c_str(), bitmap))
+ {
+ delete bitmap;
+ fUriIsValid = false;
+ return false;
+ }
+ fData.fBitmap = bitmap;
+ fDataIsAnim = false;
+ #else
+ return false;
+ #endif
+ }
+ return true;
+}
diff --git a/views/animated/SkProgressBarView.cpp b/views/animated/SkProgressBarView.cpp
new file mode 100644
index 00000000..e7754eb6
--- /dev/null
+++ b/views/animated/SkProgressBarView.cpp
@@ -0,0 +1,109 @@
+
+/*
+ * 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 "SkProgressBarView.h"
+#include "SkAnimator.h"
+#include "SkWidgetViews.h"
+#include "SkTime.h"
+#include "SkSystemEventTypes.h"
+
+SkProgressBarView::SkProgressBarView()
+{
+ init_skin_anim(kProgress_SkinEnum, &fAnim);
+ fAnim.setHostEventSink(this);
+ fProgress = 0;
+ fMax = 100;
+
+}
+
+void SkProgressBarView::changeProgress(int diff)
+{
+ int newProg = fProgress + diff;
+ if (newProg > 0 && newProg < fMax)
+ this->setProgress(newProg);
+ //otherwise i'll just leave it as it is
+ //this implies that if a new max and progress are set, max must be set first
+}
+
+/*virtual*/ void SkProgressBarView::onDraw(SkCanvas* canvas)
+{
+ SkPaint paint;
+ SkAnimator::DifferenceType diff = fAnim.draw(canvas, &paint, SkTime::GetMSecs());
+
+ if (diff == SkAnimator::kDifferent)
+ this->inval(NULL);
+ else if (diff == SkAnimator::kPartiallyDifferent)
+ {
+ SkRect bounds;
+ fAnim.getInvalBounds(&bounds);
+ this->inval(&bounds);
+ }
+}
+
+/*virtual*/ bool SkProgressBarView::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Inval))
+ {
+ this->inval(NULL);
+ return true;
+ }
+ if (evt.isType("recommendDim"))
+ {
+ SkScalar height;
+
+ if (evt.findScalar("y", &height))
+ this->setHeight(height);
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+/*virtual*/ void SkProgressBarView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+ int32_t temp;
+ if (dom.findS32(node, "max", &temp))
+ this->setMax(temp);
+ if (dom.findS32(node, "progress", &temp))
+ this->setProgress(temp);
+}
+
+/*virtual*/ void SkProgressBarView::onSizeChange()
+{
+ this->INHERITED::onSizeChange();
+ SkEvent evt("user");
+ evt.setString("id", "setDim");
+ evt.setScalar("dimX", this->width());
+ evt.setScalar("dimY", this->height());
+ fAnim.doUserEvent(evt);
+}
+
+void SkProgressBarView::reset()
+{
+ fProgress = 0;
+ SkEvent e("user");
+ e.setString("id", "reset");
+ fAnim.doUserEvent(e);
+}
+
+void SkProgressBarView::setMax(int max)
+{
+ fMax = max;
+ SkEvent e("user");
+ e.setString("id", "setMax");
+ e.setS32("newMax", max);
+ fAnim.doUserEvent(e);
+}
+
+void SkProgressBarView::setProgress(int progress)
+{
+ fProgress = progress;
+ SkEvent e("user");
+ e.setString("id", "setProgress");
+ e.setS32("newProgress", progress);
+ fAnim.doUserEvent(e);
+}
diff --git a/views/animated/SkScrollBarView.cpp b/views/animated/SkScrollBarView.cpp
new file mode 100644
index 00000000..d78cafaf
--- /dev/null
+++ b/views/animated/SkScrollBarView.cpp
@@ -0,0 +1,145 @@
+
+/*
+ * 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 "SkScrollBarView.h"
+#include "SkAnimator.h"
+#include "SkWidgetViews.h"
+#include "SkSystemEventTypes.h"
+#include "SkTime.h"
+
+//see SkProgressBarView.cpp
+//#include "SkWidgetViews.cpp"
+
+SkScrollBarView::SkScrollBarView()
+{
+ fAnim.setHostEventSink(this);
+ init_skin_anim(kScroll_SkinEnum, &fAnim);
+
+ fTotalLength = 0;
+ fStartPoint = 0;
+ fShownLength = 0;
+
+ this->adjust();
+}
+
+void SkScrollBarView::setStart(unsigned start)
+{
+ if ((int)start < 0)
+ start = 0;
+
+ if (fStartPoint != start)
+ {
+ fStartPoint = start;
+ this->adjust();
+ }
+}
+
+void SkScrollBarView::setShown(unsigned shown)
+{
+ if ((int)shown < 0)
+ shown = 0;
+
+ if (fShownLength != shown)
+ {
+ fShownLength = shown;
+ this->adjust();
+ }
+}
+
+void SkScrollBarView::setTotal(unsigned total)
+{
+ if ((int)total < 0)
+ total = 0;
+
+ if (fTotalLength != total)
+ {
+ fTotalLength = total;
+ this->adjust();
+ }
+}
+
+/* virtual */ void SkScrollBarView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ int32_t value;
+ if (dom.findS32(node, "total", &value))
+ this->setTotal(value);
+ if (dom.findS32(node, "shown", &value))
+ this->setShown(value);
+}
+
+/*virtual*/ void SkScrollBarView::onSizeChange()
+{
+ this->INHERITED::onSizeChange();
+ SkEvent evt("user");
+ evt.setString("id", "setDim");
+ evt.setScalar("dimX", this->width());
+ evt.setScalar("dimY", this->height());
+ fAnim.doUserEvent(evt);
+}
+
+/*virtual*/ void SkScrollBarView::onDraw(SkCanvas* canvas)
+{
+ SkPaint paint;
+ SkAnimator::DifferenceType diff = fAnim.draw(canvas, &paint, SkTime::GetMSecs());
+
+ if (diff == SkAnimator::kDifferent)
+ this->inval(NULL);
+ else if (diff == SkAnimator::kPartiallyDifferent)
+ {
+ SkRect bounds;
+ fAnim.getInvalBounds(&bounds);
+ this->inval(&bounds);
+ }
+}
+
+/*virtual*/ bool SkScrollBarView::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Inval))
+ {
+ this->inval(NULL);
+ return true;
+ }
+ if (evt.isType("recommendDim"))
+ {
+ SkScalar width;
+
+ if (evt.findScalar("x", &width))
+ this->setWidth(width);
+ return true;
+ }
+
+ return this->INHERITED::onEvent(evt);
+}
+
+void SkScrollBarView::adjust()
+{
+ int total = fTotalLength;
+ int start = fStartPoint;
+ int shown = fShownLength;
+// int hideBar = 0;
+
+ if (total <= 0 || shown <= 0 || shown >= total) // no bar to show
+ {
+ total = 1; // avoid divide-by-zero. should be done by skin/script
+// hideBar = 1; // signal we don't want a thumb
+ }
+ else
+ {
+ if (start + shown > total)
+ start = total - shown;
+ }
+
+ SkEvent e("user");
+ e.setString("id", "adjustScrollBar");
+ e.setScalar("_totalLength", SkIntToScalar(total));
+ e.setScalar("_startPoint", SkIntToScalar(start));
+ e.setScalar("_shownLength", SkIntToScalar(shown));
+// e.setS32("hideBar", hideBar);
+ fAnim.doUserEvent(e);
+}
diff --git a/views/animated/SkStaticTextView.cpp b/views/animated/SkStaticTextView.cpp
new file mode 100644
index 00000000..0e4cad63
--- /dev/null
+++ b/views/animated/SkStaticTextView.cpp
@@ -0,0 +1,191 @@
+
+/*
+ * 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 "SkWidgetViews.h"
+#include "SkTextBox.h"
+
+#ifdef SK_DEBUG
+static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
+{
+ const char* value = dom.findAttr(node, attr);
+ if (value)
+ SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
+}
+#else
+ #define assert_no_attr(dom, node, attr)
+#endif
+
+SkStaticTextView::SkStaticTextView()
+{
+ fMargin.set(0, 0);
+ fMode = kFixedSize_Mode;
+ fSpacingAlign = SkTextBox::kStart_SpacingAlign;
+
+// init_skin_paint(kStaticText_SkinEnum, &fPaint);
+}
+
+SkStaticTextView::~SkStaticTextView()
+{
+}
+
+void SkStaticTextView::computeSize()
+{
+ if (fMode == kAutoWidth_Mode)
+ {
+ SkScalar width = fPaint.measureText(fText.c_str(), fText.size());
+ this->setWidth(width + fMargin.fX * 2);
+ }
+ else if (fMode == kAutoHeight_Mode)
+ {
+ SkScalar width = this->width() - fMargin.fX * 2;
+ int lines = width > 0 ? SkTextLineBreaker::CountLines(fText.c_str(), fText.size(), fPaint, width) : 0;
+
+ this->setHeight(lines * fPaint.getFontSpacing() + fMargin.fY * 2);
+ }
+}
+
+void SkStaticTextView::setMode(Mode mode)
+{
+ SkASSERT((unsigned)mode < kModeCount);
+
+ if (fMode != mode)
+ {
+ fMode = SkToU8(mode);
+ this->computeSize();
+ }
+}
+
+void SkStaticTextView::setSpacingAlign(SkTextBox::SpacingAlign align)
+{
+ fSpacingAlign = SkToU8(align);
+ this->inval(NULL);
+}
+
+void SkStaticTextView::getMargin(SkPoint* margin) const
+{
+ if (margin)
+ *margin = fMargin;
+}
+
+void SkStaticTextView::setMargin(SkScalar dx, SkScalar dy)
+{
+ if (fMargin.fX != dx || fMargin.fY != dy)
+ {
+ fMargin.set(dx, dy);
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+size_t SkStaticTextView::getText(SkString* text) const
+{
+ if (text)
+ *text = fText;
+ return fText.size();
+}
+
+size_t SkStaticTextView::getText(char text[]) const
+{
+ if (text)
+ memcpy(text, fText.c_str(), fText.size());
+ return fText.size();
+}
+
+void SkStaticTextView::setText(const SkString& text)
+{
+ this->setText(text.c_str(), text.size());
+}
+
+void SkStaticTextView::setText(const char text[])
+{
+ if (text == NULL)
+ text = "";
+ this->setText(text, strlen(text));
+}
+
+void SkStaticTextView::setText(const char text[], size_t len)
+{
+ if (!fText.equals(text, len))
+ {
+ fText.set(text, len);
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+void SkStaticTextView::getPaint(SkPaint* paint) const
+{
+ if (paint)
+ *paint = fPaint;
+}
+
+void SkStaticTextView::setPaint(const SkPaint& paint)
+{
+ if (fPaint != paint)
+ {
+ fPaint = paint;
+ this->computeSize();
+ this->inval(NULL);
+ }
+}
+
+void SkStaticTextView::onDraw(SkCanvas* canvas)
+{
+ this->INHERITED::onDraw(canvas);
+
+ if (fText.isEmpty())
+ return;
+
+ SkTextBox box;
+
+ box.setMode(fMode == kAutoWidth_Mode ? SkTextBox::kOneLine_Mode : SkTextBox::kLineBreak_Mode);
+ box.setSpacingAlign(this->getSpacingAlign());
+ box.setBox(fMargin.fX, fMargin.fY, this->width() - fMargin.fX, this->height() - fMargin.fY);
+ box.draw(canvas, fText.c_str(), fText.size(), fPaint);
+}
+
+void SkStaticTextView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+if (false) { // avoid bit rot, suppress warning
+ this->INHERITED::onInflate(dom, node);
+
+ int index;
+ if ((index = dom.findList(node, "mode", "fixed,auto-width,auto-height")) >= 0) {
+ this->setMode((Mode)index);
+ } else {
+ assert_no_attr(dom, node, "mode");
+ }
+
+ if ((index = dom.findList(node, "spacing-align", "start,center,end")) >= 0) {
+ this->setSpacingAlign((SkTextBox::SpacingAlign)index);
+ } else {
+ assert_no_attr(dom, node, "spacing-align");
+ }
+
+ SkScalar s[2];
+ if (dom.findScalars(node, "margin", s, 2)) {
+ this->setMargin(s[0], s[1]);
+ } else {
+ assert_no_attr(dom, node, "margin");
+ }
+
+ const char* text = dom.findAttr(node, "text");
+ if (text) {
+ this->setText(text);
+ }
+
+ if ((node = dom.getFirstChild(node, "paint")) != NULL &&
+ (node = dom.getFirstChild(node, "screenplay")) != NULL)
+ {
+// FIXME: Including inflate_paint causes Windows build to fail -- it complains
+// that SKListView::SkListView is undefined.
+#if 0
+ inflate_paint(dom, node, &fPaint);
+#endif
+ }
+}
+}
diff --git a/views/animated/SkWidgetViews.cpp b/views/animated/SkWidgetViews.cpp
new file mode 100644
index 00000000..45e98688
--- /dev/null
+++ b/views/animated/SkWidgetViews.cpp
@@ -0,0 +1,400 @@
+
+/*
+ * 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 "SkWidgetViews.h"
+#include "SkAnimator.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkStream.h"
+#include "SkSystemEventTypes.h"
+
+/*
+I have moved this to SkWidgetViews.h
+enum SkinEnum {
+ kButton_SkinEnum,
+ kProgress_SkinEnum,
+ kScroll_SkinEnum,
+ kStaticText_SkinEnum,
+
+ kSkinEnumCount
+};
+*/
+
+SK_DEFINE_INST_COUNT(SkListSource)
+
+const char* get_skin_enum_path(SkinEnum se)
+{
+ SkASSERT((unsigned)se < kSkinEnumCount);
+
+ static const char* gSkinPaths[] = {
+ "common/default/default/skins/border3.xml",
+ "common/default/default/skins/button.xml",
+ "common/default/default/skins/progressBar.xml",
+ "common/default/default/skins/scrollBar.xml",
+ "common/default/default/skins/statictextpaint.xml"
+ };
+
+ return gSkinPaths[se];
+}
+
+void init_skin_anim(const char path[], SkAnimator* anim) {
+ SkASSERT(path && anim);
+
+ SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
+ if (!stream.get()) {
+ SkDEBUGF(("init_skin_anim: loading skin failed <%s>\n", path));
+ sk_throw();
+ }
+
+ if (!anim->decodeStream(stream)) {
+ SkDEBUGF(("init_skin_anim: decoding skin failed <%s>\n", path));
+ sk_throw();
+ }
+}
+
+void init_skin_anim(SkinEnum se, SkAnimator* anim)
+{
+ init_skin_anim(get_skin_enum_path(se), anim);
+}
+
+void init_skin_paint(SkinEnum se, SkPaint* paint)
+{
+ SkASSERT(paint);
+
+ SkAnimator anim;
+ SkCanvas canvas;
+
+ init_skin_anim(se, &anim);
+ anim.draw(&canvas, paint, 0);
+}
+
+void inflate_paint(const SkDOM& dom, const SkDOM::Node* node, SkPaint* paint)
+{
+ SkASSERT(paint);
+
+ SkAnimator anim;
+ SkCanvas canvas;
+
+ if (!anim.decodeDOM(dom, node))
+ {
+ SkDEBUGF(("inflate_paint: decoding dom failed\n"));
+ SkDEBUGCODE(dom.dump(node);)
+ sk_throw();
+ }
+ anim.draw(&canvas, paint, 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+SkWidgetView::SkWidgetView() : SkView(SkView::kFocusable_Mask | SkView::kEnabled_Mask)
+{
+}
+
+const char* SkWidgetView::getLabel() const
+{
+ return fLabel.c_str();
+}
+
+void SkWidgetView::getLabel(SkString* label) const
+{
+ if (label)
+ *label = fLabel;
+}
+
+void SkWidgetView::setLabel(const char label[])
+{
+ this->setLabel(label, label ? strlen(label) : 0);
+}
+
+void SkWidgetView::setLabel(const char label[], size_t len)
+{
+ if ((label == NULL && fLabel.size() != 0) || !fLabel.equals(label, len))
+ {
+ SkString tmp(label, len);
+
+ this->onLabelChange(fLabel.c_str(), tmp.c_str());
+ fLabel.swap(tmp);
+ }
+}
+
+void SkWidgetView::setLabel(const SkString& label)
+{
+ if (fLabel != label)
+ {
+ this->onLabelChange(fLabel.c_str(), label.c_str());
+ fLabel = label;
+ }
+}
+
+bool SkWidgetView::postWidgetEvent()
+{
+ if (!fEvent.isType(""))
+ {
+ SkEvent evt(fEvent); // make a copy since onPrepareWidgetEvent may edit the event
+
+ if (this->onPrepareWidgetEvent(&evt))
+ {
+ SkDEBUGCODE(evt.dump("SkWidgetView::postWidgetEvent");)
+
+ this->postToListeners(evt); // wonder if this should return true if there are > 0 listeners...
+ return true;
+ }
+ }
+ return false;
+}
+
+/*virtual*/ void SkWidgetView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ const char* label = dom.findAttr(node, "label");
+ if (label)
+ this->setLabel(label);
+
+ if ((node = dom.getFirstChild(node, "event")) != NULL)
+ fEvent.inflate(dom, node);
+}
+
+/*virtual*/ void SkWidgetView::onLabelChange(const char oldLabel[], const char newLabel[])
+{
+ this->inval(NULL);
+}
+
+static const char gWidgetEventSinkIDSlotName[] = "sk-widget-sinkid-slot";
+
+/*virtual*/ bool SkWidgetView::onPrepareWidgetEvent(SkEvent* evt)
+{
+ evt->setS32(gWidgetEventSinkIDSlotName, this->getSinkID());
+ return true;
+}
+
+SkEventSinkID SkWidgetView::GetWidgetEventSinkID(const SkEvent& evt)
+{
+ int32_t sinkID;
+
+ return evt.findS32(gWidgetEventSinkIDSlotName, &sinkID) ? (SkEventSinkID)sinkID : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*virtual*/ bool SkButtonView::onEvent(const SkEvent& evt)
+{
+ if (evt.isType(SK_EventType_Key) && evt.getFast32() == kOK_SkKey)
+ {
+ this->postWidgetEvent();
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkCheckButtonView::SkCheckButtonView() : fCheckState(kOff_CheckState)
+{
+}
+
+void SkCheckButtonView::setCheckState(CheckState state)
+{
+ SkASSERT((unsigned)state <= kUnknown_CheckState);
+
+ if (fCheckState != state)
+ {
+ this->onCheckStateChange(this->getCheckState(), state);
+ fCheckState = SkToU8(state);
+ }
+}
+
+/*virtual*/ void SkCheckButtonView::onCheckStateChange(CheckState oldState, CheckState newState)
+{
+ this->inval(NULL);
+}
+
+/*virtual*/ void SkCheckButtonView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
+{
+ this->INHERITED::onInflate(dom, node);
+
+ int index = dom.findList(node, "check-state", "off,on,unknown");
+ if (index >= 0)
+ this->setCheckState((CheckState)index);
+}
+
+static const char gCheckStateSlotName[] = "sk-checkbutton-check-slot";
+
+/*virtual*/ bool SkCheckButtonView::onPrepareWidgetEvent(SkEvent* evt)
+{
+ // could check if we're "disabled", and return false...
+
+ evt->setS32(gCheckStateSlotName, this->getCheckState());
+ return true;
+}
+
+bool SkCheckButtonView::GetWidgetEventCheckState(const SkEvent& evt, CheckState* state)
+{
+ int32_t state32;
+
+ if (evt.findS32(gCheckStateSlotName, &state32))
+ {
+ if (state)
+ *state = (CheckState)state32;
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "SkTime.h"
+#include <stdio.h>
+
+class SkAnimButtonView : public SkButtonView {
+public:
+ SkAnimButtonView()
+ {
+ fAnim.setHostEventSink(this);
+ init_skin_anim(kButton_SkinEnum, &fAnim);
+ }
+
+protected:
+ virtual void onLabelChange(const char oldLabel[], const char newLabel[])
+ {
+ this->INHERITED::onLabelChange(oldLabel, newLabel);
+
+ SkEvent evt("user");
+ evt.setString("id", "setLabel");
+ evt.setString("LABEL", newLabel);
+ fAnim.doUserEvent(evt);
+ }
+
+ virtual void onFocusChange(bool gainFocus)
+ {
+ this->INHERITED::onFocusChange(gainFocus);
+
+ SkEvent evt("user");
+ evt.setString("id", "setFocus");
+ evt.setS32("FOCUS", gainFocus);
+ fAnim.doUserEvent(evt);
+ }
+
+ virtual void onSizeChange()
+ {
+ this->INHERITED::onSizeChange();
+
+ SkEvent evt("user");
+ evt.setString("id", "setDim");
+ evt.setScalar("dimX", this->width());
+ evt.setScalar("dimY", this->height());
+ fAnim.doUserEvent(evt);
+ }
+
+ virtual void onDraw(SkCanvas* canvas)
+ {
+ SkPaint paint;
+ SkAnimator::DifferenceType diff = fAnim.draw(canvas, &paint, SkTime::GetMSecs());
+
+ if (diff == SkAnimator::kDifferent)
+ this->inval(NULL);
+ else if (diff == SkAnimator::kPartiallyDifferent)
+ {
+ SkRect bounds;
+ fAnim.getInvalBounds(&bounds);
+ this->inval(&bounds);
+ }
+ }
+
+ virtual bool onEvent(const SkEvent& evt)
+ {
+ if (evt.isType(SK_EventType_Inval))
+ {
+ this->inval(NULL);
+ return true;
+ }
+ if (evt.isType("recommendDim"))
+ {
+ SkScalar height;
+
+ if (evt.findScalar("y", &height))
+ this->setHeight(height);
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+ }
+
+ virtual bool onPrepareWidgetEvent(SkEvent* evt)
+ {
+ if (this->INHERITED::onPrepareWidgetEvent(evt))
+ {
+ SkEvent e("user");
+ e.setString("id", "handlePress");
+ (void)fAnim.doUserEvent(e);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ SkAnimator fAnim;
+
+ typedef SkButtonView INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////
+
+SkView* SkWidgetFactory(const char name[])
+{
+ if (name == NULL)
+ return NULL;
+
+ // must be in the same order as the SkSkinWidgetEnum is declared
+ static const char* gNames[] = {
+ "sk-border",
+ "sk-button",
+ "sk-image",
+ "sk-list",
+ "sk-progress",
+ "sk-scroll",
+ "sk-text"
+
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gNames); i++)
+ if (!strcmp(gNames[i], name))
+ return SkWidgetFactory((SkWidgetEnum)i);
+
+ return NULL;
+}
+
+#include "SkImageView.h"
+#include "SkProgressBarView.h"
+#include "SkScrollBarView.h"
+#include "SkBorderView.h"
+
+SkView* SkWidgetFactory(SkWidgetEnum sw)
+{
+ switch (sw) {
+ case kBorder_WidgetEnum:
+ return new SkBorderView;
+ case kButton_WidgetEnum:
+ return new SkAnimButtonView;
+ case kImage_WidgetEnum:
+ return new SkImageView;
+ case kList_WidgetEnum:
+ return new SkListView;
+ case kProgress_WidgetEnum:
+ return new SkProgressBarView;
+ case kScroll_WidgetEnum:
+ return new SkScrollBarView;
+ case kText_WidgetEnum:
+ return new SkStaticTextView;
+ default:
+ SkDEBUGFAIL("unknown enum passed to SkWidgetFactory");
+ break;
+ }
+ return NULL;
+}
diff --git a/views/ios/SkOSWindow_iOS.mm b/views/ios/SkOSWindow_iOS.mm
new file mode 100755
index 00000000..04a219b6
--- /dev/null
+++ b/views/ios/SkOSWindow_iOS.mm
@@ -0,0 +1,64 @@
+#import <UIKit/UIKit.h>
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#import "SkEventNotifier.h"
+#include "SkOSMenu.h"
+#include "SkTime.h"
+#include "SkTypes.h"
+#import "SkUIView.h"
+#include "SkWindow.h"
+
+#define kINVAL_UIVIEW_EventType "inval-uiview"
+
+SkOSWindow::SkOSWindow(void* hWnd) : fHWND(hWnd) {
+ fInvalEventIsPending = false;
+ fNotifier = [[SkEventNotifier alloc] init];
+}
+SkOSWindow::~SkOSWindow() {
+ [(SkEventNotifier*)fNotifier release];
+}
+
+void SkOSWindow::onHandleInval(const SkIRect& r) {
+ if (!fInvalEventIsPending) {
+ fInvalEventIsPending = true;
+ (new SkEvent(kINVAL_UIVIEW_EventType, this->getSinkID()))->post();
+ }
+}
+
+bool SkOSWindow::onEvent(const SkEvent& evt) {
+ if (evt.isType(kINVAL_UIVIEW_EventType)) {
+ fInvalEventIsPending = false;
+ const SkIRect& r = this->getDirtyBounds();
+ [(SkUIView*)fHWND postInvalWithRect:&r];
+ return true;
+ }
+ if ([(SkUIView*)fHWND onHandleEvent:evt]) {
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+void SkOSWindow::onSetTitle(const char title[]) {
+ [(SkUIView*)fHWND setSkTitle:title];
+}
+
+void SkOSWindow::onAddMenu(const SkOSMenu* menu) {
+ [(SkUIView*)fHWND onAddMenu:menu];
+}
+
+void SkOSWindow::onUpdateMenu(SkOSMenu* menu) {
+ [(SkUIView*)fHWND onUpdateMenu:menu];
+}
+
+bool SkOSWindow::attach(SkBackEndTypes /* attachType */,
+ int /* msaaSampleCount */,
+ AttachmentInfo* info) {
+ [(SkUIView*)fHWND getAttachmentInfo:info];
+ bool success = true;
+ return success;
+}
+
+void SkOSWindow::detach() {}
+
+void SkOSWindow::present() {
+}
diff --git a/views/mac/SampleApp-Info.plist b/views/mac/SampleApp-Info.plist
new file mode 100644
index 00000000..6b42c2a4
--- /dev/null
+++ b/views/mac/SampleApp-Info.plist
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>com.googlecode.skia.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+ <key>NSMainNibFile</key>
+ <string>SampleApp</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+</dict>
+</plist>
diff --git a/views/mac/SampleApp.xib b/views/mac/SampleApp.xib
new file mode 100644
index 00000000..9549e4e4
--- /dev/null
+++ b/views/mac/SampleApp.xib
@@ -0,0 +1,3962 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
+ <data>
+ <int key="IBDocument.SystemTarget">1060</int>
+ <string key="IBDocument.SystemVersion">10K540</string>
+ <string key="IBDocument.InterfaceBuilderVersion">851</string>
+ <string key="IBDocument.AppKitVersion">1038.36</string>
+ <string key="IBDocument.HIToolboxVersion">461.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">851</string>
+ </object>
+ <array class="NSMutableArray" key="IBDocument.EditedObjectIDs">
+ <integer value="24"/>
+ <integer value="372"/>
+ <integer value="634"/>
+ </array>
+ <array key="IBDocument.PluginDependencies">
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </array>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
+ <object class="NSCustomObject" id="1021">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSCustomObject" id="1014">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1050">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSCustomObject" id="976324537">
+ <string key="NSClassName">SampleAppDelegate</string>
+ </object>
+ <object class="NSCustomObject" id="76290771">
+ <string key="NSClassName">NSWindowController</string>
+ </object>
+ <object class="NSWindowTemplate" id="972006081">
+ <int key="NSWindowStyleMask">15</int>
+ <int key="NSWindowBacking">2</int>
+ <string key="NSWindowRect">{{335, 288}, {640, 480}}</string>
+ <int key="NSWTFlags">1417150464</int>
+ <string key="NSWindowTitle">Sample App</string>
+ <string key="NSWindowClass">NSWindow</string>
+ <nil key="NSViewClass"/>
+ <string key="NSWindowContentMaxSize">{3.40282e+38, 3.40282e+38}</string>
+ <object class="NSView" key="NSWindowView" id="439893737">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">256</int>
+ <string key="NSFrameSize">{640, 480}</string>
+ <reference key="NSSuperview"/>
+ </object>
+ <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string>
+ <string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string>
+ </object>
+ <object class="NSCustomView" id="758604943">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">4352</int>
+ <array class="NSMutableArray" key="NSSubviews">
+ <object class="NSScrollView" id="1038370525">
+ <reference key="NSNextResponder" ref="758604943"/>
+ <int key="NSvFlags">274</int>
+ <array class="NSMutableArray" key="NSSubviews">
+ <object class="NSClipView" id="250930136">
+ <reference key="NSNextResponder" ref="1038370525"/>
+ <int key="NSvFlags">2304</int>
+ <array class="NSMutableArray" key="NSSubviews">
+ <object class="NSTableView" id="429436769">
+ <reference key="NSNextResponder" ref="250930136"/>
+ <int key="NSvFlags">256</int>
+ <string key="NSFrameSize">{339, 319}</string>
+ <reference key="NSSuperview" ref="250930136"/>
+ <bool key="NSEnabled">YES</bool>
+ <object class="_NSCornerView" key="NSCornerView">
+ <nil key="NSNextResponder"/>
+ <int key="NSvFlags">-2147483392</int>
+ <string key="NSFrame">{{224, 0}, {16, 17}}</string>
+ </object>
+ <array class="NSMutableArray" key="NSTableColumns">
+ <object class="NSTableColumn" id="691918008">
+ <string key="NSIdentifier">Labels</string>
+ <double key="NSWidth">100</double>
+ <double key="NSMinWidth">40</double>
+ <double key="NSMaxWidth">1000</double>
+ <object class="NSTableHeaderCell" key="NSHeaderCell">
+ <int key="NSCellFlags">75628096</int>
+ <int key="NSCellFlags2">2048</int>
+ <string key="NSContents"/>
+ <object class="NSFont" key="NSSupport" id="26">
+ <string key="NSName">LucidaGrande</string>
+ <double key="NSSize">11</double>
+ <int key="NSfFlags">3100</int>
+ </object>
+ <object class="NSColor" key="NSBackgroundColor" id="805714581">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
+ </object>
+ <object class="NSColor" key="NSTextColor" id="372600372">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">headerTextColor</string>
+ <object class="NSColor" key="NSColor" id="1032326875">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MAA</bytes>
+ </object>
+ </object>
+ </object>
+ <object class="NSTextFieldCell" key="NSDataCell" id="241301801">
+ <int key="NSCellFlags">68288064</int>
+ <int key="NSCellFlags2">67241216</int>
+ <string key="NSContents">Text Cell</string>
+ <reference key="NSSupport" ref="26"/>
+ <reference key="NSControlView" ref="429436769"/>
+ <object class="NSColor" key="NSBackgroundColor" id="598476436">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">controlBackgroundColor</string>
+ <object class="NSColor" key="NSColor" id="319525538">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC42NjY2NjY2ODY1AA</bytes>
+ </object>
+ </object>
+ <object class="NSColor" key="NSTextColor" id="1055469070">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">controlTextColor</string>
+ <reference key="NSColor" ref="1032326875"/>
+ </object>
+ </object>
+ <int key="NSResizingMask">3</int>
+ <bool key="NSIsResizeable">YES</bool>
+ <bool key="NSIsEditable">YES</bool>
+ <reference key="NSTableView" ref="429436769"/>
+ </object>
+ <object class="NSTableColumn" id="394988372">
+ <string key="NSIdentifier">Controls</string>
+ <double key="NSWidth">233</double>
+ <double key="NSMinWidth">40</double>
+ <double key="NSMaxWidth">1000</double>
+ <object class="NSTableHeaderCell" key="NSHeaderCell">
+ <int key="NSCellFlags">75628096</int>
+ <int key="NSCellFlags2">2048</int>
+ <string key="NSContents"/>
+ <reference key="NSSupport" ref="26"/>
+ <reference key="NSBackgroundColor" ref="805714581"/>
+ <reference key="NSTextColor" ref="372600372"/>
+ </object>
+ <object class="NSTextFieldCell" key="NSDataCell" id="88358594">
+ <int key="NSCellFlags">67239488</int>
+ <int key="NSCellFlags2">272630784</int>
+ <string key="NSContents">Text</string>
+ <object class="NSFont" key="NSSupport">
+ <string key="NSName">LucidaGrande</string>
+ <double key="NSSize">13</double>
+ <int key="NSfFlags">1044</int>
+ </object>
+ <reference key="NSControlView" ref="429436769"/>
+ <object class="NSColor" key="NSBackgroundColor">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">controlColor</string>
+ <reference key="NSColor" ref="319525538"/>
+ </object>
+ <reference key="NSTextColor" ref="1055469070"/>
+ </object>
+ <int key="NSResizingMask">3</int>
+ <bool key="NSIsResizeable">YES</bool>
+ <bool key="NSIsEditable">YES</bool>
+ <reference key="NSTableView" ref="429436769"/>
+ </object>
+ </array>
+ <double key="NSIntercellSpacingWidth">3</double>
+ <double key="NSIntercellSpacingHeight">2</double>
+ <object class="NSColor" key="NSBackgroundColor">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">_sourceListBackgroundColor</string>
+ <object class="NSColor" key="NSColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MC44MzkyMTU2OTU5IDAuODY2NjY2Njc0NiAwLjg5ODAzOTIyMTgAA</bytes>
+ </object>
+ </object>
+ <object class="NSColor" key="NSGridColor">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">gridColor</string>
+ <object class="NSColor" key="NSColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC41AA</bytes>
+ </object>
+ </object>
+ <double key="NSRowHeight">35</double>
+ <int key="NSTvFlags">1128267776</int>
+ <reference key="NSDelegate"/>
+ <reference key="NSDataSource"/>
+ <int key="NSGridStyleMask">2</int>
+ <int key="NSColumnAutoresizingStyle">4</int>
+ <int key="NSDraggingSourceMaskForLocal">15</int>
+ <int key="NSDraggingSourceMaskForNonLocal">0</int>
+ <bool key="NSAllowsTypeSelect">NO</bool>
+ <int key="NSTableViewSelectionHighlightStyle">1</int>
+ <int key="NSTableViewDraggingDestinationStyle">1</int>
+ </object>
+ </array>
+ <string key="NSFrame">{{1, 1}, {339, 319}}</string>
+ <reference key="NSSuperview" ref="1038370525"/>
+ <reference key="NSNextKeyView" ref="429436769"/>
+ <reference key="NSDocView" ref="429436769"/>
+ <reference key="NSBGColor" ref="598476436"/>
+ <int key="NScvFlags">4</int>
+ </object>
+ <object class="NSScroller" id="617550661">
+ <reference key="NSNextResponder" ref="1038370525"/>
+ <int key="NSvFlags">-2147483392</int>
+ <string key="NSFrame">{{317, 1}, {15, 574}}</string>
+ <reference key="NSSuperview" ref="1038370525"/>
+ <reference key="NSTarget" ref="1038370525"/>
+ <string key="NSAction">_doScroller:</string>
+ <double key="NSPercent">0.99687498807907104</double>
+ </object>
+ <object class="NSScroller" id="977018641">
+ <reference key="NSNextResponder" ref="1038370525"/>
+ <int key="NSvFlags">-2147483392</int>
+ <string key="NSFrame">{{1, 263}, {157, 15}}</string>
+ <reference key="NSSuperview" ref="1038370525"/>
+ <int key="NSsFlags">1</int>
+ <reference key="NSTarget" ref="1038370525"/>
+ <string key="NSAction">_doScroller:</string>
+ <double key="NSPercent">0.99705880880355835</double>
+ </object>
+ </array>
+ <string key="NSFrameSize">{341, 321}</string>
+ <reference key="NSSuperview" ref="758604943"/>
+ <reference key="NSNextKeyView" ref="250930136"/>
+ <int key="NSsFlags">562</int>
+ <reference key="NSVScroller" ref="617550661"/>
+ <reference key="NSHScroller" ref="977018641"/>
+ <reference key="NSContentView" ref="250930136"/>
+ <bytes key="NSScrollAmts">QSAAAEEgAABCFAAAQhQAAA</bytes>
+ </object>
+ </array>
+ <string key="NSFrameSize">{341, 321}</string>
+ <reference key="NSSuperview"/>
+ <bool key="NSViewCanDrawConcurrently">YES</bool>
+ <string key="NSClassName">NSView</string>
+ </object>
+ <object class="NSDrawer" id="764451088">
+ <nil key="NSNextResponder"/>
+ <string key="NSContentSize">{300, 100}</string>
+ <string key="NSMinContentSize">{0, 0}</string>
+ <string key="NSMaxContentSize">{10000, 10000}</string>
+ <int key="NSPreferredEdge">2</int>
+ <double key="NSLeadingOffset">0.0</double>
+ <double key="NSTrailingOffset">15</double>
+ <nil key="NSParentWindow"/>
+ <nil key="NSDelegate"/>
+ </object>
+ <object class="NSMenu" id="649796088">
+ <string key="NSTitle">AMainMenu</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="694149608">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">SimpleCocoaApp</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <object class="NSCustomResource" key="NSOnImage" id="35465992">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuCheckmark</string>
+ </object>
+ <object class="NSCustomResource" key="NSMixedImage" id="502551668">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuMixedState</string>
+ </object>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="110575045">
+ <string key="NSTitle">SimpleCocoaApp</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="238522557">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">About SimpleCocoaApp</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="24092627">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="622903446">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Show Options</string>
+ <string key="NSKeyEquiv"></string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="304266470">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="609285721">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Preferences…</string>
+ <string key="NSKeyEquiv">,</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="481834944">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1046388886">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Services</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="752062318">
+ <string key="NSTitle">Services</string>
+ <array class="NSMutableArray" key="NSMenuItems"/>
+ <string key="NSName">_NSServicesMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="646227648">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="755159360">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide SimpleCocoaApp</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="342932134">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide Others</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="908899353">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Show All</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1056857174">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="632727374">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Quit SimpleCocoaApp</string>
+ <string key="NSKeyEquiv">q</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSAppleMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="379814623">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">File</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="720053764">
+ <string key="NSTitle">File</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="705341025">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">New</string>
+ <string key="NSKeyEquiv">n</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="722745758">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open…</string>
+ <string key="NSKeyEquiv">o</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1025936716">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open Recent</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="1065607017">
+ <string key="NSTitle">Open Recent</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="759406840">
+ <reference key="NSMenu" ref="1065607017"/>
+ <string key="NSTitle">Clear Menu</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSRecentDocumentsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="425164168">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="776162233">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Close</string>
+ <string key="NSKeyEquiv">w</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1023925487">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Save</string>
+ <string key="NSKeyEquiv">s</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="117038363">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Save As…</string>
+ <string key="NSKeyEquiv">S</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="579971712">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Revert to Saved</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1010469920">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="294629803">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Page Setup...</string>
+ <string key="NSKeyEquiv">P</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSToolTip"/>
+ </object>
+ <object class="NSMenuItem" id="49223823">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Print…</string>
+ <string key="NSKeyEquiv">p</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="952259628">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Edit</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="789758025">
+ <string key="NSTitle">Edit</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1058277027">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Undo</string>
+ <string key="NSKeyEquiv">z</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="790794224">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Redo</string>
+ <string key="NSKeyEquiv">Z</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1040322652">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="296257095">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Cut</string>
+ <string key="NSKeyEquiv">x</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="860595796">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Copy</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="29853731">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="82994268">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste and Match Style</string>
+ <string key="NSKeyEquiv">V</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="437104165">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Delete</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="583158037">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Select All</string>
+ <string key="NSKeyEquiv">a</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="212016141">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="892235320">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Find</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="963351320">
+ <string key="NSTitle">Find</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="447796847">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find…</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="326711663">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Next</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="270902937">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Previous</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="159080638">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Use Selection for Find</string>
+ <string key="NSKeyEquiv">e</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">7</int>
+ </object>
+ <object class="NSMenuItem" id="88285865">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Jump to Selection</string>
+ <string key="NSKeyEquiv">j</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="972420730">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Spelling and Grammar</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="769623530">
+ <string key="NSTitle">Spelling and Grammar</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="679648819">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Show Spelling and Grammar</string>
+ <string key="NSKeyEquiv">:</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="96193923">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Document Now</string>
+ <string key="NSKeyEquiv">;</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="859480356">
+ <reference key="NSMenu" ref="769623530"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="948374510">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Spelling While Typing</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="967646866">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Grammar With Spelling</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="795346622">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Correct Spelling Automatically</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="507821607">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="698887838">
+ <string key="NSTitle">Substitutions</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="65139061">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Show Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="19036812">
+ <reference key="NSMenu" ref="698887838"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="605118523">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Copy/Paste</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="197661976">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Quotes</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="672708820">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Dashes</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="708854459">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Links</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="537092702">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Text Replacement</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="288088188">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Transformations</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="579392910">
+ <string key="NSTitle">Transformations</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1060694897">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Upper Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="879586729">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Lower Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="56570060">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Capitalize</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="676164635">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Speech</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="785027613">
+ <string key="NSTitle">Speech</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="731782645">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Start Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="680220178">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Stop Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="586577488">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">View</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="466310130">
+ <string key="NSTitle">View</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="87708234">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Show Menu Key Equivalents</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <int key="NSState">1</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="102151532">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Show Toolbar</string>
+ <string key="NSKeyEquiv">t</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="237841660">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Customize Toolbar…</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="302598603">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Format</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="941447902">
+ <string key="NSTitle">Format</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="792887677">
+ <reference key="NSMenu" ref="941447902"/>
+ <string key="NSTitle">Font</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="786677654">
+ <string key="NSTitle">Font</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="159677712">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Show Fonts</string>
+ <string key="NSKeyEquiv">t</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="305399458">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Bold</string>
+ <string key="NSKeyEquiv">b</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="814362025">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Italic</string>
+ <string key="NSKeyEquiv">i</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="330926929">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Underline</string>
+ <string key="NSKeyEquiv">u</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="533507878">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="158063935">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Bigger</string>
+ <string key="NSKeyEquiv">+</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="885547335">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Smaller</string>
+ <string key="NSKeyEquiv">-</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">4</int>
+ </object>
+ <object class="NSMenuItem" id="901062459">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="767671776">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Kern</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="175441468">
+ <string key="NSTitle">Kern</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="252969304">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="766922938">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Use None</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="677519740">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Tighten</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="238351151">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Loosen</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="691570813">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Ligature</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="1058217995">
+ <string key="NSTitle">Ligature</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="706297211">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="568384683">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use None</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="663508465">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use All</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="769124883">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Baseline</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="18263474">
+ <string key="NSTitle">Baseline</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="257962622">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="644725453">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Superscript</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1037576581">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Subscript</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="941806246">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Raise</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1045724900">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Lower</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="739652853">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1012600125">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Show Colors</string>
+ <string key="NSKeyEquiv">C</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="214559597">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="596732606">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Copy Style</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="393423671">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Paste Style</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSFontMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="215659978">
+ <reference key="NSMenu" ref="941447902"/>
+ <string key="NSTitle">Text</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="446991534">
+ <string key="NSTitle">Text</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="875092757">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Align Left</string>
+ <string key="NSKeyEquiv">{</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="630155264">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Center</string>
+ <string key="NSKeyEquiv">|</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="945678886">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Justify</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="512868991">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Align Right</string>
+ <string key="NSKeyEquiv">}</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="163117631">
+ <reference key="NSMenu" ref="446991534"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="31516759">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Writing Direction</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="956096989">
+ <string key="NSTitle">Writing Direction</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="257099033">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <string key="NSTitle">Paragraph</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="551969625">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="249532473">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="607364498">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="508151438">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="981751889">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <string key="NSTitle">Selection</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="380031999">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="825984362">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="560145579">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="908105787">
+ <reference key="NSMenu" ref="446991534"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="644046920">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Show Ruler</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="231811626">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Copy Ruler</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1310720</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="883618387">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Paste Ruler</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1310720</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="713487014">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Window</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="835318025">
+ <string key="NSTitle">Window</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1011231497">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Minimize</string>
+ <string key="NSKeyEquiv">m</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="575023229">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Zoom</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="92029792">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">768 x 1024</string>
+ <string key="NSKeyEquiv">=</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="299356726">
+ <reference key="NSMenu" ref="835318025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="625202149">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Bring All to Front</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSWindowsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="448692316">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Help</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="992780483">
+ <string key="NSTitle">Help</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="105068016">
+ <reference key="NSMenu" ref="992780483"/>
+ <string key="NSTitle">SimpleCocoaApp Help</string>
+ <string key="NSKeyEquiv">?</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSHelpMenu</string>
+ </object>
+ </object>
+ </array>
+ <string key="NSName">_NSMainMenu</string>
+ </object>
+ <object class="NSCustomObject" id="755631768">
+ <string key="NSClassName">NSFontManager</string>
+ </object>
+ </array>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <array class="NSMutableArray" key="connectionRecords">
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performMiniaturize:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1011231497"/>
+ </object>
+ <int key="connectionID">37</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">arrangeInFront:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="625202149"/>
+ </object>
+ <int key="connectionID">39</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">print:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="49223823"/>
+ </object>
+ <int key="connectionID">86</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runPageLayout:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="294629803"/>
+ </object>
+ <int key="connectionID">87</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">clearRecentDocuments:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="759406840"/>
+ </object>
+ <int key="connectionID">127</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontStandardAboutPanel:</string>
+ <reference key="source" ref="1021"/>
+ <reference key="destination" ref="238522557"/>
+ </object>
+ <int key="connectionID">142</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performClose:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="776162233"/>
+ </object>
+ <int key="connectionID">193</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleContinuousSpellChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="948374510"/>
+ </object>
+ <int key="connectionID">222</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">undo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1058277027"/>
+ </object>
+ <int key="connectionID">223</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copy:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="860595796"/>
+ </object>
+ <int key="connectionID">224</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">checkSpelling:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="96193923"/>
+ </object>
+ <int key="connectionID">225</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">paste:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="29853731"/>
+ </object>
+ <int key="connectionID">226</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">stopSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="680220178"/>
+ </object>
+ <int key="connectionID">227</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">cut:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="296257095"/>
+ </object>
+ <int key="connectionID">228</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showGuessPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="679648819"/>
+ </object>
+ <int key="connectionID">230</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">redo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="790794224"/>
+ </object>
+ <int key="connectionID">231</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">selectAll:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="583158037"/>
+ </object>
+ <int key="connectionID">232</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">startSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="731782645"/>
+ </object>
+ <int key="connectionID">233</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">delete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="437104165"/>
+ </object>
+ <int key="connectionID">235</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performZoom:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="575023229"/>
+ </object>
+ <int key="connectionID">240</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="447796847"/>
+ </object>
+ <int key="connectionID">241</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">centerSelectionInVisibleArea:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="88285865"/>
+ </object>
+ <int key="connectionID">245</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleGrammarChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="967646866"/>
+ </object>
+ <int key="connectionID">347</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleSmartInsertDelete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="605118523"/>
+ </object>
+ <int key="connectionID">355</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticQuoteSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="197661976"/>
+ </object>
+ <int key="connectionID">356</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticLinkDetection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="708854459"/>
+ </object>
+ <int key="connectionID">357</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">saveDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1023925487"/>
+ </object>
+ <int key="connectionID">362</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">saveDocumentAs:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="117038363"/>
+ </object>
+ <int key="connectionID">363</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">revertDocumentToSaved:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="579971712"/>
+ </object>
+ <int key="connectionID">364</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runToolbarCustomizationPalette:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="237841660"/>
+ </object>
+ <int key="connectionID">365</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleToolbarShown:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="102151532"/>
+ </object>
+ <int key="connectionID">366</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hide:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="755159360"/>
+ </object>
+ <int key="connectionID">367</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hideOtherApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="342932134"/>
+ </object>
+ <int key="connectionID">368</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">unhideAllApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="908899353"/>
+ </object>
+ <int key="connectionID">370</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">newDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="705341025"/>
+ </object>
+ <int key="connectionID">373</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">openDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="722745758"/>
+ </object>
+ <int key="connectionID">374</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">addFontTrait:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="305399458"/>
+ </object>
+ <int key="connectionID">421</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">addFontTrait:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="814362025"/>
+ </object>
+ <int key="connectionID">422</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">modifyFont:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="885547335"/>
+ </object>
+ <int key="connectionID">423</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontFontPanel:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="159677712"/>
+ </object>
+ <int key="connectionID">424</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">modifyFont:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="158063935"/>
+ </object>
+ <int key="connectionID">425</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">raiseBaseline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="941806246"/>
+ </object>
+ <int key="connectionID">426</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">lowerBaseline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1045724900"/>
+ </object>
+ <int key="connectionID">427</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copyFont:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="596732606"/>
+ </object>
+ <int key="connectionID">428</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">subscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1037576581"/>
+ </object>
+ <int key="connectionID">429</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">superscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="644725453"/>
+ </object>
+ <int key="connectionID">430</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">tightenKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="677519740"/>
+ </object>
+ <int key="connectionID">431</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">underline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="330926929"/>
+ </object>
+ <int key="connectionID">432</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontColorPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1012600125"/>
+ </object>
+ <int key="connectionID">433</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useAllLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="663508465"/>
+ </object>
+ <int key="connectionID">434</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">loosenKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="238351151"/>
+ </object>
+ <int key="connectionID">435</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteFont:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="393423671"/>
+ </object>
+ <int key="connectionID">436</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">unscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="257962622"/>
+ </object>
+ <int key="connectionID">437</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useStandardKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="252969304"/>
+ </object>
+ <int key="connectionID">438</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useStandardLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="706297211"/>
+ </object>
+ <int key="connectionID">439</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">turnOffLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="568384683"/>
+ </object>
+ <int key="connectionID">440</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">turnOffKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="766922938"/>
+ </object>
+ <int key="connectionID">441</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">terminate:</string>
+ <reference key="source" ref="1050"/>
+ <reference key="destination" ref="632727374"/>
+ </object>
+ <int key="connectionID">449</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticSpellingCorrection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="795346622"/>
+ </object>
+ <int key="connectionID">456</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontSubstitutionsPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="65139061"/>
+ </object>
+ <int key="connectionID">458</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticDashSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="672708820"/>
+ </object>
+ <int key="connectionID">461</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticTextReplacement:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="537092702"/>
+ </object>
+ <int key="connectionID">463</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">uppercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1060694897"/>
+ </object>
+ <int key="connectionID">464</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">capitalizeWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="56570060"/>
+ </object>
+ <int key="connectionID">467</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">lowercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="879586729"/>
+ </object>
+ <int key="connectionID">468</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteAsPlainText:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="82994268"/>
+ </object>
+ <int key="connectionID">486</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="326711663"/>
+ </object>
+ <int key="connectionID">487</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="270902937"/>
+ </object>
+ <int key="connectionID">488</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="159080638"/>
+ </object>
+ <int key="connectionID">489</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showHelp:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="105068016"/>
+ </object>
+ <int key="connectionID">493</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignCenter:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="630155264"/>
+ </object>
+ <int key="connectionID">518</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="883618387"/>
+ </object>
+ <int key="connectionID">519</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="644046920"/>
+ </object>
+ <int key="connectionID">520</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="512868991"/>
+ </object>
+ <int key="connectionID">521</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copyRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="231811626"/>
+ </object>
+ <int key="connectionID">522</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignJustified:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="945678886"/>
+ </object>
+ <int key="connectionID">523</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="875092757"/>
+ </object>
+ <int key="connectionID">524</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionNatural:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="551969625"/>
+ </object>
+ <int key="connectionID">525</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionLeftToRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="249532473"/>
+ </object>
+ <int key="connectionID">526</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionRightToLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="607364498"/>
+ </object>
+ <int key="connectionID">527</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionNatural:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="380031999"/>
+ </object>
+ <int key="connectionID">528</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionLeftToRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="825984362"/>
+ </object>
+ <int key="connectionID">529</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionRightToLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="560145579"/>
+ </object>
+ <int key="connectionID">530</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">contentView</string>
+ <reference key="source" ref="764451088"/>
+ <reference key="destination" ref="758604943"/>
+ </object>
+ <int key="connectionID">542</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">parentWindow</string>
+ <reference key="source" ref="764451088"/>
+ <reference key="destination" ref="972006081"/>
+ </object>
+ <int key="connectionID">651</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="1021"/>
+ <reference key="destination" ref="976324537"/>
+ </object>
+ <int key="connectionID">656</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">fOptionsDelegate</string>
+ <reference key="source" ref="439893737"/>
+ <reference key="destination" ref="429436769"/>
+ </object>
+ <int key="connectionID">667</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">fWindow</string>
+ <reference key="source" ref="976324537"/>
+ <reference key="destination" ref="972006081"/>
+ </object>
+ <int key="connectionID">673</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">fOptions</string>
+ <reference key="source" ref="976324537"/>
+ <reference key="destination" ref="429436769"/>
+ </object>
+ <int key="connectionID">674</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">fView</string>
+ <reference key="source" ref="976324537"/>
+ <reference key="destination" ref="439893737"/>
+ </object>
+ <int key="connectionID">682</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggle:</string>
+ <reference key="source" ref="764451088"/>
+ <reference key="destination" ref="622903446"/>
+ </object>
+ <int key="connectionID">707</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleKeyEquivalents:</string>
+ <reference key="source" ref="429436769"/>
+ <reference key="destination" ref="87708234"/>
+ </object>
+ <int key="connectionID">719</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toiPadSize:</string>
+ <reference key="source" ref="976324537"/>
+ <reference key="destination" ref="92029792"/>
+ </object>
+ <int key="connectionID">721</int>
+ </object>
+ </array>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <array key="orderedObjects">
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <array key="object" id="0"/>
+ <reference key="children" ref="1048"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1021"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1014"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1050"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">29</int>
+ <reference key="object" ref="649796088"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="713487014"/>
+ <reference ref="694149608"/>
+ <reference ref="952259628"/>
+ <reference ref="379814623"/>
+ <reference ref="586577488"/>
+ <reference ref="448692316"/>
+ <reference ref="302598603"/>
+ </array>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">19</int>
+ <reference key="object" ref="713487014"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="835318025"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">56</int>
+ <reference key="object" ref="694149608"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="110575045"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">217</int>
+ <reference key="object" ref="952259628"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="789758025"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">83</int>
+ <reference key="object" ref="379814623"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="720053764"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">81</int>
+ <reference key="object" ref="720053764"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1023925487"/>
+ <reference ref="117038363"/>
+ <reference ref="49223823"/>
+ <reference ref="722745758"/>
+ <reference ref="705341025"/>
+ <reference ref="1025936716"/>
+ <reference ref="294629803"/>
+ <reference ref="776162233"/>
+ <reference ref="425164168"/>
+ <reference ref="579971712"/>
+ <reference ref="1010469920"/>
+ </array>
+ <reference key="parent" ref="379814623"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">75</int>
+ <reference key="object" ref="1023925487"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">80</int>
+ <reference key="object" ref="117038363"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">78</int>
+ <reference key="object" ref="49223823"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">72</int>
+ <reference key="object" ref="722745758"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">82</int>
+ <reference key="object" ref="705341025"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">124</int>
+ <reference key="object" ref="1025936716"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1065607017"/>
+ </array>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">77</int>
+ <reference key="object" ref="294629803"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">73</int>
+ <reference key="object" ref="776162233"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">79</int>
+ <reference key="object" ref="425164168"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">112</int>
+ <reference key="object" ref="579971712"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">74</int>
+ <reference key="object" ref="1010469920"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">125</int>
+ <reference key="object" ref="1065607017"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="759406840"/>
+ </array>
+ <reference key="parent" ref="1025936716"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">126</int>
+ <reference key="object" ref="759406840"/>
+ <reference key="parent" ref="1065607017"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">205</int>
+ <reference key="object" ref="789758025"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="437104165"/>
+ <reference ref="583158037"/>
+ <reference ref="1058277027"/>
+ <reference ref="212016141"/>
+ <reference ref="296257095"/>
+ <reference ref="29853731"/>
+ <reference ref="860595796"/>
+ <reference ref="1040322652"/>
+ <reference ref="790794224"/>
+ <reference ref="892235320"/>
+ <reference ref="972420730"/>
+ <reference ref="676164635"/>
+ <reference ref="507821607"/>
+ <reference ref="288088188"/>
+ <reference ref="82994268"/>
+ </array>
+ <reference key="parent" ref="952259628"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">202</int>
+ <reference key="object" ref="437104165"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">198</int>
+ <reference key="object" ref="583158037"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">207</int>
+ <reference key="object" ref="1058277027"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">214</int>
+ <reference key="object" ref="212016141"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">199</int>
+ <reference key="object" ref="296257095"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">203</int>
+ <reference key="object" ref="29853731"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">197</int>
+ <reference key="object" ref="860595796"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">206</int>
+ <reference key="object" ref="1040322652"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">215</int>
+ <reference key="object" ref="790794224"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">218</int>
+ <reference key="object" ref="892235320"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="963351320"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">216</int>
+ <reference key="object" ref="972420730"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="769623530"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">200</int>
+ <reference key="object" ref="769623530"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="948374510"/>
+ <reference ref="96193923"/>
+ <reference ref="679648819"/>
+ <reference ref="967646866"/>
+ <reference ref="859480356"/>
+ <reference ref="795346622"/>
+ </array>
+ <reference key="parent" ref="972420730"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">219</int>
+ <reference key="object" ref="948374510"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">201</int>
+ <reference key="object" ref="96193923"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">204</int>
+ <reference key="object" ref="679648819"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">220</int>
+ <reference key="object" ref="963351320"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="270902937"/>
+ <reference ref="88285865"/>
+ <reference ref="159080638"/>
+ <reference ref="326711663"/>
+ <reference ref="447796847"/>
+ </array>
+ <reference key="parent" ref="892235320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">213</int>
+ <reference key="object" ref="270902937"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">210</int>
+ <reference key="object" ref="88285865"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">221</int>
+ <reference key="object" ref="159080638"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">208</int>
+ <reference key="object" ref="326711663"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">209</int>
+ <reference key="object" ref="447796847"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">57</int>
+ <reference key="object" ref="110575045"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="238522557"/>
+ <reference ref="755159360"/>
+ <reference ref="908899353"/>
+ <reference ref="632727374"/>
+ <reference ref="646227648"/>
+ <reference ref="609285721"/>
+ <reference ref="481834944"/>
+ <reference ref="304266470"/>
+ <reference ref="1046388886"/>
+ <reference ref="1056857174"/>
+ <reference ref="342932134"/>
+ <reference ref="622903446"/>
+ <reference ref="24092627"/>
+ </array>
+ <reference key="parent" ref="694149608"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">58</int>
+ <reference key="object" ref="238522557"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">134</int>
+ <reference key="object" ref="755159360"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">150</int>
+ <reference key="object" ref="908899353"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">136</int>
+ <reference key="object" ref="632727374"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">144</int>
+ <reference key="object" ref="646227648"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">129</int>
+ <reference key="object" ref="609285721"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">143</int>
+ <reference key="object" ref="481834944"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">236</int>
+ <reference key="object" ref="304266470"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">131</int>
+ <reference key="object" ref="1046388886"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="752062318"/>
+ </array>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">149</int>
+ <reference key="object" ref="1056857174"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">145</int>
+ <reference key="object" ref="342932134"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">130</int>
+ <reference key="object" ref="752062318"/>
+ <reference key="parent" ref="1046388886"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">24</int>
+ <reference key="object" ref="835318025"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="299356726"/>
+ <reference ref="625202149"/>
+ <reference ref="575023229"/>
+ <reference ref="1011231497"/>
+ <reference ref="92029792"/>
+ </array>
+ <reference key="parent" ref="713487014"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">92</int>
+ <reference key="object" ref="299356726"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">5</int>
+ <reference key="object" ref="625202149"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">239</int>
+ <reference key="object" ref="575023229"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">23</int>
+ <reference key="object" ref="1011231497"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">295</int>
+ <reference key="object" ref="586577488"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="466310130"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">296</int>
+ <reference key="object" ref="466310130"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="102151532"/>
+ <reference ref="237841660"/>
+ <reference ref="87708234"/>
+ </array>
+ <reference key="parent" ref="586577488"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">297</int>
+ <reference key="object" ref="102151532"/>
+ <reference key="parent" ref="466310130"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">298</int>
+ <reference key="object" ref="237841660"/>
+ <reference key="parent" ref="466310130"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">211</int>
+ <reference key="object" ref="676164635"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="785027613"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">212</int>
+ <reference key="object" ref="785027613"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="680220178"/>
+ <reference ref="731782645"/>
+ </array>
+ <reference key="parent" ref="676164635"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">195</int>
+ <reference key="object" ref="680220178"/>
+ <reference key="parent" ref="785027613"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">196</int>
+ <reference key="object" ref="731782645"/>
+ <reference key="parent" ref="785027613"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">346</int>
+ <reference key="object" ref="967646866"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">348</int>
+ <reference key="object" ref="507821607"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="698887838"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">349</int>
+ <reference key="object" ref="698887838"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="605118523"/>
+ <reference ref="197661976"/>
+ <reference ref="708854459"/>
+ <reference ref="65139061"/>
+ <reference ref="19036812"/>
+ <reference ref="672708820"/>
+ <reference ref="537092702"/>
+ </array>
+ <reference key="parent" ref="507821607"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">350</int>
+ <reference key="object" ref="605118523"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">351</int>
+ <reference key="object" ref="197661976"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">354</int>
+ <reference key="object" ref="708854459"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">371</int>
+ <reference key="object" ref="972006081"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="439893737"/>
+ </array>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">372</int>
+ <reference key="object" ref="439893737"/>
+ <array class="NSMutableArray" key="children"/>
+ <reference key="parent" ref="972006081"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">375</int>
+ <reference key="object" ref="302598603"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="941447902"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">376</int>
+ <reference key="object" ref="941447902"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="792887677"/>
+ <reference ref="215659978"/>
+ </array>
+ <reference key="parent" ref="302598603"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">377</int>
+ <reference key="object" ref="792887677"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="786677654"/>
+ </array>
+ <reference key="parent" ref="941447902"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">388</int>
+ <reference key="object" ref="786677654"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="159677712"/>
+ <reference ref="305399458"/>
+ <reference ref="814362025"/>
+ <reference ref="330926929"/>
+ <reference ref="533507878"/>
+ <reference ref="158063935"/>
+ <reference ref="885547335"/>
+ <reference ref="901062459"/>
+ <reference ref="767671776"/>
+ <reference ref="691570813"/>
+ <reference ref="769124883"/>
+ <reference ref="739652853"/>
+ <reference ref="1012600125"/>
+ <reference ref="214559597"/>
+ <reference ref="596732606"/>
+ <reference ref="393423671"/>
+ </array>
+ <reference key="parent" ref="792887677"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">389</int>
+ <reference key="object" ref="159677712"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">390</int>
+ <reference key="object" ref="305399458"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">391</int>
+ <reference key="object" ref="814362025"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">392</int>
+ <reference key="object" ref="330926929"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">393</int>
+ <reference key="object" ref="533507878"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">394</int>
+ <reference key="object" ref="158063935"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">395</int>
+ <reference key="object" ref="885547335"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">396</int>
+ <reference key="object" ref="901062459"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">397</int>
+ <reference key="object" ref="767671776"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="175441468"/>
+ </array>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">398</int>
+ <reference key="object" ref="691570813"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1058217995"/>
+ </array>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">399</int>
+ <reference key="object" ref="769124883"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="18263474"/>
+ </array>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">400</int>
+ <reference key="object" ref="739652853"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">401</int>
+ <reference key="object" ref="1012600125"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">402</int>
+ <reference key="object" ref="214559597"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">403</int>
+ <reference key="object" ref="596732606"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">404</int>
+ <reference key="object" ref="393423671"/>
+ <reference key="parent" ref="786677654"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">405</int>
+ <reference key="object" ref="18263474"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="257962622"/>
+ <reference ref="644725453"/>
+ <reference ref="1037576581"/>
+ <reference ref="941806246"/>
+ <reference ref="1045724900"/>
+ </array>
+ <reference key="parent" ref="769124883"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">406</int>
+ <reference key="object" ref="257962622"/>
+ <reference key="parent" ref="18263474"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">407</int>
+ <reference key="object" ref="644725453"/>
+ <reference key="parent" ref="18263474"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">408</int>
+ <reference key="object" ref="1037576581"/>
+ <reference key="parent" ref="18263474"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">409</int>
+ <reference key="object" ref="941806246"/>
+ <reference key="parent" ref="18263474"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">410</int>
+ <reference key="object" ref="1045724900"/>
+ <reference key="parent" ref="18263474"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">411</int>
+ <reference key="object" ref="1058217995"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="706297211"/>
+ <reference ref="568384683"/>
+ <reference ref="663508465"/>
+ </array>
+ <reference key="parent" ref="691570813"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">412</int>
+ <reference key="object" ref="706297211"/>
+ <reference key="parent" ref="1058217995"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">413</int>
+ <reference key="object" ref="568384683"/>
+ <reference key="parent" ref="1058217995"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">414</int>
+ <reference key="object" ref="663508465"/>
+ <reference key="parent" ref="1058217995"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">415</int>
+ <reference key="object" ref="175441468"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="252969304"/>
+ <reference ref="766922938"/>
+ <reference ref="677519740"/>
+ <reference ref="238351151"/>
+ </array>
+ <reference key="parent" ref="767671776"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">416</int>
+ <reference key="object" ref="252969304"/>
+ <reference key="parent" ref="175441468"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">417</int>
+ <reference key="object" ref="766922938"/>
+ <reference key="parent" ref="175441468"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">418</int>
+ <reference key="object" ref="677519740"/>
+ <reference key="parent" ref="175441468"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">419</int>
+ <reference key="object" ref="238351151"/>
+ <reference key="parent" ref="175441468"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">420</int>
+ <reference key="object" ref="755631768"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">450</int>
+ <reference key="object" ref="288088188"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="579392910"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">451</int>
+ <reference key="object" ref="579392910"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1060694897"/>
+ <reference ref="879586729"/>
+ <reference ref="56570060"/>
+ </array>
+ <reference key="parent" ref="288088188"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">452</int>
+ <reference key="object" ref="1060694897"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">453</int>
+ <reference key="object" ref="859480356"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">454</int>
+ <reference key="object" ref="795346622"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">457</int>
+ <reference key="object" ref="65139061"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">459</int>
+ <reference key="object" ref="19036812"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">460</int>
+ <reference key="object" ref="672708820"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">462</int>
+ <reference key="object" ref="537092702"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">465</int>
+ <reference key="object" ref="879586729"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">466</int>
+ <reference key="object" ref="56570060"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">485</int>
+ <reference key="object" ref="82994268"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">490</int>
+ <reference key="object" ref="448692316"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="992780483"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">491</int>
+ <reference key="object" ref="992780483"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="105068016"/>
+ </array>
+ <reference key="parent" ref="448692316"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">492</int>
+ <reference key="object" ref="105068016"/>
+ <reference key="parent" ref="992780483"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">496</int>
+ <reference key="object" ref="215659978"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="446991534"/>
+ </array>
+ <reference key="parent" ref="941447902"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">497</int>
+ <reference key="object" ref="446991534"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="875092757"/>
+ <reference ref="630155264"/>
+ <reference ref="945678886"/>
+ <reference ref="512868991"/>
+ <reference ref="163117631"/>
+ <reference ref="31516759"/>
+ <reference ref="908105787"/>
+ <reference ref="644046920"/>
+ <reference ref="231811626"/>
+ <reference ref="883618387"/>
+ </array>
+ <reference key="parent" ref="215659978"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">498</int>
+ <reference key="object" ref="875092757"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">499</int>
+ <reference key="object" ref="630155264"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">500</int>
+ <reference key="object" ref="945678886"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">501</int>
+ <reference key="object" ref="512868991"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">502</int>
+ <reference key="object" ref="163117631"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">503</int>
+ <reference key="object" ref="31516759"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="956096989"/>
+ </array>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">504</int>
+ <reference key="object" ref="908105787"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">505</int>
+ <reference key="object" ref="644046920"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">506</int>
+ <reference key="object" ref="231811626"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">507</int>
+ <reference key="object" ref="883618387"/>
+ <reference key="parent" ref="446991534"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">508</int>
+ <reference key="object" ref="956096989"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="257099033"/>
+ <reference ref="551969625"/>
+ <reference ref="249532473"/>
+ <reference ref="607364498"/>
+ <reference ref="508151438"/>
+ <reference ref="981751889"/>
+ <reference ref="380031999"/>
+ <reference ref="825984362"/>
+ <reference ref="560145579"/>
+ </array>
+ <reference key="parent" ref="31516759"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">509</int>
+ <reference key="object" ref="257099033"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">510</int>
+ <reference key="object" ref="551969625"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">511</int>
+ <reference key="object" ref="249532473"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">512</int>
+ <reference key="object" ref="607364498"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">513</int>
+ <reference key="object" ref="508151438"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">514</int>
+ <reference key="object" ref="981751889"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">515</int>
+ <reference key="object" ref="380031999"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">516</int>
+ <reference key="object" ref="825984362"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">517</int>
+ <reference key="object" ref="560145579"/>
+ <reference key="parent" ref="956096989"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">538</int>
+ <reference key="object" ref="758604943"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1038370525"/>
+ </array>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Drawer Content View</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">539</int>
+ <reference key="object" ref="764451088"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">629</int>
+ <reference key="object" ref="1038370525"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="617550661"/>
+ <reference ref="977018641"/>
+ <reference ref="429436769"/>
+ </array>
+ <reference key="parent" ref="758604943"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">630</int>
+ <reference key="object" ref="617550661"/>
+ <reference key="parent" ref="1038370525"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">631</int>
+ <reference key="object" ref="977018641"/>
+ <reference key="parent" ref="1038370525"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">632</int>
+ <reference key="object" ref="429436769"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="691918008"/>
+ <reference ref="394988372"/>
+ </array>
+ <reference key="parent" ref="1038370525"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">634</int>
+ <reference key="object" ref="691918008"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="241301801"/>
+ </array>
+ <reference key="parent" ref="429436769"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">637</int>
+ <reference key="object" ref="241301801"/>
+ <reference key="parent" ref="691918008"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">494</int>
+ <reference key="object" ref="976324537"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">661</int>
+ <reference key="object" ref="76290771"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">635</int>
+ <reference key="object" ref="394988372"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="88358594"/>
+ </array>
+ <reference key="parent" ref="429436769"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">698</int>
+ <reference key="object" ref="88358594"/>
+ <reference key="parent" ref="394988372"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">705</int>
+ <reference key="object" ref="622903446"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">706</int>
+ <reference key="object" ref="24092627"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">718</int>
+ <reference key="object" ref="87708234"/>
+ <reference key="parent" ref="466310130"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">720</int>
+ <reference key="object" ref="92029792"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ </array>
+ </object>
+ <dictionary class="NSMutableDictionary" key="flattenedProperties">
+ <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="112.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="112.ImportedFromIB2"/>
+ <string key="124.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="124.ImportedFromIB2"/>
+ <string key="125.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="125.ImportedFromIB2"/>
+ <string key="125.editorWindowContentRectSynchronizationRect">{{522, 812}, {146, 23}}</string>
+ <string key="126.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="126.ImportedFromIB2"/>
+ <string key="129.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="129.ImportedFromIB2"/>
+ <string key="130.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="130.ImportedFromIB2"/>
+ <string key="130.editorWindowContentRectSynchronizationRect">{{436, 809}, {64, 6}}</string>
+ <string key="131.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="131.ImportedFromIB2"/>
+ <string key="134.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="134.ImportedFromIB2"/>
+ <string key="136.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="136.ImportedFromIB2"/>
+ <string key="143.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="143.ImportedFromIB2"/>
+ <string key="144.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="144.ImportedFromIB2"/>
+ <string key="145.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="145.ImportedFromIB2"/>
+ <string key="149.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="149.ImportedFromIB2"/>
+ <string key="150.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="150.ImportedFromIB2"/>
+ <string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="19.ImportedFromIB2"/>
+ <string key="195.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="195.ImportedFromIB2"/>
+ <string key="196.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="196.ImportedFromIB2"/>
+ <string key="197.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="197.ImportedFromIB2"/>
+ <string key="198.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="198.ImportedFromIB2"/>
+ <string key="199.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="199.ImportedFromIB2"/>
+ <string key="200.IBEditorWindowLastContentRect">{{753, 187}, {275, 113}}</string>
+ <string key="200.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="200.ImportedFromIB2"/>
+ <string key="200.editorWindowContentRectSynchronizationRect">{{608, 612}, {275, 83}}</string>
+ <string key="201.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="201.ImportedFromIB2"/>
+ <string key="202.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="202.ImportedFromIB2"/>
+ <string key="203.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="203.ImportedFromIB2"/>
+ <string key="204.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="204.ImportedFromIB2"/>
+ <string key="205.IBEditorWindowLastContentRect">{{547, 216}, {254, 283}}</string>
+ <string key="205.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="205.ImportedFromIB2"/>
+ <string key="205.editorWindowContentRectSynchronizationRect">{{187, 434}, {243, 243}}</string>
+ <string key="206.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="206.ImportedFromIB2"/>
+ <string key="207.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="207.ImportedFromIB2"/>
+ <string key="208.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="208.ImportedFromIB2"/>
+ <string key="209.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="209.ImportedFromIB2"/>
+ <string key="210.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="210.ImportedFromIB2"/>
+ <string key="211.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="211.ImportedFromIB2"/>
+ <string key="212.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="212.ImportedFromIB2"/>
+ <string key="212.editorWindowContentRectSynchronizationRect">{{608, 612}, {167, 43}}</string>
+ <string key="213.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="213.ImportedFromIB2"/>
+ <string key="214.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="214.ImportedFromIB2"/>
+ <string key="215.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="215.ImportedFromIB2"/>
+ <string key="216.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="216.ImportedFromIB2"/>
+ <string key="217.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="217.ImportedFromIB2"/>
+ <string key="218.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="218.ImportedFromIB2"/>
+ <string key="219.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="219.ImportedFromIB2"/>
+ <string key="220.IBEditorWindowLastContentRect">{{753, 217}, {238, 103}}</string>
+ <string key="220.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="220.ImportedFromIB2"/>
+ <string key="220.editorWindowContentRectSynchronizationRect">{{608, 612}, {241, 103}}</string>
+ <string key="221.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="221.ImportedFromIB2"/>
+ <string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="23.ImportedFromIB2"/>
+ <string key="236.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="236.ImportedFromIB2"/>
+ <string key="239.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="239.ImportedFromIB2"/>
+ <string key="24.IBEditorWindowLastContentRect">{{707, 406}, {194, 93}}</string>
+ <string key="24.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="24.ImportedFromIB2"/>
+ <string key="24.editorWindowContentRectSynchronizationRect">{{525, 802}, {197, 73}}</string>
+ <string key="29.IBEditorWindowLastContentRect">{{354, 499}, {485, 20}}</string>
+ <string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="29.ImportedFromIB2"/>
+ <string key="29.WindowOrigin">{74, 862}</string>
+ <string key="29.editorWindowContentRectSynchronizationRect">{{6, 978}, {478, 20}}</string>
+ <string key="295.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="296.IBEditorWindowLastContentRect">{{591, 436}, {276, 63}}</string>
+ <string key="296.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="296.editorWindowContentRectSynchronizationRect">{{475, 832}, {234, 43}}</string>
+ <string key="297.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="298.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="346.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="346.ImportedFromIB2"/>
+ <string key="348.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="348.ImportedFromIB2"/>
+ <string key="349.IBEditorWindowLastContentRect">{{746, 287}, {220, 133}}</string>
+ <string key="349.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="349.ImportedFromIB2"/>
+ <string key="349.editorWindowContentRectSynchronizationRect">{{608, 612}, {215, 63}}</string>
+ <string key="350.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="350.ImportedFromIB2"/>
+ <string key="351.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="351.ImportedFromIB2"/>
+ <string key="354.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="354.ImportedFromIB2"/>
+ <string key="371.IBEditorWindowLastContentRect">{{254, 23}, {640, 480}}</string>
+ <string key="371.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="371.IBWindowTemplateEditedContentRect">{{254, 23}, {640, 480}}</string>
+ <integer value="1" key="371.NSWindowTemplate.visibleAtLaunch"/>
+ <string key="371.editorWindowContentRectSynchronizationRect">{{33, 99}, {480, 360}}</string>
+ <string key="371.windowTemplate.maxSize">{3.40282e+38, 3.40282e+38}</string>
+ <string key="371.windowTemplate.minSize">{0, 0}</string>
+ <string key="372.CustomClassName">SkSampleNSView</string>
+ <string key="372.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <object class="NSAffineTransform" key="372.IBViewBoundsToFrameTransform"/>
+ <string key="375.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="376.IBEditorWindowLastContentRect">{{591, 456}, {83, 43}}</string>
+ <string key="376.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="377.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="388.IBEditorWindowLastContentRect">{{523, 2}, {178, 283}}</string>
+ <string key="388.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="389.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="390.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="391.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="392.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="393.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="394.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="395.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="396.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="397.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="398.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="399.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="400.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="401.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="402.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="403.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="404.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="405.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="406.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="407.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="408.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="409.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="410.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="411.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="412.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="413.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="414.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="415.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="416.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="417.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="418.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="419.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="450.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="451.IBEditorWindowLastContentRect">{{753, 197}, {170, 63}}</string>
+ <string key="451.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="452.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="453.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="454.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="457.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="459.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="460.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="462.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="465.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="466.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="485.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="490.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="491.IBEditorWindowLastContentRect">{{778, 476}, {221, 23}}</string>
+ <string key="491.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="492.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="496.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="497.IBEditorWindowLastContentRect">{{674, 260}, {204, 183}}</string>
+ <string key="497.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="498.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="499.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="5.ImportedFromIB2"/>
+ <string key="500.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="501.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="502.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="503.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="504.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="505.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="506.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="507.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="508.IBEditorWindowLastContentRect">{{878, 180}, {164, 173}}</string>
+ <string key="508.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="509.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="510.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="511.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="512.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="513.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="514.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="515.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="516.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="517.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="538.IBEditorWindowLastContentRect">{{136, 685}, {341, 321}}</string>
+ <string key="538.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="539.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="56.ImportedFromIB2"/>
+ <string key="57.IBEditorWindowLastContentRect">{{366, 286}, {254, 213}}</string>
+ <string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="57.ImportedFromIB2"/>
+ <string key="57.editorWindowContentRectSynchronizationRect">{{23, 794}, {245, 183}}</string>
+ <string key="58.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="58.ImportedFromIB2"/>
+ <string key="629.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <object class="NSAffineTransform" key="629.IBViewBoundsToFrameTransform">
+ <bytes key="NSTransformStruct">P4AAAL+AAAAAAAAAw5+AAA</bytes>
+ </object>
+ <string key="630.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="631.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="632.CustomClassName">SkOptionsTableView</string>
+ <string key="632.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <object class="NSAffineTransform" key="632.IBViewBoundsToFrameTransform"/>
+ <string key="634.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="635.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="637.CustomClassName">SkTextFieldCell</string>
+ <string key="637.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="661.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="698.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="705.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="706.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="718.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="72.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="72.ImportedFromIB2"/>
+ <string key="720.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="73.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="73.ImportedFromIB2"/>
+ <string key="74.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="74.ImportedFromIB2"/>
+ <string key="75.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="75.ImportedFromIB2"/>
+ <string key="77.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="77.ImportedFromIB2"/>
+ <string key="78.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="78.ImportedFromIB2"/>
+ <string key="79.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="79.ImportedFromIB2"/>
+ <string key="80.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="80.ImportedFromIB2"/>
+ <string key="81.IBEditorWindowLastContentRect">{{505, 296}, {196, 203}}</string>
+ <string key="81.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="81.ImportedFromIB2"/>
+ <string key="81.editorWindowContentRectSynchronizationRect">{{145, 474}, {199, 203}}</string>
+ <string key="82.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="82.ImportedFromIB2"/>
+ <string key="83.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="83.ImportedFromIB2"/>
+ <string key="92.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <integer value="1" key="92.ImportedFromIB2"/>
+ </dictionary>
+ <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
+ <nil key="activeLocalization"/>
+ <dictionary class="NSMutableDictionary" key="localizations"/>
+ <nil key="sourceID"/>
+ <int key="maxID">721</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <array class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <object class="IBPartialClassDescription">
+ <string key="className">SampleAppDelegate</string>
+ <string key="superclassName">NSObject</string>
+ <object class="NSMutableDictionary" key="actions">
+ <string key="NS.key.0">toiPadSize:</string>
+ <string key="NS.object.0">id</string>
+ </object>
+ <object class="NSMutableDictionary" key="actionInfosByName">
+ <string key="NS.key.0">toiPadSize:</string>
+ <object class="IBActionInfo" key="NS.object.0">
+ <string key="name">toiPadSize:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ </object>
+ <dictionary class="NSMutableDictionary" key="outlets">
+ <string key="fOptions">SkOptionsTableView</string>
+ <string key="fView">SkNSView</string>
+ <string key="fWindow">NSWindow</string>
+ </dictionary>
+ <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <object class="IBToOneOutletInfo" key="fOptions">
+ <string key="name">fOptions</string>
+ <string key="candidateClassName">SkOptionsTableView</string>
+ </object>
+ <object class="IBToOneOutletInfo" key="fView">
+ <string key="name">fView</string>
+ <string key="candidateClassName">SkNSView</string>
+ </object>
+ <object class="IBToOneOutletInfo" key="fWindow">
+ <string key="name">fWindow</string>
+ <string key="candidateClassName">NSWindow</string>
+ </object>
+ </dictionary>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">../../src/utils/mac/SampleAppDelegate.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">SkNSView</string>
+ <string key="superclassName">NSView</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <string key="NS.key.0">fOptionsDelegate</string>
+ <string key="NS.object.0">id</string>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <string key="NS.key.0">fOptionsDelegate</string>
+ <object class="IBToOneOutletInfo" key="NS.object.0">
+ <string key="name">fOptionsDelegate</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">../../src/utils/mac/SkNSView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">SkOptionsTableView</string>
+ <string key="superclassName">NSTableView</string>
+ <object class="NSMutableDictionary" key="actions">
+ <string key="NS.key.0">toggleKeyEquivalents:</string>
+ <string key="NS.object.0">id</string>
+ </object>
+ <object class="NSMutableDictionary" key="actionInfosByName">
+ <string key="NS.key.0">toggleKeyEquivalents:</string>
+ <object class="IBActionInfo" key="NS.object.0">
+ <string key="name">toggleKeyEquivalents:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">../../src/utils/mac/SkOptionsTableView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">SkSampleNSView</string>
+ <string key="superclassName">SkNSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">../../src/utils/mac/SkSampleNSView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">SkTextFieldCell</string>
+ <string key="superclassName">NSTextFieldCell</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">../../src/utils/mac/SkTextFieldCell.h</string>
+ </object>
+ </object>
+ </array>
+ <array class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
+ <object class="IBPartialClassDescription">
+ <string key="className">NSFormatter</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSFormatter.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSArchiver.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSClassDescription.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSError.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSObjectScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSPortCoder.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptClassDescription.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptKeyValueCoding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptObjectSpecifiers.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptWhoseTests.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURLDownload.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">PrintCore.framework/Headers/PDEPluginInterface.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CAAnimation.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CALayer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CIImageProvider.h</string>
+ </object>
+ </object>
+ </array>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <string key="IBDocument.LastKnownRelativeProjectPath">../../../out/gyp/SampleApp.xcodeproj</string>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+ <string key="NSMenuCheckmark">{9, 8}</string>
+ <string key="NSMenuMixedState">{7, 2}</string>
+ </dictionary>
+ </data>
+</archive>
diff --git a/views/mac/SampleAppDelegate.h b/views/mac/SampleAppDelegate.h
new file mode 100644
index 00000000..8f2bead7
--- /dev/null
+++ b/views/mac/SampleAppDelegate.h
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#import <Cocoa/Cocoa.h>
+#import "SkSampleNSView.h"
+#import "SkOptionsTableView.h"
+@interface SampleAppDelegate : NSObject <NSApplicationDelegate> {
+ NSWindow* fWindow;
+ SkSampleNSView* fView;
+ SkOptionsTableView* fOptions;
+}
+
+@property (assign) IBOutlet NSWindow* fWindow;
+@property (assign) IBOutlet SkSampleNSView* fView;
+@property (assign) IBOutlet SkOptionsTableView* fOptions;
+
+- (IBAction)toiPadSize:(id)sender;
+@end
diff --git a/views/mac/SampleAppDelegate.mm b/views/mac/SampleAppDelegate.mm
new file mode 100644
index 00000000..91dcada8
--- /dev/null
+++ b/views/mac/SampleAppDelegate.mm
@@ -0,0 +1,16 @@
+#import "SampleAppDelegate.h"
+@implementation SampleAppDelegate
+@synthesize fWindow, fView, fOptions;
+
+-(void) applicationDidFinishLaunching:(NSNotification *)aNotification {
+ //Load specified skia views after launching
+ fView.fOptionsDelegate = fOptions;
+ [fWindow setAcceptsMouseMovedEvents:YES];
+ [fOptions registerMenus:fView.fWind->getMenus()];
+}
+
+- (IBAction)toiPadSize:(id)sender {
+ NSRect frame = NSMakeRect(fWindow.frame.origin.x, fWindow.frame.origin.y, 768, 1024);
+ [fWindow setFrame:frame display:YES animate:YES];
+}
+@end
diff --git a/views/mac/SkEventNotifier.h b/views/mac/SkEventNotifier.h
new file mode 100644
index 00000000..ea6bbf1e
--- /dev/null
+++ b/views/mac/SkEventNotifier.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#import <Foundation/Foundation.h>
+
+@interface SkEventNotifier : NSObject
+- (void)receiveSkEvent:(NSNotification*)notification;
++ (void)postTimedSkEvent:(NSTimeInterval)ti;
++ (void)timerFireMethod:(NSTimer*)theTimer;
+@end
diff --git a/views/mac/SkEventNotifier.mm b/views/mac/SkEventNotifier.mm
new file mode 100644
index 00000000..0864380d
--- /dev/null
+++ b/views/mac/SkEventNotifier.mm
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkEventNotifier.h"
+#include "SkEvent.h"
+#define SkEventClass @"SkEvenClass"
+@implementation SkEventNotifier
+- (id)init {
+ self = [super init];
+ if (self) {
+ //Register as an observer for SkEventClass events and call
+ //receiveSkEvent: upon receiving the event
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(receiveSkEvent:)
+ name:SkEventClass
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+-(BOOL) acceptsFirstResponder {
+ return YES;
+}
+
+//SkEvent Handers
+- (void)receiveSkEvent:(NSNotification *)notification {
+ if(SkEvent::ProcessEvent())
+ SkEvent::SignalNonEmptyQueue();
+}
+
++ (void)postTimedSkEvent:(NSTimeInterval)timeInterval {
+ [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
+ selector:@selector(timerFireMethod:)
+ userInfo:nil repeats:NO];
+}
+
++ (void)timerFireMethod:(NSTimer*)theTimer {
+ SkEvent::ServiceQueueTimer();
+}
+
+@end
+////////////////////////////////////////////////////////////////////////////////
+void SkEvent::SignalNonEmptyQueue() {
+ //post a SkEventClass event to the default notification queue
+ NSNotification* notification = [NSNotification notificationWithName:SkEventClass object:nil];
+ [[NSNotificationQueue defaultQueue] enqueueNotification:notification
+ postingStyle:NSPostWhenIdle
+ coalesceMask:NSNotificationNoCoalescing
+ forModes:nil];
+}
+
+void SkEvent::SignalQueueTimer(SkMSec delay) {
+ if (delay) {
+ //Convert to seconds
+ NSTimeInterval ti = delay/(float)SK_MSec1;
+ [SkEventNotifier postTimedSkEvent:ti];
+ }
+}
diff --git a/views/mac/SkNSView.h b/views/mac/SkNSView.h
new file mode 100644
index 00000000..dfc81ea5
--- /dev/null
+++ b/views/mac/SkNSView.h
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import <QuartzCore/QuartzCore.h>
+#import <Cocoa/Cocoa.h>
+#import "SkWindow.h"
+class SkEvent;
+@class SkNSView;
+
+@protocol SkNSViewOptionsDelegate <NSObject>
+@optional
+// Called when the view needs to handle adding an SkOSMenu
+- (void) view:(SkNSView*)view didAddMenu:(const SkOSMenu*)menu;
+- (void) view:(SkNSView*)view didUpdateMenu:(const SkOSMenu*)menu;
+@end
+
+@interface SkNSView : NSView {
+ BOOL fRedrawRequestPending;
+
+ NSString* fTitle;
+ SkOSWindow* fWind;
+#if SK_SUPPORT_GPU
+ NSOpenGLContext* fGLContext;
+#endif
+ id<SkNSViewOptionsDelegate> fOptionsDelegate;
+}
+
+@property (nonatomic, readonly) SkOSWindow *fWind;
+@property (nonatomic, retain) NSString* fTitle;
+#if SK_SUPPORT_GPU
+@property (nonatomic, retain) NSOpenGLContext* fGLContext;
+#endif
+@property (nonatomic, assign) id<SkNSViewOptionsDelegate> fOptionsDelegate;
+
+- (id)initWithDefaults;
+- (void)setUpWindow;
+- (void)resizeSkView:(NSSize)newSize;
+- (void)setSkTitle:(const char*)title;
+- (void)onAddMenu:(const SkOSMenu*)menu;
+- (void)onUpdateMenu:(const SkOSMenu*)menu;
+- (void)postInvalWithRect:(const SkIRect*)rectOrNil;
+- (BOOL)onHandleEvent:(const SkEvent&)event;
+
+- (bool)attach:(SkOSWindow::SkBackEndTypes)attachType withMSAASampleCount:(int) sampleCount andGetInfo:(SkOSWindow::AttachmentInfo*) info;
+- (void)detach;
+- (void)present;
+@end
diff --git a/views/mac/SkNSView.mm b/views/mac/SkNSView.mm
new file mode 100644
index 00000000..475b08f6
--- /dev/null
+++ b/views/mac/SkNSView.mm
@@ -0,0 +1,419 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkNSView.h"
+#include "SkCanvas.h"
+#include "SkCGUtils.h"
+#include "SkEvent.h"
+SK_COMPILE_ASSERT(SK_SUPPORT_GPU, not_implemented_for_non_gpu_build);
+
+//#define FORCE_REDRAW
+// Can be dropped when we no longer support 10.6.
+#define RETINA_API_AVAILABLE (defined(MAC_OS_X_VERSION_10_7) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
+@implementation SkNSView
+@synthesize fWind, fTitle, fOptionsDelegate, fGLContext;
+
+- (id)initWithCoder:(NSCoder*)coder {
+ if ((self = [super initWithCoder:coder])) {
+ self = [self initWithDefaults];
+ [self setUpWindow];
+ }
+ return self;
+}
+
+- (id)initWithFrame:(NSRect)frameRect {
+ if ((self = [super initWithFrame:frameRect])) {
+ self = [self initWithDefaults];
+ [self setUpWindow];
+ }
+ return self;
+}
+
+- (id)initWithDefaults {
+#if RETINA_API_AVAILABLE
+ [self setWantsBestResolutionOpenGLSurface:YES];
+#endif
+ fRedrawRequestPending = false;
+ fWind = NULL;
+ return self;
+}
+
+- (void)setUpWindow {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(backingPropertiesChanged:)
+ name:@"NSWindowDidChangeBackingPropertiesNotification"
+ object:[self window]];
+ if (NULL != fWind) {
+ fWind->setVisibleP(true);
+ NSSize size = self.frame.size;
+#if RETINA_API_AVAILABLE
+ size = [self convertSizeToBacking:self.frame.size];
+#endif
+ fWind->resize((int) size.width, (int) size.height,
+ SkBitmap::kARGB_8888_Config);
+ }
+}
+
+-(BOOL) isFlipped {
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (float)scaleFactor {
+ NSWindow *window = [self window];
+#if RETINA_API_AVAILABLE
+ if (window) {
+ return [window backingScaleFactor];
+ }
+ return [[NSScreen mainScreen] backingScaleFactor];
+#else
+ if (window) {
+ return [window userSpaceScaleFactor];
+ }
+ return [[NSScreen mainScreen] userSpaceScaleFactor];
+#endif
+}
+
+- (void)backingPropertiesChanged:(NSNotification *)notification {
+ CGFloat oldBackingScaleFactor = (CGFloat)[
+ [notification.userInfo objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue
+ ];
+ CGFloat newBackingScaleFactor = [self scaleFactor];
+ if (oldBackingScaleFactor == newBackingScaleFactor) {
+ return;
+ }
+
+ // TODO: need a better way to force a refresh (that works).
+ // [fGLContext update] does not appear to update if the point size has not changed,
+ // even if the backing size has changed.
+ [self setFrameSize:NSMakeSize(self.frame.size.width + 1, self.frame.size.height + 1)];
+}
+
+- (void)resizeSkView:(NSSize)newSize {
+#if RETINA_API_AVAILABLE
+ newSize = [self convertSizeToBacking:newSize];
+#endif
+ if (NULL != fWind &&
+ (fWind->width() != newSize.width ||
+ fWind->height() != newSize.height))
+ {
+ fWind->resize((int) newSize.width, (int) newSize.height);
+ if (NULL != fGLContext) {
+ glClear(GL_STENCIL_BUFFER_BIT);
+ [fGLContext update];
+ }
+ }
+}
+
+- (void) setFrameSize:(NSSize)newSize {
+ [super setFrameSize:newSize];
+ [self resizeSkView:newSize];
+}
+
+- (void)dealloc {
+ delete fWind;
+ self.fGLContext = nil;
+ self.fTitle = nil;
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+- (void)drawSkia {
+ fRedrawRequestPending = false;
+ if (NULL != fWind) {
+ SkAutoTUnref<SkCanvas> canvas(fWind->createCanvas());
+ fWind->draw(canvas);
+#ifdef FORCE_REDRAW
+ fWind->inval(NULL);
+#endif
+ }
+}
+
+- (void)setSkTitle:(const char *)title {
+ self.fTitle = [NSString stringWithUTF8String:title];
+ [[self window] setTitle:self.fTitle];
+}
+
+- (BOOL)onHandleEvent:(const SkEvent&)evt {
+ return false;
+}
+
+#include "SkOSMenu.h"
+- (void)onAddMenu:(const SkOSMenu*)menu {
+ [self.fOptionsDelegate view:self didAddMenu:menu];
+}
+
+- (void)onUpdateMenu:(const SkOSMenu*)menu {
+ [self.fOptionsDelegate view:self didUpdateMenu:menu];
+}
+
+- (void)postInvalWithRect:(const SkIRect*)r {
+ if (!fRedrawRequestPending) {
+ fRedrawRequestPending = true;
+ [self setNeedsDisplay:YES];
+ [self performSelector:@selector(drawSkia) withObject:nil afterDelay:0];
+ }
+}
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkKey.h"
+enum {
+ SK_MacReturnKey = 36,
+ SK_MacDeleteKey = 51,
+ SK_MacEndKey = 119,
+ SK_MacLeftKey = 123,
+ SK_MacRightKey = 124,
+ SK_MacDownKey = 125,
+ SK_MacUpKey = 126,
+ SK_Mac0Key = 0x52,
+ SK_Mac1Key = 0x53,
+ SK_Mac2Key = 0x54,
+ SK_Mac3Key = 0x55,
+ SK_Mac4Key = 0x56,
+ SK_Mac5Key = 0x57,
+ SK_Mac6Key = 0x58,
+ SK_Mac7Key = 0x59,
+ SK_Mac8Key = 0x5b,
+ SK_Mac9Key = 0x5c
+};
+
+static SkKey raw2key(UInt32 raw)
+{
+ static const struct {
+ UInt32 fRaw;
+ SkKey fKey;
+ } gKeys[] = {
+ { SK_MacUpKey, kUp_SkKey },
+ { SK_MacDownKey, kDown_SkKey },
+ { SK_MacLeftKey, kLeft_SkKey },
+ { SK_MacRightKey, kRight_SkKey },
+ { SK_MacReturnKey, kOK_SkKey },
+ { SK_MacDeleteKey, kBack_SkKey },
+ { SK_MacEndKey, kEnd_SkKey },
+ { SK_Mac0Key, k0_SkKey },
+ { SK_Mac1Key, k1_SkKey },
+ { SK_Mac2Key, k2_SkKey },
+ { SK_Mac3Key, k3_SkKey },
+ { SK_Mac4Key, k4_SkKey },
+ { SK_Mac5Key, k5_SkKey },
+ { SK_Mac6Key, k6_SkKey },
+ { SK_Mac7Key, k7_SkKey },
+ { SK_Mac8Key, k8_SkKey },
+ { SK_Mac9Key, k9_SkKey }
+ };
+
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++)
+ if (gKeys[i].fRaw == raw)
+ return gKeys[i].fKey;
+ return kNONE_SkKey;
+}
+
+- (void)keyDown:(NSEvent *)event {
+ if (NULL == fWind)
+ return;
+
+ SkKey key = raw2key([event keyCode]);
+ if (kNONE_SkKey != key)
+ fWind->handleKey(key);
+ else{
+ unichar c = [[event characters] characterAtIndex:0];
+ fWind->handleChar((SkUnichar)c);
+ }
+}
+
+- (void)keyUp:(NSEvent *)event {
+ if (NULL == fWind)
+ return;
+
+ SkKey key = raw2key([event keyCode]);
+ if (kNONE_SkKey != key)
+ fWind->handleKeyUp(key);
+ // else
+ // unichar c = [[event characters] characterAtIndex:0];
+}
+
+static const struct {
+ unsigned fNSModifierMask;
+ unsigned fSkModifierMask;
+} gModifierMasks[] = {
+ { NSAlphaShiftKeyMask, kShift_SkModifierKey },
+ { NSShiftKeyMask, kShift_SkModifierKey },
+ { NSControlKeyMask, kControl_SkModifierKey },
+ { NSAlternateKeyMask, kOption_SkModifierKey },
+ { NSCommandKeyMask, kCommand_SkModifierKey },
+};
+
+static unsigned convertNSModifiersToSk(NSUInteger nsModi) {
+ unsigned skModi = 0;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifierMasks); ++i) {
+ if (nsModi & gModifierMasks[i].fNSModifierMask) {
+ skModi |= gModifierMasks[i].fSkModifierMask;
+ }
+ }
+ return skModi;
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ NSPoint p = [event locationInWindow];
+ unsigned modi = convertNSModifiersToSk([event modifierFlags]);
+
+ if ([self mouse:p inRect:[self bounds]] && NULL != fWind) {
+ NSPoint loc = [self convertPoint:p fromView:nil];
+#if RETINA_API_AVAILABLE
+ loc = [self convertPointToBacking:loc]; //y-up
+ loc.y = -loc.y;
+#endif
+ fWind->handleClick((int) loc.x, (int) loc.y,
+ SkView::Click::kDown_State, self, modi);
+ }
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+ NSPoint p = [event locationInWindow];
+ unsigned modi = convertNSModifiersToSk([event modifierFlags]);
+
+ if ([self mouse:p inRect:[self bounds]] && NULL != fWind) {
+ NSPoint loc = [self convertPoint:p fromView:nil];
+#if RETINA_API_AVAILABLE
+ loc = [self convertPointToBacking:loc]; //y-up
+ loc.y = -loc.y;
+#endif
+ fWind->handleClick((int) loc.x, (int) loc.y,
+ SkView::Click::kMoved_State, self, modi);
+ }
+}
+
+- (void)mouseMoved:(NSEvent *)event {
+ NSPoint p = [event locationInWindow];
+ unsigned modi = convertNSModifiersToSk([event modifierFlags]);
+
+ if ([self mouse:p inRect:[self bounds]] && NULL != fWind) {
+ NSPoint loc = [self convertPoint:p fromView:nil];
+#if RETINA_API_AVAILABLE
+ loc = [self convertPointToBacking:loc]; //y-up
+ loc.y = -loc.y;
+#endif
+ fWind->handleClick((int) loc.x, (int) loc.y,
+ SkView::Click::kMoved_State, self, modi);
+ }
+}
+
+- (void)mouseUp:(NSEvent *)event {
+ NSPoint p = [event locationInWindow];
+ unsigned modi = convertNSModifiersToSk([event modifierFlags]);
+
+ if ([self mouse:p inRect:[self bounds]] && NULL != fWind) {
+ NSPoint loc = [self convertPoint:p fromView:nil];
+#if RETINA_API_AVAILABLE
+ loc = [self convertPointToBacking:loc]; //y-up
+ loc.y = -loc.y;
+#endif
+ fWind->handleClick((int) loc.x, (int) loc.y,
+ SkView::Click::kUp_State, self, modi);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#include <OpenGL/OpenGL.h>
+
+namespace {
+CGLContextObj createGLContext(int msaaSampleCount) {
+ GLint major, minor;
+ CGLGetVersion(&major, &minor);
+
+ static const CGLPixelFormatAttribute attributes[] = {
+ kCGLPFAStencilSize, (CGLPixelFormatAttribute) 8,
+ kCGLPFAAccelerated,
+ kCGLPFADoubleBuffer,
+ (CGLPixelFormatAttribute)0
+ };
+
+ CGLPixelFormatObj format;
+ GLint npix = 0;
+ if (msaaSampleCount > 0) {
+ static int kAttributeCount = SK_ARRAY_COUNT(attributes);
+ CGLPixelFormatAttribute msaaAttributes[kAttributeCount + 5];
+ memcpy(msaaAttributes, attributes, sizeof(attributes));
+ SkASSERT(0 == msaaAttributes[kAttributeCount - 1]);
+ msaaAttributes[kAttributeCount - 1] = kCGLPFASampleBuffers;
+ msaaAttributes[kAttributeCount + 0] = (CGLPixelFormatAttribute)1;
+ msaaAttributes[kAttributeCount + 1] = kCGLPFAMultisample;
+ msaaAttributes[kAttributeCount + 2] = kCGLPFASamples;
+ msaaAttributes[kAttributeCount + 3] =
+ (CGLPixelFormatAttribute)msaaSampleCount;
+ msaaAttributes[kAttributeCount + 4] = (CGLPixelFormatAttribute)0;
+ CGLChoosePixelFormat(msaaAttributes, &format, &npix);
+ }
+ if (!npix) {
+ CGLChoosePixelFormat(attributes, &format, &npix);
+ }
+ CGLContextObj ctx;
+ CGLCreateContext(format, NULL, &ctx);
+ CGLDestroyPixelFormat(format);
+
+ static const GLint interval = 1;
+ CGLSetParameter(ctx, kCGLCPSwapInterval, &interval);
+ CGLSetCurrentContext(ctx);
+ return ctx;
+}
+}
+
+- (void)viewDidMoveToWindow {
+ [super viewDidMoveToWindow];
+
+ //Attaching view to fGLContext requires that the view to be part of a window,
+ //and that the NSWindow instance must have a CoreGraphics counterpart (or
+ //it must NOT be deferred or should have been on screen at least once)
+ if ([fGLContext view] != self && nil != self.window) {
+ [fGLContext setView:self];
+ }
+}
+- (bool)attach:(SkOSWindow::SkBackEndTypes)attachType
+ withMSAASampleCount:(int) sampleCount
+ andGetInfo:(SkOSWindow::AttachmentInfo*) info {
+ if (nil == fGLContext) {
+ CGLContextObj ctx = createGLContext(sampleCount);
+ fGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj:ctx];
+ CGLReleaseContext(ctx);
+ if (NULL == fGLContext) {
+ return false;
+ }
+ [fGLContext setView:self];
+ }
+
+ [fGLContext makeCurrentContext];
+ CGLPixelFormatObj format = CGLGetPixelFormat((CGLContextObj)[fGLContext CGLContextObj]);
+ CGLDescribePixelFormat(format, 0, kCGLPFASamples, &info->fSampleCount);
+ CGLDescribePixelFormat(format, 0, kCGLPFAStencilSize, &info->fStencilBits);
+ NSSize size = self.bounds.size;
+#if RETINA_API_AVAILABLE
+ size = [self convertSizeToBacking:size];
+#endif
+ glViewport(0, 0, (int) size.width, (int) size.height);
+ glClearColor(0, 0, 0, 0);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ return true;
+}
+
+- (void)detach {
+ [fGLContext release];
+ fGLContext = nil;
+}
+
+- (void)present {
+ if (nil != fGLContext) {
+ [fGLContext flushBuffer];
+ }
+}
+@end
diff --git a/views/mac/SkOSWindow_Mac.cpp b/views/mac/SkOSWindow_Mac.cpp
new file mode 100644
index 00000000..e6f35803
--- /dev/null
+++ b/views/mac/SkOSWindow_Mac.cpp
@@ -0,0 +1,542 @@
+/*
+ * 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 "SkTypes.h"
+
+#if defined(SK_BUILD_FOR_MAC)
+
+#include <AGL/agl.h>
+
+#include <Carbon/Carbon.h>
+#include "SkCGUtils.h"
+
+#include "SkWindow.h"
+#include "SkCanvas.h"
+#include "SkOSMenu.h"
+#include "SkTime.h"
+
+#include "SkGraphics.h"
+#include <new.h>
+
+static void (*gPrevNewHandler)();
+
+extern "C" {
+ static void sk_new_handler()
+ {
+ if (SkGraphics::SetFontCacheUsed(0))
+ return;
+ if (gPrevNewHandler)
+ gPrevNewHandler();
+ else
+ sk_throw();
+ }
+}
+
+static SkOSWindow* gCurrOSWin;
+static EventTargetRef gEventTarget;
+static EventQueueRef gCurrEventQ;
+
+static OSStatus MyDrawEventHandler(EventHandlerCallRef myHandler,
+ EventRef event, void *userData) {
+ // NOTE: GState is save/restored by the HIView system doing the callback,
+ // so the draw handler doesn't need to do it
+
+ OSStatus status = noErr;
+ CGContextRef context;
+ HIRect bounds;
+
+ // Get the CGContextRef
+ status = GetEventParameter (event, kEventParamCGContextRef,
+ typeCGContextRef, NULL,
+ sizeof (CGContextRef),
+ NULL,
+ &context);
+
+ if (status != noErr) {
+ SkDebugf("Got error %d getting the context!\n", status);
+ return status;
+ }
+
+ // Get the bounding rectangle
+ HIViewGetBounds ((HIViewRef) userData, &bounds);
+
+ gCurrOSWin->doPaint(context);
+ return status;
+}
+
+#define SK_MacEventClass FOUR_CHAR_CODE('SKec')
+#define SK_MacEventKind FOUR_CHAR_CODE('SKek')
+#define SK_MacEventParamName FOUR_CHAR_CODE('SKev')
+#define SK_MacEventSinkIDParamName FOUR_CHAR_CODE('SKes')
+
+static void set_bindingside(HISideBinding* side, HIViewRef parent, HIBindingKind kind) {
+ side->toView = parent;
+ side->kind = kind;
+ side->offset = 0;
+}
+
+static void set_axisscale(HIAxisScale* axis, HIViewRef parent) {
+ axis->toView = parent;
+ axis->kind = kHILayoutScaleAbsolute;
+ axis->ratio = 1;
+}
+
+static void set_axisposition(HIAxisPosition* pos, HIViewRef parent, HIPositionKind kind) {
+ pos->toView = parent;
+ pos->kind = kind;
+ pos->offset = 0;
+}
+
+SkOSWindow::SkOSWindow(void* hWnd) : fHWND(hWnd), fAGLCtx(NULL)
+{
+ OSStatus result;
+ WindowRef wr = (WindowRef)hWnd;
+
+ HIViewRef imageView, parent;
+ HIViewRef rootView = HIViewGetRoot(wr);
+ HIViewFindByID(rootView, kHIViewWindowContentID, &parent);
+ result = HIImageViewCreate(NULL, &imageView);
+ SkASSERT(result == noErr);
+
+ result = HIViewAddSubview(parent, imageView);
+ SkASSERT(result == noErr);
+
+ fHVIEW = imageView;
+
+ HIViewSetVisible(imageView, true);
+ HIViewPlaceInSuperviewAt(imageView, 0, 0);
+
+ if (true) {
+ HILayoutInfo layout;
+ layout.version = kHILayoutInfoVersionZero;
+ set_bindingside(&layout.binding.left, parent, kHILayoutBindLeft);
+ set_bindingside(&layout.binding.top, parent, kHILayoutBindTop);
+ set_bindingside(&layout.binding.right, parent, kHILayoutBindRight);
+ set_bindingside(&layout.binding.bottom, parent, kHILayoutBindBottom);
+ set_axisscale(&layout.scale.x, parent);
+ set_axisscale(&layout.scale.y, parent);
+ set_axisposition(&layout.position.x, parent, kHILayoutPositionLeft);
+ set_axisposition(&layout.position.y, rootView, kHILayoutPositionTop);
+ HIViewSetLayoutInfo(imageView, &layout);
+ }
+
+ HIImageViewSetOpaque(imageView, true);
+ HIImageViewSetScaleToFit(imageView, false);
+
+ static const EventTypeSpec gTypes[] = {
+ { kEventClassKeyboard, kEventRawKeyDown },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseDragged },
+ { kEventClassMouse, kEventMouseMoved },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent },
+ { kEventClassWindow, kEventWindowBoundsChanged },
+// { kEventClassWindow, kEventWindowDrawContent },
+ { SK_MacEventClass, SK_MacEventKind }
+ };
+
+ EventHandlerUPP handlerUPP = NewEventHandlerUPP(SkOSWindow::EventHandler);
+ int count = SK_ARRAY_COUNT(gTypes);
+
+ result = InstallEventHandler(GetWindowEventTarget(wr), handlerUPP,
+ count, gTypes, this, nil);
+ SkASSERT(result == noErr);
+
+ gCurrOSWin = this;
+ gCurrEventQ = GetCurrentEventQueue();
+ gEventTarget = GetWindowEventTarget(wr);
+
+ static bool gOnce = true;
+ if (gOnce) {
+ gOnce = false;
+ gPrevNewHandler = set_new_handler(sk_new_handler);
+ }
+}
+
+void SkOSWindow::doPaint(void* ctx)
+{
+#if 0
+ this->update(NULL);
+
+ const SkBitmap& bm = this->getBitmap();
+ CGImageRef img = SkCreateCGImageRef(bm);
+
+ if (img) {
+ CGRect r = CGRectMake(0, 0, bm.width(), bm.height());
+
+ CGContextRef cg = reinterpret_cast<CGContextRef>(ctx);
+
+ CGContextSaveGState(cg);
+ CGContextTranslateCTM(cg, 0, r.size.height);
+ CGContextScaleCTM(cg, 1, -1);
+
+ CGContextDrawImage(cg, r, img);
+
+ CGContextRestoreGState(cg);
+
+ CGImageRelease(img);
+ }
+#endif
+}
+
+void SkOSWindow::updateSize()
+{
+ Rect r;
+
+ GetWindowBounds((WindowRef)fHWND, kWindowContentRgn, &r);
+ this->resize(r.right - r.left, r.bottom - r.top);
+
+#if 0
+ HIRect frame;
+ HIViewRef imageView = (HIViewRef)getHVIEW();
+ HIViewRef parent = HIViewGetSuperview(imageView);
+
+ HIViewGetBounds(imageView, &frame);
+ SkDebugf("------ %d bounds %g %g %g %g\n", r.right - r.left,
+ frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
+#endif
+}
+
+void SkOSWindow::onHandleInval(const SkIRect& r)
+{
+ (new SkEvent("inval-imageview", this->getSinkID()))->post();
+}
+
+bool SkOSWindow::onEvent(const SkEvent& evt) {
+ if (evt.isType("inval-imageview")) {
+ this->update(NULL);
+
+ SkEvent query("ignore-window-bitmap");
+ if (!this->doQuery(&query) || !query.getFast32()) {
+ const SkBitmap& bm = this->getBitmap();
+
+ CGImageRef img = SkCreateCGImageRef(bm);
+ HIImageViewSetImage((HIViewRef)getHVIEW(), img);
+ CGImageRelease(img);
+ }
+ return true;
+ }
+ return INHERITED::onEvent(evt);
+}
+
+void SkOSWindow::onSetTitle(const char title[])
+{
+ CFStringRef str = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8);
+ SetWindowTitleWithCFString((WindowRef)fHWND, str);
+ CFRelease(str);
+}
+
+void SkOSWindow::onAddMenu(const SkOSMenu* sk_menu)
+{
+}
+
+static void getparam(EventRef inEvent, OSType name, OSType type, UInt32 size, void* data)
+{
+ EventParamType actualType;
+ UInt32 actualSize;
+ OSStatus status;
+
+ status = GetEventParameter(inEvent, name, type, &actualType, size, &actualSize, data);
+ SkASSERT(status == noErr);
+ SkASSERT(actualType == type);
+ SkASSERT(actualSize == size);
+}
+
+enum {
+ SK_MacReturnKey = 36,
+ SK_MacDeleteKey = 51,
+ SK_MacEndKey = 119,
+ SK_MacLeftKey = 123,
+ SK_MacRightKey = 124,
+ SK_MacDownKey = 125,
+ SK_MacUpKey = 126,
+
+ SK_Mac0Key = 0x52,
+ SK_Mac1Key = 0x53,
+ SK_Mac2Key = 0x54,
+ SK_Mac3Key = 0x55,
+ SK_Mac4Key = 0x56,
+ SK_Mac5Key = 0x57,
+ SK_Mac6Key = 0x58,
+ SK_Mac7Key = 0x59,
+ SK_Mac8Key = 0x5b,
+ SK_Mac9Key = 0x5c
+};
+
+static SkKey raw2key(UInt32 raw)
+{
+ static const struct {
+ UInt32 fRaw;
+ SkKey fKey;
+ } gKeys[] = {
+ { SK_MacUpKey, kUp_SkKey },
+ { SK_MacDownKey, kDown_SkKey },
+ { SK_MacLeftKey, kLeft_SkKey },
+ { SK_MacRightKey, kRight_SkKey },
+ { SK_MacReturnKey, kOK_SkKey },
+ { SK_MacDeleteKey, kBack_SkKey },
+ { SK_MacEndKey, kEnd_SkKey },
+ { SK_Mac0Key, k0_SkKey },
+ { SK_Mac1Key, k1_SkKey },
+ { SK_Mac2Key, k2_SkKey },
+ { SK_Mac3Key, k3_SkKey },
+ { SK_Mac4Key, k4_SkKey },
+ { SK_Mac5Key, k5_SkKey },
+ { SK_Mac6Key, k6_SkKey },
+ { SK_Mac7Key, k7_SkKey },
+ { SK_Mac8Key, k8_SkKey },
+ { SK_Mac9Key, k9_SkKey }
+ };
+
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++)
+ if (gKeys[i].fRaw == raw)
+ return gKeys[i].fKey;
+ return kNONE_SkKey;
+}
+
+static void post_skmacevent()
+{
+ EventRef ref;
+ OSStatus status = CreateEvent(nil, SK_MacEventClass, SK_MacEventKind, 0, 0, &ref);
+ SkASSERT(status == noErr);
+
+#if 0
+ status = SetEventParameter(ref, SK_MacEventParamName, SK_MacEventParamName, sizeof(evt), &evt);
+ SkASSERT(status == noErr);
+ status = SetEventParameter(ref, SK_MacEventSinkIDParamName, SK_MacEventSinkIDParamName, sizeof(sinkID), &sinkID);
+ SkASSERT(status == noErr);
+#endif
+
+ EventTargetRef target = gEventTarget;
+ SetEventParameter(ref, kEventParamPostTarget, typeEventTargetRef, sizeof(target), &target);
+ SkASSERT(status == noErr);
+
+ status = PostEventToQueue(gCurrEventQ, ref, kEventPriorityStandard);
+ SkASSERT(status == noErr);
+
+ ReleaseEvent(ref);
+}
+
+pascal OSStatus SkOSWindow::EventHandler( EventHandlerCallRef inHandler, EventRef inEvent, void* userData )
+{
+ SkOSWindow* win = (SkOSWindow*)userData;
+ OSStatus result = eventNotHandledErr;
+ UInt32 wClass = GetEventClass(inEvent);
+ UInt32 wKind = GetEventKind(inEvent);
+
+ gCurrOSWin = win; // will need to be in TLS. Set this so PostEvent will work
+
+ switch (wClass) {
+ case kEventClassMouse: {
+ Point pt;
+ getparam(inEvent, kEventParamMouseLocation, typeQDPoint, sizeof(pt), &pt);
+ SetPortWindowPort((WindowRef)win->getHWND());
+ GlobalToLocal(&pt);
+
+ switch (wKind) {
+ case kEventMouseDown:
+ if (win->handleClick(pt.h, pt.v, Click::kDown_State)) {
+ result = noErr;
+ }
+ break;
+ case kEventMouseMoved:
+ // fall through
+ case kEventMouseDragged:
+ (void)win->handleClick(pt.h, pt.v, Click::kMoved_State);
+ // result = noErr;
+ break;
+ case kEventMouseUp:
+ (void)win->handleClick(pt.h, pt.v, Click::kUp_State);
+ // result = noErr;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case kEventClassKeyboard:
+ if (wKind == kEventRawKeyDown) {
+ UInt32 raw;
+ getparam(inEvent, kEventParamKeyCode, typeUInt32, sizeof(raw), &raw);
+ SkKey key = raw2key(raw);
+ if (key != kNONE_SkKey)
+ (void)win->handleKey(key);
+ } else if (wKind == kEventRawKeyUp) {
+ UInt32 raw;
+ getparam(inEvent, kEventParamKeyCode, typeUInt32, sizeof(raw), &raw);
+ SkKey key = raw2key(raw);
+ if (key != kNONE_SkKey)
+ (void)win->handleKeyUp(key);
+ }
+ break;
+ case kEventClassTextInput:
+ if (wKind == kEventTextInputUnicodeForKeyEvent) {
+ UInt16 uni;
+ getparam(inEvent, kEventParamTextInputSendText, typeUnicodeText, sizeof(uni), &uni);
+ win->handleChar(uni);
+ }
+ break;
+ case kEventClassWindow:
+ switch (wKind) {
+ case kEventWindowBoundsChanged:
+ win->updateSize();
+ break;
+ case kEventWindowDrawContent: {
+ CGContextRef cg;
+ result = GetEventParameter(inEvent,
+ kEventParamCGContextRef,
+ typeCGContextRef,
+ NULL,
+ sizeof (CGContextRef),
+ NULL,
+ &cg);
+ if (result != 0) {
+ cg = NULL;
+ }
+ win->doPaint(cg);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case SK_MacEventClass: {
+ SkASSERT(wKind == SK_MacEventKind);
+ if (SkEvent::ProcessEvent()) {
+ post_skmacevent();
+ }
+ #if 0
+ SkEvent* evt;
+ SkEventSinkID sinkID;
+ getparam(inEvent, SK_MacEventParamName, SK_MacEventParamName, sizeof(evt), &evt);
+ getparam(inEvent, SK_MacEventSinkIDParamName, SK_MacEventSinkIDParamName, sizeof(sinkID), &sinkID);
+ #endif
+ result = noErr;
+ break;
+ }
+ default:
+ break;
+ }
+ if (result == eventNotHandledErr) {
+ result = CallNextEventHandler(inHandler, inEvent);
+ }
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::SignalNonEmptyQueue()
+{
+ post_skmacevent();
+// SkDebugf("signal nonempty\n");
+}
+
+static TMTask gTMTaskRec;
+static TMTask* gTMTaskPtr;
+
+static void sk_timer_proc(TMTask* rec)
+{
+ SkEvent::ServiceQueueTimer();
+// SkDebugf("timer task fired\n");
+}
+
+void SkEvent::SignalQueueTimer(SkMSec delay)
+{
+ if (gTMTaskPtr)
+ {
+ RemoveTimeTask((QElem*)gTMTaskPtr);
+ DisposeTimerUPP(gTMTaskPtr->tmAddr);
+ gTMTaskPtr = nil;
+ }
+ if (delay)
+ {
+ gTMTaskPtr = &gTMTaskRec;
+ memset(gTMTaskPtr, 0, sizeof(gTMTaskRec));
+ gTMTaskPtr->tmAddr = NewTimerUPP(sk_timer_proc);
+ OSErr err = InstallTimeTask((QElem*)gTMTaskPtr);
+// SkDebugf("installtimetask of %d returned %d\n", delay, err);
+ PrimeTimeTask((QElem*)gTMTaskPtr, delay);
+ }
+}
+
+#define USE_MSAA 0
+
+AGLContext create_gl(WindowRef wref)
+{
+ GLint major, minor;
+ AGLContext ctx;
+
+ aglGetVersion(&major, &minor);
+ SkDebugf("---- agl version %d %d\n", major, minor);
+
+ const GLint pixelAttrs[] = {
+ AGL_RGBA,
+ AGL_STENCIL_SIZE, 8,
+#if USE_MSAA
+ AGL_SAMPLE_BUFFERS_ARB, 1,
+ AGL_MULTISAMPLE,
+ AGL_SAMPLES_ARB, 8,
+#endif
+ AGL_ACCELERATED,
+ AGL_DOUBLEBUFFER,
+ AGL_NONE
+ };
+ AGLPixelFormat format = aglChoosePixelFormat(NULL, 0, pixelAttrs);
+ //AGLPixelFormat format = aglCreatePixelFormat(pixelAttrs);
+ SkDebugf("----- agl format %p\n", format);
+ ctx = aglCreateContext(format, NULL);
+ SkDebugf("----- agl context %p\n", ctx);
+ aglDestroyPixelFormat(format);
+
+ static const GLint interval = 1;
+ aglSetInteger(ctx, AGL_SWAP_INTERVAL, &interval);
+ aglSetCurrentContext(ctx);
+ return ctx;
+}
+
+bool SkOSWindow::attach(SkBackEndTypes /* attachType */)
+{
+ if (NULL == fAGLCtx) {
+ fAGLCtx = create_gl((WindowRef)fHWND);
+ if (NULL == fAGLCtx) {
+ return false;
+ }
+ }
+
+ GLboolean success = true;
+
+ int width, height;
+
+ success = aglSetWindowRef((AGLContext)fAGLCtx, (WindowRef)fHWND);
+ width = this->width();
+ height = this->height();
+
+ GLenum err = aglGetError();
+ if (err) {
+ SkDebugf("---- aglSetWindowRef %d %d %s [%d %d]\n", success, err,
+ aglErrorString(err), width, height);
+ }
+
+ if (success) {
+ glViewport(0, 0, width, height);
+ glClearColor(0, 0, 0, 0);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ }
+ return success;
+}
+
+void SkOSWindow::detach() {
+ aglSetWindowRef((AGLContext)fAGLCtx, NULL);
+}
+
+void SkOSWindow::present() {
+ aglSwapBuffers((AGLContext)fAGLCtx);
+}
+
+#endif
diff --git a/views/mac/SkOSWindow_Mac.mm b/views/mac/SkOSWindow_Mac.mm
new file mode 100644
index 00000000..b0f006a8
--- /dev/null
+++ b/views/mac/SkOSWindow_Mac.mm
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#if defined(SK_BUILD_FOR_MAC)
+
+#import <Cocoa/Cocoa.h>
+#include "SkOSWindow_Mac.h"
+#include "SkOSMenu.h"
+#include "SkTypes.h"
+#include "SkWindow.h"
+#import "SkNSView.h"
+#import "SkEventNotifier.h"
+#define kINVAL_NSVIEW_EventType "inval-nsview"
+
+SK_COMPILE_ASSERT(SK_SUPPORT_GPU, not_implemented_for_non_gpu_build);
+
+SkOSWindow::SkOSWindow(void* hWnd) : fHWND(hWnd) {
+ fInvalEventIsPending = false;
+ fGLContext = NULL;
+ fNotifier = [[SkEventNotifier alloc] init];
+}
+SkOSWindow::~SkOSWindow() {
+ [(SkEventNotifier*)fNotifier release];
+}
+
+void SkOSWindow::onHandleInval(const SkIRect& r) {
+ if (!fInvalEventIsPending) {
+ fInvalEventIsPending = true;
+ (new SkEvent(kINVAL_NSVIEW_EventType, this->getSinkID()))->post();
+ }
+}
+
+bool SkOSWindow::onEvent(const SkEvent& evt) {
+ if (evt.isType(kINVAL_NSVIEW_EventType)) {
+ fInvalEventIsPending = false;
+ const SkIRect& r = this->getDirtyBounds();
+ [(SkNSView*)fHWND postInvalWithRect:&r];
+ [(NSOpenGLContext*)fGLContext update];
+ return true;
+ }
+ if ([(SkNSView*)fHWND onHandleEvent:evt]) {
+ return true;
+ }
+ return this->INHERITED::onEvent(evt);
+}
+
+bool SkOSWindow::onDispatchClick(int x, int y, Click::State state, void* owner,
+ unsigned modi) {
+ return this->INHERITED::onDispatchClick(x, y, state, owner, modi);
+}
+
+void SkOSWindow::onSetTitle(const char title[]) {
+ [(SkNSView*)fHWND setSkTitle:title];
+}
+
+void SkOSWindow::onAddMenu(const SkOSMenu* menu) {
+ [(SkNSView*)fHWND onAddMenu:menu];
+}
+
+void SkOSWindow::onUpdateMenu(const SkOSMenu* menu) {
+ [(SkNSView*)fHWND onUpdateMenu:menu];
+}
+
+bool SkOSWindow::attach(SkBackEndTypes attachType, int sampleCount, AttachmentInfo* info) {
+ return [(SkNSView*)fHWND attach:attachType withMSAASampleCount:sampleCount andGetInfo:info];
+}
+
+void SkOSWindow::detach() {
+ [(SkNSView*)fHWND detach];
+}
+
+void SkOSWindow::present() {
+ [(SkNSView*)fHWND present];
+}
+
+#endif
diff --git a/views/mac/SkOptionsTableView.h b/views/mac/SkOptionsTableView.h
new file mode 100644
index 00000000..1f9b36a1
--- /dev/null
+++ b/views/mac/SkOptionsTableView.h
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "SkNSView.h"
+#import "SkOSMenu.h"
+#import "SkEvent.h"
+@interface SkOptionItem : NSObject {
+ NSCell* fCell;
+ const SkOSMenu::Item* fItem;
+}
+@property (nonatomic, assign) const SkOSMenu::Item* fItem;
+@property (nonatomic, retain) NSCell* fCell;
+@end
+
+@interface SkOptionsTableView : NSTableView <SkNSViewOptionsDelegate, NSTableViewDelegate, NSTableViewDataSource> {
+ NSMutableArray* fItems;
+ const SkTDArray<SkOSMenu*>* fMenus;
+ BOOL fShowKeys;
+}
+@property (nonatomic, retain) NSMutableArray* fItems;
+
+- (void)registerMenus:(const SkTDArray<SkOSMenu*>*)menus;
+- (void)updateMenu:(const SkOSMenu*)menu;
+- (void)loadMenu:(const SkOSMenu*)menu;
+- (IBAction)toggleKeyEquivalents:(id)sender;
+
+- (NSCell*)createAction;
+- (NSCell*)createList:(NSArray*)items current:(int)index;
+- (NSCell*)createSlider:(float)value min:(float)min max:(float)max;
+- (NSCell*)createSwitch:(BOOL)state;
+- (NSCell*)createTextField:(NSString*)placeHolder;
+- (NSCell*)createTriState:(NSCellStateValue)state;
+
+@end
diff --git a/views/mac/SkOptionsTableView.mm b/views/mac/SkOptionsTableView.mm
new file mode 100644
index 00000000..7a6afe42
--- /dev/null
+++ b/views/mac/SkOptionsTableView.mm
@@ -0,0 +1,297 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkOptionsTableView.h"
+#import "SkTextFieldCell.h"
+@implementation SkOptionItem
+@synthesize fCell, fItem;
+- (void)dealloc {
+ [fCell release];
+ [super dealloc];
+}
+@end
+
+@implementation SkOptionsTableView
+@synthesize fItems;
+
+- (id)initWithCoder:(NSCoder*)coder {
+ if ((self = [super initWithCoder:coder])) {
+ self.dataSource = self;
+ self.delegate = self;
+ fMenus = NULL;
+ fShowKeys = YES;
+ [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
+ self.fItems = [NSMutableArray array];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ self.fItems = nil;
+ [super dealloc];
+}
+
+- (void) view:(SkNSView*)view didAddMenu:(const SkOSMenu*)menu {}
+- (void) view:(SkNSView*)view didUpdateMenu:(const SkOSMenu*)menu {
+ [self updateMenu:menu];
+}
+
+- (IBAction)toggleKeyEquivalents:(id)sender {
+ fShowKeys = !fShowKeys;
+ NSMenuItem* item = (NSMenuItem*)sender;
+ [item setState:fShowKeys];
+ [self reloadData];
+}
+
+- (void)registerMenus:(const SkTDArray<SkOSMenu*>*)menus {
+ fMenus = menus;
+ for (int i = 0; i < fMenus->count(); ++i) {
+ [self loadMenu:(*fMenus)[i]];
+ }
+}
+
+- (void)updateMenu:(const SkOSMenu*)menu {
+ // the first menu is always assumed to be the static, the second is
+ // repopulated every time over and over again
+
+ // seems pretty weird that we have to get rid of the const'ness here,
+ // but trying to propagate the const'ness through all the way to the fMenus
+ // vector was a non-starter.
+
+ int menuIndex = fMenus->find(const_cast<SkOSMenu *>(menu));
+ if (menuIndex >= 0 && menuIndex < fMenus->count()) {
+ NSUInteger first = 0;
+ for (NSInteger i = 0; i < menuIndex; ++i) {
+ first += (*fMenus)[i]->getCount();
+ }
+ [fItems removeObjectsInRange:NSMakeRange(first, [fItems count] - first)];
+ [self loadMenu:menu];
+ }
+ [self reloadData];
+}
+
+- (NSCellStateValue)triStateToNSState:(SkOSMenu::TriState)state {
+ if (SkOSMenu::kOnState == state)
+ return NSOnState;
+ else if (SkOSMenu::kOffState == state)
+ return NSOffState;
+ else
+ return NSMixedState;
+}
+
+- (void)loadMenu:(const SkOSMenu*)menu {
+ const SkOSMenu::Item* menuitems[menu->getCount()];
+ menu->getItems(menuitems);
+ for (int i = 0; i < menu->getCount(); ++i) {
+ const SkOSMenu::Item* item = menuitems[i];
+ SkOptionItem* option = [[SkOptionItem alloc] init];
+ option.fItem = item;
+
+ if (SkOSMenu::kList_Type == item->getType()) {
+ int index = 0, count = 0;
+ SkOSMenu::FindListItemCount(*item->getEvent(), &count);
+ NSMutableArray* optionstrs = [[NSMutableArray alloc] initWithCapacity:count];
+ SkAutoTDeleteArray<SkString> ada(new SkString[count]);
+ SkString* options = ada.get();
+ SkOSMenu::FindListItems(*item->getEvent(), options);
+ for (int i = 0; i < count; ++i)
+ [optionstrs addObject:[NSString stringWithUTF8String:options[i].c_str()]];
+ SkOSMenu::FindListIndex(*item->getEvent(), item->getSlotName(), &index);
+ option.fCell = [self createList:optionstrs current:index];
+ [optionstrs release];
+ }
+ else {
+ bool state = false;
+ SkString str;
+ SkOSMenu::TriState tristate;
+ switch (item->getType()) {
+ case SkOSMenu::kAction_Type:
+ option.fCell = [self createAction];
+ break;
+ case SkOSMenu::kSlider_Type:
+ SkScalar min, max, value;
+ SkOSMenu::FindSliderValue(*item->getEvent(), item->getSlotName(), &value);
+ SkOSMenu::FindSliderMin(*item->getEvent(), &min);
+ SkOSMenu::FindSliderMax(*item->getEvent(), &max);
+ option.fCell = [self createSlider:value
+ min:min
+ max:max];
+ break;
+ case SkOSMenu::kSwitch_Type:
+ SkOSMenu::FindSwitchState(*item->getEvent(), item->getSlotName(), &state);
+ option.fCell = [self createSwitch:(BOOL)state];
+ break;
+ case SkOSMenu::kTriState_Type:
+ SkOSMenu::FindTriState(*item->getEvent(), item->getSlotName(), &tristate);
+ option.fCell = [self createTriState:[self triStateToNSState:tristate]];
+ break;
+ case SkOSMenu::kTextField_Type:
+ SkOSMenu::FindText(*item->getEvent(),item->getSlotName(), &str);
+ option.fCell = [self createTextField:[NSString stringWithUTF8String:str.c_str()]];
+ break;
+ default:
+ break;
+ }
+ }
+ [fItems addObject:option];
+ [option release];
+ }
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
+ return [self.fItems count];
+}
+
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+ int columnIndex = [tableView columnWithIdentifier:[tableColumn identifier]];
+ if (columnIndex == 0) {
+ const SkOSMenu::Item* item = ((SkOptionItem*)[fItems objectAtIndex:row]).fItem;
+ NSString* label = [NSString stringWithUTF8String:item->getLabel()];
+ if (fShowKeys)
+ return [NSString stringWithFormat:@"%@ (%c)", label, item->getKeyEquivalent()];
+ else
+ return label;
+ }
+ else
+ return nil;
+}
+
+- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+ if (tableColumn) {
+ int columnIndex = [tableView columnWithIdentifier:[tableColumn identifier]];
+ if (columnIndex == 1)
+ return [((SkOptionItem*)[fItems objectAtIndex:row]).fCell copy];
+ else
+ return [[[SkTextFieldCell alloc] init] autorelease];
+ }
+ return nil;
+}
+
+- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+ int columnIndex = [tableView columnWithIdentifier:[tableColumn identifier]];
+ if (columnIndex == 1) {
+ SkOptionItem* option = (SkOptionItem*)[self.fItems objectAtIndex:row];
+ NSCell* storedCell = option.fCell;
+ const SkOSMenu::Item* item = option.fItem;
+ switch (item->getType()) {
+ case SkOSMenu::kAction_Type:
+ break;
+ case SkOSMenu::kList_Type:
+ [cell selectItemAtIndex:[(NSPopUpButtonCell*)storedCell indexOfSelectedItem]];
+ break;
+ case SkOSMenu::kSlider_Type:
+ [cell setFloatValue:[storedCell floatValue]];
+ break;
+ case SkOSMenu::kSwitch_Type:
+ [cell setState:[(NSButtonCell*)storedCell state]];
+ break;
+ case SkOSMenu::kTextField_Type:
+ if ([[storedCell stringValue] length] > 0)
+ [cell setStringValue:[storedCell stringValue]];
+ break;
+ case SkOSMenu::kTriState_Type:
+ [cell setState:[(NSButtonCell*)storedCell state]];
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ [(SkTextFieldCell*)cell setEditable:NO];
+ }
+}
+
+- (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+ int columnIndex = [tableView columnWithIdentifier:[tableColumn identifier]];
+ if (columnIndex == 1) {
+ SkOptionItem* option = (SkOptionItem*)[self.fItems objectAtIndex:row];
+ NSCell* cell = option.fCell;
+ const SkOSMenu::Item* item = option.fItem;
+ switch (item->getType()) {
+ case SkOSMenu::kAction_Type:
+ item->postEvent();
+ break;
+ case SkOSMenu::kList_Type:
+ [(NSPopUpButtonCell*)cell selectItemAtIndex:[anObject intValue]];
+ item->setInt([anObject intValue]);
+ break;
+ case SkOSMenu::kSlider_Type:
+ [cell setFloatValue:[anObject floatValue]];
+ item->setScalar([anObject floatValue]);
+ break;
+ case SkOSMenu::kSwitch_Type:
+ [cell setState:[anObject boolValue]];
+ item->setBool([anObject boolValue]);
+ break;
+ case SkOSMenu::kTextField_Type:
+ if ([anObject length] > 0) {
+ [cell setStringValue:anObject];
+ item->setString([anObject UTF8String]);
+ }
+ break;
+ case SkOSMenu::kTriState_Type:
+ [cell setState:[anObject intValue]];
+ item->setTriState((SkOSMenu::TriState)[anObject intValue]);
+ break;
+ default:
+ break;
+ }
+ item->postEvent();
+ }
+}
+
+- (NSCell*)createAction{
+ NSButtonCell* cell = [[[NSButtonCell alloc] init] autorelease];
+ [cell setTitle:@""];
+ [cell setButtonType:NSMomentaryPushInButton];
+ [cell setBezelStyle:NSSmallSquareBezelStyle];
+ return cell;
+}
+
+- (NSCell*)createList:(NSArray*)items current:(int)index {
+ NSPopUpButtonCell* cell = [[[NSPopUpButtonCell alloc] init] autorelease];
+ [cell addItemsWithTitles:items];
+ [cell selectItemAtIndex:index];
+ [cell setArrowPosition:NSPopUpArrowAtBottom];
+ [cell setBezelStyle:NSSmallSquareBezelStyle];
+ return cell;
+}
+
+- (NSCell*)createSlider:(float)value min:(float)min max:(float)max {
+ NSSliderCell* cell = [[[NSSliderCell alloc] init] autorelease];
+ [cell setFloatValue:value];
+ [cell setMinValue:min];
+ [cell setMaxValue:max];
+ return cell;
+}
+
+- (NSCell*)createSwitch:(BOOL)state {
+ NSButtonCell* cell = [[[NSButtonCell alloc] init] autorelease];
+ [cell setState:state];
+ [cell setTitle:@""];
+ [cell setButtonType:NSSwitchButton];
+ return cell;
+}
+
+- (NSCell*)createTextField:(NSString*)placeHolder {
+ SkTextFieldCell* cell = [[[SkTextFieldCell alloc] init] autorelease];
+ [cell setEditable:YES];
+ [cell setStringValue:@""];
+ [cell setPlaceholderString:placeHolder];
+ return cell;
+}
+
+- (NSCell*)createTriState:(NSCellStateValue)state {
+ NSButtonCell* cell = [[[NSButtonCell alloc] init] autorelease];
+ [cell setAllowsMixedState:TRUE];
+ [cell setTitle:@""];
+ [cell setState:(NSInteger)state];
+ [cell setButtonType:NSSwitchButton];
+ return cell;
+}
+@end
diff --git a/views/mac/SkSampleNSView.h b/views/mac/SkSampleNSView.h
new file mode 100644
index 00000000..d3aca9a7
--- /dev/null
+++ b/views/mac/SkSampleNSView.h
@@ -0,0 +1,12 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkNSView.h"
+@interface SkSampleNSView : SkNSView
+- (id)initWithDefaults;
+@end
diff --git a/views/mac/SkSampleNSView.mm b/views/mac/SkSampleNSView.mm
new file mode 100644
index 00000000..ce5f8aab
--- /dev/null
+++ b/views/mac/SkSampleNSView.mm
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkSampleNSView.h"
+#include "SampleApp.h"
+#include <crt_externs.h>
+@implementation SkSampleNSView
+
+- (id)initWithDefaults {
+ if ((self = [super initWithDefaults])) {
+ fWind = new SampleWindow(self, *_NSGetArgc(), *_NSGetArgv(), NULL);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ delete fWind;
+ [super dealloc];
+}
+
+- (void)swipeWithEvent:(NSEvent *)event {
+ CGFloat x = [event deltaX];
+ if (x < 0)
+ ((SampleWindow*)fWind)->previousSample();
+ else if (x > 0)
+ ((SampleWindow*)fWind)->nextSample();
+ else
+ ((SampleWindow*)fWind)->showOverview();
+}
+
+@end
diff --git a/views/mac/SkTextFieldCell.h b/views/mac/SkTextFieldCell.h
new file mode 100644
index 00000000..93d0e4de
--- /dev/null
+++ b/views/mac/SkTextFieldCell.h
@@ -0,0 +1,15 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#import <Cocoa/Cocoa.h>
+//A text field cell that has vertically centered text
+@interface SkTextFieldCell : NSTextFieldCell {
+ BOOL selectingOrEditing;
+}
+@end
diff --git a/views/mac/SkTextFieldCell.m b/views/mac/SkTextFieldCell.m
new file mode 100644
index 00000000..c5efc464
--- /dev/null
+++ b/views/mac/SkTextFieldCell.m
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import "SkTextFieldCell.h"
+@implementation SkTextFieldCell
+- (NSRect)drawingRectForBounds:(NSRect)theRect {
+ NSRect newRect = [super drawingRectForBounds:theRect];
+ if (selectingOrEditing == NO) {
+ NSSize textSize = [self cellSizeForBounds:theRect];
+ float heightDelta = newRect.size.height - textSize.height;
+ if (heightDelta > 0) {
+ newRect.size.height -= heightDelta;
+ newRect.origin.y += (heightDelta / 2);
+ }
+ }
+ return newRect;
+}
+
+- (void)selectWithFrame:(NSRect)aRect
+ inView:(NSView *)controlView
+ editor:(NSText *)textObj
+ delegate:(id)anObject
+ start:(NSInteger)selStart
+ length:(NSInteger)selLength {
+ aRect = [self drawingRectForBounds:aRect];
+ selectingOrEditing = YES;
+ [super selectWithFrame:aRect
+ inView:controlView
+ editor:textObj
+ delegate:anObject
+ start:selStart
+ length:selLength];
+ selectingOrEditing = NO;
+}
+
+- (void)editWithFrame:(NSRect)aRect
+ inView:(NSView *)controlView
+ editor:(NSText *)textObj
+ delegate:(id)anObject
+ event:(NSEvent *)theEvent {
+ aRect = [self drawingRectForBounds:aRect];
+ selectingOrEditing = YES;
+ [super editWithFrame:aRect
+ inView:controlView
+ editor:textObj
+ delegate:anObject
+ event:theEvent];
+ selectingOrEditing = NO;
+}
+
+@end
diff --git a/views/mac/skia_mac.mm b/views/mac/skia_mac.mm
new file mode 100644
index 00000000..e3c31c40
--- /dev/null
+++ b/views/mac/skia_mac.mm
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "SkApplication.h"
+
+int main(int argc, char *argv[]) {
+ signal(SIGPIPE, SIG_IGN);
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ application_init();
+ int retVal = NSApplicationMain(argc, (const char **)argv);
+ application_term();
+ [pool release];
+ return retVal;
+}
diff --git a/views/sdl/SkOSWindow_SDL.cpp b/views/sdl/SkOSWindow_SDL.cpp
new file mode 100644
index 00000000..2a1fae28
--- /dev/null
+++ b/views/sdl/SkOSWindow_SDL.cpp
@@ -0,0 +1,226 @@
+
+/*
+ * 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 "SkOSWindow_SDL.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkGLCanvas.h"
+#include "SkOSMenu.h"
+#include "SkTime.h"
+
+static void post_SkEvent_event() {
+ SDL_Event evt;
+ evt.type = SDL_USEREVENT;
+ evt.user.type = SDL_USEREVENT;
+ evt.user.code = 0;
+ evt.user.data1 = NULL;
+ evt.user.data2 = NULL;
+ SDL_PushEvent(&evt);
+}
+
+static bool skia_setBitmapFromSurface(SkBitmap* dst, SDL_Surface* src) {
+ SkBitmap::Config config;
+
+ switch (src->format->BytesPerPixel) {
+ case 2:
+ config = SkBitmap::kRGB_565_Config;
+ break;
+ case 4:
+ config = SkBitmap::kARGB_8888_Config;
+ break;
+ default:
+ return false;
+ }
+
+ dst->setConfig(config, src->w, src->h, src->pitch);
+ dst->setPixels(src->pixels);
+ return true;
+}
+
+SkOSWindow::SkOSWindow(void* screen) {
+ fScreen = reinterpret_cast<SDL_Surface*>(screen);
+ this->resize(fScreen->w, fScreen->h);
+
+ uint32_t rmask = SK_R32_MASK << SK_R32_SHIFT;
+ uint32_t gmask = SK_G32_MASK << SK_G32_SHIFT;
+ uint32_t bmask = SK_B32_MASK << SK_B32_SHIFT;
+ uint32_t amask = SK_A32_MASK << SK_A32_SHIFT;
+
+ if (fScreen->flags & SDL_OPENGL) {
+ fSurface = NULL;
+ fGLCanvas = new SkGLCanvas;
+ fGLCanvas->setViewport(fScreen->w, fScreen->h);
+ } else {
+ fGLCanvas = NULL;
+ fSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, fScreen->w, fScreen->h,
+ 32, rmask, gmask, bmask, amask);
+ }
+}
+
+SkOSWindow::~SkOSWindow() {
+ delete fGLCanvas;
+ if (fSurface) {
+ SDL_FreeSurface(fSurface);
+ }
+}
+
+#include <OpenGL/gl.h>
+
+void SkOSWindow::doDraw() {
+ if (fGLCanvas) {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
+ glEnable(GL_TEXTURE_2D);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ int count = fGLCanvas->save();
+ this->draw(fGLCanvas);
+ fGLCanvas->restoreToCount(count);
+ SDL_GL_SwapBuffers( );
+ } else {
+ if ( SDL_MUSTLOCK(fSurface) ) {
+ if ( SDL_LockSurface(fSurface) < 0 ) {
+ return;
+ }
+ }
+
+ SkBitmap bitmap;
+
+ if (skia_setBitmapFromSurface(&bitmap, fSurface)) {
+ SkCanvas canvas(bitmap);
+ this->draw(&canvas);
+ }
+
+ if ( SDL_MUSTLOCK(fSurface) ) {
+ SDL_UnlockSurface(fSurface);
+ }
+
+ int result = SDL_BlitSurface(fSurface, NULL, fScreen, NULL);
+ if (result) {
+ SkDebugf("------- SDL_BlitSurface returned %d\n", result);
+ }
+ SDL_UpdateRect(fScreen, 0, 0, fScreen->w, fScreen->h);
+ }
+}
+
+static SkKey find_skkey(SDLKey src) {
+ // this array must match the enum order in SkKey.h
+ static const SDLKey gKeys[] = {
+ SDLK_UNKNOWN,
+ SDLK_UNKNOWN, // left softkey
+ SDLK_UNKNOWN, // right softkey
+ SDLK_UNKNOWN, // home
+ SDLK_UNKNOWN, // back
+ SDLK_UNKNOWN, // send
+ SDLK_UNKNOWN, // end
+ SDLK_0,
+ SDLK_1,
+ SDLK_2,
+ SDLK_3,
+ SDLK_4,
+ SDLK_5,
+ SDLK_6,
+ SDLK_7,
+ SDLK_8,
+ SDLK_9,
+ SDLK_ASTERISK,
+ SDLK_HASH,
+ SDLK_UP,
+ SDLK_DOWN,
+ SDLK_LEFT,
+ SDLK_RIGHT,
+ SDLK_RETURN, // OK
+ SDLK_UNKNOWN, // volume up
+ SDLK_UNKNOWN, // volume down
+ SDLK_UNKNOWN, // power
+ SDLK_UNKNOWN, // camera
+ };
+
+ const SDLKey* array = gKeys;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gKeys); i++) {
+ if (array[i] == src) {
+ return static_cast<SkKey>(i);
+ }
+ }
+ return kNONE_SkKey;
+}
+
+void SkOSWindow::handleSDLEvent(const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_VIDEORESIZE:
+ this->resize(event.resize.w, event.resize.h);
+ break;
+ case SDL_VIDEOEXPOSE:
+ this->doDraw();
+ break;
+ case SDL_MOUSEMOTION:
+ if (event.motion.state == SDL_PRESSED) {
+ this->handleClick(event.motion.x, event.motion.y,
+ SkView::Click::kMoved_State);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ this->handleClick(event.button.x, event.button.y,
+ event.button.state == SDL_PRESSED ?
+ SkView::Click::kDown_State :
+ SkView::Click::kUp_State);
+ break;
+ case SDL_KEYDOWN: {
+ SkKey sk = find_skkey(event.key.keysym.sym);
+ if (kNONE_SkKey != sk) {
+ if (event.key.state == SDL_PRESSED) {
+ this->handleKey(sk);
+ } else {
+ this->handleKeyUp(sk);
+ }
+ }
+ break;
+ }
+ case SDL_USEREVENT:
+ if (SkEvent::ProcessEvent()) {
+ post_SkEvent_event();
+ }
+ break;
+ }
+}
+
+void SkOSWindow::onHandleInval(const SkIRect& r) {
+ SDL_Event evt;
+ evt.type = SDL_VIDEOEXPOSE;
+ evt.expose.type = SDL_VIDEOEXPOSE;
+ SDL_PushEvent(&evt);
+}
+
+void SkOSWindow::onSetTitle(const char title[]) {
+ SDL_WM_SetCaption(title, NULL);
+}
+
+void SkOSWindow::onAddMenu(const SkOSMenu* sk_menu) {}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::SignalNonEmptyQueue() {
+ SkDebugf("-------- signal nonempty\n");
+ post_SkEvent_event();
+}
+
+static Uint32 timer_callback(Uint32 interval) {
+// SkDebugf("-------- timercallback %d\n", interval);
+ SkEvent::ServiceQueueTimer();
+ return 0;
+}
+
+void SkEvent::SignalQueueTimer(SkMSec delay)
+{
+ SDL_SetTimer(0, NULL);
+ if (delay) {
+ SDL_SetTimer(delay, timer_callback);
+ }
+}
diff --git a/views/unix/SkOSWindow_Unix.cpp b/views/unix/SkOSWindow_Unix.cpp
new file mode 100644
index 00000000..9fa2b304
--- /dev/null
+++ b/views/unix/SkOSWindow_Unix.cpp
@@ -0,0 +1,422 @@
+
+/*
+ * 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 <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/XKBlib.h>
+#include <GL/glx.h>
+#include <GL/gl.h>
+#include <GL/glu.h>
+
+#include "SkWindow.h"
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkEvent.h"
+#include "SkKey.h"
+#include "SkWindow.h"
+#include "XkeysToSkKeys.h"
+extern "C" {
+ #include "keysym2ucs.h"
+}
+
+const int WIDTH = 500;
+const int HEIGHT = 500;
+
+// Determine which events to listen for.
+const long EVENT_MASK = StructureNotifyMask|ButtonPressMask|ButtonReleaseMask
+ |ExposureMask|PointerMotionMask|KeyPressMask|KeyReleaseMask;
+
+SkOSWindow::SkOSWindow(void*)
+ : fVi(NULL)
+ , fMSAASampleCount(0) {
+ fUnixWindow.fDisplay = NULL;
+ fUnixWindow.fGLContext = NULL;
+ this->initWindow(0, NULL);
+ this->resize(WIDTH, HEIGHT);
+}
+
+SkOSWindow::~SkOSWindow() {
+ this->closeWindow();
+}
+
+void SkOSWindow::closeWindow() {
+ if (NULL != fUnixWindow.fDisplay) {
+ this->detach();
+ SkASSERT(NULL != fUnixWindow.fGc);
+ XFreeGC(fUnixWindow.fDisplay, fUnixWindow.fGc);
+ fUnixWindow.fGc = NULL;
+ XDestroyWindow(fUnixWindow.fDisplay, fUnixWindow.fWin);
+ fVi = NULL;
+ XCloseDisplay(fUnixWindow.fDisplay);
+ fUnixWindow.fDisplay = NULL;
+ fMSAASampleCount = 0;
+ }
+}
+
+void SkOSWindow::initWindow(int requestedMSAASampleCount, AttachmentInfo* info) {
+ if (fMSAASampleCount != requestedMSAASampleCount) {
+ this->closeWindow();
+ }
+ // presence of fDisplay means we already have a window
+ if (NULL != fUnixWindow.fDisplay) {
+ if (NULL != info) {
+ if (NULL != fVi) {
+ glXGetConfig(fUnixWindow.fDisplay, fVi, GLX_SAMPLES_ARB, &info->fSampleCount);
+ glXGetConfig(fUnixWindow.fDisplay, fVi, GLX_STENCIL_SIZE, &info->fStencilBits);
+ } else {
+ info->fSampleCount = 0;
+ info->fStencilBits = 0;
+ }
+ }
+ return;
+ }
+ fUnixWindow.fDisplay = XOpenDisplay(NULL);
+ Display* dsp = fUnixWindow.fDisplay;
+ if (NULL == dsp) {
+ SkDebugf("Could not open an X Display");
+ return;
+ }
+ // Attempt to create a window that supports GL
+ GLint att[] = {
+ GLX_RGBA,
+ GLX_DEPTH_SIZE, 24,
+ GLX_DOUBLEBUFFER,
+ GLX_STENCIL_SIZE, 8,
+ None
+ };
+ SkASSERT(NULL == fVi);
+ if (requestedMSAASampleCount > 0) {
+ static const GLint kAttCount = SK_ARRAY_COUNT(att);
+ GLint msaaAtt[kAttCount + 4];
+ memcpy(msaaAtt, att, sizeof(att));
+ SkASSERT(None == msaaAtt[kAttCount - 1]);
+ msaaAtt[kAttCount - 1] = GLX_SAMPLE_BUFFERS_ARB;
+ msaaAtt[kAttCount + 0] = 1;
+ msaaAtt[kAttCount + 1] = GLX_SAMPLES_ARB;
+ msaaAtt[kAttCount + 2] = requestedMSAASampleCount;
+ msaaAtt[kAttCount + 3] = None;
+ fVi = glXChooseVisual(dsp, DefaultScreen(dsp), msaaAtt);
+ fMSAASampleCount = requestedMSAASampleCount;
+ }
+ if (NULL == fVi) {
+ fVi = glXChooseVisual(dsp, DefaultScreen(dsp), att);
+ fMSAASampleCount = 0;
+ }
+
+ if (fVi) {
+ if (NULL != info) {
+ glXGetConfig(dsp, fVi, GLX_SAMPLES_ARB, &info->fSampleCount);
+ glXGetConfig(dsp, fVi, GLX_STENCIL_SIZE, &info->fStencilBits);
+ }
+ Colormap colorMap = XCreateColormap(dsp,
+ RootWindow(dsp, fVi->screen),
+ fVi->visual,
+ AllocNone);
+ XSetWindowAttributes swa;
+ swa.colormap = colorMap;
+ swa.event_mask = EVENT_MASK;
+ fUnixWindow.fWin = XCreateWindow(dsp,
+ RootWindow(dsp, fVi->screen),
+ 0, 0, // x, y
+ WIDTH, HEIGHT,
+ 0, // border width
+ fVi->depth,
+ InputOutput,
+ fVi->visual,
+ CWEventMask | CWColormap,
+ &swa);
+ } else {
+ if (NULL != info) {
+ info->fSampleCount = 0;
+ info->fStencilBits = 0;
+ }
+ // Create a simple window instead. We will not be able to show GL
+ fUnixWindow.fWin = XCreateSimpleWindow(dsp,
+ DefaultRootWindow(dsp),
+ 0, 0, // x, y
+ WIDTH, HEIGHT,
+ 0, // border width
+ 0, // border value
+ 0); // background value
+ }
+ this->mapWindowAndWait();
+ fUnixWindow.fGc = XCreateGC(dsp, fUnixWindow.fWin, 0, NULL);
+}
+
+static unsigned getModi(const XEvent& evt) {
+ static const struct {
+ unsigned fXMask;
+ unsigned fSkMask;
+ } gModi[] = {
+ // X values found by experiment. Is there a better way?
+ { 1, kShift_SkModifierKey },
+ { 4, kControl_SkModifierKey },
+ { 8, kOption_SkModifierKey },
+ };
+
+ unsigned modi = 0;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModi); ++i) {
+ if (evt.xkey.state & gModi[i].fXMask) {
+ modi |= gModi[i].fSkMask;
+ }
+ }
+ return modi;
+}
+
+static SkMSec gTimerDelay;
+
+static bool MyXNextEventWithDelay(Display* dsp, XEvent* evt) {
+ SkMSec ms = gTimerDelay;
+ if (ms > 0) {
+ int x11_fd = ConnectionNumber(dsp);
+ fd_set input_fds;
+ FD_ZERO(&input_fds);
+ FD_SET(x11_fd, &input_fds);
+
+ timeval tv;
+ tv.tv_sec = ms / 1000; // seconds
+ tv.tv_usec = (ms % 1000) * 1000; // microseconds
+
+ if (!select(x11_fd + 1, &input_fds, NULL, NULL, &tv)) {
+ if (!XPending(dsp)) {
+ return false;
+ }
+ }
+ }
+ XNextEvent(dsp, evt);
+ return true;
+}
+
+SkOSWindow::NextXEventResult SkOSWindow::nextXEvent() {
+ XEvent evt;
+ Display* dsp = fUnixWindow.fDisplay;
+
+ if (!MyXNextEventWithDelay(fUnixWindow.fDisplay, &evt)) {
+ return kContinue_NextXEventResult;
+ }
+
+ switch (evt.type) {
+ case Expose:
+ if (0 == evt.xexpose.count) {
+ return kPaintRequest_NextXEventResult;
+ }
+ break;
+ case ConfigureNotify:
+ this->resize(evt.xconfigure.width, evt.xconfigure.height);
+ break;
+ case ButtonPress:
+ if (evt.xbutton.button == Button1)
+ this->handleClick(evt.xbutton.x, evt.xbutton.y,
+ SkView::Click::kDown_State, NULL, getModi(evt));
+ break;
+ case ButtonRelease:
+ if (evt.xbutton.button == Button1)
+ this->handleClick(evt.xbutton.x, evt.xbutton.y,
+ SkView::Click::kUp_State, NULL, getModi(evt));
+ break;
+ case MotionNotify:
+ this->handleClick(evt.xmotion.x, evt.xmotion.y,
+ SkView::Click::kMoved_State, NULL, getModi(evt));
+ break;
+ case KeyPress: {
+ int shiftLevel = (evt.xkey.state & ShiftMask) ? 1 : 0;
+ KeySym keysym = XkbKeycodeToKeysym(dsp, evt.xkey.keycode,
+ 0, shiftLevel);
+ if (keysym == XK_Escape) {
+ return kQuitRequest_NextXEventResult;
+ }
+ this->handleKey(XKeyToSkKey(keysym));
+ long uni = keysym2ucs(keysym);
+ if (uni != -1) {
+ this->handleChar((SkUnichar) uni);
+ }
+ break;
+ }
+ case KeyRelease:
+ this->handleKeyUp(XKeyToSkKey(XkbKeycodeToKeysym(dsp, evt.xkey.keycode, 0, 0)));
+ break;
+ default:
+ // Do nothing for other events
+ break;
+ }
+ return kContinue_NextXEventResult;
+}
+
+void SkOSWindow::loop() {
+ Display* dsp = fUnixWindow.fDisplay;
+ if (NULL == dsp) {
+ return;
+ }
+ Window win = fUnixWindow.fWin;
+
+ XSelectInput(dsp, win, EVENT_MASK);
+
+ bool sentExposeEvent = false;
+
+ for (;;) {
+ SkEvent::ServiceQueueTimer();
+
+ bool moreToDo = SkEvent::ProcessEvent();
+
+ if (this->isDirty() && !sentExposeEvent) {
+ sentExposeEvent = true;
+
+ XEvent evt;
+ sk_bzero(&evt, sizeof(evt));
+ evt.type = Expose;
+ evt.xexpose.display = dsp;
+ XSendEvent(dsp, win, false, ExposureMask, &evt);
+ }
+
+ if (XPending(dsp) || !moreToDo) {
+ switch (this->nextXEvent()) {
+ case kContinue_NextXEventResult:
+ break;
+ case kPaintRequest_NextXEventResult:
+ sentExposeEvent = false;
+ if (this->isDirty()) {
+ this->update(NULL);
+ }
+ this->doPaint();
+ break;
+ case kQuitRequest_NextXEventResult:
+ return;
+ }
+ }
+ }
+}
+
+void SkOSWindow::mapWindowAndWait() {
+ SkASSERT(NULL != fUnixWindow.fDisplay);
+ Display* dsp = fUnixWindow.fDisplay;
+ Window win = fUnixWindow.fWin;
+ XMapWindow(dsp, win);
+
+ long eventMask = StructureNotifyMask;
+ XSelectInput(dsp, win, eventMask);
+
+ // Wait until screen is ready.
+ XEvent evt;
+ do {
+ XNextEvent(dsp, &evt);
+ } while(evt.type != MapNotify);
+
+}
+
+bool SkOSWindow::attach(SkBackEndTypes, int msaaSampleCount, AttachmentInfo* info) {
+ this->initWindow(msaaSampleCount, info);
+
+ if (NULL == fUnixWindow.fDisplay) {
+ return false;
+ }
+ if (NULL == fUnixWindow.fGLContext) {
+ SkASSERT(NULL != fVi);
+
+ fUnixWindow.fGLContext = glXCreateContext(fUnixWindow.fDisplay,
+ fVi,
+ NULL,
+ GL_TRUE);
+ if (NULL == fUnixWindow.fGLContext) {
+ return false;
+ }
+ }
+ glXMakeCurrent(fUnixWindow.fDisplay,
+ fUnixWindow.fWin,
+ fUnixWindow.fGLContext);
+ glViewport(0, 0,
+ SkScalarRound(this->width()), SkScalarRound(this->height()));
+ glClearColor(0, 0, 0, 0);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ return true;
+}
+
+void SkOSWindow::detach() {
+ if (NULL == fUnixWindow.fDisplay || NULL == fUnixWindow.fGLContext) {
+ return;
+ }
+ glXMakeCurrent(fUnixWindow.fDisplay, None, NULL);
+ glXDestroyContext(fUnixWindow.fDisplay, fUnixWindow.fGLContext);
+ fUnixWindow.fGLContext = NULL;
+}
+
+void SkOSWindow::present() {
+ if (NULL != fUnixWindow.fDisplay && NULL != fUnixWindow.fGLContext) {
+ glXSwapBuffers(fUnixWindow.fDisplay, fUnixWindow.fWin);
+ }
+}
+
+void SkOSWindow::onSetTitle(const char title[]) {
+ if (NULL == fUnixWindow.fDisplay) {
+ return;
+ }
+ XTextProperty textProp;
+ textProp.value = (unsigned char*)title;
+ textProp.format = 8;
+ textProp.nitems = strlen((char*)textProp.value);
+ textProp.encoding = XA_STRING;
+ XSetWMName(fUnixWindow.fDisplay, fUnixWindow.fWin, &textProp);
+}
+
+static bool convertBitmapToXImage(XImage& image, const SkBitmap& bitmap) {
+ sk_bzero(&image, sizeof(image));
+
+ int bitsPerPixel = bitmap.bytesPerPixel() * 8;
+ image.width = bitmap.width();
+ image.height = bitmap.height();
+ image.format = ZPixmap;
+ image.data = (char*) bitmap.getPixels();
+ image.byte_order = LSBFirst;
+ image.bitmap_unit = bitsPerPixel;
+ image.bitmap_bit_order = LSBFirst;
+ image.bitmap_pad = bitsPerPixel;
+ image.depth = 24;
+ image.bytes_per_line = bitmap.rowBytes() - bitmap.width() * 4;
+ image.bits_per_pixel = bitsPerPixel;
+ return XInitImage(&image);
+}
+
+void SkOSWindow::doPaint() {
+ if (NULL == fUnixWindow.fDisplay) {
+ return;
+ }
+ // If we are drawing with GL, we don't need XPutImage.
+ if (NULL != fUnixWindow.fGLContext) {
+ return;
+ }
+ // Draw the bitmap to the screen.
+ const SkBitmap& bitmap = getBitmap();
+ int width = bitmap.width();
+ int height = bitmap.height();
+
+ XImage image;
+ if (!convertBitmapToXImage(image, bitmap)) {
+ return;
+ }
+
+ XPutImage(fUnixWindow.fDisplay,
+ fUnixWindow.fWin,
+ fUnixWindow.fGc,
+ &image,
+ 0, 0, // src x,y
+ 0, 0, // dst x,y
+ width, height);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::SignalNonEmptyQueue() {
+ // nothing to do, since we spin on our event-queue, polling for XPending
+}
+
+void SkEvent::SignalQueueTimer(SkMSec delay) {
+ // just need to record the delay time. We handle waking up for it in
+ // MyXNextEventWithDelay()
+ gTimerDelay = delay;
+}
diff --git a/views/unix/keysym2ucs.c b/views/unix/keysym2ucs.c
new file mode 100644
index 00000000..a0c4ced9
--- /dev/null
+++ b/views/unix/keysym2ucs.c
@@ -0,0 +1,848 @@
+/* $XFree86$
+ * This module converts keysym values into the corresponding ISO 10646
+ * (UCS, Unicode) values.
+ *
+ * The array keysymtab[] contains pairs of X11 keysym values for graphical
+ * characters and the corresponding Unicode value. The function
+ * keysym2ucs() maps a keysym onto a Unicode value using a binary search,
+ * therefore keysymtab[] must remain SORTED by keysym value.
+ *
+ * The keysym -> UTF-8 conversion will hopefully one day be provided
+ * by Xlib via XmbLookupString() and should ideally not have to be
+ * done in X applications. But we are not there yet.
+ *
+ * We allow to represent any UCS character in the range U-00000000 to
+ * U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
+ * This admittedly does not cover the entire 31-bit space of UCS, but
+ * it does cover all of the characters up to U-10FFFF, which can be
+ * represented by UTF-16, and more, and it is very unlikely that higher
+ * UCS codes will ever be assigned by ISO. So to get Unicode character
+ * U+ABCD you can directly use keysym 0x0100abcd.
+ *
+ * NOTE: The comments in the table below contain the actual character
+ * encoded in UTF-8, so for viewing and editing best use an editor in
+ * UTF-8 mode.
+ *
+ * Author: Markus G. Kuhn <http://www.cl.cam.ac.uk/~mgk25/>,
+ * University of Cambridge, April 2001
+ *
+ * Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
+ * an initial draft of the mapping table.
+ *
+ * This software is in the public domain. Share and enjoy!
+ *
+ * AUTOMATICALLY GENERATED FILE, DO NOT EDIT !!! (unicode/convmap.pl)
+ */
+
+#include "keysym2ucs.h"
+
+struct codepair {
+ unsigned short keysym;
+ unsigned short ucs;
+} keysymtab[] = {
+ { 0x01a1, 0x0104 }, /* Aogonek Ä„ LATIN CAPITAL LETTER A WITH OGONEK */
+ { 0x01a2, 0x02d8 }, /* breve ˘ BREVE */
+ { 0x01a3, 0x0141 }, /* Lstroke Å LATIN CAPITAL LETTER L WITH STROKE */
+ { 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
+ { 0x01a6, 0x015a }, /* Sacute Åš LATIN CAPITAL LETTER S WITH ACUTE */
+ { 0x01a9, 0x0160 }, /* Scaron Å  LATIN CAPITAL LETTER S WITH CARON */
+ { 0x01aa, 0x015e }, /* Scedilla Åž LATIN CAPITAL LETTER S WITH CEDILLA */
+ { 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
+ { 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
+ { 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
+ { 0x01af, 0x017b }, /* Zabovedot Å» LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+ { 0x01b1, 0x0105 }, /* aogonek Ä… LATIN SMALL LETTER A WITH OGONEK */
+ { 0x01b2, 0x02db }, /* ogonek Ë› OGONEK */
+ { 0x01b3, 0x0142 }, /* lstroke Å‚ LATIN SMALL LETTER L WITH STROKE */
+ { 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */
+ { 0x01b6, 0x015b }, /* sacute Å› LATIN SMALL LETTER S WITH ACUTE */
+ { 0x01b7, 0x02c7 }, /* caron ˇ CARON */
+ { 0x01b9, 0x0161 }, /* scaron Å¡ LATIN SMALL LETTER S WITH CARON */
+ { 0x01ba, 0x015f }, /* scedilla ÅŸ LATIN SMALL LETTER S WITH CEDILLA */
+ { 0x01bb, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */
+ { 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */
+ { 0x01bd, 0x02dd }, /* doubleacute Ë DOUBLE ACUTE ACCENT */
+ { 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */
+ { 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
+ { 0x01c0, 0x0154 }, /* Racute Å” LATIN CAPITAL LETTER R WITH ACUTE */
+ { 0x01c3, 0x0102 }, /* Abreve Ä‚ LATIN CAPITAL LETTER A WITH BREVE */
+ { 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
+ { 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
+ { 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
+ { 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
+ { 0x01cc, 0x011a }, /* Ecaron Äš LATIN CAPITAL LETTER E WITH CARON */
+ { 0x01cf, 0x010e }, /* Dcaron ÄŽ LATIN CAPITAL LETTER D WITH CARON */
+ { 0x01d0, 0x0110 }, /* Dstroke Ä LATIN CAPITAL LETTER D WITH STROKE */
+ { 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
+ { 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
+ { 0x01d5, 0x0150 }, /* Odoubleacute Å LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+ { 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
+ { 0x01d9, 0x016e }, /* Uring Å® LATIN CAPITAL LETTER U WITH RING ABOVE */
+ { 0x01db, 0x0170 }, /* Udoubleacute Å° LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+ { 0x01de, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
+ { 0x01e0, 0x0155 }, /* racute Å• LATIN SMALL LETTER R WITH ACUTE */
+ { 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */
+ { 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
+ { 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */
+ { 0x01e8, 0x010d }, /* ccaron Ä LATIN SMALL LETTER C WITH CARON */
+ { 0x01ea, 0x0119 }, /* eogonek Ä™ LATIN SMALL LETTER E WITH OGONEK */
+ { 0x01ec, 0x011b }, /* ecaron Ä› LATIN SMALL LETTER E WITH CARON */
+ { 0x01ef, 0x010f }, /* dcaron Ä LATIN SMALL LETTER D WITH CARON */
+ { 0x01f0, 0x0111 }, /* dstroke Ä‘ LATIN SMALL LETTER D WITH STROKE */
+ { 0x01f1, 0x0144 }, /* nacute Å„ LATIN SMALL LETTER N WITH ACUTE */
+ { 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */
+ { 0x01f5, 0x0151 }, /* odoubleacute Å‘ LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+ { 0x01f8, 0x0159 }, /* rcaron Å™ LATIN SMALL LETTER R WITH CARON */
+ { 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */
+ { 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+ { 0x01fe, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
+ { 0x01ff, 0x02d9 }, /* abovedot Ë™ DOT ABOVE */
+ { 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
+ { 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+ { 0x02a9, 0x0130 }, /* Iabovedot Ä° LATIN CAPITAL LETTER I WITH DOT ABOVE */
+ { 0x02ab, 0x011e }, /* Gbreve Äž LATIN CAPITAL LETTER G WITH BREVE */
+ { 0x02ac, 0x0134 }, /* Jcircumflex Ä´ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+ { 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */
+ { 0x02b6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
+ { 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */
+ { 0x02bb, 0x011f }, /* gbreve ÄŸ LATIN SMALL LETTER G WITH BREVE */
+ { 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
+ { 0x02c5, 0x010a }, /* Cabovedot ÄŠ LATIN CAPITAL LETTER C WITH DOT ABOVE */
+ { 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+ { 0x02d5, 0x0120 }, /* Gabovedot Ä  LATIN CAPITAL LETTER G WITH DOT ABOVE */
+ { 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+ { 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
+ { 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+ { 0x02e5, 0x010b }, /* cabovedot Ä‹ LATIN SMALL LETTER C WITH DOT ABOVE */
+ { 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
+ { 0x02f5, 0x0121 }, /* gabovedot Ä¡ LATIN SMALL LETTER G WITH DOT ABOVE */
+ { 0x02f8, 0x011d }, /* gcircumflex Ä LATIN SMALL LETTER G WITH CIRCUMFLEX */
+ { 0x02fd, 0x016d }, /* ubreve Å­ LATIN SMALL LETTER U WITH BREVE */
+ { 0x02fe, 0x015d }, /* scircumflex Å LATIN SMALL LETTER S WITH CIRCUMFLEX */
+ { 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */
+ { 0x03a3, 0x0156 }, /* Rcedilla Å– LATIN CAPITAL LETTER R WITH CEDILLA */
+ { 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
+ { 0x03a6, 0x013b }, /* Lcedilla Ä» LATIN CAPITAL LETTER L WITH CEDILLA */
+ { 0x03aa, 0x0112 }, /* Emacron Ä’ LATIN CAPITAL LETTER E WITH MACRON */
+ { 0x03ab, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
+ { 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
+ { 0x03b3, 0x0157 }, /* rcedilla Å— LATIN SMALL LETTER R WITH CEDILLA */
+ { 0x03b5, 0x0129 }, /* itilde Ä© LATIN SMALL LETTER I WITH TILDE */
+ { 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
+ { 0x03ba, 0x0113 }, /* emacron Ä“ LATIN SMALL LETTER E WITH MACRON */
+ { 0x03bb, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
+ { 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */
+ { 0x03bd, 0x014a }, /* ENG ÅŠ LATIN CAPITAL LETTER ENG */
+ { 0x03bf, 0x014b }, /* eng Å‹ LATIN SMALL LETTER ENG */
+ { 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
+ { 0x03c7, 0x012e }, /* Iogonek Ä® LATIN CAPITAL LETTER I WITH OGONEK */
+ { 0x03cc, 0x0116 }, /* Eabovedot Ä– LATIN CAPITAL LETTER E WITH DOT ABOVE */
+ { 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
+ { 0x03d1, 0x0145 }, /* Ncedilla Å… LATIN CAPITAL LETTER N WITH CEDILLA */
+ { 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
+ { 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
+ { 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
+ { 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
+ { 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
+ { 0x03e0, 0x0101 }, /* amacron Ä LATIN SMALL LETTER A WITH MACRON */
+ { 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */
+ { 0x03ec, 0x0117 }, /* eabovedot Ä— LATIN SMALL LETTER E WITH DOT ABOVE */
+ { 0x03ef, 0x012b }, /* imacron Ä« LATIN SMALL LETTER I WITH MACRON */
+ { 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
+ { 0x03f2, 0x014d }, /* omacron Å LATIN SMALL LETTER O WITH MACRON */
+ { 0x03f3, 0x0137 }, /* kcedilla Ä· LATIN SMALL LETTER K WITH CEDILLA */
+ { 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */
+ { 0x03fd, 0x0169 }, /* utilde Å© LATIN SMALL LETTER U WITH TILDE */
+ { 0x03fe, 0x016b }, /* umacron Å« LATIN SMALL LETTER U WITH MACRON */
+ { 0x047e, 0x203e }, /* overline ‾ OVERLINE */
+ { 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */
+ { 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */
+ { 0x04a3, 0x300d }, /* kana_closingbracket 〠RIGHT CORNER BRACKET */
+ { 0x04a4, 0x3001 }, /* kana_comma 〠IDEOGRAPHIC COMMA */
+ { 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */
+ { 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */
+ { 0x04a7, 0x30a1 }, /* kana_a ã‚¡ KATAKANA LETTER SMALL A */
+ { 0x04a8, 0x30a3 }, /* kana_i ã‚£ KATAKANA LETTER SMALL I */
+ { 0x04a9, 0x30a5 }, /* kana_u ã‚¥ KATAKANA LETTER SMALL U */
+ { 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */
+ { 0x04ab, 0x30a9 }, /* kana_o ã‚© KATAKANA LETTER SMALL O */
+ { 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */
+ { 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */
+ { 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */
+ { 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */
+ { 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+ { 0x04b1, 0x30a2 }, /* kana_A ã‚¢ KATAKANA LETTER A */
+ { 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */
+ { 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */
+ { 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */
+ { 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */
+ { 0x04b6, 0x30ab }, /* kana_KA ã‚« KATAKANA LETTER KA */
+ { 0x04b7, 0x30ad }, /* kana_KI ã‚­ KATAKANA LETTER KI */
+ { 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */
+ { 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */
+ { 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */
+ { 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */
+ { 0x04bc, 0x30b7 }, /* kana_SHI ã‚· KATAKANA LETTER SI */
+ { 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */
+ { 0x04be, 0x30bb }, /* kana_SE ã‚» KATAKANA LETTER SE */
+ { 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */
+ { 0x04c0, 0x30bf }, /* kana_TA ã‚¿ KATAKANA LETTER TA */
+ { 0x04c1, 0x30c1 }, /* kana_CHI ムKATAKANA LETTER TI */
+ { 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */
+ { 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */
+ { 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */
+ { 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */
+ { 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */
+ { 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */
+ { 0x04c8, 0x30cd }, /* kana_NE ムKATAKANA LETTER NE */
+ { 0x04c9, 0x30ce }, /* kana_NO ノ KATAKANA LETTER NO */
+ { 0x04ca, 0x30cf }, /* kana_HA ムKATAKANA LETTER HA */
+ { 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */
+ { 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */
+ { 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */
+ { 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */
+ { 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */
+ { 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */
+ { 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */
+ { 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */
+ { 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */
+ { 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */
+ { 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */
+ { 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */
+ { 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */
+ { 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */
+ { 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */
+ { 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */
+ { 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */
+ { 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */
+ { 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */
+ { 0x04de, 0x309b }, /* voicedsound ã‚› KATAKANA-HIRAGANA VOICED SOUND MARK */
+ { 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+ { 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */
+ { 0x05bb, 0x061b }, /* Arabic_semicolon Ø› ARABIC SEMICOLON */
+ { 0x05bf, 0x061f }, /* Arabic_question_mark ØŸ ARABIC QUESTION MARK */
+ { 0x05c1, 0x0621 }, /* Arabic_hamza Ø¡ ARABIC LETTER HAMZA */
+ { 0x05c2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
+ { 0x05c3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
+ { 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
+ { 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
+ { 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
+ { 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */
+ { 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */
+ { 0x05c9, 0x0629 }, /* Arabic_tehmarbuta Ø© ARABIC LETTER TEH MARBUTA */
+ { 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */
+ { 0x05cb, 0x062b }, /* Arabic_theh Ø« ARABIC LETTER THEH */
+ { 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */
+ { 0x05cd, 0x062d }, /* Arabic_hah Ø­ ARABIC LETTER HAH */
+ { 0x05ce, 0x062e }, /* Arabic_khah Ø® ARABIC LETTER KHAH */
+ { 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */
+ { 0x05d0, 0x0630 }, /* Arabic_thal Ø° ARABIC LETTER THAL */
+ { 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */
+ { 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */
+ { 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */
+ { 0x05d4, 0x0634 }, /* Arabic_sheen Ø´ ARABIC LETTER SHEEN */
+ { 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */
+ { 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */
+ { 0x05d7, 0x0637 }, /* Arabic_tah Ø· ARABIC LETTER TAH */
+ { 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */
+ { 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */
+ { 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */
+ { 0x05e0, 0x0640 }, /* Arabic_tatweel Ù€ ARABIC TATWEEL */
+ { 0x05e1, 0x0641 }, /* Arabic_feh Ù ARABIC LETTER FEH */
+ { 0x05e2, 0x0642 }, /* Arabic_qaf Ù‚ ARABIC LETTER QAF */
+ { 0x05e3, 0x0643 }, /* Arabic_kaf Ùƒ ARABIC LETTER KAF */
+ { 0x05e4, 0x0644 }, /* Arabic_lam Ù„ ARABIC LETTER LAM */
+ { 0x05e5, 0x0645 }, /* Arabic_meem Ù… ARABIC LETTER MEEM */
+ { 0x05e6, 0x0646 }, /* Arabic_noon Ù† ARABIC LETTER NOON */
+ { 0x05e7, 0x0647 }, /* Arabic_ha Ù‡ ARABIC LETTER HEH */
+ { 0x05e8, 0x0648 }, /* Arabic_waw Ùˆ ARABIC LETTER WAW */
+ { 0x05e9, 0x0649 }, /* Arabic_alefmaksura Ù‰ ARABIC LETTER ALEF MAKSURA */
+ { 0x05ea, 0x064a }, /* Arabic_yeh ÙŠ ARABIC LETTER YEH */
+ { 0x05eb, 0x064b }, /* Arabic_fathatan Ù‹ ARABIC FATHATAN */
+ { 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */
+ { 0x05ed, 0x064d }, /* Arabic_kasratan Ù ARABIC KASRATAN */
+ { 0x05ee, 0x064e }, /* Arabic_fatha ÙŽ ARABIC FATHA */
+ { 0x05ef, 0x064f }, /* Arabic_damma Ù ARABIC DAMMA */
+ { 0x05f0, 0x0650 }, /* Arabic_kasra Ù ARABIC KASRA */
+ { 0x05f1, 0x0651 }, /* Arabic_shadda Ù‘ ARABIC SHADDA */
+ { 0x05f2, 0x0652 }, /* Arabic_sukun Ù’ ARABIC SUKUN */
+ { 0x06a1, 0x0452 }, /* Serbian_dje Ñ’ CYRILLIC SMALL LETTER DJE */
+ { 0x06a2, 0x0453 }, /* Macedonia_gje Ñ“ CYRILLIC SMALL LETTER GJE */
+ { 0x06a3, 0x0451 }, /* Cyrillic_io Ñ‘ CYRILLIC SMALL LETTER IO */
+ { 0x06a4, 0x0454 }, /* Ukrainian_ie Ñ” CYRILLIC SMALL LETTER UKRAINIAN IE */
+ { 0x06a5, 0x0455 }, /* Macedonia_dse Ñ• CYRILLIC SMALL LETTER DZE */
+ { 0x06a6, 0x0456 }, /* Ukrainian_i Ñ– CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+ { 0x06a7, 0x0457 }, /* Ukrainian_yi Ñ— CYRILLIC SMALL LETTER YI */
+ { 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */
+ { 0x06a9, 0x0459 }, /* Cyrillic_lje Ñ™ CYRILLIC SMALL LETTER LJE */
+ { 0x06aa, 0x045a }, /* Cyrillic_nje Ñš CYRILLIC SMALL LETTER NJE */
+ { 0x06ab, 0x045b }, /* Serbian_tshe Ñ› CYRILLIC SMALL LETTER TSHE */
+ { 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
+ { 0x06ae, 0x045e }, /* Byelorussian_shortu Ñž CYRILLIC SMALL LETTER SHORT U */
+ { 0x06af, 0x045f }, /* Cyrillic_dzhe ÑŸ CYRILLIC SMALL LETTER DZHE */
+ { 0x06b0, 0x2116 }, /* numerosign â„– NUMERO SIGN */
+ { 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
+ { 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
+ { 0x06b3, 0x0401 }, /* Cyrillic_IO Ð CYRILLIC CAPITAL LETTER IO */
+ { 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+ { 0x06b5, 0x0405 }, /* Macedonia_DSE Ð… CYRILLIC CAPITAL LETTER DZE */
+ { 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+ { 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
+ { 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
+ { 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
+ { 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
+ { 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
+ { 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
+ { 0x06be, 0x040e }, /* Byelorussian_SHORTU ÐŽ CYRILLIC CAPITAL LETTER SHORT U */
+ { 0x06bf, 0x040f }, /* Cyrillic_DZHE Ð CYRILLIC CAPITAL LETTER DZHE */
+ { 0x06c0, 0x044e }, /* Cyrillic_yu ÑŽ CYRILLIC SMALL LETTER YU */
+ { 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */
+ { 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */
+ { 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
+ { 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */
+ { 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */
+ { 0x06c6, 0x0444 }, /* Cyrillic_ef Ñ„ CYRILLIC SMALL LETTER EF */
+ { 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
+ { 0x06c8, 0x0445 }, /* Cyrillic_ha Ñ… CYRILLIC SMALL LETTER HA */
+ { 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */
+ { 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
+ { 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */
+ { 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */
+ { 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */
+ { 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */
+ { 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */
+ { 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */
+ { 0x06d1, 0x044f }, /* Cyrillic_ya Ñ CYRILLIC SMALL LETTER YA */
+ { 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */
+ { 0x06d3, 0x0441 }, /* Cyrillic_es Ñ CYRILLIC SMALL LETTER ES */
+ { 0x06d4, 0x0442 }, /* Cyrillic_te Ñ‚ CYRILLIC SMALL LETTER TE */
+ { 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */
+ { 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
+ { 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */
+ { 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
+ { 0x06d9, 0x044b }, /* Cyrillic_yeru Ñ‹ CYRILLIC SMALL LETTER YERU */
+ { 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
+ { 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
+ { 0x06dc, 0x044d }, /* Cyrillic_e Ñ CYRILLIC SMALL LETTER E */
+ { 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
+ { 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
+ { 0x06df, 0x044a }, /* Cyrillic_hardsign ÑŠ CYRILLIC SMALL LETTER HARD SIGN */
+ { 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
+ { 0x06e1, 0x0410 }, /* Cyrillic_A Ð CYRILLIC CAPITAL LETTER A */
+ { 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
+ { 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
+ { 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
+ { 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
+ { 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
+ { 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
+ { 0x06e8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
+ { 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */
+ { 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
+ { 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
+ { 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
+ { 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
+ { 0x06ee, 0x041d }, /* Cyrillic_EN Ð CYRILLIC CAPITAL LETTER EN */
+ { 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */
+ { 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
+ { 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
+ { 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
+ { 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
+ { 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
+ { 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */
+ { 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
+ { 0x06f7, 0x0412 }, /* Cyrillic_VE Ð’ CYRILLIC CAPITAL LETTER VE */
+ { 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
+ { 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
+ { 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
+ { 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
+ { 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
+ { 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
+ { 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
+ { 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
+ { 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
+ { 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
+ { 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
+ { 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
+ { 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+ { 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
+ { 0x07a8, 0x038e }, /* Greek_UPSILONaccent ÎŽ GREEK CAPITAL LETTER UPSILON WITH TONOS */
+ { 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+ { 0x07ab, 0x038f }, /* Greek_OMEGAaccent Î GREEK CAPITAL LETTER OMEGA WITH TONOS */
+ { 0x07ae, 0x0385 }, /* Greek_accentdieresis Î… GREEK DIALYTIKA TONOS */
+ { 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */
+ { 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
+ { 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
+ { 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
+ { 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
+ { 0x07b5, 0x03ca }, /* Greek_iotadieresis ÏŠ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+ { 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis Î GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+ { 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
+ { 0x07b8, 0x03cd }, /* Greek_upsilonaccent Ï GREEK SMALL LETTER UPSILON WITH TONOS */
+ { 0x07b9, 0x03cb }, /* Greek_upsilondieresis Ï‹ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+ { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+ { 0x07bb, 0x03ce }, /* Greek_omegaaccent ÏŽ GREEK SMALL LETTER OMEGA WITH TONOS */
+ { 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
+ { 0x07c2, 0x0392 }, /* Greek_BETA Î’ GREEK CAPITAL LETTER BETA */
+ { 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
+ { 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
+ { 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
+ { 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
+ { 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */
+ { 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */
+ { 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
+ { 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
+ { 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
+ { 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */
+ { 0x07cd, 0x039d }, /* Greek_NU Î GREEK CAPITAL LETTER NU */
+ { 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */
+ { 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
+ { 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */
+ { 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
+ { 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
+ { 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */
+ { 0x07d5, 0x03a5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
+ { 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */
+ { 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */
+ { 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
+ { 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
+ { 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */
+ { 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */
+ { 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */
+ { 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */
+ { 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */
+ { 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */
+ { 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */
+ { 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */
+ { 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */
+ { 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */
+ { 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */
+ { 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */
+ { 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */
+ { 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */
+ { 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */
+ { 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */
+ { 0x07f1, 0x03c1 }, /* Greek_rho Ï GREEK SMALL LETTER RHO */
+ { 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */
+ { 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma Ï‚ GREEK SMALL LETTER FINAL SIGMA */
+ { 0x07f4, 0x03c4 }, /* Greek_tau Ï„ GREEK SMALL LETTER TAU */
+ { 0x07f5, 0x03c5 }, /* Greek_upsilon Ï… GREEK SMALL LETTER UPSILON */
+ { 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */
+ { 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */
+ { 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */
+ { 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */
+ { 0x08a1, 0x23b7 }, /* leftradical ⎷ ??? */
+ { 0x08a2, 0x250c }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+ { 0x08a3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */
+ { 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */
+ { 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */
+ { 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
+ { 0x08a7, 0x23a1 }, /* topleftsqbracket ⎡ ??? */
+ { 0x08a8, 0x23a3 }, /* botleftsqbracket ⎣ ??? */
+ { 0x08a9, 0x23a4 }, /* toprightsqbracket ⎤ ??? */
+ { 0x08aa, 0x23a6 }, /* botrightsqbracket ⎦ ??? */
+ { 0x08ab, 0x239b }, /* topleftparens ⎛ ??? */
+ { 0x08ac, 0x239d }, /* botleftparens ⎠??? */
+ { 0x08ad, 0x239e }, /* toprightparens ⎞ ??? */
+ { 0x08ae, 0x23a0 }, /* botrightparens ⎠ ??? */
+ { 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ⎨ ??? */
+ { 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ⎬ ??? */
+/* 0x08b1 topleftsummation ? ??? */
+/* 0x08b2 botleftsummation ? ??? */
+/* 0x08b3 topvertsummationconnector ? ??? */
+/* 0x08b4 botvertsummationconnector ? ??? */
+/* 0x08b5 toprightsummation ? ??? */
+/* 0x08b6 botrightsummation ? ??? */
+/* 0x08b7 rightmiddlesummation ? ??? */
+ { 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */
+ { 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */
+ { 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
+ { 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */
+ { 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */
+ { 0x08c1, 0x221d }, /* variation ∠PROPORTIONAL TO */
+ { 0x08c2, 0x221e }, /* infinity ∞ INFINITY */
+ { 0x08c5, 0x2207 }, /* nabla ∇ NABLA */
+ { 0x08c8, 0x223c }, /* approximate ∼ TILDE OPERATOR */
+ { 0x08c9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */
+ { 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
+ { 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */
+ { 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */
+ { 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */
+ { 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */
+ { 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */
+ { 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */
+ { 0x08dd, 0x222a }, /* union ∪ UNION */
+ { 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */
+ { 0x08df, 0x2228 }, /* logicalor ∨ LOGICAL OR */
+ { 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */
+ { 0x08f6, 0x0192 }, /* function Æ’ LATIN SMALL LETTER F WITH HOOK */
+ { 0x08fb, 0x2190 }, /* leftarrow ↠LEFTWARDS ARROW */
+ { 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */
+ { 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */
+ { 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */
+/* 0x09df blank ? ??? */
+ { 0x09e0, 0x25c6 }, /* soliddiamond â—† BLACK DIAMOND */
+ { 0x09e1, 0x2592 }, /* checkerboard â–’ MEDIUM SHADE */
+ { 0x09e2, 0x2409 }, /* ht ≠SYMBOL FOR HORIZONTAL TABULATION */
+ { 0x09e3, 0x240c }, /* ff ⌠SYMBOL FOR FORM FEED */
+ { 0x09e4, 0x240d }, /* cr â SYMBOL FOR CARRIAGE RETURN */
+ { 0x09e5, 0x240a }, /* lf ⊠SYMBOL FOR LINE FEED */
+ { 0x09e8, 0x2424 }, /* nl ⤠SYMBOL FOR NEWLINE */
+ { 0x09e9, 0x240b }, /* vt â‹ SYMBOL FOR VERTICAL TABULATION */
+ { 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
+ { 0x09eb, 0x2510 }, /* uprightcorner â” BOX DRAWINGS LIGHT DOWN AND LEFT */
+ { 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+ { 0x09ed, 0x2514 }, /* lowleftcorner â”” BOX DRAWINGS LIGHT UP AND RIGHT */
+ { 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+ { 0x09ef, 0x23ba }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
+ { 0x09f0, 0x23bb }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
+ { 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
+ { 0x09f2, 0x23bc }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
+ { 0x09f3, 0x23bd }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
+ { 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+ { 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+ { 0x09f6, 0x2534 }, /* bott â”´ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+ { 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+ { 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */
+ { 0x0aa1, 0x2003 }, /* emspace   EM SPACE */
+ { 0x0aa2, 0x2002 }, /* enspace   EN SPACE */
+ { 0x0aa3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */
+ { 0x0aa4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */
+ { 0x0aa5, 0x2007 }, /* digitspace   FIGURE SPACE */
+ { 0x0aa6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */
+ { 0x0aa7, 0x2009 }, /* thinspace   THIN SPACE */
+ { 0x0aa8, 0x200a }, /* hairspace   HAIR SPACE */
+ { 0x0aa9, 0x2014 }, /* emdash — EM DASH */
+ { 0x0aaa, 0x2013 }, /* endash – EN DASH */
+/* 0x0aac signifblank ? ??? */
+ { 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */
+ { 0x0aaf, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */
+ { 0x0ab0, 0x2153 }, /* onethird â…“ VULGAR FRACTION ONE THIRD */
+ { 0x0ab1, 0x2154 }, /* twothirds â…” VULGAR FRACTION TWO THIRDS */
+ { 0x0ab2, 0x2155 }, /* onefifth â…• VULGAR FRACTION ONE FIFTH */
+ { 0x0ab3, 0x2156 }, /* twofifths â…– VULGAR FRACTION TWO FIFTHS */
+ { 0x0ab4, 0x2157 }, /* threefifths â…— VULGAR FRACTION THREE FIFTHS */
+ { 0x0ab5, 0x2158 }, /* fourfifths â…˜ VULGAR FRACTION FOUR FIFTHS */
+ { 0x0ab6, 0x2159 }, /* onesixth â…™ VULGAR FRACTION ONE SIXTH */
+ { 0x0ab7, 0x215a }, /* fivesixths â…š VULGAR FRACTION FIVE SIXTHS */
+ { 0x0ab8, 0x2105 }, /* careof â„… CARE OF */
+ { 0x0abb, 0x2012 }, /* figdash ‒ FIGURE DASH */
+ { 0x0abc, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
+/* 0x0abd decimalpoint ? ??? */
+ { 0x0abe, 0x232a }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
+/* 0x0abf marker ? ??? */
+ { 0x0ac3, 0x215b }, /* oneeighth â…› VULGAR FRACTION ONE EIGHTH */
+ { 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
+ { 0x0ac5, 0x215d }, /* fiveeighths â… VULGAR FRACTION FIVE EIGHTHS */
+ { 0x0ac6, 0x215e }, /* seveneighths â…ž VULGAR FRACTION SEVEN EIGHTHS */
+ { 0x0ac9, 0x2122 }, /* trademark â„¢ TRADE MARK SIGN */
+ { 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */
+/* 0x0acb trademarkincircle ? ??? */
+ { 0x0acc, 0x25c1 }, /* leftopentriangle â— WHITE LEFT-POINTING TRIANGLE */
+ { 0x0acd, 0x25b7 }, /* rightopentriangle â–· WHITE RIGHT-POINTING TRIANGLE */
+ { 0x0ace, 0x25cb }, /* emopencircle â—‹ WHITE CIRCLE */
+ { 0x0acf, 0x25af }, /* emopenrectangle â–¯ WHITE VERTICAL RECTANGLE */
+ { 0x0ad0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */
+ { 0x0ad1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */
+ { 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
+ { 0x0ad3, 0x201d }, /* rightdoublequotemark †RIGHT DOUBLE QUOTATION MARK */
+ { 0x0ad4, 0x211e }, /* prescription â„ž PRESCRIPTION TAKE */
+ { 0x0ad6, 0x2032 }, /* minutes ′ PRIME */
+ { 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */
+ { 0x0ad9, 0x271d }, /* latincross ✠LATIN CROSS */
+/* 0x0ada hexagram ? ??? */
+ { 0x0adb, 0x25ac }, /* filledrectbullet â–¬ BLACK RECTANGLE */
+ { 0x0adc, 0x25c0 }, /* filledlefttribullet â—€ BLACK LEFT-POINTING TRIANGLE */
+ { 0x0add, 0x25b6 }, /* filledrighttribullet â–¶ BLACK RIGHT-POINTING TRIANGLE */
+ { 0x0ade, 0x25cf }, /* emfilledcircle â— BLACK CIRCLE */
+ { 0x0adf, 0x25ae }, /* emfilledrect â–® BLACK VERTICAL RECTANGLE */
+ { 0x0ae0, 0x25e6 }, /* enopencircbullet â—¦ WHITE BULLET */
+ { 0x0ae1, 0x25ab }, /* enopensquarebullet â–« WHITE SMALL SQUARE */
+ { 0x0ae2, 0x25ad }, /* openrectbullet â–­ WHITE RECTANGLE */
+ { 0x0ae3, 0x25b3 }, /* opentribulletup â–³ WHITE UP-POINTING TRIANGLE */
+ { 0x0ae4, 0x25bd }, /* opentribulletdown â–½ WHITE DOWN-POINTING TRIANGLE */
+ { 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */
+ { 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */
+ { 0x0ae7, 0x25aa }, /* enfilledsqbullet â–ª BLACK SMALL SQUARE */
+ { 0x0ae8, 0x25b2 }, /* filledtribulletup â–² BLACK UP-POINTING TRIANGLE */
+ { 0x0ae9, 0x25bc }, /* filledtribulletdown â–¼ BLACK DOWN-POINTING TRIANGLE */
+ { 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */
+ { 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */
+ { 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */
+ { 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */
+ { 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */
+ { 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */
+ { 0x0af1, 0x2020 }, /* dagger † DAGGER */
+ { 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */
+ { 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */
+ { 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */
+ { 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */
+ { 0x0af6, 0x266d }, /* musicalflat â™­ MUSIC FLAT SIGN */
+ { 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */
+ { 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */
+ { 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */
+ { 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */
+ { 0x0afb, 0x2117 }, /* phonographcopyright â„— SOUND RECORDING COPYRIGHT */
+ { 0x0afc, 0x2038 }, /* caret ‸ CARET */
+ { 0x0afd, 0x201a }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */
+ { 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
+/* 0x0aff cursor ? ??? */
+ { 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */
+ { 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */
+ { 0x0ba8, 0x2228 }, /* downcaret ∨ LOGICAL OR */
+ { 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */
+ { 0x0bc0, 0x00af }, /* overbar ¯ MACRON */
+ { 0x0bc2, 0x22a5 }, /* downtack ⊥ UP TACK */
+ { 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */
+ { 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */
+ { 0x0bc6, 0x005f }, /* underbar _ LOW LINE */
+ { 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */
+ { 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */
+ { 0x0bce, 0x22a4 }, /* uptack ⊤ DOWN TACK */
+ { 0x0bcf, 0x25cb }, /* circle â—‹ WHITE CIRCLE */
+ { 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */
+ { 0x0bd6, 0x222a }, /* downshoe ∪ UNION */
+ { 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */
+ { 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */
+ { 0x0bdc, 0x22a2 }, /* lefttack ⊢ RIGHT TACK */
+ { 0x0bfc, 0x22a3 }, /* righttack ⊣ LEFT TACK */
+ { 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */
+ { 0x0ce0, 0x05d0 }, /* hebrew_aleph × HEBREW LETTER ALEF */
+ { 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */
+ { 0x0ce2, 0x05d2 }, /* hebrew_gimel ×’ HEBREW LETTER GIMEL */
+ { 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */
+ { 0x0ce4, 0x05d4 }, /* hebrew_he ×” HEBREW LETTER HE */
+ { 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */
+ { 0x0ce6, 0x05d6 }, /* hebrew_zain ×– HEBREW LETTER ZAYIN */
+ { 0x0ce7, 0x05d7 }, /* hebrew_chet ×— HEBREW LETTER HET */
+ { 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */
+ { 0x0ce9, 0x05d9 }, /* hebrew_yod ×™ HEBREW LETTER YOD */
+ { 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
+ { 0x0ceb, 0x05db }, /* hebrew_kaph ×› HEBREW LETTER KAF */
+ { 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */
+ { 0x0ced, 0x05dd }, /* hebrew_finalmem × HEBREW LETTER FINAL MEM */
+ { 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */
+ { 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */
+ { 0x0cf0, 0x05e0 }, /* hebrew_nun ×  HEBREW LETTER NUN */
+ { 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */
+ { 0x0cf2, 0x05e2 }, /* hebrew_ayin ×¢ HEBREW LETTER AYIN */
+ { 0x0cf3, 0x05e3 }, /* hebrew_finalpe ×£ HEBREW LETTER FINAL PE */
+ { 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */
+ { 0x0cf5, 0x05e5 }, /* hebrew_finalzade ×¥ HEBREW LETTER FINAL TSADI */
+ { 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */
+ { 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */
+ { 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */
+ { 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */
+ { 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */
+ { 0x0da1, 0x0e01 }, /* Thai_kokai ภTHAI CHARACTER KO KAI */
+ { 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */
+ { 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
+ { 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
+ { 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */
+ { 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
+ { 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */
+ { 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */
+ { 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */
+ { 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */
+ { 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */
+ { 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
+ { 0x0dad, 0x0e0d }, /* Thai_yoying ภTHAI CHARACTER YO YING */
+ { 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */
+ { 0x0daf, 0x0e0f }, /* Thai_topatak ภTHAI CHARACTER TO PATAK */
+ { 0x0db0, 0x0e10 }, /* Thai_thothan ภTHAI CHARACTER THO THAN */
+ { 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
+ { 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
+ { 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */
+ { 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */
+ { 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */
+ { 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */
+ { 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */
+ { 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */
+ { 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */
+ { 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
+ { 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */
+ { 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */
+ { 0x0dbd, 0x0e1d }, /* Thai_fofa ภTHAI CHARACTER FO FA */
+ { 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */
+ { 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */
+ { 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
+ { 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */
+ { 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */
+ { 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */
+ { 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */
+ { 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */
+ { 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */
+ { 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */
+ { 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */
+ { 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */
+ { 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */
+ { 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */
+ { 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */
+ { 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */
+ { 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
+ { 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
+ { 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */
+ { 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
+ { 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */
+ { 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */
+ { 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */
+ { 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */
+ { 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */
+ { 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */
+ { 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */
+ { 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */
+ { 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */
+/* 0x0dde Thai_maihanakat_maitho ? ??? */
+ { 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
+ { 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */
+ { 0x0de1, 0x0e41 }, /* Thai_saraae ๠THAI CHARACTER SARA AE */
+ { 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */
+ { 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
+ { 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
+ { 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
+ { 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
+ { 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
+ { 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */
+ { 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */
+ { 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */
+ { 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
+ { 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
+ { 0x0ded, 0x0e4d }, /* Thai_nikhahit ๠THAI CHARACTER NIKHAHIT */
+ { 0x0df0, 0x0e50 }, /* Thai_leksun ๠THAI DIGIT ZERO */
+ { 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */
+ { 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */
+ { 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */
+ { 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */
+ { 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */
+ { 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */
+ { 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */
+ { 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */
+ { 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */
+ { 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
+ { 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
+ { 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
+ { 0x0ea4, 0x3134 }, /* Hangul_Nieun ã„´ HANGUL LETTER NIEUN */
+ { 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
+ { 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
+ { 0x0ea7, 0x3137 }, /* Hangul_Dikeud ã„· HANGUL LETTER TIKEUT */
+ { 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
+ { 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
+ { 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
+ { 0x0eab, 0x313b }, /* Hangul_RieulMieum ã„» HANGUL LETTER RIEUL-MIEUM */
+ { 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
+ { 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
+ { 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
+ { 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ã„¿ HANGUL LETTER RIEUL-PHIEUPH */
+ { 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ã…€ HANGUL LETTER RIEUL-HIEUH */
+ { 0x0eb1, 0x3141 }, /* Hangul_Mieum ã… HANGUL LETTER MIEUM */
+ { 0x0eb2, 0x3142 }, /* Hangul_Pieub ã…‚ HANGUL LETTER PIEUP */
+ { 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ã…ƒ HANGUL LETTER SSANGPIEUP */
+ { 0x0eb4, 0x3144 }, /* Hangul_PieubSios ã…„ HANGUL LETTER PIEUP-SIOS */
+ { 0x0eb5, 0x3145 }, /* Hangul_Sios ã…… HANGUL LETTER SIOS */
+ { 0x0eb6, 0x3146 }, /* Hangul_SsangSios ã…† HANGUL LETTER SSANGSIOS */
+ { 0x0eb7, 0x3147 }, /* Hangul_Ieung ã…‡ HANGUL LETTER IEUNG */
+ { 0x0eb8, 0x3148 }, /* Hangul_Jieuj ã…ˆ HANGUL LETTER CIEUC */
+ { 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ã…‰ HANGUL LETTER SSANGCIEUC */
+ { 0x0eba, 0x314a }, /* Hangul_Cieuc ã…Š HANGUL LETTER CHIEUCH */
+ { 0x0ebb, 0x314b }, /* Hangul_Khieuq ã…‹ HANGUL LETTER KHIEUKH */
+ { 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
+ { 0x0ebd, 0x314d }, /* Hangul_Phieuf ã… HANGUL LETTER PHIEUPH */
+ { 0x0ebe, 0x314e }, /* Hangul_Hieuh ã…Ž HANGUL LETTER HIEUH */
+ { 0x0ebf, 0x314f }, /* Hangul_A ã… HANGUL LETTER A */
+ { 0x0ec0, 0x3150 }, /* Hangul_AE ã… HANGUL LETTER AE */
+ { 0x0ec1, 0x3151 }, /* Hangul_YA ã…‘ HANGUL LETTER YA */
+ { 0x0ec2, 0x3152 }, /* Hangul_YAE ã…’ HANGUL LETTER YAE */
+ { 0x0ec3, 0x3153 }, /* Hangul_EO ã…“ HANGUL LETTER EO */
+ { 0x0ec4, 0x3154 }, /* Hangul_E ã…” HANGUL LETTER E */
+ { 0x0ec5, 0x3155 }, /* Hangul_YEO ã…• HANGUL LETTER YEO */
+ { 0x0ec6, 0x3156 }, /* Hangul_YE ã…– HANGUL LETTER YE */
+ { 0x0ec7, 0x3157 }, /* Hangul_O ã…— HANGUL LETTER O */
+ { 0x0ec8, 0x3158 }, /* Hangul_WA ã…˜ HANGUL LETTER WA */
+ { 0x0ec9, 0x3159 }, /* Hangul_WAE ã…™ HANGUL LETTER WAE */
+ { 0x0eca, 0x315a }, /* Hangul_OE ã…š HANGUL LETTER OE */
+ { 0x0ecb, 0x315b }, /* Hangul_YO ã…› HANGUL LETTER YO */
+ { 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */
+ { 0x0ecd, 0x315d }, /* Hangul_WEO ã… HANGUL LETTER WEO */
+ { 0x0ece, 0x315e }, /* Hangul_WE ã…ž HANGUL LETTER WE */
+ { 0x0ecf, 0x315f }, /* Hangul_WI ã…Ÿ HANGUL LETTER WI */
+ { 0x0ed0, 0x3160 }, /* Hangul_YU ã…  HANGUL LETTER YU */
+ { 0x0ed1, 0x3161 }, /* Hangul_EU ã…¡ HANGUL LETTER EU */
+ { 0x0ed2, 0x3162 }, /* Hangul_YI ã…¢ HANGUL LETTER YI */
+ { 0x0ed3, 0x3163 }, /* Hangul_I ã…£ HANGUL LETTER I */
+ { 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
+ { 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
+ { 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
+ { 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
+ { 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
+ { 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
+ { 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
+ { 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
+ { 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
+ { 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
+ { 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
+ { 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
+ { 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
+ { 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
+ { 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
+ { 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
+ { 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
+ { 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
+ { 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
+ { 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
+ { 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
+ { 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
+ { 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
+ { 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
+ { 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
+ { 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇠHANGUL JONGSEONG PHIEUPH */
+ { 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
+ { 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ã…­ HANGUL LETTER RIEUL-YEORINHIEUH */
+ { 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ã…± HANGUL LETTER KAPYEOUNMIEUM */
+ { 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ã…¸ HANGUL LETTER KAPYEOUNPIEUP */
+ { 0x0ef2, 0x317f }, /* Hangul_PanSios ã…¿ HANGUL LETTER PANSIOS */
+ { 0x0ef3, 0x3181 }, /* Hangul_KkogjiDalrinIeung ㆠHANGUL LETTER YESIEUNG */
+ { 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
+ { 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
+ { 0x0ef6, 0x318d }, /* Hangul_AraeA ㆠHANGUL LETTER ARAEA */
+ { 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
+ { 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
+ { 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */
+ { 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
+ { 0x0eff, 0x20a9 }, /* Korean_Won â‚© WON SIGN */
+ { 0x13a4, 0x20ac }, /* Euro € EURO SIGN */
+ { 0x13bc, 0x0152 }, /* OE Å’ LATIN CAPITAL LIGATURE OE */
+ { 0x13bd, 0x0153 }, /* oe Å“ LATIN SMALL LIGATURE OE */
+ { 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
+ { 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */
+};
+
+long keysym2ucs(KeySym keysym)
+{
+ int min = 0;
+ int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
+ int mid;
+
+ /* first check for Latin-1 characters (1:1 mapping) */
+ if ((keysym >= 0x0020 && keysym <= 0x007e) ||
+ (keysym >= 0x00a0 && keysym <= 0x00ff))
+ return keysym;
+
+ /* also check for directly encoded 24-bit UCS characters */
+ if ((keysym & 0xff000000) == 0x01000000)
+ return keysym & 0x00ffffff;
+
+ /* binary search in table */
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (keysymtab[mid].keysym < keysym)
+ min = mid + 1;
+ else if (keysymtab[mid].keysym > keysym)
+ max = mid - 1;
+ else {
+ /* found it */
+ return keysymtab[mid].ucs;
+ }
+ }
+
+ /* no matching Unicode value found */
+ return -1;
+}
diff --git a/views/unix/skia_unix.cpp b/views/unix/skia_unix.cpp
new file mode 100644
index 00000000..7dcbecca
--- /dev/null
+++ b/views/unix/skia_unix.cpp
@@ -0,0 +1,37 @@
+
+/*
+ * 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 "X11/Xlib.h"
+#include "X11/keysym.h"
+
+#include "SkApplication.h"
+#include "SkEvent.h"
+#include "SkWindow.h"
+#include "SkTypes.h"
+
+#include <signal.h>
+#include <sys/time.h>
+
+SkOSWindow* gWindow;
+
+int main(int argc, char** argv){
+ gWindow = create_sk_window(NULL, argc, argv);
+
+ // drain any events that occurred before gWindow was assigned.
+ while (SkEvent::ProcessEvent());
+
+ // Start normal Skia sequence
+ application_init();
+
+ gWindow->loop();
+
+ delete gWindow;
+ application_term();
+ return 0;
+}
+
+// SkEvent handlers
diff --git a/views/win/SkOSWindow_win.cpp b/views/win/SkOSWindow_win.cpp
new file mode 100644
index 00000000..9aeec6d1
--- /dev/null
+++ b/views/win/SkOSWindow_win.cpp
@@ -0,0 +1,612 @@
+
+/*
+ * 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 "SkTypes.h"
+
+#if defined(SK_BUILD_FOR_WIN)
+
+#include <GL/gl.h>
+#include <WindowsX.h>
+#include "SkWGL.h"
+#include "SkWindow.h"
+#include "SkCanvas.h"
+#include "SkOSMenu.h"
+#include "SkTime.h"
+#include "SkUtils.h"
+
+#include "SkGraphics.h"
+
+#if SK_ANGLE
+#include "gl/GrGLInterface.h"
+
+#include "GLES2/gl2.h"
+
+#define ANGLE_GL_CALL(IFACE, X) \
+ do { \
+ (IFACE)->f##X; \
+ } while (false)
+
+#endif
+
+#define INVALIDATE_DELAY_MS 200
+
+static SkOSWindow* gCurrOSWin;
+static HWND gEventTarget;
+
+#define WM_EVENT_CALLBACK (WM_USER+0)
+
+void post_skwinevent()
+{
+ PostMessage(gEventTarget, WM_EVENT_CALLBACK, 0, 0);
+}
+
+SkOSWindow::SkOSWindow(void* hWnd) {
+ fHWND = hWnd;
+#if SK_SUPPORT_GPU
+#if SK_ANGLE
+ fDisplay = EGL_NO_DISPLAY;
+ fContext = EGL_NO_CONTEXT;
+ fSurface = EGL_NO_SURFACE;
+#endif
+ fHGLRC = NULL;
+#endif
+ fAttached = kNone_BackEndType;
+ gEventTarget = (HWND)hWnd;
+}
+
+SkOSWindow::~SkOSWindow() {
+#if SK_SUPPORT_GPU
+ if (NULL != fHGLRC) {
+ wglDeleteContext((HGLRC)fHGLRC);
+ }
+#if SK_ANGLE
+ if (EGL_NO_CONTEXT != fContext) {
+ eglDestroyContext(fDisplay, fContext);
+ fContext = EGL_NO_CONTEXT;
+ }
+
+ if (EGL_NO_SURFACE != fSurface) {
+ eglDestroySurface(fDisplay, fSurface);
+ fSurface = EGL_NO_SURFACE;
+ }
+
+ if (EGL_NO_DISPLAY != fDisplay) {
+ eglTerminate(fDisplay);
+ fDisplay = EGL_NO_DISPLAY;
+ }
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+}
+
+static SkKey winToskKey(WPARAM vk) {
+ static const struct {
+ WPARAM fVK;
+ SkKey fKey;
+ } gPair[] = {
+ { VK_BACK, kBack_SkKey },
+ { VK_CLEAR, kBack_SkKey },
+ { VK_RETURN, kOK_SkKey },
+ { VK_UP, kUp_SkKey },
+ { VK_DOWN, kDown_SkKey },
+ { VK_LEFT, kLeft_SkKey },
+ { VK_RIGHT, kRight_SkKey }
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fVK == vk) {
+ return gPair[i].fKey;
+ }
+ }
+ return kNONE_SkKey;
+}
+
+static unsigned getModifiers(UINT message) {
+ return 0; // TODO
+}
+
+bool SkOSWindow::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
+ switch (message) {
+ case WM_KEYDOWN: {
+ SkKey key = winToskKey(wParam);
+ if (kNONE_SkKey != key) {
+ this->handleKey(key);
+ return true;
+ }
+ } break;
+ case WM_KEYUP: {
+ SkKey key = winToskKey(wParam);
+ if (kNONE_SkKey != key) {
+ this->handleKeyUp(key);
+ return true;
+ }
+ } break;
+ case WM_UNICHAR:
+ this->handleChar(wParam);
+ return true;
+ case WM_CHAR: {
+ this->handleChar(SkUTF8_ToUnichar((char*)&wParam));
+ return true;
+ } break;
+ case WM_SIZE:
+ this->resize(lParam & 0xFFFF, lParam >> 16);
+ break;
+ case WM_PAINT: {
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hWnd, &ps);
+ this->doPaint(hdc);
+ EndPaint(hWnd, &ps);
+ return true;
+ } break;
+
+ case WM_TIMER: {
+ RECT* rect = (RECT*)wParam;
+ InvalidateRect(hWnd, rect, FALSE);
+ KillTimer(hWnd, (UINT_PTR)rect);
+ delete rect;
+ return true;
+ } break;
+
+ case WM_LBUTTONDOWN:
+ this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
+ Click::kDown_State, NULL, getModifiers(message));
+ return true;
+
+ case WM_MOUSEMOVE:
+ this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
+ Click::kMoved_State, NULL, getModifiers(message));
+ return true;
+
+ case WM_LBUTTONUP:
+ this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
+ Click::kUp_State, NULL, getModifiers(message));
+ return true;
+
+ case WM_EVENT_CALLBACK:
+ if (SkEvent::ProcessEvent()) {
+ post_skwinevent();
+ }
+ return true;
+ }
+ return false;
+}
+
+void SkOSWindow::doPaint(void* ctx) {
+ this->update(NULL);
+
+ if (kNone_BackEndType == fAttached)
+ {
+ HDC hdc = (HDC)ctx;
+ const SkBitmap& bitmap = this->getBitmap();
+
+ BITMAPINFO bmi;
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.bmiHeader.biWidth = bitmap.width();
+ bmi.bmiHeader.biHeight = -bitmap.height(); // top-down image
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+ bmi.bmiHeader.biSizeImage = 0;
+
+ //
+ // Do the SetDIBitsToDevice.
+ //
+ // TODO(wjmaclean):
+ // Fix this call to handle SkBitmaps that have rowBytes != width,
+ // i.e. may have padding at the end of lines. The SkASSERT below
+ // may be ignored by builds, and the only obviously safe option
+ // seems to be to copy the bitmap to a temporary (contiguous)
+ // buffer before passing to SetDIBitsToDevice().
+ SkASSERT(bitmap.width() * bitmap.bytesPerPixel() == bitmap.rowBytes());
+ bitmap.lockPixels();
+ int ret = SetDIBitsToDevice(hdc,
+ 0, 0,
+ bitmap.width(), bitmap.height(),
+ 0, 0,
+ 0, bitmap.height(),
+ bitmap.getPixels(),
+ &bmi,
+ DIB_RGB_COLORS);
+ (void)ret; // we're ignoring potential failures for now.
+ bitmap.unlockPixels();
+ }
+}
+
+#if 0
+void SkOSWindow::updateSize()
+{
+ RECT r;
+ GetWindowRect((HWND)this->getHWND(), &r);
+ this->resize(r.right - r.left, r.bottom - r.top);
+}
+#endif
+
+void SkOSWindow::onHandleInval(const SkIRect& r) {
+ RECT* rect = new RECT;
+ rect->left = r.fLeft;
+ rect->top = r.fTop;
+ rect->right = r.fRight;
+ rect->bottom = r.fBottom;
+ SetTimer((HWND)fHWND, (UINT_PTR)rect, INVALIDATE_DELAY_MS, NULL);
+}
+
+void SkOSWindow::onAddMenu(const SkOSMenu* sk_menu)
+{
+}
+
+void SkOSWindow::onSetTitle(const char title[]){
+ SetWindowTextA((HWND)fHWND, title);
+}
+
+enum {
+ SK_MacReturnKey = 36,
+ SK_MacDeleteKey = 51,
+ SK_MacEndKey = 119,
+ SK_MacLeftKey = 123,
+ SK_MacRightKey = 124,
+ SK_MacDownKey = 125,
+ SK_MacUpKey = 126,
+
+ SK_Mac0Key = 0x52,
+ SK_Mac1Key = 0x53,
+ SK_Mac2Key = 0x54,
+ SK_Mac3Key = 0x55,
+ SK_Mac4Key = 0x56,
+ SK_Mac5Key = 0x57,
+ SK_Mac6Key = 0x58,
+ SK_Mac7Key = 0x59,
+ SK_Mac8Key = 0x5b,
+ SK_Mac9Key = 0x5c
+};
+
+static SkKey raw2key(uint32_t raw)
+{
+ static const struct {
+ uint32_t fRaw;
+ SkKey fKey;
+ } gKeys[] = {
+ { SK_MacUpKey, kUp_SkKey },
+ { SK_MacDownKey, kDown_SkKey },
+ { SK_MacLeftKey, kLeft_SkKey },
+ { SK_MacRightKey, kRight_SkKey },
+ { SK_MacReturnKey, kOK_SkKey },
+ { SK_MacDeleteKey, kBack_SkKey },
+ { SK_MacEndKey, kEnd_SkKey },
+ { SK_Mac0Key, k0_SkKey },
+ { SK_Mac1Key, k1_SkKey },
+ { SK_Mac2Key, k2_SkKey },
+ { SK_Mac3Key, k3_SkKey },
+ { SK_Mac4Key, k4_SkKey },
+ { SK_Mac5Key, k5_SkKey },
+ { SK_Mac6Key, k6_SkKey },
+ { SK_Mac7Key, k7_SkKey },
+ { SK_Mac8Key, k8_SkKey },
+ { SK_Mac9Key, k9_SkKey }
+ };
+
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++)
+ if (gKeys[i].fRaw == raw)
+ return gKeys[i].fKey;
+ return kNONE_SkKey;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void SkEvent::SignalNonEmptyQueue()
+{
+ post_skwinevent();
+ //SkDebugf("signal nonempty\n");
+}
+
+static UINT_PTR gTimer;
+
+VOID CALLBACK sk_timer_proc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ SkEvent::ServiceQueueTimer();
+ //SkDebugf("timer task fired\n");
+}
+
+void SkEvent::SignalQueueTimer(SkMSec delay)
+{
+ if (gTimer)
+ {
+ KillTimer(NULL, gTimer);
+ gTimer = NULL;
+ }
+ if (delay)
+ {
+ gTimer = SetTimer(NULL, 0, delay, sk_timer_proc);
+ //SkDebugf("SetTimer of %d returned %d\n", delay, gTimer);
+ }
+}
+
+#if SK_SUPPORT_GPU
+
+bool SkOSWindow::attachGL(int msaaSampleCount, AttachmentInfo* info) {
+ HDC dc = GetDC((HWND)fHWND);
+ if (NULL == fHGLRC) {
+ fHGLRC = SkCreateWGLContext(dc, msaaSampleCount, false);
+ if (NULL == fHGLRC) {
+ return false;
+ }
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ }
+ if (wglMakeCurrent(dc, (HGLRC)fHGLRC)) {
+ // use DescribePixelFormat to get the stencil bit depth.
+ int pixelFormat = GetPixelFormat(dc);
+ PIXELFORMATDESCRIPTOR pfd;
+ DescribePixelFormat(dc, pixelFormat, sizeof(pfd), &pfd);
+ info->fStencilBits = pfd.cStencilBits;
+
+ // Get sample count if the MSAA WGL extension is present
+ SkWGLExtensions extensions;
+ if (extensions.hasExtension(dc, "WGL_ARB_multisample")) {
+ static const int kSampleCountAttr = SK_WGL_SAMPLES;
+ extensions.getPixelFormatAttribiv(dc,
+ pixelFormat,
+ 0,
+ 1,
+ &kSampleCountAttr,
+ &info->fSampleCount);
+ } else {
+ info->fSampleCount = 0;
+ }
+
+ glViewport(0, 0, SkScalarRound(this->width()), SkScalarRound(this->height()));
+ return true;
+ }
+ return false;
+}
+
+void SkOSWindow::detachGL() {
+ wglMakeCurrent(GetDC((HWND)fHWND), 0);
+ wglDeleteContext((HGLRC)fHGLRC);
+ fHGLRC = NULL;
+}
+
+void SkOSWindow::presentGL() {
+ glFlush();
+ HDC dc = GetDC((HWND)fHWND);
+ SwapBuffers(dc);
+ ReleaseDC((HWND)fHWND, dc);
+}
+
+#if SK_ANGLE
+bool create_ANGLE(EGLNativeWindowType hWnd,
+ int msaaSampleCount,
+ EGLDisplay* eglDisplay,
+ EGLContext* eglContext,
+ EGLSurface* eglSurface,
+ EGLConfig* eglConfig) {
+ static const EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE, EGL_NONE
+ };
+ static const EGLint configAttribList[] = {
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 8,
+ EGL_STENCIL_SIZE, 8,
+ EGL_NONE
+ };
+ static const EGLint surfaceAttribList[] = {
+ EGL_NONE, EGL_NONE
+ };
+
+ EGLDisplay display = eglGetDisplay(GetDC(hWnd));
+ if (display == EGL_NO_DISPLAY ) {
+ return false;
+ }
+
+ // Initialize EGL
+ EGLint majorVersion, minorVersion;
+ if (!eglInitialize(display, &majorVersion, &minorVersion)) {
+ return false;
+ }
+
+ EGLint numConfigs;
+ if (!eglGetConfigs(display, NULL, 0, &numConfigs)) {
+ return false;
+ }
+
+ // Choose config
+ bool foundConfig = false;
+ if (msaaSampleCount) {
+ static const int kConfigAttribListCnt =
+ SK_ARRAY_COUNT(configAttribList);
+ EGLint msaaConfigAttribList[kConfigAttribListCnt + 4];
+ memcpy(msaaConfigAttribList,
+ configAttribList,
+ sizeof(configAttribList));
+ SkASSERT(EGL_NONE == msaaConfigAttribList[kConfigAttribListCnt - 1]);
+ msaaConfigAttribList[kConfigAttribListCnt - 1] = EGL_SAMPLE_BUFFERS;
+ msaaConfigAttribList[kConfigAttribListCnt + 0] = 1;
+ msaaConfigAttribList[kConfigAttribListCnt + 1] = EGL_SAMPLES;
+ msaaConfigAttribList[kConfigAttribListCnt + 2] = msaaSampleCount;
+ msaaConfigAttribList[kConfigAttribListCnt + 3] = EGL_NONE;
+ if (eglChooseConfig(display, configAttribList, eglConfig, 1, &numConfigs)) {
+ SkASSERT(numConfigs > 0);
+ foundConfig = true;
+ }
+ }
+ if (!foundConfig) {
+ if (!eglChooseConfig(display, configAttribList, eglConfig, 1, &numConfigs)) {
+ return false;
+ }
+ }
+
+ // Create a surface
+ EGLSurface surface = eglCreateWindowSurface(display, *eglConfig,
+ (EGLNativeWindowType)hWnd,
+ surfaceAttribList);
+ if (surface == EGL_NO_SURFACE) {
+ return false;
+ }
+
+ // Create a GL context
+ EGLContext context = eglCreateContext(display, *eglConfig,
+ EGL_NO_CONTEXT,
+ contextAttribs );
+ if (context == EGL_NO_CONTEXT ) {
+ return false;
+ }
+
+ // Make the context current
+ if (!eglMakeCurrent(display, surface, surface, context)) {
+ return false;
+ }
+
+ *eglDisplay = display;
+ *eglContext = context;
+ *eglSurface = surface;
+ return true;
+}
+
+bool SkOSWindow::attachANGLE(int msaaSampleCount, AttachmentInfo* info) {
+ if (EGL_NO_DISPLAY == fDisplay) {
+ bool bResult = create_ANGLE((HWND)fHWND,
+ msaaSampleCount,
+ &fDisplay,
+ &fContext,
+ &fSurface,
+ &fConfig);
+ if (false == bResult) {
+ return false;
+ }
+ SkAutoTUnref<const GrGLInterface> intf(GrGLCreateANGLEInterface());
+
+ if (intf) {
+ ANGLE_GL_CALL(intf, ClearStencil(0));
+ ANGLE_GL_CALL(intf, ClearColor(0, 0, 0, 0));
+ ANGLE_GL_CALL(intf, StencilMask(0xffffffff));
+ ANGLE_GL_CALL(intf, Clear(GL_STENCIL_BUFFER_BIT |GL_COLOR_BUFFER_BIT));
+ }
+ }
+ if (eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) {
+ eglGetConfigAttrib(fDisplay, fConfig, EGL_STENCIL_SIZE, &info->fStencilBits);
+ eglGetConfigAttrib(fDisplay, fConfig, EGL_SAMPLES, &info->fSampleCount);
+
+ SkAutoTUnref<const GrGLInterface> intf(GrGLCreateANGLEInterface());
+
+ if (intf ) {
+ ANGLE_GL_CALL(intf, Viewport(0, 0, SkScalarRound(this->width()),
+ SkScalarRound(this->height())));
+ }
+ return true;
+ }
+ return false;
+}
+
+void SkOSWindow::detachANGLE() {
+ eglMakeCurrent(fDisplay, EGL_NO_SURFACE , EGL_NO_SURFACE , EGL_NO_CONTEXT);
+
+ eglDestroyContext(fDisplay, fContext);
+ fContext = EGL_NO_CONTEXT;
+
+ eglDestroySurface(fDisplay, fSurface);
+ fSurface = EGL_NO_SURFACE;
+
+ eglTerminate(fDisplay);
+ fDisplay = EGL_NO_DISPLAY;
+}
+
+void SkOSWindow::presentANGLE() {
+ SkAutoTUnref<const GrGLInterface> intf(GrGLCreateANGLEInterface());
+
+ if (intf) {
+ ANGLE_GL_CALL(intf, Flush());
+ }
+
+ eglSwapBuffers(fDisplay, fSurface);
+}
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+
+// return true on success
+bool SkOSWindow::attach(SkBackEndTypes attachType, int msaaSampleCount, AttachmentInfo* info) {
+
+ // attach doubles as "windowResize" so we need to allo
+ // already bound states to pass through again
+ // TODO: split out the resize functionality
+// SkASSERT(kNone_BackEndType == fAttached);
+ bool result = true;
+
+ switch (attachType) {
+ case kNone_BackEndType:
+ // nothing to do
+ break;
+#if SK_SUPPORT_GPU
+ case kNativeGL_BackEndType:
+ result = attachGL(msaaSampleCount, info);
+ break;
+#if SK_ANGLE
+ case kANGLE_BackEndType:
+ result = attachANGLE(msaaSampleCount, info);
+ break;
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+ default:
+ SkASSERT(false);
+ result = false;
+ break;
+ }
+
+ if (result) {
+ fAttached = attachType;
+ }
+
+ return result;
+}
+
+void SkOSWindow::detach() {
+ switch (fAttached) {
+ case kNone_BackEndType:
+ // nothing to do
+ break;
+#if SK_SUPPORT_GPU
+ case kNativeGL_BackEndType:
+ detachGL();
+ break;
+#if SK_ANGLE
+ case kANGLE_BackEndType:
+ detachANGLE();
+ break;
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+ default:
+ SkASSERT(false);
+ break;
+ }
+ fAttached = kNone_BackEndType;
+}
+
+void SkOSWindow::present() {
+ switch (fAttached) {
+ case kNone_BackEndType:
+ // nothing to do
+ return;
+#if SK_SUPPORT_GPU
+ case kNativeGL_BackEndType:
+ presentGL();
+ break;
+#if SK_ANGLE
+ case kANGLE_BackEndType:
+ presentANGLE();
+ break;
+#endif // SK_ANGLE
+#endif // SK_SUPPORT_GPU
+ default:
+ SkASSERT(false);
+ break;
+ }
+}
+
+#endif
diff --git a/views/win/skia_win.cpp b/views/win/skia_win.cpp
new file mode 100644
index 00000000..2d66bd82
--- /dev/null
+++ b/views/win/skia_win.cpp
@@ -0,0 +1,207 @@
+
+/*
+ * 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, SK_ARRAY_COUNT(exename));
+ // we're ignoring the possibility that the exe name exceeds the exename buffer
+ (void) exenameLen;
+ 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/xml/SkBML_Verbs.h b/xml/SkBML_Verbs.h
new file mode 100644
index 00000000..709764d0
--- /dev/null
+++ b/xml/SkBML_Verbs.h
@@ -0,0 +1,25 @@
+
+/*
+ * 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.
+ */
+
+
+#ifndef SkBML_Verbs_DEFINED
+#define SkBML_Verbs_DEFINED
+
+enum Verbs {
+ kStartElem_Value_Verb,
+ kStartElem_Index_Verb,
+ kEndElem_Verb,
+ kAttr_Value_Value_Verb,
+ kAttr_Value_Index_Verb,
+ kAttr_Index_Value_Verb,
+ kAttr_Index_Index_Verb,
+
+ kVerbCount
+};
+
+#endif // SkBML_Verbs_DEFINED
diff --git a/xml/SkBML_XMLParser.cpp b/xml/SkBML_XMLParser.cpp
new file mode 100644
index 00000000..c0a5af55
--- /dev/null
+++ b/xml/SkBML_XMLParser.cpp
@@ -0,0 +1,181 @@
+
+/*
+ * 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 "SkBML_XMLParser.h"
+#include "SkBML_Verbs.h"
+#include "SkStream.h"
+#include "SkXMLWriter.h"
+
+static uint8_t rbyte(SkStream& s)
+{
+ uint8_t b;
+ SkDEBUGCODE(size_t size = ) s.read(&b, 1);
+ SkASSERT(size == 1);
+ return b;
+}
+
+static int rdata(SkStream& s, int data)
+{
+ SkASSERT((data & ~31) == 0);
+ if (data == 31)
+ {
+ data = rbyte(s);
+ if (data == 0xFF)
+ {
+ data = rbyte(s);
+ data = (data << 8) | rbyte(s);
+ }
+ }
+ return data;
+}
+
+static void set(char* array[256], int index, SkStream& s, int data)
+{
+ SkASSERT((unsigned)index <= 255);
+
+ size_t size = rdata(s, data);
+
+ if (array[index] == NULL)
+ array[index] = (char*)sk_malloc_throw(size + 1);
+ else
+ {
+ if (strlen(array[index]) < size)
+ array[index] = (char*)sk_realloc_throw(array[index], size + 1);
+ }
+
+ s.read(array[index], size);
+ array[index][size] = 0;
+}
+
+static void freeAll(char* array[256])
+{
+ for (int i = 0; i < 256; i++)
+ sk_free(array[i]);
+}
+
+struct BMLW {
+ char* fElems[256];
+ char* fNames[256];
+ char* fValues[256];
+
+ // important that these are uint8_t, so we get automatic wrap-around
+ uint8_t fNextElem, fNextName, fNextValue;
+
+ BMLW()
+ {
+ memset(fElems, 0, sizeof(fElems));
+ memset(fNames, 0, sizeof(fNames));
+ memset(fValues, 0, sizeof(fValues));
+
+ fNextElem = fNextName = fNextValue = 0;
+ }
+ ~BMLW()
+ {
+ freeAll(fElems);
+ freeAll(fNames);
+ freeAll(fValues);
+ }
+};
+
+static void rattr(unsigned verb, SkStream& s, BMLW& rec, SkXMLWriter& writer)
+{
+ int data = verb & 31;
+ verb >>= 5;
+
+ int nameIndex, valueIndex;
+
+ switch (verb) {
+ case kAttr_Value_Value_Verb:
+ nameIndex = rec.fNextName; // record before the ++
+ set(rec.fNames, rec.fNextName++, s, data);
+ valueIndex = rec.fNextValue; // record before the ++
+ set(rec.fValues, rec.fNextValue++, s, 31);
+ break;
+ case kAttr_Value_Index_Verb:
+ nameIndex = rec.fNextName; // record before the ++
+ set(rec.fNames, rec.fNextName++, s, data);
+ valueIndex = rbyte(s);
+ break;
+ case kAttr_Index_Value_Verb:
+ nameIndex = rdata(s, data);
+ valueIndex = rec.fNextValue; // record before the ++
+ set(rec.fValues, rec.fNextValue++, s, 31);
+ break;
+ case kAttr_Index_Index_Verb:
+ nameIndex = rdata(s, data);
+ valueIndex = rbyte(s);
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ writer.addAttribute(rec.fNames[nameIndex], rec.fValues[valueIndex]);
+}
+
+static void relem(unsigned verb, SkStream& s, BMLW& rec, SkXMLWriter& writer)
+{
+ int data = verb & 31;
+ verb >>= 5;
+
+ int elemIndex;
+
+ if (verb == kStartElem_Value_Verb)
+ {
+ elemIndex = rec.fNextElem; // record before the ++
+ set(rec.fElems, rec.fNextElem++, s, data);
+ }
+ else
+ {
+ SkASSERT(verb == kStartElem_Index_Verb);
+ elemIndex = rdata(s, data);
+ }
+
+ writer.startElement(rec.fElems[elemIndex]);
+
+ for (;;)
+ {
+ verb = rbyte(s);
+ switch (verb >> 5) {
+ case kAttr_Value_Value_Verb:
+ case kAttr_Value_Index_Verb:
+ case kAttr_Index_Value_Verb:
+ case kAttr_Index_Index_Verb:
+ rattr(verb, s, rec, writer);
+ break;
+ case kStartElem_Value_Verb:
+ case kStartElem_Index_Verb:
+ relem(verb, s, rec, writer);
+ break;
+ case kEndElem_Verb:
+ writer.endElement();
+ return;
+ default:
+ SkDEBUGFAIL("bad verb");
+ }
+ }
+}
+
+void BML_XMLParser::Read(SkStream& s, SkXMLWriter& writer)
+{
+ BMLW rec;
+ writer.writeHeader();
+ relem(rbyte(s), s, rec, writer);
+}
+
+void BML_XMLParser::Read(SkStream& s, SkWStream& output)
+{
+ SkXMLStreamWriter writer(&output);
+ Read(s, writer);
+}
+
+void BML_XMLParser::Read(SkStream& s, SkXMLParser& output)
+{
+ SkXMLParserWriter writer(&output);
+ Read(s, writer);
+}
diff --git a/xml/SkDOM.cpp b/xml/SkDOM.cpp
new file mode 100644
index 00000000..98546089
--- /dev/null
+++ b/xml/SkDOM.cpp
@@ -0,0 +1,503 @@
+
+/*
+ * 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 "SkDOM.h"
+
+/////////////////////////////////////////////////////////////////////////
+
+#include "SkXMLParser.h"
+
+bool SkXMLParser::parse(const SkDOM& dom, const SkDOMNode* node)
+{
+ const char* elemName = dom.getName(node);
+
+ if (this->startElement(elemName))
+ return false;
+
+ SkDOM::AttrIter iter(dom, node);
+ const char* name, *value;
+
+ while ((name = iter.next(&value)) != NULL)
+ if (this->addAttribute(name, value))
+ return false;
+
+ if ((node = dom.getFirstChild(node)) != NULL)
+ do {
+ if (!this->parse(dom, node))
+ return false;
+ } while ((node = dom.getNextSibling(node)) != NULL);
+
+ return !this->endElement(elemName);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+struct SkDOMAttr {
+ const char* fName;
+ const char* fValue;
+};
+
+struct SkDOMNode {
+ const char* fName;
+ SkDOMNode* fFirstChild;
+ SkDOMNode* fNextSibling;
+ uint16_t fAttrCount;
+ uint8_t fType;
+ uint8_t fPad;
+
+ const SkDOMAttr* attrs() const
+ {
+ return (const SkDOMAttr*)(this + 1);
+ }
+ SkDOMAttr* attrs()
+ {
+ return (SkDOMAttr*)(this + 1);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////
+
+#define kMinChunkSize 512
+
+SkDOM::SkDOM() : fAlloc(kMinChunkSize), fRoot(NULL)
+{
+}
+
+SkDOM::~SkDOM()
+{
+}
+
+const SkDOM::Node* SkDOM::getRootNode() const
+{
+ return fRoot;
+}
+
+const SkDOM::Node* SkDOM::getFirstChild(const Node* node, const char name[]) const
+{
+ SkASSERT(node);
+ const Node* child = node->fFirstChild;
+
+ if (name)
+ {
+ for (; child != NULL; child = child->fNextSibling)
+ if (!strcmp(name, child->fName))
+ break;
+ }
+ return child;
+}
+
+const SkDOM::Node* SkDOM::getNextSibling(const Node* node, const char name[]) const
+{
+ SkASSERT(node);
+ const Node* sibling = node->fNextSibling;
+ if (name)
+ {
+ for (; sibling != NULL; sibling = sibling->fNextSibling)
+ if (!strcmp(name, sibling->fName))
+ break;
+ }
+ return sibling;
+}
+
+SkDOM::Type SkDOM::getType(const Node* node) const
+{
+ SkASSERT(node);
+ return (Type)node->fType;
+}
+
+const char* SkDOM::getName(const Node* node) const
+{
+ SkASSERT(node);
+ return node->fName;
+}
+
+const char* SkDOM::findAttr(const Node* node, const char name[]) const
+{
+ SkASSERT(node);
+ const Attr* attr = node->attrs();
+ const Attr* stop = attr + node->fAttrCount;
+
+ while (attr < stop)
+ {
+ if (!strcmp(attr->fName, name))
+ return attr->fValue;
+ attr += 1;
+ }
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+const SkDOM::Attr* SkDOM::getFirstAttr(const Node* node) const
+{
+ return node->fAttrCount ? node->attrs() : NULL;
+}
+
+const SkDOM::Attr* SkDOM::getNextAttr(const Node* node, const Attr* attr) const
+{
+ SkASSERT(node);
+ if (attr == NULL)
+ return NULL;
+ return (attr - node->attrs() + 1) < node->fAttrCount ? attr + 1 : NULL;
+}
+
+const char* SkDOM::getAttrName(const Node* node, const Attr* attr) const
+{
+ SkASSERT(node);
+ SkASSERT(attr);
+ return attr->fName;
+}
+
+const char* SkDOM::getAttrValue(const Node* node, const Attr* attr) const
+{
+ SkASSERT(node);
+ SkASSERT(attr);
+ return attr->fValue;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+SkDOM::AttrIter::AttrIter(const SkDOM&, const SkDOM::Node* node)
+{
+ SkASSERT(node);
+ fAttr = node->attrs();
+ fStop = fAttr + node->fAttrCount;
+}
+
+const char* SkDOM::AttrIter::next(const char** value)
+{
+ const char* name = NULL;
+
+ if (fAttr < fStop)
+ {
+ name = fAttr->fName;
+ if (value)
+ *value = fAttr->fValue;
+ fAttr += 1;
+ }
+ return name;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+#include "SkXMLParser.h"
+#include "SkTDArray.h"
+
+static char* dupstr(SkChunkAlloc* chunk, const char src[])
+{
+ SkASSERT(chunk && src);
+ size_t len = strlen(src);
+ char* dst = (char*)chunk->alloc(len + 1, SkChunkAlloc::kThrow_AllocFailType);
+ memcpy(dst, src, len + 1);
+ return dst;
+}
+
+class SkDOMParser : public SkXMLParser {
+ bool fNeedToFlush;
+public:
+ SkDOMParser(SkChunkAlloc* chunk) : SkXMLParser(&fParserError), fAlloc(chunk)
+ {
+ fRoot = NULL;
+ fLevel = 0;
+ fNeedToFlush = true;
+ }
+ SkDOM::Node* getRoot() const { return fRoot; }
+ SkXMLParserError fParserError;
+protected:
+ void flushAttributes()
+ {
+ int attrCount = fAttrs.count();
+
+ SkDOM::Node* node = (SkDOM::Node*)fAlloc->alloc(sizeof(SkDOM::Node) + attrCount * sizeof(SkDOM::Attr),
+ SkChunkAlloc::kThrow_AllocFailType);
+
+ node->fName = fElemName;
+ node->fFirstChild = NULL;
+ node->fAttrCount = SkToU16(attrCount);
+ node->fType = SkDOM::kElement_Type;
+
+ if (fRoot == NULL)
+ {
+ node->fNextSibling = NULL;
+ fRoot = node;
+ }
+ else // this adds siblings in reverse order. gets corrected in onEndElement()
+ {
+ SkDOM::Node* parent = fParentStack.top();
+ SkASSERT(fRoot && parent);
+ node->fNextSibling = parent->fFirstChild;
+ parent->fFirstChild = node;
+ }
+ *fParentStack.push() = node;
+
+ memcpy(node->attrs(), fAttrs.begin(), attrCount * sizeof(SkDOM::Attr));
+ fAttrs.reset();
+
+ }
+ virtual bool onStartElement(const char elem[])
+ {
+ if (fLevel > 0 && fNeedToFlush)
+ this->flushAttributes();
+ fNeedToFlush = true;
+ fElemName = dupstr(fAlloc, elem);
+ ++fLevel;
+ return false;
+ }
+ virtual bool onAddAttribute(const char name[], const char value[])
+ {
+ SkDOM::Attr* attr = fAttrs.append();
+ attr->fName = dupstr(fAlloc, name);
+ attr->fValue = dupstr(fAlloc, value);
+ return false;
+ }
+ virtual bool onEndElement(const char elem[])
+ {
+ --fLevel;
+ if (fNeedToFlush)
+ this->flushAttributes();
+ fNeedToFlush = false;
+
+ SkDOM::Node* parent;
+
+ fParentStack.pop(&parent);
+
+ SkDOM::Node* child = parent->fFirstChild;
+ SkDOM::Node* prev = NULL;
+ while (child)
+ {
+ SkDOM::Node* next = child->fNextSibling;
+ child->fNextSibling = prev;
+ prev = child;
+ child = next;
+ }
+ parent->fFirstChild = prev;
+ return false;
+ }
+private:
+ SkTDArray<SkDOM::Node*> fParentStack;
+ SkChunkAlloc* fAlloc;
+ SkDOM::Node* fRoot;
+
+ // state needed for flushAttributes()
+ SkTDArray<SkDOM::Attr> fAttrs;
+ char* fElemName;
+ int fLevel;
+};
+
+const SkDOM::Node* SkDOM::build(const char doc[], size_t len)
+{
+ fAlloc.reset();
+ SkDOMParser parser(&fAlloc);
+ if (!parser.parse(doc, len))
+ {
+ SkDEBUGCODE(SkDebugf("xml parse error, line %d\n", parser.fParserError.getLineNumber());)
+ fRoot = NULL;
+ fAlloc.reset();
+ return NULL;
+ }
+ fRoot = parser.getRoot();
+ return fRoot;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser)
+{
+ const char* elem = dom.getName(node);
+
+ parser->startElement(elem);
+
+ SkDOM::AttrIter iter(dom, node);
+ const char* name;
+ const char* value;
+ while ((name = iter.next(&value)) != NULL)
+ parser->addAttribute(name, value);
+
+ node = dom.getFirstChild(node, NULL);
+ while (node)
+ {
+ walk_dom(dom, node, parser);
+ node = dom.getNextSibling(node, NULL);
+ }
+
+ parser->endElement(elem);
+}
+
+const SkDOM::Node* SkDOM::copy(const SkDOM& dom, const SkDOM::Node* node)
+{
+ fAlloc.reset();
+ SkDOMParser parser(&fAlloc);
+
+ walk_dom(dom, node, &parser);
+
+ fRoot = parser.getRoot();
+ return fRoot;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+int SkDOM::countChildren(const Node* node, const char elem[]) const
+{
+ int count = 0;
+
+ node = this->getFirstChild(node, elem);
+ while (node)
+ {
+ count += 1;
+ node = this->getNextSibling(node, elem);
+ }
+ return count;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#include "SkParse.h"
+
+bool SkDOM::findS32(const Node* node, const char name[], int32_t* value) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr && SkParse::FindS32(vstr, value);
+}
+
+bool SkDOM::findScalars(const Node* node, const char name[], SkScalar value[], int count) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr && SkParse::FindScalars(vstr, value, count);
+}
+
+bool SkDOM::findHex(const Node* node, const char name[], uint32_t* value) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr && SkParse::FindHex(vstr, value);
+}
+
+bool SkDOM::findBool(const Node* node, const char name[], bool* value) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr && SkParse::FindBool(vstr, value);
+}
+
+int SkDOM::findList(const Node* node, const char name[], const char list[]) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr ? SkParse::FindList(vstr, list) : -1;
+}
+
+bool SkDOM::hasAttr(const Node* node, const char name[], const char value[]) const
+{
+ const char* vstr = this->findAttr(node, name);
+ return vstr && !strcmp(vstr, value);
+}
+
+bool SkDOM::hasS32(const Node* node, const char name[], int32_t target) const
+{
+ const char* vstr = this->findAttr(node, name);
+ int32_t value;
+ return vstr && SkParse::FindS32(vstr, &value) && value == target;
+}
+
+bool SkDOM::hasScalar(const Node* node, const char name[], SkScalar target) const
+{
+ const char* vstr = this->findAttr(node, name);
+ SkScalar value;
+ return vstr && SkParse::FindScalar(vstr, &value) && value == target;
+}
+
+bool SkDOM::hasHex(const Node* node, const char name[], uint32_t target) const
+{
+ const char* vstr = this->findAttr(node, name);
+ uint32_t value;
+ return vstr && SkParse::FindHex(vstr, &value) && value == target;
+}
+
+bool SkDOM::hasBool(const Node* node, const char name[], bool target) const
+{
+ const char* vstr = this->findAttr(node, name);
+ bool value;
+ return vstr && SkParse::FindBool(vstr, &value) && value == target;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+static void tab(int level)
+{
+ while (--level >= 0)
+ SkDebugf("\t");
+}
+
+void SkDOM::dump(const Node* node, int level) const
+{
+ if (node == NULL)
+ node = this->getRootNode();
+ if (node)
+ {
+ tab(level);
+ SkDebugf("<%s", this->getName(node));
+
+ const Attr* attr = node->attrs();
+ const Attr* stop = attr + node->fAttrCount;
+ for (; attr < stop; attr++)
+ SkDebugf(" %s=\"%s\"", attr->fName, attr->fValue);
+
+ const Node* child = this->getFirstChild(node);
+ if (child)
+ {
+ SkDebugf(">\n");
+ while (child)
+ {
+ this->dump(child, level+1);
+ child = this->getNextSibling(child);
+ }
+ tab(level);
+ SkDebugf("</%s>\n", node->fName);
+ }
+ else
+ SkDebugf("/>\n");
+ }
+}
+
+void SkDOM::UnitTest()
+{
+#ifdef SK_SUPPORT_UNITTEST
+ static const char gDoc[] =
+ "<root a='1' b='2'>"
+ "<elem1 c='3' />"
+ "<elem2 d='4' />"
+ "<elem3 e='5'>"
+ "<subelem1/>"
+ "<subelem2 f='6' g='7'/>"
+ "</elem3>"
+ "<elem4 h='8'/>"
+ "</root>"
+ ;
+
+ SkDOM dom;
+
+ SkASSERT(dom.getRootNode() == NULL);
+
+ const Node* root = dom.build(gDoc, sizeof(gDoc) - 1);
+ SkASSERT(root && dom.getRootNode() == root);
+
+ const char* v = dom.findAttr(root, "a");
+ SkASSERT(v && !strcmp(v, "1"));
+ v = dom.findAttr(root, "b");
+ SkASSERT(v && !strcmp(v, "2"));
+ v = dom.findAttr(root, "c");
+ SkASSERT(v == NULL);
+
+ SkASSERT(dom.getFirstChild(root, "elem1"));
+ SkASSERT(!dom.getFirstChild(root, "subelem1"));
+
+ dom.dump();
+#endif
+}
+
+#endif
diff --git a/xml/SkJS.cpp b/xml/SkJS.cpp
new file mode 100644
index 00000000..8167c9c1
--- /dev/null
+++ b/xml/SkJS.cpp
@@ -0,0 +1,228 @@
+
+/*
+ * 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 <jsapi.h>
+
+#include "SkJS.h"
+#include "SkString.h"
+
+#ifdef _WIN32_WCE
+extern "C" {
+ void abort() {
+ SkASSERT(0);
+ }
+
+ unsigned int _control87(unsigned int _new, unsigned int mask ) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ time_t mktime(struct tm *timeptr ) {
+ SkASSERT(0);
+ return 0;
+ }
+
+// int errno;
+
+ char *strdup(const char *) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ char *strerror(int errnum) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ int isatty(void* fd) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ int putenv(const char *envstring) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ char *getenv(const char *varname) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ void GetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime) {
+ SkASSERT(0);
+ }
+
+ struct tm * localtime(const time_t *timer) {
+ SkASSERT(0);
+ return 0;
+ }
+
+ size_t strftime(char *strDest, size_t maxsize, const char *format,
+ const struct tm *timeptr ) {
+ SkASSERT(0);
+ return 0;
+ }
+
+}
+#endif
+
+static JSBool
+global_enumerate(JSContext *cx, JSObject *obj)
+{
+#ifdef LAZY_STANDARD_CLASSES
+ return JS_EnumerateStandardClasses(cx, obj);
+#else
+ return JS_TRUE;
+#endif
+}
+
+static JSBool
+global_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp)
+{
+#ifdef LAZY_STANDARD_CLASSES
+ if ((flags & JSRESOLVE_ASSIGNING) == 0) {
+ JSBool resolved;
+
+ if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
+ return JS_FALSE;
+ if (resolved) {
+ *objp = obj;
+ return JS_TRUE;
+ }
+ }
+#endif
+
+#if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX)
+ if ((flags & (JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING)) == 0) {
+ /*
+ * Do this expensive hack only for unoptimized Unix builds, which are
+ * not used for benchmarking.
+ */
+ char *path, *comp, *full;
+ const char *name;
+ JSBool ok, found;
+ JSFunction *fun;
+
+ if (!JSVAL_IS_STRING(id))
+ return JS_TRUE;
+ path = getenv("PATH");
+ if (!path)
+ return JS_TRUE;
+ path = JS_strdup(cx, path);
+ if (!path)
+ return JS_FALSE;
+ name = JS_GetStringBytes(JSVAL_TO_STRING(id));
+ ok = JS_TRUE;
+ for (comp = strtok(path, ":"); comp; comp = strtok(NULL, ":")) {
+ if (*comp != '\0') {
+ full = JS_smprintf("%s/%s", comp, name);
+ if (!full) {
+ JS_ReportOutOfMemory(cx);
+ ok = JS_FALSE;
+ break;
+ }
+ } else {
+ full = (char *)name;
+ }
+ found = (access(full, X_OK) == 0);
+ if (*comp != '\0')
+ free(full);
+ if (found) {
+ fun = JS_DefineFunction(cx, obj, name, Exec, 0, JSPROP_ENUMERATE);
+ ok = (fun != NULL);
+ if (ok)
+ *objp = obj;
+ break;
+ }
+ }
+ JS_free(cx, path);
+ return ok;
+ }
+#else
+ return JS_TRUE;
+#endif
+}
+
+JSClass global_class = {
+ "global", JSCLASS_NEW_RESOLVE,
+ JS_PropertyStub, JS_PropertyStub,
+ JS_PropertyStub, JS_PropertyStub,
+ global_enumerate, (JSResolveOp) global_resolve,
+ JS_ConvertStub, JS_FinalizeStub
+};
+
+SkJS::SkJS(void* hwnd) : SkOSWindow(hwnd) {
+ if ((fRuntime = JS_NewRuntime(0x100000)) == NULL) {
+ SkASSERT(0);
+ return;
+ }
+ if ((fContext = JS_NewContext(fRuntime, 0x1000)) == NULL) {
+ SkASSERT(0);
+ return;
+ }
+ ;
+ if ((fGlobal = JS_NewObject(fContext, &global_class, NULL, NULL)) == NULL) {
+ SkASSERT(0);
+ return;
+ }
+ if (JS_InitStandardClasses(fContext, fGlobal) == NULL) {
+ SkASSERT(0);
+ return;
+ }
+ setConfig(SkBitmap::kARGB32_Config);
+ updateSize();
+ setVisibleP(true);
+ InitializeDisplayables(getBitmap(), fContext, fGlobal, NULL);
+}
+
+SkJS::~SkJS() {
+ DisposeDisplayables();
+ JS_DestroyContext(fContext);
+ JS_DestroyRuntime(fRuntime);
+ JS_ShutDown();
+}
+
+SkBool SkJS::EvaluateScript(const char* script, jsval* rVal) {
+ return JS_EvaluateScript(fContext, fGlobal, script, strlen(script),
+ "memory" /* no file name */, 0 /* no line number */, rVal);
+}
+
+SkBool SkJS::ValueToString(jsval value, SkString* string) {
+ JSString* str = JS_ValueToString(fContext, value);
+ if (str == NULL)
+ return false;
+ string->set(JS_GetStringBytes(str));
+ return true;
+}
+
+#ifdef SK_DEBUG
+void SkJS::Test(void* hwnd) {
+ SkJS js(hwnd);
+ jsval val;
+ SkBool success = js.EvaluateScript("22/7", &val);
+ SkASSERT(success);
+ SkString string;
+ success = js.ValueToString(val, &string);
+ SkASSERT(success);
+ SkASSERT(strcmp(string.c_str(), "3.142857142857143") == 0);
+ success = js.EvaluateScript(
+ "var rect = new rectangle();"
+ "rect.left = 4;"
+ "rect.top = 10;"
+ "rect.right = 20;"
+ "rect.bottom = 30;"
+ "rect.width = rect.height + 20;"
+ "rect.draw();"
+ , &val);
+ SkASSERT(success);
+ success = js.ValueToString(val, &string);
+ SkASSERT(success);
+}
+#endifASSERT(success);
diff --git a/xml/SkJSDisplayable.cpp b/xml/SkJSDisplayable.cpp
new file mode 100644
index 00000000..50277974
--- /dev/null
+++ b/xml/SkJSDisplayable.cpp
@@ -0,0 +1,460 @@
+
+/*
+ * 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 <jsapi.h>
+#include "SkJS.h"
+#include "SkDisplayType.h"
+//#include "SkAnimateColor.h"
+#include "SkAnimateMaker.h"
+#include "SkAnimateSet.h"
+//#include "SkAnimateTransform.h"
+#include "SkCanvas.h"
+//#include "SkDimensions.h"
+#include "SkDisplayAdd.h"
+#include "SkDisplayApply.h"
+//#include "SkDisplayBefore.h"
+#include "SkDisplayEvent.h"
+//#include "SkDisplayFocus.h"
+#include "SkDisplayInclude.h"
+#include "SkDisplayPost.h"
+#include "SkDisplayRandom.h"
+#include "SkDraw3D.h"
+#include "SkDrawBitmap.h"
+#include "SkDrawClip.h"
+#include "SkDrawDash.h"
+#include "SkDrawDiscrete.h"
+#include "SkDrawEmboss.h"
+//#include "SkDrawFont.h"
+#include "SkDrawFull.h"
+#include "SkDrawGradient.h"
+#include "SkDrawLine.h"
+//#include "SkDrawMaskFilter.h"
+#include "SkDrawMatrix.h"
+#include "SkDrawOval.h"
+#include "SkDrawPaint.h"
+#include "SkDrawPath.h"
+#include "SkDrawPoint.h"
+// #include "SkDrawStroke.h"
+#include "SkDrawText.h"
+#include "SkDrawTo.h"
+//#include "SkDrawTransferMode.h"
+#include "SkDrawTransparentShader.h"
+//#include "SkDrawUse.h"
+#include "SkMatrixParts.h"
+#include "SkPathParts.h"
+#include "SkPostParts.h"
+#include "SkScript.h"
+#include "SkSnapshot.h"
+#include "SkTextOnPath.h"
+#include "SkTextToPath.h"
+
+
+class SkJSDisplayable {
+public:
+ SkJSDisplayable() : fDisplayable(NULL) {}
+ ~SkJSDisplayable() { delete fDisplayable; }
+ static void Destructor(JSContext *cx, JSObject *obj);
+ static JSBool GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+ static JSBool SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+ static SkCanvas* gCanvas;
+ static SkPaint* gPaint;
+ static JSBool Draw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
+ SkDisplayable* fDisplayable;
+};
+
+SkCanvas* SkJSDisplayable::gCanvas;
+SkPaint* SkJSDisplayable::gPaint;
+
+JSBool SkJSDisplayable::Draw(JSContext *cx, JSObject *obj, uintN argc,
+ jsval *argv, jsval *rval)
+{
+ SkJSDisplayable *p = (SkJSDisplayable*) JS_GetPrivate(cx, obj);
+ SkASSERT(p->fDisplayable->isDrawable());
+ SkDrawable* drawable = (SkDrawable*) p->fDisplayable;
+ SkAnimateMaker maker(NULL, gCanvas, gPaint);
+ drawable->draw(maker);
+ return JS_TRUE;
+}
+
+
+JSFunctionSpec SkJSDisplayable_methods[] =
+{
+ { "draw", SkJSDisplayable::Draw, 1, 0, 0 },
+ { 0 }
+};
+
+static JSPropertySpec* gDisplayableProperties[kNumberOfTypes];
+static JSClass gDisplayableClasses[kNumberOfTypes];
+
+#define JS_INIT(_prefix, _class) \
+static JSBool _class##Constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { \
+ SkJSDisplayable* jsDisplayable = new SkJSDisplayable(); \
+ jsDisplayable->fDisplayable = new _prefix##_class(); \
+ JS_SetPrivate(cx, obj, (void*) jsDisplayable); \
+ return JS_TRUE; \
+} \
+ \
+static JSObject* _class##Init(JSContext *cx, JSObject *obj, JSObject *proto) { \
+ JSObject *newProtoObj = JS_InitClass(cx, obj, proto, &gDisplayableClasses[SkType_##_class], \
+ _class##Constructor, 0, \
+ NULL, SkJSDisplayable_methods , \
+ NULL, NULL); \
+ JS_DefineProperties(cx, newProtoObj, gDisplayableProperties[SkType_##_class]); \
+ return newProtoObj; \
+}
+
+JS_INIT(Sk, Add)
+JS_INIT(Sk, AddCircle)
+JS_INIT(Sk, AddOval)
+JS_INIT(Sk, AddPath)
+JS_INIT(Sk, AddRectangle)
+JS_INIT(Sk, AddRoundRect)
+//JS_INIT(Sk, After)
+JS_INIT(Sk, Apply)
+// JS_INIT(Sk, Animate)
+//JS_INIT(Sk, AnimateColor)
+JS_INIT(Sk, AnimateField)
+//JS_INIT(Sk, AnimateRotate)
+//JS_INIT(Sk, AnimateScale)
+//JS_INIT(Sk, AnimateTranslate)
+JS_INIT(SkDraw, Bitmap)
+JS_INIT(Sk, BaseBitmap)
+//JS_INIT(Sk, Before)
+JS_INIT(SkDraw, BitmapShader)
+JS_INIT(SkDraw, Blur)
+JS_INIT(SkDraw, Clip)
+JS_INIT(SkDraw, Color)
+JS_INIT(Sk, CubicTo)
+JS_INIT(Sk, Dash)
+JS_INIT(Sk, Data)
+//JS_INIT(Sk, Dimensions)
+JS_INIT(Sk, Discrete)
+JS_INIT(Sk, DrawTo)
+JS_INIT(SkDraw, Emboss)
+JS_INIT(SkDisplay, Event)
+// JS_INIT(SkDraw, Font)
+// JS_INIT(Sk, Focus)
+JS_INIT(Sk, Image)
+JS_INIT(Sk, Include)
+// JS_INIT(Sk, Input)
+JS_INIT(Sk, Line)
+JS_INIT(Sk, LinearGradient)
+JS_INIT(Sk, LineTo)
+JS_INIT(SkDraw, Matrix)
+JS_INIT(Sk, Move)
+JS_INIT(Sk, MoveTo)
+JS_INIT(Sk, Oval)
+JS_INIT(SkDraw, Path)
+JS_INIT(SkDraw, Paint)
+JS_INIT(Sk, DrawPoint)
+JS_INIT(Sk, PolyToPoly)
+JS_INIT(Sk, Polygon)
+JS_INIT(Sk, Polyline)
+JS_INIT(Sk, Post)
+JS_INIT(Sk, QuadTo)
+JS_INIT(Sk, RadialGradient)
+JS_INIT(SkDisplay, Random)
+JS_INIT(Sk, RectToRect)
+JS_INIT(Sk, Rectangle)
+JS_INIT(Sk, Remove)
+JS_INIT(Sk, Replace)
+JS_INIT(Sk, Rotate)
+JS_INIT(Sk, RoundRect)
+JS_INIT(Sk, Scale)
+JS_INIT(Sk, Set)
+JS_INIT(Sk, Skew)
+// JS_INIT(Sk, 3D_Camera)
+// JS_INIT(Sk, 3D_Patch)
+JS_INIT(Sk, Snapshot)
+// JS_INIT(SkDraw, Stroke)
+JS_INIT(Sk, Text)
+JS_INIT(Sk, TextOnPath)
+JS_INIT(Sk, TextToPath)
+JS_INIT(Sk, Translate)
+//JS_INIT(Sk, Use)
+
+#if SK_USE_CONDENSED_INFO == 0
+static void GenerateTables() {
+ for (int index = 0; index < kTypeNamesSize; index++) {
+ int infoCount;
+ SkDisplayTypes type = gTypeNames[index].fType;
+ const SkMemberInfo* info = SkDisplayType::GetMembers(NULL /* fMaker */, type, &infoCount);
+ if (info == NULL)
+ continue;
+ gDisplayableProperties[type] = new JSPropertySpec[infoCount + 1];
+ JSPropertySpec* propertySpec = gDisplayableProperties[type];
+ memset(propertySpec, 0, sizeof (JSPropertySpec) * (infoCount + 1));
+ for (int inner = 0; inner < infoCount; inner++) {
+ if (info[inner].fType == SkType_BaseClassInfo)
+ continue;
+ propertySpec[inner].name = info[inner].fName;
+ propertySpec[inner].tinyid = inner;
+ propertySpec[inner].flags = JSPROP_ENUMERATE;
+ }
+ gDisplayableClasses[type].name = gTypeNames[index].fName;
+ gDisplayableClasses[type].flags = JSCLASS_HAS_PRIVATE;
+ gDisplayableClasses[type].addProperty = JS_PropertyStub;
+ gDisplayableClasses[type].delProperty = JS_PropertyStub;
+ gDisplayableClasses[type].getProperty = SkJSDisplayable::GetProperty;
+ gDisplayableClasses[type].setProperty = SkJSDisplayable::SetProperty;
+ gDisplayableClasses[type].enumerate = JS_EnumerateStub;
+ gDisplayableClasses[type].resolve = JS_ResolveStub;
+ gDisplayableClasses[type].convert = JS_ConvertStub;
+ gDisplayableClasses[type].finalize = SkJSDisplayable::Destructor;
+ }
+}
+#endif
+
+void SkJSDisplayable::Destructor(JSContext *cx, JSObject *obj) {
+ delete (SkJSDisplayable*) JS_GetPrivate(cx, obj);
+}
+
+JSBool SkJSDisplayable::GetProperty(JSContext *cx, JSObject *obj, jsval id,
+ jsval *vp)
+{
+ if (JSVAL_IS_INT(id) == 0)
+ return JS_TRUE;
+ SkJSDisplayable *p = (SkJSDisplayable *) JS_GetPrivate(cx, obj);
+ SkDisplayable* displayable = p->fDisplayable;
+ SkDisplayTypes displayableType = displayable->getType();
+ int members;
+ const SkMemberInfo* info = SkDisplayType::GetMembers(NULL /* fMaker */, displayableType, &members);
+ int idIndex = JSVAL_TO_INT(id);
+ SkASSERT(idIndex >= 0 && idIndex < members);
+ info = &info[idIndex];
+ SkDisplayTypes infoType = (SkDisplayTypes) info->fType;
+ SkScalar scalar = 0;
+ S32 s32 = 0;
+ SkString* string= NULL;
+ JSString *str;
+ if (infoType == SkType_MemberProperty) {
+ infoType = info->propertyType();
+ switch (infoType) {
+ case SkType_Scalar: {
+ SkScriptValue scriptValue;
+ bool success = displayable->getProperty(info->propertyIndex(), &scriptValue);
+ SkASSERT(scriptValue.fType == SkType_Scalar);
+ scalar = scriptValue.fOperand.fScalar;
+ } break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ } else {
+ SkASSERT(info->fCount == 1);
+ switch (infoType) {
+ case SkType_Boolean:
+ case SkType_Color:
+ case SkType_S32:
+ s32 = *(S32*) info->memberData(displayable);
+ break;
+ case SkType_String:
+ info->getString(displayable, &string);
+ break;
+ case SkType_Scalar:
+ SkOperand operand;
+ info->getValue(displayable, &operand, 1);
+ scalar = operand.fScalar;
+ break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ }
+ switch (infoType) {
+ case SkType_Boolean:
+ *vp = BOOLEAN_TO_JSVAL(s32);
+ break;
+ case SkType_Color:
+ case SkType_S32:
+ *vp = INT_TO_JSVAL(s32);
+ break;
+ case SkType_Scalar:
+ if (SkScalarFraction(scalar) == 0)
+ *vp = INT_TO_JSVAL(SkScalarFloor(scalar));
+ else
+#ifdef SK_SCALAR_IS_FLOAT
+ *vp = DOUBLE_TO_JSVAL(scalar);
+#else
+ *vp = DOUBLE_TO_JSVAL(scalar / 65536.0f );
+#endif
+ break;
+ case SkType_String:
+ str = JS_NewStringCopyN(cx, string->c_str(), string->size());
+ *vp = STRING_TO_JSVAL(str);
+ break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ return JS_TRUE;
+}
+
+JSBool SkJSDisplayable::SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
+ if (JSVAL_IS_INT(id) == 0)
+ return JS_TRUE;
+ SkJSDisplayable *p = (SkJSDisplayable *) JS_GetPrivate(cx, obj);
+ SkDisplayable* displayable = p->fDisplayable;
+ SkDisplayTypes displayableType = displayable->getType();
+ int members;
+ const SkMemberInfo* info = SkDisplayType::GetMembers(NULL /* fMaker */, displayableType, &members);
+ int idIndex = JSVAL_TO_INT(id);
+ SkASSERT(idIndex >= 0 && idIndex < members);
+ info = &info[idIndex];
+ SkDisplayTypes infoType = info->getType();
+ SkScalar scalar = 0;
+ S32 s32 = 0;
+ SkString string;
+ JSString* str;
+ jsval value = *vp;
+ switch (infoType) {
+ case SkType_Boolean:
+ s32 = JSVAL_TO_BOOLEAN(value);
+ break;
+ case SkType_Color:
+ case SkType_S32:
+ s32 = JSVAL_TO_INT(value);
+ break;
+ case SkType_Scalar:
+ if (JSVAL_IS_INT(value))
+ scalar = SkIntToScalar(JSVAL_TO_INT(value));
+ else {
+ SkASSERT(JSVAL_IS_DOUBLE(value));
+#ifdef SK_SCALAR_IS_FLOAT
+ scalar = (float) *(double*) JSVAL_TO_DOUBLE(value);
+#else
+ scalar = (SkFixed) (*(double*)JSVAL_TO_DOUBLE(value) * 65536.0);
+#endif
+ }
+ break;
+ case SkType_String:
+ str = JS_ValueToString(cx, value);
+ string.set(JS_GetStringBytes(str));
+ break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ if (info->fType == SkType_MemberProperty) {
+ switch (infoType) {
+ case SkType_Scalar: {
+ SkScriptValue scriptValue;
+ scriptValue.fType = SkType_Scalar;
+ scriptValue.fOperand.fScalar = scalar;
+ displayable->setProperty(-1 - (int) info->fOffset, scriptValue);
+ } break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ } else {
+ SkASSERT(info->fCount == 1);
+ switch (infoType) {
+ case SkType_Boolean:
+ case SkType_Color:
+ case SkType_S32:
+ s32 = *(S32*) ((const char*) displayable + info->fOffset);
+ break;
+ case SkType_String:
+ info->setString(displayable, &string);
+ break;
+ case SkType_Scalar:
+ SkOperand operand;
+ operand.fScalar = scalar;
+ info->setValue(displayable, &operand, 1);
+ break;
+ default:
+ SkASSERT(0); // !!! unimplemented
+ }
+ }
+ return JS_TRUE;
+}
+
+void SkJS::InitializeDisplayables(const SkBitmap& bitmap, JSContext *cx, JSObject *obj, JSObject *proto) {
+ SkJSDisplayable::gCanvas = new SkCanvas(bitmap);
+ SkJSDisplayable::gPaint = new SkPaint();
+#if SK_USE_CONDENSED_INFO == 0
+ GenerateTables();
+#else
+ SkASSERT(0); // !!! compressed version hasn't been implemented
+#endif
+ AddInit(cx, obj, proto);
+ AddCircleInit(cx, obj, proto);
+ AddOvalInit(cx, obj, proto);
+ AddPathInit(cx, obj, proto);
+ AddRectangleInit(cx, obj, proto);
+ AddRoundRectInit(cx, obj, proto);
+// AfterInit(cx, obj, proto);
+ ApplyInit(cx, obj, proto);
+ // AnimateInit(cx, obj, proto);
+// AnimateColorInit(cx, obj, proto);
+ AnimateFieldInit(cx, obj, proto);
+// AnimateRotateInit(cx, obj, proto);
+// AnimateScaleInit(cx, obj, proto);
+// AnimateTranslateInit(cx, obj, proto);
+ BitmapInit(cx, obj, proto);
+// BaseBitmapInit(cx, obj, proto);
+// BeforeInit(cx, obj, proto);
+ BitmapShaderInit(cx, obj, proto);
+ BlurInit(cx, obj, proto);
+ ClipInit(cx, obj, proto);
+ ColorInit(cx, obj, proto);
+ CubicToInit(cx, obj, proto);
+ DashInit(cx, obj, proto);
+ DataInit(cx, obj, proto);
+// DimensionsInit(cx, obj, proto);
+ DiscreteInit(cx, obj, proto);
+ DrawToInit(cx, obj, proto);
+ EmbossInit(cx, obj, proto);
+ EventInit(cx, obj, proto);
+// FontInit(cx, obj, proto);
+// FocusInit(cx, obj, proto);
+ ImageInit(cx, obj, proto);
+ IncludeInit(cx, obj, proto);
+// InputInit(cx, obj, proto);
+ LineInit(cx, obj, proto);
+ LinearGradientInit(cx, obj, proto);
+ LineToInit(cx, obj, proto);
+ MatrixInit(cx, obj, proto);
+ MoveInit(cx, obj, proto);
+ MoveToInit(cx, obj, proto);
+ OvalInit(cx, obj, proto);
+ PathInit(cx, obj, proto);
+ PaintInit(cx, obj, proto);
+ DrawPointInit(cx, obj, proto);
+ PolyToPolyInit(cx, obj, proto);
+ PolygonInit(cx, obj, proto);
+ PolylineInit(cx, obj, proto);
+ PostInit(cx, obj, proto);
+ QuadToInit(cx, obj, proto);
+ RadialGradientInit(cx, obj, proto);
+ RandomInit(cx, obj, proto);
+ RectToRectInit(cx, obj, proto);
+ RectangleInit(cx, obj, proto);
+ RemoveInit(cx, obj, proto);
+ ReplaceInit(cx, obj, proto);
+ RotateInit(cx, obj, proto);
+ RoundRectInit(cx, obj, proto);
+ ScaleInit(cx, obj, proto);
+ SetInit(cx, obj, proto);
+ SkewInit(cx, obj, proto);
+ // 3D_CameraInit(cx, obj, proto);
+ // 3D_PatchInit(cx, obj, proto);
+ SnapshotInit(cx, obj, proto);
+// StrokeInit(cx, obj, proto);
+ TextInit(cx, obj, proto);
+ TextOnPathInit(cx, obj, proto);
+ TextToPathInit(cx, obj, proto);
+ TranslateInit(cx, obj, proto);
+// UseInit(cx, obj, proto);
+}
+
+void SkJS::DisposeDisplayables() {
+ delete SkJSDisplayable::gPaint;
+ delete SkJSDisplayable::gCanvas;
+ for (int index = 0; index < kTypeNamesSize; index++) {
+ SkDisplayTypes type = gTypeNames[index].fType;
+ delete[] gDisplayableProperties[type];
+ }
+}
diff --git a/xml/SkXMLParser.cpp b/xml/SkXMLParser.cpp
new file mode 100644
index 00000000..63929a94
--- /dev/null
+++ b/xml/SkXMLParser.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * 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 "SkXMLParser.h"
+
+static char const* const gErrorStrings[] = {
+ "empty or missing file ",
+ "unknown element ",
+ "unknown attribute name ",
+ "error in attribute value ",
+ "duplicate ID ",
+ "unknown error "
+};
+
+SkXMLParserError::SkXMLParserError() : fCode(kNoError), fLineNumber(-1),
+ fNativeCode(-1)
+{
+ reset();
+}
+
+SkXMLParserError::~SkXMLParserError()
+{
+ // need a virtual destructor for our subclasses
+}
+
+void SkXMLParserError::getErrorString(SkString* str) const
+{
+ SkASSERT(str);
+ SkString temp;
+ if (fCode != kNoError) {
+ if ((unsigned)fCode < SK_ARRAY_COUNT(gErrorStrings))
+ temp.set(gErrorStrings[fCode - 1]);
+ temp.append(fNoun);
+ } else
+ SkXMLParser::GetNativeErrorString(fNativeCode, &temp);
+ str->append(temp);
+}
+
+void SkXMLParserError::reset() {
+ fCode = kNoError;
+ fLineNumber = -1;
+ fNativeCode = -1;
+}
+
+
+////////////////
+
+SkXMLParser::SkXMLParser(SkXMLParserError* parserError) : fParser(NULL), fError(parserError)
+{
+}
+
+SkXMLParser::~SkXMLParser()
+{
+}
+
+bool SkXMLParser::startElement(const char elem[])
+{
+ return this->onStartElement(elem);
+}
+
+bool SkXMLParser::addAttribute(const char name[], const char value[])
+{
+ return this->onAddAttribute(name, value);
+}
+
+bool SkXMLParser::endElement(const char elem[])
+{
+ return this->onEndElement(elem);
+}
+
+bool SkXMLParser::text(const char text[], int len)
+{
+ return this->onText(text, len);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SkXMLParser::onStartElement(const char elem[]) {return false; }
+bool SkXMLParser::onAddAttribute(const char name[], const char value[]) {return false; }
+bool SkXMLParser::onEndElement(const char elem[]) { return false; }
+bool SkXMLParser::onText(const char text[], int len) {return false; }
diff --git a/xml/SkXMLPullParser.cpp b/xml/SkXMLPullParser.cpp
new file mode 100644
index 00000000..ed042281
--- /dev/null
+++ b/xml/SkXMLPullParser.cpp
@@ -0,0 +1,138 @@
+
+/*
+ * 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 "SkXMLParser.h"
+#include "SkStream.h"
+
+static void reset(SkXMLPullParser::Curr* curr)
+{
+ curr->fEventType = SkXMLPullParser::ERROR;
+ curr->fName = "";
+ curr->fAttrInfoCount = 0;
+ curr->fIsWhitespace = false;
+}
+
+SkXMLPullParser::SkXMLPullParser() : fStream(NULL)
+{
+ fCurr.fEventType = ERROR;
+ fDepth = -1;
+}
+
+SkXMLPullParser::SkXMLPullParser(SkStream* stream) : fStream(NULL)
+{
+ fCurr.fEventType = ERROR;
+ fDepth = 0;
+
+ this->setStream(stream);
+}
+
+SkXMLPullParser::~SkXMLPullParser()
+{
+ this->setStream(NULL);
+}
+
+SkStream* SkXMLPullParser::setStream(SkStream* stream)
+{
+ if (fStream && !stream)
+ this->onExit();
+
+ SkRefCnt_SafeAssign(fStream, stream);
+
+ if (fStream)
+ {
+ fCurr.fEventType = START_DOCUMENT;
+ this->onInit();
+ }
+ else
+ {
+ fCurr.fEventType = ERROR;
+ }
+ fDepth = 0;
+
+ return fStream;
+}
+
+SkXMLPullParser::EventType SkXMLPullParser::nextToken()
+{
+ switch (fCurr.fEventType) {
+ case ERROR:
+ case END_DOCUMENT:
+ break;
+ case END_TAG:
+ fDepth -= 1;
+ // fall through
+ default:
+ reset(&fCurr);
+ fCurr.fEventType = this->onNextToken();
+ break;
+ }
+
+ switch (fCurr.fEventType) {
+ case START_TAG:
+ fDepth += 1;
+ break;
+ default:
+ break;
+ }
+
+ return fCurr.fEventType;
+}
+
+const char* SkXMLPullParser::getName()
+{
+ switch (fCurr.fEventType) {
+ case START_TAG:
+ case END_TAG:
+ return fCurr.fName;
+ default:
+ return NULL;
+ }
+}
+
+const char* SkXMLPullParser::getText()
+{
+ switch (fCurr.fEventType) {
+ case TEXT:
+ case IGNORABLE_WHITESPACE:
+ return fCurr.fName;
+ default:
+ return NULL;
+ }
+}
+
+bool SkXMLPullParser::isWhitespace()
+{
+ switch (fCurr.fEventType) {
+ case IGNORABLE_WHITESPACE:
+ return true;
+ case TEXT:
+ case CDSECT:
+ return fCurr.fIsWhitespace;
+ default:
+ return false; // unknown/illegal
+ }
+}
+
+int SkXMLPullParser::getAttributeCount()
+{
+ return fCurr.fAttrInfoCount;
+}
+
+void SkXMLPullParser::getAttributeInfo(int index, AttrInfo* info)
+{
+ SkASSERT((unsigned)index < (unsigned)fCurr.fAttrInfoCount);
+
+ if (info)
+ *info = fCurr.fAttrInfos[index];
+}
+
+bool SkXMLPullParser::onEntityReplacement(const char name[],
+ SkString* replacement)
+{
+ // TODO: std 5 entities here
+ return false;
+}
diff --git a/xml/SkXMLWriter.cpp b/xml/SkXMLWriter.cpp
new file mode 100644
index 00000000..2ff47eae
--- /dev/null
+++ b/xml/SkXMLWriter.cpp
@@ -0,0 +1,333 @@
+
+/*
+ * 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 "SkXMLWriter.h"
+#include "SkStream.h"
+
+SkXMLWriter::SkXMLWriter(bool doEscapeMarkup) : fDoEscapeMarkup(doEscapeMarkup)
+{
+}
+
+SkXMLWriter::~SkXMLWriter()
+{
+ SkASSERT(fElems.count() == 0);
+}
+
+void SkXMLWriter::flush()
+{
+ while (fElems.count())
+ this->endElement();
+}
+
+void SkXMLWriter::addAttribute(const char name[], const char value[])
+{
+ this->addAttributeLen(name, value, strlen(value));
+}
+
+void SkXMLWriter::addS32Attribute(const char name[], int32_t value)
+{
+ SkString tmp;
+ tmp.appendS32(value);
+ this->addAttribute(name, tmp.c_str());
+}
+
+void SkXMLWriter::addHexAttribute(const char name[], uint32_t value, int minDigits)
+{
+ SkString tmp("0x");
+ tmp.appendHex(value, minDigits);
+ this->addAttribute(name, tmp.c_str());
+}
+
+void SkXMLWriter::addScalarAttribute(const char name[], SkScalar value)
+{
+ SkString tmp;
+ tmp.appendScalar(value);
+ this->addAttribute(name, tmp.c_str());
+}
+
+void SkXMLWriter::doEnd(Elem* elem)
+{
+ delete elem;
+}
+
+bool SkXMLWriter::doStart(const char name[], size_t length)
+{
+ int level = fElems.count();
+ bool firstChild = level > 0 && !fElems[level-1]->fHasChildren;
+ if (firstChild)
+ fElems[level-1]->fHasChildren = true;
+ Elem** elem = fElems.push();
+ *elem = new Elem;
+ (*elem)->fName.set(name, length);
+ (*elem)->fHasChildren = 0;
+ return firstChild;
+}
+
+SkXMLWriter::Elem* SkXMLWriter::getEnd()
+{
+ Elem* elem;
+ fElems.pop(&elem);
+ return elem;
+}
+
+const char* SkXMLWriter::getHeader()
+{
+ static const char gHeader[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
+ return gHeader;
+}
+
+void SkXMLWriter::startElement(const char name[])
+{
+ this->startElementLen(name, strlen(name));
+}
+
+static const char* escape_char(char c, char storage[2])
+{
+ static const char* gEscapeChars[] = {
+ "<&lt;",
+ ">&gt;",
+ //"\"&quot;",
+ //"'&apos;",
+ "&&amp;"
+ };
+
+ const char** array = gEscapeChars;
+ for (unsigned i = 0; i < SK_ARRAY_COUNT(gEscapeChars); i++)
+ {
+ if (array[i][0] == c)
+ return &array[i][1];
+ }
+ storage[0] = c;
+ storage[1] = 0;
+ return storage;
+}
+
+static size_t escape_markup(char dst[], const char src[], size_t length)
+{
+ size_t extra = 0;
+ const char* stop = src + length;
+
+ while (src < stop)
+ {
+ char orig[2];
+ const char* seq = escape_char(*src, orig);
+ size_t seqSize = strlen(seq);
+
+ if (dst)
+ {
+ memcpy(dst, seq, seqSize);
+ dst += seqSize;
+ }
+
+ // now record the extra size needed
+ extra += seqSize - 1; // minus one to subtract the original char
+
+ // bump to the next src char
+ src += 1;
+ }
+ return extra;
+}
+
+void SkXMLWriter::addAttributeLen(const char name[], const char value[], size_t length)
+{
+ SkString valueStr;
+
+ if (fDoEscapeMarkup)
+ {
+ size_t extra = escape_markup(NULL, value, length);
+ if (extra)
+ {
+ valueStr.resize(length + extra);
+ (void)escape_markup(valueStr.writable_str(), value, length);
+ value = valueStr.c_str();
+ length += extra;
+ }
+ }
+ this->onAddAttributeLen(name, value, length);
+}
+
+void SkXMLWriter::startElementLen(const char elem[], size_t length)
+{
+ this->onStartElementLen(elem, length);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+static void write_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLWriter* w, bool skipRoot)
+{
+ if (!skipRoot)
+ {
+ w->startElement(dom.getName(node));
+
+ SkDOM::AttrIter iter(dom, node);
+ const char* name;
+ const char* value;
+ while ((name = iter.next(&value)) != NULL)
+ w->addAttribute(name, value);
+ }
+
+ node = dom.getFirstChild(node, NULL);
+ while (node)
+ {
+ write_dom(dom, node, w, false);
+ node = dom.getNextSibling(node, NULL);
+ }
+
+ if (!skipRoot)
+ w->endElement();
+}
+
+void SkXMLWriter::writeDOM(const SkDOM& dom, const SkDOM::Node* node, bool skipRoot)
+{
+ if (node)
+ write_dom(dom, node, this, skipRoot);
+}
+
+void SkXMLWriter::writeHeader()
+{
+}
+
+// SkXMLStreamWriter
+
+static void tab(SkWStream& stream, int level)
+{
+ for (int i = 0; i < level; i++)
+ stream.writeText("\t");
+}
+
+SkXMLStreamWriter::SkXMLStreamWriter(SkWStream* stream) : fStream(*stream)
+{
+}
+
+SkXMLStreamWriter::~SkXMLStreamWriter()
+{
+ this->flush();
+}
+
+void SkXMLStreamWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
+{
+ SkASSERT(!fElems.top()->fHasChildren);
+ fStream.writeText(" ");
+ fStream.writeText(name);
+ fStream.writeText("=\"");
+ fStream.write(value, length);
+ fStream.writeText("\"");
+}
+
+void SkXMLStreamWriter::onEndElement()
+{
+ Elem* elem = getEnd();
+ if (elem->fHasChildren)
+ {
+ tab(fStream, fElems.count());
+ fStream.writeText("</");
+ fStream.writeText(elem->fName.c_str());
+ fStream.writeText(">");
+ }
+ else
+ fStream.writeText("/>");
+ fStream.newline();
+ doEnd(elem);
+}
+
+void SkXMLStreamWriter::onStartElementLen(const char name[], size_t length)
+{
+ int level = fElems.count();
+ if (this->doStart(name, length))
+ {
+ // the first child, need to close with >
+ fStream.writeText(">");
+ fStream.newline();
+ }
+
+ tab(fStream, level);
+ fStream.writeText("<");
+ fStream.write(name, length);
+}
+
+void SkXMLStreamWriter::writeHeader()
+{
+ const char* header = getHeader();
+ fStream.write(header, strlen(header));
+ fStream.newline();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "SkXMLParser.h"
+
+SkXMLParserWriter::SkXMLParserWriter(SkXMLParser* parser)
+ : SkXMLWriter(false), fParser(*parser)
+{
+}
+
+SkXMLParserWriter::~SkXMLParserWriter()
+{
+ this->flush();
+}
+
+void SkXMLParserWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
+{
+ SkASSERT(fElems.count() == 0 || !fElems.top()->fHasChildren);
+ SkString str(value, length);
+ fParser.addAttribute(name, str.c_str());
+}
+
+void SkXMLParserWriter::onEndElement()
+{
+ Elem* elem = this->getEnd();
+ fParser.endElement(elem->fName.c_str());
+ this->doEnd(elem);
+}
+
+void SkXMLParserWriter::onStartElementLen(const char name[], size_t length)
+{
+ (void)this->doStart(name, length);
+ SkString str(name, length);
+ fParser.startElement(str.c_str());
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkXMLStreamWriter::UnitTest()
+{
+#ifdef SK_SUPPORT_UNITTEST
+ SkDebugWStream s;
+ SkXMLStreamWriter w(&s);
+
+ w.startElement("elem0");
+ w.addAttribute("hello", "world");
+ w.addS32Attribute("dec", 42);
+ w.addHexAttribute("hex", 0x42, 3);
+#ifdef SK_SCALAR_IS_FLOAT
+ w.addScalarAttribute("scalar", -4.2f);
+#endif
+ w.startElement("elem1");
+ w.endElement();
+ w.startElement("elem1");
+ w.addAttribute("name", "value");
+ w.endElement();
+ w.startElement("elem1");
+ w.startElement("elem2");
+ w.startElement("elem3");
+ w.addAttribute("name", "value");
+ w.endElement();
+ w.endElement();
+ w.startElement("elem2");
+ w.endElement();
+ w.endElement();
+ w.endElement();
+#endif
+}
+
+#endif