Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/paint/ClipPathClipper.h" | 5 #include "core/paint/ClipPathClipper.h" |
| 6 | 6 |
| 7 #include "core/dom/ElementTraversal.h" | |
| 7 #include "core/layout/svg/LayoutSVGResourceClipper.h" | 8 #include "core/layout/svg/LayoutSVGResourceClipper.h" |
| 9 #include "core/layout/svg/SVGLayoutSupport.h" | |
| 8 #include "core/layout/svg/SVGResources.h" | 10 #include "core/layout/svg/SVGResources.h" |
| 9 #include "core/layout/svg/SVGResourcesCache.h" | 11 #include "core/layout/svg/SVGResourcesCache.h" |
| 10 #include "core/paint/SVGClipPainter.h" | 12 #include "core/paint/LayoutObjectDrawingRecorder.h" |
| 13 #include "core/paint/PaintInfo.h" | |
| 14 #include "core/paint/TransformRecorder.h" | |
| 11 #include "core/style/ClipPathOperation.h" | 15 #include "core/style/ClipPathOperation.h" |
| 16 #include "platform/graphics/paint/ClipPathDisplayItem.h" | |
| 12 #include "platform/graphics/paint/ClipPathRecorder.h" | 17 #include "platform/graphics/paint/ClipPathRecorder.h" |
| 18 #include "platform/graphics/paint/DrawingDisplayItem.h" | |
| 19 #include "platform/graphics/paint/PaintController.h" | |
| 20 #include "platform/graphics/paint/PaintRecordBuilder.h" | |
| 13 | 21 |
| 14 namespace blink { | 22 namespace blink { |
| 15 | 23 |
| 16 namespace { | 24 namespace { |
| 17 | 25 |
| 26 class SVGClipExpansionCycleHelper { | |
| 27 public: | |
| 28 SVGClipExpansionCycleHelper(LayoutSVGResourceClipper& clip) : m_clip(clip) { | |
| 29 clip.beginClipExpansion(); | |
| 30 } | |
| 31 ~SVGClipExpansionCycleHelper() { m_clip.endClipExpansion(); } | |
| 32 | |
| 33 private: | |
| 34 LayoutSVGResourceClipper& m_clip; | |
| 35 }; | |
| 36 | |
| 18 LayoutSVGResourceClipper* resolveElementReference( | 37 LayoutSVGResourceClipper* resolveElementReference( |
| 19 const LayoutObject& layoutObject, | 38 const LayoutObject& layoutObject, |
| 20 const ReferenceClipPathOperation& referenceClipPathOperation) { | 39 const ReferenceClipPathOperation& referenceClipPathOperation) { |
| 21 if (layoutObject.isSVGChild()) { | 40 if (layoutObject.isSVGChild()) { |
| 22 // The reference will have been resolved in | 41 // The reference will have been resolved in |
| 23 // SVGResources::buildResources, so we can just use the LayoutObject's | 42 // SVGResources::buildResources, so we can just use the LayoutObject's |
| 24 // SVGResources. | 43 // SVGResources. |
| 25 SVGResources* resources = | 44 SVGResources* resources = |
| 26 SVGResourcesCache::cachedResourcesForLayoutObject(&layoutObject); | 45 SVGResourcesCache::cachedResourcesForLayoutObject(&layoutObject); |
| 27 return resources ? resources->clipper() : nullptr; | 46 return resources ? resources->clipper() : nullptr; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 50 m_layoutObject(layoutObject), | 69 m_layoutObject(layoutObject), |
| 51 m_context(context) { | 70 m_context(context) { |
| 52 if (clipPathOperation.type() == ClipPathOperation::SHAPE) { | 71 if (clipPathOperation.type() == ClipPathOperation::SHAPE) { |
| 53 ShapeClipPathOperation& shape = toShapeClipPathOperation(clipPathOperation); | 72 ShapeClipPathOperation& shape = toShapeClipPathOperation(clipPathOperation); |
| 54 if (!shape.isValid()) | 73 if (!shape.isValid()) |
| 55 return; | 74 return; |
| 56 m_clipPathRecorder.emplace(context, layoutObject, shape.path(referenceBox)); | 75 m_clipPathRecorder.emplace(context, layoutObject, shape.path(referenceBox)); |
| 57 m_clipperState = ClipperState::AppliedPath; | 76 m_clipperState = ClipperState::AppliedPath; |
| 58 } else { | 77 } else { |
| 59 DCHECK_EQ(clipPathOperation.type(), ClipPathOperation::REFERENCE); | 78 DCHECK_EQ(clipPathOperation.type(), ClipPathOperation::REFERENCE); |
| 60 LayoutSVGResourceClipper* clipper = resolveElementReference( | 79 m_resourceClipper = resolveElementReference( |
| 61 layoutObject, toReferenceClipPathOperation(clipPathOperation)); | 80 layoutObject, toReferenceClipPathOperation(clipPathOperation)); |
| 62 if (!clipper) | 81 if (!m_resourceClipper) |
| 63 return; | 82 return; |
| 64 // Compute the (conservative) bounds of the clip-path. | 83 // Compute the (conservative) bounds of the clip-path. |
| 65 FloatRect clipPathBounds = clipper->resourceBoundingBox(referenceBox); | 84 FloatRect clipPathBounds = |
| 85 m_resourceClipper->resourceBoundingBox(referenceBox); | |
| 66 // When SVG applies the clip, and the coordinate system is "userspace on | 86 // When SVG applies the clip, and the coordinate system is "userspace on |
| 67 // use", we must explicitly pass in the offset to have the clip paint in the | 87 // use", we must explicitly pass in the offset to have the clip paint in the |
| 68 // correct location. When the coordinate system is "object bounding box" the | 88 // correct location. When the coordinate system is "object bounding box" the |
| 69 // offset should already be accounted for in the reference box. | 89 // offset should already be accounted for in the reference box. |
| 70 FloatPoint originTranslation; | 90 FloatPoint originTranslation; |
| 71 if (clipper->clipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) { | 91 if (m_resourceClipper->clipPathUnits() == |
| 92 SVGUnitTypes::kSvgUnitTypeUserspaceonuse) { | |
| 72 clipPathBounds.moveBy(origin); | 93 clipPathBounds.moveBy(origin); |
| 73 originTranslation = origin; | 94 originTranslation = origin; |
| 74 } | 95 } |
| 75 if (!SVGClipPainter(*clipper).prepareEffect( | 96 if (!prepareEffect(referenceBox, clipPathBounds, originTranslation)) { |
| 76 layoutObject, referenceBox, clipPathBounds, originTranslation, | 97 // Indicate there is no cleanup to do. |
| 77 context, m_clipperState)) | 98 m_resourceClipper = nullptr; |
| 78 return; | 99 return; |
| 79 m_resourceClipper = clipper; | 100 } |
| 80 } | 101 } |
| 81 } | 102 } |
| 82 | 103 |
| 83 ClipPathClipper::~ClipPathClipper() { | 104 ClipPathClipper::~ClipPathClipper() { |
| 84 if (m_resourceClipper) | 105 if (m_resourceClipper) |
| 85 SVGClipPainter(*m_resourceClipper) | 106 finishEffect(); |
| 86 .finishEffect(m_layoutObject, m_context, m_clipperState); | 107 } |
| 108 | |
| 109 bool ClipPathClipper::prepareEffect(const FloatRect& targetBoundingBox, | |
| 110 const FloatRect& visualRect, | |
| 111 const FloatPoint& layerPositionOffset) { | |
| 112 DCHECK_EQ(m_clipperState, ClipperState::NotApplied); | |
| 113 SECURITY_DCHECK(!m_resourceClipper->needsLayout()); | |
| 114 | |
| 115 m_resourceClipper->clearInvalidationMask(); | |
| 116 | |
| 117 if (m_resourceClipper->hasCycle()) | |
| 118 return false; | |
| 119 | |
| 120 SVGClipExpansionCycleHelper inClipExpansionChange(*m_resourceClipper); | |
| 121 | |
| 122 AffineTransform animatedLocalTransform = | |
| 123 toSVGClipPathElement(m_resourceClipper->element()) | |
| 124 ->calculateTransform(SVGElement::IncludeMotionTransform); | |
| 125 // When drawing a clip for non-SVG elements, the CTM does not include the zoom | |
| 126 // factor. In this case, we need to apply the zoom scale explicitly - but | |
| 127 // only for clips with userSpaceOnUse units (the zoom is accounted for | |
| 128 // objectBoundingBox-resolved lengths). | |
| 129 if (!m_layoutObject.isSVG() && m_resourceClipper->clipPathUnits() == | |
| 130 SVGUnitTypes::kSvgUnitTypeUserspaceonuse) { | |
| 131 DCHECK(m_resourceClipper->style()); | |
| 132 animatedLocalTransform.scale(m_resourceClipper->style()->effectiveZoom()); | |
| 133 } | |
| 134 | |
| 135 // First, try to apply the clip as a clipPath. | |
| 136 Path clipPath; | |
| 137 if (m_resourceClipper->asPath(animatedLocalTransform, targetBoundingBox, | |
| 138 clipPath)) { | |
| 139 AffineTransform positionTransform; | |
| 140 positionTransform.translate(layerPositionOffset.x(), | |
| 141 layerPositionOffset.y()); | |
| 142 clipPath.transform(positionTransform); | |
| 143 m_clipperState = ClipperState::AppliedPath; | |
| 144 m_context.getPaintController().createAndAppend<BeginClipPathDisplayItem>( | |
| 145 m_layoutObject, clipPath); | |
| 146 return true; | |
| 147 } | |
| 148 | |
| 149 // Fall back to masking. | |
| 150 m_clipperState = ClipperState::AppliedMask; | |
| 151 | |
| 152 // Begin compositing the clip mask. | |
| 153 m_maskClipRecorder.emplace(m_context, m_layoutObject, SkBlendMode::kSrcOver, | |
| 154 1, &visualRect); | |
| 155 { | |
| 156 if (!drawClipAsMask(targetBoundingBox, visualRect, animatedLocalTransform, | |
| 157 layerPositionOffset)) { | |
| 158 // End the clip mask's compositor. | |
| 159 m_maskClipRecorder.reset(); | |
| 160 return false; | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 // Masked content layer start. | |
| 165 m_maskContentRecorder.emplace(m_context, m_layoutObject, SkBlendMode::kSrcIn, | |
| 166 1, &visualRect); | |
| 167 | |
| 168 return true; | |
| 169 } | |
| 170 | |
| 171 bool ClipPathClipper::drawClipAsMask(const FloatRect& targetBoundingBox, | |
| 172 const FloatRect& targetVisualRect, | |
| 173 const AffineTransform& localTransform, | |
| 174 const FloatPoint& layerPositionOffset) { | |
| 175 if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible( | |
| 176 m_context, m_layoutObject, DisplayItem::kSVGClip)) | |
| 177 return true; | |
| 178 | |
| 179 PaintRecordBuilder maskBuilder(targetVisualRect, nullptr, &m_context); | |
| 180 GraphicsContext& maskContext = maskBuilder.context(); | |
| 181 { | |
| 182 TransformRecorder recorder(maskContext, m_layoutObject, localTransform); | |
| 183 | |
| 184 // Apply any clip-path clipping this clipPath (nested shape/clipPath.) | |
| 185 Optional<ClipPathClipper> nestedClipPathClipper; | |
| 186 if (ClipPathOperation* clipPathOperation = | |
| 187 m_resourceClipper->styleRef().clipPath()) { | |
| 188 nestedClipPathClipper.emplace(maskContext, *clipPathOperation, | |
| 189 *m_resourceClipper, targetBoundingBox, | |
| 190 layerPositionOffset); | |
| 191 } | |
| 192 | |
| 193 { | |
| 194 AffineTransform contentTransform; | |
| 195 if (m_resourceClipper->clipPathUnits() == | |
| 196 SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { | |
| 197 contentTransform.translate(targetBoundingBox.x(), | |
| 198 targetBoundingBox.y()); | |
| 199 contentTransform.scaleNonUniform(targetBoundingBox.width(), | |
| 200 targetBoundingBox.height()); | |
| 201 } | |
| 202 SubtreeContentTransformScope contentTransformScope(contentTransform); | |
| 203 | |
| 204 TransformRecorder contentTransformRecorder(maskContext, m_layoutObject, | |
| 205 contentTransform); | |
| 206 maskContext.getPaintController().createAndAppend<DrawingDisplayItem>( | |
| 207 m_layoutObject, DisplayItem::kSVGClip, | |
| 208 m_resourceClipper->createPaintRecord()); | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 LayoutObjectDrawingRecorder drawingRecorder( | |
| 213 m_context, m_layoutObject, DisplayItem::kSVGClip, targetVisualRect); | |
| 214 sk_sp<PaintRecord> maskPaintRecord = maskBuilder.endRecording(); | |
| 215 m_context.drawRecord(maskPaintRecord.get()); | |
| 216 return true; | |
| 217 } | |
| 218 | |
| 219 void ClipPathClipper::finishEffect() { | |
| 220 switch (m_clipperState) { | |
| 221 case ClipperState::AppliedPath: | |
| 222 // Path-only clipping, no layers to restore but we need to emit an end to | |
| 223 // the clip path display item. | |
| 224 m_context.getPaintController().endItem<EndClipPathDisplayItem>( | |
| 225 m_layoutObject); | |
| 226 break; | |
| 227 case ClipperState::AppliedMask: | |
| 228 // Transfer content -> clip mask (SrcIn) | |
| 229 m_maskContentRecorder.reset(); | |
| 230 | |
| 231 // Transfer clip mask -> bg (SrcOver) | |
| 232 m_maskClipRecorder.reset(); | |
| 233 break; | |
| 234 case ClipperState::NotApplied: | |
|
danakj
2017/03/07 15:46:57
I made one little change here since I noticed it.
| |
| 235 NOTREACHED(); | |
| 236 break; | |
| 237 } | |
| 87 } | 238 } |
| 88 | 239 |
| 89 } // namespace blink | 240 } // namespace blink |
| OLD | NEW |