OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "config.h" | 5 #include "config.h" |
6 #include "core/paint/BackgroundImageGeometry.h" | 6 #include "core/paint/BackgroundImageGeometry.h" |
7 | 7 |
| 8 #include "core/layout/LayoutBox.h" |
| 9 #include "core/layout/LayoutBoxModelObject.h" |
| 10 #include "core/layout/LayoutView.h" |
| 11 #include "core/layout/compositing/CompositedDeprecatedPaintLayerMapping.h" |
| 12 #include "core/paint/DeprecatedPaintLayer.h" |
| 13 #include "platform/LayoutUnit.h" |
| 14 #include "platform/geometry/LayoutRect.h" |
| 15 |
8 namespace blink { | 16 namespace blink { |
9 | 17 |
| 18 namespace { |
| 19 |
| 20 inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSiz
e& positioningAreaSize) |
| 21 { |
| 22 tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tile
Size.width().ceil() : tileSize.width().floor()); |
| 23 tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? t
ileSize.height().ceil() : tileSize.height().floor()); |
| 24 } |
| 25 |
| 26 // Return the amount of space to leave between image tiles for the background-re
peat: space property. |
| 27 inline int getSpaceBetweenImageTiles(int areaSize, int tileSize) |
| 28 { |
| 29 int numberOfTiles = areaSize / tileSize; |
| 30 int space = -1; |
| 31 |
| 32 if (numberOfTiles > 1) { |
| 33 // Spec doesn't specify rounding, so use the same method as for backgrou
nd-repeat: round. |
| 34 space = lroundf((areaSize - numberOfTiles * tileSize) / (float)(numberOf
Tiles - 1)); |
| 35 } |
| 36 |
| 37 return space; |
| 38 } |
| 39 |
| 40 bool fixedBackgroundPaintsInLocalCoordinates(const LayoutObject& obj, const Glob
alPaintFlags globalPaintFlags) |
| 41 { |
| 42 if (!obj.isLayoutView()) |
| 43 return false; |
| 44 |
| 45 const LayoutView& view = toLayoutView(obj); |
| 46 |
| 47 if (globalPaintFlags & GlobalPaintFlattenCompositingLayers) |
| 48 return false; |
| 49 |
| 50 DeprecatedPaintLayer* rootLayer = view.layer(); |
| 51 if (!rootLayer || rootLayer->compositingState() == NotComposited) |
| 52 return false; |
| 53 |
| 54 return rootLayer->compositedDeprecatedPaintLayerMapping()->backgroundLayerPa
intsFixedRootBackground(); |
| 55 } |
| 56 |
| 57 IntSize calculateFillTileSize(const LayoutBoxModelObject& obj, const FillLayer&
fillLayer, const IntSize& positioningAreaSize) |
| 58 { |
| 59 StyleImage* image = fillLayer.image(); |
| 60 EFillSizeType type = fillLayer.size().type; |
| 61 |
| 62 IntSize imageIntrinsicSize = obj.calculateImageIntrinsicDimensions(image, po
sitioningAreaSize, LayoutBoxModelObject::ScaleByEffectiveZoom); |
| 63 imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScal
eFactor()); |
| 64 switch (type) { |
| 65 case SizeLength: { |
| 66 LayoutSize tileSize(positioningAreaSize); |
| 67 |
| 68 Length layerWidth = fillLayer.size().size.width(); |
| 69 Length layerHeight = fillLayer.size().size.height(); |
| 70 |
| 71 if (layerWidth.isFixed()) |
| 72 tileSize.setWidth(layerWidth.value()); |
| 73 else if (layerWidth.hasPercent()) |
| 74 tileSize.setWidth(valueForLength(layerWidth, positioningAreaSize.wid
th())); |
| 75 |
| 76 if (layerHeight.isFixed()) |
| 77 tileSize.setHeight(layerHeight.value()); |
| 78 else if (layerHeight.hasPercent()) |
| 79 tileSize.setHeight(valueForLength(layerHeight, positioningAreaSize.h
eight())); |
| 80 |
| 81 applySubPixelHeuristicForTileSize(tileSize, positioningAreaSize); |
| 82 |
| 83 // If one of the values is auto we have to use the appropriate |
| 84 // scale to maintain our aspect ratio. |
| 85 if (layerWidth.isAuto() && !layerHeight.isAuto()) { |
| 86 if (imageIntrinsicSize.height()) { |
| 87 LayoutUnit adjustedWidth = imageIntrinsicSize.width() * tileSize
.height() / imageIntrinsicSize.height(); |
| 88 if (imageIntrinsicSize.width() >= 1 && adjustedWidth < 1) |
| 89 adjustedWidth = 1; |
| 90 tileSize.setWidth(adjustedWidth); |
| 91 } |
| 92 } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { |
| 93 if (imageIntrinsicSize.width()) { |
| 94 LayoutUnit adjustedHeight = imageIntrinsicSize.height() * tileSi
ze.width() / imageIntrinsicSize.width(); |
| 95 if (imageIntrinsicSize.height() >= 1 && adjustedHeight < 1) |
| 96 adjustedHeight = 1; |
| 97 tileSize.setHeight(adjustedHeight); |
| 98 } |
| 99 } else if (layerWidth.isAuto() && layerHeight.isAuto()) { |
| 100 // If both width and height are auto, use the image's intrinsic size
. |
| 101 tileSize = LayoutSize(imageIntrinsicSize); |
| 102 } |
| 103 |
| 104 tileSize.clampNegativeToZero(); |
| 105 return flooredIntSize(tileSize); |
| 106 } |
| 107 case SizeNone: { |
| 108 // If both values are 'auto' then the intrinsic width and/or height of t
he image should be used, if any. |
| 109 if (!imageIntrinsicSize.isEmpty()) |
| 110 return imageIntrinsicSize; |
| 111 |
| 112 // If the image has neither an intrinsic width nor an intrinsic height,
its size is determined as for 'contain'. |
| 113 type = Contain; |
| 114 } |
| 115 case Contain: |
| 116 case Cover: { |
| 117 float horizontalScaleFactor = imageIntrinsicSize.width() |
| 118 ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSi
ze.width() : 1; |
| 119 float verticalScaleFactor = imageIntrinsicSize.height() |
| 120 ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicS
ize.height() : 1; |
| 121 float scaleFactor = type == Contain ? std::min(horizontalScaleFactor, ve
rticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor); |
| 122 return IntSize(std::max(1l, lround(imageIntrinsicSize.width() * scaleFac
tor)), std::max(1l, lround(imageIntrinsicSize.height() * scaleFactor))); |
| 123 } |
| 124 } |
| 125 |
| 126 ASSERT_NOT_REACHED(); |
| 127 return IntSize(); |
| 128 } |
| 129 |
| 130 } // anonymous namespace |
| 131 |
10 void BackgroundImageGeometry::setNoRepeatX(int xOffset) | 132 void BackgroundImageGeometry::setNoRepeatX(int xOffset) |
11 { | 133 { |
12 m_destRect.move(std::max(xOffset, 0), 0); | 134 m_destRect.move(std::max(xOffset, 0), 0); |
13 m_phase.setX(-std::min(xOffset, 0)); | 135 m_phase.setX(-std::min(xOffset, 0)); |
14 m_destRect.setWidth(m_tileSize.width() + std::min(xOffset, 0)); | 136 m_destRect.setWidth(m_tileSize.width() + std::min(xOffset, 0)); |
15 } | 137 } |
| 138 |
16 void BackgroundImageGeometry::setNoRepeatY(int yOffset) | 139 void BackgroundImageGeometry::setNoRepeatY(int yOffset) |
17 { | 140 { |
18 m_destRect.move(0, std::max(yOffset, 0)); | 141 m_destRect.move(0, std::max(yOffset, 0)); |
19 m_phase.setY(-std::min(yOffset, 0)); | 142 m_phase.setY(-std::min(yOffset, 0)); |
20 m_destRect.setHeight(m_tileSize.height() + std::min(yOffset, 0)); | 143 m_destRect.setHeight(m_tileSize.height() + std::min(yOffset, 0)); |
21 } | 144 } |
22 | 145 |
23 void BackgroundImageGeometry::useFixedAttachment(const IntPoint& attachmentPoint
) | 146 void BackgroundImageGeometry::useFixedAttachment(const IntPoint& attachmentPoint
) |
24 { | 147 { |
25 IntPoint alignedPoint = attachmentPoint; | 148 IntPoint alignedPoint = attachmentPoint; |
26 m_phase.move(std::max(alignedPoint.x() - m_destRect.x(), 0), std::max(aligne
dPoint.y() - m_destRect.y(), 0)); | 149 m_phase.move(std::max(alignedPoint.x() - m_destRect.x(), 0), std::max(aligne
dPoint.y() - m_destRect.y(), 0)); |
27 } | 150 } |
28 | 151 |
29 void BackgroundImageGeometry::clip(const IntRect& clipRect) | 152 void BackgroundImageGeometry::clip(const IntRect& clipRect) |
30 { | 153 { |
31 m_destRect.intersect(clipRect); | 154 m_destRect.intersect(clipRect); |
32 } | 155 } |
33 | 156 |
| 157 void BackgroundImageGeometry::calculate(const LayoutBoxModelObject& obj, const L
ayoutBoxModelObject* paintContainer, |
| 158 const GlobalPaintFlags globalPaintFlags, const FillLayer& fillLayer, const L
ayoutRect& paintRect, |
| 159 LayoutObject* backgroundObject) |
| 160 { |
| 161 LayoutUnit left = 0; |
| 162 LayoutUnit top = 0; |
| 163 IntSize positioningAreaSize; |
| 164 IntRect snappedPaintRect = pixelSnappedIntRect(paintRect); |
| 165 bool isLayoutView = obj.isLayoutView(); |
| 166 const LayoutBox* rootBox = nullptr; |
| 167 if (isLayoutView) { |
| 168 // It is only possible reach here when root element has a box. |
| 169 Element* documentElement = obj.document().documentElement(); |
| 170 ASSERT(documentElement); |
| 171 ASSERT(documentElement->layoutObject()); |
| 172 ASSERT(documentElement->layoutObject()->isBox()); |
| 173 rootBox = toLayoutBox(documentElement->layoutObject()); |
| 174 } |
| 175 const LayoutBoxModelObject& positioningBox = isLayoutView ? static_cast<cons
t LayoutBoxModelObject&>(*rootBox) : obj; |
| 176 |
| 177 // Determine the background positioning area and set destRect to the backgro
und painting area. |
| 178 // destRect will be adjusted later if the background is non-repeating. |
| 179 // FIXME: transforms spec says that fixed backgrounds behave like scroll ins
ide transforms. |
| 180 bool fixedAttachment = fillLayer.attachment() == FixedBackgroundAttachment; |
| 181 |
| 182 if (RuntimeEnabledFeatures::fastMobileScrollingEnabled()) { |
| 183 // As a side effect of an optimization to blit on scroll, we do not hono
r the CSS |
| 184 // property "background-attachment: fixed" because it may result in rend
ering |
| 185 // artifacts. Note, these artifacts only appear if we are blitting on sc
roll of |
| 186 // a page that has fixed background images. |
| 187 fixedAttachment = false; |
| 188 } |
| 189 |
| 190 if (!fixedAttachment) { |
| 191 setDestRect(snappedPaintRect); |
| 192 |
| 193 LayoutUnit right = 0; |
| 194 LayoutUnit bottom = 0; |
| 195 // Scroll and Local. |
| 196 if (fillLayer.origin() != BorderFillBox) { |
| 197 left = positioningBox.borderLeft(); |
| 198 right = positioningBox.borderRight(); |
| 199 top = positioningBox.borderTop(); |
| 200 bottom = positioningBox.borderBottom(); |
| 201 if (fillLayer.origin() == ContentFillBox) { |
| 202 left += positioningBox.paddingLeft(); |
| 203 right += positioningBox.paddingRight(); |
| 204 top += positioningBox.paddingTop(); |
| 205 bottom += positioningBox.paddingBottom(); |
| 206 } |
| 207 } |
| 208 |
| 209 if (isLayoutView) { |
| 210 // The background of the box generated by the root element covers th
e entire canvas and will |
| 211 // be painted by the view object, but the we should still use the ro
ot element box for |
| 212 // positioning. |
| 213 positioningAreaSize = pixelSnappedIntSize(rootBox->size() - LayoutSi
ze(left + right, top + bottom), rootBox->location()); |
| 214 // The input paint rect is specified in root element local coordinat
e (i.e. a transform |
| 215 // is applied on the context for painting), and is expanded to cover
the whole canvas. |
| 216 // Since left/top is relative to the paint rect, we need to offset t
hem back. |
| 217 left -= paintRect.x(); |
| 218 top -= paintRect.y(); |
| 219 } else { |
| 220 positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutS
ize(left + right, top + bottom), paintRect.location()); |
| 221 } |
| 222 } else { |
| 223 setHasNonLocalGeometry(); |
| 224 |
| 225 IntRect viewportRect = pixelSnappedIntRect(obj.viewRect()); |
| 226 if (fixedBackgroundPaintsInLocalCoordinates(obj, globalPaintFlags)) |
| 227 viewportRect.setLocation(IntPoint()); |
| 228 else if (FrameView* frameView = obj.view()->frameView()) |
| 229 viewportRect.setLocation(frameView->scrollPosition()); |
| 230 |
| 231 if (paintContainer) { |
| 232 IntPoint absoluteContainerOffset = roundedIntPoint(paintContainer->l
ocalToAbsolute(FloatPoint())); |
| 233 viewportRect.moveBy(-absoluteContainerOffset); |
| 234 } |
| 235 |
| 236 setDestRect(viewportRect); |
| 237 positioningAreaSize = destRect().size(); |
| 238 } |
| 239 |
| 240 const LayoutObject* clientForBackgroundImage = backgroundObject ? background
Object : &obj; |
| 241 IntSize fillTileSize = calculateFillTileSize(positioningBox, fillLayer, posi
tioningAreaSize); |
| 242 fillLayer.image()->setContainerSizeForLayoutObject(clientForBackgroundImage,
fillTileSize, obj.style()->effectiveZoom()); |
| 243 setTileSize(fillTileSize); |
| 244 |
| 245 EFillRepeat backgroundRepeatX = fillLayer.repeatX(); |
| 246 EFillRepeat backgroundRepeatY = fillLayer.repeatY(); |
| 247 int availableWidth = positioningAreaSize.width() - tileSize().width(); |
| 248 int availableHeight = positioningAreaSize.height() - tileSize().height(); |
| 249 |
| 250 LayoutUnit computedXPosition = roundedMinimumValueForLength(fillLayer.xPosit
ion(), availableWidth); |
| 251 if (backgroundRepeatX == RoundFill && positioningAreaSize.width() > 0 && fil
lTileSize.width() > 0) { |
| 252 long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.width() /
fillTileSize.width())); |
| 253 |
| 254 // Round tile size per css3-background spec. |
| 255 fillTileSize.setWidth(lroundf(positioningAreaSize.width() / (float)nrTil
es)); |
| 256 |
| 257 // Maintain aspect ratio if background-size: auto is set |
| 258 if (fillLayer.size().size.height().isAuto() && backgroundRepeatY != Roun
dFill) { |
| 259 fillTileSize.setHeight(fillTileSize.height() * positioningAreaSize.w
idth() / (nrTiles * fillTileSize.width())); |
| 260 } |
| 261 |
| 262 setTileSize(fillTileSize); |
| 263 setPhaseX(tileSize().width() ? tileSize().width() - roundToInt(computedX
Position + left) % tileSize().width() : 0); |
| 264 setSpaceSize(IntSize()); |
| 265 } |
| 266 |
| 267 LayoutUnit computedYPosition = roundedMinimumValueForLength(fillLayer.yPosit
ion(), availableHeight); |
| 268 if (backgroundRepeatY == RoundFill && positioningAreaSize.height() > 0 && fi
llTileSize.height() > 0) { |
| 269 long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.height()
/ fillTileSize.height())); |
| 270 |
| 271 // Round tile size per css3-background spec. |
| 272 fillTileSize.setHeight(lroundf(positioningAreaSize.height() / (float)nrT
iles)); |
| 273 |
| 274 // Maintain aspect ratio if background-size: auto is set |
| 275 if (fillLayer.size().size.width().isAuto() && backgroundRepeatX != Round
Fill) { |
| 276 fillTileSize.setWidth(fillTileSize.width() * positioningAreaSize.hei
ght() / (nrTiles * fillTileSize.height())); |
| 277 } |
| 278 |
| 279 setTileSize(fillTileSize); |
| 280 setPhaseY(tileSize().height() ? tileSize().height() - roundToInt(compute
dYPosition + top) % tileSize().height() : 0); |
| 281 setSpaceSize(IntSize()); |
| 282 } |
| 283 |
| 284 if (backgroundRepeatX == RepeatFill) { |
| 285 int xOffset = fillLayer.backgroundXOrigin() == RightEdge ? availableWidt
h - computedXPosition : computedXPosition; |
| 286 setPhaseX(tileSize().width() ? tileSize().width() - roundToInt(xOffset +
left) % tileSize().width() : 0); |
| 287 setSpaceSize(IntSize()); |
| 288 } else if (backgroundRepeatX == SpaceFill && fillTileSize.width() > 0) { |
| 289 int space = getSpaceBetweenImageTiles(positioningAreaSize.width(), tileS
ize().width()); |
| 290 int actualWidth = tileSize().width() + space; |
| 291 |
| 292 if (space >= 0) { |
| 293 computedXPosition = roundedMinimumValueForLength(Length(), available
Width); |
| 294 setSpaceSize(IntSize(space, 0)); |
| 295 setPhaseX(actualWidth ? actualWidth - roundToInt(computedXPosition +
left) % actualWidth : 0); |
| 296 } else { |
| 297 backgroundRepeatX = NoRepeatFill; |
| 298 } |
| 299 } |
| 300 if (backgroundRepeatX == NoRepeatFill) { |
| 301 int xOffset = fillLayer.backgroundXOrigin() == RightEdge ? availableWidt
h - computedXPosition : computedXPosition; |
| 302 setNoRepeatX(left + xOffset); |
| 303 setSpaceSize(IntSize(0, spaceSize().height())); |
| 304 } |
| 305 |
| 306 if (backgroundRepeatY == RepeatFill) { |
| 307 int yOffset = fillLayer.backgroundYOrigin() == BottomEdge ? availableHei
ght - computedYPosition : computedYPosition; |
| 308 setPhaseY(tileSize().height() ? tileSize().height() - roundToInt(yOffset
+ top) % tileSize().height() : 0); |
| 309 setSpaceSize(IntSize(spaceSize().width(), 0)); |
| 310 } else if (backgroundRepeatY == SpaceFill && fillTileSize.height() > 0) { |
| 311 int space = getSpaceBetweenImageTiles(positioningAreaSize.height(), tile
Size().height()); |
| 312 int actualHeight = tileSize().height() + space; |
| 313 |
| 314 if (space >= 0) { |
| 315 computedYPosition = roundedMinimumValueForLength(Length(), available
Height); |
| 316 setSpaceSize(IntSize(spaceSize().width(), space)); |
| 317 setPhaseY(actualHeight ? actualHeight - roundToInt(computedYPosition
+ top) % actualHeight : 0); |
| 318 } else { |
| 319 backgroundRepeatY = NoRepeatFill; |
| 320 } |
| 321 } |
| 322 if (backgroundRepeatY == NoRepeatFill) { |
| 323 int yOffset = fillLayer.backgroundYOrigin() == BottomEdge ? availableHei
ght - computedYPosition : computedYPosition; |
| 324 setNoRepeatY(top + yOffset); |
| 325 setSpaceSize(IntSize(spaceSize().width(), 0)); |
| 326 } |
| 327 |
| 328 if (fixedAttachment) |
| 329 useFixedAttachment(snappedPaintRect.location()); |
| 330 |
| 331 clip(snappedPaintRect); |
| 332 } |
| 333 |
34 } // namespace blink | 334 } // namespace blink |
OLD | NEW |